Compare commits

...

57 Commits

Author SHA1 Message Date
Ayke van Laethem fa5df4f524 main: version 0.5.0 2019-04-20 20:17:41 +02:00
Ayke van Laethem 09db7ead50 cgo: better error message when using an undefined CGo function pointer 2019-04-20 10:18:38 +02:00
Ayke van Laethem 21a4c14e86 cgo: implement C.struct_ types
These types (called elaborated types in C) are used as part of linked
lists, among others.

This is part an extra feature (to be compatible with CGo C.struct_
types) and part a bugfix: linked lists would result in endless recursion
leading to a stack overflow.
2019-04-20 10:18:38 +02:00
Ayke van Laethem b716cf1afd loader/libclang: fix CGo-related crash
Sometimes when a GC happens while processing a C fragment with libclang,
a pointer-typed integer with value 0x1 ends up on the Go stack and the
GC will trip over it. This commit changes the offending struct type to
be uintptr_t instead of void*.

See https://go-review.googlesource.com/c/go/+/66332 for a similar
change.
2019-04-20 10:07:26 +02:00
Johan Brandhorst 586023b45d src/examples/wasm: Show both methods supported
Adds another example showing the simple case
of executing main, adds a README explaining how
everything fits together and how to execute the compiled
code in the browser. Include a minimal webserver for
local testing.
2019-04-19 17:46:46 +02:00
Ron Evans a00a51e70e examples: add microbit blink example
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-19 15:30:12 +02:00
Ron Evans 745b5dfb81 examples: remove colorlamp example that is better suited for the TinyGo Zoo (and already in it)
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-18 19:03:13 +02:00
Ayke van Laethem 9c50d47b82 BUILDING.md: update to commands included in Makefile
This should make it easier to make a working build of LLVM.
2019-04-17 23:15:37 +02:00
Ayke van Laethem 6c63a0d6e7 Makefile: build static binaries only
This replaces the older way which just does the following:

    go install .

and

    go test -v .

Instead, `make` and `make test` will now build TinyGo statically linked
against LLVM, so that `go install` and `go test -v` should be used
manually.
2019-04-17 23:15:37 +02:00
Ayke van Laethem 2a0a7722f9 compiler: lower func values to switch + direct call
This has several advantages, among them:
  - Many passes (heap-to-stack, dead arg elimination, inlining) do not
    work with function pointer calls. Making them normal function calls
    improves their effectiveness.
  - Goroutine lowering to LLVM coroutines does not currently support
    function pointers. By eliminating function pointers, coroutine
    lowering gets support for them for free.
    This is especially useful for WebAssembly.
Because of the second point, this work is currently only enabled for the
WebAssembly target.
2019-04-17 23:12:59 +02:00
Ayke van Laethem 1460877c28 compiler: refactor func value handling
This commit refactors all func value handling into a new file, which
makes it easier to comprehend it and extend it later.
2019-04-17 23:12:59 +02:00
Ayke van Laethem 0739775719 compiler: extract inline asm builtins into separate file
This commit refactors the compiler a bit to have all inline assembly in
a separate file.
2019-04-17 23:12:59 +02:00
Ron Evans 8d3b5a58d1 machine/atsamd21: correct pad/pin handling when using both UART and USBCDC interfaces at same time
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-17 22:50:08 +02:00
Ayke van Laethem 0d2a3ce532 Makefile: check whether LLVM was build before making a static binary 2019-04-17 19:13:42 +02:00
Ayke van Laethem d2b3a5486c cgo: implement C unions
Unions are somewhat hard to implement in Go because they are not a
native type. But it is actually possible with some compiler magic.

This commit inserts a special "C union" field at the start of a struct
to indicate that it is a union. As such a field cannot be written
directly in Go, this is a useful to distinguish structs and unions.
2019-04-17 11:56:40 +02:00
Ayke van Laethem 536086988c cgo: implement Go arrays (constant arrays in C) 2019-04-17 11:56:40 +02:00
Ayke van Laethem 684543b7f1 cgo: implement struct types
Not complete: packed structs are treated as regular structs.
2019-04-17 11:56:40 +02:00
Ayke van Laethem bd8e47af80 cgo: make libclang code thread-safe 2019-04-17 11:56:40 +02:00
Ayke van Laethem 5b34713d41 main: fix linker script includes when running outside TINYGOROOT
This commit adds the TinyGo root directory (`TINYGOROOT`) to the linker
script `-L` search path, so that linker scripts can be found when
running `tinygo` outside of the TinyGo root.

This was already working before when using an external linker by setting
the working directory, but this is not possible when using the internal
linker. However, by adding the root directory to the linker search path
(`-L`), it can now find these linker scripts.

fixes #265
2019-04-17 09:21:15 +02:00
Ron Evans f1aea13c51 tools: remove Makefile tasks that are redundant thanks to TinyGo improvements
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-16 14:08:31 +02:00
Ayke van Laethem 7bcabe53ca compiler: fix interface lowering pass
It was removing some globals that still had uses left.
2019-04-15 19:43:17 +02:00
Ayke van Laethem 7de3d4be2b all: support interface asserts in interp
This adds support for the math/rand package.
2019-04-13 20:55:56 +02:00
Ayke van Laethem 02ecab833f all: check formatting on CI 2019-04-13 19:58:58 +02:00
Yusuke Mitsuki 1322f404a6 stm32: add support for the STM32F4Discovery
Signed-off-by: Yusuke Mitsuki <mickey.happygolucky@gmail.com>
2019-04-13 13:32:05 +02:00
Ayke van Laethem 315cd4059f main: drop the dependency on llvm-ar
The ar file format is pretty simple and can be implemented by using a Go
library. Use that instead of calling out to llvm-ar.

There are a few limitations to the used package, but that doesn't seem
to matter for our use case (linking compiler-rt for use with ld.lld):

  * no index is created
  * long filenames are truncated
  * no support for archives bigger than 4GB
2019-04-11 11:53:58 +02:00
Ayke van Laethem 078dd9ff52 cgo: improve diagnostics
This makes CGo-emitted diagnostics very similar to regular errors
emitted while parsing/typechecking a package.
It's not complete, but after introducing some errors in testdata/cgo,
this is the resulting output:

    # ./testdata/cgo/
    testdata/cgo/main.h:18:11: error: a parameter list without types is only allowed in a function definition
    testdata/cgo/main.go:5:10: note: in file included from testdata/cgo/main.go!cgo.c:2:
    testdata/cgo/main.go:6:19: error: expected identifier or '('

Previously, this was the output:

    /home/ayke/src/github.com/tinygo-org/tinygo/testdata/cgo/main.h:18:11: error: a parameter list without types is only allowed in a function definition
    cgo-fake.c:3:19: error: expected identifier or '('
    # ./testdata/cgo/
    cgo: libclang cannot parse fragment
2019-04-11 10:11:09 +02:00
Ayke van Laethem e5029c63d1 compiler: optimize ptrtoint+add+inttoptr pattern
This pattern is often used in some runtime intrinsics (especially the
ones related to slices) to do pointer arithmetic with unsafe.Pointer and
uintptr because Go does not support pointer arithmetic.

Recognizing this pattern and replacing it with a gep instruction
improves code size in various tests.
2019-04-11 09:16:10 +02:00
Ayke van Laethem dcffbc49c4 compiler: add param attrs to memmove and memcpy
Add nocapture, readonly, and writeonly to runtime.memmove and
runtime.memcpy where appropriate. This teaches LLVM some more
optimizations it may perform, leading to reduced .text size in some
cases.
2019-04-11 09:16:10 +02:00
Ayke van Laethem 6a2a587dff compiler: fix MakeSlice bounds check and casting 2019-04-10 20:21:33 +02:00
Ayke van Laethem 3a76a49ddf compiler: fix int casting to use the source signedness
Previously, when casting an integer to a bigger integer, the destination
signedness was used. This is problematic when casting a negative int16
to uint32, for example, because it would cause zero-extension.
2019-04-10 20:21:33 +02:00
Ayke van Laethem 81a1114ee5 compiler: truncate string slice indices if necessary
This didn't trigger on most platforms but does trigger on AVR where
almost all slice operations on strings are with integers that are bigger
than uintptr.
2019-04-10 20:21:33 +02:00
Ayke van Laethem 30e192e7e8 compiler: let the default 'low' slice bound be a uintptr 2019-04-10 20:21:33 +02:00
Ayke van Laethem a965882a34 compiler: support i16 uintptr in cap builtin 2019-04-10 20:21:33 +02:00
Ayke van Laethem f967c6919a interp: fix segmentation fault in some builds
A call to .IsConstant() also returns true for constant globals, not just
constant expressions. Do an extra check that we're really operating on a
constant expression.
2019-04-10 18:29:22 +02:00
Ayke van Laethem 2e926789f5 circleci: do macOS builds here instead of on Travis CI
This provides several advantages. Among others:
  * Much faster and hopefully more reliable.
  * Good caching support to store LLVM builds.
  * Building and testing of release-ready artifacts.
2019-04-08 09:13:16 +02:00
Ayke van Laethem 38c3d0852e compiler: implement casting named structs and pointers to them 2019-04-06 08:32:28 +02:00
Ron Evans 85f2ef40f8 machine/itsybitsy-m0: set pins and pin mode mapping for i2c0 bus
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-05 15:25:11 +02:00
Ron Evans ae4ead8690 machine/circuitplay: correct pin and pin mode mapping for both i2c0 (external) and i2c1 (internal) buses
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-05 14:31:26 +02:00
Ayke van Laethem ebebdd5651 circleci: build LLVM statically 2019-04-05 13:30:38 +02:00
Ayke van Laethem 1778d92858 Makefile: automatically build LLVM when needed 2019-04-05 13:30:38 +02:00
Ayke van Laethem 86f8778748 darwin: use custom syscall pkg that uses libsystem
Go 1.12 switched to using libSystem.dylib for system calls, because
Apple recommends against doing direct system calls that Go 1.11 and
earlier did. For more information, see:
  https://github.com/golang/go/issues/17490
  https://developer.apple.com/library/archive/qa/qa1118/_index.html

While the old syscall package was relatively easy to support in TinyGo
(just implement syscall.Syscall*), this got a whole lot harder with Go
1.12 as all syscalls now go through CGo magic to call the underlying
libSystem functions. Therefore, this commit overrides the stdlib syscall
package with a custom package that performs calls with libc (libSystem).
This may be useful not just for darwin but for other platforms as well
that do not place the stable ABI at the syscall boundary like Linux but
at the libc boundary.

Only a very minimal part of the syscall package has been implemented, to
get the tests to pass. More calls can easily be added in the future.
2019-04-05 09:53:51 +02:00
Ayke van Laethem 2523772b5d arm: use the lld linker
LLD version 8 has added support for armv6m:
    https://reviews.llvm.org/D55555
This means we can use LLD instead of arm-none-eabi-ld, eliminating our
dependency on GNU binutils.

There are small differences in code size, but never more than a few
bytes.
2019-04-04 12:50:15 +02:00
Ayke van Laethem 31d57fd3d1 main: use wasm-ld instead of wasm-ld-8 on macOS
This commit does a few things:
  * remove the -8 suffix on macOS, where it is not necessary
  * add smoke tests for compiling wasm files on Linux and macOS
2019-04-04 12:50:15 +02:00
Ayke van Laethem d653088cbe compiler: fix escapes due to nil checks
Some tests get bigger, most get smaller. However, all tested driver
examples get smaller in size showing that this is a good change in the
real world.
2019-04-04 09:32:30 +02:00
Ayke van Laethem cd8471acae all: support Go 1.12 2019-04-03 21:32:29 +02:00
Ayke van Laethem b64fc5484a runtime: implement memmove intrinsic
This should fix the following issue:
https://github.com/tinygo-org/tinygo/issues/252
2019-04-03 17:04:13 +02:00
Ayke van Laethem 38f8cf7bee compiler: imporove escape analysis to allow icmp
The icmp instruction is often used in nil checks, so this instruction
happens very frequently now that TinyGo automatically inserts nil checks
everywhere. Escape analysis would conservatively mark such pointers as
escaping, which they obviously don't.
This commit improves escape analysis to allow icmp instructions.
2019-04-03 16:38:08 +02:00
Ayke van Laethem f484dddbc2 circleci: don't install the llvm package
This is not necessary anymore since d6c2d6e301: llvm-ar has been
replaced with llvm-ar-8.
2019-04-02 19:10:48 +02:00
Ayke van Laethem a2d0f79be3 all: pretend to be linux/arm in baremetal targets
So far, we've pretended to be js/wasm in baremetal targets to make the
stdlib happy. Unfortunately, this has various problems because
syscall/js (a dependency of many stdlib packages) thinks it can do JS
calls, and emulating them gets quite hard with all changes to the
syscall/js packages in Go 1.12.

This commit does a few things:
  * It lets baremetal targets pretend to be linux/arm instead of
    js/wasm.
  * It lets the loader only select particular packages from the src
    overlay, instead of inserting them just before GOROOT. This makes it
    possible to pick which packages to overlay for a given target.
  * It adds a baremetal-only syscall package that stubs out almost all
    syscalls.
2019-03-23 22:58:26 +01:00
Ayke van Laethem 792274e86f runtime/wasm: provide dummy runtime.setEventHandler
Event handlers aren't supported yet. This commit gets syscall/js to
compile for the WebAssembly target.
2019-03-23 16:16:19 +01:00
Ayke van Laethem 06aa88abfb reflect: add stubs for Value.MapRange
This object was added in Go 1.12 and is required by the fmt package.
2019-03-23 16:16:19 +01:00
Ayke van Laethem ad7297a539 all: implement trivial select statements
Implement two trivial uses of the select statement.

Always blocking:
    select {}

No-op:
    select {
    default:
    }

Go 1.12 added a `select {}` instruction to syscall/js, so this is needed
for Go 1.12 support. More complete support for select will be added in
the future.
2019-03-23 16:16:19 +01:00
Ayke van Laethem 4d82f42d61 runtime: add runtime.nanotime
This function returns the current timestamp, or 0 at compile time.

runtime.nanotime is used at package initialization by the time package
starting with Go 1.12.
2019-03-23 16:16:19 +01:00
Ayke van Laethem 9c41011e17 all: switch to LLVM 8 2019-03-22 22:55:11 +01:00
Ayke van Laethem 5569cd1b6b main: version 0.4.1 2019-03-15 13:37:37 +01:00
Ron Evans d6c2d6e301 main: use OS specific name for llvm-ar-7 tool to ensure that llvm7 toolchain works as expected
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-03-15 12:46:03 +01:00
Ayke van Laethem a466dd8f2b main: include .data section in .hex file
The function extracting the firmware image for .hex and .bin files
wasn't working correctly: it only extracted the .text segment and not
the .data segment.
This commit fixes this issue, so that it behaves (hopefully) just like
objcopy -O{ihex|binary}.

