Compare commits

...

176 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
Ayke van Laethem b1744db2c8 main: version 0.4.0 2019-03-09 20:41:38 +01:00
Ayke van Laethem bd6a7b69ce compiler: inline slice bounds checking
This improves code size in all tests by about 1% and up to 5% in some
cases, likely because LLVM can better reason about inline bounds checks.
2019-03-08 19:11:22 +01:00
Ayke van Laethem 051ad07755 compiler: refactor slice related asserts
Move these asserts into compiler/asserts.go, to keep them together.

The make([]T) asserts aren't moved yet because that code is (still!)
quite ugly and in need of some clean up.
2019-03-08 19:11:22 +01:00
Ron Evans 09e85b7859 machine/stm32f103xx: correct convertion for fractional timing of RTC as used in ticks() function
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-03-08 17:52:51 +01:00
Ayke van Laethem 622d0ebde6 compiler: implement nil checks
This commit implements nil checks for all platforms. These nil checks
can be optimized on systems with a MMU, but since a major target is
systems without MMU, keep it this way for now.

It implements three checks:
  * Nil checks before dereferencing a pointer.
  * Nil checks before calculating an address (*ssa.FieldAddr and
    *ssa.IndexAddr)
  * Nil checks before calling a function pointer.

The first check has by far the biggest impact, with around 5% increase
in code size. The other checks only trigger in only some test cases and
have a minimal impact on code size.
This first nil check is also the one that is easiest to avoid on systems
with MMU, if necessary.
2019-03-08 17:36:53 +01:00
Ayke van Laethem b7cdf8cd0c interp: refactor to eliminate lots of code
This may cause a small performance penalty, but the code is easier to
maange as a result.
2019-03-08 17:36:53 +01:00
Ayke van Laethem cfc1a66e8d interp: use correct initialization order on panic() calls
Whenever interp hits an unreachable instruction, it bails out at that
point. However, it used to insert new instructions at the bottom with
the old init calls still at the top. So when a panic() happened in a
non-main package, the last packages to init would actually be called
first.

This commit fixes this by setting the insert point at the top of
runtime.initAll before starting interpretation, so the initialization
order is still correct when a panic() happens during init.
2019-03-07 16:22:06 +01:00
Ayke van Laethem 4ad9bd8643 wasm: ignore arguments and environment variables
The wasm_exec.js file copied from the main Go repository did write those
values to address 4096 in linear memory, which led to memory corruption
in linear memory. Remove these things for now, until they're actually
supported, if support is ever added.
2019-03-07 13:13:11 +01:00
Ron Evans 2a1dd98661 compiler: support output file using UF2 bootloader format
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-03-06 18:18:23 +01:00
Ayke van Laethem 2c03192691 LICENSE: update author and year 2019-03-06 17:15:31 +01:00
Ron Evans 9d6df2b4c7 machine/samd21: implement ADC
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-03-06 17:01:16 +01:00
Ayke van Laethem 5939729c45 main: only run WebAssembly tests on Linux
The WebAssembly target is not yet considered stable in LLVM 7, but has
been enabled in the Debian builds so tests can run on Debian. However,
the Homebrew builds don't have it enabled which results in test
failures.

Temporarily run WebAssembly tests only on Linux to fix this. This can be
reverted after a switch to LLVM 8, which has WebAssembly enabled by
default.
2019-03-06 11:28:59 +01:00
Ayke van Laethem c7b91da8c4 compiler: support function pointers outside of addrspace 0
In LLVM 8, the AVR backend has moved all function pointers to address
space 1 by default. Much of the code still assumes function pointers
live in address space 0, leading to assertion failures.

This commit fixes this problem by autodetecting function pointers and
avoiding them in interface pseudo-calls.
2019-03-05 19:54:55 +01:00
Ayke van Laethem c7fdb6741f compiler: rename biggestInt → capacityType 2019-03-05 19:25:42 +01:00
Ayke van Laethem b837c94366 compiler: calculate max number of entries in slice at compile time
This avoids difficult multiply-with-overflow code and avoids a multiply
at runtime.
2019-03-05 19:25:42 +01:00
Ayke van Laethem 26e7e93478 compiler: make sure make([]T, ...) checks for Ts bigger than 1
Without this, the following code would not panic:

    func getInt(i int) { return i }
    make([][1<<18], getInt(1<<18))

Or this code would be allowed to compile for 32-bit systems:

    make([][1<<18], 1<<18)
2019-03-05 19:25:42 +01:00
Ayke van Laethem 8e99c3313b compiler: fix make([]T, ...) with big integers on 32-bit systems or less
Previously, this would have resulted in a LLVM verification error
because runtime.sliceBoundsCheckMake would not accept 64-bit integers on
these platforms.
2019-03-05 19:25:42 +01:00
Ron Evans 28987ae061 docs: update README with recently added Adafruit Circuit Playground Express board
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-03-05 10:08:39 +01:00
Ayke van Laethem b594f212fb test: add WebAssembly tests 2019-03-04 21:58:40 +01:00
Ayke van Laethem 41e093d7bb wasm: switch emulator to node.js
Unfortunately, the olin/cwa emulator does not handle floats correctly.
Node.js does, and because it is also supported by the Go WebAssembly
implementation it has better support in general.
2019-03-04 21:58:40 +01:00
Ron Evans 665c3bdaa6 machine/samd21: implement SPI interface for currently supported SAMD21 boards
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-03-04 21:47:09 +01:00
Ayke van Laethem ea3d232c84 circleci: replace Linux tests on Travis CI with CircleCI
CircleCI is faster and has more features than Travis CI. Additionally,
based on the recent news, the future of Travis CI is rather uncertain.

Keep using Travis CI for macOS testing at the moment, as open source
projects will need to get special permission to use CircleCI for macOS
tests.
2019-03-04 21:42:12 +01:00
Ayke van Laethem 4f932b6e66 all: use internal objcopy implementation
This lessens the dependency on binutils (e.g. arm-none-eabi-objcopy).
2019-03-04 21:17:56 +01:00
Ron Evans 3538ba943c machine/samd21: move definitions for I2C interfaces into board files, since pin connections on each SAMD21-based board implementation can differ
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-03-04 20:54:13 +01:00
Ron Evans 543696eafc machine/samd21: correct get/setPinCfg and get/setPMux functions for PORTB pins
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-03-04 20:53:07 +01:00
Ron Evans 6e5ae83302 machine/samd21: init all SERCOM clocks to better handle board variants
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-03-04 17:17:03 +01:00
Ayke van Laethem 9b4071237f arm: switch to hardfloat ABI for Linux
This avoids an error on the Raspberry Pi 3.
2019-03-01 20:36:12 +01:00
Ayke van Laethem 1c68da89af main: version 0.3.0 2019-02-27 12:14:04 +01:00
Ron Evans 4424fe087d machine/circuitplay_express: add basic support for Adafruit Circuit Playground express pin mappings
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-02-24 23:01:22 +01:00
Ron Evans 34939ab422 machine/atsamd21: add GPIO_INPUT_PULLUP and GPIO_INPUT_PULLDOWN GPIO pin config options
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-02-24 21:20:59 +01:00
Ron Evans c56b2a45fa machine/samd21: handle PINMUX and PINCFG registers correctly for PORTB pins
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-02-24 17:43:11 +01:00
Ayke van Laethem b1c70d85f7 nrf: add CPU frequency 2019-02-24 13:45:10 +01:00
Ayke van Laethem 714d98354c arm: provide intrinsics to disable/enable interrupts 2019-02-23 18:52:49 +01:00
Ayke van Laethem 6e8df2fc40 samd21: define and use hardware pin numbers 2019-02-23 16:20:56 +01:00
Ayke van Laethem 902f40867f samd21: add GPIO support for port B 2019-02-23 13:53:59 +01:00
Ron Evans 5438f16fcb machine/atsamd21: support for USB CDC aka serial interface
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-02-23 13:34:00 +01:00
Ron Evans 7f027ddd33 machine/samd21: correct calculation for runtime ticks() function so that go routine scheduling can function as expected as described in issue #149
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-02-23 13:22:24 +01:00
Ron Evans acaf096586 compiler: extend flash command to support different output file types, based on contents of flash key in target file
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-02-23 11:31:38 +01:00
Ron Evans 942d4903ce machine/atsamd21: extracts functionality for processor family into shared files.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-02-20 14:16:09 +01:00
Ayke van Laethem 0b212cf2f6 all: add macOS support 2019-02-19 15:54:36 +01:00
Ron Evans 2d5bc836f5 build: correct Makefile to build tinygo executable correctly when build directory does not exist, such as after running 'make clean'
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-02-19 12:28:50 +01:00
Ayke van Laethem 856e5fa179 ir: remove old cgo related code
There is now a custom implementation of CGo based on libclang.
2019-02-19 09:08:13 +01:00
Ayke van Laethem 07733ca056 compiler: remove some dead code reported by go vet 2019-02-19 09:08:13 +01:00
Ayke van Laethem 92d9b780b5 all: remove init interpretation during IR construction
The interp package does a much better job at interpretation, and is
implemented as a pass on the IR which makes it much easier to compose.
Also, the implementation works much better as it is based on LLVM IR
instead of Go SSA.
2019-02-19 09:08:13 +01:00
Ayke van Laethem da345e8723 cgo: implement bool/float/complex types 2019-02-18 17:17:56 +01:00
Ayke van Laethem fab38a0749 compiler: use Clang data layout for complex numbers
Match data layout of complex numbers to that of Clang, for better
interoperability. This makes alignment of complex numbes the same as the
individual elements (real and imaginary), as is required by the C spec
and implemented in Clang, but unlike the gc compler. The Go language
specification is silent on this matter.

> Each complex type has the same object representation and alignment
> requirements as an array of two elements of the corresponding real
> type (float for float complex, double for double complex, long double
> for long double complex). The first element of the array holds the
> real part, and the second element of the array holds the imaginary
> component.