Another small change is that the formatting of the .hex file was made
more like the output of objcopy: no entry addres (old Intel CPU
holdover) and 16 bytes of data on each line.
2019-03-11 17:10:16 +01:00
99 changed files with 3819 additions and 943 deletions
+200 -16
View File
@@ -18,14 +18,12 @@ commands:
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
sudo apt-get update
sudo apt-get install \
llvm \
python3 \
llvm<<parameters.llvm>>-dev \
clang<<parameters.llvm>> \
libclang<<parameters.llvm>>-dev \
lld<<parameters.llvm>> \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
@@ -50,36 +48,39 @@ commands:
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure --vendor-only
smoketest:
steps:
- smoketest-no-avr
- run: tinygo build -size short -o test.elf -target=arduino examples/blinky1
- run: tinygo build -size short -o test.elf -target=digispark examples/blinky1
smoketest-no-avr:
steps:
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
- run: tinygo build -size short -o blinky2 examples/blinky2
- run: tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
- run: tinygo build -size short -o test.elf -target=pca10040 examples/test
- run: tinygo build -size short -o test.elf -target=microbit examples/echo
- run: tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
- run: tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
- run: tinygo build -size short -o test.elf -target=bluepill examples/blinky1
- run: tinygo build -size short -o test.elf -target=arduino examples/blinky1
- run: tinygo build -size short -o test.elf -target=digispark examples/blinky1
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky1
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky2
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
jobs:
test-llvm7-go111:
docker:
- image: circleci/golang:1.11
working_directory: /go/src/github.com/tinygo-org/tinygo
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
test-linux:
parameters:
llvm:
type: string
steps:
- checkout
- submodules
- apt-dependencies:
llvm: "-7"
llvm: <<parameters.llvm>>
- install-node
- restore_cache:
keys:
@@ -87,7 +88,7 @@ jobs:
- go-cache-{{ checksum "Gopkg.lock" }}
- dep
- run: go install .
- run: make test
- run: go test -v
- run: make gen-device -j4
- smoketest
- save_cache:
@@ -95,8 +96,191 @@ jobs:
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- run: make fmt-check
build-linux:
steps:
- checkout
- submodules
- run:
name: "Install apt dependencies"
command: |
sudo apt-get install \
libtinfo-dev \
python3 \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
- install-node
- restore_cache:
keys:
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- restore_cache:
keys:
- llvm-source-8-v2
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-8-v2
paths:
- llvm
- restore_cache:
keys:
- llvm-build-8-v2
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
sudo apt-get install cmake clang ninja-build
# make build faster
export CC=clang
export CXX=clang++
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
# build!
make llvm-build
fi
- save_cache:
key: llvm-build-8-v2
paths:
llvm-build
- run:
name: "Create LLVM symlinks"
command: |
ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8
- dep
- run:
name: "Test TinyGo"
command: make test
- run:
name: "Build TinyGo release"
command: |
make release -j3
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
- store_artifacts:
path: /tmp/tinygo.linux-amd64.tar.gz
- save_cache:
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- run:
name: "Extract release tarball"
command: |
mkdir -p ~/lib
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
tinygo version
- smoketest
build-macos:
steps:
- checkout
- submodules
- run:
name: "Install dependencies"
command: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install go dep qemu
- restore_cache:
keys:
- llvm-source-8-macos-v2
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-8-macos-v2
paths:
- llvm
- restore_cache:
keys:
- llvm-build-8-macos-v2
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# build!
make llvm-build
fi
- save_cache:
key: llvm-build-8-macos-v2
paths:
llvm-build
- run:
name: "Create LLVM symlinks"
command: |
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
- run:
name: "Install Go dependencies"
command: dep ensure --vendor-only
- run:
name: "Test TinyGo"
command: make test
- run:
name: "Build TinyGo release"
command: |
make release -j3
cp -p build/release.tar.gz /tmp/tinygo.darwin-amd64.tar.gz
- store_artifacts:
path: /tmp/tinygo.darwin-amd64.tar.gz
- run:
name: "Extract release tarball"
command: |
mkdir -p ~/lib
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
tinygo version
- smoketest-no-avr
jobs:
test-llvm8-go111:
docker:
- image: circleci/golang:1.11
working_directory: /go/src/github.com/tinygo-org/tinygo
steps:
- test-linux:
llvm: "-8"
test-llvm8-go112:
docker:
- image: circleci/golang:1.12
working_directory: /go/src/github.com/tinygo-org/tinygo
steps:
- test-linux:
llvm: "-8"
build-linux:
docker:
- image: circleci/golang:1.12
working_directory: /go/src/github.com/tinygo-org/tinygo
steps:
- build-linux
build-macos:
macos:
xcode: "10.1.0"
working_directory: ~/go/src/github.com/tinygo-org/tinygo
steps:
- build-macos
workflows:
test-all:
jobs:
- test-llvm7-go111
- test-llvm8-go111
- test-llvm8-go112
- build-linux
- build-macos
+1 -1
View File
@@ -13,4 +13,4 @@
[submodule "lib/compiler-rt"]
path = lib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt.git
branch = release_70
branch = release_80
-40
View File
@@ -1,40 +0,0 @@
language: go
matrix:
include:
- os: osx
go: "1.11"
env: PATH="/usr/local/opt/llvm/bin:$PATH"
before_install:
- mkdir -p /Users/travis/gopath/bin
addons:
homebrew:
update: true
taps: ArmMbed/homebrew-formulae
packages:
- llvm@7
- qemu
- arm-none-eabi-gcc
install:
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
- dep ensure --vendor-only
script:
- go install github.com/tinygo-org/tinygo
- go test -v .
- make gen-device
- tinygo build -size short -o blinky1.nrf.elf -target=pca10040 examples/blinky1
- tinygo build -size short -o blinky2.nrf.elf -target=pca10040 examples/blinky2
- tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
- tinygo build -size short -o test.nrf.elf -target=pca10040 examples/test
- tinygo build -size short -o blinky1.nrf51.elf -target=microbit examples/echo
- tinygo build -size short -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
- tinygo build -size short -o blinky1.nrf51d.elf -target=pca10031 examples/blinky1
- tinygo build -size short -o blinky1.stm32.elf -target=bluepill examples/blinky1
- tinygo build -size short -o blinky1.reel.elf -target=reelboard examples/blinky1
- tinygo build -size short -o blinky2.reel.elf -target=reelboard examples/blinky2
- tinygo build -size short -o blinky1.pca10056.elf -target=pca10056 examples/blinky1
- tinygo build -size short -o blinky2.pca10056.elf -target=pca10056 examples/blinky2
- tinygo build -size short -o blinky1.samd21.elf -target=itsybitsy-m0 examples/blinky1
+26 -60
View File
@@ -2,9 +2,9 @@
TinyGo depends on LLVM and libclang, which are both big C++ libraries. It can
also optionally use a built-in lld to ease cross compiling. There are two ways
these can be linked: dynamically and statically. The default is dynamic linking
because it is fast and works almost out of the box on Debian-based systems with
the right libraries installed.
these can be linked: dynamically and statically. An install with `go install` is
dynamic linking because it is fast and works almost out of the box on
Debian-based systems with the right packages installed.
This guide describes how to statically link TinyGo against LLVM, libclang and
lld so that the binary can be easily moved between systems. It also shows how to
@@ -18,81 +18,49 @@ build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.11+)
* [dep](https://golang.github.io/dep/)
* Standard build tools (gcc/clang)
* git or subversion
* git
* CMake
* [Ninja](https://ninja-build.org/) or make (preferably Ninja)
* [Ninja](https://ninja-build.org/)
The rest of this guide assumes you're running Linux, but it should be equivalent
on a different system like Mac.
## Download the source
The first step is to get the source code. Place it in some directory, assuming
`$HOME/src` here, but you can pick a different one of course:
The first step is to download the TinyGo sources. Then, inside the directory,
perform these steps:
git clone -b release_70 https://github.com/llvm-mirror/llvm.git $HOME/src/llvm
git clone -b release_70 https://github.com/llvm-mirror/clang.git $HOME/src/llvm/tools/clang
git clone -b release_70 https://github.com/llvm-mirror/lld.git $HOME/src/llvm/tools/lld
go get -d github.com/tinygo-org/tinygo
cd $HOME/go/src/github.com/tinygo-org/tinygo
dep ensure -vendor-only # download dependencies
dep ensure -vendor-only # download Go dependencies
make llvm-source # download LLVM
Note that Clang and LLD must be placed inside the tools subdirectory of LLVM to
be automatically built with the rest of the system.
You can also store LLVM outside of the TinyGo root directory by setting the
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
covered by this guide.
## Build LLVM, Clang, LLD
Building LLVM is quite easy compared to some other software packages. However,
the default configuration is _not_ optimized for distribution. It is optimized
for development, meaning that binaries produce accurate error messages at the
cost of huge binaries and slow compiles.
Before configuring, you may want to set the following environment variables to
speed up the build. Most Linux distributions ship with GCC as the default
compiler, but Clang is significantly faster and uses much less memory while
producing binaries that are about as fast.
Before starting the build, you may want to set the following environment
variables to speed up the build. Most Linux distributions ship with GCC as the
default compiler, but Clang is significantly faster and uses much less memory
while producing binaries that are about as fast.
export CC=clang
export CXX=clang++
Make a build directory. LLVM requires out-of-tree builds:
The Makefile includes a default configuration that is good for most users. It
builds a release version of LLVM (optimized, no asserts) and includes all
targets supported by TinyGo:
mkdir $HOME/src/llvm-build
cd $HOME/src/llvm-build
Configure LLVM with CMake:
cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;WebAssembly" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON
You can also choose a different build system than Ninja, but Ninja is fast.
There are various options you can tune here, but the options given above are
preferable for releases. Here is what they do:
* `LLVM_TARGETS_TO_BUILD` and `LLVM_EXPERIMENTAL_TARGETS_TO_BUILD`: the
targets that are natively supported by the LLVM code generators. The targets
listed here are the ones supported by TinyGo. Note that LLVM is a cross
compiler by default, unlike some other compilers.
* `CMAKE_BUILD_TYPE`: the default is Debug, which produces large inefficient
binaries that are easy to debug. We want small and fast binaries.
* `LLVM_ENABLE_ASSERTIONS`: the default is ON, which greatly slows down LLVM
and is only really useful during development. Disable them here.
* `LIBCLANG_BUILD_STATIC`: unlike LLVM, libclang is built as a shared library
by default. We want a static library for easy distribution.
Now build it:
ninja # or make, if you choose make in the previous step
make llvm-build
This can take over an hour depending on the speed of your system.
## Build TinyGo
Now that you have a working version of LLVM, build TinyGo using it. You need to
specify the directories to the LLVM build directory and to the Clang and LLD source.
The last step of course is to build TinyGo itself. This can again be done with
make:
cd $HOME/go/src/github.com/tinygo-org/tinygo
make static LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
make
## Verify TinyGo
@@ -109,14 +77,12 @@ The result should not contain libclang or libLLVM.
## Make a release tarball
Now that we have a working static build, it's time to make a release tarball.
This is just a slight change from the command to build TinyGo:
Now that we have a working static build, it's time to make a release tarball:
cd $HOME/go/src/github.com/tinygo-org/tinygo
make release LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
make release
The release tarball is stored in build/release.tar.gz, and can be extracted with
the following command:
the following command (for example in ~/lib):
tar -xvf path/to/release.tar.gz
+41
View File
@@ -1,3 +1,44 @@
0.5.0
---
- **compiler driver**
- use `wasm-ld` instead of `wasm-ld-8` on macOS
- drop dependency on `llvm-ar`
- fix linker script includes when running outside `TINYGOROOT`
- **compiler**
- switch to LLVM 8
- add support for the Go 1.12 standard library (Go 1.11 is still supported)
- work around lack of escape analysis due to nil checks
- implement casting named structs and pointers to them
- fix int casting to use the source signedness
- fix some bugs around `make([]T, …)` with uncommon index types
- some other optimizations
- support interface asserts in interp for "math/rand" support
- resolve all func value targets at compile time (wasm-only at the moment)
- **cgo**
- improve diagnostics
- implement C `struct`, `union`, and arrays
- fix CGo-related crash in libclang
- implement `C.struct_` types
- **targets**
- all baremetal: pretend to be linux/arm instead of js/wasm
- `avr`: improve `uintptr` support
- `cortexm`: implement memmove intrinsic generated by LLVM
- `cortexm`: use the lld linker instead of `arm-none-eabi-ld`
- `darwin`: use custom syscall package that links to libSystem.dylib
- `microbit`: add blink example
- `samd21`: support I2C1
- `samd21`: machine/atsamd21: correct pad/pin handling when using both UART
and USBCDC interfaces at same time
- `stm32f4discovery`: add support for this board
- `wasm`: support async func values
- `wasm`: improve documentation and add extra example
0.4.1
---
- **compiler**
- fix `objcopy` replacement to include the .data section in the firmware image
- use `llvm-ar-7` on Linux to fix the Docker image
0.4.0
---
- **compiler**
+7 -7
View File
@@ -1,10 +1,10 @@
# TinyGo base stage just installs LLVM 7 and the TinyGo compiler itself.
# TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
FROM golang:latest AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-7-dev libclang-7-dev git
apt-get install -y llvm-8-dev libclang-8-dev git
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
@@ -25,9 +25,9 @@ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y libllvm7 lld-7
apt-get install -y libllvm8 lld-8
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
FROM tinygo-base AS tinygo-avr
@@ -59,7 +59,7 @@ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 && \
apt-get install -y apt-utils python3 make clang-8 && \
make gen-device-nrf && make gen-device-stm32 && \
apt-get remove -y python3 make && \
apt-get autoremove -y && \
@@ -74,7 +74,7 @@ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 binutils-avr gcc-avr avr-libc && \
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
make gen-device && \
apt-get remove -y python3 make && \
apt-get autoremove -y && \
Generated
+13 -4
View File
@@ -1,6 +1,14 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
name = "github.com/blakesmith/ar"
packages = ["."]
pruneopts = "UT"
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
[[projects]]
branch = "master"
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
@@ -19,20 +27,21 @@
"go/types/typeutil",
]
pruneopts = "UT"
revision = "3744606dbb67b99c60d3f11cb10bd3f9e6dad472"
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
[[projects]]
branch = "master"
digest = "1:a6a25fd8906c74978f1ed811bc9fd3422da8093be863b458874b02a782b6ae3e"
branch = "llvm8"
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
name = "tinygo.org/x/go-llvm"
packages = ["."]
pruneopts = "UT"
revision = "d5f730401f5069618b275a5241c6417eb0c38a65"
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/blakesmith/ar",
"github.com/marcinbor85/gohex",
"golang.org/x/tools/go/ast/astutil",
"golang.org/x/tools/go/ssa",
+1 -1
View File
@@ -1,5 +1,5 @@
[[constraint]]
branch = "master"
branch = "llvm8"
name = "tinygo.org/x/go-llvm"
[[constraint]]
+41 -84
View File
@@ -3,48 +3,24 @@
all: tinygo
tinygo: build/tinygo
.PHONY: all tinygo static run-test run-blinky run-blinky2 clean fmt gen-device gen-device-nrf gen-device-avr
.PHONY: all tinygo build/tinygo test llvm-build llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
TARGET ?= unix
ifeq ($(TARGET),unix)
# Regular *nix system.
else ifeq ($(TARGET),pca10040)
# PCA10040: nRF52832 development board
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),microbit)
# BBC micro:bit
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),reelboard)
# reel board
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),bluepill)
# "blue pill" development board
# See: https://wiki.stm32duino.com/index.php?title=Blue_Pill
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),arduino)
OBJCOPY = avr-objcopy
TGOFLAGS += -target $(TARGET)
else
$(error Unknown target)
endif
# Default build and source directories, as created by `make llvm-build`.
LLVM_BUILDDIR ?= llvm-build
CLANG_SRC ?= llvm/tools/clang
LLD_SRC ?= llvm/tools/lld
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
CLANG_LIBS = -Wl,--start-group $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor -Wl,--end-group -lstdc++
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
endif
LLD_LIBS = -Wl,--start-group -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML -Wl,--end-group
CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor $(END_GROUP) -lstdc++
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
# For static linking.
@@ -53,40 +29,14 @@ CGO_CXXFLAGS=-std=c++11
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
run-test: build/test
./build/test
run-blinky: run-blinky2
run-blinky2: build/blinky2
./build/blinky2
ifeq ($(TARGET),pca10040)
flash-%: build/%.hex
nrfjprog -f nrf52 --sectorerase --program $< --reset
else ifeq ($(TARGET),microbit)
flash-%: build/%.hex
openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -c 'program $< reset exit'
else ifeq ($(TARGET),reelboard)
flash-%: build/%.hex
openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -c 'program $< reset exit'
else ifeq ($(TARGET),arduino)
flash-%: build/%.hex
avrdude -c arduino -p atmega328p -P /dev/ttyACM0 -U flash:w:$<
else ifeq ($(TARGET),bluepill)
flash-%: build/%.hex
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c 'program $< reset exit'
endif
clean:
@rm -rf build
FMT_PATHS = ./*.go compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
fmt:
@go fmt . ./compiler ./interp ./loader ./ir ./src/device/arm ./src/examples/* ./src/machine ./src/os ./src/runtime ./src/sync
@go fmt ./testdata/*.go
test:
@go test -v .
@gofmt -l -w $(FMT_PATHS)
fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
@@ -107,15 +57,34 @@ gen-device-stm32:
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
go fmt ./src/device/stm32
# Get LLVM sources.
llvm/README.txt:
git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
llvm/tools/clang/README.txt:
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
llvm/tools/lld/README.md:
git clone -b release_80 https://github.com/llvm-mirror/lld.git llvm/tools/lld
llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.md
# Configure LLVM.
llvm-build/build.ninja: llvm-source
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON
# Build LLVM.
llvm-build: llvm-build/build.ninja
cd llvm-build; ninja
# Build the Go compiler.
build/tinygo:
@mkdir -p build
go build -o build/tinygo .
static:
@if [ ! -f llvm-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
release: static gen-device
test:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
release: build/tinygo gen-device
@mkdir -p build/release/tinygo/bin
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
@@ -136,15 +105,3 @@ release: static gen-device
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
tar -czf build/release.tar.gz -C build/release tinygo
# Binary that can run on the host.
build/%: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
# ELF file that can run on a microcontroller.
build/%.elf: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
# Convert executable to Intel hex file (for flashing).
build/%.hex: build/%.elf
$(OBJCOPY) -O ihex $^ $@
-1
View File
@@ -1,6 +1,5 @@
# TinyGo - Go compiler for small places
[![Travis CI](https://travis-ci.com/tinygo-org/tinygo.svg?branch=dev)](https://travis-ci.com/tinygo-org/tinygo)
[![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
+42 -6
View File
@@ -1,11 +1,16 @@
package main
import (
"errors"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/blakesmith/ar"
)
// These are the GENERIC_SOURCES according to CMakeList.txt.
@@ -246,14 +251,45 @@ func compileBuiltins(target string, callback func(path string) error) error {
}
// Put all builtins in an archive to link as a static library.
// Note: this does not create a symbol index, but ld.lld doesn't seem to
// care.
arpath := filepath.Join(dir, "librt.a")
cmd := exec.Command(commands["ar"], append([]string{"cr", arpath}, objs...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
err = cmd.Run()
arfile, err := os.Create(arpath)
if err != nil {
return &commandError{"failed to make static library", arpath, err}
return err
}
defer arfile.Close()
arwriter := ar.NewWriter(arfile)
err = arwriter.WriteGlobalHeader()
if err != nil {
return &os.PathError{"write ar header", arpath, err}
}
for _, objpath := range objs {
name := filepath.Base(objpath)
objfile, err := os.Open(objpath)
if err != nil {
return err
}
defer objfile.Close()
st, err := objfile.Stat()
if err != nil {
return err
}
arwriter.WriteHeader(&ar.Header{
Name: name,
ModTime: time.Unix(0, 0),
Uid: 0,
Gid: 0,
Mode: 0644,
Size: st.Size(),
})
n, err := io.Copy(arwriter, objfile)
if err != nil {
return err
}
if n != st.Size() {
return errors.New("file modified during ar creation: " + arpath)
}
}
// Give the caller the resulting file. The callback must copy the file,
+10
View File
@@ -0,0 +1,10 @@
// +build !darwin
package main
// commands used by the compilation process might have different file names on Linux than those used on macOS.
var commands = map[string]string{
"clang": "clang-8",
"ld.lld": "ld.lld-8",
"wasm-ld": "wasm-ld-8",
}
+10
View File
@@ -0,0 +1,10 @@
// +build darwin
package main
// commands used by the compilation process might have different file names on macOS than those used on Linux.
var commands = map[string]string{
"clang": "clang-8",
"ld.lld": "ld.lld",
"wasm-ld": "wasm-ld",
}
+22 -2
View File
@@ -51,6 +51,11 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
// sure it is within bounds.
//
// This function is both used for slicing a slice (low and high have their
// normal meaning) and for creating a new slice, where 'capacity' means the
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases.
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
if frame.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
@@ -115,8 +120,23 @@ func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Compare against nil.
nilptr := llvm.ConstPointerNull(ptr.Type())
isnil := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
var isnil llvm.Value
if ptr.Type().PointerAddressSpace() == 0 {
// Do the nil check using the isnil builtin, which marks the parameter
// as nocapture.
// The reason it has to go through a builtin, is that a regular icmp
// instruction may capture the pointer in LLVM semantics, see
// https://reviews.llvm.org/D60047 for details. Pointer capturing
// unfortunately breaks escape analysis, so we use this trick to let the
// functionattr pass know that this pointer doesn't really escape.
ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "")
isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "")
} else {
// Do the nil check using a regular icmp. This can happen with function
// pointers on AVR, which don't benefit from escape analysis anyway.
nilptr := llvm.ConstPointerNull(ptr.Type())
isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
}
c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
+270 -336
View File
@@ -9,7 +9,6 @@ import (
"go/types"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
@@ -162,11 +161,11 @@ func (c *Compiler) selectGC() string {
func (c *Compiler) Compile(mainPath string) error {
// Prefix the GOPATH with the system GOROOT, as GOROOT is already set to
// the TinyGo root.
gopath := c.GOPATH
if gopath == "" {
gopath = runtime.GOROOT()
overlayGopath := c.GOPATH
if overlayGopath == "" {
overlayGopath = runtime.GOROOT()
} else {
gopath = runtime.GOROOT() + string(filepath.ListSeparator) + gopath
overlayGopath = runtime.GOROOT() + string(filepath.ListSeparator) + overlayGopath
}
wd, err := os.Getwd()
@@ -177,13 +176,40 @@ func (c *Compiler) Compile(mainPath string) error {
Build: &build.Context{
GOARCH: c.GOARCH,
GOOS: c.GOOS,
GOROOT: c.RootDir,
GOPATH: gopath,
GOROOT: runtime.GOROOT(),
GOPATH: c.GOPATH,
CgoEnabled: true,
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
},
OverlayBuild: &build.Context{
GOARCH: c.GOARCH,
GOOS: c.GOOS,
GOROOT: c.RootDir,
GOPATH: overlayGopath,
CgoEnabled: true,
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
},
ShouldOverlay: func(path string) bool {
switch path {
case "machine", "os", "reflect", "runtime", "sync":
return true
default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
return true
} else if path == "syscall" {
for _, tag := range c.BuildTags {
if tag == "avr" || tag == "cortexm" || tag == "darwin" {
return true
}
}
}
}
return false
},
TypeChecker: types.Config{
Sizes: &StdSizes{
IntSize: int64(c.targetData.TypeAllocSize(c.intType)),
@@ -344,12 +370,32 @@ func (c *Compiler) Compile(mainPath string) error {
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage)
// Load some attributes
getAttr := func(attrName string) llvm.Attribute {
attrKind := llvm.AttributeKindID(attrName)
return c.ctx.CreateEnumAttribute(attrKind, 0)
}
nocapture := getAttr("nocapture")
writeonly := getAttr("writeonly")
readonly := getAttr("readonly")
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
// returns values that are never null and never alias to an existing value.
for _, name := range []string{"noalias", "nonnull"} {
attrKind := llvm.AttributeKindID(name)
attr := c.ctx.CreateEnumAttribute(attrKind, 0)
c.mod.NamedFunction("runtime.alloc").AddAttributeAtIndex(0, attr)
for _, attrName := range []string{"noalias", "nonnull"} {
c.mod.NamedFunction("runtime.alloc").AddAttributeAtIndex(0, getAttr(attrName))
}
// See emitNilCheck in asserts.go.
c.mod.NamedFunction("runtime.isnil").AddAttributeAtIndex(1, nocapture)
// Memory copy operations do not capture pointers, even though some weird
// pointer arithmetic is happening in the Go implementation.
for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} {
fn := c.mod.NamedFunction(fnName)
fn.AddAttributeAtIndex(1, nocapture)
fn.AddAttributeAtIndex(1, writeonly)
fn.AddAttributeAtIndex(2, nocapture)
fn.AddAttributeAtIndex(2, readonly)
}
// see: https://reviews.llvm.org/D18355
@@ -427,58 +473,8 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) {
return llvm.Type{}, err
}
return llvm.PointerType(ptrTo, 0), nil
case *types.Signature: // function pointer
// return value
var err error
var returnType llvm.Type
if typ.Results().Len() == 0 {
returnType = c.ctx.VoidType()
} else if typ.Results().Len() == 1 {
returnType, err = c.getLLVMType(typ.Results().At(0).Type())
if err != nil {
return llvm.Type{}, err
}
} else {
// Multiple return values. Put them together in a struct.
members := make([]llvm.Type, typ.Results().Len())
for i := 0; i < typ.Results().Len(); i++ {
returnType, err := c.getLLVMType(typ.Results().At(i).Type())
if err != nil {
return llvm.Type{}, err
}
members[i] = returnType
}
returnType = c.ctx.StructType(members, false)
}
// param values
var paramTypes []llvm.Type
if typ.Recv() != nil {
recv, err := c.getLLVMType(typ.Recv().Type())
if err != nil {
return llvm.Type{}, err
}
if recv.StructName() == "runtime._interface" {
// This is a call on an interface, not a concrete type.
// The receiver is not an interface, but a i8* type.
recv = c.i8ptrType
}
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
}
params := typ.Params()
for i := 0; i < params.Len(); i++ {
subType, err := c.getLLVMType(params.At(i).Type())
if err != nil {
return llvm.Type{}, err
}
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
}
// make a closure type (with a function pointer type inside):
// {context, funcptr}
paramTypes = append(paramTypes, c.i8ptrType) // context
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
ptr := llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace)
ptr = c.ctx.StructType([]llvm.Type{c.i8ptrType, ptr}, false)
return ptr, nil
case *types.Signature: // function value
return c.getFuncType(typ)
case *types.Slice:
elemType, err := c.getLLVMType(typ.Elem())
if err != nil {
@@ -499,6 +495,43 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) {
}
members[i] = member
}
if len(members) > 2 && typ.Field(0).Name() == "C union" {
// Not a normal struct but a C union emitted by cgo.
// Such a field name cannot be entered in regular Go code, this must
// be manually inserted in the AST so this is safe.
maxAlign := 0
maxSize := uint64(0)
mainType := members[0]
for _, member := range members {
align := c.targetData.ABITypeAlignment(member)
size := c.targetData.TypeAllocSize(member)
if align > maxAlign {
maxAlign = align
mainType = member
} else if align == maxAlign && size > maxSize {
maxAlign = align
maxSize = size
mainType = member
} else if size > maxSize {
maxSize = size
}
}
members = []llvm.Type{mainType}
mainTypeSize := c.targetData.TypeAllocSize(mainType)
if mainTypeSize < maxSize {
members = append(members, llvm.ArrayType(c.ctx.Int8Type(), int(maxSize-mainTypeSize)))
}
}
return c.ctx.StructType(members, false), nil
case *types.Tuple:
members := make([]llvm.Type, typ.Len())
for i := 0; i < typ.Len(); i++ {
member, err := c.getLLVMType(typ.At(i).Type())
if err != nil {
return llvm.Type{}, err
}
members[i] = member
}
return c.ctx.StructType(members, false), nil
default:
return llvm.Type{}, errors.New("todo: unknown type: " + goType.String())
@@ -1053,16 +1086,21 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string,
if err != nil {
return llvm.Value{}, err
}
var llvmCap llvm.Value
switch args[0].Type().(type) {
case *types.Chan:
// Channel. Buffered channels haven't been implemented yet so always
// return 0.
return llvm.ConstInt(c.intType, 0, false), nil
llvmCap = llvm.ConstInt(c.intType, 0, false)
case *types.Slice:
return c.builder.CreateExtractValue(value, 2, "cap"), nil
llvmCap = c.builder.CreateExtractValue(value, 2, "cap")
default:
return llvm.Value{}, c.makeError(pos, "todo: cap: unknown type")
}
if c.targetData.TypeAllocSize(llvmCap.Type()) < c.targetData.TypeAllocSize(c.intType) {
llvmCap = c.builder.CreateZExt(llvmCap, c.intType, "len.int")
}
return llvmCap, nil
case "close":
return llvm.Value{}, c.emitChanClose(frame, args[0])
case "complex":
@@ -1253,120 +1291,15 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
// Try to call the function directly for trivially static calls.
if fn := instr.StaticCallee(); fn != nil {
if fn.RelString(nil) == "device/arm.Asm" || fn.RelString(nil) == "device/avr.Asm" {
// Magic function: insert inline assembly instead of calling it.
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false)
asm := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
}
if fn.RelString(nil) == "device/arm.ReadRegister" {
// Magic function: return the given register.
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
}
if strings.HasPrefix(fn.RelString(nil), "device/arm.SVCall") {
// Magic function: inline this call as a SVC instruction.
num, _ := constant.Uint64Val(instr.Args[0].(*ssa.Const).Value)
args := []llvm.Value{}
argTypes := []llvm.Type{}
asm := "svc #" + strconv.FormatUint(num, 10)
constraints := "={r0}"
for i, arg := range instr.Args[1:] {
arg = arg.(*ssa.MakeInterface).X
if i == 0 {
constraints += ",0"
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
}
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
// Implement the ARM calling convention by marking r1-r3 as
// clobbered. r0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{r1},~{r2},~{r3}"
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
return c.builder.CreateCall(target, args, ""), nil
}
if fn.RelString(nil) == "device/arm.AsmFull" || fn.RelString(nil) == "device/avr.AsmFull" {
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
registers := map[string]llvm.Value{}
registerMap := instr.Args[1].(*ssa.MakeMap)
for _, r := range *registerMap.Referrers() {
switch r := r.(type) {
case *ssa.DebugRef:
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.Block() {
return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block")
}
key := constant.StringVal(r.Key.(*ssa.Const).Value)
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
value, err := c.parseExpr(frame, r.Value.(*ssa.MakeInterface).X)
if err != nil {
return llvm.Value{}, err
}
registers[key] = value
case *ssa.Call:
if r.Common() == instr {
break
}
default:
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
}
}
// TODO: handle dollar signs in asm string
registerNumbers := map[string]int{}
var err error
argTypes := []llvm.Type{}
args := []llvm.Value{}
constraints := []string{}
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
// TODO: skip strings like {r4} etc. that look like ARM push/pop
// instructions.
name := s[1 : len(s)-1]
if _, ok := registers[name]; !ok {
if err == nil {
err = c.makeError(instr.Pos(), "unknown register name: "+name)
}
return s
}
if _, ok := registerNumbers[name]; !ok {
registerNumbers[name] = len(registerNumbers)
argTypes = append(argTypes, registers[name].Type())
args = append(args, registers[name])
switch registers[name].Type().TypeKind() {
case llvm.IntegerTypeKind:
constraints = append(constraints, "r")
case llvm.PointerTypeKind:
constraints = append(constraints, "*m")
default:
err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
return s
}
}
return fmt.Sprintf("${%v}", registerNumbers[name])
})
if err != nil {
return llvm.Value{}, err
}
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
return c.builder.CreateCall(target, args, ""), nil
}
switch fn.RelString(nil) {
case "device/arm.ReadRegister":
return c.emitReadRegister(instr.Args)
case "device/arm.Asm", "device/avr.Asm":
return c.emitAsm(instr.Args)
case "device/arm.AsmFull", "device/avr.AsmFull":
return c.emitAsmFull(frame, instr)
case "device/arm.SVCall0", "device/arm.SVCall1", "device/arm.SVCall2", "device/arm.SVCall3", "device/arm.SVCall4":
return c.emitSVCall(frame, instr.Args)
case "syscall.Syscall", "syscall.Syscall6", "syscall.Syscall9":
return c.emitSyscall(frame, instr)
}
@@ -1376,20 +1309,20 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
return llvm.Value{}, c.makeError(instr.Pos(), "undefined function: "+targetFunc.LinkName())
}
var context llvm.Value
// This function call is to a (potential) closure, not a regular
// function. See whether it is a closure and if so, call it as such.
// Else, supply a dummy nil pointer as the last parameter.
if targetFunc.IsExported() {
// don't pass a context parameter
} else if mkClosure, ok := instr.Value.(*ssa.MakeClosure); ok {
// closure is {context, function pointer}
closure, err := c.parseExpr(frame, mkClosure)
switch value := instr.Value.(type) {
case *ssa.Function:
// Regular function call. No context is necessary.
context = llvm.Undef(c.i8ptrType)
case *ssa.MakeClosure:
// A call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue, err := c.parseExpr(frame, value)
if err != nil {
return llvm.Value{}, err
}
context = c.builder.CreateExtractValue(closure, 0, "")
} else {
context = llvm.Undef(c.i8ptrType)
context = c.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
return c.parseFunctionCall(frame, instr.Args, targetFunc.LLVMFn, context, targetFunc.IsExported())
}
@@ -1403,13 +1336,14 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
if err != nil {
return llvm.Value{}, err
}
// 'value' is a closure, not a raw function pointer.
// Extract the function pointer and the context pointer.
// closure: {context, function pointer}
context := c.builder.CreateExtractValue(value, 0, "")
value = c.builder.CreateExtractValue(value, 1, "")
c.emitNilCheck(frame, value, "fpcall")
return c.parseFunctionCall(frame, instr.Args, value, context, false)
// This is a func value, which cannot be called directly. We have to
// extract the function pointer and context first from the func value.
funcPtr, context, err := c.decodeFuncValue(value, instr.Value.Type().(*types.Signature))
if err != nil {
return llvm.Value{}, err
}
c.emitNilCheck(frame, funcPtr, "fpcall")
return c.parseFunctionCall(frame, instr.Args, funcPtr, context, false)
}
}
@@ -1475,21 +1409,41 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
// https://research.swtch.com/interfaces
return c.parseExpr(frame, expr.X)
case *ssa.ChangeType:
// This instruction changes the type, but the underlying value remains
// the same. This is often a no-op, but sometimes we have to change the
// LLVM type as well.
x, err := c.parseExpr(frame, expr.X)
if err != nil {
return llvm.Value{}, err
}
// The only case when we need to bitcast is when casting between named
// struct types, as those are actually different in LLVM. Let's just
// bitcast all struct types for ease of use.
if _, ok := expr.Type().Underlying().(*types.Struct); ok {
llvmType, err := c.getLLVMType(expr.X.Type())
if err != nil {
return llvm.Value{}, err
}
return c.builder.CreateBitCast(x, llvmType, "changetype"), nil
llvmType, err := c.getLLVMType(expr.Type())
if err != nil {
return llvm.Value{}, err
}
if x.Type() == llvmType {
// Different Go type but same LLVM type (for example, named int).
// This is the common case.
return x, nil
}
// Figure out what kind of type we need to cast.
switch llvmType.TypeKind() {
case llvm.StructTypeKind:
// Unfortunately, we can't just bitcast structs. We have to
// actually create a new struct of the correct type and insert the
// values from the previous struct in there.
value := llvm.Undef(llvmType)
for i := 0; i < llvmType.StructElementTypesCount(); i++ {
field := c.builder.CreateExtractValue(x, i, "changetype.field")
value = c.builder.CreateInsertValue(value, field, i, "changetype.struct")
}
return value, nil
case llvm.PointerTypeKind:
// This can happen with pointers to structs. This case is easy:
// simply bitcast the pointer to the destination type.
return c.builder.CreateBitCast(x, llvmType, "changetype.pointer"), nil
default:
return llvm.Value{}, errors.New("todo: unknown ChangeType type: " + expr.X.Type().String())
}
return x, nil
case *ssa.Const:
return c.parseConst(frame.fn.LinkName(), expr)
case *ssa.Convert:
@@ -1510,6 +1464,19 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
if err != nil {
return llvm.Value{}, err
}
if s := expr.X.Type().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
// Extract a field from a CGo union.
// This could be done directly, but as this is a very infrequent
// operation it's much easier to bitcast it through an alloca.
resultType, err := c.getLLVMType(expr.Type())
if err != nil {
return llvm.Value{}, err
}
alloca := c.builder.CreateAlloca(value.Type(), "")
c.builder.CreateStore(value, alloca)
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "")
return c.builder.CreateLoad(bitcast, ""), nil
}
result := c.builder.CreateExtractValue(value, expr.Field, "")
return result, nil
case *ssa.FieldAddr:
@@ -1517,27 +1484,34 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
if err != nil {
return llvm.Value{}, err
}
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(expr.Field), false),
}
// Check for nil pointer before calculating the address, from the spec:
// > For an operand x of type T, the address operation &x generates a
// > pointer of type *T to x. [...] If the evaluation of x would cause a
// > run-time panic, then the evaluation of &x does too.
c.emitNilCheck(frame, val, "gep")
return c.builder.CreateGEP(val, indices, ""), nil
if s := expr.X.Type().(*types.Pointer).Elem().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
// This is not a regular struct but actually an union.
// That simplifies things, as we can just bitcast the pointer to the
// right type.
ptrType, err := c.getLLVMType(expr.Type())
if err != nil {
return llvm.Value{}, nil
}
return c.builder.CreateBitCast(val, ptrType, ""), nil
} else {
// Do a GEP on the pointer to get the field address.
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(expr.Field), false),
}
return c.builder.CreateGEP(val, indices, ""), nil
}
case *ssa.Function:
fn := c.ir.GetFunction(expr)
if fn.IsExported() {
return llvm.Value{}, c.makeError(expr.Pos(), "cannot use an exported function as value")
}
// Create closure for function pointer.
// Closure is: {context, function pointer}
return c.ctx.ConstStruct([]llvm.Value{
llvm.Undef(c.i8ptrType),
fn.LLVMFn,
}, false), nil
return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature)
case *ssa.Global:
value := c.ir.GetGlobal(expr).LLVMGlobal
if value.IsNil() {
@@ -1653,8 +1627,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
case *ssa.MakeChan:
return c.emitMakeChan(expr)
case *ssa.MakeClosure:
// A closure returns a function pointer with context:
// {context, fp}
return c.parseMakeClosure(frame, expr)
case *ssa.MakeInterface:
val, err := c.parseExpr(frame, expr.X)
@@ -1705,37 +1677,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
}
// Bounds checking.
if !frame.fn.IsNoBounds() {
checkFunc := "sliceBoundsCheckMake"
capacityType := c.uintptrType
capacityTypeWidth := capacityType.IntTypeWidth()
if sliceLen.Type().IntTypeWidth() > capacityTypeWidth || sliceCap.Type().IntTypeWidth() > capacityTypeWidth {
// System that is less than 64bit, meaning that the slice make
// params are bigger than uintptr.
checkFunc = "sliceBoundsCheckMake64"
capacityType = c.ctx.Int64Type()
capacityTypeWidth = capacityType.IntTypeWidth()
}
if sliceLen.Type().IntTypeWidth() < capacityTypeWidth {
if expr.Len.Type().(*types.Basic).Info()&types.IsUnsigned != 0 {
sliceLen = c.builder.CreateZExt(sliceLen, capacityType, "")
} else {
sliceLen = c.builder.CreateSExt(sliceLen, capacityType, "")
}
}
if sliceCap.Type().IntTypeWidth() < capacityTypeWidth {
if expr.Cap.Type().(*types.Basic).Info()&types.IsUnsigned != 0 {
sliceCap = c.builder.CreateZExt(sliceCap, capacityType, "")
} else {
sliceCap = c.builder.CreateSExt(sliceCap, capacityType, "")
}
}
maxSliceSize := maxSize
if elemSize != 0 { // avoid divide by zero
maxSliceSize = llvm.ConstSDiv(maxSize, llvm.ConstInt(c.uintptrType, elemSize, false))
}
c.createRuntimeCall(checkFunc, []llvm.Value{sliceLen, sliceCap, maxSliceSize}, "")
}
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, expr.Len.Type().(*types.Basic), expr.Cap.Type().(*types.Basic))
// Allocate the backing array.
// TODO: escape analysis
@@ -1747,9 +1689,15 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
slicePtr := c.createRuntimeCall("alloc", []llvm.Value{sliceSize}, "makeslice.buf")
slicePtr = c.builder.CreateBitCast(slicePtr, llvm.PointerType(llvmElemType, 0), "makeslice.array")
if c.targetData.TypeAllocSize(sliceLen.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
sliceLen = c.builder.CreateTrunc(sliceLen, c.uintptrType, "")
sliceCap = c.builder.CreateTrunc(sliceCap, c.uintptrType, "")
// Extend or truncate if necessary. This is safe as we've already done
// the bounds check.
sliceLen, err = c.parseConvert(expr.Len.Type(), types.Typ[types.Uintptr], sliceLen, expr.Pos())
if err != nil {
return llvm.Value{}, err
}
sliceCap, err = c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
if err != nil {
return llvm.Value{}, err
}
// Create the slice.
@@ -1821,6 +1769,29 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
}
c.builder.CreateStore(zero, it)
return it, nil
case *ssa.Select:
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType, err := c.getLLVMType(expr.Type())
if err != nil {
return llvm.Value{}, err
}
if expr.Blocking {
// Blocks forever:
// select {}
c.createRuntimeCall("deadlockStub", nil, "")
return llvm.Undef(llvmType), nil
} else {
// No-op:
// select {
// default:
// }
retval := llvm.Undef(llvmType)
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
return retval, nil // {-1, false}
}
}
return llvm.Value{}, c.makeError(expr.Pos(), "unimplemented: "+expr.String())
case *ssa.Slice:
if expr.Max != nil {
return llvm.Value{}, c.makeError(expr.Pos(), "todo: full slice expressions (with max): "+expr.Type().String())
@@ -1847,8 +1818,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
}
}
} else {
lowType = types.Typ[types.Int]
low = llvm.ConstInt(c.intType, 0, false)
lowType = types.Typ[types.Uintptr]
low = llvm.ConstInt(c.uintptrType, 0, false)
}
if expr.High != nil {
@@ -1883,6 +1854,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
c.emitSliceBoundsCheck(frame, llvmLen, low, high, lowType, highType)
// Truncate ints bigger than uintptr. This is after the bounds
// check so it's safe.
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
high = c.builder.CreateTrunc(high, c.uintptrType, "")
}
@@ -1915,6 +1888,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
c.emitSliceBoundsCheck(frame, oldCap, low, high, lowType, highType)
// Truncate ints bigger than uintptr. This is after the bounds
// check so it's safe.
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
low = c.builder.CreateTrunc(low, c.uintptrType, "")
}
@@ -1948,6 +1923,15 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
c.emitSliceBoundsCheck(frame, oldLen, low, high, lowType, highType)
// Truncate ints bigger than uintptr. This is after the bounds
// check so it's safe.
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
low = c.builder.CreateTrunc(low, c.uintptrType, "")
}
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
high = c.builder.CreateTrunc(high, c.uintptrType, "")
}
newPtr := c.builder.CreateGEP(oldPtr, []llvm.Value{low}, "")
newLen := c.builder.CreateSub(high, low, "")
str, err := c.getZeroValue(c.mod.GetTypeByName("runtime._string"))
@@ -2154,12 +2138,13 @@ func (c *Compiler) parseBinOp(op token.Token, typ types.Type, x, y llvm.Value, p
return llvm.Value{}, c.makeError(pos, "todo: unknown basic type in binop: "+typ.String())
}
case *types.Signature:
// Extract function pointers from the function values (closures).
// Get raw scalars from the function value and compare those.
// Function values may be implemented in multiple ways, but they all
// have some way of getting a scalar value identifying the function.
// This is safe: function pointers are generally not comparable
// against each other, only against nil. So one or both has to be
// nil, so we can ignore the closure context.
x = c.builder.CreateExtractValue(x, 1, "")
y = c.builder.CreateExtractValue(y, 1, "")
// against each other, only against nil. So one of these has to be nil.
x = c.extractFuncScalar(x)
y = c.extractFuncScalar(y)
switch op {
case token.EQL: // ==
return c.builder.CreateICmp(llvm.IntEQ, x, y, ""), nil
@@ -2410,6 +2395,27 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
if isPtrFrom && !isPtrTo {
return c.builder.CreatePtrToInt(value, llvmTypeTo, ""), nil
} else if !isPtrFrom && isPtrTo {
if !value.IsABinaryOperator().IsNil() && value.InstructionOpcode() == llvm.Add {
// This is probably a pattern like the following:
// unsafe.Pointer(uintptr(ptr) + index)
// Used in functions like memmove etc. for lack of pointer
// arithmetic. Convert it to real pointer arithmatic here.
ptr := value.Operand(0)
index := value.Operand(1)
if !index.IsAPtrToIntInst().IsNil() {
// Swap if necessary, if ptr and index are reversed.
ptr, index = index, ptr
}
if !ptr.IsAPtrToIntInst().IsNil() {
origptr := ptr.Operand(0)
if origptr.Type() == c.i8ptrType {
// This pointer can be calculated from the original
// ptrtoint instruction with a GEP. The leftover inttoptr
// instruction is trivial to optimize away.
return c.builder.CreateGEP(origptr, []llvm.Value{index}, ""), nil
}
}
}
return c.builder.CreateIntToPtr(value, llvmTypeTo, ""), nil
}
@@ -2456,7 +2462,7 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
// Conversion between two integers.
if sizeFrom > sizeTo {
return c.builder.CreateTrunc(value, llvmTypeTo, ""), nil
} else if typeTo.Info()&types.IsUnsigned != 0 { // if unsigned
} else if typeFrom.Info()&types.IsUnsigned != 0 { // if unsigned
return c.builder.CreateZExt(value, llvmTypeTo, ""), nil
} else { // if signed
return c.builder.CreateSExt(value, llvmTypeTo, ""), nil
@@ -2536,81 +2542,6 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
}
}
func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.Value, error) {
if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables")
}
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
// Collect all bound variables.
boundVars := make([]llvm.Value, 0, len(expr.Bindings))
boundVarTypes := make([]llvm.Type, 0, len(expr.Bindings))
for _, binding := range expr.Bindings {
// The context stores the bound variables.
llvmBoundVar, err := c.parseExpr(frame, binding)
if err != nil {
return llvm.Value{}, err
}
boundVars = append(boundVars, llvmBoundVar)
boundVarTypes = append(boundVarTypes, llvmBoundVar.Type())
}
contextType := c.ctx.StructType(boundVarTypes, false)
// Allocate memory for the context.
contextAlloc := llvm.Value{}
contextHeapAlloc := llvm.Value{}
if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) {
// Context fits in a pointer - e.g. when it is a pointer. Store it
// directly in the stack after a convert.
// Because contextType is a struct and we have to cast it to a *i8,
// store it in an alloca first for bitcasting (store+bitcast+load).
contextAlloc = c.builder.CreateAlloca(contextType, "")
} else {
// Context is bigger than a pointer, so allocate it on the heap.
size := c.targetData.TypeAllocSize(contextType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
contextHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
contextAlloc = c.builder.CreateBitCast(contextHeapAlloc, llvm.PointerType(contextType, 0), "")
}
// Store all bound variables in the alloca or heap pointer.
for i, boundVar := range boundVars {
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}
gep := c.builder.CreateInBoundsGEP(contextAlloc, indices, "")
c.builder.CreateStore(boundVar, gep)
}
context := llvm.Value{}
if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) {
// Load value (as *i8) from the alloca.
contextAlloc = c.builder.CreateBitCast(contextAlloc, llvm.PointerType(c.i8ptrType, 0), "")
context = c.builder.CreateLoad(contextAlloc, "")
} else {
// Get the original heap allocation pointer, which already is an
// *i8.
context = contextHeapAlloc
}
// Get the function signature type, which is a closure type.
// A closure is a tuple of {context, function pointer}.
typ, err := c.getLLVMType(f.Signature)
if err != nil {
return llvm.Value{}, err
}
// Create the closure, which is a struct: {context, function pointer}.
closure, err := c.getZeroValue(typ)
if err != nil {
return llvm.Value{}, err
}
closure = c.builder.CreateInsertValue(closure, f.LLVMFn, 1, "")
closure = c.builder.CreateInsertValue(closure, context, 0, "")
return closure, nil
}
func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
x, err := c.parseExpr(frame, unop.X)
if err != nil {
@@ -2645,6 +2576,9 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
global := c.ir.GetGlobal(unop.X.(*ssa.Global))
name := global.LinkName()[:len(global.LinkName())-len("$funcaddr")]
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
return llvm.Value{}, c.makeError(unop.Pos(), "cgo function not found: "+name)
}
return c.builder.CreateBitCast(fn, c.i8ptrType, ""), nil
} else {
c.emitNilCheck(frame, x, "deref")
+269
View File
@@ -0,0 +1,269 @@
package compiler
// This file lowers func values into their final form. This is necessary for
// funcValueSwitch, which needs full program analysis.
import (
"sort"
"strconv"
"tinygo.org/x/go-llvm"
)
// funcSignatureInfo keeps information about a single signature and its uses.
type funcSignatureInfo struct {
sig llvm.Value // *uint8 to identify the signature
funcValueWithSignatures []llvm.Value // slice of runtime.funcValueWithSignature
}
// funcWithUses keeps information about a single function used as func value and
// the assigned function ID. More commonly used functions are assigned a lower
// ID.
type funcWithUses struct {
funcPtr llvm.Value
useCount int // how often this function is used in a func value
id int // assigned ID
}
// Slice to sort functions by their use counts, or else their name if they're
// used equally often.
type funcWithUsesList []*funcWithUses
func (l funcWithUsesList) Len() int { return len(l) }
func (l funcWithUsesList) Less(i, j int) bool {
if l[i].useCount != l[j].useCount {
// return the reverse: we want the highest use counts sorted first
return l[i].useCount > l[j].useCount
}
iName := l[i].funcPtr.Name()
jName := l[j].funcPtr.Name()
return iName < jName
}
func (l funcWithUsesList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
// LowerFuncValue lowers the runtime.funcValueWithSignature type and
// runtime.getFuncPtr function to their final form.
func (c *Compiler) LowerFuncValues() {
if c.funcImplementation() != funcValueSwitch {
return
}
// Find all func values used in the program with their signatures.
funcValueWithSignaturePtr := llvm.PointerType(c.mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
signatures := map[string]*funcSignatureInfo{}
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.Type() != funcValueWithSignaturePtr {
continue
}
sig := llvm.ConstExtractValue(global.Initializer(), []uint32{1})
name := sig.Name()
if info, ok := signatures[name]; ok {
info.funcValueWithSignatures = append(info.funcValueWithSignatures, global)
} else {
signatures[name] = &funcSignatureInfo{
sig: sig,
funcValueWithSignatures: []llvm.Value{global},
}
}
}
// Sort the signatures, for deterministic execution.
names := make([]string, 0, len(signatures))
for name := range signatures {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
info := signatures[name]
functions := make(funcWithUsesList, len(info.funcValueWithSignatures))
for i, use := range info.funcValueWithSignatures {
var useCount int
for _, use2 := range getUses(use) {
useCount += len(getUses(use2))
}
functions[i] = &funcWithUses{
funcPtr: llvm.ConstExtractValue(use.Initializer(), []uint32{0}).Operand(0),
useCount: useCount,
}
}
sort.Sort(functions)
for i, fn := range functions {
fn.id = i + 1
for _, ptrtoint := range getUses(fn.funcPtr) {
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
continue
}
for _, funcValueWithSignatureConstant := range getUses(ptrtoint) {
for _, funcValueWithSignatureGlobal := range getUses(funcValueWithSignatureConstant) {
for _, use := range getUses(funcValueWithSignatureGlobal) {
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
panic("expected const ptrtoint")
}
use.ReplaceAllUsesWith(llvm.ConstInt(c.uintptrType, uint64(fn.id), false))
}
}
}
}
}
for _, getFuncPtrCall := range getUses(info.sig) {
if getFuncPtrCall.IsACallInst().IsNil() {
continue
}
if getFuncPtrCall.CalledValue().Name() != "runtime.getFuncPtr" {
panic("expected all call uses to be runtime.getFuncPtr")
}
funcID := getFuncPtrCall.Operand(1)
switch len(functions) {
case 0:
// There are no functions used in a func value that implement
// this signature. The only possible value is a nil value.
for _, inttoptr := range getUses(getFuncPtrCall) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
nilptr := llvm.ConstPointerNull(inttoptr.Type())
inttoptr.ReplaceAllUsesWith(nilptr)
inttoptr.EraseFromParentAsInstruction()
}
getFuncPtrCall.EraseFromParentAsInstruction()
case 1:
// There is exactly one function with this signature that is
// used in a func value. The func value itself can be either nil
// or this one function.
c.builder.SetInsertPointBefore(getFuncPtrCall)
zero := llvm.ConstInt(c.uintptrType, 0, false)
isnil := c.builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
funcPtrNil := llvm.ConstPointerNull(functions[0].funcPtr.Type())
funcPtr := c.builder.CreateSelect(isnil, funcPtrNil, functions[0].funcPtr, "")
for _, inttoptr := range getUses(getFuncPtrCall) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
inttoptr.ReplaceAllUsesWith(funcPtr)
inttoptr.EraseFromParentAsInstruction()
}
getFuncPtrCall.EraseFromParentAsInstruction()
default:
// There are multiple functions used in a func value that
// implement this signature.
// What we'll do is transform the following:
// rawPtr := runtime.getFuncPtr(fn)
// if func.rawPtr == nil {
// runtime.nilpanic()
// }
// result := func.rawPtr(...args, func.context)
// into this:
// if false {
// runtime.nilpanic()
// }
// var result // Phi
// switch fn.id {
// case 0:
// runtime.nilpanic()
// case 1:
// result = call first implementation...
// case 2:
// result = call second implementation...
// default:
// unreachable
// }
// Remove some casts, checks, and the old call which we're going
// to replace.
var funcCall llvm.Value
for _, inttoptr := range getUses(getFuncPtrCall) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
for _, ptrUse := range getUses(inttoptr) {
if !ptrUse.IsABitCastInst().IsNil() {
for _, bitcastUse := range getUses(ptrUse) {
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().Name() != "runtime.isnil" {
panic("expected a call to runtime.isnil")
}
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
bitcastUse.EraseFromParentAsInstruction()
}
ptrUse.EraseFromParentAsInstruction()
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == inttoptr {
if !funcCall.IsNil() {
panic("multiple calls on a single runtime.getFuncPtr")
}
funcCall = ptrUse
} else {
panic("unexpected getFuncPtrCall")
}
}
}
if funcCall.IsNil() {
panic("expected exactly one call use of a runtime.getFuncPtr")
}
// The block that cannot be reached with correct funcValues (to
// help the optimizer).
c.builder.SetInsertPointBefore(funcCall)
defaultBlock := llvm.AddBasicBlock(funcCall.InstructionParent().Parent(), "func.default")
c.builder.SetInsertPointAtEnd(defaultBlock)
c.builder.CreateUnreachable()
// Create the switch.
c.builder.SetInsertPointBefore(funcCall)
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
// Split right after the switch. We will need to insert a few
// basic blocks in this gap.
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
// The 0 case, which is actually a nil check.
nilBlock := llvm.InsertBasicBlock(nextBlock, "func.nil")
c.builder.SetInsertPointAtEnd(nilBlock)
c.createRuntimeCall("nilpanic", nil, "")
c.builder.CreateUnreachable()
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
// Gather the list of parameters for every call we're going to
// make.
callParams := make([]llvm.Value, funcCall.OperandsCount()-1)
for i := range callParams {
callParams[i] = funcCall.Operand(i)
}
// If the call produces a value, we need to get it using a PHI
// node.
phiBlocks := make([]llvm.BasicBlock, len(functions))
phiValues := make([]llvm.Value, len(functions))
for i, fn := range functions {
// Insert a switch case.
bb := llvm.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
c.builder.SetInsertPointAtEnd(bb)
result := c.builder.CreateCall(fn.funcPtr, callParams, "")
c.builder.CreateBr(nextBlock)
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
phiBlocks[i] = bb
phiValues[i] = result
}
// Create the PHI node so that the call result flows into the
// next block (after the split). This is only necessary when the
// call produced a value.
if funcCall.Type().TypeKind() != llvm.VoidTypeKind {
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
phi := c.builder.CreatePHI(funcCall.Type(), "")
phi.AddIncoming(phiValues, phiBlocks)
funcCall.ReplaceAllUsesWith(phi)
}
// Finally, remove the old instructions.
funcCall.EraseFromParentAsInstruction()
for _, inttoptr := range getUses(getFuncPtrCall) {
inttoptr.EraseFromParentAsInstruction()
}
getFuncPtrCall.EraseFromParentAsInstruction()
}
}
}
}
+265
View File
@@ -0,0 +1,265 @@
package compiler
// This file implements function values and closures. It may need some lowering
// in a later step, see func-lowering.go.
import (
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
type funcValueImplementation int
const (
funcValueNone funcValueImplementation = iota
// A func value is implemented as a pair of pointers:
// {context, function pointer}
// where the context may be a pointer to a heap-allocated struct containing
// the free variables, or it may be undef if the function being pointed to
// doesn't need a context. The function pointer is a regular function
// pointer.
funcValueDoubleword
// As funcValueDoubleword, but with the function pointer replaced by a
// unique ID per function signature. Function values are called by using a
// switch statement and choosing which function to call.
funcValueSwitch
)
// funcImplementation picks an appropriate func value implementation for the
// target.
func (c *Compiler) funcImplementation() funcValueImplementation {
if c.GOARCH == "wasm" {
return funcValueSwitch
} else {
return funcValueDoubleword
}
}
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) (llvm.Value, error) {
var funcValueScalar llvm.Value
switch c.funcImplementation() {
case funcValueDoubleword:
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
case funcValueSwitch:
sigGlobal := c.getFuncSignature(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.mod.GetTypeByName("runtime.funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
sigGlobal,
})
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
funcValueWithSignatureGlobal.SetGlobalConstant(true)
funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage)
}
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
default:
panic("unimplemented func value variant")
}
funcValueType, err := c.getFuncType(sig)
if err != nil {
return llvm.Value{}, err
}
funcValue := llvm.Undef(funcValueType)
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
return funcValue, nil
}
// getFuncSignature returns a global for identification of a particular function
// signature. It is used in runtime.funcValueWithSignature and in calls to
// getFuncPtr.
func (c *Compiler) getFuncSignature(sig *types.Signature) llvm.Value {
typeCodeName := getTypeCodeName(sig)
sigGlobalName := "reflect/types.type:" + typeCodeName
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetInitializer(llvm.Undef(c.ctx.Int8Type()))
sigGlobal.SetGlobalConstant(true)
sigGlobal.SetLinkage(llvm.InternalLinkage)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation.
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
return c.builder.CreateExtractValue(funcValue, 1, "")
}
// extractFuncContext extracts the context pointer from this function value. It
// is a cheap operation.
func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
return c.builder.CreateExtractValue(funcValue, 0, "")
}
// decodeFuncValue extracts the context and the function pointer from this func
// value. This may be an expensive operation.
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value, err error) {
context = c.builder.CreateExtractValue(funcValue, 0, "")
switch c.funcImplementation() {
case funcValueDoubleword:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch:
llvmSig, err := c.getRawFuncType(sig)
if err != nil {
return llvm.Value{}, llvm.Value{}, err
}
sigGlobal := c.getFuncSignature(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
default:
panic("unimplemented func value variant")
}
return
}
// getFuncType returns the type of a func value given a signature.
func (c *Compiler) getFuncType(typ *types.Signature) (llvm.Type, error) {
switch c.funcImplementation() {
case funcValueDoubleword:
rawPtr, err := c.getRawFuncType(typ)
if err != nil {
return llvm.Type{}, err
}
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false), nil
case funcValueSwitch:
return c.mod.GetTypeByName("runtime.funcValue"), nil
default:
panic("unimplemented func value variant")
}
}
// getRawFuncType returns a LLVM function pointer type for a given signature.
func (c *Compiler) getRawFuncType(typ *types.Signature) (llvm.Type, error) {
// Get the return type.
var err error
var returnType llvm.Type
switch typ.Results().Len() {
case 0:
// No return values.
returnType = c.ctx.VoidType()
case 1:
// Just one return value.
returnType, err = c.getLLVMType(typ.Results().At(0).Type())
if err != nil {
return llvm.Type{}, err
}
default:
// Multiple return values. Put them together in a struct.
// This appears to be the common way to handle multiple return values in
// LLVM.
members := make([]llvm.Type, typ.Results().Len())
for i := 0; i < typ.Results().Len(); i++ {
returnType, err := c.getLLVMType(typ.Results().At(i).Type())
if err != nil {
return llvm.Type{}, err
}
members[i] = returnType
}
returnType = c.ctx.StructType(members, false)
}
// Get the parameter types.
var paramTypes []llvm.Type
if typ.Recv() != nil {
recv, err := c.getLLVMType(typ.Recv().Type())
if err != nil {
return llvm.Type{}, err
}
if recv.StructName() == "runtime._interface" {
// This is a call on an interface, not a concrete type.
// The receiver is not an interface, but a i8* type.
recv = c.i8ptrType
}
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
}
for i := 0; i < typ.Params().Len(); i++ {
subType, err := c.getLLVMType(typ.Params().At(i).Type())
if err != nil {
return llvm.Type{}, err
}
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
}
// All functions take these parameters at the end.
paramTypes = append(paramTypes, c.i8ptrType) // context
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
// Make a func type out of the signature.
return llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace), nil
}
// parseMakeClosure makes a function value (with context) from the given
// closure expression.
func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.Value, error) {
if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables")
}
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
// Collect all bound variables.
boundVars := make([]llvm.Value, 0, len(expr.Bindings))
boundVarTypes := make([]llvm.Type, 0, len(expr.Bindings))
for _, binding := range expr.Bindings {
// The context stores the bound variables.
llvmBoundVar, err := c.parseExpr(frame, binding)
if err != nil {
return llvm.Value{}, err
}
boundVars = append(boundVars, llvmBoundVar)
boundVarTypes = append(boundVarTypes, llvmBoundVar.Type())
}
contextType := c.ctx.StructType(boundVarTypes, false)
// Allocate memory for the context.
contextAlloc := llvm.Value{}
contextHeapAlloc := llvm.Value{}
if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) {
// Context fits in a pointer - e.g. when it is a pointer. Store it
// directly in the stack after a convert.
// Because contextType is a struct and we have to cast it to a *i8,
// store it in an alloca first for bitcasting (store+bitcast+load).
contextAlloc = c.builder.CreateAlloca(contextType, "")
} else {
// Context is bigger than a pointer, so allocate it on the heap.
size := c.targetData.TypeAllocSize(contextType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
contextHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
contextAlloc = c.builder.CreateBitCast(contextHeapAlloc, llvm.PointerType(contextType, 0), "")
}
// Store all bound variables in the alloca or heap pointer.
for i, boundVar := range boundVars {
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}
gep := c.builder.CreateInBoundsGEP(contextAlloc, indices, "")
c.builder.CreateStore(boundVar, gep)
}
context := llvm.Value{}
if c.targetData.TypeAllocSize(contextType) <= c.targetData.TypeAllocSize(c.i8ptrType) {
// Load value (as *i8) from the alloca.
contextAlloc = c.builder.CreateBitCast(contextAlloc, llvm.PointerType(c.i8ptrType, 0), "")
context = c.builder.CreateLoad(contextAlloc, "")
} else {
// Get the original heap allocation pointer, which already is an
// *i8.
context = contextHeapAlloc
}
// Create the closure.
return c.createFuncValue(f.LLVMFn, context, f.Signature)
}
+26 -2
View File
@@ -170,6 +170,10 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
if !sleep.IsNil() {
worklist = append(worklist, sleep)
}
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
if !deadlockStub.IsNil() {
worklist = append(worklist, deadlockStub)
}
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
if !chanSendStub.IsNil() {
worklist = append(worklist, chanSendStub)
@@ -288,7 +292,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Transform all async functions into coroutines.
for _, f := range asyncList {
if f == sleep || f == chanSendStub || f == chanRecvStub {
if f == sleep || f == deadlockStub || f == chanSendStub || f == chanRecvStub {
continue
}
@@ -305,7 +309,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if !inst.IsACallInst().IsNil() {
callee := inst.CalledValue()
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == chanSendStub || callee == chanRecvStub {
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSendStub || callee == chanRecvStub {
continue
}
asyncCalls = append(asyncCalls, inst)
@@ -439,6 +443,26 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
sleepCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.deadlockStub into coroutine suspends (without
// resume).
for _, deadlockCall := range getUses(deadlockStub) {
// deadlockCall must be a call instruction.
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
// Exit coroutine.
c.builder.SetInsertPointBefore(deadlockCall)
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 1, false), // final suspend
}, "")
c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead")
c.builder.SetInsertPointBefore(deadlockCall)
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
deadlockCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.chanSendStub into channel send operations.
for _, sendOp := range getUses(chanSendStub) {
// sendOp must be a call instruction.
+162
View File
@@ -0,0 +1,162 @@
package compiler
// This file implements inline asm support by calling special functions.
import (
"fmt"
"go/constant"
"regexp"
"strconv"
"strings"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// This is a compiler builtin, which reads the given register by name:
//
// func ReadRegister(name string) uintptr
//
// The register name must be a constant, for example "sp".
func (c *Compiler) emitReadRegister(args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
}
// This is a compiler builtin, which emits a piece of inline assembly with no
// operands or return values. It is useful for trivial instructions, like wfi in
// ARM or sleep in AVR.
//
// func Asm(asm string)
//
// The provided assembly must be a constant.
func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
// Magic function: insert inline assembly instead of calling it.
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false)
asm := constant.StringVal(args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
}
// This is a compiler builtin, which allows assembly to be called in a flexible
// way.
//
// func AsmFull(asm string, regs map[string]interface{})
//
// The asm parameter must be a constant string. The regs parameter must be
// provided immediately. For example:
//
// arm.AsmFull(
// "str {value}, {result}",
// map[string]interface{}{
// "value": 1
// "result": &dest,
// })
func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
registers := map[string]llvm.Value{}
registerMap := instr.Args[1].(*ssa.MakeMap)
for _, r := range *registerMap.Referrers() {
switch r := r.(type) {
case *ssa.DebugRef:
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.Block() {
return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block")
}
key := constant.StringVal(r.Key.(*ssa.Const).Value)
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
value, err := c.parseExpr(frame, r.Value.(*ssa.MakeInterface).X)
if err != nil {
return llvm.Value{}, err
}
registers[key] = value
case *ssa.Call:
if r.Common() == instr {
break
}
default:
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
}
}
// TODO: handle dollar signs in asm string
registerNumbers := map[string]int{}
var err error
argTypes := []llvm.Type{}
args := []llvm.Value{}
constraints := []string{}
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
// TODO: skip strings like {r4} etc. that look like ARM push/pop
// instructions.
name := s[1 : len(s)-1]
if _, ok := registers[name]; !ok {
if err == nil {
err = c.makeError(instr.Pos(), "unknown register name: "+name)
}
return s
}
if _, ok := registerNumbers[name]; !ok {
registerNumbers[name] = len(registerNumbers)
argTypes = append(argTypes, registers[name].Type())
args = append(args, registers[name])
switch registers[name].Type().TypeKind() {
case llvm.IntegerTypeKind:
constraints = append(constraints, "r")
case llvm.PointerTypeKind:
constraints = append(constraints, "*m")
default:
err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
return s
}
}
return fmt.Sprintf("${%v}", registerNumbers[name])
})
if err != nil {
return llvm.Value{}, err
}
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
return c.builder.CreateCall(target, args, ""), nil
}
// This is a compiler builtin which emits an inline SVCall instruction. It can
// be one of:
//
// func SVCall0(num uintptr) uintptr
// func SVCall1(num uintptr, a1 interface{}) uintptr
// func SVCall2(num uintptr, a1, a2 interface{}) uintptr
// func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
// func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
//
// The num parameter must be a constant. All other parameters may be any scalar
// value supported by LLVM inline assembly.
func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error) {
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
asm := "svc #" + strconv.FormatUint(num, 10)
constraints := "={r0}"
for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X
if i == 0 {
constraints += ",0"
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
}
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
// Implement the ARM calling convention by marking r1-r3 as
// clobbered. r0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{r1},~{r2},~{r3}"
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
return c.builder.CreateCall(target, llvmArgs, ""), nil
}
+46 -71
View File
@@ -4,7 +4,6 @@ package compiler
// form, optimizing them in the process.
//
// During SSA construction, the following pseudo-calls are created:
// runtime.makeInterface(typecode, methodSet)
// runtime.typeAssert(typecode, assertedType)
// runtime.interfaceImplements(typecode, interfaceMethodSet)
// runtime.interfaceMethod(typecode, interfaceMethodSet, signature)
@@ -14,16 +13,13 @@ package compiler
//
// This pass lowers the above functions to their final form:
//
// makeInterface:
// Replaced with a constant typecode.
//
// typeAssert:
// Replaced with an icmp instruction so it can be directly used in a type
// switch. This is very easy to optimize for LLVM: it will often translate a
// type switch into a regular switch statement.
// When this type assert is not possible (the type is never used in an
// interface with makeInterface), this call is replaced with a constant
// false to optimize the type assert away completely.
// interface), this call is replaced with a constant false to optimize the
// type assert away completely.
//
// interfaceImplements:
// This call is translated into a call that checks whether the underlying
@@ -166,25 +162,36 @@ func (c *Compiler) LowerInterfaces() {
// run runs the pass itself.
func (p *lowerInterfacesPass) run() {
// Count per type how often it is put in an interface. Also, collect all
// methods this type has (if it is named).
makeInterface := p.mod.NamedFunction("runtime.makeInterface")
makeInterfaceUses := getUses(makeInterface)
for _, use := range makeInterfaceUses {
typecode := use.Operand(0)
name := typecode.Name()
if t, ok := p.types[name]; !ok {
// This is the first time this type has been seen, add it to the
// list of types.
t = p.addType(typecode)
p.addTypeMethods(t, use.Operand(1))
} else {
p.addTypeMethods(t, use.Operand(1))
}
// Collect all type codes.
typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
var typesInInterfaces []llvm.Value
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
switch global.Type() {
case typecodeIDPtr:
// Retrieve Go type information based on an opaque global variable.
// Only the name of the global is relevant, the object itself is
// discarded afterwards.
name := global.Name()
t := &typeInfo{
name: name,
typecode: global,
}
p.types[name] = t
case typeInInterfacePtr:
// Count per type how often it is put in an interface. Also, collect
// all methods this type has (if it is named).
typesInInterfaces = append(typesInInterfaces, global)
initializer := global.Initializer()
typecode := llvm.ConstExtractValue(initializer, []uint32{0})
methodSet := llvm.ConstExtractValue(initializer, []uint32{1})
t := p.types[typecode.Name()]
p.addTypeMethods(t, methodSet)
// Count the number of MakeInterface instructions, for sorting the
// typecodes later.
p.types[name].countMakeInterfaces++
// Count the number of MakeInterface instructions, for sorting the
// typecodes later.
t.countMakeInterfaces += len(getUses(global))
}
}
// Count per type how often it is type asserted on (e.g. in a switch
@@ -194,9 +201,6 @@ func (p *lowerInterfacesPass) run() {
for _, use := range typeAssertUses {
typecode := use.Operand(1)
name := typecode.Name()
if _, ok := p.types[name]; !ok {
p.addType(typecode)
}
p.types[name].countTypeAsserts++
}
@@ -286,16 +290,6 @@ func (p *lowerInterfacesPass) run() {
typecode := use.Operand(0)
signature := p.signatures[use.Operand(2).Name()]
// If the interface was created in the same function, we can insert a
// direct call. This may not happen often but it is an easy
// optimization so let's do it anyway.
if !typecode.IsACallInst().IsNil() && typecode.CalledValue() == makeInterface {
name := typecode.Operand(0).Name()
typ := p.types[name]
p.replaceInvokeWithCall(use, typ, signature)
continue
}
methodSet := use.Operand(1).Operand(0) // global variable
itf := p.interfaces[methodSet.Name()]
if len(itf.types) == 0 {
@@ -356,20 +350,6 @@ func (p *lowerInterfacesPass) run() {
// types, if possible.
for _, use := range interfaceImplementsUses {
actualType := use.Operand(0)
if !actualType.IsACallInst().IsNil() && actualType.CalledValue() == makeInterface {
// Type assert is in the same function that creates the interface
// value. This means the underlying type is already known so match
// on that.
// This may not happen often but it is an easy optimization.
name := actualType.Operand(0).Name()
typ := p.types[name]
p.builder.SetInsertPointBefore(use)
assertedType := p.builder.CreatePtrToInt(typ.typecode, p.uintptrType, "typeassert.typecode")
commaOk := p.builder.CreateICmp(llvm.IntEQ, assertedType, actualType, "typeassert.ok")
use.ReplaceAllUsesWith(commaOk)
use.EraseFromParentAsInstruction()
continue
}
methodSet := use.Operand(1).Operand(0) // global variable
itf := p.interfaces[methodSet.Name()]
@@ -416,12 +396,14 @@ func (p *lowerInterfacesPass) run() {
// Assign a type code for each type.
p.assignTypeCodes(typeSlice)
// Replace each call to runtime.makeInterface with the constant type code.
for _, use := range makeInterfaceUses {
global := use.Operand(0)
t := p.types[global.Name()]
use.ReplaceAllUsesWith(llvm.ConstPtrToInt(t.typecode, p.uintptrType))
use.EraseFromParentAsInstruction()
// Replace each use of a runtime.typeInInterface with the constant type
// code.
for _, global := range typesInInterfaces {
for _, use := range getUses(global) {
t := p.types[llvm.ConstExtractValue(global.Initializer(), []uint32{0}).Name()]
typecode := llvm.ConstInt(p.uintptrType, t.num, false)
use.ReplaceAllUsesWith(typecode)
}
}
// Replace each type assert with an actual type comparison or (if the type
@@ -458,12 +440,18 @@ func (p *lowerInterfacesPass) run() {
// numbers.
for _, typ := range p.types {
for _, use := range getUses(typ.typecode) {
if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
if !use.IsAConstantExpr().IsNil() && use.Opcode() == llvm.PtrToInt {
use.ReplaceAllUsesWith(llvm.ConstInt(p.uintptrType, typ.num, false))
}
}
}
// Remove stray runtime.typeInInterface globals. Required for the following
// cleanup.
for _, global := range typesInInterfaces {
global.EraseFromParentAsGlobal()
}
// Remove method sets of types. Unnecessary, but cleans up the IR for
// inspection.
for _, typ := range p.types {
@@ -474,19 +462,6 @@ func (p *lowerInterfacesPass) run() {
}
}
// addType retrieves Go type information based on a i16 global variable.
// Only the name of the i16 is relevant, the object itself is const-propagated
// and discared afterwards.
func (p *lowerInterfacesPass) addType(typecode llvm.Value) *typeInfo {
name := typecode.Name()
t := &typeInfo{
name: name,
typecode: typecode,
}
p.types[name] = t
return t
}
// addTypeMethods reads the method set of the given type info struct. It
// retrieves the signatures and the references to the method functions
// themselves for later type<->interface matching.
+15 -7
View File
@@ -59,7 +59,15 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
if err != nil {
return llvm.Value{}, nil
}
itfTypeCode := c.createRuntimeCall("makeInterface", []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}, "makeinterface.typecode")
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
itfConcreteTypeGlobal.SetGlobalConstant(true)
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
}
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
@@ -73,7 +81,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
globalName := "type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
global.SetGlobalConstant(true)
}
return global
@@ -162,6 +170,10 @@ func getTypeCodeName(t types.Type) string {
return "slice:" + name + getTypeCodeName(t.Elem())
case *types.Struct:
elems := make([]string, t.NumFields())
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
// TODO: report this as a normal error instead of panicking.
panic("cgo unions are not allowed in interfaces")
}
for i := 0; i < t.NumFields(); i++ {
elems[i] = getTypeCodeName(t.Field(i).Type())
}
@@ -384,14 +396,10 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
return llvm.Value{}, nil, err
}
llvmFnType, err := c.getLLVMType(instr.Method.Type())
llvmFnType, err := c.getRawFuncType(instr.Method.Type().(*types.Signature))
if err != nil {
return llvm.Value{}, nil, err
}
// getLLVMType() has created a closure type for us, but we don't actually
// want a closure type as an interface call can never be a closure call. So
// extract the function pointer type from the closure.
llvmFnType = llvmFnType.Subtypes()[1]
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
values := []llvm.Value{
+21
View File
@@ -43,6 +43,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
c.OptimizeStringToBytes()
c.OptimizeAllocs()
c.LowerInterfaces()
c.LowerFuncValues()
// After interfaces are lowered, there are many more opportunities for
// interprocedural optimizations. To get them to work, function
@@ -53,6 +54,22 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
c.OptimizeAllocs()
c.OptimizeStringToBytes()
// Lower runtime.isnil calls to regular nil comparisons.
isnil := c.mod.NamedFunction("runtime.isnil")
if !isnil.IsNil() {
for _, use := range getUses(isnil) {
c.builder.SetInsertPointBefore(use)
ptr := use.Operand(0)
if !ptr.IsABitCastInst().IsNil() {
ptr = ptr.Operand(0)
}
nilptr := llvm.ConstPointerNull(ptr.Type())
icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
use.ReplaceAllUsesWith(icmp)
use.EraseFromParentAsInstruction()
}
}
err := c.LowerGoroutines()
if err != nil {
return err
@@ -60,6 +77,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
} else {
// Must be run at any optimization level.
c.LowerInterfaces()
c.LowerFuncValues()
err := c.LowerGoroutines()
if err != nil {
return err
@@ -281,6 +299,9 @@ func (c *Compiler) doesEscape(value llvm.Value) bool {
if !c.hasFlag(use, value, "nocapture") {
return true
}
} else if use.IsAICmpInst() != nilValue {
// Comparing pointers don't let the pointer escape.
// This is often a compiler-inserted nil check.
} else {
// Unknown instruction, might escape.
return true
+36 -3
View File
@@ -63,6 +63,12 @@ func (s *StdSizes) Alignof(T types.Type) int64 {
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
offsets := make([]int64, len(fields))
if len(fields) > 1 && fields[0].Name() == "C union" {
// This struct contains the magic "C union" field which indicates that
// this is actually a union from CGo.
// All fields in the union start at 0 so return that.
return offsets // all fields are still set to 0
}
var o int64
for i, f := range fields {
a := s.Alignof(f.Type())
@@ -125,11 +131,38 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
return 0
}
fields := make([]*types.Var, t.NumFields())
maxAlign := int64(1)
for i := range fields {
fields[i] = t.Field(i)
field := t.Field(i)
fields[i] = field
al := s.Alignof(field.Type())
if al > maxAlign {
maxAlign = al
}
}
if fields[0].Name() == "C union" {
// Magic field that indicates this is a CGo union and not a struct.
// The size is the biggest element, aligned to the element with the
// biggest alignment. This is not necessarily the same, for example
// in the following union:
// union { int32_t l; int16_t s[3] }
maxSize := int64(0)
for _, field := range fields[1:] {
si := s.Sizeof(field.Type())
if si > maxSize {
maxSize = si
}
}
return align(maxSize, maxAlign)
} else {
// This is a regular struct.
// Pick the size that fits this struct and add some alignment. Some
// structs have some extra padding at the end which should also be
// taken care of:
// struct { int32 n; byte b }
offsets := s.Offsetsof(fields)
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
}
offsets := s.Offsetsof(fields)
return offsets[n-1] + s.Sizeof(fields[n-1].Type())
case *types.Interface:
return s.PtrSize * 2
case *types.Pointer:
+41 -4
View File
@@ -95,7 +95,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case !inst.IsALoadInst().IsNil():
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
var value llvm.Value
if !operand.IsConstant() || inst.IsVolatile() || operand.Underlying.Opcode() == llvm.BitCast {
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
} else {
value = operand.Load()
@@ -307,9 +307,46 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
fr.locals[inst] = &LocalValue{fr.Eval, ret}
case callee.Name() == "runtime.makeInterface":
uintptrType := callee.Type().Context().IntType(fr.TargetData.PointerSize() * 8)
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), uintptrType)}
case callee.Name() == "runtime.interfaceImplements":
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
panic("interp: expected typecode to be a ptrtoint")
}
typecode = typecode.Operand(0)
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
panic("interp: expected method set in runtime.interfaceImplements to be a constant gep")
}
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
panic("interp: expected method set to be a constant gep")
}
methodSet = methodSet.Operand(0).Initializer()
// Make a set of all the methods on the concrete type, for
// easier checking in the next step.
definedMethods := map[string]struct{}{}
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
methodInfo := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)})
name := llvm.ConstExtractValue(methodInfo, []uint32{0}).Name()
definedMethods[name] = struct{}{}
}
// Check whether all interface methods are also in the list
// of defined methods calculated above.
implements := uint64(1) // i1 true
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
if _, ok := definedMethods[name]; !ok {
// There is a method on the interface that is not
// implemented by the type.
implements = 0 // i1 false
break
}
}
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)}
case callee.Name() == "runtime.nanotime":
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
// This are all print instructions, which necessarily have side
// effects but no results.
+5 -5
View File
@@ -28,8 +28,13 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
case "runtime.alloc":
// Cannot be scanned but can be interpreted.
return &sideEffectResult{severity: sideEffectNone}
case "runtime.nanotime":
// Fixed value at compile time.
return &sideEffectResult{severity: sideEffectNone}
case "runtime._panic":
return &sideEffectResult{severity: sideEffectLimited}
case "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone}
}
if e.sideEffectFuncs == nil {
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
@@ -81,11 +86,6 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
continue
}
if child.IsDeclaration() {
switch child.Name() {
case "runtime.makeInterface":
// Can be interpreted so does not have side effects.
continue
}
// External function call. Assume only limited side effects
// (no affected globals, etc.).
if e.hasLocalSideEffects(dirtyLocals, inst) {
+1 -2
View File
@@ -22,7 +22,7 @@ import "C"
// Link invokes a linker with the given name and flags.
//
// This version uses the built-in linker when trying to use lld.
func Link(dir, linker string, flags ...string) error {
func Link(linker string, flags ...string) error {
switch linker {
case "ld.lld", commands["ld.lld"]:
flags = append([]string{"tinygo:" + linker}, flags...)
@@ -60,7 +60,6 @@ func Link(dir, linker string, flags ...string) error {
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
return cmd.Run()
}
}
+1 -2
View File
@@ -13,10 +13,9 @@ import (
// Link invokes a linker with the given name and arguments.
//
// This version always runs the linker as an external command.
func Link(dir, linker string, flags ...string) error {
func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
return cmd.Run()
}
+59 -15
View File
@@ -16,11 +16,13 @@ import (
// fileInfo holds all Cgo-related information of a given *ast.File.
type fileInfo struct {
*ast.File
filename string
functions map[string]*functionInfo
globals map[string]*globalInfo
typedefs map[string]*typedefInfo
importCPos token.Pos
*Package
filename string
functions map[string]*functionInfo
globals map[string]*globalInfo
typedefs map[string]*typedefInfo
elaboratedTypes map[string]ast.Expr
importCPos token.Pos
}
// functionInfo stores some information about a Cgo function found by libclang
@@ -78,13 +80,15 @@ typedef unsigned long long _Cgo_ulonglong;
// processCgo extracts the `import "C"` statement from the AST, parses the
// comment with libclang, and modifies the AST to use this information.
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) error {
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) []error {
info := &fileInfo{
File: f,
filename: filename,
functions: map[string]*functionInfo{},
globals: map[string]*globalInfo{},
typedefs: map[string]*typedefInfo{},
File: f,
Package: p,
filename: filename,
functions: map[string]*functionInfo{},
globals: map[string]*globalInfo{},
typedefs: map[string]*typedefInfo{},
elaboratedTypes: map[string]ast.Expr{},
}
// Find `import "C"` statements in the file.
@@ -114,9 +118,10 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
// source location.
info.importCPos = spec.Path.ValuePos
err = info.parseFragment(cgoComment+cgoTypes, cflags)
if err != nil {
return err
pos := info.fset.PositionFor(genDecl.Doc.Pos(), true)
errs := info.parseFragment(cgoComment+cgoTypes, cflags, pos.Filename, pos.Line)
if errs != nil {
return errs
}
// Remove this import declaration.
@@ -139,9 +144,12 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
// Forward C types to Go types (like C.uint32_t -> uint32).
info.addTypeAliases()
// Add type declarations for C types, declared using typeef in C.
// Add type declarations for C types, declared using typedef in C.
info.addTypedefs()
// Add elaborated types for C structs and unions.
info.addElaboratedTypes()
// Patch the AST to use the declared types and functions.
f = astutil.Apply(f, info.walker, nil).(*ast.File)
@@ -373,6 +381,42 @@ func (info *fileInfo) addTypedefs() {
info.Decls = append(info.Decls, gen)
}
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
// foo" or "union foo" types, often used in a typedef.
//
// See also:
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
func (info *fileInfo) addElaboratedTypes() {
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.TYPE,
}
names := make([]string, 0, len(info.elaboratedTypes))
for name := range info.elaboratedTypes {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
typ := info.elaboratedTypes[name]
typeName := "C.struct_" + name
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: info.importCPos,
Name: typeName,
Obj: obj,
},
Type: typ,
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
}
info.Decls = append(info.Decls, gen)
}
// walker replaces all "C".<something> expressions to literal "C.<something>"
// expressions. Such expressions are impossible to write in Go (a dot cannot be
// used in the middle of a name) so in practice all C identifiers live in a
+231 -35
View File
@@ -4,29 +4,69 @@ package loader
// modification. It does not touch the AST itself.
import (
"errors"
"go/ast"
"go/scanner"
"go/token"
"path/filepath"
"strconv"
"strings"
"unsafe"
)
/*
#include <clang-c/Index.h> // if this fails, install libclang-7-dev
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
#include <stdlib.h>
#include <stdint.h>
int tinygo_clang_visitor(CXCursor c, CXCursor parent, CXClientData client_data);
// This struct should be ABI-compatible on all platforms (uintptr_t has the same
// alignment etc. as void*) but does not include void* pointers that are not
// always real pointers.
// The Go garbage collector assumes that all non-nil pointer-typed integers are
// actually pointers. This is not always true, as data[1] often contains 0x1,
// which is clearly not a valid pointer. Usually the GC won't catch this issue,
// but occasionally it will leading to a crash with a vague error message.
typedef struct {
enum CXCursorKind kind;
int xdata;
uintptr_t data[3];
} GoCXCursor;
// Forwarding functions. They are implemented in libclang_stubs.c and forward to
// the real functions without doing anything else, thus they are entirely
// compatible with the versions without tinygo_ prefix. The only difference is
// the CXCursor type, which has been replaced with GoCXCursor.
GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu);
unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data);
CXString tinygo_clang_getCursorSpelling(GoCXCursor c);
enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c);
CXType tinygo_clang_getCursorType(GoCXCursor c);
GoCXCursor tinygo_clang_getTypeDeclaration(CXType t);
CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
CXType tinygo_clang_getCursorResultType(GoCXCursor c);
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
*/
import "C"
var globalFileInfo *fileInfo
// refMap stores references to types, used for clang_visitChildren.
var refMap RefMap
func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
index := C.clang_createIndex(0, 1)
var diagnosticSeverity = [...]string{
C.CXDiagnostic_Ignored: "ignored",
C.CXDiagnostic_Note: "note",
C.CXDiagnostic_Warning: "warning",
C.CXDiagnostic_Error: "error",
C.CXDiagnostic_Fatal: "fatal",
}
func (info *fileInfo) parseFragment(fragment string, cflags []string, posFilename string, posLine int) []error {
index := C.clang_createIndex(0, 0)
defer C.clang_disposeIndex(index)
filenameC := C.CString("cgo-fake.c")
filenameC := C.CString(posFilename + "!cgo.c")
defer C.free(unsafe.Pointer(filenameC))
fragmentC := C.CString(fragment)
@@ -61,43 +101,80 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
}
defer C.clang_disposeTranslationUnit(unit)
if C.clang_getNumDiagnostics(unit) != 0 {
return errors.New("cgo: libclang cannot parse fragment")
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
errs := []error{}
addDiagnostic := func(diagnostic C.CXDiagnostic) {
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
location := C.clang_getDiagnosticLocation(diagnostic)
var file C.CXFile
var line C.unsigned
var column C.unsigned
var offset C.unsigned
C.clang_getExpansionLocation(location, &file, &line, &column, &offset)
filename := getString(C.clang_getFileName(file))
if filename == posFilename+"!cgo.c" {
// Adjust errors from the `import "C"` snippet.
// Note: doesn't adjust filenames inside the error message
// itself.
filename = posFilename
line += C.uint(posLine)
offset = 0 // hard to calculate
} else if filepath.IsAbs(filename) {
// Relative paths for readability, like other Go parser errors.
relpath, err := filepath.Rel(info.Program.Dir, filename)
if err == nil {
filename = relpath
}
}
errs = append(errs, &scanner.Error{
Pos: token.Position{
Filename: filename,
Offset: int(offset),
Line: int(line),
Column: int(column),
},
Msg: severity + ": " + spelling,
})
}
for i := 0; i < numDiagnostics; i++ {
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
addDiagnostic(diagnostic)
// Child diagnostics (like notes on redefinitions).
diagnostics := C.clang_getChildDiagnostics(diagnostic)
for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ {
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
}
}
return errs
}
if globalFileInfo != nil {
// There is a race condition here but that doesn't really matter as it
// is a sanity check anyway.
panic("libclang.go cannot be used concurrently yet")
}
globalFileInfo = info
defer func() {
globalFileInfo = nil
}()
cursor := C.clang_getTranslationUnitCursor(unit)
C.clang_visitChildren(cursor, (*[0]byte)(unsafe.Pointer(C.tinygo_clang_visitor)), C.CXClientData(uintptr(0)))
ref := refMap.Put(info)
defer refMap.Remove(ref)
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
return nil
}
//export tinygo_clang_visitor
func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.int {
info := globalFileInfo
kind := C.clang_getCursorKind(c)
//export tinygo_clang_globals_visitor
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
info := refMap.Get(unsafe.Pointer(client_data)).(*fileInfo)
kind := C.tinygo_clang_getCursorKind(c)
switch kind {
case C.CXCursor_FunctionDecl:
name := getString(C.clang_getCursorSpelling(c))
cursorType := C.clang_getCursorType(c)
name := getString(C.tinygo_clang_getCursorSpelling(c))
cursorType := C.tinygo_clang_getCursorType(c)
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
return C.CXChildVisit_Continue // not supported
}
numArgs := int(C.clang_Cursor_getNumArguments(c))
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
fn := &functionInfo{}
info.functions[name] = fn
for i := 0; i < numArgs; i++ {
arg := C.clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.clang_getCursorSpelling(arg))
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
argType := C.clang_getArgType(cursorType, C.uint(i))
if argName == "" {
argName = "$" + strconv.Itoa(i)
@@ -107,7 +184,7 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in
typeExpr: info.makeASTType(argType),
})
}
resultType := C.clang_getCursorResultType(c)
resultType := C.tinygo_clang_getCursorResultType(c)
if resultType.kind != C.CXType_Void {
fn.results = &ast.FieldList{
List: []*ast.Field{
@@ -118,9 +195,9 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in
}
}
case C.CXCursor_TypedefDecl:
typedefType := C.clang_getCursorType(c)
typedefType := C.tinygo_clang_getCursorType(c)
name := getString(C.clang_getTypedefName(typedefType))
underlyingType := C.clang_getTypedefDeclUnderlyingType(c)
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
expr := info.makeASTType(underlyingType)
if strings.HasPrefix(name, "_Cgo_") {
expr := expr.(*ast.Ident)
@@ -155,8 +232,8 @@ func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.in
typeExpr: expr,
}
case C.CXCursor_VarDecl:
name := getString(C.clang_getCursorSpelling(c))
cursorType := C.clang_getCursorType(c)
name := getString(C.tinygo_clang_getCursorSpelling(c))
cursorType := C.tinygo_clang_getCursorType(c)
info.globals[name] = &globalInfo{
typeExpr: info.makeASTType(cursorType),
}
@@ -220,6 +297,16 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
Star: info.importCPos,
X: info.makeASTType(C.clang_getPointeeType(typ)),
}
case C.CXType_ConstantArray:
return &ast.ArrayType{
Lbrack: info.importCPos,
Len: &ast.BasicLit{
ValuePos: info.importCPos,
Kind: token.INT,
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
},
Elt: info.makeASTType(C.clang_getElementType(typ)),
}
case C.CXType_FunctionProto:
// Be compatible with gc, which uses the *[0]byte type for function
// pointer types.
@@ -237,6 +324,84 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
Name: "byte",
},
}
case C.CXType_Typedef:
typedefName := getString(C.clang_getTypedefName(typ))
return &ast.Ident{
NamePos: info.importCPos,
Name: "C." + typedefName,
}
case C.CXType_Elaborated:
underlying := C.clang_Type_getNamedType(typ)
switch underlying.kind {
case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
// It is possible that this is a recursive definition, for example
// in linked lists (structs contain a pointer to the next element
// of the same type). If the name exists in info.elaboratedTypes,
// it is being processed, although it may not be fully defined yet.
if _, ok := info.elaboratedTypes[name]; !ok {
info.elaboratedTypes[name] = nil // predeclare (to avoid endless recursion)
info.elaboratedTypes[name] = info.makeASTType(underlying)
}
return &ast.Ident{
NamePos: info.importCPos,
Name: "C.struct_" + name,
}
default:
panic("unknown elaborated type")
}
case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
fieldList := &ast.FieldList{
Opening: info.importCPos,
Closing: info.importCPos,
}
ref := refMap.Put(struct {
fieldList *ast.FieldList
info *fileInfo
}{fieldList, info})
defer refMap.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
return &ast.StructType{
Struct: info.importCPos,
Fields: fieldList,
}
case C.CXCursor_UnionDecl:
if len(fieldList.List) > 1 {
// Insert a special field at the front (of zero width) as a
// marker that this is struct is actually a union. This is done
// by giving the field a name that cannot be expressed directly
// in Go.
// Other parts of the compiler look at the first element in a
// struct (of size > 2) to know whether this is a union.
// Note that we don't have to insert it for single-element
// unions as they're basically equivalent to a struct.
unionMarker := &ast.Field{
Type: &ast.StructType{
Struct: info.importCPos,
},
}
unionMarker.Names = []*ast.Ident{
&ast.Ident{
NamePos: info.importCPos,
Name: "C union",
Obj: &ast.Object{
Kind: ast.Var,
Name: "C union",
Decl: unionMarker,
},
},
}
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
}
return &ast.StructType{
Struct: info.importCPos,
Fields: fieldList,
}
}
}
if typeName == "" {
// Fallback, probably incorrect but at least the error points to an odd
@@ -248,3 +413,34 @@ func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
Name: typeName,
}
}
//export tinygo_clang_struct_visitor
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
passed := refMap.Get(unsafe.Pointer(client_data)).(struct {
fieldList *ast.FieldList
info *fileInfo
})
fieldList := passed.fieldList
info := passed.info
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
panic("expected field inside cursor")
}
name := getString(C.tinygo_clang_getCursorSpelling(c))
typ := C.tinygo_clang_getCursorType(c)
field := &ast.Field{
Type: info.makeASTType(typ),
}
field.Names = []*ast.Ident{
&ast.Ident{
NamePos: info.importCPos,
Name: name,
Obj: &ast.Object{
Kind: ast.Var,
Name: name,
Decl: field,
},
},
}
fieldList.List = append(fieldList.List, field)
return C.CXChildVisit_Continue
}
+2 -2
View File
@@ -3,9 +3,9 @@
package loader
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-7/include
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
#cgo linux LDFLAGS: -L/usr/lib/llvm-8/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm/lib -lclang -lffi
*/
import "C"
+46
View File
@@ -0,0 +1,46 @@
// This file implements some small trampoline functions. The signatures
// are slightly different from the ones defined in libclang.go, but they
// should be ABI compatible.
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
return clang_getTranslationUnitCursor(tu);
}
unsigned tinygo_clang_visitChildren(CXCursor parent, CXCursorVisitor visitor, CXClientData client_data) {
return clang_visitChildren(parent, visitor, client_data);
}
CXString tinygo_clang_getCursorSpelling(CXCursor c) {
return clang_getCursorSpelling(c);
}
enum CXCursorKind tinygo_clang_getCursorKind(CXCursor c) {
return clang_getCursorKind(c);
}
CXType tinygo_clang_getCursorType(CXCursor c) {
return clang_getCursorType(c);
}
CXCursor tinygo_clang_getTypeDeclaration(CXType t) {
return clang_getTypeDeclaration(t);
}
CXType tinygo_clang_getTypedefDeclUnderlyingType(CXCursor c) {
return clang_getTypedefDeclUnderlyingType(c);
}
CXType tinygo_clang_getCursorResultType(CXCursor c) {
return clang_getCursorResultType(c);
}
int tinygo_clang_Cursor_getNumArguments(CXCursor c) {
return clang_Cursor_getNumArguments(c);
}
CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
return clang_Cursor_getArgument(c, i);
}
+17 -11
View File
@@ -14,13 +14,15 @@ import (
// Program holds all packages and some metadata about the program as a whole.
type Program struct {
Build *build.Context
Packages map[string]*Package
sorted []*Package
fset *token.FileSet
TypeChecker types.Config
Dir string // current working directory (for error reporting)
CFlags []string
Build *build.Context
OverlayBuild *build.Context
ShouldOverlay func(path string) bool
Packages map[string]*Package
sorted []*Package
fset *token.FileSet
TypeChecker types.Config
Dir string // current working directory (for error reporting)
CFlags []string
}
// Package holds a loaded package, its imports, and its parsed files.
@@ -42,7 +44,11 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
}
// Load this package.
buildPkg, err := p.Build.Import(path, srcDir, build.ImportComment)
ctx := p.Build
if p.ShouldOverlay(path) {
ctx = p.OverlayBuild
}
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
if err != nil {
return nil, err
}
@@ -293,9 +299,9 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
fileErrs = append(fileErrs, err)
continue
}
err = p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir))
if err != nil {
fileErrs = append(fileErrs, err)
errs := p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir))
if errs != nil {
fileErrs = append(fileErrs, errs...)
continue
}
files = append(files, f)
+46
View File
@@ -0,0 +1,46 @@
package loader
import (
"sync"
"unsafe"
)
// #include <stdlib.h>
import "C"
// RefMap is a convenient way to store opaque references that can be passed to
// C. It is useful if an API uses function pointers and you cannot pass a Go
// pointer but only a C pointer.
type RefMap struct {
refs map[unsafe.Pointer]interface{}
lock sync.Mutex
}
// Put stores a value in the map. It can later be retrieved using Get. It must
// be removed using Remove to avoid memory leaks.
func (m *RefMap) Put(v interface{}) unsafe.Pointer {
m.lock.Lock()
defer m.lock.Unlock()
if m.refs == nil {
m.refs = make(map[unsafe.Pointer]interface{}, 1)
}
ref := C.malloc(1)
m.refs[ref] = v
return ref
}
// Get returns a stored value previously inserted with Put. Use the same
// reference as you got from Put.
func (m *RefMap) Get(ref unsafe.Pointer) interface{} {
m.lock.Lock()
defer m.lock.Unlock()
return m.refs[ref]
}
// Remove deletes a single reference from the map.
func (m *RefMap) Remove(ref unsafe.Pointer) {
m.lock.Lock()
defer m.lock.Unlock()
delete(m.refs, ref)
C.free(ref)
}
+6 -9
View File
@@ -21,13 +21,6 @@ import (
"github.com/tinygo-org/tinygo/loader"
)
var commands = map[string]string{
"ar": "llvm-ar",
"clang": "clang-7",
"ld.lld": "ld.lld-7",
"wasm-ld": "wasm-ld-7",
}
// commandError is an error type to wrap os/exec.Command errors. This provides
// some more information regarding what went wrong while running a command.
type commandError struct {
@@ -198,7 +191,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
// Prepare link command.
executable := filepath.Join(dir, "main")
tmppath := executable // final file
ldflags := append(spec.LDFlags, "-o", executable, objfile)
ldflags := append(spec.LDFlags, "-o", executable, objfile, "-L", sourceDir())
if spec.RTLib == "compiler-rt" {
ldflags = append(ldflags, librt)
}
@@ -235,7 +228,11 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
}
// Link the object files together.
err = Link(sourceDir(), spec.Linker, ldflags...)
if linker, ok := commands[spec.Linker]; ok {
err = Link(linker, ldflags...)
} else {
err = Link(spec.Linker, ldflags...)
}
if err != nil {
return &commandError{"failed to link", executable, err}
}
+62 -13
View File
@@ -2,8 +2,10 @@ package main
import (
"debug/elf"
"io/ioutil"
"os"
"path/filepath"
"sort"
"github.com/marcinbor85/gohex"
)
@@ -21,24 +23,72 @@ func (e ObjcopyError) Error() string {
return e.Op + ": " + e.Err.Error()
}
// ExtractTextSegment returns the .text segment and the first address from the
// ELF file in the given path.
func ExtractTextSegment(path string) (uint64, []byte, error) {
type ProgSlice []*elf.Prog
func (s ProgSlice) Len() int { return len(s) }
func (s ProgSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr }
func (s ProgSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// ExtractROM extracts a firmware image and the first load address from the
// given ELF file. It tries to emulate the behavior of objcopy.
func ExtractROM(path string) (uint64, []byte, error) {
f, err := elf.Open(path)
if err != nil {
return 0, nil, ObjcopyError{"failed to open ELF file to extract text segment", err}
}
defer f.Close()
text := f.Section(".text")
if text == nil {
return 0, nil, ObjcopyError{"file does not contain .text segment: " + path, nil}
// The GNU objcopy command does the following for firmware extraction (from
// the man page):
// > When objcopy generates a raw binary file, it will essentially produce a
// > memory dump of the contents of the input object file. All symbols and
// > relocation information will be discarded. The memory dump will start at
// > the load address of the lowest section copied into the output file.
// Find the lowest section address.
startAddr := ^uint64(0)
for _, section := range f.Sections {
if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 {
continue
}
if section.Addr < startAddr {
startAddr = section.Addr
}
}
data, err := text.Data()
if err != nil {
return 0, nil, ObjcopyError{"failed to extract .text segment from ELF file", err}
progs := make(ProgSlice, 0, 2)
for _, prog := range f.Progs {
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
continue
}
progs = append(progs, prog)
}
if len(progs) == 0 {
return 0, nil, ObjcopyError{"file does not contain ROM segments: " + path, nil}
}
sort.Sort(progs)
var rom []byte
for _, prog := range progs {
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
return 0, nil, ObjcopyError{"ROM segments are non-contiguous: " + path, nil}
}
data, err := ioutil.ReadAll(prog.Open())
if err != nil {
return 0, nil, ObjcopyError{"failed to extract segment from ELF file: " + path, err}
}
rom = append(rom, data...)
}
if progs[0].Paddr < startAddr {
// The lowest memory address is before the first section. This means
// that there is some extra data loaded at the start of the image that
// should be discarded.
// Example: ELF files where .text doesn't start at address 0 because
// there is a bootloader at the start.
return startAddr, rom[startAddr-progs[0].Paddr:], nil
} else {
return progs[0].Paddr, rom, nil
}
return text.Addr, data, nil
}
// Objcopy converts an ELF file to a different (simpler) output file format:
@@ -51,7 +101,7 @@ func Objcopy(infile, outfile string) error {
defer f.Close()
// Read the .text segment.
addr, data, err := ExtractTextSegment(infile)
addr, data, err := ExtractROM(infile)
if err != nil {
return err
}
@@ -65,12 +115,11 @@ func Objcopy(infile, outfile string) error {
return err
case ".hex":
mem := gohex.NewMemory()
mem.SetStartAddress(uint32(addr)) // ignored in most cases (Intel-specific)
err := mem.AddBinary(uint32(addr), data)
if err != nil {
return ObjcopyError{"failed to create .hex file", err}
}
mem.DumpIntelHex(f, 32) // TODO: handle error
mem.DumpIntelHex(f, 16) // TODO: handle error
return nil
default:
panic("unreachable")
-79
View File
@@ -1,79 +0,0 @@
// This program runs on an Arduino that has the following four devices connected:
// - Button connected to D2
// - Rotary analog dial connected to A0
// - RGB LED connected to D3, D5, and D6 used as PWM pins
// - BlinkM I2C RGB LED
//
// Pushing the button switches which color is selected.
// Rotating the dial changes the value for the currently selected color.
// Changing the color value updates the color displayed on both the
// PWM-controlled RGB LED and the I2C-controlled BlinkM.
package main
import (
"machine"
"time"
)
const (
buttonPin = 2
redPin = 3
greenPin = 5
bluePin = 6
red = 0
green = 1
blue = 2
)
func main() {
machine.InitADC()
machine.InitPWM()
machine.I2C0.Configure(machine.I2CConfig{})
// Init BlinkM
machine.I2C0.WriteTo(0x09, []byte("o"))
button := machine.GPIO{buttonPin}
button.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT})
dial := machine.ADC{machine.ADC0}
dial.Configure()
redLED := machine.PWM{redPin}
redLED.Configure()
greenLED := machine.PWM{greenPin}
greenLED.Configure()
blueLED := machine.PWM{bluePin}
blueLED.Configure()
selectedColor := red
colors := []uint16{0, 0, 0}
for {
// If we pushed the button, switch active color.
if !button.Get() {
if selectedColor == blue {
selectedColor = red
} else {
selectedColor++
}
}
// Change the intensity for the currently selected color based on the dial setting.
colors[selectedColor] = (dial.Get())
// Update the RGB LED.
redLED.Set(colors[red])
greenLED.Set(colors[green])
blueLED.Set(colors[blue])
// Update the BlinkM.
machine.I2C0.WriteTo(0x09, []byte("n"))
machine.I2C0.WriteTo(0x09, []byte{byte(colors[red] >> 8), byte(colors[green] >> 8), byte(colors[blue] >> 8)})
time.Sleep(time.Millisecond * 100)
}
}
@@ -0,0 +1,19 @@
// blink program for the BBC micro:bit that uses the entire LED matrix
package main
import (
"machine"
"time"
)
func main() {
machine.InitLEDMatrix()
for {
machine.ClearLEDMatrix()
time.Sleep(time.Millisecond * 500)
machine.SetEntireLEDMatrixOn()
time.Sleep(time.Millisecond * 500)
}
}
+1
View File
@@ -0,0 +1 @@
html/*
+15
View File
@@ -0,0 +1,15 @@
export: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm ./export/wasm.go
cp ./export/wasm.js ./html/
cp ./export/index.html ./html/
main: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm ./main/main.go
cp ./main/index.html ./html/
wasm_exec:
cp ../../../targets/wasm_exec.js ./html/
clean:
rm -rf ./html
mkdir ./html
+111
View File
@@ -0,0 +1,111 @@
# TinyGo WebAssembly examples
The examples here show two different ways of using WebAssembly with TinyGo;
1. Defining and exporting functions via the `//go:export <name>` directive. See
[the export folder](./export) for an example of this.
1. Defining and executing a `func main()`. This is similar to how the Go
standard library implementation works. See [the main folder](./main) for an
example of this.
## Building
Build using the `tinygo` compiler:
```bash
$ tinygo build -o ./wasm.wasm -target wasm ./main/main.go
```
This creates a `wasm.wasm` file, which we can load in JavaScript and execute in
a browser.
This examples folder contains two examples that can be built using `make`:
```bash
$ make export
```
```bash
$ make main
```
## Running
Start the local webserver:
```bash
$ go run main.go
Serving ./html on http://localhost:8080
```
`fmt.Println` prints to the browser console.
## How it works
Execution of the contents require a few JS helper functions which are called
from WebAssembly. We have defined these in
[wasm_exec.js](../../../targets/wasm_exec.js). It is based on
`$GOROOT/misc/wasm/wasm_exec.js` from the standard library, but is slightly
different. Ensure you are using the same version of `wasm_exec.js` as the
version of `tinygo` you are using to compile.
The general steps required to run the WebAssembly file in the browser includes
loading it into JavaScript with `WebAssembly.instantiateStreaming`, or
`WebAssembly.instantiate` in some browsers:
```js
const go = new Go(); // Defined in wasm_exec.js
const WASM_URL = 'wasm.wasm';
var wasm;
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}
```
If you have used explicit exports, you can call them by invoking them under the
`wasm.exports` namespace. See the [`export`](./export/wasm.js) directory for an
example of this.
In addition to this piece of JavaScript, it is important that the file is served
with the correct `Content-Type` header set.
```go
package main
import (
"log"
"net/http"
"strings"
)
const dir = "./html"
func main() {
fs := http.FileServer(http.Dir(dir))
log.Print("Serving " + dir + " on http://localhost:8080")
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if strings.HasSuffix(req.URL.Path, ".wasm") {
resp.Header().Set("content-type", "application/wasm")
}
fs.ServeHTTP(resp, req)
}))
}
```
This simple server serves anything inside the `./html` directory on port `8080`,
setting any `*.wasm` files `Content-Type` header appropriately.
+20
View File
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Go WebAssembly</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="wasm_exec.js" defer></script>
<script src="wasm.js" defer></script>
</head>
<body>
<h1>WebAssembly</h1>
<p>Add two numbers, using WebAssembly:</p>
<input type="number" id="a" value="2" /> + <input type="number" id="b" value="2" /> = <input type="number"
id="result" readonly />
</body>
</html>
@@ -16,10 +16,10 @@ func add(a, b int) int {
//go:export update
func update() {
document := js.Global().Get("document")
a_str := document.Call("getElementById", "a").Get("value").String()
b_str := document.Call("getElementById", "b").Get("value").String()
a, _ := strconv.Atoi(a_str)
b, _ := strconv.Atoi(b_str)
result := a + b
aStr := document.Call("getElementById", "a").Get("value").String()
bStr := document.Call("getElementById", "b").Get("value").String()
a, _ := strconv.Atoi(aStr)
b, _ := strconv.Atoi(bStr)
result := add(a, b)
document.Call("getElementById", "result").Set("value", result)
}
@@ -1,6 +1,6 @@
'use strict';
const WASM_URL = '../../../wasm.wasm';
const WASM_URL = 'wasm.wasm';
var wasm;
@@ -14,7 +14,7 @@ function init() {
const go = new Go();
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function(obj) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
updateResult();
@@ -23,7 +23,7 @@ function init() {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function(obj) {
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
updateResult();
+8
View File
@@ -0,0 +1,8 @@
# WebAssembly main execution example
A simple hello world that prints to the browser console.
## License
Note that `index.html` is copied almost verbatim from the Go 1.12 source at
`$GOROOT/misc/wasm/wasm_exec.html`. Its license applies to this file.
+49
View File
@@ -0,0 +1,49 @@
<!doctype html>
<!--
Copyright 2018 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<html>
<head>
<meta charset="utf-8">
<title>Go wasm</title>
</head>
<body>
<!--
Add the following polyfill for Microsoft Edge 17/18 support:
<script src="https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js"></script>
(see https://caniuse.com/#feat=textencoder)
-->
<script src="wasm_exec.js"></script>
<script>
if (!WebAssembly.instantiateStreaming) { // polyfill
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}
const go = new Go();
let mod, inst;
WebAssembly.instantiateStreaming(fetch("wasm.wasm"), go.importObject).then((result) => {
mod = result.module;
inst = result.instance;
document.getElementById("runButton").disabled = false;
}).catch((err) => {
console.error(err);
});
async function run() {
console.clear();
await go.run(inst);
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
}
</script>
<button onClick="run();" id="runButton" disabled>Run</button>
</body>
</html>
+9
View File
@@ -0,0 +1,9 @@
package main
import (
"fmt"
)
func main() {
fmt.Println("Hello world!")
}
+21
View File
@@ -0,0 +1,21 @@
package main
import (
"log"
"net/http"
"strings"
)
const dir = "./html"
func main() {
fs := http.FileServer(http.Dir(dir))
log.Print("Serving " + dir + " on http://localhost:8080")
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if strings.HasSuffix(req.URL.Path, ".wasm") {
resp.Header().Set("content-type", "application/wasm")
}
fs.ServeHTTP(resp, req)
}))
}
-16
View File
@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Go WebAssembly</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<script src="../../../targets/wasm_exec.js" defer></script>
<script src="wasm.js" defer></script>
</head>
<body>
<h1>WebAssembly</h1>
<p>Add two numbers, using WebAssembly:</p>
<input type="number" id="a" value="2"/> + <input type="number" id="b" value="2"/> = <input type="number" id="result" readonly/>
</body>
</html>
+10 -2
View File
@@ -76,8 +76,16 @@ const (
// I2C on the Circuit Playground Express.
var (
I2C0 = I2C{Bus: sam.SERCOM5_I2CM} // external device
I2C1 = I2C{Bus: sam.SERCOM1_I2CM} // internal device
// external device
I2C0 = I2C{Bus: sam.SERCOM5_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: GPIO_SERCOM}
// internal device
I2C1 = I2C{Bus: sam.SERCOM1_I2CM,
SDA: SDA1_PIN,
SCL: SCL1_PIN,
PinMode: GPIO_SERCOM_ALT}
)
// SPI pins (internal flash)
+6 -3
View File
@@ -44,8 +44,8 @@ const (
// UART1 pins
const (
UART_TX_PIN = D1
UART_RX_PIN = D0
UART_TX_PIN = D10
UART_RX_PIN = D11
)
// I2C pins
@@ -56,7 +56,10 @@ const (
// I2C on the ItsyBitsy M0.
var (
I2C0 = I2C{Bus: sam.SERCOM3_I2CM}
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: GPIO_SERCOM}
)
// SPI pins
+112
View File
@@ -0,0 +1,112 @@
// +build stm32,stm32f4disco
package machine
const (
PA0 = portA + 0
PA1 = portA + 1
PA2 = portA + 2
PA3 = portA + 3
PA4 = portA + 4
PA5 = portA + 5
PA6 = portA + 6
PA7 = portA + 7
PA8 = portA + 8
PA9 = portA + 9
PA10 = portA + 10
PA11 = portA + 11
PA12 = portA + 12
PA13 = portA + 13
PA14 = portA + 14
PA15 = portA + 15
PB0 = portB + 0
PB1 = portB + 1
PB2 = portB + 2
PB3 = portB + 3
PB4 = portB + 4
PB5 = portB + 5
PB6 = portB + 6
PB7 = portB + 7
PB8 = portB + 8
PB9 = portB + 9
PB10 = portB + 10
PB11 = portB + 11
PB12 = portB + 12
PB13 = portB + 13
PB14 = portB + 14
PB15 = portB + 15
PC0 = portC + 0
PC1 = portC + 1
PC2 = portC + 2
PC3 = portC + 3
PC4 = portC + 4
PC5 = portC + 5
PC6 = portC + 6
PC7 = portC + 7
PC8 = portC + 8
PC9 = portC + 9
PC10 = portC + 10
PC11 = portC + 11
PC12 = portC + 12
PC13 = portC + 13
PC14 = portC + 14
PC15 = portC + 15
PD0 = portD + 0
PD1 = portD + 1
PD2 = portD + 2
PD3 = portD + 3
PD4 = portD + 4
PD5 = portD + 5
PD6 = portD + 6
PD7 = portD + 7
PD8 = portD + 8
PD9 = portD + 9
PD10 = portD + 10
PD11 = portD + 11
PD12 = portD + 12
PD13 = portD + 13
PD14 = portD + 14
PD15 = portD + 15
PE0 = portE + 0
PE1 = portE + 1
PE2 = portE + 2
PE3 = portE + 3
PE4 = portE + 4
PE5 = portE + 5
PE6 = portE + 6
PE7 = portE + 7
PE8 = portE + 8
PE9 = portE + 9
PE10 = portE + 10
PE11 = portE + 11
PE12 = portE + 12
PE13 = portE + 13
PE14 = portE + 14
PE15 = portE + 15
PH0 = portH + 0
PH1 = portH + 1
)
const (
LED = LED_BUILTIN
LED1 = LED_GREEN
LED2 = LED_ORANGE
LED3 = LED_RED
LED4 = LED_BLUE
LED_BUILTIN = LED_GREEN
LED_GREEN = PD12
LED_ORANGE = PD13
LED_RED = PD14
LED_BLUE = PD15
)
// UART pins
const (
UART_TX_PIN = PA2
UART_RX_PIN = PA3
)
+19 -16
View File
@@ -416,7 +416,7 @@ var (
UART0 = USBCDC{Buffer: NewRingBuffer()}
// The first hardware serial port on the SAMD21. Uses the SERCOM0 interface.
UART1 = UART{Bus: sam.SERCOM0_USART, Buffer: NewRingBuffer()}
UART1 = UART{Bus: sam.SERCOM1_USART, Buffer: NewRingBuffer()}
)
const (
@@ -453,26 +453,26 @@ func (uart UART) Configure(config UARTConfig) {
// determine pads
var txpad, rxpad int
switch config.TX {
case UART_TX_PIN:
case PA10:
txpad = sercomTXPad2
case D10:
case PA18:
txpad = sercomTXPad2
case D11:
case PA16:
txpad = sercomTXPad0
default:
panic("Invalid TX pin for UART")
}
switch config.RX {
case UART_RX_PIN:
case PA11:
rxpad = sercomRXPad3
case D10:
case PA18:
rxpad = sercomRXPad2
case D11:
case PA16:
rxpad = sercomRXPad0
case D12:
case PA19:
rxpad = sercomRXPad3
case D13:
case PA17:
rxpad = sercomRXPad1
default:
panic("Invalid RX pin for UART")
@@ -531,11 +531,11 @@ func (uart UART) Configure(config UARTConfig) {
uart.Bus.INTENSET = sam.SERCOM_USART_INTENSET_RXC
// Enable RX IRQ.
if config.TX == UART_TX_PIN {
if config.TX == PA10 {
// UART0
arm.EnableIRQ(sam.IRQ_SERCOM0)
} else {
// UART1
// UART1 which is the normal default, since UART0 is used for USBCDC.
arm.EnableIRQ(sam.IRQ_SERCOM1)
}
}
@@ -563,7 +563,7 @@ func (uart UART) WriteByte(c byte) error {
return nil
}
//go:export SERCOM0_IRQHandler
//go:export SERCOM1_IRQHandler
func handleUART1() {
// should reset IRQ
UART1.Receive(byte((UART1.Bus.DATA & 0xFF)))
@@ -572,7 +572,10 @@ func handleUART1() {
// I2C on the SAMD21.
type I2C struct {
Bus *sam.SERCOM_I2CM_Type
Bus *sam.SERCOM_I2CM_Type
SCL uint8
SDA uint8
PinMode GPIOMode
}
// I2CConfig is used to store config info for I2C.
@@ -608,7 +611,7 @@ func (i2c I2C) Configure(config I2CConfig) {
config.Frequency = TWI_FREQ_100KHZ
}
// reset SERCOM3
// reset SERCOM
i2c.Bus.CTRLA |= sam.SERCOM_I2CM_CTRLA_SWRST
for (i2c.Bus.CTRLA&sam.SERCOM_I2CM_CTRLA_SWRST) > 0 ||
(i2c.Bus.SYNCBUSY&sam.SERCOM_I2CM_SYNCBUSY_SWRST) > 0 {
@@ -632,8 +635,8 @@ func (i2c I2C) Configure(config I2CConfig) {
}
// enable pins
GPIO{SDA_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
GPIO{SCL_PIN}.Configure(GPIOConfig{Mode: GPIO_SERCOM})
GPIO{i2c.SDA}.Configure(GPIOConfig{Mode: i2c.PinMode})
GPIO{i2c.SCL}.Configure(GPIOConfig{Mode: i2c.PinMode})
}
// SetBaudRate sets the communication speed for the I2C.
+1
View File
@@ -14,4 +14,5 @@ const (
portE
portF
portG
portH
)
+220
View File
@@ -0,0 +1,220 @@
// +build stm32,stm32f407
package machine
// Peripheral abstraction layer for the stm32.
import (
"device/arm"
"device/stm32"
)
const CPU_FREQUENCY = 168000000
const (
// Mode Flag
GPIO_OUTPUT = 0
GPIO_INPUT = GPIO_INPUT_PULLDOWN
GPIO_INPUT_FLOATING = 1
GPIO_INPUT_PULLDOWN = 2
GPIO_INPUT_PULLUP = 3
// for UART
GPIO_UART_TX = 4
GPIO_UART_RX = 5
//GPIOx_MODER
GPIO_MODE_INPUT = 0
GPIO_MODE_GENERAL_OUTPUT = 1
GPIO_MODE_ALTERNABTIVE = 2
GPIO_MODE_ANALOG = 3
//GPIOx_OTYPER
GPIO_OUTPUT_MODE_PUSH_PULL = 0
GPIO_OUTPUT_MODE_OPEN_DRAIN = 1
// GPIOx_OSPEEDR
GPIO_SPEED_LOW = 0
GPIO_SPEED_MID = 1
GPIO_SPEED_HI = 2
GPIO_SPEED_VERY_HI = 3
// GPIOx_PUPDR
GPIO_FLOATING = 0
GPIO_PULL_UP = 1
GPIO_PULL_DOWN = 2
)
func (p GPIO) getPort() *stm32.GPIO_Type {
switch p.Pin / 16 {
case 0:
return stm32.GPIOA
case 1:
return stm32.GPIOB
case 2:
return stm32.GPIOC
case 3:
return stm32.GPIOD
case 4:
return stm32.GPIOE
case 5:
return stm32.GPIOF
case 6:
return stm32.GPIOG
case 7:
return stm32.GPIOH
case 8:
return stm32.GPIOI
default:
panic("machine: unknown port")
}
}
// enableClock enables the clock for this desired GPIO port.
func (p GPIO) enableClock() {
switch p.Pin / 16 {
case 0:
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOAEN
case 1:
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOBEN
case 2:
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOCEN
case 3:
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIODEN
case 4:
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOEEN
case 5:
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOFEN
case 6:
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOGEN
case 7:
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOHEN
case 8:
stm32.RCC.AHB1ENR |= stm32.RCC_AHB1ENR_GPIOIEN
default:
panic("machine: unknown port")
}
}
// Configure this pin with the given configuration.
func (p GPIO) Configure(config GPIOConfig) {
// Configure the GPIO pin.
p.enableClock()
port := p.getPort()
pin := p.Pin % 16
pos := pin * 2
if config.Mode == GPIO_INPUT_FLOATING {
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_INPUT) << pos)))
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_FLOATING) << pos)))
} else if config.Mode == GPIO_INPUT_PULLDOWN {
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_INPUT) << pos)))
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_PULL_DOWN) << pos)))
} else if config.Mode == GPIO_INPUT_PULLUP {
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_INPUT) << pos)))
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_PULL_UP) << pos)))
} else if config.Mode == GPIO_OUTPUT {
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_GENERAL_OUTPUT) << pos)))
port.OSPEEDR = stm32.RegValue((uint32(port.OSPEEDR)&^(0x3<<pos) | (uint32(GPIO_SPEED_HI) << pos)))
} else if config.Mode == GPIO_UART_TX {
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_ALTERNABTIVE) << pos)))
port.OSPEEDR = stm32.RegValue((uint32(port.OSPEEDR)&^(0x3<<pos) | (uint32(GPIO_SPEED_HI) << pos)))
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_PULL_UP) << pos)))
p.setAltFunc(0x7)
} else if config.Mode == GPIO_UART_RX {
port.MODER = stm32.RegValue((uint32(port.MODER)&^(0x3<<pos) | (uint32(GPIO_MODE_ALTERNABTIVE) << pos)))
port.PUPDR = stm32.RegValue((uint32(port.PUPDR)&^(0x3<<pos) | (uint32(GPIO_FLOATING) << pos)))
p.setAltFunc(0x7)
}
}
func (p GPIO) setAltFunc(af uint32) {
port := p.getPort()
pin := p.Pin % 16
pos := pin * 4
if pin >= 8 {
port.AFRH = stm32.RegValue(uint32(port.AFRH)&^(0xF<<pos) | ((af & 0xF) << pos))
} else {
port.AFRL = stm32.RegValue(uint32(port.AFRL)&^(0xF<<pos) | ((af & 0xF) << pos))
}
}
// Set the pin to high or low.
// Warning: only use this on an output pin!
func (p GPIO) Set(high bool) {
port := p.getPort()
pin := p.Pin % 16
if high {
port.BSRR = 1 << pin
} else {
port.BSRR = 1 << (pin + 16)
}
}
// UART
type UART struct {
Buffer *RingBuffer
}
var (
// Both UART0 and UART1 refer to USART2.
UART0 = UART{Buffer: NewRingBuffer()}
UART1 = &UART0
)
// Configure the UART.
func (uart UART) Configure(config UARTConfig) {
// Default baud rate to 115200.
if config.BaudRate == 0 {
config.BaudRate = 115200
}
// pins
switch config.TX {
default:
// use standard TX/RX pins PA2 and PA3
GPIO{UART_TX_PIN}.Configure(GPIOConfig{Mode: GPIO_UART_TX})
GPIO{UART_RX_PIN}.Configure(GPIOConfig{Mode: GPIO_UART_RX})
}
// Enable USART2 clock
stm32.RCC.APB1ENR |= stm32.RCC_APB1ENR_USART2EN
/*
Set baud rate(115200)
OVER8 = 0, APB2 = 42mhz
+----------+--------+
| baudrate | BRR |
+----------+--------+
| 1200 | 0x88B8 |
| 2400 | 0x445C |
| 9600 | 0x1117 |
| 19200 | 0x88C |
| 38400 | 0x446 |
| 57600 | 0x2D9 |
| 115200 | 0x16D |
+----------+--------+
*/
stm32.USART2.BRR = 0x16c
// Enable USART2 port.
stm32.USART2.CR1 = stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE
// Enable RX IRQ.
arm.SetPriority(stm32.IRQ_USART2, 0xc0)
arm.EnableIRQ(stm32.IRQ_USART2)
}
// WriteByte writes a byte of data to the UART.
func (uart UART) WriteByte(c byte) error {
stm32.USART2.DR = stm32.RegValue(c)
for (stm32.USART2.SR & stm32.USART_SR_TXE) == 0 {
}
return nil
}
//go:export USART2_IRQHandler
func handleUSART2() {
UART1.Receive(byte((stm32.USART2.DR & 0xFF)))
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build wasm
// +build avr cortexm wasm
package os
+1 -1
View File
@@ -1,4 +1,4 @@
// +build darwin linux
// +build darwin linux,!avr,!cortexm
package os
+3 -3
View File
@@ -134,18 +134,18 @@ func (t Type) String() string {
}
func (t Type) Kind() Kind {
if t % 2 == 0 {
if t%2 == 0 {
// basic type
return Kind((t >> 1) % 32)
} else {
return Kind(t >> 1) % 8 + 19
return Kind(t>>1)%8 + 19
}
}
func (t Type) Elem() Type {
switch t.Kind() {
case Chan, Ptr, Slice:
if (t >> 4) % 2 != 0 {
if (t>>4)%2 != 0 {
panic("unimplemented: (reflect.Type).Elem() for named types")
}
return t >> 5
+21 -2
View File
@@ -335,7 +335,7 @@ func (v Value) Index(i int) Value {
typecode: v.Type().Elem(),
indirect: true,
}
addr := uintptr(slice.Data) + elem.Type().Size() * uintptr(i) // pointer to new value
addr := uintptr(slice.Data) + elem.Type().Size()*uintptr(i) // pointer to new value
elem.value = unsafe.Pointer(addr)
return elem
case String:
@@ -348,7 +348,7 @@ func (v Value) Index(i int) Value {
}
return Value{
typecode: Uint8.basicType(),
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
}
case Array:
panic("unimplemented: (reflect.Value).Index()")
@@ -365,6 +365,25 @@ func (v Value) MapIndex(key Value) Value {
panic("unimplemented: (reflect.Value).MapIndex()")
}
func (v Value) MapRange() *MapIter {
panic("unimplemented: (reflect.Value).MapRange()")
}
type MapIter struct {
}
func (it *MapIter) Key() Value {
panic("unimplemented: (*reflect.MapIter).Key()")
}
func (it *MapIter) Value() Value {
panic("unimplemented: (*reflect.MapIter).Value()")
}
func (it *MapIter) Next() bool {
panic("unimplemented: (*reflect.MapIter).Next()")
}
func (v Value) Set(x Value) {
if !v.indirect {
panic("reflect: value is not addressable")
+2
View File
@@ -1,3 +1,5 @@
// +build arm,!avr,!cortexm
package runtime
const GOARCH = "arm"
@@ -1,4 +1,4 @@
// +build tinygo.arm
// +build cortexm
package runtime
+1 -1
View File
@@ -1,4 +1,4 @@
// +build wasm,!tinygo.arm,!avr
// +build wasm
package runtime
+1
View File
@@ -41,6 +41,7 @@ const (
func chanSendStub(caller *coroutine, ch *channel, _ unsafe.Pointer, size uintptr)
func chanRecvStub(caller *coroutine, ch *channel, _ unsafe.Pointer, _ *bool, size uintptr)
func deadlockStub()
// chanSend sends a single value over the channel. If this operation can
// complete immediately (there is a goroutine waiting for a value), it sends the
+28
View File
@@ -0,0 +1,28 @@
package runtime
// This file implements some data types that may be useful for some
// implementations of func values.
import (
"unsafe"
)
// funcValue is the underlying type of func values, depending on which func
// value representation was used.
type funcValue struct {
context unsafe.Pointer // function context, for closures and bound methods
id uintptr // ptrtoint of *funcValueWithSignature before lowering, opaque index (non-0) after lowering
}
// funcValueWithSignature is used before the func lowering pass.
type funcValueWithSignature struct {
funcPtr uintptr // ptrtoint of the actual function pointer
signature *uint8 // pointer to identify this signature (the value is undef)
}
// getFuncPtr is a dummy function that may be used if the func lowering pass is
// not used. It is generally too slow but may be a useful fallback to debug the
// func lowering pass.
func getFuncPtr(val funcValue, signature *uint8) uintptr {
return (*funcValueWithSignature)(unsafe.Pointer(val.id)).funcPtr
}
+11 -4
View File
@@ -43,15 +43,22 @@ type interfaceMethodInfo struct {
funcptr uintptr // bitcast from the actual function pointer
}
// Pseudo function call used while putting a concrete value in an interface,
// that must be lowered to a constant uintptr.
func makeInterface(typecode *uint8, methodSet *interfaceMethodInfo) uintptr
type typecodeID struct{}
// Pseudo type used before interface lowering. By using a struct instead of a
// function call, this is simpler to reason about during init interpretation
// than a function call. Also, by keeping the method set around it is easier to
// implement interfaceImplements in the interp package.
type typeInInterface struct {
typecode *typecodeID
methodSet *interfaceMethodInfo // nil or a GEP of an array
}
// Pseudo function call used during a type assert. It is used during interface
// lowering, to assign the lowest type numbers to the types with the most type
// asserts. Also, it is replaced with const false if this type assert can never
// happen.
func typeAssert(actualType uintptr, assertedType *uint8) bool
func typeAssert(actualType uintptr, assertedType *typecodeID) bool
// Pseudo function call that returns whether a given type implements all methods
// of the given interface.
+3 -1
View File
@@ -1 +1,3 @@
TheVersion = `go0.1.0`
package sys
const TheVersion = `go0.1.0`
-28
View File
@@ -1,28 +0,0 @@
// +build js,tinygo.arm avr
package runtime
// This file stubs out some external functions declared by the syscall/js
// package. They cannot be used on microcontrollers.
type js_ref uint64
//go:linkname js_valueGet syscall/js.valueGet
func js_valueGet(v js_ref, p string) js_ref {
return 0
}
//go:linkname js_valueNew syscall/js.valueNew
func js_valueNew(v js_ref, args []js_ref) (js_ref, bool) {
return 0, true
}
//go:linkname js_valueCall syscall/js.valueCall
func js_valueCall(v js_ref, m string, args []js_ref) (js_ref, bool) {
return 0, true
}
//go:linkname js_stringVal syscall/js.stringVal
func js_stringVal(x string) js_ref {
return 0
}
+8 -14
View File
@@ -27,6 +27,14 @@ func _recover() interface{} {
return nil
}
// See emitNilCheck in compiler/asserts.go.
// This function is a dummy function that has its first and only parameter
// marked 'nocapture' to work around a limitation in LLVM: a regular pointer
// comparison captures the pointer.
func isnil(ptr *uint8) bool {
return ptr == nil
}
// Panic when trying to dereference a nil pointer.
func nilpanic() {
runtimePanic("nil pointer dereference")
@@ -41,17 +49,3 @@ func lookuppanic() {
func slicepanic() {
runtimePanic("slice out of range")
}
// Check for bounds in *ssa.MakeSlice.
func sliceBoundsCheckMake(length, capacity uintptr, max uintptr) {
if length > capacity || capacity > max {
runtimePanic("slice size out of range")
}
}
// Check for bounds in *ssa.MakeSlice. Supports 64-bit indexes.
func sliceBoundsCheckMake64(length, capacity uint64, max uintptr) {
if length > capacity || capacity > uint64(max) {
runtimePanic("slice size out of range")
}
}
+1 -1
View File
@@ -39,7 +39,7 @@ func printuint16(n uint16) {
printuint32(uint32(n))
}
func printint16(n uint16) {
func printint16(n int16) {
printint32(int32(n))
}
+5 -1
View File
@@ -84,9 +84,13 @@ func sleep(d int64) {
sleepTicks(timeUnit(d / tickMicros))
}
func nanotime() int64 {
return int64(ticks()) * tickMicros
}
//go:linkname now time.now
func now() (sec int64, nsec int32, mono int64) {
mono = int64(ticks()) * tickMicros
mono = nanotime()
sec = mono / (1000 * 1000 * 1000)
nsec = int32(mono - sec*(1000*1000*1000))
return
@@ -1,4 +1,4 @@
// +build tinygo.arm
// +build cortexm
package runtime
@@ -46,10 +46,16 @@ func abort() {
}
}
// Implement memset for compiler-rt.
// Implement memset for LLVM and compiler-rt.
//go:export memset
func memset(ptr unsafe.Pointer, c byte, size uintptr) {
func libc_memset(ptr unsafe.Pointer, c byte, size uintptr) {
for i := uintptr(0); i < size; i++ {
*(*byte)(unsafe.Pointer(uintptr(ptr) + i)) = c
}
}
// Implement memmove for LLVM and compiler-rt.
//go:export memmove
func libc_memmove(dst, src unsafe.Pointer, size uintptr) {
memmove(dst, src, size)
}
+208
View File
@@ -0,0 +1,208 @@
// +build stm32,stm32f407
package runtime
import (
"device/arm"
"device/stm32"
"machine"
)
func init() {
initCLK()
initTIM3()
machine.UART0.Configure(machine.UARTConfig{})
initTIM7()
}
func putchar(c byte) {
machine.UART0.WriteByte(c)
}
const (
HSE_STARTUP_TIMEOUT = 0x0500
/* PLL Options - See RM0090 Reference Manual pg. 95 */
PLL_M = 8 /* PLL_VCO = (HSE_VALUE or HSI_VLAUE / PLL_M) * PLL_N */
PLL_N = 336
PLL_P = 2 /* SYSCLK = PLL_VCO / PLL_P */
PLL_Q = 7 /* USB OTS FS, SDIO and RNG Clock = PLL_VCO / PLL_Q */
)
/*
clock settings
+-------------+--------+
| HSE | 8mhz |
| SYSCLK | 168mhz |
| HCLK | 168mhz |
| APB2(PCLK2) | 84mhz |
| APB1(PCLK1) | 42mhz |
+-------------+--------+
*/
func initCLK() {
// Reset clock registers
// Set HSION
stm32.RCC.CR |= stm32.RCC_CR_HSION
for (stm32.RCC.CR & stm32.RCC_CR_HSIRDY) == 0 {
}
// Reset CFGR
stm32.RCC.CFGR = 0x00000000
// Reset HSEON, CSSON and PLLON
stm32.RCC.CR &= 0xFEF6FFFF
// Reset PLLCFGR
stm32.RCC.PLLCFGR = 0x24003010
// Reset HSEBYP
stm32.RCC.CR &= 0xFFFBFFFF
// Disable all interrupts
stm32.RCC.CIR = 0x00000000
// Set up the clock
var startupCounter uint32 = 0
// Enable HSE
stm32.RCC.CR = stm32.RCC_CR_HSEON
// Wait till HSE is ready and if timeout is reached exit
for {
startupCounter++
if (stm32.RCC.CR&stm32.RCC_CR_HSERDY != 0) || (startupCounter == HSE_STARTUP_TIMEOUT) {
break
}
}
if (stm32.RCC.CR & stm32.RCC_CR_HSERDY) != 0 {
// Enable high performance mode, System frequency up to 168MHz
stm32.RCC.APB1ENR |= stm32.RCC_APB1ENR_PWREN
stm32.PWR.CR |= 0x4000 // PWR_CR_VOS
// HCLK = SYSCLK / 1
stm32.RCC.CFGR |= (0x0 << stm32.RCC_CFGR_HPRE_Pos)
// PCLK2 = HCLK / 2
stm32.RCC.CFGR |= (0x4 << stm32.RCC_CFGR_PPRE2_Pos)
// PCLK1 = HCLK / 4
stm32.RCC.CFGR |= (0x5 << stm32.RCC_CFGR_PPRE1_Pos)
// Configure the main PLL
// PLL Options - See RM0090 Reference Manual pg. 95
stm32.RCC.PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) - 1) << 16) |
(1 << stm32.RCC_PLLCFGR_PLLSRC_Pos) | (PLL_Q << 24)
// Enable main PLL
stm32.RCC.CR |= stm32.RCC_CR_PLLON
// Wait till the main PLL is ready
for (stm32.RCC.CR & stm32.RCC_CR_PLLRDY) == 0 {
}
// Configure Flash prefetch, Instruction cache, Data cache and wait state
stm32.FLASH.ACR = stm32.FLASH_ACR_ICEN | stm32.FLASH_ACR_DCEN | (5 << stm32.FLASH_ACR_LATENCY_Pos)
// Select the main PLL as system clock source
stm32.RCC.CFGR &^= stm32.RCC_CFGR_SW0 | stm32.RCC_CFGR_SW1
stm32.RCC.CFGR |= (0x2 << stm32.RCC_CFGR_SW0_Pos)
for (stm32.RCC.CFGR & (0x3 << stm32.RCC_CFGR_SWS0_Pos)) != (0x2 << stm32.RCC_CFGR_SWS0_Pos) {
}
} else {
// If HSE failed to start up, the application will have wrong clock configuration
for {
}
}
// Enable the CCM RAM clock
stm32.RCC.AHB1ENR |= (1 << 20)
}
const tickMicros = 1000
var (
// tick in milliseconds
tickCount timeUnit
)
//go:volatile
type isrFlag bool
var timerWakeup isrFlag
// Enable the TIM3 clock.(sleep count)
func initTIM3() {
stm32.RCC.APB1ENR |= stm32.RCC_APB1ENR_TIM3EN
arm.SetPriority(stm32.IRQ_TIM3, 0xc3)
arm.EnableIRQ(stm32.IRQ_TIM3)
}
// Enable the TIM7 clock.(tick count)
func initTIM7() {
stm32.RCC.APB1ENR |= stm32.RCC_APB1ENR_TIM7EN
// CK_INT = APB1 x2 = 84mhz
stm32.TIM7.PSC = 84000000/10000 - 1 // 84mhz to 10khz(0.1ms)
stm32.TIM7.ARR = stm32.RegValue(10) - 1 // interrupt per 1ms
// Enable the hardware interrupt.
stm32.TIM7.DIER |= stm32.TIM_DIER_UIE
// Enable the timer.
stm32.TIM7.CR1 |= stm32.TIM_CR1_CEN
arm.SetPriority(stm32.IRQ_TIM7, 0xc1)
arm.EnableIRQ(stm32.IRQ_TIM7)
}
const asyncScheduler = false
// sleepTicks should sleep for specific number of microseconds.
func sleepTicks(d timeUnit) {
timerSleep(uint32(d))
}
// number of ticks (microseconds) since start.
func ticks() timeUnit {
// milliseconds to microseconds
return tickCount * 1000
}
// ticks are in microseconds
func timerSleep(ticks uint32) {
timerWakeup = false
// CK_INT = APB1 x2 = 84mhz
// prescale counter down from 84mhz to 10khz aka 0.1 ms frequency.
stm32.TIM3.PSC = 84000000/10000 - 1 // 8399
// set duty aka duration
arr := (ticks / 100) - 1 // convert from microseconds to 0.1 ms
if arr == 0 {
arr = 1 // avoid blocking
}
stm32.TIM3.ARR = stm32.RegValue(arr)
// Enable the hardware interrupt.
stm32.TIM3.DIER |= stm32.TIM_DIER_UIE
// Enable the timer.
stm32.TIM3.CR1 |= stm32.TIM_CR1_CEN
// wait till timer wakes up
for !timerWakeup {
arm.Asm("wfi")
}
}
//go:export TIM3_IRQHandler
func handleTIM3() {
if (stm32.TIM3.SR & stm32.TIM_SR_UIF) > 0 {
// Disable the timer.
stm32.TIM3.CR1 &^= stm32.TIM_CR1_CEN
// clear the update flag
stm32.TIM3.SR &^= stm32.TIM_SR_UIF
// timer was triggered
timerWakeup = true
}
}
//go:export TIM7_IRQHandler
func handleTIM7() {
if (stm32.TIM7.SR & stm32.TIM_SR_UIF) > 0 {
// clear the update flag
stm32.TIM7.SR &^= stm32.TIM_SR_UIF
tickCount++
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build darwin linux
// +build darwin linux,!avr,!cortexm
package runtime
+6 -1
View File
@@ -1,4 +1,4 @@
// +build wasm,!tinygo.arm,!avr
// +build wasm
package runtime
@@ -37,6 +37,11 @@ func putchar(c byte) {
resource_write(stdout, &c, 1)
}
//go:linkname setEventHandler syscall/js.setEventHandler
func setEventHandler(fn func()) {
// TODO
}
//go:export go_scheduler
func go_scheduler() {
scheduler()
+30
View File
@@ -0,0 +1,30 @@
package syscall
// Most code here has been copied from the Go sources:
// https://github.com/golang/go/blob/go1.12/src/syscall/syscall_js.go
// It has the following copyright note:
//
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// An Errno is an unsigned number describing an error condition.
// It implements the error interface. The zero Errno is by convention
// a non-error, so code to convert from Errno to error should use:
// err = nil
// if errno != 0 {
// err = errno
// }
type Errno uintptr
func (e Errno) Error() string {
return "errno " + itoa(int(e))
}
func (e Errno) Temporary() bool {
return e == EINTR || e == EMFILE || e.Timeout()
}
func (e Errno) Timeout() bool {
return e == EAGAIN || e == EWOULDBLOCK || e == ETIMEDOUT
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2009 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 syscall
func itoa(val int) string { // do it here rather than with fmt to avoid dependency
if val < 0 {
return "-" + uitoa(uint(-val))
}
return uitoa(uint(val))
}
func uitoa(val uint) string {
var buf [32]byte // big enough for int64
i := len(buf) - 1
for val >= 10 {
buf[i] = byte(val%10 + '0')
i--
val /= 10
}
buf[i] = byte(val + '0')
return string(buf[i:])
}
+123
View File
@@ -0,0 +1,123 @@
// +build avr cortexm
package syscall
// Most code here has been copied from the Go sources:
// https://github.com/golang/go/blob/go1.12/src/syscall/syscall_js.go
// It has the following copyright note:
//
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// A Signal is a number describing a process signal.
// It implements the os.Signal interface.
type Signal int
const (
_ Signal = iota
SIGCHLD
SIGINT
SIGKILL
SIGTRAP
SIGQUIT
SIGTERM
)
// File system
const (
Stdin = 0
Stdout = 1
Stderr = 2
)
const (
O_RDONLY = 0
O_WRONLY = 1
O_RDWR = 2
O_CREAT = 0100
O_CREATE = O_CREAT
O_TRUNC = 01000
O_APPEND = 02000
O_EXCL = 0200
O_SYNC = 010000
O_CLOEXEC = 0
)
func Getenv(key string) (value string, found bool) {
return "", false // stub
}
func Open(path string, mode int, perm uint32) (fd int, err error) {
return 0, ENOSYS
}
func Read(fd int, p []byte) (n int, err error) {
return 0, ENOSYS
}
func Seek(fd int, offset int64, whence int) (off int64, err error) {
return 0, ENOSYS
}
func Close(fd int) (err error) {
return ENOSYS
}
// Processes
type WaitStatus uint32
func (w WaitStatus) Exited() bool { return false }
func (w WaitStatus) ExitStatus() int { return 0 }
func (w WaitStatus) Signaled() bool { return false }
func (w WaitStatus) Signal() Signal { return 0 }
func (w WaitStatus) CoreDump() bool { return false }
func (w WaitStatus) Stopped() bool { return false }
func (w WaitStatus) Continued() bool { return false }
func (w WaitStatus) StopSignal() Signal { return 0 }
func (w WaitStatus) TrapCause() int { return 0 }
// XXX made up
type Rusage struct {
Utime Timeval
Stime Timeval
}
// XXX made up
type ProcAttr struct {
Dir string
Env []string
Files []uintptr
Sys *SysProcAttr
}
type SysProcAttr struct {
}
func Getegid() int { return 1 }
func Geteuid() int { return 1 }
func Getgid() int { return 1 }
func Getgroups() ([]int, error) { return []int{1}, nil }
func Getppid() int { return 2 }
func Getpid() int { return 3 }
func Gettimeofday(tv *Timeval) error { return ENOSYS }
func Getuid() int { return 1 }
func Kill(pid int, signum Signal) error { return ENOSYS }
func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) {
return 0, ENOSYS
}
func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
return 0, 0, ENOSYS
}
func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, err error) {
return 0, ENOSYS
}
type Timeval struct {
Sec int64
Usec int64
}
+51
View File
@@ -0,0 +1,51 @@
package syscall
// This file defines errno and constants to match the darwin libsystem ABI.
// Values have been determined experimentally by compiling some C code on macOS
// with Clang and looking at the resulting LLVM IR.
// This function returns the error location in the darwin ABI.
// Discovered by compiling the following code using Clang:
//
// #include <errno.h>
// int getErrno() {
// return errno;
// }
//
//go:export __error
func libc___error() *int32
// getErrno returns the current C errno. It may not have been caused by the last
// call, so it should only be relied upon when the last call indicates an error
// (for example, by returning -1).
func getErrno() Errno {
errptr := libc___error()
return Errno(uintptr(*errptr))
}
const (
ENOENT Errno = 2
EINTR Errno = 4
EMFILE Errno = 24
EAGAIN Errno = 35
ETIMEDOUT Errno = 60
ENOSYS Errno = 78
EWOULDBLOCK Errno = EAGAIN
)
type Signal int
const (
SIGCHLD Signal = 20
SIGINT Signal = 2
SIGKILL Signal = 9
SIGTRAP Signal = 5
SIGQUIT Signal = 3
SIGTERM Signal = 15
)
const (
O_RDONLY = 0
O_WRONLY = 1
O_RDWR = 2
)
+57
View File
@@ -0,0 +1,57 @@
// +build darwin
package syscall
import (
"unsafe"
)
func Close(fd int) (err error) {
return ENOSYS // TODO
}
func Write(fd int, p []byte) (n int, err error) {
buf, count := splitSlice(p)
n = libc_write(int32(fd), buf, uint(count))
if n < 0 {
err = getErrno()
}
return
}
func Read(fd int, p []byte) (n int, err error) {
return 0, ENOSYS // TODO
}
func Seek(fd int, offset int64, whence int) (off int64, err error) {
return 0, ENOSYS // TODO
}
func Open(path string, mode int, perm uint32) (fd int, err error) {
return 0, ENOSYS // TODO
}
func Kill(pid int, sig Signal) (err error) {
return ENOSYS // TODO
}
func Getpid() (pid int) {
panic("unimplemented: getpid") // TODO
}
func Getenv(key string) (value string, found bool) {
return "", false // TODO
}
func splitSlice(p []byte) (buf *byte, len uintptr) {
slice := (*struct {
buf *byte
len uintptr
cap uintptr
})(unsafe.Pointer(&p))
return slice.buf, slice.len
}
// ssize_t write(int fd, const void *buf, size_t count)
//go:export write
func libc_write(fd int32, buf *byte, count uint) int
+371
View File
@@ -0,0 +1,371 @@
// Copyright 2013 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.
// +build avr cortexm
package syscall
// TODO: generate with runtime/mknacl.sh, allow override with IRT.
const (
sys_null = 1
sys_nameservice = 2
sys_dup = 8
sys_dup2 = 9
sys_open = 10
sys_close = 11
sys_read = 12
sys_write = 13
sys_lseek = 14
sys_stat = 16
sys_fstat = 17
sys_chmod = 18
sys_isatty = 19
sys_brk = 20
sys_mmap = 21
sys_munmap = 22
sys_getdents = 23
sys_mprotect = 24
sys_list_mappings = 25
sys_exit = 30
sys_getpid = 31
sys_sched_yield = 32
sys_sysconf = 33
sys_gettimeofday = 40
sys_clock = 41
sys_nanosleep = 42
sys_clock_getres = 43
sys_clock_gettime = 44
sys_mkdir = 45
sys_rmdir = 46
sys_chdir = 47
sys_getcwd = 48
sys_unlink = 49
sys_imc_makeboundsock = 60
sys_imc_accept = 61
sys_imc_connect = 62
sys_imc_sendmsg = 63
sys_imc_recvmsg = 64
sys_imc_mem_obj_create = 65
sys_imc_socketpair = 66
sys_mutex_create = 70
sys_mutex_lock = 71
sys_mutex_trylock = 72
sys_mutex_unlock = 73
sys_cond_create = 74
sys_cond_wait = 75
sys_cond_signal = 76
sys_cond_broadcast = 77
sys_cond_timed_wait_abs = 79
sys_thread_create = 80
sys_thread_exit = 81
sys_tls_init = 82
sys_thread_nice = 83
sys_tls_get = 84
sys_second_tls_set = 85
sys_second_tls_get = 86
sys_exception_handler = 87
sys_exception_stack = 88
sys_exception_clear_flag = 89
sys_sem_create = 100
sys_sem_wait = 101
sys_sem_post = 102
sys_sem_get_value = 103
sys_dyncode_create = 104
sys_dyncode_modify = 105
sys_dyncode_delete = 106
sys_test_infoleak = 109
sys_test_crash = 110
sys_test_syscall_1 = 111
sys_test_syscall_2 = 112
sys_futex_wait_abs = 120
sys_futex_wake = 121
sys_pread = 130
sys_pwrite = 131
sys_truncate = 140
sys_lstat = 141
sys_link = 142
sys_rename = 143
sys_symlink = 144
sys_access = 145
sys_readlink = 146
sys_utimes = 147
sys_get_random_bytes = 150
)
// TODO: Auto-generate some day. (Hard-coded in binaries so not likely to change.)
const (
// native_client/src/trusted/service_runtime/include/sys/errno.h
// The errors are mainly copied from Linux.
EPERM Errno = 1 /* Operation not permitted */
ENOENT Errno = 2 /* No such file or directory */
ESRCH Errno = 3 /* No such process */
EINTR Errno = 4 /* Interrupted system call */
EIO Errno = 5 /* I/O error */
ENXIO Errno = 6 /* No such device or address */
E2BIG Errno = 7 /* Argument list too long */
ENOEXEC Errno = 8 /* Exec format error */
EBADF Errno = 9 /* Bad file number */
ECHILD Errno = 10 /* No child processes */
EAGAIN Errno = 11 /* Try again */
ENOMEM Errno = 12 /* Out of memory */
EACCES Errno = 13 /* Permission denied */
EFAULT Errno = 14 /* Bad address */
EBUSY Errno = 16 /* Device or resource busy */
EEXIST Errno = 17 /* File exists */
EXDEV Errno = 18 /* Cross-device link */
ENODEV Errno = 19 /* No such device */
ENOTDIR Errno = 20 /* Not a directory */
EISDIR Errno = 21 /* Is a directory */
EINVAL Errno = 22 /* Invalid argument */
ENFILE Errno = 23 /* File table overflow */
EMFILE Errno = 24 /* Too many open files */
ENOTTY Errno = 25 /* Not a typewriter */
EFBIG Errno = 27 /* File too large */
ENOSPC Errno = 28 /* No space left on device */
ESPIPE Errno = 29 /* Illegal seek */
EROFS Errno = 30 /* Read-only file system */
EMLINK Errno = 31 /* Too many links */
EPIPE Errno = 32 /* Broken pipe */
ENAMETOOLONG Errno = 36 /* File name too long */
ENOSYS Errno = 38 /* Function not implemented */
EDQUOT Errno = 122 /* Quota exceeded */
EDOM Errno = 33 /* Math arg out of domain of func */
ERANGE Errno = 34 /* Math result not representable */
EDEADLK Errno = 35 /* Deadlock condition */
ENOLCK Errno = 37 /* No record locks available */
ENOTEMPTY Errno = 39 /* Directory not empty */
ELOOP Errno = 40 /* Too many symbolic links */
ENOMSG Errno = 42 /* No message of desired type */
EIDRM Errno = 43 /* Identifier removed */
ECHRNG Errno = 44 /* Channel number out of range */
EL2NSYNC Errno = 45 /* Level 2 not synchronized */
EL3HLT Errno = 46 /* Level 3 halted */
EL3RST Errno = 47 /* Level 3 reset */
ELNRNG Errno = 48 /* Link number out of range */
EUNATCH Errno = 49 /* Protocol driver not attached */
ENOCSI Errno = 50 /* No CSI structure available */
EL2HLT Errno = 51 /* Level 2 halted */
EBADE Errno = 52 /* Invalid exchange */
EBADR Errno = 53 /* Invalid request descriptor */
EXFULL Errno = 54 /* Exchange full */
ENOANO Errno = 55 /* No anode */
EBADRQC Errno = 56 /* Invalid request code */
EBADSLT Errno = 57 /* Invalid slot */
EDEADLOCK Errno = EDEADLK /* File locking deadlock error */
EBFONT Errno = 59 /* Bad font file fmt */
ENOSTR Errno = 60 /* Device not a stream */
ENODATA Errno = 61 /* No data (for no delay io) */
ETIME Errno = 62 /* Timer expired */
ENOSR Errno = 63 /* Out of streams resources */
ENONET Errno = 64 /* Machine is not on the network */
ENOPKG Errno = 65 /* Package not installed */
EREMOTE Errno = 66 /* The object is remote */
ENOLINK Errno = 67 /* The link has been severed */
EADV Errno = 68 /* Advertise error */
ESRMNT Errno = 69 /* Srmount error */
ECOMM Errno = 70 /* Communication error on send */
EPROTO Errno = 71 /* Protocol error */
EMULTIHOP Errno = 72 /* Multihop attempted */
EDOTDOT Errno = 73 /* Cross mount point (not really error) */
EBADMSG Errno = 74 /* Trying to read unreadable message */
EOVERFLOW Errno = 75 /* Value too large for defined data type */
ENOTUNIQ Errno = 76 /* Given log. name not unique */
EBADFD Errno = 77 /* f.d. invalid for this operation */
EREMCHG Errno = 78 /* Remote address changed */
ELIBACC Errno = 79 /* Can't access a needed shared lib */
ELIBBAD Errno = 80 /* Accessing a corrupted shared lib */
ELIBSCN Errno = 81 /* .lib section in a.out corrupted */
ELIBMAX Errno = 82 /* Attempting to link in too many libs */
ELIBEXEC Errno = 83 /* Attempting to exec a shared library */
EILSEQ Errno = 84
EUSERS Errno = 87
ENOTSOCK Errno = 88 /* Socket operation on non-socket */
EDESTADDRREQ Errno = 89 /* Destination address required */
EMSGSIZE Errno = 90 /* Message too long */
EPROTOTYPE Errno = 91 /* Protocol wrong type for socket */
ENOPROTOOPT Errno = 92 /* Protocol not available */
EPROTONOSUPPORT Errno = 93 /* Unknown protocol */
ESOCKTNOSUPPORT Errno = 94 /* Socket type not supported */
EOPNOTSUPP Errno = 95 /* Operation not supported on transport endpoint */
EPFNOSUPPORT Errno = 96 /* Protocol family not supported */
EAFNOSUPPORT Errno = 97 /* Address family not supported by protocol family */
EADDRINUSE Errno = 98 /* Address already in use */
EADDRNOTAVAIL Errno = 99 /* Address not available */
ENETDOWN Errno = 100 /* Network interface is not configured */
ENETUNREACH Errno = 101 /* Network is unreachable */
ENETRESET Errno = 102
ECONNABORTED Errno = 103 /* Connection aborted */
ECONNRESET Errno = 104 /* Connection reset by peer */
ENOBUFS Errno = 105 /* No buffer space available */
EISCONN Errno = 106 /* Socket is already connected */
ENOTCONN Errno = 107 /* Socket is not connected */
ESHUTDOWN Errno = 108 /* Can't send after socket shutdown */
ETOOMANYREFS Errno = 109
ETIMEDOUT Errno = 110 /* Connection timed out */
ECONNREFUSED Errno = 111 /* Connection refused */
EHOSTDOWN Errno = 112 /* Host is down */
EHOSTUNREACH Errno = 113 /* Host is unreachable */
EALREADY Errno = 114 /* Socket already connected */
EINPROGRESS Errno = 115 /* Connection already in progress */
ESTALE Errno = 116
ENOTSUP Errno = EOPNOTSUPP /* Not supported */
ENOMEDIUM Errno = 123 /* No medium (in tape drive) */
ECANCELED Errno = 125 /* Operation canceled. */
ELBIN Errno = 2048 /* Inode is remote (not really error) */
EFTYPE Errno = 2049 /* Inappropriate file type or format */
ENMFILE Errno = 2050 /* No more files */
EPROCLIM Errno = 2051
ENOSHARE Errno = 2052 /* No such host or network path */
ECASECLASH Errno = 2053 /* Filename exists with different case */
EWOULDBLOCK Errno = EAGAIN /* Operation would block */
)
// Do the interface allocations only once for common
// Errno values.
var (
errEAGAIN error = EAGAIN
errEINVAL error = EINVAL
errENOENT error = ENOENT
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e Errno) error {
switch e {
case 0:
return nil
case EAGAIN:
return errEAGAIN
case EINVAL:
return errEINVAL
case ENOENT:
return errENOENT
}
return e
}
var errnoByCode = map[string]Errno{
"EPERM": EPERM,
"ENOENT": ENOENT,
"ESRCH": ESRCH,
"EINTR": EINTR,
"EIO": EIO,
"ENXIO": ENXIO,
"E2BIG": E2BIG,
"ENOEXEC": ENOEXEC,
"EBADF": EBADF,
"ECHILD": ECHILD,
"EAGAIN": EAGAIN,
"ENOMEM": ENOMEM,
"EACCES": EACCES,
"EFAULT": EFAULT,
"EBUSY": EBUSY,
"EEXIST": EEXIST,
"EXDEV": EXDEV,
"ENODEV": ENODEV,
"ENOTDIR": ENOTDIR,
"EISDIR": EISDIR,
"EINVAL": EINVAL,
"ENFILE": ENFILE,
"EMFILE": EMFILE,
"ENOTTY": ENOTTY,
"EFBIG": EFBIG,
"ENOSPC": ENOSPC,
"ESPIPE": ESPIPE,
"EROFS": EROFS,
"EMLINK": EMLINK,
"EPIPE": EPIPE,
"ENAMETOOLONG": ENAMETOOLONG,
"ENOSYS": ENOSYS,
"EDQUOT": EDQUOT,
"EDOM": EDOM,
"ERANGE": ERANGE,
"EDEADLK": EDEADLK,
"ENOLCK": ENOLCK,
"ENOTEMPTY": ENOTEMPTY,
"ELOOP": ELOOP,
"ENOMSG": ENOMSG,
"EIDRM": EIDRM,
"ECHRNG": ECHRNG,
"EL2NSYNC": EL2NSYNC,
"EL3HLT": EL3HLT,
"EL3RST": EL3RST,
"ELNRNG": ELNRNG,
"EUNATCH": EUNATCH,
"ENOCSI": ENOCSI,
"EL2HLT": EL2HLT,
"EBADE": EBADE,
"EBADR": EBADR,
"EXFULL": EXFULL,
"ENOANO": ENOANO,
"EBADRQC": EBADRQC,
"EBADSLT": EBADSLT,
"EDEADLOCK": EDEADLOCK,
"EBFONT": EBFONT,
"ENOSTR": ENOSTR,
"ENODATA": ENODATA,
"ETIME": ETIME,
"ENOSR": ENOSR,
"ENONET": ENONET,
"ENOPKG": ENOPKG,
"EREMOTE": EREMOTE,
"ENOLINK": ENOLINK,
"EADV": EADV,
"ESRMNT": ESRMNT,
"ECOMM": ECOMM,
"EPROTO": EPROTO,
"EMULTIHOP": EMULTIHOP,
"EDOTDOT": EDOTDOT,
"EBADMSG": EBADMSG,
"EOVERFLOW": EOVERFLOW,
"ENOTUNIQ": ENOTUNIQ,
"EBADFD": EBADFD,
"EREMCHG": EREMCHG,
"ELIBACC": ELIBACC,
"ELIBBAD": ELIBBAD,
"ELIBSCN": ELIBSCN,
"ELIBMAX": ELIBMAX,
"ELIBEXEC": ELIBEXEC,
"EILSEQ": EILSEQ,
"EUSERS": EUSERS,
"ENOTSOCK": ENOTSOCK,
"EDESTADDRREQ": EDESTADDRREQ,
"EMSGSIZE": EMSGSIZE,
"EPROTOTYPE": EPROTOTYPE,
"ENOPROTOOPT": ENOPROTOOPT,
"EPROTONOSUPPORT": EPROTONOSUPPORT,
"ESOCKTNOSUPPORT": ESOCKTNOSUPPORT,
"EOPNOTSUPP": EOPNOTSUPP,
"EPFNOSUPPORT": EPFNOSUPPORT,
"EAFNOSUPPORT": EAFNOSUPPORT,
"EADDRINUSE": EADDRINUSE,
"EADDRNOTAVAIL": EADDRNOTAVAIL,
"ENETDOWN": ENETDOWN,
"ENETUNREACH": ENETUNREACH,
"ENETRESET": ENETRESET,
"ECONNABORTED": ECONNABORTED,
"ECONNRESET": ECONNRESET,
"ENOBUFS": ENOBUFS,
"EISCONN": EISCONN,
"ENOTCONN": ENOTCONN,
"ESHUTDOWN": ESHUTDOWN,
"ETOOMANYREFS": ETOOMANYREFS,
"ETIMEDOUT": ETIMEDOUT,
"ECONNREFUSED": ECONNREFUSED,
"EHOSTDOWN": EHOSTDOWN,
"EHOSTUNREACH": EHOSTUNREACH,
"EALREADY": EALREADY,
"EINPROGRESS": EINPROGRESS,
"ESTALE": ESTALE,
"ENOTSUP": ENOTSUP,
"ENOMEDIUM": ENOMEDIUM,
"ECANCELED": ECANCELED,
"ELBIN": ELBIN,
"EFTYPE": EFTYPE,
"ENMFILE": ENMFILE,
"EPROCLIM": EPROCLIM,
"ENOSHARE": ENOSHARE,
"ECASECLASH": ECASECLASH,
"EWOULDBLOCK": EWOULDBLOCK,
}
-3
View File
@@ -1,3 +0,0 @@
package unsafe
// dummy
+1
View File
@@ -54,6 +54,7 @@ SECTIONS
/DISCARD/ :
{
*(.ARM.exidx) /* causes 'no memory region specified' error in lld */
*(.ARM.exidx.*) /* causes spurious 'undefined reference' errors */
}
}
+3 -3
View File
@@ -1,7 +1,7 @@
{
"build-tags": ["avr", "js", "wasm"],
"goos": "js",
"goarch": "wasm",
"build-tags": ["avr", "linux", "arm"],
"goos": "linux",
"goarch": "arm",
"compiler": "avr-gcc",
"linker": "avr-gcc",
"ldflags": [
+5 -5
View File
@@ -1,10 +1,10 @@
{
"build-tags": ["tinygo.arm", "js", "wasm"],
"goos": "js",
"goarch": "wasm",
"compiler": "clang-7",
"build-tags": ["cortexm", "linux", "arm"],
"goos": "linux",
"goarch": "arm",
"compiler": "clang-8",
"gc": "marksweep",
"linker": "arm-none-eabi-ld",
"linker": "ld.lld",
"rtlib": "compiler-rt",
"cflags": [
"-Oz",
+10
View File
@@ -0,0 +1,10 @@
MEMORY
{
FLASH_TEXT (rw) : ORIGIN = 0x08000000, LENGTH = 1M
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
}
_stack_size = 4K;
INCLUDE "targets/arm.ld"
+18
View File
@@ -0,0 +1,18 @@
{
"inherits": ["cortex-m"],
"llvm-target": "armv7em-none-eabi",
"build-tags": ["stm32f4disco", "stm32f407", "stm32"],
"cflags": [
"--target=armv7em-none-eabi",
"-Qunused-arguments"
],
"ldflags": [
"-T", "targets/stm32f407.ld"
],
"extra-files": [
"src/device/stm32/stm32f407.s"
],
"flash": "openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c 'program {hex} reset exit'",
"ocd-daemon": ["openocd", "-f", "interface/stlink.cfg", "-f", "target/stm32f4x.cfg"],
"gdb-initial-cmds": ["target remote :3333", "monitor halt", "load", "monitor reset", "c"]
}
+4 -3
View File
@@ -3,14 +3,15 @@
"build-tags": ["js", "wasm"],
"goos": "js",
"goarch": "wasm",
"compiler": "clang-7",
"linker": "wasm-ld-7",
"compiler": "clang-8",
"linker": "wasm-ld",
"cflags": [
"--target=wasm32",
"-Oz"
],
"ldflags": [
"-allow-undefined"
"--allow-undefined",
"--export-all"
],
"emulator": ["node", "targets/wasm_exec.js"]
}
+19
View File
@@ -8,6 +8,11 @@ double globalDouble = 3.2;
_Complex float globalComplexFloat = 4.1+3.3i;
_Complex double globalComplexDouble = 4.2+3.4i;
_Complex double globalComplexLongDouble = 4.3+3.5i;
collection_t globalStruct = {256, -123456, 3.14, 88};
int globalStructSize = sizeof(globalStruct);
short globalArray[3] = {5, 6, 7};
joined_t globalUnion;
int globalUnionSize = sizeof(globalUnion);
int fortytwo() {
return 42;
@@ -24,3 +29,17 @@ int doCallback(int a, int b, binop_t callback) {
void store(int value, int *ptr) {
*ptr = value;
}
void unionSetShort(short s) {
globalUnion.s = s;
}
void unionSetFloat(float f) {
globalUnion.f = f;
}
void unionSetData(short f0, short f1, short f2) {
globalUnion.data[0] = 5;
globalUnion.data[1] = 8;
globalUnion.data[2] = 1;
}
+34
View File
@@ -9,6 +9,10 @@ import "C"
import "unsafe"
func (s C.myint) Int() int {
return int(s)
}
func main() {
println("fortytwo:", C.fortytwo())
println("add:", C.add(C.int(3), 5))
@@ -36,6 +40,36 @@ func main() {
println("complex float:", C.globalComplexFloat)
println("complex double:", C.globalComplexDouble)
println("complex long double:", C.globalComplexLongDouble)
// complex types
println("struct:", C.int(unsafe.Sizeof(C.globalStruct)) == C.globalStructSize, C.globalStruct.s, C.globalStruct.l, C.globalStruct.f)
var _ [3]C.short = C.globalArray
println("array:", C.globalArray[0], C.globalArray[1], C.globalArray[2])
println("union:", C.int(unsafe.Sizeof(C.globalUnion)) == C.globalUnionSize)
C.unionSetShort(22)
println("union s:", C.globalUnion.s)
C.unionSetFloat(3.14)
println("union f:", C.globalUnion.f)
C.unionSetData(5, 8, 1)
println("union global data:", C.globalUnion.data[0], C.globalUnion.data[1], C.globalUnion.data[2])
println("union field:", printUnion(C.globalUnion).f)
// recursive types, test using a linked list
lastElement := &C.list_t{n: 7, next: nil}
list := &C.list_t{n: 3, next: &C.struct_list_t{n: 6, next: (*C.struct_list_t)(lastElement)}}
for list != nil {
println("n in chain:", list.n)
list = (*C.list_t)(list.next)
}
}
func printUnion(union C.joined_t) C.joined_t {
println("union local data: ", union.data[0], union.data[1], union.data[2])
union.s = -33
println("union s method:", union.s.Int(), union.data[0] == 5)
union.f = 6.28
println("union f:", union.f)
return union
}
//export mul
+27
View File
@@ -5,6 +5,28 @@ int doCallback(int a, int b, binop_t cb);
typedef int * intPointer;
void store(int value, int *ptr);
typedef struct collection {
short s;
long l;
float f;
unsigned char c;
} collection_t;
// linked list
typedef struct list_t {
int n;
struct list_t *next;
} list_t;
typedef union joined {
myint s;
float f;
short data[3];
} joined_t;
void unionSetShort(short s);
void unionSetFloat(float f);
void unionSetData(short f0, short f1, short f2);
// test globals
extern int global;
extern _Bool globalBool;
@@ -14,6 +36,11 @@ extern double globalDouble;
extern _Complex float globalComplexFloat;
extern _Complex double globalComplexDouble;
extern _Complex double globalComplexLongDouble;
extern collection_t globalStruct;
extern int globalStructSize;
extern short globalArray[3];
extern joined_t globalUnion;
extern int globalUnionSize;
// test duplicate definitions
int add(int a, int b);
+13
View File
@@ -14,3 +14,16 @@ double: +3.200000e+000
complex float: (+4.100000e+000+3.300000e+000i)
complex double: (+4.200000e+000+3.400000e+000i)
complex long double: (+4.300000e+000+3.500000e+000i)
struct: true 256 -123456 +3.140000e+000
array: 5 6 7
union: true
union s: 22
union f: +3.140000e+000
union global data: 5 8 1
union local data: 5 8 1
union s method: -33 false
union f: +6.280000e+000
union field: +6.280000e+000
n in chain: 3
n in chain: 6
n in chain: 7
+18
View File
@@ -43,6 +43,10 @@ func main() {
}
println("sum(100):", sum)
// Test select
go selectDeadlock()
go selectNoOp()
// Allow goroutines to exit.
time.Sleep(time.Microsecond)
}
@@ -93,3 +97,17 @@ func iterator(ch chan int, top int) {
}
close(ch)
}
func selectDeadlock() {
println("deadlocking")
select {}
println("unreachable")
}
func selectNoOp() {
println("select no-op")
select {
default:
}
println("after no-op")
}
+3
View File
@@ -19,3 +19,6 @@ sum: 25
sum: 29
sum: 33
sum(100): 4950
deadlocking
select no-op
after no-op
+3
View File
@@ -2,6 +2,7 @@ package main
import (
"fmt"
"math/rand"
"os"
)
@@ -9,4 +10,6 @@ func main() {
fmt.Println("stdin: ", os.Stdin.Fd())
fmt.Println("stdout:", os.Stdout.Fd())
fmt.Println("stderr:", os.Stderr.Fd())
fmt.Println("pseudorandom number:", rand.Int31())
}
+1
View File
@@ -1,3 +1,4 @@
stdin: 0
stdout: 1
stderr: 2
pseudorandom number: 1298498081
+18
View File
@@ -28,6 +28,14 @@ type s4 struct {
d byte
}
// same struct, different type
type s4b struct {
a byte
b byte
c byte
d byte
}
type s5 struct {
a struct {
aa byte
@@ -70,6 +78,16 @@ func test3(s s3) {
func test4(s s4) {
println("test4", s.a, s.b, s.c, s.d)
test4b(s4b(s))
test4bp((*s4b)(&s))
}
func test4b(s s4b) {
println("test4b", s.a, s.b, s.c, s.d)
}
func test4bp(s *s4b) {
println("test4bp", s.a, s.b, s.c, s.d)
}
func test5(s s5) {
+2
View File
@@ -3,6 +3,8 @@ test1 1
test2 1 2
test3 1 2 3
test4 1 2 3 4
test4b 1 2 3 4
test4bp 1 2 3 4
test5 1 2 3
test6 foo 3 5
test7 (0:nil) 8
+1 -1
View File
@@ -15,7 +15,7 @@ import (
// ConvertELFFileToUF2File converts an ELF file to a UF2 file.
func ConvertELFFileToUF2File(infile, outfile string) error {
// Read the .text segment.
_, data, err := ExtractTextSegment(infile)
_, data, err := ExtractROM(infile)
if err != nil {
return err
}
+1 -1
View File
@@ -2,4 +2,4 @@ package main
// version of this package.
// Update this value before release of new version of software.
const version = "0.4.0"
const version = "0.5.0"