Source: https://en.cppreference.com/w/c/language/arithmetic_types
2019-02-18 17:17:56 +01:00
Daniel Esteban 0a3dbbd1cb Added regular pins const for bbc:microbit (#181)
* Added "GPIO/Analog" pins const for bbc:microbit
2019-02-11 16:33:10 +01:00
admin 4c29f0fdb6 wasm: support wasm example on Safari 2019-02-11 14:20:20 +01:00
Ayke van Laethem fbc2099ee3 main: version v0.2.0 2019-02-08 16:54:50 +01:00
Ayke van Laethem 95d895646a loader/cgo: add support for function pointers 2019-02-08 13:19:02 +01:00
Ayke van Laethem 35fb594f8f loader/cgo: add support for pointer types 2019-02-08 13:19:02 +01:00
Ayke 01f6aff422 loader: support global variables in CGo (#173)
Global variables (like functions) must be declared in the import "C" preamble and can then be used from Go.
2019-02-08 13:04:03 +01:00
Ron Evans 7dd5839f47 build: display output file sizes for target smoke test builds on Travis (#175)
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-02-08 13:03:20 +01:00
Ron Evans 403fee7e06 Add tinygo version subcommand (#172)
* cmd: add tinygo version subcommand to display current software version. Also displayed when usage is displayed
2019-02-08 11:22:47 +01:00
Ron Evans 7657238c24 docs: refactor README content (#171)
* docs: refactor README to avoid duplication with information on the web site, and to reorder to make it easier for new users.
2019-02-08 08:59:06 +01:00
Ayke van Laethem 3cba36f2ba compiler: add syscalls for 64-bit arm 2019-02-07 07:00:37 +01:00
Ayke van Laethem 93d5269fef compiler: add syscalls for 32-bit arm 2019-02-07 07:00:37 +01:00
Ayke van Laethem 4b477fad55 all: update Travis CI to Ubuntu Xenial
This lets us test with a more recent base, and should fix various
issues.
2019-02-05 19:39:33 +01:00
Ayke van Laethem 6360e318a7 runtime: add support for math package
The math package uses routines written in Go assembly language which
LLVM/Clang cannot parse. Additionally, not all instruction sets are
supported.

Redirect all math functions written in assembly to their Go equivalent.
This is not the fastest option, but it gets packages requiring math
functions to work.
2019-02-05 19:37:21 +01:00
Ayke van Laethem 0757eb5919 main: link with --gc-sections
This may help reduce code size in some cases.
2019-02-05 19:37:21 +01:00
Ayke van Laethem 013a71aa3d compiler: support NaN in float comparisons
LLVM supports both "ordered" and "unordered" floating point comparisons.
The difference is that unordered comparisons ignore NaN and produce
incorrect results when presented with a NaN value.
This commit switches these comparisons from ordered to unordered.
2019-02-05 19:37:21 +01:00
Ron Evans f0091b31b5 Add CONTRIBUTING.md to help us help you (#169)
* docs: add contributing guidelines to make it easier to help the project

Signed-off-by: Ron Evans <ron@hybridgroup.com>

* docs: add info about Slack channel to contributing guidelines

Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-02-05 18:25:19 +01:00
Ayke van Laethem 709a296150 os: add basic OS functionality 2019-02-05 17:37:55 +01:00
Ayke van Laethem f7b2a2c977 compiler: implement syscall.Syscall* as builtins
Treating them as builtins is easier to implement and likely reduces code
size.
2019-02-05 17:37:55 +01:00
Ayke van Laethem 6ae4b43eb2 interp: fix recursive scanning
There were a few issues that made interp not perform as it should:

  * The scan was non-recursive due to a bug.
  * Recursive scanning would always return the severity level, which is
    not always the best strategy.
2019-02-05 17:37:55 +01:00
Ayke van Laethem bece6b9648 interp: remove init call when hitting 'unreachable'
The interp package interprets calls in runtime.initAll and replaces
these calls with non-interpretable instructions if needed.
When hitting an unreachable instruction, this call should be removed,
but it wasn't. This commit makes sure the call is removed even before
trying to interpret the package init function.
2019-02-05 17:37:55 +01:00
Ayke van Laethem 553f00bdb8 interp: fix uintptr type context in interface
This bug led to a non-obvious validation error after interp had run.
2019-02-05 17:37:55 +01:00
Ayke van Laethem a789108926 test: add -short flag that only tests on the host 2019-02-05 17:37:55 +01:00
Ayke van Laethem d63ce0646c compiler: avoid all debug info with -no-debug 2019-02-05 17:37:55 +01:00
Ayke van Laethem 003211b4ff reflect: implement Value.Set*() for basic types 2019-02-05 17:11:09 +01:00
Ayke van Laethem e6720d7ddc compiler: add support for comparing complex numbers 2019-02-05 17:11:09 +01:00
Ayke van Laethem dfef168139 reflect: add limited support for all type kinds
This commit makes sure all Go types can be encoded in the interface type
code, so that Type.Kind() always returns a proper type kind for any
non-nil interface.
2019-02-05 17:11:09 +01:00
Ayke van Laethem 101f2e519b compiler: add support for zero channel constant 2019-02-05 17:11:09 +01:00
Ayke van Laethem eb34afde4b amd64: align on 16 bytes instead of 8
Some instructions emitted by LLVM (like movaps) expect 16-byte
alignment, while the allocator assumed that 8-byte alignment is good
enough.

TODO: this issue came to light with LLVM optimizing a complex128 store
to the movaps instruction, which must be aligned. It looks like this
means that the <2 x double> IR type is actually 16-byte aligned instead
of 8-byte like a double. If this is the case, the alignment of complex
numbers needs to be updated in the whole compiler.
2019-02-05 17:11:09 +01:00
Ayke van Laethem 63f2a3dfe9 reflect: support slices and indexing of strings and slices 2019-02-05 17:11:09 +01:00
Ayke van Laethem fb23e9c212 reflect: add support for non-named basic types 2019-02-05 17:11:09 +01:00
Ayke van Laethem 222c4c75b1 test: check for errors when globbing test packages 2019-02-05 17:11:08 +01:00
Ayke van Laethem 5a509f5bfe compiler: support some more types in interfaces 2019-02-05 17:11:08 +01:00
Ayke van Laethem 2e46275c45 compiler: support complex64 constants 2019-02-05 17:11:08 +01:00
Ayke van Laethem 1d34f868da compiler: sort interface type codes in reverse order
This makes sure the most commonly used types have the lowest type codes.
This was intended to be the case, but apparently I forgot to sort them
the right way.
2019-02-05 17:11:08 +01:00
Ayke van Laethem f0904779a5 reflect: add reflect.TypeOf
This is the beginning of true reflection support in TinyGo.
2019-02-05 17:11:08 +01:00
Samuel Lang 70f1064f36 making Docker build resilient (#168)
Currently, if the user hasn't run
`git submodule update --init` beforehand, the docker build will fail

This little addition makes the build atomic and ready for automatic CI tests for the future
2019-02-05 15:57:52 +01:00
Ayke van Laethem 930de54dc5 main: add instructions how to build a release tarball 2019-02-01 13:26:32 +01:00
Ayke van Laethem 25cd982263 main: optionally build with LLD
When building statically against LLVM, LLD is also included now. When
included, the built in wasm-ld will automatically be used instead of the
external command.

There is also support for linking ELF files but because lld does not
fully support armv6m this is not yet enabled (it produces a warning).
2019-02-01 13:26:32 +01:00
Ayke van Laethem 9bbb233cf0 main: include prebuilt compiler-rt libraries in release tarball
This avoids depending on clang-7 to build compiler-rt for the most
common ARM microcontrollers.
2019-02-01 13:26:32 +01:00
Ayke van Laethem 5b507593d2 main: give more context when running an external results in an error 2019-02-01 13:26:32 +01:00
Ayke van Laethem 05d2c14600 all: add static and release Makefile targets 2019-02-01 13:26:32 +01:00
Ayke van Laethem e7ad366f20 target: detect source directory more reliably
Use the binary path as a first guess.
2019-02-01 13:26:32 +01:00
Ayke van Laethem 8aed8d8c56 Makefile: rename tgo to tinygo 2019-02-01 13:26:32 +01:00
Ron Evans c3a15885f5 machine/itsybitsym0: correct comments for UART1 pin mapping and note which of the analog pin mapping require the second port, which is not yet implemented
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-02-01 13:15:01 +01:00
Ayke van Laethem 914d383a96 all: update import paths to github.com/tinygo-org/tinygo 2019-01-31 17:43:19 +01:00
Konstantin Yegupov 504c82a0e7 compiler: support for byte arrays as keys in maps 2019-01-31 16:35:22 +01:00
Konstantin Yegupov f8a1e5f449 interp: support map literals with integer keys 2019-01-31 16:34:59 +01:00
Konstantin Yegupov 0308c92e67 compiler: better error message on trying to do a map lookup using an unsupported type 2019-01-31 16:31:08 +01:00
Ayke van Laethem 1db9a162da all: go fmt
The import path changes changed the order of imports, but no `go fmt`
was run before the commit. Oops...
2019-01-31 16:22:05 +01:00
Ron Evans 19b4476cbb Implement PWM interface for SAMD21 (#157)
* machine/atsamd21: implement PWM interface for all pins that support it
* machine/atsamd21: correct PWM channel mapping for pin PA18
* machine/atsamd21: move clock init into InitPWM() to hopefully save power
2019-01-28 13:48:52 +01:00
Michael Teichgraeber 7461c298dd runtime: make stringNext use byteindex only, fix index offset
Use stringIterator.byteindex as the loop index, and remove
stringIterator.rangeindex, as "the index of the loop is the starting
position of the current rune, measured in bytes".  This patch also fixes
the current loop index returned by stringNext, using `it.byteindex'
before - not after - `length' is added.
2019-01-27 23:31:43 +01:00
Ayke van Laethem 9092dbcc53 all: rename go-llvm to new import path
the new import path is:

    tinygo.org/x/go-llvm
2019-01-27 19:26:16 +01:00
Konstantin Yegupov e6d90d89fa loader: better error message on import cycles 2019-01-27 10:38:02 +01:00
Ron Evans 4f4d7976c6 Add core support for multiple UARTs (#152)
* machine/uart: add core support for multiple UARTs by allowing for multiple RingBuffers
* machine/uart: complete core support for multiple UARTs
* machine/uart: no need to store pointer to UART, better to treat like I2C and SPI
* machine/uart: increase ring buffer size to 128 bytes
* machine/uart: improve godocs comments and use comma-ok idiom for buffer Put/Get methods
2019-01-25 22:09:13 +01:00
Ron Evans d820c36c4f runtime/strings: add implementation of strings.IndexByte() (#155)
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-25 13:44:26 +01:00
Ayke van Laethem 85108514df compiler: fix indexing of strings on AVR
Extract directly from the string instead of calling the len() builtin.
This is both cleaner and avoids a zero-extension to an integer on AVR,
which led to a LLVM verification error.
2019-01-25 13:15:08 +01:00
Ayke van Laethem 589569fc35 loader: fix ARM compatibility
The magic CGo construct to turn a C array into a slice turned out to be
not portable.

Note: this is not the only problem, there is also a bug in the Go
bindings for LLVM. With that one fixed, it is possible to build TinyGo
on a Raspberry Pi (32-bit).
2019-01-22 21:16:41 +01:00
Ayke van Laethem 2e4dd09bbc compiler: add support for channel operations
Support for channels is not complete. The following pieces are missing:

  * Channels with values bigger than int. An int in TinyGo can always
    contain at least a pointer, so pointers are okay to send.
  * Buffered channels.
  * The select statement.
2019-01-21 22:09:37 +01:00
Ayke van Laethem 602c264749 all: rewrite goroutine lowering
Before this commit, goroutine support was spread through the compiler.
This commit changes this support, so that the compiler itself only
generates simple intrinsics and leaves the real support to a compiler
pass that runs as one of the TinyGo-specific optimization passes.

The biggest change, that was done together with the rewrite, was support
for goroutines in WebAssembly for JavaScript. The challenge in
JavaScript is that in general no blocking operations are allowed, which
means that programs that call time.Sleep() but do not start goroutines
also have to be scheduled by the scheduler.
2019-01-21 22:09:33 +01:00
Ayke van Laethem 072ef603fe wasm: add GOOS/GOARCH properties
This was an oversight in commit 107fccb288.
2019-01-21 22:08:12 +01:00
Ayke van Laethem 54baf48266 compiler/interface: fix LLVM context for boolean variable
Apparently the given code path was never properly tested.
2019-01-21 22:08:12 +01:00
Ayke van Laethem 072eb590a6 compiler/interface: correct comment on function 2019-01-21 22:08:12 +01:00
Ayke van Laethem c0ab91a263 interp: extra safety check in string emulation 2019-01-21 22:08:12 +01:00
Ron Evans 3ebf464da2 machine/samd21: I2C implementation
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-21 21:54:43 +01:00
Ron Evans 38c5e384af machine/itsybitsy-m0: specify which pins to use for UART0
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-21 21:14:06 +01:00
Ron Evans 65ea74bd84 machine/atsamd21: implements UART0 using the SERCOM0 interface
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-21 21:14:00 +01:00
Ron Evans 683e2a66e1 machine/atsamd21: correct clock calibration based on stored fuse value
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-20 20:26:54 +01:00
Ron Evans 8cbbbb0e76 machine/atsamd21: improve GPIO config to support all 32 pins on PORTA as well as correct handling for OUTPUT and SERCOM pin modes
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-20 18:42:52 +01:00
Ron Evans f89c695c8c generators: correctly handle clustered subtypes used in Atmel SAMD21 SVD for important peripherals
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-20 18:40:49 +01:00
Ron Evans e2be7ccf76 sam: add support for Atmel SAMD21 based ItsyBitsy M0
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-18 18:30:27 +01:00
Ron Evans 1f511786d3 lib/cmsis-svd: update to latest release with updated SAMD21 and SAMD51 SVD files
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-18 18:30:27 +01:00
Seth Junot c9f4e41073 wasm: fix typo in wasm_exec.js preventing memory import
Seems to have been left over from the original copy. This correction
should fix calls to the "memory" variable in syscall/js.
2019-01-18 13:30:17 +01:00
Seth Junot 67fbfe6305 runtime/wasm: add memset()
Copied from the ARM runtime and modified to return a pointer.
https://pubs.opengroup.org/onlinepubs/9699919799/functions/memset.html
2019-01-18 13:20:22 +01:00
174 changed files with 13007 additions and 3275 deletions
+286
View File
@@ -0,0 +1,286 @@
version: 2.1
commands:
submodules:
steps:
- run:
name: "Pull submodules"
command: git submodule update --init
apt-dependencies:
parameters:
llvm:
type: string
steps:
- run:
name: "Install apt dependencies"
command: |
echo 'deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
sudo apt-get update
sudo apt-get install \
python3 \
llvm<<parameters.llvm>>-dev \
clang<<parameters.llvm>> \
libclang<<parameters.llvm>>-dev \
lld<<parameters.llvm>> \
gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
install-node:
steps:
- run:
name: "Install node.js"
command: |
wget https://nodejs.org/dist/v10.15.1/node-v10.15.1-linux-x64.tar.xz
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
rm node-v10.15.1-linux-x64.tar.xz
dep:
steps:
- run:
name: "Install Go dependencies"
command: |
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure --vendor-only
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 -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
- run: tinygo build -size short -o test.elf -target=pca10040 examples/test
- run: tinygo build -size short -o test.elf -target=microbit examples/echo
- run: tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
- run: tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
- run: tinygo build -size short -o test.elf -target=bluepill examples/blinky1
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky1
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky2
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
- run: tinygo build -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: <<parameters.llvm>>
- install-node
- restore_cache:
keys:
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- dep
- run: go install .
- run: go test -v
- run: make gen-device -j4
- smoketest
- save_cache:
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
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-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
-33
View File
@@ -1,33 +0,0 @@
language: go
go:
- "1.11"
before_install:
- echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" | sudo tee -a /etc/apt/sources.list
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list
- sudo apt-get update -qq
- sudo apt-get install llvm-7-dev clang-7 libclang-7-dev binutils-arm-none-eabi qemu-system-arm --allow-unauthenticated -y
- sudo ln -s /usr/bin/clang-7 /usr/local/bin/cc # work around missing -no-pie in old GCC version
install:
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
- dep ensure --vendor-only
script:
- go install github.com/aykevl/tinygo
- go test -v .
- make gen-device
- tinygo build -o blinky1.nrf.elf -target=pca10040 examples/blinky1
- tinygo build -o blinky2.nrf.elf -target=pca10040 examples/blinky2
- tinygo build -o blinky2 examples/blinky2
- tinygo build -o test.nrf.elf -target=pca10040 examples/test
- tinygo build -o blinky1.nrf51.elf -target=microbit examples/echo
- tinygo build -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
- tinygo build -o blinky1.nrf51d.elf -target=pca10031 examples/blinky1
- tinygo build -o blinky1.stm32.elf -target=bluepill examples/blinky1
- tinygo build -o blinky1.avr.o -target=arduino examples/blinky1 # TODO: avr-as/avr-gcc doesn't work
- tinygo build -o blinky1.reel.elf -target=reelboard examples/blinky1
- tinygo build -o blinky2.reel.elf -target=reelboard examples/blinky2
- tinygo build -o blinky1.pca10056.elf -target=pca10056 examples/blinky1
- tinygo build -o blinky2.pca10056.elf -target=pca10056 examples/blinky2
+91
View File
@@ -0,0 +1,91 @@
# Building TinyGo
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. 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
build a release tarball that includes this binary and all necessary extra files.
## Dependencies
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.11+)
* [dep](https://golang.github.io/dep/)
* Standard build tools (gcc/clang)
* git
* CMake
* [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 download the TinyGo sources. Then, inside the directory,
perform these steps:
dep ensure -vendor-only # download Go dependencies
make llvm-source # download LLVM
You can also store LLVM outside of the TinyGo root directory by setting the
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
covered by this guide.
## Build LLVM, Clang, LLD
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++
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:
make llvm-build
This can take over an hour depending on the speed of your system.
## Build TinyGo
The last step of course is to build TinyGo itself. This can again be done with
make:
make
## Verify TinyGo
Try running TinyGo:
./build/tinygo help
Also, make sure the `tinygo` binary really is statically linked. Check this
using `ldd` (not to be confused with `lld`):
ldd ./build/tinygo
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:
make release
The release tarball is stored in build/release.tar.gz, and can be extracted with
the following command (for example in ~/lib):
tar -xvf path/to/release.tar.gz
TinyGo will get extracted to a `tinygo` directory. You can then call it with:
./tinygo/bin/tinygo
+98
View File
@@ -0,0 +1,98 @@
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**
- switch to the hardfloat ABI on ARM, which is more widely used
- avoid a dependency on `objcopy` (`arm-none-eabi-objcopy` etc.)
- fix a bug in `make([]T, n)` where `n` is 64-bits on a 32-bit platform
- adapt to a change in the AVR backend in LLVM 8
- directly support the .uf2 firmware format as used on Adafruit boards
- fix a bug when calling `panic()` at init time outside of the main package
- implement nil checks, which results in a ~5% increase in code size
- inline slice bounds checking, which results in a ~1% decrease in code size
- **targets**
- `samd21`: fix a bug in port B pins
- `samd21`: implement SPI peripheral
- `samd21`: implement ADC peripheral
- `stm32`: fix a bug in timekeeping
- `wasm`: fix a bug in `wasm_exec.js` that caused corruption in linear memory
when running on Node.js.
0.3.0
---
- **compiler**
- remove old `-initinterp` flag
- add support for macOS
- **cgo**
- add support for bool/float/complex types
- **standard library**
- `device/arm`: add support to disable/enable hardware interrupts
- `machine`: add CPU frequency for nrf-based boards
- `syscall`: add support for darwin/amd64
- **targets**
- `circuitplay_express`: add support for this board
- `microbit`: add regular pin constants
- `samd21`: fix time function for goroutine support
- `samd21`: add support for USB-CDC (serial over USB)
- `samd21`: add support for pins in port B
- `samd21`: add support for pullup and pulldown pins
- `wasm`: add support for Safari in example
0.2.0
---
- **command line**
- add version subcommand
- **compiler**
- fix a bug in floating point comparisons with NaN values
- fix a bug when calling `panic` in package initialization code
- add support for comparing `complex64` and `complex128`
- **cgo**
- add support for external globals
- add support for pointers and function pointers
- **standard library**
- `fmt`: initial support, `fmt.Println` works
- `math`: support for most/all functions
- `os`: initial support (only stdin/stdout/stderr)
- `reflect`: initial support
- `syscall`: add support for amd64, arm, and arm64
+48
View File
@@ -0,0 +1,48 @@
# How to contribute
Thank you for your interest in improving TinyGo.
We would like your help to make this project better, so we appreciate any contributions. See if one of the following descriptions matches your situation:
### New to TinyGo
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
### Something in TinyGo is not working as you expect
Please open a Github issue with your problem, and we will be happy to assist.
### Something in Go that you want/need does not appear to be in TinyGo
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
### Some specific hardware you want to use does not appear to be in TinyGo
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
## How to use our Github repository
The `master` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
Here is how to contribute back some code or documentation:
- Fork repo
- Create a feature branch off of the `dev` branch
- Make some useful change
- Make sure the tests still pass
- Submit a pull request against the `dev` branch.
- Be kind
## How to run tests
To run the tests:
```
make test
```
+31 -28
View File
@@ -1,42 +1,45 @@
# 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
apt-get install -y llvm-8-dev libclang-8-dev git
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
COPY . /go/src/github.com/aykevl/tinygo
COPY . /go/src/github.com/tinygo-org/tinygo
RUN cd /go/src/github.com/aykevl/tinygo/ && \
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
git submodule update --init
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
dep ensure --vendor-only && \
go install /go/src/github.com/aykevl/tinygo/
go install /go/src/github.com/tinygo-org/tinygo/
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
FROM tinygo-base AS tinygo-wasm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/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
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
RUN cd /go/src/github.com/aykevl/tinygo/ && \
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
make gen-device-avr && \
@@ -48,15 +51,15 @@ RUN cd /go/src/github.com/aykevl/tinygo/ && \
FROM tinygo-base AS tinygo-arm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
RUN cd /go/src/github.com/aykevl/tinygo/ && \
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 && \
@@ -65,13 +68,13 @@ RUN cd /go/src/github.com/aykevl/tinygo/ && \
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
FROM tinygo-wasm AS tinygo-all
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
RUN cd /go/src/github.com/aykevl/tinygo/ && \
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
+25 -18
View File
@@ -3,42 +3,49 @@
[[projects]]
branch = "master"
digest = "1:f250e2a6d7e4f9ebc5ba37e5e2ec91b46eb1399ee43f2fdaeb20cd4fd1aeee59"
name = "github.com/aykevl/go-llvm"
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
name = "github.com/blakesmith/ar"
packages = ["."]
pruneopts = "UT"
revision = "d8539684f173a591ea9474d6262ac47ef2277d64"
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
[[projects]]
branch = "master"
digest = "1:d1102ae84d8c9318db4ce2ad2673eb2bf54569ab2a4a5d57e70d8aef726b681d"
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
name = "github.com/marcinbor85/gohex"
packages = ["."]
pruneopts = "UT"
revision = "7a43cd876e46e0f6ddc553f10f91731a78e6e949"
[[projects]]
branch = "master"
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
name = "golang.org/x/tools"
packages = [
"go/ast/astutil",
"go/buildutil",
"go/gcexportdata",
"go/internal/cgo",
"go/internal/gcimporter",
"go/loader",
"go/packages",
"go/ssa",
"go/ssa/ssautil",
"go/types/typeutil",
"internal/fastwalk",
"internal/gopathwalk",
"internal/semver",
]
pruneopts = "UT"
revision = "3e7aa9e59977626dc60433e9aeadf1bb63d28295"
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
[[projects]]
branch = "llvm8"
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
name = "tinygo.org/x/go-llvm"
packages = ["."]
pruneopts = "UT"
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/aykevl/go-llvm",
"golang.org/x/tools/go/loader",
"github.com/blakesmith/ar",
"github.com/marcinbor85/gohex",
"golang.org/x/tools/go/ast/astutil",
"golang.org/x/tools/go/ssa",
"golang.org/x/tools/go/ssa/ssautil",
"tinygo.org/x/go-llvm",
]
solver-name = "gps-cdcl"
solver-version = 1
+2 -2
View File
@@ -1,6 +1,6 @@
[[constraint]]
branch = "master"
name = "github.com/aykevl/go-llvm"
branch = "llvm8"
name = "tinygo.org/x/go-llvm"
[[constraint]]
branch = "master"
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2018 Ayke van Laethem. All rights reserved.
Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
+68 -74
View File
@@ -1,80 +1,42 @@
# aliases
all: tgo
tgo: build/tgo
all: tinygo
tinygo: build/tinygo
.PHONY: all tgo 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
# 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
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)
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
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
endif
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)
run-test: build/test
./build/test
# For static linking.
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++11
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
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/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
@@ -95,19 +57,51 @@ 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/tgo: *.go compiler/*.go interp/*.go loader/*.go ir/*.go
@mkdir -p build
go build -o build/tgo -i .
build/tinygo:
@if [ ! -f llvm-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
# Binary that can run on the host.
build/%: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go
./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
test:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
# ELF file that can run on a microcontroller.
build/%.elf: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go
./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
# Convert executable to Intel hex file (for flashing).
build/%.hex: build/%.elf
$(OBJCOPY) -O ihex $^ $@
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
@mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
@cp -p build/tinygo build/release/tinygo/bin
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
@cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets
./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
tar -czf build/release.tar.gz -C build/release tinygo
+69 -131
View File
@@ -1,144 +1,71 @@
# TinyGo - Go compiler for microcontrollers
# TinyGo - Go compiler for small places
[![Build Status](https://travis-ci.com/aykevl/tinygo.svg?branch=master)](https://travis-ci.com/aykevl/tinygo)
[![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
> We never expected Go to be an embedded language and so it's got serious
> problems [...].
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
TinyGo is a project to bring Go to microcontrollers and small systems with a
single processor core. It is similar to [emgo](https://github.com/ziutek/emgo)
but a major difference is that I want to keep the Go memory model (which implies
garbage collection of some sort). Another difference is that TinyGo uses LLVM
internally instead of emitting C, which hopefully leads to smaller and more
efficient code and certainly leads to more flexibility.
My original reasoning was: if [Python](https://micropython.org/) can run on
microcontrollers, then certainly [Go](https://golang.org/) should be able to and
run on even lower level micros.
Example program (blinky):
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
```go
package main
import (
"machine"
"time"
"machine"
"time"
)
func main() {
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for {
led.Low()
time.Sleep(time.Millisecond * 1000)
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for {
led.Low()
time.Sleep(time.Millisecond * 1000)
led.High()
time.Sleep(time.Millisecond * 1000)
}
led.High()
time.Sleep(time.Millisecond * 1000)
}
}
```
Currently supported features:
The above program can be compiled and run without modification on an Arduino Uno, an Adafruit ItsyBitsy M0, or any of the supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno:
* control flow
* many (but not all) basic types: most ints, floats, strings, structs
* function calling
* interfaces for basic types (with type switches and asserts)
* goroutines (very initial support)
* function pointers (non-blocking)
* interface methods
* standard library (but most packages won't work due to missing language
features)
* slices (partially)
* maps (very rough, unfinished)
* defer
* closures
* bound methods
* complex numbers (except for arithmetic)
Not yet supported:
* complex arithmetic
* garbage collection
* recover
* channels
* introspection (if it ever gets implemented)
* ...
```shell
tinygo flash -target arduino examples/blinky1
```
## Installation
See the [getting started instructions](https://tinygo.org/getting-started/).
See the [getting started instructions](https://tinygo.org/getting-started/) for information on how to install TinyGo, as well as how to run the TinyGo compiler using our Docker container.
### Running with Docker
## Supported boards/targets
A docker container exists for easy access to the `tinygo` CLI:
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
```sh
$ docker run --rm -v $(pwd):/src tinygo/tinygo tinygo build -o /src/wasm.wasm -target wasm examples/wasm
```
The following microcontroller boards are currently supported:
Note that you cannot run `tinygo flash` from inside the docker container,
so it is less useful for microcontroller development.
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [BBC:Microbit](https://microbit.org/)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
* [Digispark](http://digistump.com/products/1)
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
## Supported targets
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
The following architectures/systems are currently supported:
## Currently supported features:
* ARM (Cortex-M)
* AVR (Arduino Uno)
* Linux
* WebAssembly
For more information, see [this list of targets and
boards](https://tinygo.org/targets/). Pull requests for
broader support are welcome!
## Analysis and optimizations
The goal is to reduce code size (and increase performance) by performing all
kinds of whole-program analysis passes. The official Go compiler doesn't do a
whole lot of analysis (except for escape analysis) because it needs to be fast,
but embedded programs are necessarily smaller so it becomes practical. And I
think especially program size can be reduced by a large margin when actually
trying to optimize for it.
Implemented compiler passes:
* Analyse which functions are blocking. Blocking functions are functions that
call sleep, chan send, etc. Its parents are also blocking.
* Analyse whether the scheduler is needed. It is only needed when there are
`go` statements for blocking functions.
* Analyse whether a given type switch or type assert is possible with
[type-based alias analysis](https://en.wikipedia.org/wiki/Alias_analysis#Type-based_alias_analysis).
I would like to use flow-based alias analysis in the future, if feasible.
* Do basic dead code elimination of functions. This pass makes later passes
better and probably improves compile time as well.
## Scope
Goals:
* Have very small binary sizes. Don't pay for what you don't use.
* Support for most common microcontroller boards.
* Be usable on the web using WebAssembly.
* Good CGo support, with no more overhead than a regular function call.
* Support most standard library packages and compile most Go code without
modification.
Non-goals:
* Using more than one core.
* Be efficient while using zillions of goroutines. However, good goroutine
support is certainly a goal.
* Be as fast as `gc`. However, LLVM will probably be better at optimizing
certain things so TinyGo might actually turn out to be faster for number
crunching.
* Be able to compile every Go program out there.
For a description of currently supported Go language features, please see [https://tinygo.org/lang-support/](https://tinygo.org/lang-support/).
## Documentation
Documentation is currently maintained on a dedicated web site located at [https://tinygo.org/](https://tinygo.org/).
Documentation is located on our web site at [https://tinygo.org/](https://tinygo.org/).
You can find the web site code at [https://github.com/tinygo-org/tinygo-site](https://github.com/tinygo-org/tinygo-site).
@@ -153,26 +80,37 @@ should arrive fairly quickly (under 1 min): https://invite.slack.golangbridge.or
## Contributing
Patches are welcome!
Your contributions are welcome!
If you want to contribute, here are some suggestions:
Please take a look at our [CONTRIBUTING.md](./CONTRIBUTING.md) document for details.
* A long tail of small (and large) language features haven't been implemented
yet. In almost all cases, the compiler will show a `todo:` error from
`compiler/compiler.go` when you try to use it. You can try implementing it,
or open a bug report with a small code sample that fails to compile.
* Lots of targets/boards are still unsupported. Adding an architecture often
requires a few compiler changes, but if the architecture is supported you
can try implementing support for a new chip or board in `src/runtime`. For
details, see [this wiki entry on adding
archs/chips/boards](https://github.com/aykevl/tinygo/wiki/Adding-a-new-board).
* Microcontrollers have lots of peripherals and many don't have an
implementation yet in the `machine` package. Adding support for new
peripherals is very useful.
* Just raising bugs for things you'd like to see implemented is also a form of
contributing! It helps prioritization.
## Project Scope
Goals:
* Have very small binary sizes. Don't pay for what you don't use.
* Support for most common microcontroller boards.
* Be usable on the web using WebAssembly.
* Good CGo support, with no more overhead than a regular function call.
* Support most standard library packages and compile most Go code without modification.
Non-goals:
* Using more than one core.
* Be efficient while using zillions of goroutines. However, good goroutine support is certainly a goal.
* Be as fast as `gc`. However, LLVM will probably be better at optimizing certain things so TinyGo might actually turn out to be faster for number crunching.
* Be able to compile every Go program out there.
## Why this project exists
> We never expected Go to be an embedded language and so its got serious problems...
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
TinyGo is a project to bring Go to microcontrollers and small systems with a single processor core. It is similar to [emgo](https://github.com/ziutek/emgo) but a major difference is that we want to keep the Go memory model (which implies garbage collection of some sort). Another difference is that TinyGo uses LLVM internally instead of emitting C, which hopefully leads to smaller and more efficient code and certainly leads to more flexibility.
The original reasoning was: if [Python](https://micropython.org/) can run on microcontrollers, then certainly [Go](https://golang.org/) should be able to run on even lower level micros.
## License
This project is licensed under the BSD 3-clause license, just like the
[Go project](https://golang.org/LICENSE) itself.
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
+38 -22
View File
@@ -80,29 +80,45 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,
return "", err
}
cachepath := filepath.Join(dir, name)
err = os.Rename(tmppath, cachepath)
err = moveFile(tmppath, cachepath)
if err != nil {
inf, err := os.Open(tmppath)
if err != nil {
return "", err
}
defer inf.Close()
outf, err := os.Create(cachepath + ".tmp")
if err != nil {
return "", err
}
_, err = io.Copy(outf, inf)
if err != nil {
return "", err
}
err = os.Rename(cachepath+".tmp", cachepath)
if err != nil {
return "", err
}
return cachepath, outf.Close()
return "", err
}
return cachepath, nil
}
// moveFile renames the file from src to dst. If renaming doesn't work (for
// example, the rename crosses a filesystem boundary), the file is copied and
// the old file is removed.
func moveFile(src, dst string) error {
err := os.Rename(src, dst)
if err == nil {
// Success!
return nil
}
// Failed to move, probably a different filesystem.
// Do a copy + remove.
inf, err := os.Open(src)
if err != nil {
return err
}
defer inf.Close()
outpath := dst + ".tmp"
outf, err := os.Create(outpath)
if err != nil {
return err
}
_, err = io.Copy(outf, inf)
if err != nil {
os.Remove(outpath)
return err
}
err = os.Rename(dst+".tmp", dst)
if err != nil {
return err
}
return outf.Close()
}
+91 -13
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.
@@ -157,16 +162,29 @@ var aeabiBuiltins = []string{
func builtinFiles(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if target[:3] == "arm" {
if strings.HasPrefix(target, "arm") {
builtins = append(builtins, aeabiBuiltins...)
}
return builtins
}
// builtinsDir returns the directory where the sources for compiler-rt are kept.
func builtinsDir() string {
return filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
}
// Get the builtins archive, possibly generating it as needed.
func loadBuiltins(target string) (path string, err error) {
// Try to load a precompiled compiler-rt library.
precompiledPath := filepath.Join(sourceDir(), "pkg", target, "compiler-rt.a")
if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled compiler-rt for this OS/architecture. Return the
// path directly.
return precompiledPath, nil
}
outfile := "librt-" + target + ".a"
builtinsDir := filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
builtinsDir := builtinsDir()
builtins := builtinFiles(target)
srcs := make([]string, len(builtins))
@@ -178,9 +196,33 @@ func loadBuiltins(target string) (path string, err error) {
return path, err
}
dir, err := ioutil.TempDir("", "tinygo-builtins")
var cachepath string
err = compileBuiltins(target, func(path string) error {
path, err := cacheStore(path, outfile, commands["clang"], srcs)
cachepath = path
return err
})
return cachepath, err
}
// compileBuiltins compiles builtins from compiler-rt into a static library.
// When it succeeds, it will call the callback with the resulting path. The path
// will be removed after callback returns. If callback returns an error, this is
// passed through to the return value of this function.
func compileBuiltins(target string, callback func(path string) error) error {
builtinsDir := builtinsDir()
builtins := builtinFiles(target)
srcs := make([]string, len(builtins))
for i, name := range builtins {
srcs[i] = filepath.Join(builtinsDir, name)
}
dirPrefix := "tinygo-builtins"
remapDir := filepath.Join(os.TempDir(), dirPrefix)
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
if err != nil {
return "", err
return err
}
defer os.RemoveAll(dir)
@@ -195,26 +237,62 @@ func loadBuiltins(target string) (path string, err error) {
objpath := filepath.Join(dir, objname+".o")
objs = append(objs, objpath)
srcpath := filepath.Join(builtinsDir, name)
cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-o", objpath, srcpath)
// Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the
// archive itself, which varies each run.
cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
err = cmd.Run()
if err != nil {
return "", err
return &commandError{"failed to build", srcpath, err}
}
}
// Put all builtins in an archive to link as a static library.
// Note: this does not create a symbol index, but ld.lld doesn't seem to
// care.
arpath := filepath.Join(dir, "librt.a")
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 "", 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)
}
}
return cacheStore(arpath, outfile, commands["clang"], srcs)
// Give the caller the resulting file. The callback must copy the file,
// because after it returns the temporary directory will be removed.
return callback(arpath)
}
+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",
}
+149
View File
@@ -0,0 +1,149 @@
package compiler
// This file implements functions that do certain safety checks that are
// required by the Go programming language.
import (
"go/types"
"tinygo.org/x/go-llvm"
)
// emitLookupBoundsCheck emits a bounds check before doing a lookup into a
// slice. This is required by the Go language spec: an index out of bounds must
// cause a panic.
func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
if frame.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
}
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
// correctly extend that type.
if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 {
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
} else {
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
}
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
// The index is bigger than the array length type, so extend it.
arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "")
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now do the bounds check: index >= arrayLen
outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("lookuppanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
}
// 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
// checking.
return
}
// Extend the capacity integer to be at least as wide as low and high.
capacityType := capacity.Type()
if low.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = low.Type()
}
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = high.Type()
}
if capacityType != capacity.Type() {
capacity = c.builder.CreateZExt(capacity, capacityType, "")
}
// Extend low and high to be the same size as capacity.
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if lowType.Info()&types.IsUnsigned != 0 {
low = c.builder.CreateZExt(low, capacityType, "")
} else {
low = c.builder.CreateSExt(low, capacityType, "")
}
}
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if highType.Info()&types.IsUnsigned != 0 {
high = c.builder.CreateZExt(high, capacityType, "")
} else {
high = c.builder.CreateSExt(high, capacityType, "")
}
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now do the bounds check: low > high || high > capacity
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("slicepanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
}
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
// has no effect in well-behaved programs, but makes sure no uncaught nil
// pointer dereferences exist in valid Go code.
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
// Check whether this is a nil pointer.
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Compare against nil.
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.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("nilpanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
}
+3 -2
View File
@@ -1,8 +1,8 @@
package compiler
import (
"github.com/aykevl/go-llvm"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// For a description of the calling convention in prose, see:
@@ -21,7 +21,8 @@ func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name stri
}
fn := c.ir.GetFunction(member.(*ssa.Function))
if !fn.IsExported() {
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
}
return c.createCall(fn.LLVMFn, args, name)
}
+97
View File
@@ -0,0 +1,97 @@
package compiler
// This file lowers channel operations (make/send/recv/close) to runtime calls
// or pseudo-operations that are lowered during goroutine lowering.
import (
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// emitMakeChan returns a new channel value for the given channel type.
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
valueType, err := c.getLLVMType(expr.Type().(*types.Chan).Elem())
if err != nil {
return llvm.Value{}, err
}
if c.targetData.TypeAllocSize(valueType) > c.targetData.TypeAllocSize(c.intType) {
// Values bigger than int overflow the data part of the coroutine.
// TODO: make the coroutine data part big enough to hold these bigger
// values.
return llvm.Value{}, c.makeError(expr.Pos(), "todo: channel with values bigger than int")
}
chanType := c.mod.GetTypeByName("runtime.channel")
size := c.targetData.TypeAllocSize(chanType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan")
return ptr, nil
}
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
// channel send operation during goroutine lowering.
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) error {
valueType, err := c.getLLVMType(instr.Chan.Type().(*types.Chan).Elem())
if err != nil {
return err
}
ch, err := c.parseExpr(frame, instr.Chan)
if err != nil {
return err
}
chanValue, err := c.parseExpr(frame, instr.X)
if err != nil {
return err
}
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
c.builder.CreateStore(chanValue, valueAlloca)
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
c.createRuntimeCall("chanSendStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueSize}, "")
return nil
}
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
// actual channel receive operation during goroutine lowering.
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
valueType, err := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
if err != nil {
return llvm.Value{}, err
}
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
ch, err := c.parseExpr(frame, unop.X)
if err != nil {
return llvm.Value{}, err
}
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
valueOk := c.builder.CreateAlloca(c.ctx.Int1Type(), "chan.comma-ok.alloca")
c.createRuntimeCall("chanRecvStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueOk, valueSize}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received")
if unop.CommaOk {
commaOk := c.builder.CreateLoad(valueOk, "chan.comma-ok")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
return tuple, nil
} else {
return received, nil
}
}
// emitChanClose closes the given channel.
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) error {
valueType, err := c.getLLVMType(param.Type().(*types.Chan).Elem())
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
if err != nil {
return err
}
ch, err := c.parseExpr(frame, param)
if err != nil {
return err
}
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
return nil
}
+537 -1019
View File
File diff suppressed because it is too large Load Diff
+11 -2
View File
@@ -14,9 +14,9 @@ package compiler
// frames.
import (
"github.com/aykevl/go-llvm"
"github.com/aykevl/tinygo/ir"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// deferInitFunc sets up this function for future deferred calls. It must be
@@ -243,6 +243,9 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
fnPtr, _, err := c.getInvokeCall(frame, callback)
if err != nil {
return err
@@ -277,6 +280,9 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Call real function.
c.createCall(callback.LLVMFn, forwardParams, "")
@@ -305,6 +311,9 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
forwardParams = append(forwardParams, forwardParam)
}
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Call deferred function.
c.createCall(fn.LLVMFn, forwardParams, "")
+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)
}
+612
View File
@@ -0,0 +1,612 @@
package compiler
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
// scheduler at runtime. It uses coroutine support in LLVM for this
// transformation: https://llvm.org/docs/Coroutines.html
//
// For example, take the following code:
//
// func main() {
// go foo()
// time.Sleep(2 * time.Second)
// println("some other operation")
// bar()
// println("done")
// }
//
// func foo() {
// for {
// println("foo!")
// time.Sleep(time.Second)
// }
// }
//
// func bar() {
// time.Sleep(time.Second)
// println("blocking operation completed)
// }
//
// It is transformed by the IR generator in compiler.go into the following
// pseudo-Go code:
//
// func main() {
// fn := runtime.makeGoroutine(foo)
// fn()
// time.Sleep(2 * time.Second)
// println("some other operation")
// bar() // imagine an 'await' keyword in front of this call
// println("done")
// }
//
// func foo() {
// for {
// println("foo!")
// time.Sleep(time.Second)
// }
// }
//
// func bar() {
// time.Sleep(time.Second)
// println("blocking operation completed)
// }
//
// The pass in this file transforms this code even further, to the following
// async/await style pseudocode:
//
// func main(parent) {
// hdl := llvm.makeCoroutine()
// foo(nil) // do not pass the parent coroutine: this is an independent goroutine
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
// llvm.suspend(hdl) // suspend point
// println("some other operation")
// bar(hdl) // await, pass a continuation (hdl) to bar
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
// println("done")
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
// }
//
// func foo(parent) {
// hdl := llvm.makeCoroutine()
// for {
// println("foo!")
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
// llvm.suspend(hdl) // suspend point
// }
// }
//
// func bar(parent) {
// hdl := llvm.makeCoroutine()
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
// llvm.suspend(hdl) // suspend point
// println("blocking operation completed)
// runtime.activateTask(parent) // re-activate the parent coroutine before returning
// }
//
// The real LLVM code is more complicated, but this is the general idea.
//
// The LLVM coroutine passes will then process this file further transforming
// these three functions into coroutines. Most of the actual work is done by the
// scheduler, which runs in the background scheduling all coroutines.
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
)
type asyncFunc struct {
taskHandle llvm.Value
cleanupBlock llvm.BasicBlock
suspendBlock llvm.BasicBlock
unreachableBlock llvm.BasicBlock
}
// LowerGoroutines is a pass called during optimization that transforms the IR
// into one where all blocking functions are turned into goroutines and blocking
// calls into await calls.
func (c *Compiler) LowerGoroutines() error {
needsScheduler, err := c.markAsyncFunctions()
if err != nil {
return err
}
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
panic("expected exactly 1 call of runtime.callMain, check the entry point")
}
mainCall := uses[0]
// Replace call of runtime.callMain() with a real call to main.main(),
// optionally followed by a call to runtime.scheduler().
c.builder.SetInsertPointBefore(mainCall)
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
c.builder.CreateCall(realMain, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType)}, "")
if needsScheduler {
c.createRuntimeCall("scheduler", nil, "")
}
mainCall.EraseFromParentAsInstruction()
if !needsScheduler {
go_scheduler := c.mod.NamedFunction("go_scheduler")
if !go_scheduler.IsNil() {
// This is the WebAssembly backend.
// There is no need to export the go_scheduler function, but it is
// still exported. Make sure it is optimized away.
go_scheduler.SetLinkage(llvm.InternalLinkage)
}
}
// main.main was set to external linkage during IR construction. Set it to
// internal linkage to enable interprocedural optimizations.
realMain.SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.chanRecv").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
return nil
}
// markAsyncFunctions does the bulk of the work of lowering goroutines. It
// determines whether a scheduler is needed, and if it is, it transforms
// blocking operations into goroutines and blocking calls into await calls.
//
// It does the following operations:
// * Find all blocking functions.
// * Determine whether a scheduler is necessary. If not, it skips the
// following operations.
// * Transform call instructions into await calls.
// * Transform return instructions into final suspends.
// * Set up the coroutine frames for async functions.
// * Transform blocking calls into their async equivalents.
func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
var worklist []llvm.Value
sleep := c.mod.NamedFunction("time.Sleep")
if !sleep.IsNil() {
worklist = append(worklist, sleep)
}
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
if !deadlockStub.IsNil() {
worklist = append(worklist, deadlockStub)
}
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
if !chanSendStub.IsNil() {
worklist = append(worklist, chanSendStub)
}
chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub")
if !chanRecvStub.IsNil() {
worklist = append(worklist, chanRecvStub)
}
if len(worklist) == 0 {
// There are no blocking operations, so no need to transform anything.
return false, c.lowerMakeGoroutineCalls()
}
// Find all async functions.
// Keep reducing this worklist by marking a function as recursively async
// from the worklist and pushing all its parents that are non-async.
// This is somewhat similar to a worklist in a mark-sweep garbage collector:
// the work items are then grey objects.
asyncFuncs := make(map[llvm.Value]*asyncFunc)
asyncList := make([]llvm.Value, 0, 4)
for len(worklist) != 0 {
// Pick the topmost.
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
if _, ok := asyncFuncs[f]; ok {
continue // already processed
}
// Add to set of async functions.
asyncFuncs[f] = &asyncFunc{}
asyncList = append(asyncList, f)
// Add all callees to the worklist.
for _, use := range getUses(f) {
if use.IsConstant() && use.Opcode() == llvm.BitCast {
bitcastUses := getUses(use)
for _, call := range bitcastUses {
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine")
}
}
// This is a go statement. Do not mark the parent as async, as
// starting a goroutine is not a blocking operation.
continue
}
if use.IsACallInst().IsNil() {
// Not a call instruction. Maybe a store to a global? In any
// case, this requires support for async calls across function
// pointers which is not yet supported.
return false, errors.New("async function " + f.Name() + " used as function pointer")
}
parent := use.InstructionParent().Parent()
for i := 0; i < use.OperandsCount()-1; i++ {
if use.Operand(i) == f {
return false, errors.New("async function " + f.Name() + " used as function pointer in " + parent.Name())
}
}
worklist = append(worklist, parent)
}
}
// Check whether a scheduler is needed.
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
if c.GOOS == "js" && strings.HasPrefix(c.Triple, "wasm") {
// JavaScript always needs a scheduler, as in general no blocking
// operations are possible. Blocking operations block the browser UI,
// which is very bad.
needsScheduler = true
} else {
// Only use a scheduler when an async goroutine is started. When the
// goroutine is not async (does not do any blocking operation), no
// scheduler is necessary as it can be called directly.
for _, use := range getUses(makeGoroutine) {
// Input param must be const bitcast of function.
bitcast := use.Operand(0)
if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast {
panic("expected const bitcast operand of runtime.makeGoroutine")
}
goroutine := bitcast.Operand(0)
if _, ok := asyncFuncs[goroutine]; ok {
needsScheduler = true
break
}
}
}
if !needsScheduler {
// No scheduler is needed. Do not transform all functions here.
// However, make sure that all go calls (which are all non-async) are
// transformed into regular calls.
return false, c.lowerMakeGoroutineCalls()
}
// Create a few LLVM intrinsics for coroutine support.
coroIdType := llvm.FunctionType(c.ctx.TokenType(), []llvm.Type{c.ctx.Int32Type(), c.i8ptrType, c.i8ptrType, c.i8ptrType}, false)
coroIdFunc := llvm.AddFunction(c.mod, "llvm.coro.id", coroIdType)
coroSizeType := llvm.FunctionType(c.ctx.Int32Type(), nil, false)
coroSizeFunc := llvm.AddFunction(c.mod, "llvm.coro.size.i32", coroSizeType)
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
coroPromiseType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.i8ptrType, c.ctx.Int32Type(), c.ctx.Int1Type()}, false)
coroPromiseFunc := llvm.AddFunction(c.mod, "llvm.coro.promise", coroPromiseType)
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
coroEndType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType, c.ctx.Int1Type()}, false)
coroEndFunc := llvm.AddFunction(c.mod, "llvm.coro.end", coroEndType)
coroFreeType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
coroFreeFunc := llvm.AddFunction(c.mod, "llvm.coro.free", coroFreeType)
// Transform all async functions into coroutines.
for _, f := range asyncList {
if f == sleep || f == deadlockStub || f == chanSendStub || f == chanRecvStub {
continue
}
frame := asyncFuncs[f]
frame.cleanupBlock = c.ctx.AddBasicBlock(f, "task.cleanup")
frame.suspendBlock = c.ctx.AddBasicBlock(f, "task.suspend")
frame.unreachableBlock = c.ctx.AddBasicBlock(f, "task.unreachable")
// Scan for async calls and return instructions that need to have
// suspend points inserted.
var asyncCalls []llvm.Value
var returns []llvm.Value
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if !inst.IsACallInst().IsNil() {
callee := inst.CalledValue()
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSendStub || callee == chanRecvStub {
continue
}
asyncCalls = append(asyncCalls, inst)
} else if !inst.IsAReturnInst().IsNil() {
returns = append(returns, inst)
}
}
}
// Coroutine setup.
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
stateI8,
llvm.ConstNull(c.i8ptrType),
llvm.ConstNull(c.i8ptrType),
}, "task.token")
size := c.builder.CreateCall(coroSizeFunc, nil, "task.size")
if c.targetData.TypeAllocSize(size.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
size = c.builder.CreateTrunc(size, c.uintptrType, "task.size.uintptr")
} else if c.targetData.TypeAllocSize(size.Type()) < c.targetData.TypeAllocSize(c.uintptrType) {
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
}
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
// Modify async calls so this function suspends right after the child
// returns, because the child is probably not finished yet. Wait until
// the child reactivates the parent.
for _, inst := range asyncCalls {
inst.SetOperand(inst.OperandsCount()-2, frame.taskHandle)
// Split this basic block.
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
// Set task state to TASK_STATE_CALL.
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
// Suspend.
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
}
// Replace return instructions with suspend points that should
// reactivate the parent coroutine.
for _, inst := range returns {
if inst.OperandsCount() == 0 {
// These properties were added by the functionattrs pass.
// Remove them, because now we start using the parameter.
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
for _, kind := range []string{"nocapture", "readnone"} {
kindID := llvm.AttributeKindID(kind)
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
}
// Reactivate the parent coroutine. This adds it back to
// the run queue, so it is started again by the
// scheduler when possible (possibly right after the
// following suspend).
c.builder.SetInsertPointBefore(inst)
parentHandle := f.LastParam()
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
// Suspend this coroutine.
// It would look like this is unnecessary, but if this
// suspend point is left out, it leads to undefined
// behavior somehow (with the unreachable instruction).
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
}, "ret")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
inst.EraseFromParentAsInstruction()
} else {
panic("todo: return value from coroutine")
}
}
// Coroutine cleanup. Free resources associated with this coroutine.
c.builder.SetInsertPointAtEnd(frame.cleanupBlock)
mem := c.builder.CreateCall(coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free")
c.createRuntimeCall("free", []llvm.Value{mem}, "")
c.builder.CreateBr(frame.suspendBlock)
// Coroutine suspend. A call to llvm.coro.suspend() will branch here.
c.builder.SetInsertPointAtEnd(frame.suspendBlock)
c.builder.CreateCall(coroEndFunc, []llvm.Value{frame.taskHandle, llvm.ConstInt(c.ctx.Int1Type(), 0, false)}, "unused")
returnType := f.Type().ElementType().ReturnType()
if returnType.TypeKind() == llvm.VoidTypeKind {
c.builder.CreateRetVoid()
} else {
c.builder.CreateRet(llvm.Undef(returnType))
}
// Coroutine exit. All final suspends (return instructions) will branch
// here.
c.builder.SetInsertPointAtEnd(frame.unreachableBlock)
c.builder.CreateUnreachable()
}
// Transform calls to time.Sleep() into coroutine suspend points.
for _, sleepCall := range getUses(sleep) {
// sleepCall must be a call instruction.
frame := asyncFuncs[sleepCall.InstructionParent().Parent()]
duration := sleepCall.Operand(0)
// Set task state to TASK_STATE_SLEEP and set the duration.
c.builder.SetInsertPointBefore(sleepCall)
c.createRuntimeCall("sleepTask", []llvm.Value{frame.taskHandle, duration}, "")
// Yield to scheduler.
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
wakeup := c.splitBasicBlock(sleepCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
c.builder.SetInsertPointBefore(sleepCall)
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
sleepCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.deadlockStub into coroutine suspends (without
// resume).
for _, deadlockCall := range getUses(deadlockStub) {
// deadlockCall must be a call instruction.
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
// Exit coroutine.
c.builder.SetInsertPointBefore(deadlockCall)
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 1, false), // final suspend
}, "")
c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead")
c.builder.SetInsertPointBefore(deadlockCall)
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
deadlockCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.chanSendStub into channel send operations.
for _, sendOp := range getUses(chanSendStub) {
// sendOp must be a call instruction.
frame := asyncFuncs[sendOp.InstructionParent().Parent()]
// Send the value over the channel, or block.
sendOp.SetOperand(0, frame.taskHandle)
sendOp.SetOperand(sendOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanSend"))
// Use taskState.data to store the value to send:
// *(*valueType)(&coroutine.promise().data) = valueToSend
// runtime.chanSend(coroutine, ch)
bitcast := sendOp.Operand(2)
valueAlloca := bitcast.Operand(0)
c.builder.SetInsertPointBefore(valueAlloca)
promiseType := c.mod.GetTypeByName("runtime.taskState")
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
frame.taskHandle,
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "task.promise.raw")
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
}, "task.promise.data")
sendOp.SetOperand(2, llvm.Undef(c.i8ptrType))
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
bitcast.EraseFromParentAsInstruction()
valueAlloca.EraseFromParentAsInstruction()
// Yield to scheduler.
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.sent")
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
}
// Transform calls to runtime.chanRecvStub into channel receive operations.
for _, recvOp := range getUses(chanRecvStub) {
// recvOp must be a call instruction.
frame := asyncFuncs[recvOp.InstructionParent().Parent()]
bitcast := recvOp.Operand(2)
commaOk := recvOp.Operand(3)
valueAlloca := bitcast.Operand(0)
// Receive the value over the channel, or block.
recvOp.SetOperand(0, frame.taskHandle)
recvOp.SetOperand(recvOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanRecv"))
recvOp.SetOperand(2, llvm.Undef(c.i8ptrType))
bitcast.EraseFromParentAsInstruction()
// Yield to scheduler.
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.received")
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
// The value to receive is stored in taskState.data:
// runtime.chanRecv(coroutine, ch)
// promise := coroutine.promise()
// valueReceived := *(*valueType)(&promise.data)
// ok := promise.commaOk
c.builder.SetInsertPointBefore(wakeup.FirstInstruction())
promiseType := c.mod.GetTypeByName("runtime.taskState")
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
frame.taskHandle,
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "task.promise.raw")
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
}, "task.promise.data")
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
valueAlloca.EraseFromParentAsInstruction()
commaOkPtr := c.builder.CreateGEP(promise, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 1, false),
}, "task.promise.comma-ok")
commaOk.ReplaceAllUsesWith(commaOkPtr)
recvOp.SetOperand(3, llvm.Undef(commaOk.Type()))
}
return true, c.lowerMakeGoroutineCalls()
}
// Lower runtime.makeGoroutine calls to regular call instructions. This is done
// after the regular goroutine transformations. The started goroutines are
// either non-blocking (in which case they can be called directly) or blocking,
// in which case they will ask the scheduler themselves to be rescheduled.
func (c *Compiler) lowerMakeGoroutineCalls() error {
// The following Go code:
// go startedGoroutine()
//
// Is translated to the following during IR construction, to preserve the
// fact that this function should be called as a new goroutine.
// %0 = call i8* @runtime.makeGoroutine(i8* bitcast (void (i8*, i8*)* @main.startedGoroutine to i8*), i8* undef, i8* null)
// %1 = bitcast i8* %0 to void (i8*, i8*)*
// call void %1(i8* undef, i8* undef)
//
// This function rewrites it to a direct call:
// call void @main.startedGoroutine(i8* undef, i8* null)
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
for _, goroutine := range getUses(makeGoroutine) {
bitcastIn := goroutine.Operand(0)
origFunc := bitcastIn.Operand(0)
uses := getUses(goroutine)
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
}
bitcastOut := uses[0]
uses = getUses(bitcastOut)
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
}
realCall := uses[0]
// Create call instruction.
var params []llvm.Value
for i := 0; i < realCall.OperandsCount()-1; i++ {
params = append(params, realCall.Operand(i))
}
params[len(params)-1] = llvm.ConstPointerNull(c.i8ptrType) // parent coroutine handle (must be nil)
c.builder.SetInsertPointBefore(realCall)
c.builder.CreateCall(origFunc, params, "")
realCall.EraseFromParentAsInstruction()
bitcastOut.EraseFromParentAsInstruction()
goroutine.EraseFromParentAsInstruction()
}
return nil
}
+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
}
+71 -107
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
@@ -49,7 +45,7 @@ import (
"sort"
"strings"
"github.com/aykevl/go-llvm"
"tinygo.org/x/go-llvm"
)
// signatureInfo is a Go signature of an interface method. It does not represent
@@ -104,15 +100,6 @@ func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo {
panic("could not find method")
}
// id returns the fully-qualified type name including import path, removing the
// $type suffix.
func (t *typeInfo) id() string {
if !strings.HasSuffix(t.name, "$type") {
panic("concrete type does not have $type suffix: " + t.name)
}
return t.name[:len(t.name)-len("$type")]
}
// typeInfoSlice implements sort.Slice, sorting the most commonly used types
// first.
type typeInfoSlice []*typeInfo
@@ -175,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
@@ -203,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++
}
@@ -295,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 {
@@ -313,7 +298,7 @@ func (p *lowerInterfacesPass) run() {
// interface value should already have returned false.
// Replace the function pointer with undef (which will then be
// called), indicating to the optimizer this code is unreachable.
use.ReplaceAllUsesWith(llvm.Undef(p.i8ptrType))
use.ReplaceAllUsesWith(llvm.Undef(p.uintptrType))
use.EraseFromParentAsInstruction()
} else if len(itf.types) == 1 {
// There is only one implementation of the given type.
@@ -323,12 +308,12 @@ func (p *lowerInterfacesPass) run() {
// There are multiple types implementing this interface, thus there
// are multiple possible functions to call. Delegate calling the
// right function to a special wrapper function.
bitcasts := getUses(use)
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() {
panic("expected exactly one bitcast use of runtime.interfaceMethod")
inttoptrs := getUses(use)
if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
panic("expected exactly one inttoptr use of runtime.interfaceMethod")
}
bitcast := bitcasts[0]
calls := getUses(bitcast)
inttoptr := inttoptrs[0]
calls := getUses(inttoptr)
if len(calls) != 1 || calls[0].IsACallInst().IsNil() {
panic("expected exactly one call use of runtime.interfaceMethod")
}
@@ -349,14 +334,14 @@ func (p *lowerInterfacesPass) run() {
// call, after selecting the right concrete type.
redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
// Replace the old lookup/bitcast/call with the new call.
// Replace the old lookup/inttoptr/call with the new call.
p.builder.SetInsertPointBefore(call)
retval := p.builder.CreateCall(redirector, params, "")
if retval.Type().TypeKind() != llvm.VoidTypeKind {
call.ReplaceAllUsesWith(retval)
}
call.EraseFromParentAsInstruction()
bitcast.EraseFromParentAsInstruction()
inttoptr.EraseFromParentAsInstruction()
use.EraseFromParentAsInstruction()
}
}
@@ -365,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()]
@@ -415,7 +386,7 @@ func (p *lowerInterfacesPass) run() {
for _, t := range p.types {
typeSlice = append(typeSlice, t)
}
sort.Sort(typeSlice)
sort.Sort(sort.Reverse(typeSlice))
// A type code must fit in 16 bits.
if len(typeSlice) >= 1<<16 {
@@ -423,16 +394,16 @@ func (p *lowerInterfacesPass) run() {
}
// Assign a type code for each type.
for i, t := range typeSlice {
t.num = uint64(i + 1)
}
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
@@ -444,7 +415,7 @@ func (p *lowerInterfacesPass) run() {
var commaOk llvm.Value
if t.countMakeInterfaces == 0 {
// impossible type assert: optimize accordingly
commaOk = llvm.ConstInt(llvm.Int1Type(), 0, false)
commaOk = llvm.ConstInt(p.ctx.Int1Type(), 0, false)
} else {
// regular type assert
p.builder.SetInsertPointBefore(use)
@@ -469,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 {
@@ -485,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.
@@ -553,22 +517,22 @@ func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
return p.signatures[name]
}
// replaceInvokeWithCall replaces a runtime.interfaceMethod + bitcast with a
// replaceInvokeWithCall replaces a runtime.interfaceMethod + inttoptr with a
// concrete method. This can be done when only one type implements the
// interface.
func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInfo, signature *signatureInfo) {
bitcasts := getUses(use)
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() {
panic("expected exactly one bitcast use of runtime.interfaceMethod")
inttoptrs := getUses(use)
if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
panic("expected exactly one inttoptr use of runtime.interfaceMethod")
}
bitcast := bitcasts[0]
inttoptr := inttoptrs[0]
function := typ.getMethod(signature).function
if bitcast.Type() != function.Type() {
if inttoptr.Type() != function.Type() {
p.builder.SetInsertPointBefore(use)
function = p.builder.CreateBitCast(function, bitcast.Type(), "")
function = p.builder.CreateBitCast(function, inttoptr.Type(), "")
}
bitcast.ReplaceAllUsesWith(function)
bitcast.EraseFromParentAsInstruction()
inttoptr.ReplaceAllUsesWith(function)
inttoptr.EraseFromParentAsInstruction()
use.EraseFromParentAsInstruction()
}
@@ -631,9 +595,9 @@ func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo)
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
}
// getInterfaceMethodFunc return a function that returns a function pointer for
// calling a method on an interface. It only declares the function,
// createInterfaceMethodFunc actually defines the function.
// getInterfaceMethodFunc returns a thunk for calling a method on an interface.
// It only declares the function, createInterfaceMethodFunc actually defines the
// function.
func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, params []llvm.Type) llvm.Value {
if fn, ok := itf.methodFuncs[signature]; ok {
// This function has already been created.
@@ -691,7 +655,7 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
// Define all possible functions that can be called.
for _, typ := range itf.types {
bb := llvm.AddBasicBlock(fn, typ.id())
bb := llvm.AddBasicBlock(fn, typ.name)
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
// The function we will redirect to when the interface has this type.
+126 -32
View File
@@ -8,10 +8,12 @@ package compiler
import (
"go/token"
"go/types"
"strconv"
"strings"
"github.com/aykevl/go-llvm"
"github.com/aykevl/tinygo/ir"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// parseMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
@@ -20,28 +22,17 @@ import (
// value field.
//
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global string, pos token.Pos) (llvm.Value, error) {
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) (llvm.Value, error) {
var itfValue llvm.Value
size := c.targetData.TypeAllocSize(val.Type())
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
if global != "" {
// Allocate in a global variable.
global := llvm.AddGlobal(c.mod, val.Type(), global+"$itfvalue")
global.SetInitializer(val)
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
itfValueRaw := llvm.ConstInBoundsGEP(global, []llvm.Value{zero, zero})
itfValue = llvm.ConstBitCast(itfValueRaw, c.i8ptrType)
} else {
// Allocate on the heap and put a pointer in the interface.
// TODO: escape analysis.
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "makeinterface.alloc")
itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "makeinterface.cast.value")
c.builder.CreateStore(val, itfValueCast)
itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "makeinterface.cast.i8ptr")
}
// Allocate on the heap and put a pointer in the interface.
// TODO: escape analysis.
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "makeinterface.alloc")
itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "makeinterface.cast.value")
c.builder.CreateStore(val, itfValueCast)
itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "makeinterface.cast.i8ptr")
} else if size == 0 {
itfValue = llvm.ConstPointerNull(c.i8ptrType)
} else {
@@ -51,7 +42,7 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global str
itfValue = c.builder.CreateIntToPtr(val, c.i8ptrType, "makeinterface.cast.int")
case llvm.PointerTypeKind:
itfValue = c.builder.CreateBitCast(val, c.i8ptrType, "makeinterface.cast.ptr")
case llvm.StructTypeKind:
case llvm.StructTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
// A bitcast would be useful here, but bitcast doesn't allow
// aggregate types. So we'll bitcast it using an alloca.
// Hopefully this will get optimized away.
@@ -68,7 +59,15 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global str
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, "")
@@ -79,14 +78,111 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global str
// It returns a pointer to an external global which should be replaced with the
// real type in the interface lowering pass.
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$type")
globalName := "type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), typ.String()+"$type")
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
global.SetGlobalConstant(true)
}
return global
}
// getTypeCodeName returns a name for this type that can be used in the
// interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum.
func getTypeCodeName(t types.Type) string {
name := ""
if named, ok := t.(*types.Named); ok {
name = "~" + named.String() + ":"
t = t.Underlying()
}
switch t := t.(type) {
case *types.Array:
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
case *types.Basic:
var kind string
switch t.Kind() {
case types.Bool:
kind = "bool"
case types.Int:
kind = "int"
case types.Int8:
kind = "int8"
case types.Int16:
kind = "int16"
case types.Int32:
kind = "int32"
case types.Int64:
kind = "int64"
case types.Uint:
kind = "uint"
case types.Uint8:
kind = "uint8"
case types.Uint16:
kind = "uint16"
case types.Uint32:
kind = "uint32"
case types.Uint64:
kind = "uint64"
case types.Uintptr:
kind = "uintptr"
case types.Float32:
kind = "float32"
case types.Float64:
kind = "float64"
case types.Complex64:
kind = "complex64"
case types.Complex128:
kind = "complex128"
case types.String:
kind = "string"
case types.UnsafePointer:
kind = "unsafeptr"
default:
panic("unknown basic type: " + t.Name())
}
return "basic:" + name + kind
case *types.Chan:
return "chan:" + name + getTypeCodeName(t.Elem())
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ {
methods[i] = getTypeCodeName(t.Method(i).Type())
}
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
case *types.Map:
keyType := getTypeCodeName(t.Key())
elemType := getTypeCodeName(t.Elem())
return "map:" + name + "{" + keyType + "," + elemType + "}"
case *types.Pointer:
return "pointer:" + name + getTypeCodeName(t.Elem())
case *types.Signature:
params := make([]string, t.Params().Len())
for i := 0; i < t.Params().Len(); i++ {
params[i] = getTypeCodeName(t.Params().At(i).Type())
}
results := make([]string, t.Results().Len())
for i := 0; i < t.Results().Len(); i++ {
results[i] = getTypeCodeName(t.Results().At(i).Type())
}
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
case *types.Slice:
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())
}
return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
default:
panic("unknown type: " + t.String())
}
}
// getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass.
func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
@@ -119,7 +215,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
}
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal,
llvm.ConstBitCast(fn, c.i8ptrType),
llvm.ConstPtrToInt(fn, c.uintptrType),
})
methods[i] = methodInfo
}
@@ -300,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{
@@ -316,7 +408,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
c.getMethodSignature(instr.Method),
}
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast")
fnCast := c.builder.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
args := []llvm.Value{receiverValue}
@@ -330,6 +422,8 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
// Add the context parameter. An interface call never takes a context but we
// have to supply the parameter anyway.
args = append(args, llvm.Undef(c.i8ptrType))
// Add the parent goroutine handle.
args = append(args, llvm.Undef(c.i8ptrType))
return fnCast, args, nil
}
+95
View File
@@ -0,0 +1,95 @@
package compiler
import (
"tinygo.org/x/go-llvm"
)
// This file contains helper functions for LLVM that are not exposed in the Go
// bindings.
// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
if value.IsNil() {
return nil
}
var uses []llvm.Value
use := value.FirstUse()
for !use.IsNil() {
uses = append(uses, use.User())
use = use.NextUse()
}
return uses
}
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
// after afterInst are moved into a new basic block (created right after the
// current one) with the given name.
func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
oldBlock := afterInst.InstructionParent()
newBlock := c.ctx.InsertBasicBlock(insertAfter, name)
var nextInstructions []llvm.Value // values to move
// Collect to-be-moved instructions.
inst := afterInst
for {
inst = llvm.NextInstruction(inst)
if inst.IsNil() {
break
}
nextInstructions = append(nextInstructions, inst)
}
// Move instructions.
c.builder.SetInsertPointAtEnd(newBlock)
for _, inst := range nextInstructions {
inst.RemoveFromParentAsInstruction()
c.builder.Insert(inst)
}
// Find PHI nodes to update.
var phiNodes []llvm.Value // PHI nodes to update
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst.IsAPHINode().IsNil() {
continue
}
needsUpdate := false
incomingCount := inst.IncomingCount()
for i := 0; i < incomingCount; i++ {
if inst.IncomingBlock(i) == oldBlock {
needsUpdate = true
break
}
}
if !needsUpdate {
// PHI node has no incoming edge from the old block.
continue
}
phiNodes = append(phiNodes, inst)
}
}
// Update PHI nodes.
for _, phi := range phiNodes {
c.builder.SetInsertPointBefore(phi)
newPhi := c.builder.CreatePHI(phi.Type(), "")
incomingCount := phi.IncomingCount()
incomingVals := make([]llvm.Value, incomingCount)
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
for i := 0; i < incomingCount; i++ {
value := phi.IncomingValue(i)
block := phi.IncomingBlock(i)
if block == oldBlock {
block = newBlock
}
incomingVals[i] = value
incomingBlocks[i] = block
}
newPhi.AddIncoming(incomingVals, incomingBlocks)
phi.ReplaceAllUsesWith(newPhi)
phi.EraseFromParentAsInstruction()
}
return newBlock
}
+8 -4
View File
@@ -6,7 +6,7 @@ import (
"go/token"
"go/types"
"github.com/aykevl/go-llvm"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
@@ -29,7 +29,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
params := []llvm.Value{m, keyPtr, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
} else {
return llvm.Value{}, c.makeError(pos, "todo: map lookup key type: "+keyType.String())
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
if commaOk {
@@ -61,7 +61,7 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
c.createRuntimeCall("hashmapBinarySet", params, "")
return nil
} else {
return c.makeError(pos, "todo: map update key type: "+keyType.String())
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
}
@@ -80,7 +80,7 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
c.createRuntimeCall("hashmapBinaryDelete", params, "")
return nil
} else {
return c.makeError(pos, "todo: map delete key type: "+keyType.String())
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
}
@@ -120,6 +120,10 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
}
}
return true
case *types.Array:
return hashmapIsBinaryKey(keyType.Elem())
case *types.Named:
return hashmapIsBinaryKey(keyType.Underlying())
default:
return false
}
+38 -16
View File
@@ -3,7 +3,7 @@ package compiler
import (
"errors"
"github.com/aykevl/go-llvm"
"tinygo.org/x/go-llvm"
)
// Run the LLVM optimizer over the module.
@@ -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
@@ -52,9 +53,35 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
// Run TinyGo-specific interprocedural optimizations.
c.OptimizeAllocs()
c.OptimizeStringToBytes()
// Lower runtime.isnil calls to regular nil comparisons.
isnil := c.mod.NamedFunction("runtime.isnil")
if !isnil.IsNil() {
for _, use := range getUses(isnil) {
c.builder.SetInsertPointBefore(use)
ptr := use.Operand(0)
if !ptr.IsABitCastInst().IsNil() {
ptr = ptr.Operand(0)
}
nilptr := llvm.ConstPointerNull(ptr.Type())
icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
use.ReplaceAllUsesWith(icmp)
use.EraseFromParentAsInstruction()
}
}
err := c.LowerGoroutines()
if err != nil {
return err
}
} else {
// Must be run at any optimization level.
c.LowerInterfaces()
c.LowerFuncValues()
err := c.LowerGoroutines()
if err != nil {
return err
}
}
if err := c.Verify(); err != nil {
return errors.New("optimizations caused a verification failure")
@@ -70,6 +97,13 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
}
}
// Run function passes again, because without it, llvm.coro.size.i32()
// doesn't get lowered.
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
// Run module passes.
modPasses := llvm.NewPassManager()
defer modPasses.Dispose()
@@ -265,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
@@ -324,18 +361,3 @@ func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool {
}
return true
}
// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
if value.IsNil() {
return nil
}
var uses []llvm.Value
use := value.FirstUse()
for !use.IsNil() {
uses = append(uses, use.User())
use = use.NextUse()
}
return uses
}
+156
View File
@@ -0,0 +1,156 @@
package compiler
import (
"math/big"
"strings"
)
var basicTypes = map[string]int64{
"bool": 1,
"int": 2,
"int8": 3,
"int16": 4,
"int32": 5,
"int64": 6,
"uint": 7,
"uint8": 8,
"uint16": 9,
"uint32": 10,
"uint64": 11,
"uintptr": 12,
"float32": 13,
"float64": 14,
"complex64": 15,
"complex128": 16,
"string": 17,
"unsafeptr": 18,
}
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
fn := c.mod.NamedFunction("reflect.ValueOf")
if fn.IsNil() {
// reflect.ValueOf is never used, so we can use the most efficient
// encoding possible.
for i, t := range typeSlice {
t.num = uint64(i + 1)
}
return
}
// Assign typecodes the way the reflect package expects.
fallbackIndex := 1
namedTypes := make(map[string]int)
for _, t := range typeSlice {
if t.name[:5] != "type:" {
panic("expected type name to start with 'type:'")
}
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
// TODO: support this in some way, using a side table for example.
// That's less efficient but better than not working at all.
// Particularly important on systems with 16-bit pointers (e.g.
// AVR).
panic("compiler: could not store type code number inside interface type code")
}
t.num = num.Uint64()
}
}
// getTypeCodeNum returns the typecode for a given type as expected by the
// reflect package. Also see getTypeCodeName, which serializes types to a string
// based on a types.Type value for this function.
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
// Note: see src/reflect/type.go for bit allocations.
// A type can be named or unnamed. Example of both:
// basic:~foo:uint64
// basic:uint64
// Extract the class (basic, slice, pointer, etc.), the name, and the
// contents of this type ID string. Allocate bits based on that, as
// src/runtime/types.go expects.
class := id[:strings.IndexByte(id, ':')]
value := id[len(class)+1:]
name := ""
if value[0] == '~' {
name = value[1:strings.IndexByte(value, ':')]
value = value[len(name)+2:]
}
if class == "basic" {
// Basic types follow the following bit pattern:
// ...xxxxx0
// where xxxxx is allocated for the 18 possible basic types and all the
// upper bits are used to indicate the named type.
num, ok := basicTypes[value]
if !ok {
panic("invalid basic type: " + id)
}
if name != "" {
// This type is named, set the upper bits to the name ID.
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
}
return big.NewInt(num << 1)
} else {
// Complex types use the following bit pattern:
// ...nxxx1
// where xxx indicates the complex type (any non-basic type). The upper
// bits contain whatever the type contains. Types that wrap a single
// other type (channel, interface, pointer, slice) just contain the bits
// of the wrapped type. Other types (like struct) have a different
// method of encoding the contents of the type.
var num *big.Int
var classNumber int64
switch class {
case "chan":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 0
case "interface":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 1
case "pointer":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 2
case "slice":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 3
case "array":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 4
case "func":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 5
case "map":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 6
case "struct":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 7
default:
panic("unknown type kind: " + id)
}
if name == "" {
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
} else {
// TODO: store num in a sidetable
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
}
return num
}
}
// getNamedTypeNum returns an appropriate (unique) number for the given named
// type. If the name already has a number that number is returned, else a new
// number is returned. The number is always non-zero.
func getNamedTypeNum(namedTypes map[string]int, name string) int {
if num, ok := namedTypes[name]; ok {
return num
} else {
num = len(namedTypes) + 1
namedTypes[name] = num
return num
}
}
+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:
+176
View File
@@ -0,0 +1,176 @@
package compiler
// This file implements the syscall.Syscall and syscall.Syscall6 instructions as
// compiler builtins.
import (
"go/constant"
"strconv"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// emitSyscall emits an inline system call instruction, depending on the target
// OS/arch.
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
var syscallResult llvm.Value
switch {
case c.GOARCH == "amd64":
if c.GOOS == "darwin" {
// Darwin adds this magic number to system call numbers:
//
// > Syscall classes for 64-bit system call entry.
// > For 64-bit users, the 32-bit syscall number is partitioned
// > with the high-order bits representing the class and low-order
// > bits being the syscall number within that class.
// > The high-order 32-bits of the 64-bit syscall number are unused.
// > All system classes enter the kernel via the syscall instruction.
//
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
num += 0x2000000
}
// Sources:
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
argTypes := []llvm.Type{c.uintptrType}
// Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
constraints := "={rax},0"
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
"{rdi}",
"{rsi}",
"{rdx}",
"{r10}",
"{r8}",
"{r9}",
"{r11}",
"{r12}",
"{r13}",
}[i]
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH == "arm" && c.GOOS == "linux":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
args := []llvm.Value{}
argTypes := []llvm.Type{}
// Constraints will look something like:
// ={r0},0,{r1},{r2},{r7},~{r3}
constraints := "={r0}"
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
"0", // tie to output
"{r1}",
"{r2}",
"{r3}",
"{r4}",
"{r5}",
"{r6}",
}[i]
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
argTypes = append(argTypes, c.uintptrType)
constraints += ",{r7}" // syscall number
for i := len(call.Args) - 1; i < 4; i++ {
// r0-r3 get clobbered after the syscall returns
constraints += ",~{r" + strconv.Itoa(i) + "}"
}
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH == "arm64" && c.GOOS == "linux":
// Source: syscall(2) man page.
args := []llvm.Value{}
argTypes := []llvm.Type{}
// Constraints will look something like:
// ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
constraints := "={x0}"
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
"0", // tie to output
"{x1}",
"{x2}",
"{x3}",
"{x4}",
"{x5}",
}[i]
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
argTypes = append(argTypes, c.uintptrType)
constraints += ",{x8}" // syscall number
for i := len(call.Args) - 1; i < 8; i++ {
// x0-x7 may get clobbered during the syscall following the aarch64
// calling convention.
constraints += ",~{x" + strconv.Itoa(i) + "}"
}
constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = c.builder.CreateCall(target, args, "")
default:
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
}
switch c.GOOS {
case "linux":
// Return values: r0, r1 uintptr, err Errno
// Pseudocode:
// var err uintptr
// if syscallResult < 0 && syscallResult > -4096 {
// err = -syscallResult
// }
// return syscallResult, 0, err
zero := llvm.ConstInt(c.uintptrType, 0, false)
inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
case "darwin":
// Return values: r0, r1 uintptr, err Errno
// Pseudocode:
// var err uintptr
// if syscallResult != 0 {
// err = syscallResult
// }
// return syscallResult, 0, err
zero := llvm.ConstInt(c.uintptrType, 0, false)
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
default:
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ package interp
// This file provides useful types for errors encountered during IR evaluation.
import (
"github.com/aykevl/go-llvm"
"tinygo.org/x/go-llvm"
)
type Unsupported struct {
+61 -18
View File
@@ -7,7 +7,7 @@ import (
"errors"
"strings"
"github.com/aykevl/go-llvm"
"tinygo.org/x/go-llvm"
)
type frame struct {
@@ -84,15 +84,18 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// Memory operators
case !inst.IsAAllocaInst().IsNil():
fr.locals[inst] = &AllocaValue{
Underlying: getZeroValue(inst.Type().ElementType()),
Dirty: false,
allocType := inst.Type().ElementType()
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
alloca.SetInitializer(getZeroValue(allocType))
alloca.SetLinkage(llvm.InternalLinkage)
fr.locals[inst] = &LocalValue{
Underlying: alloca,
Eval: fr.Eval,
}
case !inst.IsALoadInst().IsNil():
operand := fr.getLocal(inst.Operand(0))
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
var value llvm.Value
if !operand.IsConstant() || inst.IsVolatile() {
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()
@@ -173,11 +176,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
continue // special case: bitcast of alloc
}
}
value := fr.getLocal(operand)
if bc, ok := value.(*PointerCastValue); ok {
value = bc.Underlying // avoid double bitcasts
}
fr.locals[inst] = &PointerCastValue{Eval: fr.Eval, Underlying: value, CastType: inst.Type()}
value := fr.getLocal(operand).(*LocalValue)
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
// Other operators
case !inst.IsAICmpInst().IsNil():
@@ -222,7 +222,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
alloc.SetInitializer(getZeroValue(allocType))
alloc.SetLinkage(llvm.InternalLinkage)
result := &GlobalValue{
result := &LocalValue{
Underlying: alloc,
Eval: fr.Eval,
}
@@ -244,13 +244,18 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case callee.Name() == "runtime.hashmapStringSet":
// set a string key in the map
m := fr.getLocal(inst.Operand(0)).(*MapValue)
keyBuf := fr.getLocal(inst.Operand(1))
keyLen := fr.getLocal(inst.Operand(2))
valPtr := fr.getLocal(inst.Operand(3))
// "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters.
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
keyLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
valPtr := fr.getLocal(inst.Operand(3)).(*LocalValue)
m.PutString(keyBuf, keyLen, valPtr)
case callee.Name() == "runtime.hashmapBinarySet":
// set a binary (int etc.) key in the map
// TODO: unimplemented
m := fr.getLocal(inst.Operand(0)).(*MapValue)
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
m.PutBinary(keyBuf, valPtr)
case callee.Name() == "runtime.stringConcat":
// adding two strings together
buf1Ptr := fr.getLocal(inst.Operand(0))
@@ -302,8 +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":
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), fr.TargetData.IntPtrType())}
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.
+15 -9
View File
@@ -10,7 +10,7 @@ import (
"errors"
"strings"
"github.com/aykevl/go-llvm"
"tinygo.org/x/go-llvm"
)
type Eval struct {
@@ -42,10 +42,19 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
initAll := mod.NamedFunction(name)
bb := initAll.EntryBasicBlock()
e.builder.SetInsertPointBefore(bb.LastInstruction())
// Create a dummy alloca in the entry block that we can set the insert point
// to. This is necessary because otherwise we might be removing the
// instruction (init call) that we are removing after successful
// interpretation.
e.builder.SetInsertPointBefore(bb.FirstInstruction())
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
e.builder.SetInsertPointBefore(dummy)
e.builder.SetInstDebugLocation(bb.FirstInstruction())
var initCalls []llvm.Value
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst == dummy {
continue
}
if !inst.IsAReturnInst().IsNil() {
break // ret void
}
@@ -63,14 +72,15 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
return errors.New("expected all instructions in " + name + " to be *.init() calls")
}
pkgName := initName[:len(initName)-5]
_, err := e.Function(call.CalledValue(), []Value{&LocalValue{e, undefPtr}}, pkgName)
fn := call.CalledValue()
call.EraseFromParentAsInstruction()
_, err := e.Function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
if err == ErrUnreachable {
break
}
if err != nil {
return err
}
call.EraseFromParentAsInstruction()
}
return nil
@@ -114,11 +124,7 @@ func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (
// getValue determines what kind of LLVM value it gets and returns the
// appropriate Value type.
func (e *Eval) getValue(v llvm.Value) Value {
if !v.IsAGlobalVariable().IsNil() {
return &GlobalValue{e, v}
} else {
return &LocalValue{e, v}
}
return &LocalValue{e, v}
}
// markDirty marks the passed-in LLVM value dirty, recursively. For example,
+25 -11
View File
@@ -1,7 +1,7 @@
package interp
import (
"github.com/aykevl/go-llvm"
"tinygo.org/x/go-llvm"
)
type sideEffectSeverity int
@@ -24,6 +24,18 @@ type sideEffectResult struct {
// returns whether this function has side effects and if it does, which globals
// it mentions anywhere in this function or any called functions.
func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
switch fn.Name() {
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)
}
@@ -73,25 +85,27 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
result.updateSeverity(sideEffectAll)
continue
}
name := child.Name()
if child.IsDeclaration() {
if name == "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 result.hasLocalSideEffects(dirtyLocals, inst) {
if e.hasLocalSideEffects(dirtyLocals, inst) {
result.updateSeverity(sideEffectLimited)
}
continue
}
childSideEffects := e.hasSideEffects(fn)
childSideEffects := e.hasSideEffects(child)
switch childSideEffects.severity {
case sideEffectInProgress, sideEffectNone:
// no side effects or recursive function - continue scanning
case sideEffectLimited:
// The return value may be problematic.
if e.hasLocalSideEffects(dirtyLocals, inst) {
result.updateSeverity(sideEffectLimited)
}
case sideEffectAll:
result.updateSeverity(sideEffectAll)
default:
result.update(childSideEffects)
panic("unreachable")
}
case llvm.Load, llvm.Store:
if inst.IsVolatile() {
@@ -118,7 +132,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
// hasLocalSideEffects checks whether the given instruction flows into a branch
// or return instruction, in which case the whole function must be marked as
// having side effects and be called at runtime.
func (r *sideEffectResult) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
if _, ok := dirtyLocals[inst]; ok {
// It is already known that this local is dirty.
return true
@@ -156,7 +170,7 @@ func (r *sideEffectResult) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct
// For a list:
// https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode
dirtyLocals[user] = struct{}{}
if r.hasLocalSideEffects(dirtyLocals, user) {
if e.hasLocalSideEffects(dirtyLocals, user) {
return true
}
}
+4 -1
View File
@@ -1,7 +1,7 @@
package interp
import (
"github.com/aykevl/go-llvm"
"tinygo.org/x/go-llvm"
)
// Return a list of values (actually, instructions) where this value is used as
@@ -63,6 +63,9 @@ func getZeroValue(typ llvm.Type) llvm.Value {
// getStringBytes loads the byte slice of a Go string represented as a
// {ptr, len} pair.
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
if !strLen.IsConstant() {
panic("getStringBytes with a non-constant length")
}
buf := make([]byte, strLen.ZExtValue())
for i := range buf {
c := strPtr.GetElementPtr([]uint32{uint32(i)}).Load()
+94 -292
View File
@@ -5,7 +5,7 @@ package interp
import (
"strconv"
"github.com/aykevl/go-llvm"
"tinygo.org/x/go-llvm"
)
// A Value is a LLVM value with some extra methods attached for easier
@@ -36,11 +36,17 @@ func (v *LocalValue) Type() llvm.Type {
}
func (v *LocalValue) IsConstant() bool {
if _, ok := v.Eval.dirtyGlobals[v.Underlying]; ok {
return false
}
return v.Underlying.IsConstant()
}
// Load loads a constant value if this is a constant GEP, otherwise it panics.
// Load loads a constant value if this is a constant pointer.
func (v *LocalValue) Load() llvm.Value {
if !v.Underlying.IsAGlobalVariable().IsNil() {
return v.Underlying.Initializer()
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr:
indices := v.getConstGEPIndices()
@@ -50,21 +56,32 @@ func (v *LocalValue) Load() llvm.Value {
global := v.Eval.getValue(v.Underlying.Operand(0))
agg := global.Load()
return llvm.ConstExtractValue(agg, indices[1:])
case llvm.BitCast:
panic("interp: load from a bitcast")
default:
panic("interp: load from a constant")
}
}
// Store stores to the underlying value if the value type is a constant GEP,
// Store stores to the underlying value if the value type is a pointer type,
// otherwise it panics.
func (v *LocalValue) Store(value llvm.Value) {
if !v.Underlying.IsAGlobalVariable().IsNil() {
if !value.IsConstant() {
v.MarkDirty()
v.Eval.builder.CreateStore(value, v.Underlying)
} else {
v.Underlying.SetInitializer(value)
}
return
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr:
indices := v.getConstGEPIndices()
if indices[0] != 0 {
panic("invalid GEP")
}
global := &GlobalValue{v.Eval, v.Underlying.Operand(0)}
global := &LocalValue{v.Eval, v.Underlying.Operand(0)}
agg := global.Load()
agg = llvm.ConstInsertValue(agg, value, indices[1:])
global.Store(agg)
@@ -74,10 +91,13 @@ func (v *LocalValue) Store(value llvm.Value) {
}
}
// GetElementPtr returns a constant GEP when the underlying value is also a
// constant GEP. It panics when the underlying value is not a constant GEP:
// getting the pointer to a constant is not possible.
// GetElementPtr returns a GEP when the underlying value is of pointer type.
func (v *LocalValue) GetElementPtr(indices []uint32) Value {
if !v.Underlying.IsAGlobalVariable().IsNil() {
int32Type := v.Underlying.Type().Context().Int32Type()
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
return &LocalValue{v.Eval, gep}
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr, llvm.IntToPtr:
int32Type := v.Underlying.Type().Context().Int32Type()
@@ -107,283 +127,18 @@ func (v *LocalValue) getConstGEPIndices() []uint32 {
return indices
}
// GlobalValue wraps a LLVM global variable.
type GlobalValue struct {
Eval *Eval
Underlying llvm.Value
}
// Value returns the initializer for this global variable.
func (v *GlobalValue) Value() llvm.Value {
return v.Underlying
}
// Type returns the type of this global variable, which is a pointer type. Use
// Type().ElementType() to get the actual global variable type.
func (v *GlobalValue) Type() llvm.Type {
return v.Underlying.Type()
}
// IsConstant returns true if this global is not dirty, false otherwise.
func (v *GlobalValue) IsConstant() bool {
if _, ok := v.Eval.dirtyGlobals[v.Underlying]; ok {
return false
}
return true
}
// Load returns the initializer of the global variable.
func (v *GlobalValue) Load() llvm.Value {
return v.Underlying.Initializer()
}
// Store sets the initializer of the global variable.
func (v *GlobalValue) Store(value llvm.Value) {
if !value.IsConstant() {
v.MarkDirty()
v.Eval.builder.CreateStore(value, v.Underlying)
} else {
v.Underlying.SetInitializer(value)
}
}
// GetElementPtr returns a constant GEP on this global, which can be used in
// load and store instructions.
func (v *GlobalValue) GetElementPtr(indices []uint32) Value {
int32Type := v.Underlying.Type().Context().Int32Type()
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
return &LocalValue{v.Eval, gep}
}
func (v *GlobalValue) String() string {
return "&GlobalValue{" + v.Underlying.Name() + "}"
}
// MarkDirty marks this global as dirty, meaning that every load from and store
// to this global (from now on) must be performed at runtime.
func (v *GlobalValue) MarkDirty() {
func (v *LocalValue) MarkDirty() {
if v.Underlying.IsAGlobalVariable().IsNil() {
panic("trying to mark a non-global as dirty")
}
if !v.IsConstant() {
return // already dirty
}
v.Eval.dirtyGlobals[v.Underlying] = struct{}{}
}
// An alloca represents a local alloca, which is a stack allocated variable.
// It is emulated by storing the constant of the alloca.
type AllocaValue struct {
Eval *Eval
Underlying llvm.Value // the constant value itself if not dirty, otherwise the alloca instruction
Dirty bool // this value must be evaluated at runtime
}
// Value turns this alloca into a runtime alloca instead of a compile-time
// constant (if not already converted), and returns the alloca itself.
func (v *AllocaValue) Value() llvm.Value {
if !v.Dirty {
// Mark this alloca a dirty, meaning it is run at runtime instead of
// compile time.
alloca := v.Eval.builder.CreateAlloca(v.Underlying.Type(), "")
v.Eval.builder.CreateStore(v.Underlying, alloca)
v.Dirty = true
v.Underlying = alloca
}
return v.Underlying
}
// Type returns the type of this alloca, which is always a pointer.
func (v *AllocaValue) Type() llvm.Type {
if v.Dirty {
return v.Underlying.Type()
} else {
return llvm.PointerType(v.Underlying.Type(), 0)
}
}
func (v *AllocaValue) IsConstant() bool {
return !v.Dirty
}
// Load returns the value this alloca contains, which may be evaluated at
// runtime.
func (v *AllocaValue) Load() llvm.Value {
if v.Dirty {
ret := v.Eval.builder.CreateLoad(v.Underlying, "")
if ret.IsNil() {
panic("alloca is nil")
}
return ret
} else {
if v.Underlying.IsNil() {
panic("alloca is nil")
}
return v.Underlying
}
}
// Store updates the value of this alloca.
func (v *AllocaValue) Store(value llvm.Value) {
if v.Underlying.Type() != value.Type() {
panic("interp: trying to store to an alloca with a different type")
}
if v.Dirty || !value.IsConstant() {
v.Eval.builder.CreateStore(value, v.Value())
} else {
v.Underlying = value
}
}
// GetElementPtr returns a value (a *GetElementPtrValue) that keeps a reference
// to this alloca, so that Load() and Store() continue to work.
func (v *AllocaValue) GetElementPtr(indices []uint32) Value {
return &GetElementPtrValue{v, indices}
}
func (v *AllocaValue) String() string {
return "&AllocaValue{Type: " + v.Type().String() + "}"
}
// GetElementPtrValue wraps an alloca, keeping track of what the GEP points to
// so it can be used as a pointer value (with Load() and Store()).
type GetElementPtrValue struct {
Alloca *AllocaValue
Indices []uint32
}
// Type returns the type of this GEP, which is always of type pointer.
func (v *GetElementPtrValue) Type() llvm.Type {
if v.Alloca.Dirty {
return v.Value().Type()
} else {
return llvm.PointerType(v.Load().Type(), 0)
}
}
func (v *GetElementPtrValue) IsConstant() bool {
return v.Alloca.IsConstant()
}
// Value creates the LLVM GEP instruction of this GetElementPtrValue wrapper and
// returns it.
func (v *GetElementPtrValue) Value() llvm.Value {
if v.Alloca.Dirty {
alloca := v.Alloca.Value()
int32Type := v.Alloca.Type().Context().Int32Type()
llvmIndices := getLLVMIndices(int32Type, v.Indices)
return v.Alloca.Eval.builder.CreateGEP(alloca, llvmIndices, "")
} else {
panic("interp: todo: pointer to alloca gep")
}
}
// Load deferences the pointer this GEP points to. For a constant GEP, it
// extracts the value from the underlying alloca.
func (v *GetElementPtrValue) Load() llvm.Value {
if v.Alloca.Dirty {
gep := v.Value()
return v.Alloca.Eval.builder.CreateLoad(gep, "")
} else {
underlying := v.Alloca.Load()
indices := v.Indices
if indices[0] != 0 {
panic("invalid GEP")
}
return llvm.ConstExtractValue(underlying, indices[1:])
}
}
// Store stores to the pointer this GEP points to. For a constant GEP, it
// updates the underlying allloca.
func (v *GetElementPtrValue) Store(value llvm.Value) {
if v.Alloca.Dirty || !value.IsConstant() {
alloca := v.Alloca.Value()
int32Type := v.Alloca.Type().Context().Int32Type()
llvmIndices := getLLVMIndices(int32Type, v.Indices)
gep := v.Alloca.Eval.builder.CreateGEP(alloca, llvmIndices, "")
v.Alloca.Eval.builder.CreateStore(value, gep)
} else {
underlying := v.Alloca.Load()
indices := v.Indices
if indices[0] != 0 {
panic("invalid GEP")
}
underlying = llvm.ConstInsertValue(underlying, value, indices[1:])
v.Alloca.Store(underlying)
}
}
func (v *GetElementPtrValue) GetElementPtr(indices []uint32) Value {
if v.Alloca.Dirty {
panic("interp: todo: gep on a dirty gep")
} else {
combined := append([]uint32{}, v.Indices...)
combined[len(combined)-1] += indices[0]
combined = append(combined, indices[1:]...)
return &GetElementPtrValue{v.Alloca, combined}
}
}
func (v *GetElementPtrValue) String() string {
indices := ""
for _, n := range v.Indices {
if indices != "" {
indices += ", "
}
indices += strconv.Itoa(int(n))
}
return "&GetElementPtrValue{Alloca: " + v.Alloca.String() + ", Indices: [" + indices + "]}"
}
// PointerCastValue represents a bitcast operation on a pointer.
type PointerCastValue struct {
Eval *Eval
Underlying Value
CastType llvm.Type
}
// Value returns a constant bitcast value.
func (v *PointerCastValue) Value() llvm.Value {
from := v.Underlying.Value()
return llvm.ConstBitCast(from, v.CastType)
}
// Type returns the type this pointer has been cast to.
func (v *PointerCastValue) Type() llvm.Type {
return v.CastType
}
func (v *PointerCastValue) IsConstant() bool {
return v.Underlying.IsConstant()
}
// Load tries to load and bitcast the given value. If this value cannot be
// bitcasted, Load panics.
func (v *PointerCastValue) Load() llvm.Value {
if v.Underlying.IsConstant() {
typeFrom := v.Underlying.Type().ElementType()
typeTo := v.CastType.ElementType()
if isScalar(typeFrom) && isScalar(typeTo) && v.Eval.TargetData.TypeAllocSize(typeFrom) == v.Eval.TargetData.TypeAllocSize(typeTo) {
return llvm.ConstBitCast(v.Underlying.Load(), v.CastType.ElementType())
}
}
panic("interp: load from a pointer bitcast: " + v.String())
}
// Store panics: it is not (yet) possible to store directly to a bitcast.
func (v *PointerCastValue) Store(value llvm.Value) {
panic("interp: store on a pointer bitcast")
}
// GetElementPtr panics: it is not (yet) possible to do a GEP operation on a
// bitcast.
func (v *PointerCastValue) GetElementPtr(indices []uint32) Value {
panic("interp: GEP on a pointer bitcast")
}
func (v *PointerCastValue) String() string {
return "&PointerCastValue{Value: " + v.Underlying.String() + ", CastType: " + v.CastType.String() + "}"
}
// MapValue implements a Go map which is created at compile time and stored as a
// global variable.
type MapValue struct {
@@ -451,6 +206,13 @@ func (v *MapValue) Value() llvm.Value {
keyBuf[i] = byte(n)
n >>= 8
}
} else if key.Type().TypeKind() == llvm.ArrayTypeKind &&
key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind &&
key.Type().ElementType().IntTypeWidth() == 8 {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
for i := range keyBuf {
keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue())
}
} else {
panic("interp: map key type not implemented: " + key.Type().String())
}
@@ -527,27 +289,24 @@ func (v *MapValue) GetElementPtr(indices []uint32) Value {
// PutString does a map assign operation, assuming that the map is of type
// map[string]T.
func (v *MapValue) PutString(keyBuf, keyLen, valPtr Value) {
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
if !v.Underlying.IsNil() {
panic("map already created")
}
var value llvm.Value
switch valPtr := valPtr.(type) {
case *PointerCastValue:
value = valPtr.Underlying.Load()
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
panic("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
panic("interp: map store value type is inconsistent")
}
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
}
value := valPtr.Load()
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
panic("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
panic("interp: map store value type is inconsistent")
}
default:
panic("interp: todo: handle map value pointer")
}
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
@@ -561,6 +320,49 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr Value) {
v.Values = append(v.Values, &LocalValue{v.Eval, value})
}
// PutBinary does a map assign operation.
func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) {
if !v.Underlying.IsNil() {
panic("map already created")
}
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
}
value := valPtr.Load()
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
panic("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
panic("interp: map store value type is inconsistent")
}
}
if keyPtr.Underlying.Opcode() == llvm.BitCast {
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
} else if keyPtr.Underlying.Opcode() == llvm.GetElementPtr {
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
}
key := keyPtr.Load()
if v.KeyType.IsNil() {
v.KeyType = key.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize {
panic("interp: map store key type has the wrong size")
}
} else {
if key.Type() != v.KeyType {
panic("interp: map store key type is inconsistent")
}
}
// TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value})
}
// Get FNV-1a hash of this string.
//
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
-522
View File
@@ -1,522 +0,0 @@
package ir
// This file provides functionality to interpret very basic Go SSA, for
// compile-time initialization of globals.
import (
"errors"
"fmt"
"go/constant"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/ssa"
)
var ErrCGoWrapper = errors.New("tinygo internal: cgo wrapper") // a signal, not an error
// Ignore these calls (replace with a zero return value) when encountered during
// interpretation.
var ignoreInitCalls = map[string]struct{}{
"syscall.runtime_envs": struct{}{},
"syscall/js.predefValue": struct{}{},
"(syscall/js.Value).Get": struct{}{},
"(syscall/js.Value).New": struct{}{},
"(syscall/js.Value).Int": struct{}{},
"os.init$1": struct{}{},
}
// Interpret instructions as far as possible, and drop those instructions from
// the basic block.
func (p *Program) Interpret(block *ssa.BasicBlock, dumpSSA bool) error {
if dumpSSA {
fmt.Printf("\ninterpret: %s\n", block.Parent().Pkg.Pkg.Path())
}
for {
i, err := p.interpret(block.Instrs, nil, nil, nil, dumpSSA)
if err == ErrCGoWrapper {
// skip this instruction
block.Instrs = block.Instrs[i+1:]
continue
}
block.Instrs = block.Instrs[i:]
return err
}
}
// Interpret instructions as far as possible, and return the index of the first
// unknown instruction.
func (p *Program) interpret(instrs []ssa.Instruction, paramKeys []*ssa.Parameter, paramValues []Value, results []Value, dumpSSA bool) (int, error) {
locals := map[ssa.Value]Value{}
for i, key := range paramKeys {
locals[key] = paramValues[i]
}
for i, instr := range instrs {
if _, ok := instr.(*ssa.DebugRef); ok {
continue
}
if dumpSSA {
if val, ok := instr.(ssa.Value); ok && val.Name() != "" {
fmt.Printf("\t%s: %s = %s\n", instr.Parent().RelString(nil), val.Name(), val.String())
} else {
fmt.Printf("\t%s: %s\n", instr.Parent().RelString(nil), instr.String())
}
}
switch instr := instr.(type) {
case *ssa.Alloc:
alloc, err := p.getZeroValue(instr.Type().Underlying().(*types.Pointer).Elem())
if err != nil {
return i, err
}
locals[instr] = &PointerValue{nil, &alloc}
case *ssa.BinOp:
if typ, ok := instr.Type().(*types.Basic); ok && typ.Kind() == types.String {
// Concatenate two strings.
// This happens in the time package, for example.
x, err := p.getValue(instr.X, locals)
if err != nil {
return i, err
}
y, err := p.getValue(instr.Y, locals)
if err != nil {
return i, err
}
xstr := constant.StringVal(x.(*ConstValue).Expr.Value)
ystr := constant.StringVal(y.(*ConstValue).Expr.Value)
locals[instr] = &ConstValue{ssa.NewConst(constant.MakeString(xstr+ystr), types.Typ[types.String])}
} else {
return i, errors.New("init: unknown binop: " + instr.String())
}
case *ssa.Call:
common := instr.Common()
callee := common.StaticCallee()
if callee == nil {
return i, nil // don't understand dynamic dispatch
}
if _, ok := ignoreInitCalls[callee.String()]; ok {
// These calls are not needed and can be ignored, for the time
// being.
results := make([]Value, callee.Signature.Results().Len())
for i := range results {
var err error
results[i], err = p.getZeroValue(callee.Signature.Results().At(i).Type())
if err != nil {
return i, err
}
}
if len(results) == 1 {
locals[instr] = results[0]
} else if len(results) > 1 {
locals[instr] = &StructValue{Fields: results}
}
continue
}
if callee.String() == "os.NewFile" {
// Emulate the creation of os.Stdin, os.Stdout and os.Stderr.
resultPtrType := callee.Signature.Results().At(0).Type().(*types.Pointer)
resultStructOuterType := resultPtrType.Elem().Underlying().(*types.Struct)
if resultStructOuterType.NumFields() != 1 {
panic("expected 1 field in os.File struct")
}
fileInnerPtrType := resultStructOuterType.Field(0).Type().(*types.Pointer)
fileInnerType := fileInnerPtrType.Elem().(*types.Named)
fileInnerStructType := fileInnerType.Underlying().(*types.Struct)
fileInner, err := p.getZeroValue(fileInnerType) // os.file
if err != nil {
return i, err
}
for fieldIndex := 0; fieldIndex < fileInnerStructType.NumFields(); fieldIndex++ {
field := fileInnerStructType.Field(fieldIndex)
if field.Name() == "name" {
// Set the 'name' field.
name, err := p.getValue(common.Args[1], locals)
if err != nil {
return i, err
}
fileInner.(*StructValue).Fields[fieldIndex] = name
} else if field.Type().String() == "internal/poll.FD" {
// Set the file descriptor field.
field := field.Type().Underlying().(*types.Struct)
for subfieldIndex := 0; subfieldIndex < field.NumFields(); subfieldIndex++ {
subfield := field.Field(subfieldIndex)
if subfield.Name() == "Sysfd" {
sysfd, err := p.getValue(common.Args[0], locals)
if err != nil {
return i, err
}
sysfd = &ConstValue{Expr: ssa.NewConst(sysfd.(*ConstValue).Expr.Value, subfield.Type())}
fileInner.(*StructValue).Fields[fieldIndex].(*StructValue).Fields[subfieldIndex] = sysfd
}
}
}
}
fileInnerPtr := &PointerValue{fileInnerPtrType, &fileInner} // *os.file
var fileOuter Value = &StructValue{Type: resultPtrType.Elem(), Fields: []Value{fileInnerPtr}} // os.File
result := &PointerValue{resultPtrType.Elem(), &fileOuter} // *os.File
locals[instr] = result
continue
}
if canInterpret(callee) {
params := make([]Value, len(common.Args))
for i, arg := range common.Args {
val, err := p.getValue(arg, locals)
if err != nil {
return i, err
}
params[i] = val
}
results := make([]Value, callee.Signature.Results().Len())
subi, err := p.interpret(callee.Blocks[0].Instrs, callee.Params, params, results, dumpSSA)
if err != nil {
return i, err
}
if subi != len(callee.Blocks[0].Instrs) {
return i, errors.New("init: could not interpret all instructions of subroutine")
}
if len(results) == 1 {
locals[instr] = results[0]
} else {
panic("unimplemented: not exactly 1 result")
}
continue
}
if callee.Object() == nil || callee.Object().Name() == "init" {
return i, nil // arrived at the init#num functions
}
return i, errors.New("todo: init call: " + callee.String())
case *ssa.ChangeType:
x, err := p.getValue(instr.X, locals)
if err != nil {
return i, 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 := instr.Type().Underlying().(*types.Struct); ok {
return i, errors.New("todo: init: " + instr.String())
}
locals[instr] = x
case *ssa.Convert:
x, err := p.getValue(instr.X, locals)
if err != nil {
return i, err
}
typeFrom := instr.X.Type().Underlying()
switch typeTo := instr.Type().Underlying().(type) {
case *types.Basic:
if typeTo.Kind() == types.String {
return i, nil
}
if _, ok := typeFrom.(*types.Pointer); ok && typeTo.Kind() == types.UnsafePointer {
locals[instr] = &PointerBitCastValue{typeTo, x}
} else if typeFrom, ok := typeFrom.(*types.Basic); ok {
if typeFrom.Kind() == types.UnsafePointer && typeTo.Kind() == types.Uintptr {
locals[instr] = &PointerToUintptrValue{x}
} else if typeFrom.Info()&types.IsInteger != 0 && typeTo.Info()&types.IsInteger != 0 {
locals[instr] = &ConstValue{Expr: ssa.NewConst(x.(*ConstValue).Expr.Value, typeTo)}
} else {
return i, nil
}
} else {
return i, nil
}
case *types.Pointer:
if typeFrom, ok := typeFrom.(*types.Basic); ok && typeFrom.Kind() == types.UnsafePointer {
locals[instr] = &PointerBitCastValue{typeTo, x}
} else {
panic("expected unsafe pointer conversion")
}
default:
return i, nil
}
case *ssa.DebugRef:
// ignore
case *ssa.Extract:
tuple, err := p.getValue(instr.Tuple, locals)
if err != nil {
return i, err
}
locals[instr] = tuple.(*StructValue).Fields[instr.Index]
case *ssa.FieldAddr:
x, err := p.getValue(instr.X, locals)
if err != nil {
return i, err
}
var structVal *StructValue
switch x := x.(type) {
case *GlobalValue:
structVal = x.Global.initializer.(*StructValue)
case *PointerValue:
structVal = (*x.Elem).(*StructValue)
default:
panic("expected a pointer")
}
locals[instr] = &PointerValue{nil, &structVal.Fields[instr.Field]}
case *ssa.IndexAddr:
x, err := p.getValue(instr.X, locals)
if err != nil {
return i, err
}
if cnst, ok := instr.Index.(*ssa.Const); ok {
index, _ := constant.Int64Val(cnst.Value)
switch xPtr := x.(type) {
case *GlobalValue:
x = xPtr.Global.initializer
case *PointerValue:
x = *xPtr.Elem
default:
panic("expected a pointer")
}
switch x := x.(type) {
case *ArrayValue:
locals[instr] = &PointerValue{nil, &x.Elems[index]}
default:
return i, errors.New("todo: init IndexAddr not on an array or struct")
}
} else {
return i, errors.New("todo: init IndexAddr index: " + instr.Index.String())
}
case *ssa.MakeMap:
locals[instr] = &MapValue{instr.Type().Underlying().(*types.Map), nil, nil}
case *ssa.MapUpdate:
// Assume no duplicate keys exist. This is most likely true for
// autogenerated code, but may not be true when trying to interpret
// user code.
key, err := p.getValue(instr.Key, locals)
if err != nil {
return i, err
}
value, err := p.getValue(instr.Value, locals)
if err != nil {
return i, err
}
x := locals[instr.Map].(*MapValue)
x.Keys = append(x.Keys, key)
x.Values = append(x.Values, value)
case *ssa.Return:
for i, r := range instr.Results {
val, err := p.getValue(r, locals)
if err != nil {
return i, err
}
results[i] = val
}
case *ssa.Slice:
// Turn a just-allocated array into a slice.
if instr.Low != nil || instr.High != nil || instr.Max != nil {
return i, errors.New("init: slice expression with bounds")
}
source, err := p.getValue(instr.X, locals)
if err != nil {
return i, err
}
switch source := source.(type) {
case *PointerValue: // pointer to array
array := (*source.Elem).(*ArrayValue)
locals[instr] = &SliceValue{instr.Type().Underlying().(*types.Slice), array}
default:
return i, errors.New("init: unknown slice type")
}
case *ssa.Store:
if addr, ok := instr.Addr.(*ssa.Global); ok {
if strings.HasPrefix(instr.Addr.Name(), "__cgofn__cgo_") || strings.HasPrefix(instr.Addr.Name(), "_cgo_") {
// Ignore CGo global variables which we don't use.
continue
}
value, err := p.getValue(instr.Val, locals)
if err != nil {
return i, err
}
p.GetGlobal(addr).initializer = value
} else if addr, ok := locals[instr.Addr]; ok {
value, err := p.getValue(instr.Val, locals)
if err != nil {
return i, err
}
if addr, ok := addr.(*PointerValue); ok {
*(addr.Elem) = value
} else {
panic("store to non-pointer")
}
} else {
return i, errors.New("todo: init Store: " + instr.String())
}
case *ssa.UnOp:
if instr.Op != token.MUL || instr.CommaOk {
return i, errors.New("init: unknown unop: " + instr.String())
}
valPtr, err := p.getValue(instr.X, locals)
if err != nil {
return i, err
}
switch valPtr := valPtr.(type) {
case *GlobalValue:
locals[instr] = valPtr.Global.initializer
case *PointerValue:
locals[instr] = *valPtr.Elem
default:
panic("expected a pointer")
}
default:
return i, nil
}
}
return len(instrs), nil
}
// Check whether this function can be interpreted at compile time. For that, it
// needs to only contain relatively simple instructions (for example, no control
// flow).
func canInterpret(callee *ssa.Function) bool {
if len(callee.Blocks) != 1 || callee.Signature.Results().Len() != 1 {
// No control flow supported so only one basic block.
// Only exactly one return value supported right now so check that as
// well.
return false
}
for _, instr := range callee.Blocks[0].Instrs {
switch instr.(type) {
// Ignore all functions fully supported by Program.interpret()
// above.
case *ssa.Alloc:
case *ssa.ChangeType:
case *ssa.DebugRef:
case *ssa.Extract:
case *ssa.FieldAddr:
case *ssa.IndexAddr:
case *ssa.MakeMap:
case *ssa.MapUpdate:
case *ssa.Return:
case *ssa.Slice:
case *ssa.Store:
case *ssa.UnOp:
default:
return false
}
}
return true
}
func (p *Program) getValue(value ssa.Value, locals map[ssa.Value]Value) (Value, error) {
switch value := value.(type) {
case *ssa.Const:
return &ConstValue{value}, nil
case *ssa.Function:
return &FunctionValue{value.Type(), value}, nil
case *ssa.Global:
if strings.HasPrefix(value.Name(), "__cgofn__cgo_") || strings.HasPrefix(value.Name(), "_cgo_") {
// Ignore CGo global variables which we don't use.
return nil, ErrCGoWrapper
}
g := p.GetGlobal(value)
if g.initializer == nil {
value, err := p.getZeroValue(value.Type().Underlying().(*types.Pointer).Elem())
if err != nil {
return nil, err
}
g.initializer = value
}
return &GlobalValue{g}, nil
default:
if local, ok := locals[value]; ok {
return local, nil
} else {
return nil, errors.New("todo: init: unknown value: " + value.String())
}
}
}
func (p *Program) getZeroValue(t types.Type) (Value, error) {
switch typ := t.Underlying().(type) {
case *types.Array:
elems := make([]Value, typ.Len())
for i := range elems {
elem, err := p.getZeroValue(typ.Elem())
if err != nil {
return nil, err
}
elems[i] = elem
}
return &ArrayValue{typ.Elem(), elems}, nil
case *types.Basic:
return &ZeroBasicValue{typ}, nil
case *types.Signature:
return &FunctionValue{typ, nil}, nil
case *types.Map:
return &MapValue{typ, nil, nil}, nil
case *types.Pointer:
return &PointerValue{typ, nil}, nil
case *types.Struct:
elems := make([]Value, typ.NumFields())
for i := range elems {
elem, err := p.getZeroValue(typ.Field(i).Type())
if err != nil {
return nil, err
}
elems[i] = elem
}
return &StructValue{t, elems}, nil
case *types.Slice:
return &SliceValue{typ, nil}, nil
default:
return nil, errors.New("todo: init: unknown global type: " + typ.String())
}
}
// Boxed value for interpreter.
type Value interface {
}
type ConstValue struct {
Expr *ssa.Const
}
type ZeroBasicValue struct {
Type *types.Basic
}
type PointerValue struct {
Type types.Type
Elem *Value
}
type FunctionValue struct {
Type types.Type
Elem *ssa.Function
}
type PointerBitCastValue struct {
Type types.Type
Elem Value
}
type PointerToUintptrValue struct {
Elem Value
}
type GlobalValue struct {
Global *Global
}
type ArrayValue struct {
ElemType types.Type
Elems []Value
}
type StructValue struct {
Type types.Type // types.Struct or types.Named
Fields []Value
}
type SliceValue struct {
Type *types.Slice
Array *ArrayValue
}
type MapValue struct {
Type *types.Map
Keys []Value
Values []Value
}
+43 -51
View File
@@ -7,9 +7,9 @@ import (
"sort"
"strings"
"github.com/aykevl/go-llvm"
"github.com/aykevl/tinygo/loader"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// This file provides a wrapper around go/ssa values and adds extra
@@ -18,41 +18,35 @@ import (
// View on all functions, types, and globals in a program, with analysis
// results.
type Program struct {
Program *ssa.Program
LoaderProgram *loader.Program
mainPkg *ssa.Package
Functions []*Function
functionMap map[*ssa.Function]*Function
Globals []*Global
globalMap map[*ssa.Global]*Global
comments map[string]*ast.CommentGroup
NamedTypes []*NamedType
needsScheduler bool
goCalls []*ssa.Go
Program *ssa.Program
LoaderProgram *loader.Program
mainPkg *ssa.Package
Functions []*Function
functionMap map[*ssa.Function]*Function
Globals []*Global
globalMap map[*ssa.Global]*Global
comments map[string]*ast.CommentGroup
NamedTypes []*NamedType
}
// Function or method.
type Function struct {
*ssa.Function
LLVMFn llvm.Value
linkName string // go:linkname, go:export, go:interrupt
exported bool // go:export
nobounds bool // go:nobounds
blocking bool // calculated by AnalyseBlockingRecursive
flag bool // used by dead code elimination
interrupt bool // go:interrupt
parents []*Function // calculated by AnalyseCallgraph
children []*Function // calculated by AnalyseCallgraph
linkName string // go:linkname, go:export, go:interrupt
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
interrupt bool // go:interrupt
}
// Global variable, possibly constant.
type Global struct {
*ssa.Global
program *Program
LLVMGlobal llvm.Value
linkName string // go:extern
extern bool // go:extern
initializer Value
program *Program
LLVMGlobal llvm.Value
linkName string // go:extern
extern bool // go:extern
}
// Type with a name and possibly methods.
@@ -193,9 +187,6 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
func (p *Program) AddPackage(pkg *ssa.Package) {
memberNames := make([]string, 0)
for name := range pkg.Members {
if isCGoInternal(name) {
continue
}
memberNames = append(memberNames, name)
}
sort.Strings(memberNames)
@@ -204,9 +195,6 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
if isCGoInternal(member.Name()) {
continue
}
p.addFunction(member)
case *ssa.Type:
t := &NamedType{Type: member}
@@ -274,10 +262,16 @@ func (f *Function) parsePragmas() {
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
for _, comment := range decl.Doc.List {
if !strings.HasPrefix(comment.Text, "//go:") {
text := comment.Text
if strings.HasPrefix(text, "//export ") {
// Rewrite '//export' to '//go:export' for compatibility with
// gc.
text = "//go:" + text[2:]
}
if !strings.HasPrefix(text, "//go:") {
continue
}
parts := strings.Fields(comment.Text)
parts := strings.Fields(text)
switch parts[0] {
case "//go:export":
if len(parts) != 2 {
@@ -327,7 +321,7 @@ func (f *Function) IsNoBounds() bool {
// Return true iff this function is externally visible.
func (f *Function) IsExported() bool {
return f.exported
return f.exported || f.CName() != ""
}
// Return true for functions annotated with //go:interrupt. The function name is
@@ -335,7 +329,7 @@ func (f *Function) IsExported() bool {
//
// On some platforms (like AVR), interrupts need a special compiler flag.
func (f *Function) IsInterrupt() bool {
return f.exported
return f.interrupt
}
// Return the link name for this function.
@@ -394,15 +388,25 @@ func (g *Global) LinkName() string {
if g.linkName != "" {
return g.linkName
}
if name := g.CName(); name != "" {
return name
}
return g.RelString(nil)
}
func (g *Global) IsExtern() bool {
return g.extern
return g.extern || g.CName() != ""
}
func (g *Global) Initializer() Value {
return g.initializer
// Return the name of the C global if this is a CGo wrapper. Otherwise, return a
// zero-length string.
func (g *Global) CName() string {
name := g.Name()
if strings.HasPrefix(name, "C.") {
// created by ../loader/cgo.go
return name[2:]
}
return ""
}
// Return true if this named type is annotated with the //go:volatile pragma,
@@ -428,18 +432,6 @@ func (p *Program) IsVolatile(t types.Type) bool {
}
}
// Return true if this is a CGo-internal function that can be ignored.
func isCGoInternal(name string) bool {
if strings.HasPrefix(name, "_Cgo_") || strings.HasPrefix(name, "_cgo") {
// _Cgo_ptr, _Cgo_use, _cgoCheckResult, _cgo_runtime_cgocall
return true // CGo-internal functions
}
if strings.HasPrefix(name, "__cgofn__cgo_") {
return true // CGo function pointer in global scope
}
return false
}
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
+4 -125
View File
@@ -56,110 +56,6 @@ func signature(sig *types.Signature) string {
return s
}
// Fill in parents of all functions.
//
// All packages need to be added before this pass can run, or it will produce
// incorrect results.
func (p *Program) AnalyseCallgraph() {
for _, f := range p.Functions {
// Clear, if AnalyseCallgraph has been called before.
f.children = nil
f.parents = nil
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
switch instr := instr.(type) {
case *ssa.Call:
if instr.Common().IsInvoke() {
continue
}
switch call := instr.Call.Value.(type) {
case *ssa.Builtin:
// ignore
case *ssa.Function:
if isCGoInternal(call.Name()) {
continue
}
child := p.GetFunction(call)
if child.CName() != "" {
continue // assume non-blocking
}
if child.RelString(nil) == "time.Sleep" {
f.blocking = true
}
f.children = append(f.children, child)
}
}
}
}
}
for _, f := range p.Functions {
for _, child := range f.children {
child.parents = append(child.parents, f)
}
}
}
// Analyse which functions are recursively blocking.
//
// Depends on AnalyseCallgraph.
func (p *Program) AnalyseBlockingRecursive() {
worklist := make([]*Function, 0)
// Fill worklist with directly blocking functions.
for _, f := range p.Functions {
if f.blocking {
worklist = append(worklist, f)
}
}
// Keep reducing this worklist by marking a function as recursively blocking
// from the worklist and pushing all its parents that are non-blocking.
// This is somewhat similar to a worklist in a mark-sweep garbage collector.
// The work items are then grey objects.
for len(worklist) != 0 {
// Pick the topmost.
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, parent := range f.parents {
if !parent.blocking {
parent.blocking = true
worklist = append(worklist, parent)
}
}
}
}
// Check whether we need a scheduler. A scheduler is only necessary when there
// are go calls that start blocking functions (if they're not blocking, the go
// function can be turned into a regular function call).
//
// Depends on AnalyseBlockingRecursive.
func (p *Program) AnalyseGoCalls() {
p.goCalls = nil
for _, f := range p.Functions {
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
switch instr := instr.(type) {
case *ssa.Go:
p.goCalls = append(p.goCalls, instr)
}
}
}
}
for _, instr := range p.goCalls {
switch instr := instr.Call.Value.(type) {
case *ssa.Builtin:
case *ssa.Function:
if p.functionMap[instr].blocking {
p.needsScheduler = true
}
default:
panic("unknown go call function type")
}
}
}
// Simple pass that removes dead code. This pass makes later analysis passes
// more useful.
func (p *Program) SimpleDCE() {
@@ -172,11 +68,12 @@ func (p *Program) SimpleDCE() {
// functions.
main := p.mainPkg.Members["main"].(*ssa.Function)
runtimePkg := p.Program.ImportedPackage("runtime")
mathPkg := p.Program.ImportedPackage("math")
p.GetFunction(main).flag = true
worklist := []*ssa.Function{main}
for _, f := range p.Functions {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg {
if f.flag || isCGoInternal(f.Name()) {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || (f.Pkg == mathPkg && f.Pkg != nil) {
if f.flag {
continue
}
f.flag = true
@@ -206,7 +103,7 @@ func (p *Program) SimpleDCE() {
}
}
for _, operand := range instr.Operands(nil) {
if operand == nil || *operand == nil || isCGoInternal((*operand).Name()) {
if operand == nil || *operand == nil {
continue
}
switch operand := (*operand).(type) {
@@ -239,21 +136,3 @@ func (p *Program) SimpleDCE() {
}
p.Functions = livefunctions
}
// Whether this function needs a scheduler.
//
// Depends on AnalyseGoCalls.
func (p *Program) NeedsScheduler() bool {
return p.needsScheduler
}
// Whether this function blocks. Builtins are also accepted for convenience.
// They will always be non-blocking.
//
// Depends on AnalyseBlockingRecursive.
func (p *Program) IsBlocking(f *Function) bool {
if !p.needsScheduler {
return false
}
return f.blocking
}
+65
View File
@@ -0,0 +1,65 @@
// +build byollvm
package main
// This file provides a Link() function that uses the bundled lld if possible.
import (
"errors"
"os"
"os/exec"
"unsafe"
)
/*
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_link_elf(int argc, char **argv);
bool tinygo_link_wasm(int argc, char **argv);
*/
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(linker string, flags ...string) error {
switch linker {
case "ld.lld", commands["ld.lld"]:
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_link_elf(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to link using built-in ld.lld")
}
return nil
case "wasm-ld", commands["wasm-ld"]:
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
defer C.free(buf)
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_link_wasm(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to link using built-in wasm-ld")
}
return nil
default:
// Fall back to external command.
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
}
+21
View File
@@ -0,0 +1,21 @@
// +build !byollvm
package main
// This file provides a Link() function that always runs an external command. It
// is provided for when tinygo is built without linking to liblld.
import (
"os"
"os/exec"
)
// Link invokes a linker with the given name and arguments.
//
// This version always runs the linker as an external command.
func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
+19
View File
@@ -0,0 +1,19 @@
// +build byollvm
// This file provides C wrappers for liblld.
#include <lld/Common/Driver.h>
extern "C" {
bool tinygo_link_elf(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::elf::link(args, false);
}
bool tinygo_link_wasm(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::wasm::link(args, false);
}
} // external "C"
+209 -113
View File
@@ -9,36 +9,43 @@ import (
"sort"
"strconv"
"strings"
"golang.org/x/tools/go/ast/astutil"
)
// fileInfo holds all Cgo-related information of a given *ast.File.
type fileInfo struct {
*ast.File
filename string
functions []*functionInfo
typedefs []*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
// and declared in the AST.
type functionInfo struct {
name string
args []paramInfo
result string
args []paramInfo
results *ast.FieldList
}
// paramInfo is a parameter of a Cgo function (see functionInfo).
type paramInfo struct {
name string
typeName string
typeExpr ast.Expr
}
// typedefInfo contains information about a single typedef in C.
type typedefInfo struct {
newName string // newly defined type name
oldName string // old type name, may be something like "unsigned long long"
size int // size in bytes
typeExpr ast.Expr
}
// globalInfo contains information about a declared global variable in C.
type globalInfo struct {
typeExpr ast.Expr
}
// cgoAliases list type aliases between Go and C, for types that are equivalent
@@ -73,10 +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,
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.
@@ -106,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.
@@ -122,14 +135,23 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
// Declare functions found by libclang.
info.addFuncDecls()
// Declare stub function pointer values found by libclang.
info.addFuncPtrDecls()
// Declare globals found by libclang.
info.addVarDecls()
// 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.
ast.Inspect(f, info.walker)
f = astutil.Apply(f, info.walker, nil).(*ast.File)
return nil
}
@@ -139,16 +161,22 @@ func (p *Package) processCgo(filename string, f *ast.File, cflags []string) erro
func (info *fileInfo) addFuncDecls() {
// TODO: replace all uses of importCPos with the real locations from
// libclang.
for _, fn := range info.functions {
names := make([]string, 0, len(info.functions))
for name := range info.functions {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fn := info.functions[name]
obj := &ast.Object{
Kind: ast.Fun,
Name: mapCgoType(fn.name),
Name: "C." + name,
}
args := make([]*ast.Field, len(fn.args))
decl := &ast.FuncDecl{
Name: &ast.Ident{
NamePos: info.importCPos,
Name: mapCgoType(fn.name),
Name: "C." + name,
Obj: obj,
},
Type: &ast.FuncType{
@@ -158,16 +186,7 @@ func (info *fileInfo) addFuncDecls() {
List: args,
Closing: info.importCPos,
},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: &ast.Ident{
NamePos: info.importCPos,
Name: mapCgoType(fn.result),
},
},
},
},
Results: fn.results,
},
}
obj.Decl = decl
@@ -179,21 +198,107 @@ func (info *fileInfo) addFuncDecls() {
Name: arg.name,
Obj: &ast.Object{
Kind: ast.Var,
Name: mapCgoType(arg.name),
Name: arg.name,
Decl: decl,
},
},
},
Type: &ast.Ident{
NamePos: info.importCPos,
Name: mapCgoType(arg.typeName),
},
Type: arg.typeExpr,
}
}
info.Decls = append(info.Decls, decl)
}
}
// addFuncPtrDecls creates stub declarations of function pointer values. These
// values will later be replaced with the real values in the compiler.
// It adds code like the following to the AST:
//
// var (
// C.add unsafe.Pointer
// C.mul unsafe.Pointer
// // ...
// )
func (info *fileInfo) addFuncPtrDecls() {
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.VAR,
Lparen: info.importCPos,
Rparen: info.importCPos,
}
names := make([]string, 0, len(info.functions))
for name := range info.functions {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
obj := &ast.Object{
Kind: ast.Typ,
Name: "C." + name + "$funcaddr",
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
NamePos: info.importCPos,
Name: "C." + name + "$funcaddr",
Obj: obj,
}},
Type: &ast.SelectorExpr{
X: &ast.Ident{
NamePos: info.importCPos,
Name: "unsafe",
},
Sel: &ast.Ident{
NamePos: info.importCPos,
Name: "Pointer",
},
},
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
}
info.Decls = append(info.Decls, gen)
}
// addVarDecls declares external C globals in the Go source.
// It adds code like the following to the AST:
//
// var (
// C.globalInt int
// C.globalBool bool
// // ...
// )
func (info *fileInfo) addVarDecls() {
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.VAR,
Lparen: info.importCPos,
Rparen: info.importCPos,
}
names := make([]string, 0, len(info.globals))
for name := range info.globals {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
global := info.globals[name]
obj := &ast.Object{
Kind: ast.Typ,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
NamePos: info.importCPos,
Name: "C." + name,
Obj: obj,
}},
Type: global.typeExpr,
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
}
info.Decls = append(info.Decls, gen)
}
// addTypeAliases aliases some built-in Go types with their equivalent C types.
// It adds code like the following to the AST:
//
@@ -243,55 +348,32 @@ func (info *fileInfo) addTypedefs() {
TokPos: info.importCPos,
Tok: token.TYPE,
}
for _, typedef := range info.typedefs {
newType := mapCgoType(typedef.newName)
oldType := mapCgoType(typedef.oldName)
switch oldType {
// TODO: plain char (may be signed or unsigned)
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
switch typedef.size {
case 1:
oldType = "int8"
case 2:
oldType = "int16"
case 4:
oldType = "int32"
case 8:
oldType = "int64"
}
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
switch typedef.size {
case 1:
oldType = "uint8"
case 2:
oldType = "uint16"
case 4:
oldType = "uint32"
case 8:
oldType = "uint64"
}
names := make([]string, 0, len(info.typedefs))
for name := range info.typedefs {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
typedef := info.typedefs[name]
typeName := "C." + name
if strings.HasPrefix(name, "_Cgo_") {
typeName = "C." + name[len("_Cgo_"):]
}
if strings.HasPrefix(newType, "C._Cgo_") {
newType = "C." + newType[len("C._Cgo_"):]
}
if _, ok := cgoAliases[newType]; ok {
if _, ok := cgoAliases[typeName]; ok {
// This is a type that also exists in Go (defined in stdint.h).
continue
}
obj := &ast.Object{
Kind: ast.Typ,
Name: newType,
Name: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: info.importCPos,
Name: newType,
Name: typeName,
Obj: obj,
},
Type: &ast.Ident{
NamePos: info.importCPos,
Name: oldType,
},
Type: typedef.typeExpr,
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
@@ -299,12 +381,48 @@ func (info *fileInfo) addTypedefs() {
info.Decls = append(info.Decls, gen)
}
// walker replaces all "C".<something> call expressions to literal
// "C.<something>" expressions. This is impossible to write in Go (a dot cannot
// be used in the middle of a name) so is used as a new namespace for C call
// expressions.
func (info *fileInfo) walker(node ast.Node) bool {
switch node := node.(type) {
// 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
// separate namespace (no _Cgo_ hacks like in gc).
func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
switch node := cursor.Node().(type) {
case *ast.CallExpr:
fun, ok := node.Fun.(*ast.SelectorExpr)
if !ok {
@@ -314,49 +432,27 @@ func (info *fileInfo) walker(node ast.Node) bool {
if !ok {
return true
}
if x.Name == "C" {
if _, ok := info.functions[fun.Sel.Name]; ok && x.Name == "C" {
node.Fun = &ast.Ident{
NamePos: x.NamePos,
Name: mapCgoType(fun.Sel.Name),
Name: "C." + fun.Sel.Name,
}
}
case *ast.ValueSpec:
typ, ok := node.Type.(*ast.SelectorExpr)
if !ok {
return true
}
x, ok := typ.X.(*ast.Ident)
case *ast.SelectorExpr:
x, ok := node.X.(*ast.Ident)
if !ok {
return true
}
if x.Name == "C" {
node.Type = &ast.Ident{
NamePos: x.NamePos,
Name: mapCgoType(typ.Sel.Name),
name := "C." + node.Sel.Name
if _, ok := info.functions[node.Sel.Name]; ok {
name += "$funcaddr"
}
cursor.Replace(&ast.Ident{
NamePos: x.NamePos,
Name: name,
})
}
}
return true
}
// mapCgoType converts a C type name into a Go type name with a "C." prefix.
func mapCgoType(t string) string {
switch t {
case "signed char":
return "C.schar"
case "long long":
return "C.longlong"
case "unsigned char":
return "C.schar"
case "unsigned short":
return "C.ushort"
case "unsigned int":
return "C.uint"
case "unsigned long":
return "C.ulong"
case "unsigned long long":
return "C.ulonglong"
default:
return "C." + t
}
}
+17 -5
View File
@@ -1,5 +1,10 @@
package loader
import (
"go/token"
"strings"
)
// Errors contains a list of parser errors or a list of typechecker errors for
// the given package.
type Errors struct {
@@ -15,13 +20,20 @@ func (e Errors) Error() string {
// packages is a list from the root package to the leaf package that imports one
// of the packages in the list.
type ImportCycleError struct {
Packages []string
Packages []string
ImportPositions []token.Position
}
func (e *ImportCycleError) Error() string {
msg := "import cycle: " + e.Packages[0]
for _, path := range e.Packages[1:] {
msg += " → " + path
var msg strings.Builder
msg.WriteString("import cycle:\n\t")
msg.WriteString(strings.Join(e.Packages, "\n\t"))
msg.WriteString("\n at ")
for i, pos := range e.ImportPositions {
if i > 0 {
msg.WriteString(", ")
}
msg.WriteString(pos.String())
}
return msg
return msg.String()
}
+370 -50
View File
@@ -4,27 +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"
)
/*
#cgo CFLAGS: -I/usr/lib/llvm-7/include
#cgo LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
#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)
@@ -39,7 +81,7 @@ func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
// convert Go slice of strings to C array of strings.
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
defer C.free(cmdargsC)
cmdargs := (*[1<<30 - 1]*C.char)(cmdargsC)
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
for i, cflag := range cflags {
s := C.CString(cflag)
cmdargs[i] = s
@@ -59,61 +101,142 @@ 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 := C.clang_Cursor_getNumArguments(c)
fn := &functionInfo{name: name}
info.functions = append(info.functions, fn)
for i := C.int(0); i < numArgs; i++ {
arg := C.clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.clang_getCursorSpelling(arg))
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
fn := &functionInfo{}
info.functions[name] = fn
for i := 0; i < numArgs; i++ {
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))
argTypeName := getString(C.clang_getTypeSpelling(argType))
fn.args = append(fn.args, paramInfo{argName, argTypeName})
if argName == "" {
argName = "$" + strconv.Itoa(i)
}
fn.args = append(fn.args, paramInfo{
name: argName,
typeExpr: info.makeASTType(argType),
})
}
resultType := C.tinygo_clang_getCursorResultType(c)
if resultType.kind != C.CXType_Void {
fn.results = &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: info.makeASTType(resultType),
},
},
}
}
resultType := C.clang_getCursorResultType(c)
resultTypeName := getString(C.clang_getTypeSpelling(resultType))
fn.result = resultTypeName
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)
underlyingTypeName := getString(C.clang_getTypeSpelling(underlyingType))
typeSize := C.clang_Type_getSizeOf(underlyingType)
info.typedefs = append(info.typedefs, &typedefInfo{
newName: name,
oldName: underlyingTypeName,
size: int(typeSize),
})
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
expr := info.makeASTType(underlyingType)
if strings.HasPrefix(name, "_Cgo_") {
expr := expr.(*ast.Ident)
typeSize := C.clang_Type_getSizeOf(underlyingType)
switch expr.Name {
// TODO: plain char (may be signed or unsigned)
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
switch typeSize {
case 1:
expr.Name = "int8"
case 2:
expr.Name = "int16"
case 4:
expr.Name = "int32"
case 8:
expr.Name = "int64"
}
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
switch typeSize {
case 1:
expr.Name = "uint8"
case 2:
expr.Name = "uint16"
case 4:
expr.Name = "uint32"
case 8:
expr.Name = "uint64"
}
}
}
info.typedefs[name] = &typedefInfo{
typeExpr: expr,
}
case C.CXCursor_VarDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
cursorType := C.tinygo_clang_getCursorType(c)
info.globals[name] = &globalInfo{
typeExpr: info.makeASTType(cursorType),
}
}
return C.CXChildVisit_Continue
}
@@ -124,3 +247,200 @@ func getString(clangString C.CXString) (s string) {
C.clang_disposeString(clangString)
return
}
// makeASTType return the ast.Expr for the given libclang type. In other words,
// it converts a libclang type to a type in the Go AST.
func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
var typeName string
switch typ.kind {
case C.CXType_SChar:
typeName = "C.schar"
case C.CXType_UChar:
typeName = "C.uchar"
case C.CXType_Short:
typeName = "C.short"
case C.CXType_UShort:
typeName = "C.ushort"
case C.CXType_Int:
typeName = "C.int"
case C.CXType_UInt:
typeName = "C.uint"
case C.CXType_Long:
typeName = "C.long"
case C.CXType_ULong:
typeName = "C.ulong"
case C.CXType_LongLong:
typeName = "C.longlong"
case C.CXType_ULongLong:
typeName = "C.ulonglong"
case C.CXType_Bool:
typeName = "bool"
case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
switch C.clang_Type_getSizeOf(typ) {
case 4:
typeName = "float32"
case 8:
typeName = "float64"
default:
// Don't do anything, rely on the fallback code to show a somewhat
// sensible error message like "undeclared name: C.long double".
}
case C.CXType_Complex:
switch C.clang_Type_getSizeOf(typ) {
case 8:
typeName = "complex64"
case 16:
typeName = "complex128"
}
case C.CXType_Pointer:
return &ast.StarExpr{
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.
// Return type [0]byte because this is a function type, not a pointer to
// this function type.
return &ast.ArrayType{
Lbrack: info.importCPos,
Len: &ast.BasicLit{
ValuePos: info.importCPos,
Kind: token.INT,
Value: "0",
},
Elt: &ast.Ident{
NamePos: info.importCPos,
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
// type name.
typeName = "C." + getString(C.clang_getTypeSpelling(typ))
}
return &ast.Ident{
NamePos: info.importCPos,
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
}
+11
View File
@@ -0,0 +1,11 @@
// +build !byollvm
package loader
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-8/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm/lib -lclang -lffi
*/
import "C"
+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);
}
+24 -13
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
}
@@ -166,7 +172,9 @@ func (p *Program) Parse() error {
err := pkg.importRecursively()
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
if pkg.ImportPath != err.Packages[0] {
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
}
}
return err
}
@@ -291,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)
@@ -339,10 +347,13 @@ func (p *Package) importRecursively() error {
return err
}
if importedPkg.Importing {
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}}
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
}
err = importedPkg.importRecursively()
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
}
return err
}
p.Imports[to] = importedPkg
+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)
}
+107 -59
View File
@@ -11,18 +11,26 @@ import (
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/aykevl/tinygo/compiler"
"github.com/aykevl/tinygo/interp"
"github.com/aykevl/tinygo/loader"
"github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader"
)
var commands = map[string]string{
"ar": "ar",
"clang": "clang-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 {
Msg string
File string
Err error
}
func (e *commandError) Error() string {
return e.Msg + " " + e.File + ": " + e.Err.Error()
}
type BuildConfig struct {
@@ -32,7 +40,6 @@ type BuildConfig struct {
dumpSSA bool
debug bool
printSizes string
initInterp bool
cFlags []string
ldFlags []string
wasmAbi string
@@ -49,19 +56,18 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
spec.LDFlags = append(spec.LDFlags, config.ldFlags...)
compilerConfig := compiler.Config{
Triple: spec.Triple,
CPU: spec.CPU,
GOOS: spec.GOOS,
GOARCH: spec.GOARCH,
GC: config.gc,
CFlags: spec.CFlags,
LDFlags: spec.LDFlags,
Debug: config.debug,
DumpSSA: config.dumpSSA,
RootDir: sourceDir(),
GOPATH: getGopath(),
BuildTags: spec.BuildTags,
InitInterp: config.initInterp,
Triple: spec.Triple,
CPU: spec.CPU,
GOOS: spec.GOOS,
GOARCH: spec.GOARCH,
GC: config.gc,
CFlags: spec.CFlags,
LDFlags: spec.LDFlags,
Debug: config.debug,
DumpSSA: config.dumpSSA,
RootDir: sourceDir(),
GOPATH: getGopath(),
BuildTags: spec.BuildTags,
}
c, err := compiler.NewCompiler(pkgName, compilerConfig)
if err != nil {
@@ -81,17 +87,17 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
return errors.New("verification error after IR construction")
}
if config.initInterp {
err = interp.Run(c.Module(), c.TargetData(), config.dumpSSA)
if err != nil {
return err
}
if err := c.Verify(); err != nil {
return errors.New("verification error after interpreting runtime.initAll")
}
err = interp.Run(c.Module(), c.TargetData(), config.dumpSSA)
if err != nil {
return err
}
if err := c.Verify(); err != nil {
return errors.New("verification error after interpreting runtime.initAll")
}
c.ApplyFunctionSections() // -ffunction-sections
if spec.GOOS != "darwin" {
c.ApplyFunctionSections() // -ffunction-sections
}
if err := c.Verify(); err != nil {
return errors.New("verification error after applying function sections")
}
@@ -174,21 +180,20 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
// Load builtins library from the cache, possibly compiling it on the
// fly.
var cachePath string
var librt string
if spec.RTLib == "compiler-rt" {
librt, err := loadBuiltins(spec.Triple)
librt, err = loadBuiltins(spec.Triple)
if err != nil {
return err
}
cachePath, _ = filepath.Split(librt)
}
// 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, "-L", cachePath, "-lrt-"+spec.Triple)
ldflags = append(ldflags, librt)
}
// Compile extra files.
@@ -200,7 +205,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
cmd.Dir = sourceDir()
err := cmd.Run()
if err != nil {
return err
return &commandError{"failed to build", path, err}
}
ldflags = append(ldflags, outpath)
}
@@ -216,20 +221,20 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
cmd.Dir = sourceDir()
err := cmd.Run()
if err != nil {
return err
return &commandError{"failed to build", path, err}
}
ldflags = append(ldflags, outpath)
}
}
// Link the object files together.
cmd := exec.Command(spec.Linker, ldflags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
err = cmd.Run()
if linker, ok := commands[spec.Linker]; ok {
err = Link(linker, ldflags...)
} else {
err = Link(spec.Linker, ldflags...)
}
if err != nil {
return err
return &commandError{"failed to link", executable, err}
}
if config.printSizes == "short" || config.printSizes == "full" {
@@ -251,17 +256,17 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
}
}
// Get an Intel .hex file or .bin file from the .elf file.
if outext == ".hex" || outext == ".bin" {
// Get an Intel .hex file or .bin file from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
format := map[string]string{
".hex": "ihex",
".bin": "binary",
}[outext]
cmd := exec.Command(spec.Objcopy, "-O", format, executable, tmppath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
err := Objcopy(executable, tmppath)
if err != nil {
return err
}
} else if outext == ".uf2" {
// Get UF2 from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
err := ConvertELFFileToUF2File(executable, tmppath)
if err != nil {
return err
}
@@ -310,14 +315,31 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
return err
}
return Compile(pkgName, ".hex", spec, config, func(tmppath string) error {
// determine the type of file to compile
var fileExt string
switch {
case strings.Contains(spec.Flasher, "{hex}"):
fileExt = ".hex"
case strings.Contains(spec.Flasher, "{elf}"):
fileExt = ".elf"
case strings.Contains(spec.Flasher, "{bin}"):
fileExt = ".bin"
case strings.Contains(spec.Flasher, "{uf2}"):
fileExt = ".uf2"
default:
return errors.New("invalid target file - did you forget the {hex} token in the 'flash' section?")
}
return Compile(pkgName, fileExt, spec, config, func(tmppath string) error {
if spec.Flasher == "" {
return errors.New("no flash command specified - did you miss a -target flag?")
}
// Create the command.
flashCmd := spec.Flasher
flashCmd = strings.Replace(flashCmd, "{hex}", tmppath, -1)
fileToken := "{" + fileExt[1:] + "}"
flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1)
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
// Execute the command.
@@ -325,7 +347,11 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
return cmd.Run()
err := cmd.Run()
if err != nil {
return &commandError{"failed to flash", tmppath, err}
}
return nil
})
}
@@ -392,7 +418,11 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
err := cmd.Run()
if err != nil {
return &commandError{"failed to run gdb with", tmppath, err}
}
return nil
})
}
@@ -415,8 +445,9 @@ func Run(pkgName, target string, config *BuildConfig) error {
// Workaround for QEMU which always exits with an error.
return nil
}
return &commandError{"failed to run compiled binary", tmppath, err}
}
return err
return nil
} else {
// Run in an emulator.
args := append(spec.Emulator[1:], tmppath)
@@ -429,13 +460,16 @@ func Run(pkgName, target string, config *BuildConfig) error {
// Workaround for QEMU which always exits with an error.
return nil
}
return &commandError{"failed to run emulator with", tmppath, err}
}
return err
return nil
}
})
}
func usage() {
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
fmt.Fprintln(os.Stderr, "version:", version)
fmt.Fprintf(os.Stderr, "usage: %s command [-printir] [-target=<target>] -o <output> <input>\n", os.Args[0])
fmt.Fprintln(os.Stderr, "\ncommands:")
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
@@ -479,7 +513,6 @@ func main() {
printSize := flag.String("size", "", "print sizes (none, short, full)")
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
initInterp := flag.Bool("initinterp", true, "enable/disable partial evaluator of generated IR")
port := flag.String("port", "/dev/ttyACM0", "flash port")
cFlags := flag.String("cflags", "", "additional cflags for compiler")
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
@@ -500,7 +533,6 @@ func main() {
dumpSSA: *dumpSSA,
debug: !*nodebug,
printSizes: *printSize,
initInterp: *initInterp,
wasmAbi: *wasmAbi,
}
@@ -532,6 +564,20 @@ func main() {
}
err := Build(flag.Arg(0), *outpath, target, config)
handleCompilerError(err)
case "build-builtins":
// Note: this command is only meant to be used while making a release!
if *outpath == "" {
fmt.Fprintln(os.Stderr, "No output filename supplied (-o).")
usage()
os.Exit(1)
}
if *target == "" {
fmt.Fprintln(os.Stderr, "No target (-target).")
}
err := compileBuiltins(*target, func(path string) error {
return moveFile(path, *outpath)
})
handleCompilerError(err)
case "flash", "gdb":
if *outpath != "" {
fmt.Fprintln(os.Stderr, "Output cannot be specified with the flash command.")
@@ -568,6 +614,8 @@ func main() {
}
case "help":
usage()
case "version":
fmt.Printf("tinygo version %s %s/%s\n", version, runtime.GOOS, runtime.GOARCH)
default:
fmt.Fprintln(os.Stderr, "Unknown command:", command)
usage()
+43 -3
View File
@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"testing"
)
@@ -23,6 +24,9 @@ func TestCompiler(t *testing.T) {
}
dirMatches, err := filepath.Glob(TESTDATA + "/*/main.go")
if err != nil {
t.Fatal("could not read test packages:", err)
}
if len(matches) == 0 || len(dirMatches) == 0 {
t.Fatal("no test files found")
}
@@ -39,19 +43,55 @@ func TestCompiler(t *testing.T) {
}
defer os.RemoveAll(tmpdir)
t.Log("running tests on the host...")
t.Log("running tests on host...")
for _, path := range matches {
t.Run(path, func(t *testing.T) {
runTest(path, tmpdir, "", t)
})
}
t.Log("running tests on the qemu target...")
if testing.Short() {
return
}
t.Log("running tests for emulated cortex-m3...")
for _, path := range matches {
t.Run(path, func(t *testing.T) {
runTest(path, tmpdir, "qemu", t)
})
}
if runtime.GOOS == "linux" {
t.Log("running tests for linux/arm...")
for _, path := range matches {
if path == "testdata/cgo/" {
continue // TODO: improve CGo
}
t.Run(path, func(t *testing.T) {
runTest(path, tmpdir, "arm--linux-gnueabihf", t)
})
}
t.Log("running tests for linux/arm64...")
for _, path := range matches {
if path == "testdata/cgo/" {
continue // TODO: improve CGo
}
t.Run(path, func(t *testing.T) {
runTest(path, tmpdir, "aarch64--linux-gnu", t)
})
}
t.Log("running tests for WebAssembly...")
for _, path := range matches {
if path == "testdata/gc.go" {
continue // known to fail
}
t.Run(path, func(t *testing.T) {
runTest(path, tmpdir, "wasm", t)
})
}
}
}
func runTest(path, tmpdir string, target string, t *testing.T) {
@@ -76,7 +116,7 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
dumpSSA: false,
debug: false,
printSizes: "",
initInterp: true,
wasmAbi: "js",
}
binary := filepath.Join(tmpdir, "test")
err = Build("./"+path, binary, target, config)
+127
View File
@@ -0,0 +1,127 @@
package main
import (
"debug/elf"
"io/ioutil"
"os"
"path/filepath"
"sort"
"github.com/marcinbor85/gohex"
)
// ObjcopyError is an error returned by functions that act like objcopy.
type ObjcopyError struct {
Op string
Err error
}
func (e ObjcopyError) Error() string {
if e.Err == nil {
return e.Op
}
return e.Op + ": " + e.Err.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()
// 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
}
}
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
}
}
// Objcopy converts an ELF file to a different (simpler) output file format:
// .bin or .hex. It extracts only the .text section.
func Objcopy(infile, outfile string) error {
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer f.Close()
// Read the .text segment.
addr, data, err := ExtractROM(infile)
if err != nil {
return err
}
// Write to the file, in the correct format.
switch filepath.Ext(outfile) {
case ".bin":
// The address is not stored in a .bin file (therefore you
// should use .hex files in most cases).
_, err := f.Write(data)
return err
case ".hex":
mem := gohex.NewMemory()
err := mem.AddBinary(uint32(addr), data)
if err != nil {
return ObjcopyError{"failed to create .hex file", err}
}
mem.DumpIntelHex(f, 16) // TODO: handle error
return nil
default:
panic("unreachable")
}
}
+18
View File
@@ -118,3 +118,21 @@ func SetPriority(irq uint32, priority uint32) {
priority = priority << (regpos * 8) // bits to set
NVIC.IPR[regnum] = RegValue((uint32(NVIC.IPR[regnum]) &^ mask) | priority)
}
// DisableInterrupts disables all interrupts, and returns the old state.
//
// TODO: it doesn't actually return the old state, meaning that it cannot be
// nested.
func DisableInterrupts() uintptr {
Asm("cpsid if")
return 0
}
// EnableInterrupts enables all interrupts again. The value passed in must be
// the mask returned by DisableInterrupts.
//
// TODO: it doesn't actually use the old state, meaning that it cannot be
// nested.
func EnableInterrupts(mask uintptr) {
Asm("cpsie if")
}
-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)
}
}
+16 -9
View File
@@ -7,27 +7,34 @@ import (
"time"
)
// change these to test a different UART or pins if available
var (
uart = machine.UART0
tx uint8 = machine.UART_TX_PIN
rx uint8 = machine.UART_RX_PIN
)
func main() {
machine.UART0.Configure(machine.UARTConfig{})
machine.UART0.Write([]byte("Echo console enabled. Type something then press enter:\r\n"))
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n"))
input := make([]byte, 64)
i := 0
for {
if machine.UART0.Buffered() > 0 {
data, _ := machine.UART0.ReadByte()
if uart.Buffered() > 0 {
data, _ := uart.ReadByte()
switch data {
case 13:
// return key
machine.UART0.Write([]byte("\r\n"))
machine.UART0.Write([]byte("You typed: "))
machine.UART0.Write(input[:i])
machine.UART0.Write([]byte("\r\n"))
uart.Write([]byte("\r\n"))
uart.Write([]byte("You typed: "))
uart.Write(input[:i])
uart.Write([]byte("\r\n"))
i = 0
default:
// just echo the character
machine.UART0.WriteByte(data)
uart.WriteByte(data)
input[i] = data
i++
}
@@ -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)
}
+35
View File
@@ -0,0 +1,35 @@
'use strict';
const WASM_URL = 'wasm.wasm';
var wasm;
function updateResult() {
wasm.exports.update();
}
function init() {
document.querySelector('#a').oninput = updateResult;
document.querySelector('#b').oninput = updateResult;
const go = new Go();
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
updateResult();
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
updateResult();
})
)
}
}
init();
+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>
-23
View File
@@ -1,23 +0,0 @@
'use strict';
const WASM_URL = '../../../wasm.wasm';
var wasm;
function updateResult() {
wasm.exports.update();
}
function init() {
document.querySelector('#a').oninput = updateResult;
document.querySelector('#b').oninput = updateResult;
const go = new Go();
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function(obj) {
wasm = obj.instance;
go.run(wasm);
updateResult();
})
}
init();
+6
View File
@@ -16,3 +16,9 @@ const (
ADC4 = 4 // Used by TWI for SDA
ADC5 = 5 // Used by TWI for SCL
)
// UART pins
const (
UART_TX_PIN = 1
UART_RX_PIN = 0
)
+101
View File
@@ -0,0 +1,101 @@
// +build sam,atsamd21,circuitplay_express
package machine
import "device/sam"
// GPIO Pins
const (
D0 = PB09
D1 = PB08
D2 = PB02
D3 = PB03
D4 = PA28
D5 = PA14
D6 = PA05
D7 = PA15
D8 = PB23
D9 = PA06
D10 = PA07
D11 = 0xff // does not seem to exist
D12 = PA02
D13 = PA17 // PWM available
)
// Analog Pins
const (
A0 = PA02 // PWM available, also ADC/AIN[0]
A1 = PA05 // ADC/AIN[5]
A2 = PA06 // PWM available, also ADC/AIN[6]
A3 = PA07 // PWM available, also ADC/AIN[7]
A4 = PB03 // PORTB
A5 = PB02 // PORTB
A6 = PB09 // PORTB
A7 = PB08 // PORTB
A8 = PA11 // ADC/AIN[19]
A9 = PA09 // ADC/AIN[17]
A10 = PA04
)
const (
LED = D13
NEOPIXELS = D8
BUTTONA = D4
BUTTONB = D5
SLIDER = D7 // built-in slide switch
BUTTON = BUTTONA
BUTTON1 = BUTTONB
LIGHTSENSOR = A8
TEMPSENSOR = A9
PROXIMITY = A10
)
// USBCDC pins (logical UART0)
const (
USBCDC_DM_PIN = PA24
USBCDC_DP_PIN = PA25
)
// UART0 pins (logical UART1)
const (
UART_TX_PIN = PB08 // PORTB
UART_RX_PIN = PB09 // PORTB
)
// I2C pins
const (
SDA_PIN = PB02 // I2C0 external
SCL_PIN = PB03 // I2C0 external
SDA1_PIN = PA00 // I2C1 internal
SCL1_PIN = PA01 // I2C1 internal
)
// I2C on the Circuit Playground Express.
var (
// 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)
const (
SPI0_SCK_PIN = PA21 // SCK: SERCOM3/PAD[3]
SPI0_MOSI_PIN = PA20 // MOSI: SERCOM3/PAD[2]
SPI0_MISO_PIN = PA16 // MISO: SERCOM3/PAD[0]
)
// SPI on the Circuit Playground Express.
var (
SPI0 = SPI{Bus: sam.SERCOM3_SPI}
)
+75
View File
@@ -0,0 +1,75 @@
// +build sam,atsamd21,itsybitsy_m0
package machine
import "device/sam"
// GPIO Pins
const (
D0 = PA11 // UART0 RX
D1 = PA10 // UART0 TX
D2 = PA14
D3 = PA09 // PWM available
D4 = PA08 // PWM available
D5 = PA15 // PWM available
D6 = PA20 // PWM available
D7 = PA21 // PWM available
D8 = PA06 // PWM available
D9 = PA07 // PWM available
D10 = PA18 // can be used for PWM or UART1 TX
D11 = PA16 // can be used for PWM or UART1 RX
D12 = PA19 // PWM available
D13 = PA17 // PWM available
)
// Analog pins
const (
A0 = PA02 // ADC/AIN[0]
A1 = PB08 // ADC/AIN[2]
A2 = PB09 // ADC/AIN[3]
A3 = PA04 // ADC/AIN[4]
A4 = PA05 // ADC/AIN[5]
A5 = PB02 // ADC/AIN[10]
)
const (
LED = D13
)
// UART0 aka USBCDC pins
const (
USBCDC_DM_PIN = PA24
USBCDC_DP_PIN = PA25
)
// UART1 pins
const (
UART_TX_PIN = D10
UART_RX_PIN = D11
)
// I2C pins
const (
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
SCL_PIN = PA23 // SCL: SERCOM3/PAD[1]
)
// I2C on the ItsyBitsy M0.
var (
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: GPIO_SERCOM}
)
// SPI pins
const (
SPI0_SCK_PIN = PB11 // SCK: SERCOM4/PAD[3]
SPI0_MOSI_PIN = PB10 // MOSI: SERCOM4/PAD[2]
SPI0_MISO_PIN = PA12 // MISO: SERCOM4/PAD[0]
)
// SPI on the ItsyBitsy M0.
var (
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
)
+21
View File
@@ -43,6 +43,27 @@ const (
SPI0_MISO_PIN = 22 // P14 on the board
)
// GPIO/Analog pins
const (
P0 = 3
P1 = 2
P2 = 1
P3 = 4
P4 = 5
P5 = 17
P6 = 12
P7 = 11
P8 = 18
P9 = 10
P10 = 6
P11 = 26
P12 = 20
P13 = 23
P14 = 22
P15 = 21
P16 = 16
)
// LED matrix pins
const (
LED_COL_1 = 4
+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
)
+49
View File
@@ -0,0 +1,49 @@
package machine
const bufferSize = 128
//go:volatile
type volatileByte byte
// RingBuffer is ring buffer implementation inspired by post at
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
//
// It has some limitations currently due to how "volatile" variables that are
// members of a struct are not compiled correctly by TinyGo.
// See https://github.com/tinygo-org/tinygo/issues/151 for details.
type RingBuffer struct {
rxbuffer [bufferSize]volatileByte
head volatileByte
tail volatileByte
}
// NewRingBuffer returns a new ring buffer.
func NewRingBuffer() *RingBuffer {
return &RingBuffer{}
}
// Used returns how many bytes in buffer have been used.
func (rb *RingBuffer) Used() uint8 {
return uint8(rb.head - rb.tail)
}
// Put stores a byte in the buffer. If the buffer is already
// full, the method will return false.
func (rb *RingBuffer) Put(val byte) bool {
if rb.Used() != bufferSize {
rb.head++
rb.rxbuffer[rb.head%bufferSize] = volatileByte(val)
return true
}
return false
}
// Get returns a byte from the buffer. If the buffer is empty,
// the method will return a false as the second value.
func (rb *RingBuffer) Get() (byte, bool) {
if rb.Used() != 0 {
rb.tail++
return byte(rb.rxbuffer[rb.tail%bufferSize]), true
}
return 0, false
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build avr nrf stm32f103xx
// +build avr nrf sam stm32f103xx
package machine
+6 -1
View File
@@ -211,6 +211,11 @@ func (i2c I2C) readByte() byte {
return byte(*avr.TWDR)
}
// UART on the AVR.
type UART struct {
Buffer *RingBuffer
}
// Configure the UART on the AVR. Defaults to 9600 baud on Arduino.
func (uart UART) Configure(config UARTConfig) {
if config.BaudRate == 0 {
@@ -248,6 +253,6 @@ func handleUSART_RX() {
// Ensure no error.
if (*avr.UCSR0A & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
// Put data from UDR register into buffer.
bufferPut(byte(data))
UART0.Receive(byte(data))
}
}
File diff suppressed because it is too large Load Diff
+6
View File
@@ -25,6 +25,12 @@ func (p GPIO) Get() bool {
return (val > 0)
}
// UART on the AVR is a dummy implementation. UART has not been implemented for ATtiny
// devices.
type UART struct {
Buffer *RingBuffer
}
// Configure is a dummy implementation. UART has not been implemented for ATtiny
// devices.
func (uart UART) Configure(config UARTConfig) {
+1 -1
View File
@@ -92,5 +92,5 @@ var I2C0 = I2C{}
// UART
var (
// UART0 is the hardware serial port on the AVR.
UART0 = &UART{}
UART0 = UART{Buffer: NewRingBuffer()}
)
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !avr,!nrf,!stm32
// +build !avr,!nrf,!sam,!stm32
package machine
+7 -2
View File
@@ -54,10 +54,15 @@ func (p GPIO) Get() bool {
return (port.IN>>pin)&1 != 0
}
// UART on the NRF.
type UART struct {
Buffer *RingBuffer
}
// UART
var (
// UART0 is the hardware serial port on the NRF.
UART0 = &UART{}
UART0 = UART{Buffer: NewRingBuffer()}
)
// Configure the UART.
@@ -108,7 +113,7 @@ func (uart UART) WriteByte(c byte) error {
func (uart UART) handleInterrupt() {
if nrf.UART0.EVENTS_RXDRDY != 0 {
bufferPut(byte(nrf.UART0.RXD))
uart.Receive(byte(nrf.UART0.RXD))
nrf.UART0.EVENTS_RXDRDY = 0x0
}
}
+2
View File
@@ -6,6 +6,8 @@ import (
"device/nrf"
)
const CPU_FREQUENCY = 16000000
// Get peripheral and pin number for this GPIO pin.
func (p GPIO) getPortPin() (*nrf.GPIO_Type, uint8) {
return nrf.GPIO, p.Pin
+2
View File
@@ -7,6 +7,8 @@ import (
"unsafe"
)
const CPU_FREQUENCY = 64000000
// Get peripheral and pin number for this GPIO pin.
func (p GPIO) getPortPin() (*nrf.GPIO_Type, uint8) {
return nrf.P0, p.Pin
+2
View File
@@ -7,6 +7,8 @@ import (
"unsafe"
)
const CPU_FREQUENCY = 64000000
// Get peripheral and pin number for this GPIO pin.
func (p GPIO) getPortPin() (*nrf.GPIO_Type, uint8) {
if p.Pin >= 32 {
+1
View File
@@ -14,4 +14,5 @@ const (
portE
portF
portG
portH
)
+8 -4
View File
@@ -100,11 +100,15 @@ func (p GPIO) Set(high bool) {
}
// UART
type UART struct {
Buffer *RingBuffer
}
var (
// USART1 is the first hardware serial port on the STM32.
// Both UART0 and UART1 refers to USART1.
UART0 = &UART{}
UART1 = UART0
// Both UART0 and UART1 refer to USART1.
UART0 = UART{Buffer: NewRingBuffer()}
UART1 = &UART0
)
// Configure the UART.
@@ -160,7 +164,7 @@ func (uart UART) WriteByte(c byte) error {
//go:export USART1_IRQHandler
func handleUART1() {
bufferPut(byte((stm32.USART1.DR & 0xFF)))
UART1.Receive(byte((stm32.USART1.DR & 0xFF)))
}
// SPI on the STM32.
+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 nrf stm32f103xx
// +build nrf stm32f103xx atsamd21g18a
package machine
+22 -32
View File
@@ -1,4 +1,4 @@
// +build avr nrf stm32
// +build avr nrf sam stm32
package machine
@@ -10,8 +10,19 @@ type UARTConfig struct {
RX uint8
}
type UART struct {
}
// To implement the UART interface for a board, you must declare a concrete type as follows:
//
// type UART struct {
// Buffer *RingBuffer
// }
//
// You can also add additional members to this struct depending on your implementation,
// but the *RingBuffer is required.
// When you are declaring your UARTs for your board, make sure that you also declare the
// RingBuffer using the NewRingBuffer() function when you declare your UART:
//
// UART{Buffer: NewRingBuffer()}
//
// Read from the RX buffer.
func (uart UART) Read(data []byte) (n int, err error) {
@@ -47,41 +58,20 @@ func (uart UART) Write(data []byte) (n int, err error) {
// If there is no data in the buffer, returns an error.
func (uart UART) ReadByte() (byte, error) {
// check if RX buffer is empty
if uart.Buffered() == 0 {
buf, ok := uart.Buffer.Get()
if !ok {
return 0, errors.New("Buffer empty")
}
return bufferGet(), nil
return buf, nil
}
// Buffered returns the number of bytes currently stored in the RX buffer.
func (uart UART) Buffered() int {
return int(bufferUsed())
return int(uart.Buffer.Used())
}
const bufferSize = 64
// Minimal ring buffer implementation inspired by post at
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
//go:volatile
type volatileByte byte
var rxbuffer [bufferSize]volatileByte
var head volatileByte
var tail volatileByte
func bufferUsed() uint8 { return uint8(head - tail) }
func bufferPut(val byte) {
if bufferUsed() != bufferSize {
head++
rxbuffer[head%bufferSize] = volatileByte(val)
}
}
func bufferGet() byte {
if bufferUsed() != 0 {
tail++
return byte(rxbuffer[tail%bufferSize])
}
return 0
// Receive handles adding data to the UART's data buffer.
// Usually called by the IRQ handler for a machine.
func (uart UART) Receive(data byte) {
uart.Buffer.Put(data)
}
+599
View File
@@ -0,0 +1,599 @@
// +build sam
package machine
import (
"bytes"
"device/sam"
"encoding/binary"
"errors"
)
const deviceDescriptorSize = 18
// DeviceDescriptor implements the USB standard device descriptor.
//
// Table 9-8. Standard Device Descriptor
// bLength, bDescriptorType, bcdUSB, bDeviceClass, bDeviceSubClass, bDeviceProtocol, bMaxPacketSize0,
// idVendor, idProduct, bcdDevice, iManufacturer, iProduct, iSerialNumber, bNumConfigurations */
//
type DeviceDescriptor struct {
bLength uint8 // 18
bDescriptorType uint8 // 1 USB_DEVICE_DESCRIPTOR_TYPE
bcdUSB uint16 // 0x200
bDeviceClass uint8
bDeviceSubClass uint8
bDeviceProtocol uint8
bMaxPacketSize0 uint8 // Packet 0
idVendor uint16
idProduct uint16
bcdDevice uint16 // 0x100
iManufacturer uint8
iProduct uint8
iSerialNumber uint8
bNumConfigurations uint8
}
// NewDeviceDescriptor returns a USB DeviceDescriptor.
func NewDeviceDescriptor(class, subClass, proto, packetSize0 uint8, vid, pid, version uint16, im, ip, is, configs uint8) DeviceDescriptor {
return DeviceDescriptor{deviceDescriptorSize, 1, 0x200, class, subClass, proto, packetSize0, vid, pid, version, im, ip, is, configs}
}
// Bytes returns DeviceDescriptor data
func (d DeviceDescriptor) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, deviceDescriptorSize))
binary.Write(buf, binary.LittleEndian, d.bLength)
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
binary.Write(buf, binary.LittleEndian, d.bcdUSB)
binary.Write(buf, binary.LittleEndian, d.bDeviceClass)
binary.Write(buf, binary.LittleEndian, d.bDeviceSubClass)
binary.Write(buf, binary.LittleEndian, d.bDeviceProtocol)
binary.Write(buf, binary.LittleEndian, d.bMaxPacketSize0)
binary.Write(buf, binary.LittleEndian, d.idVendor)
binary.Write(buf, binary.LittleEndian, d.idProduct)
binary.Write(buf, binary.LittleEndian, d.bcdDevice)
binary.Write(buf, binary.LittleEndian, d.iManufacturer)
binary.Write(buf, binary.LittleEndian, d.iProduct)
binary.Write(buf, binary.LittleEndian, d.iSerialNumber)
binary.Write(buf, binary.LittleEndian, d.bNumConfigurations)
return buf.Bytes()
}
const configDescriptorSize = 9
// ConfigDescriptor implements the standard USB configuration descriptor.
//
// Table 9-10. Standard Configuration Descriptor
// bLength, bDescriptorType, wTotalLength, bNumInterfaces, bConfigurationValue, iConfiguration
// bmAttributes, bMaxPower
//
type ConfigDescriptor struct {
bLength uint8 // 9
bDescriptorType uint8 // 2
wTotalLength uint16 // total length
bNumInterfaces uint8
bConfigurationValue uint8
iConfiguration uint8
bmAttributes uint8
bMaxPower uint8
}
// NewConfigDescriptor returns a new USB ConfigDescriptor.
func NewConfigDescriptor(totalLength uint16, interfaces uint8) ConfigDescriptor {
return ConfigDescriptor{configDescriptorSize, 2, totalLength, interfaces, 1, 0, usb_CONFIG_BUS_POWERED | usb_CONFIG_REMOTE_WAKEUP, 50}
}
// Bytes returns ConfigDescriptor data.
func (d ConfigDescriptor) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, configDescriptorSize))
binary.Write(buf, binary.LittleEndian, d.bLength)
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
binary.Write(buf, binary.LittleEndian, d.wTotalLength)
binary.Write(buf, binary.LittleEndian, d.bNumInterfaces)
binary.Write(buf, binary.LittleEndian, d.bConfigurationValue)
binary.Write(buf, binary.LittleEndian, d.iConfiguration)
binary.Write(buf, binary.LittleEndian, d.bmAttributes)
binary.Write(buf, binary.LittleEndian, d.bMaxPower)
return buf.Bytes()
}
const interfaceDescriptorSize = 9
// InterfaceDescriptor implements the standard USB interface descriptor.
//
// Table 9-12. Standard Interface Descriptor
// bLength, bDescriptorType, bInterfaceNumber, bAlternateSetting, bNumEndpoints, bInterfaceClass,
// bInterfaceSubClass, bInterfaceProtocol, iInterface
//
type InterfaceDescriptor struct {
bLength uint8 // 9
bDescriptorType uint8 // 4
bInterfaceNumber uint8
bAlternateSetting uint8
bNumEndpoints uint8
bInterfaceClass uint8
bInterfaceSubClass uint8
bInterfaceProtocol uint8
iInterface uint8
}
// NewInterfaceDescriptor returns a new USB InterfaceDescriptor.
func NewInterfaceDescriptor(n, numEndpoints, class, subClass, protocol uint8) InterfaceDescriptor {
return InterfaceDescriptor{interfaceDescriptorSize, 4, n, 0, numEndpoints, class, subClass, protocol, 0}
}
// Bytes returns InterfaceDescriptor data.
func (d InterfaceDescriptor) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, interfaceDescriptorSize))
binary.Write(buf, binary.LittleEndian, d.bLength)
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
binary.Write(buf, binary.LittleEndian, d.bInterfaceNumber)
binary.Write(buf, binary.LittleEndian, d.bAlternateSetting)
binary.Write(buf, binary.LittleEndian, d.bNumEndpoints)
binary.Write(buf, binary.LittleEndian, d.bInterfaceClass)
binary.Write(buf, binary.LittleEndian, d.bInterfaceSubClass)
binary.Write(buf, binary.LittleEndian, d.bInterfaceProtocol)
binary.Write(buf, binary.LittleEndian, d.iInterface)
return buf.Bytes()
}
const endpointDescriptorSize = 7
// EndpointDescriptor implements the standard USB endpoint descriptor.
//
// Table 9-13. Standard Endpoint Descriptor
// bLength, bDescriptorType, bEndpointAddress, bmAttributes, wMaxPacketSize, bInterval
//
type EndpointDescriptor struct {
bLength uint8 // 7
bDescriptorType uint8 // 5
bEndpointAddress uint8
bmAttributes uint8
wMaxPacketSize uint16
bInterval uint8
}
// NewEndpointDescriptor returns a new USB EndpointDescriptor.
func NewEndpointDescriptor(addr, attr uint8, packetSize uint16, interval uint8) EndpointDescriptor {
return EndpointDescriptor{endpointDescriptorSize, 5, addr, attr, packetSize, interval}
}
// Bytes returns EndpointDescriptor data.
func (d EndpointDescriptor) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, endpointDescriptorSize))
binary.Write(buf, binary.LittleEndian, d.bLength)
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
binary.Write(buf, binary.LittleEndian, d.bEndpointAddress)
binary.Write(buf, binary.LittleEndian, d.bmAttributes)
binary.Write(buf, binary.LittleEndian, d.wMaxPacketSize)
binary.Write(buf, binary.LittleEndian, d.bInterval)
return buf.Bytes()
}
const iadDescriptorSize = 8
// IADDescriptor is an Interface Association Descriptor, which is used
// to bind 2 interfaces together in CDC composite device.
//
// Standard Interface Association Descriptor:
// bLength, bDescriptorType, bFirstInterface, bInterfaceCount, bFunctionClass, bFunctionSubClass,
// bFunctionProtocol, iFunction
//
type IADDescriptor struct {
bLength uint8 // 8
bDescriptorType uint8 // 11
bFirstInterface uint8
bInterfaceCount uint8
bFunctionClass uint8
bFunctionSubClass uint8
bFunctionProtocol uint8
iFunction uint8
}
// NewIADDescriptor returns a new USB IADDescriptor.
func NewIADDescriptor(firstInterface, count, class, subClass, protocol uint8) IADDescriptor {
return IADDescriptor{iadDescriptorSize, 11, firstInterface, count, class, subClass, protocol, 0}
}
// Bytes returns IADDescriptor data.
func (d IADDescriptor) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, iadDescriptorSize))
binary.Write(buf, binary.LittleEndian, d.bLength)
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
binary.Write(buf, binary.LittleEndian, d.bFirstInterface)
binary.Write(buf, binary.LittleEndian, d.bInterfaceCount)
binary.Write(buf, binary.LittleEndian, d.bFunctionClass)
binary.Write(buf, binary.LittleEndian, d.bFunctionSubClass)
binary.Write(buf, binary.LittleEndian, d.bFunctionProtocol)
binary.Write(buf, binary.LittleEndian, d.iFunction)
return buf.Bytes()
}
const cdcCSInterfaceDescriptorSize = 5
// CDCCSInterfaceDescriptor is a CDC CS interface descriptor.
type CDCCSInterfaceDescriptor struct {
len uint8 // 5
dtype uint8 // 0x24
subtype uint8
d0 uint8
d1 uint8
}
// NewCDCCSInterfaceDescriptor returns a new USB CDCCSInterfaceDescriptor.
func NewCDCCSInterfaceDescriptor(subtype, d0, d1 uint8) CDCCSInterfaceDescriptor {
return CDCCSInterfaceDescriptor{cdcCSInterfaceDescriptorSize, 0x24, subtype, d0, d1}
}
// Bytes returns CDCCSInterfaceDescriptor data.
func (d CDCCSInterfaceDescriptor) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, cdcCSInterfaceDescriptorSize))
binary.Write(buf, binary.LittleEndian, d.len)
binary.Write(buf, binary.LittleEndian, d.dtype)
binary.Write(buf, binary.LittleEndian, d.subtype)
binary.Write(buf, binary.LittleEndian, d.d0)
binary.Write(buf, binary.LittleEndian, d.d1)
return buf.Bytes()
}
const cmFunctionalDescriptorSize = 5
// CMFunctionalDescriptor is the functional descriptor general format.
type CMFunctionalDescriptor struct {
bFunctionLength uint8
bDescriptorType uint8 // 0x24
bDescriptorSubtype uint8 // 1
bmCapabilities uint8
bDataInterface uint8
}
// NewCMFunctionalDescriptor returns a new USB CMFunctionalDescriptor.
func NewCMFunctionalDescriptor(subtype, d0, d1 uint8) CMFunctionalDescriptor {
return CMFunctionalDescriptor{5, 0x24, subtype, d0, d1}
}
// Bytes returns the CMFunctionalDescriptor data.
func (d CMFunctionalDescriptor) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, cmFunctionalDescriptorSize))
binary.Write(buf, binary.LittleEndian, d.bFunctionLength)
binary.Write(buf, binary.LittleEndian, d.bDescriptorType)
binary.Write(buf, binary.LittleEndian, d.bDescriptorSubtype)
binary.Write(buf, binary.LittleEndian, d.bmCapabilities)
binary.Write(buf, binary.LittleEndian, d.bDataInterface)
return buf.Bytes()
}
const acmFunctionalDescriptorSize = 4
// ACMFunctionalDescriptor is a Abstract Control Model (ACM) USB descriptor.
type ACMFunctionalDescriptor struct {
len uint8
dtype uint8 // 0x24
subtype uint8 // 1
bmCapabilities uint8
}
// NewACMFunctionalDescriptor returns a new USB ACMFunctionalDescriptor.
func NewACMFunctionalDescriptor(subtype, d0 uint8) ACMFunctionalDescriptor {
return ACMFunctionalDescriptor{4, 0x24, subtype, d0}
}
// Bytes returns the ACMFunctionalDescriptor data.
func (d ACMFunctionalDescriptor) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, acmFunctionalDescriptorSize))
binary.Write(buf, binary.LittleEndian, d.len)
binary.Write(buf, binary.LittleEndian, d.dtype)
binary.Write(buf, binary.LittleEndian, d.subtype)
binary.Write(buf, binary.LittleEndian, d.bmCapabilities)
return buf.Bytes()
}
// CDCDescriptor is the Communication Device Class (CDC) descriptor.
type CDCDescriptor struct {
// IAD
iad IADDescriptor // Only needed on compound device
// Control
cif InterfaceDescriptor
header CDCCSInterfaceDescriptor
// CDC control
controlManagement ACMFunctionalDescriptor // ACM
functionalDescriptor CDCCSInterfaceDescriptor // CDC_UNION
callManagement CMFunctionalDescriptor // Call Management
cifin EndpointDescriptor
// CDC Data
dif InterfaceDescriptor
in EndpointDescriptor
out EndpointDescriptor
}
func NewCDCDescriptor(i IADDescriptor, c InterfaceDescriptor,
h CDCCSInterfaceDescriptor,
cm ACMFunctionalDescriptor,
fd CDCCSInterfaceDescriptor,
callm CMFunctionalDescriptor,
ci EndpointDescriptor,
di InterfaceDescriptor,
inp EndpointDescriptor,
outp EndpointDescriptor) CDCDescriptor {
return CDCDescriptor{iad: i,
cif: c,
header: h,
controlManagement: cm,
functionalDescriptor: fd,
callManagement: callm,
cifin: ci,
dif: di,
in: inp,
out: outp}
}
const cdcSize = iadDescriptorSize +
interfaceDescriptorSize +
cdcCSInterfaceDescriptorSize +
acmFunctionalDescriptorSize +
cdcCSInterfaceDescriptorSize +
cmFunctionalDescriptorSize +
endpointDescriptorSize +
interfaceDescriptorSize +
endpointDescriptorSize +
endpointDescriptorSize
// Bytes returns CDCDescriptor data.
func (d CDCDescriptor) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, cdcSize))
buf.Write(d.iad.Bytes())
buf.Write(d.cif.Bytes())
buf.Write(d.header.Bytes())
buf.Write(d.controlManagement.Bytes())
buf.Write(d.functionalDescriptor.Bytes())
buf.Write(d.callManagement.Bytes())
buf.Write(d.cifin.Bytes())
buf.Write(d.dif.Bytes())
buf.Write(d.in.Bytes())
buf.Write(d.out.Bytes())
return buf.Bytes()
}
// MSCDescriptor is not used yet.
type MSCDescriptor struct {
msc InterfaceDescriptor
in EndpointDescriptor
out EndpointDescriptor
}
type cdcLineInfo struct {
dwDTERate uint32
bCharFormat uint8
bParityType uint8
bDataBits uint8
lineState uint8
}
var (
// TODO: allow setting these
usb_STRING_LANGUAGE = [2]uint16{(3 << 8) | (2 + 2), 0x0409} // English
usb_STRING_PRODUCT = "Arduino Zero"
usb_STRING_MANUFACTURER = "Arduino"
usb_VID uint16 = 0x2341
usb_PID uint16 = 0x004d
)
const (
usb_IMANUFACTURER = 1
usb_IPRODUCT = 2
usb_ISERIAL = 3
usb_ENDPOINT_TYPE_CONTROL = 0x00
usb_ENDPOINT_TYPE_ISOCHRONOUS = 0x01
usb_ENDPOINT_TYPE_BULK = 0x02
usb_ENDPOINT_TYPE_INTERRUPT = 0x03
usb_DEVICE_DESCRIPTOR_TYPE = 1
usb_CONFIGURATION_DESCRIPTOR_TYPE = 2
usb_STRING_DESCRIPTOR_TYPE = 3
usb_INTERFACE_DESCRIPTOR_TYPE = 4
usb_ENDPOINT_DESCRIPTOR_TYPE = 5
usb_DEVICE_QUALIFIER = 6
usb_OTHER_SPEED_CONFIGURATION = 7
usbEndpointOut = 0x00
usbEndpointIn = 0x80
usbEndpointPacketSize = 64 // 64 for Full Speed, EPT size max is 1024
usb_EPT_NUM = 7
// standard requests
usb_GET_STATUS = 0
usb_CLEAR_FEATURE = 1
usb_SET_FEATURE = 3
usb_SET_ADDRESS = 5
usb_GET_DESCRIPTOR = 6
usb_SET_DESCRIPTOR = 7
usb_GET_CONFIGURATION = 8
usb_SET_CONFIGURATION = 9
usb_GET_INTERFACE = 10
usb_SET_INTERFACE = 11
usb_DEVICE_CLASS_COMMUNICATIONS = 0x02
usb_DEVICE_CLASS_HUMAN_INTERFACE = 0x03
usb_DEVICE_CLASS_STORAGE = 0x08
usb_DEVICE_CLASS_VENDOR_SPECIFIC = 0xFF
usb_CONFIG_POWERED_MASK = 0x40
usb_CONFIG_BUS_POWERED = 0x80
usb_CONFIG_SELF_POWERED = 0xC0
usb_CONFIG_REMOTE_WAKEUP = 0x20
// CDC
usb_CDC_ACM_INTERFACE = 0 // CDC ACM
usb_CDC_DATA_INTERFACE = 1 // CDC Data
usb_CDC_FIRST_ENDPOINT = 1
usb_CDC_ENDPOINT_ACM = 1
usb_CDC_ENDPOINT_OUT = 2
usb_CDC_ENDPOINT_IN = 3
// bmRequestType
usb_REQUEST_HOSTTODEVICE = 0x00
usb_REQUEST_DEVICETOHOST = 0x80
usb_REQUEST_DIRECTION = 0x80
usb_REQUEST_STANDARD = 0x00
usb_REQUEST_CLASS = 0x20
usb_REQUEST_VENDOR = 0x40
usb_REQUEST_TYPE = 0x60
usb_REQUEST_DEVICE = 0x00
usb_REQUEST_INTERFACE = 0x01
usb_REQUEST_ENDPOINT = 0x02
usb_REQUEST_OTHER = 0x03
usb_REQUEST_RECIPIENT = 0x1F
usb_REQUEST_DEVICETOHOST_CLASS_INTERFACE = (usb_REQUEST_DEVICETOHOST | usb_REQUEST_CLASS | usb_REQUEST_INTERFACE)
usb_REQUEST_HOSTTODEVICE_CLASS_INTERFACE = (usb_REQUEST_HOSTTODEVICE | usb_REQUEST_CLASS | usb_REQUEST_INTERFACE)
usb_REQUEST_DEVICETOHOST_STANDARD_INTERFACE = (usb_REQUEST_DEVICETOHOST | usb_REQUEST_STANDARD | usb_REQUEST_INTERFACE)
// CDC Class requests
usb_CDC_SET_LINE_CODING = 0x20
usb_CDC_GET_LINE_CODING = 0x21
usb_CDC_SET_CONTROL_LINE_STATE = 0x22
usb_CDC_SEND_BREAK = 0x23
usb_CDC_V1_10 = 0x0110
usb_CDC_COMMUNICATION_INTERFACE_CLASS = 0x02
usb_CDC_CALL_MANAGEMENT = 0x01
usb_CDC_ABSTRACT_CONTROL_MODEL = 0x02
usb_CDC_HEADER = 0x00
usb_CDC_ABSTRACT_CONTROL_MANAGEMENT = 0x02
usb_CDC_UNION = 0x06
usb_CDC_CS_INTERFACE = 0x24
usb_CDC_CS_ENDPOINT = 0x25
usb_CDC_DATA_INTERFACE_CLASS = 0x0A
)
// usbDeviceDescBank is the USB device endpoint descriptor.
// typedef struct {
// __IO USB_DEVICE_ADDR_Type ADDR; /**< \brief Offset: 0x000 (R/W 32) DEVICE_DESC_BANK Endpoint Bank, Adress of Data Buffer */
// __IO USB_DEVICE_PCKSIZE_Type PCKSIZE; /**< \brief Offset: 0x004 (R/W 32) DEVICE_DESC_BANK Endpoint Bank, Packet Size */
// __IO USB_DEVICE_EXTREG_Type EXTREG; /**< \brief Offset: 0x008 (R/W 16) DEVICE_DESC_BANK Endpoint Bank, Extended */
// __IO USB_DEVICE_STATUS_BK_Type STATUS_BK; /**< \brief Offset: 0x00A (R/W 8) DEVICE_DESC_BANK Enpoint Bank, Status of Bank */
// RoReg8 Reserved1[0x5];
// } UsbDeviceDescBank;
type usbDeviceDescBank struct {
ADDR sam.RegValue
PCKSIZE sam.RegValue
EXTREG sam.RegValue16
STATUS_BK sam.RegValue8
_reserved [5]sam.RegValue8
}
type usbDeviceDescriptor struct {
DeviceDescBank [2]usbDeviceDescBank
}
// typedef struct {
// union {
// uint8_t bmRequestType;
// struct {
// uint8_t direction : 5;
// uint8_t type : 2;
// uint8_t transferDirection : 1;
// };
// };
// uint8_t bRequest;
// uint8_t wValueL;
// uint8_t wValueH;
// uint16_t wIndex;
// uint16_t wLength;
// } USBSetup;
type usbSetup struct {
bmRequestType uint8
bRequest uint8
wValueL uint8
wValueH uint8
wIndex uint16
wLength uint16
}
func newUSBSetup(data []byte) usbSetup {
buf := bytes.NewBuffer(data)
u := usbSetup{}
binary.Read(buf, binary.LittleEndian, &(u.bmRequestType))
binary.Read(buf, binary.LittleEndian, &(u.bRequest))
binary.Read(buf, binary.LittleEndian, &(u.wValueL))
binary.Read(buf, binary.LittleEndian, &(u.wValueH))
binary.Read(buf, binary.LittleEndian, &(u.wIndex))
binary.Read(buf, binary.LittleEndian, &(u.wLength))
return u
}
// USBCDC is the serial interface that works over the USB port.
// To implement the USBCDC interface for a board, you must declare a concrete type as follows:
//
// type USBCDC struct {
// Buffer *RingBuffer
// }
//
// You can also add additional members to this struct depending on your implementation,
// but the *RingBuffer is required.
// When you are declaring the USBCDC for your board, make sure that you also declare the
// RingBuffer using the NewRingBuffer() function:
//
// USBCDC{Buffer: NewRingBuffer()}
//
// Read from the RX buffer.
func (usbcdc USBCDC) Read(data []byte) (n int, err error) {
// check if RX buffer is empty
size := usbcdc.Buffered()
if size == 0 {
return 0, nil
}
// Make sure we do not read more from buffer than the data slice can hold.
if len(data) < size {
size = len(data)
}
// only read number of bytes used from buffer
for i := 0; i < size; i++ {
v, _ := usbcdc.ReadByte()
data[i] = v
}
return size, nil
}
// Write data to the USBCDC.
func (usbcdc USBCDC) Write(data []byte) (n int, err error) {
for _, v := range data {
usbcdc.WriteByte(v)
}
return len(data), nil
}
// ReadByte reads a single byte from the RX buffer.
// If there is no data in the buffer, returns an error.
func (usbcdc USBCDC) ReadByte() (byte, error) {
// check if RX buffer is empty
buf, ok := usbcdc.Buffer.Get()
if !ok {
return 0, errors.New("Buffer empty")
}
return buf, nil
}
// Buffered returns the number of bytes currently stored in the RX buffer.
func (usbcdc USBCDC) Buffered() int {
return int(usbcdc.Buffer.Used())
}
// Receive handles adding data to the UART's data buffer.
// Usually called by the IRQ handler for a machine.
func (usbcdc USBCDC) Receive(data byte) {
usbcdc.Buffer.Put(data)
}
+40
View File
@@ -0,0 +1,40 @@
// Package os implements a subset of the Go "os" package. See
// https://godoc.org/os for details.
//
// Note that the current implementation is blocking. This limitation should be
// removed in a future version.
package os
import (
"errors"
)
// Portable analogs of some common system call errors.
var (
ErrUnsupported = errors.New("operation not supported")
)
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
// standard output, and standard error file descriptors.
var (
Stdin = &File{0, "/dev/stdin"}
Stdout = &File{1, "/dev/stdout"}
Stderr = &File{2, "/dev/stderr"}
)
// File represents an open file descriptor.
type File struct {
fd uintptr
name string
}
// NewFile returns a new File with the given file descriptor and name.
func NewFile(fd uintptr, name string) *File {
return &File{fd, name}
}
// Fd returns the integer Unix file descriptor referencing the open file. The
// file descriptor is valid only until f.Close is called.
func (f *File) Fd() uintptr {
return f.fd
}
+34
View File
@@ -0,0 +1,34 @@
// +build avr cortexm wasm
package os
import (
_ "unsafe"
)
// Read is unsupported on this system.
func (f *File) Read(b []byte) (n int, err error) {
return 0, ErrUnsupported
}
// Write writes len(b) bytes to the output. It returns the number of bytes
// written or an error if this file is not stdout or stderr.
func (f *File) Write(b []byte) (n int, err error) {
switch f.fd {
case Stdout.fd, Stderr.fd:
for _, c := range b {
putchar(c)
}
return len(b), nil
default:
return 0, ErrUnsupported
}
}
// Close is unsupported on this system.
func (f *File) Close() error {
return ErrUnsupported
}
//go:linkname putchar runtime.putchar
func putchar(c byte)
+24
View File
@@ -0,0 +1,24 @@
// +build darwin linux,!avr,!cortexm
package os
import (
"syscall"
)
// Read reads up to len(b) bytes from the File. It returns the number of bytes
// read and any error encountered. At end of file, Read returns 0, io.EOF.
func (f *File) Read(b []byte) (n int, err error) {
return syscall.Read(int(f.fd), b)
}
// Write writes len(b) bytes to the File. It returns the number of bytes written
// and an error, if any. Write returns a non-nil error when n != len(b).
func (f *File) Write(b []byte) (n int, err error) {
return syscall.Write(int(f.fd), b)
}
// Close closes the File, rendering it unusable for I/O.
func (f *File) Close() error {
return syscall.Close(int(f.fd))
}
+137 -11
View File
@@ -1,9 +1,29 @@
package reflect
// A Kind is the number that the compiler uses for this type.
import (
"unsafe"
)
// The compiler uses a compact encoding to store type information. Unlike the
// main Go compiler, most of the types are stored directly in the type code.
//
// Not used directly. These types are all replaced with the number the compiler
// uses internally for the type.
// Type code bit allocation:
// xxxxx0: basic types, where xxxxx is the basic type number (never 0).
// The higher bits indicate the named type, if any.
// nxxx1: complex types, where n indicates whether this is a named type (named
// if set) and xxx contains the type kind number:
// 0 (0001): Chan
// 1 (0011): Interface
// 2 (0101): Ptr
// 3 (0111): Slice
// 4 (1001): Array
// 5 (1011): Func
// 6 (1101): Map
// 7 (1111): Struct
// The higher bits are either the contents of the type depending on the
// type (if n is clear) or indicate the number of the named type (if n
// is set).
type Kind uintptr
// Copied from reflect/type.go
@@ -26,18 +46,82 @@ const (
Float64
Complex64
Complex128
Array
String
UnsafePointer
Chan
Func
Interface
Map
Ptr
Slice
String
Array
Func
Map
Struct
UnsafePointer
)
func (k Kind) String() string {
switch k {
case Bool:
return "bool"
case Int:
return "int"
case Int8:
return "int8"
case Int16:
return "int16"
case Int32:
return "int32"
case Int64:
return "int64"
case Uint:
return "uint"
case Uint8:
return "uint8"
case Uint16:
return "uint16"
case Uint32:
return "uint32"
case Uint64:
return "uint64"
case Uintptr:
return "uintptr"
case Float32:
return "float32"
case Float64:
return "float64"
case Complex64:
return "complex64"
case Complex128:
return "complex128"
case String:
return "string"
case UnsafePointer:
return "unsafe.Pointer"
case Chan:
return "chan"
case Interface:
return "interface"
case Ptr:
return "ptr"
case Slice:
return "slice"
case Array:
return "array"
case Func:
return "func"
case Map:
return "map"
case Struct:
return "struct"
default:
return "invalid"
}
}
// basicType returns a new Type for this kind if Kind is a basic type.
func (k Kind) basicType() Type {
return Type(k << 1)
}
// The typecode as used in an interface{}.
type Type uintptr
@@ -50,11 +134,24 @@ func (t Type) String() string {
}
func (t Type) Kind() Kind {
return Invalid // TODO
if t%2 == 0 {
// basic type
return Kind((t >> 1) % 32)
} else {
return Kind(t>>1)%8 + 19
}
}
func (t Type) Elem() Type {
panic("unimplemented: (reflect.Type).Elem()")
switch t.Kind() {
case Chan, Ptr, Slice:
if (t>>4)%2 != 0 {
panic("unimplemented: (reflect.Type).Elem() for named types")
}
return t >> 5
default: // not implemented: Array, Map
panic("unimplemented: (reflect.Type).Elem()")
}
}
func (t Type) Field(i int) StructField {
@@ -74,7 +171,36 @@ func (t Type) NumField() int {
}
func (t Type) Size() uintptr {
panic("unimplemented: (reflect.Type).Size()")
switch t.Kind() {
case Bool, Int8, Uint8:
return 1
case Int16, Uint16:
return 2
case Int32, Uint32:
return 4
case Int64, Uint64:
return 8
case Int, Uint:
return unsafe.Sizeof(int(0))
case Uintptr:
return unsafe.Sizeof(uintptr(0))
case Float32:
return 4
case Float64:
return 8
case Complex64:
return 8
case Complex128:
return 16
case String:
return unsafe.Sizeof(StringHeader{})
case UnsafePointer, Chan, Map, Ptr:
return unsafe.Sizeof(uintptr(0))
case Slice:
return unsafe.Sizeof(SliceHeader{})
default:
panic("unimplemented: size of type")
}
}
type StructField struct {
+415 -31
View File
@@ -1,42 +1,94 @@
package reflect
import (
_ "unsafe" // for go:linkname
"unsafe"
)
// This is the same thing as an interface{}.
type Value struct {
typecode Type
value *uint8
value unsafe.Pointer
indirect bool
}
func Indirect(v Value) Value {
return v
if v.Kind() != Ptr {
return v
}
return v.Elem()
}
func ValueOf(i interface{}) Value
//go:linkname _ValueOf reflect.ValueOf
func _ValueOf(i Value) Value {
return i
func ValueOf(i interface{}) Value {
v := (*interfaceHeader)(unsafe.Pointer(&i))
return Value{
typecode: v.typecode,
value: v.value,
}
}
func (v Value) Interface() interface{}
func (v Value) Interface() interface{} {
i := interfaceHeader{
typecode: v.typecode,
value: v.value,
}
if v.indirect && v.Type().Size() <= unsafe.Sizeof(uintptr(0)) {
// Value was indirect but must be put back directly in the interface
// value.
var value uintptr
for j := v.Type().Size(); j != 0; j-- {
value = (value << 8) | uintptr(*(*uint8)(unsafe.Pointer(uintptr(v.value) + j - 1)))
}
i.value = unsafe.Pointer(value)
}
return *(*interface{})(unsafe.Pointer(&i))
}
func (v Value) Type() Type {
return v.typecode
}
func (v Value) Kind() Kind {
return Invalid // TODO
return v.Type().Kind()
}
func (v Value) IsNil() bool {
panic("unimplemented: (reflect.Value).IsNil()")
switch v.Kind() {
case Chan, Map, Ptr:
return v.value == nil
case Func:
if v.value == nil {
return true
}
fn := (*funcHeader)(v.value)
return fn.Code == nil
case Slice:
if v.value == nil {
return true
}
slice := (*SliceHeader)(v.value)
return slice.Data == 0
case Interface:
if v.value == nil {
return true
}
itf := (*interfaceHeader)(v.value)
return itf.value == nil
default:
panic(&ValueError{"IsNil"})
}
}
func (v Value) Pointer() uintptr {
panic("unimplemented: (reflect.Value).Pointer()")
switch v.Kind() {
case Chan, Map, Ptr, UnsafePointer:
return uintptr(v.value)
case Slice:
slice := (*SliceHeader)(v.value)
return slice.Data
case Func:
panic("unimplemented: (reflect.Value).Pointer()")
default: // not implemented: Func
panic(&ValueError{"Pointer"})
}
}
func (v Value) IsValid() bool {
@@ -44,7 +96,8 @@ func (v Value) IsValid() bool {
}
func (v Value) CanInterface() bool {
panic("unimplemented: (reflect.Value).CanInterface()")
// No Value types of private data can be constructed at the moment.
return true
}
func (v Value) CanAddr() bool {
@@ -56,31 +109,160 @@ func (v Value) Addr() Value {
}
func (v Value) CanSet() bool {
panic("unimplemented: (reflect.Value).CanSet()")
return v.indirect
}
func (v Value) Bool() bool {
panic("unimplemented: (reflect.Value).Bool()")
switch v.Kind() {
case Bool:
if v.indirect {
return *((*bool)(v.value))
} else {
return uintptr(v.value) != 0
}
default:
panic(&ValueError{"Bool"})
}
}
func (v Value) Int() int64 {
panic("unimplemented: (reflect.Value).Int()")
switch v.Kind() {
case Int:
if v.indirect || unsafe.Sizeof(int(0)) > unsafe.Sizeof(uintptr(0)) {
return int64(*(*int)(v.value))
} else {
return int64(int(uintptr(v.value)))
}
case Int8:
if v.indirect {
return int64(*(*int8)(v.value))
} else {
return int64(int8(uintptr(v.value)))
}
case Int16:
if v.indirect {
return int64(*(*int16)(v.value))
} else {
return int64(int16(uintptr(v.value)))
}
case Int32:
if v.indirect || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) {
return int64(*(*int32)(v.value))
} else {
return int64(int32(uintptr(v.value)))
}
case Int64:
if v.indirect || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) {
return int64(*(*int64)(v.value))
} else {
return int64(int64(uintptr(v.value)))
}
default:
panic(&ValueError{"Int"})
}
}
func (v Value) Uint() uint64 {
panic("unimplemented: (reflect.Value).Uint()")
switch v.Kind() {
case Uintptr:
if v.indirect {
return uint64(*(*uintptr)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint8:
if v.indirect {
return uint64(*(*uint8)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint16:
if v.indirect {
return uint64(*(*uint16)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint:
if v.indirect || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) {
return uint64(*(*uint)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint32:
if v.indirect || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) {
return uint64(*(*uint32)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint64:
if v.indirect || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) {
return uint64(*(*uint64)(v.value))
} else {
return uint64(uintptr(v.value))
}
default:
panic(&ValueError{"Uint"})
}
}
func (v Value) Float() float64 {
panic("unimplemented: (reflect.Value).Float()")
switch v.Kind() {
case Float32:
if v.indirect || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) {
// The float is stored as an external value on systems with 16-bit
// pointers.
return float64(*(*float32)(v.value))
} else {
// The float is directly stored in the interface value on systems
// with 32-bit and 64-bit pointers.
return float64(*(*float32)(unsafe.Pointer(&v.value)))
}
case Float64:
if v.indirect || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) {
// For systems with 16-bit and 32-bit pointers.
return *(*float64)(v.value)
} else {
// The float is directly stored in the interface value on systems
// with 64-bit pointers.
return *(*float64)(unsafe.Pointer(&v.value))
}
default:
panic(&ValueError{"Float"})
}
}
func (v Value) Complex() complex128 {
panic("unimplemented: (reflect.Value).Complex()")
switch v.Kind() {
case Complex64:
if v.indirect || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) {
// The complex number is stored as an external value on systems with
// 16-bit and 32-bit pointers.
return complex128(*(*complex64)(v.value))
} else {
// The complex number is directly stored in the interface value on
// systems with 64-bit pointers.
return complex128(*(*complex64)(unsafe.Pointer(&v.value)))
}
case Complex128:
// This is a 128-bit value, which is always stored as an external value.
// It may be stored in the pointer directly on very uncommon
// architectures with 128-bit pointers, however.
return *(*complex128)(v.value)
default:
panic(&ValueError{"Complex"})
}
}
func (v Value) String() string {
panic("unimplemented: (reflect.Value).String()")
switch v.Kind() {
case String:
// A string value is always bigger than a pointer as it is made of a
// pointer and a length.
return *(*string)(v.value)
default:
// Special case because of the special treatment of .String() in Go.
return "<T>"
}
}
func (v Value) Bytes() []byte {
@@ -92,7 +274,25 @@ func (v Value) Slice(i, j int) Value {
}
func (v Value) Len() int {
panic("unimplemented: (reflect.Value).Len()")
t := v.Type()
switch t.Kind() {
case Slice:
return int((*SliceHeader)(v.value).Len)
case String:
return int((*StringHeader)(v.value).Len)
default: // Array, Chan, Map
panic("unimplemented: (reflect.Value).Len()")
}
}
func (v Value) Cap() int {
t := v.Type()
switch t.Kind() {
case Slice:
return int((*SliceHeader)(v.value).Cap)
default: // Array, Chan
panic("unimplemented: (reflect.Value).Cap()")
}
}
func (v Value) NumField() int {
@@ -100,7 +300,23 @@ func (v Value) NumField() int {
}
func (v Value) Elem() Value {
panic("unimplemented: (reflect.Value).Elem()")
switch v.Kind() {
case Ptr:
ptr := v.value
if v.indirect {
ptr = *(*unsafe.Pointer)(ptr)
}
if ptr == nil {
return Value{}
}
return Value{
typecode: v.Type().Elem(),
value: ptr,
indirect: true,
}
default: // not implemented: Interface
panic(&ValueError{"Elem"})
}
}
func (v Value) Field(i int) Value {
@@ -108,7 +324,37 @@ func (v Value) Field(i int) Value {
}
func (v Value) Index(i int) Value {
panic("unimplemented: (reflect.Value).Index()")
switch v.Kind() {
case Slice:
// Extract an element from the slice.
slice := *(*SliceHeader)(v.value)
if uint(i) >= uint(slice.Len) {
panic("reflect: slice index out of range")
}
elem := Value{
typecode: v.Type().Elem(),
indirect: true,
}
addr := uintptr(slice.Data) + elem.Type().Size()*uintptr(i) // pointer to new value
elem.value = unsafe.Pointer(addr)
return elem
case String:
// Extract a character from a string.
// A string is never stored directly in the interface, but always as a
// pointer to the string value.
s := *(*StringHeader)(v.value)
if uint(i) >= uint(s.Len) {
panic("reflect: string index out of range")
}
return Value{
typecode: Uint8.basicType(),
value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))),
}
case Array:
panic("unimplemented: (reflect.Value).Index()")
default:
panic(&ValueError{"Index"})
}
}
func (v Value) MapKeys() []Value {
@@ -119,34 +365,172 @@ 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) {
panic("unimplemented: (reflect.Value).Set()")
if !v.indirect {
panic("reflect: value is not addressable")
}
if v.Type() != x.Type() {
if v.Kind() == Interface {
panic("reflect: unimplemented: assigning to interface of different type")
} else {
panic("reflect: cannot assign")
}
}
size := v.Type().Size()
xptr := x.value
if size <= unsafe.Sizeof(uintptr(0)) && !x.indirect {
value := x.value
xptr = unsafe.Pointer(&value)
}
memcpy(v.value, xptr, size)
}
func (v Value) SetBool(x bool) {
panic("unimplemented: (reflect.Value).SetBool()")
if !v.indirect {
panic("reflect: value is not addressable")
}
switch v.Kind() {
case Bool:
*(*bool)(v.value) = x
default:
panic(&ValueError{"SetBool"})
}
}
func (v Value) SetInt(x int64) {
panic("unimplemented: (reflect.Value).SetInt()")
if !v.indirect {
panic("reflect: value is not addressable")
}
switch v.Kind() {
case Int:
*(*int)(v.value) = int(x)
case Int8:
*(*int8)(v.value) = int8(x)
case Int16:
*(*int16)(v.value) = int16(x)
case Int32:
*(*int32)(v.value) = int32(x)
case Int64:
*(*int64)(v.value) = x
default:
panic(&ValueError{"SetInt"})
}
}
func (v Value) SetUint(x uint64) {
panic("unimplemented: (reflect.Value).SetUint()")
if !v.indirect {
panic("reflect: value is not addressable")
}
switch v.Kind() {
case Uint:
*(*uint)(v.value) = uint(x)
case Uint8:
*(*uint8)(v.value) = uint8(x)
case Uint16:
*(*uint16)(v.value) = uint16(x)
case Uint32:
*(*uint32)(v.value) = uint32(x)
case Uint64:
*(*uint64)(v.value) = x
case Uintptr:
*(*uintptr)(v.value) = uintptr(x)
default:
panic(&ValueError{"SetUint"})
}
}
func (v Value) SetFloat(x float64) {
panic("unimplemented: (reflect.Value).SetFloat()")
if !v.indirect {
panic("reflect: value is not addressable")
}
switch v.Kind() {
case Float32:
*(*float32)(v.value) = float32(x)
case Float64:
*(*float64)(v.value) = x
default:
panic(&ValueError{"SetFloat"})
}
}
func (v Value) SetComplex(x complex128) {
panic("unimplemented: (reflect.Value).SetComplex()")
if !v.indirect {
panic("reflect: value is not addressable")
}
switch v.Kind() {
case Complex64:
*(*complex64)(v.value) = complex64(x)
case Complex128:
*(*complex128)(v.value) = x
default:
panic(&ValueError{"SetComplex"})
}
}
func (v Value) SetString(x string) {
panic("unimplemented: (reflect.Value).SetString()")
if !v.indirect {
panic("reflect: value is not addressable")
}
switch v.Kind() {
case String:
*(*string)(v.value) = x
default:
panic(&ValueError{"SetString"})
}
}
func MakeSlice(typ Type, len, cap int) Value {
panic("unimplemented: reflect.MakeSlice()")
}
type funcHeader struct {
Context unsafe.Pointer
Code unsafe.Pointer
}
// This is the same thing as an interface{}.
type interfaceHeader struct {
typecode Type
value unsafe.Pointer
}
type SliceHeader struct {
Data uintptr
Len uintptr
Cap uintptr
}
type StringHeader struct {
Data uintptr
Len uintptr
}
type ValueError struct {
Method string
}
func (e *ValueError) Error() string {
return "reflect: call of reflect.Value." + e.Method + " on invalid type"
}
//go:linkname memcpy runtime.memcpy
func memcpy(dst, src unsafe.Pointer, size uintptr)

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