Compare commits

...

250 Commits

Author SHA1 Message Date
Ayke van Laethem 515daa7d3c main: version 0.7.0 2019-07-17 15:36:38 +02:00
Ron Evans ced964f039 docs: add Arduino Nano33 IoT to README
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-17 06:30:58 -07:00
Ayke van Laethem 8a704e43fc os: gofmt 2019-07-15 17:53:01 +02:00
Justin Clift 847681457f runtime: add several os package stubs 2019-07-15 01:18:37 +02:00
Ayke van Laethem 66d8899aa7 main: set the current working directory when calling an external linker
In particular, while LLVM lld supports -L for linker scripts imported
with the `INCLUDE` command, GNU ld does not seem to support this.

This is a prerequisite for supporting the HiFive1 board in the TinyGo
Playground.
2019-07-08 00:37:53 +02:00
Ayke van Laethem b0cad7ed63 runtime: add support for math intrinsics where supported
In particular, add support for a few math intrinsics for WebAssembly,
but add a few intrinsics to other systems as well at the same time. Some
may be missing still but will be easy to add if needed.

This increases the performance of one example by 50% to 100% depending
on the browser: the bottleneck was the inefficient sqrt implementation.
2019-07-08 00:32:42 +02:00
Ayke van Laethem 00cc486619 wasm: set the stack at the start of linear memory
This makes sure that a stack overflow will cause a "memory access out of
bounds" error instead of a corruption of a global variable.

Here is more background on a very similar stack overflow protection:
https://blog.japaric.io/stack-overflow-protection/
2019-07-08 00:09:59 +02:00
Ayke van Laethem d627208c48 all: make WebAssembly initial linear memory size configurable
When the target supports it, allow the (initial) heap size to be
configured. Currently only supported in WebAssembly.

This also changes the default heap size of WebAssembly from 64kB to 1MB.
2019-07-08 00:09:59 +02:00
Ayke van Laethem 152caa3b0a compiler: do not create stack objects for functions that don't allocate
This is a useful optimization for targets with the portable garbage
collector. It isn't as big as you might guess but it does optimize
functions inside the garbage collector itself (which obviously should
not allocate). WebAssembly output in one test is about 1% smaller.
2019-07-08 00:02:28 +02:00
Ayke van Laethem 7ed6b45149 compiler: add the //go:noinline pragma
This is directly useful to avoid some unsafety around runtime.alloc and
should be useful in general.

This pragma has the same form as in the main Go compiler:
https://github.com/golang/go/issues/12312
2019-07-08 00:02:28 +02:00
Ayke van Laethem c66d979ba3 compiler: avoid some stack frames when this is unnecessary
Some instructions do not create new values, they transform existing
values in some way (bitcast, getelementptr, etc.). Do not store them in
the stack object.

This lowers the size of the repulsion demo from 100kB to 98kB (with a
baseline of 72kB for the leaking GC). That's a useful reduction in file
size.
2019-07-08 00:02:28 +02:00
Ron Evans fc9188a298 machine/samd21/arduino-nano33: adds support for Arduino Nano33 IoT along with mapping to NINA-W102 WiFi chip.
Also adds DTR and RTS functions along with timeouts to USBCDC functions to prevent lockups.

Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-07 18:09:05 +02:00
Ayke van Laethem ffa38b183b all: add HiFive1 rev B board with RISC-V architecture
This page has been a big help in adding support for this new chip:
https://wiki.osdev.org/HiFive-1_Bare_Bones
2019-07-07 14:03:24 +02:00
Ayke van Laethem f0eb4eef5a ci: make smoketests more flexible 2019-07-07 14:03:24 +02:00
Ayke van Laethem fa928e8cd3 tools/gen-device-svd: be a bit more forgiving for stm32 svd files
Some newer files have a few mistakes. Allow them to be processed.
2019-07-06 17:16:18 +02:00
Ayke van Laethem c49d80628c tools/gen-device-svd: refactor to make the code more declarative 2019-07-06 17:16:18 +02:00
Ayke van Laethem af523c63d5 ci: fix cache paths for LLVM source
This makes the LLVM source code cacheable again.
2019-07-06 14:57:54 +02:00
diamondburned aabb6ba22b main: use zversion.go and VERSION files to detect version, fixes #433 2019-07-06 02:31:49 +02:00
Ayke van Laethem 6611578ec8 compiler: remove some TODOs
These have long since been implemented.
2019-07-03 21:22:12 +02:00
Ayke van Laethem d49a363d0d compiler: track all pointers returned by runtime.alloc
In particular, track the pointers to the memory allocated for coroutine
frames. Before this change, the following code would show memory
corruption:
https://github.com/johanbrandhorst/wasm-experiments/blob/master/canvas/main.go
2019-07-03 21:22:12 +02:00
Ayke van Laethem 385d1d0a5d compiler,runtime: implement a portable conservative GC 2019-07-01 16:30:33 +02:00
Ayke van Laethem 00e91ec569 all: rename garbage collectors
dumb -> leaking:
  make it more clear what this "GC" does: leak everything.
marksweep -> conservative:
  "marksweep" is too generic, use "conservative" to differentiate
  between future garbage collectors: precise marksweep / mark-compact /
  refcounting.
2019-07-01 13:03:07 +02:00
Daniel Esteban 1fd0c8d48c adds PowerSupplyActive to enable supply voltages to nRF52840 and (#430)
* machine/reelboard: adds PowerSupplyActive to enable supply voltages to nRF52840 and
peripherals.
2019-06-30 12:23:44 +02:00
Daniel Esteban d34bb7e708 add reelboard pins for the epaper display and the reset pin (#429)
* machine/reelboard: added descriptive pin names for the epaper display and the reset pin
2019-06-30 09:57:07 +02:00
Ayke van Laethem a328bbdff3 main: small refactor in error printing
Use a type switch instead of an if/else chain. This results in much more
readable code.
2019-06-29 19:18:46 +02:00
Ayke van Laethem 4ecd478d82 machine: add generic board support on non-baremetal hardware
Instead of trying to modify periperhals directly, external functions are
called. For example, __tinygo_gpio_set sets a GPIO pin to a specified
value (high or low). It is expected that binaries made this way will be
linked with some extra libraries that implement support for these
functions.

One particularly interesting case is this experimental board simulator:
https://github.com/aykevl/tinygo-play
Compiling code to WebAssembly with the correct build tag for a board
will enable this board to be simulated in the browser.

Atmel/Microchip based SAMD boards are not currently supported, because
their I2C/SPI support is somewhat uncommon and harder to support in the
machine API. They may require a modification to the machine API for
proper support.
2019-06-28 10:00:14 +02:00
Justin Clift 40b193f1fa circleci: update source and build cache keys 2019-06-24 16:22:37 +02:00
Justin Clift 7fd5ec8661 Makefile: use the LLVM monorepo
The -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF option turning off the
building of the extra clang tools is required, otherwise linking
the tinygo binary fails.
2019-06-24 16:22:37 +02:00
m-chichikalov 84618c45eb Added supporting suctom TargetSpec json file via -target flag. 2019-06-20 19:10:15 +02:00
Carolyn Van Slyck c0ff4e566d Include test in helptext 2019-06-20 19:05:06 +02:00
Carolyn Van Slyck ce9b21a270 Default package name to . when not specified
When running tinygo build, and the positional argument for the
package is not specified, default it to "."
2019-06-20 19:05:06 +02:00
Ayke van Laethem ed7c242a09 runtime: fix a heap corruption where some blocks were not marked as reachable
This is a rather critical error and I wonder why it hasn't been
discovered earlier.
2019-06-20 13:32:45 +02:00
Justin Clift 46872a70b1 Trivial typo fix 2019-06-20 13:30:38 +02:00
scriptonist e9f6e51fc8 compiler,runtime: implement string to []rune conversion
Commit message by aykevl
2019-06-19 01:17:21 +02:00
Ayke van Laethem fa5855bff5 main: add support for -tags flags 2019-06-18 16:02:20 +02:00
Ron Evans 16201c41dc test: replace ExitStatus() with go1.11 compatible syntax
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-06-18 13:45:11 +02:00
Carolyn Van Slyck 208e1719ad Add test command to tinygo (#243)
* Add test command to tinygo
2019-06-18 12:23:59 +02:00
Ayke van Laethem a3d1f1a514 all: try more locations to find Clang built-in headers
This commit fixes the case where TinyGo was built with `go build` and
LLVM sources were never downloaded into the TinyGo source directory.
2019-06-13 15:21:04 +02:00
Tomer Elmalem 3e1c0d90c5 Add implementation for os.Exit and syscall.Exit 2019-06-12 19:04:34 +02:00
Ayke van Laethem b7197bcaae compiler,runtime: implement non-blocking selects
Blocking selects are much more complicated, so let's do non-blocking
ones first.
2019-06-12 18:26:52 +02:00
Ayke van Laethem 8890a0f3c8 compiler,runtime: store channel size in the channel itself
This may have a small effect on code size sometimes, but will simplify
the implementation of the select statement.
2019-06-12 18:26:52 +02:00
Justin Clift 5be412dabe Makefile: using an alternative LLVM build dir now works 2019-06-12 14:05:45 +02:00
Ayke van Laethem 412ec5b789 Makefile: drop the -lclangToolingRefactor library
It is not necessary and does not exist in LLVM 9.
2019-06-12 08:43:08 +02:00
Ayke van Laethem b0e767c4a7 compiler: move global handling from ir to compiler package
This is part of a larger rafactor that tries to shrink the ir package
and in general tries to shrink the amount of state that is kept around
in the compiler. The end goal is being able to compile packages
independent of each other, linking them together in a later stage. Along
the way, it cleans up lots of old cruft that has accumulated over the
months.

This refactor also results in globals being loaded lazily. This may be a
problem for some specific programs but will probably change back in a
commit in the near future.
2019-06-08 22:17:09 +02:00
Ayke van Laethem 6b5b4a681d compiler: refactor named types to create them lazily
This commit refactors named types to be created lazily. Instead of
defining all types in advance, do it only when necessary.
2019-06-08 22:17:09 +02:00
Ayke van Laethem 88bb61f287 compiler: return a valid (undef) value from a unsupported select
Returning a nil value may lead to problems later on. Just return undef
here, so that further compilation will at least be safe (the result
will be discarded anyway).
2019-06-08 21:48:05 +02:00
Ayke van Laethem e169e3b996 compiler: remove superfluous 'err' result in decodeFuncValue 2019-06-08 21:48:05 +02:00
Ayke van Laethem aa8957dd05 compiler: support non-constant syscall numbers
Not all syscalls use constant numbers.
2019-06-08 21:48:05 +02:00
Ayke van Laethem 8e5731aee7 compiler: add support for pointers as map keys 2019-06-08 21:48:05 +02:00
Justin Clift 0cc35b6188 Add a note to use --recursive when cloning the TinyGo repo 2019-06-08 22:50:14 +10:00
Ayke van Laethem 5cf7fba1a4 compiler: remove //go:volatile support 2019-06-06 19:46:49 +02:00
Ayke van Laethem e67506ee68 arm: update to avoid //go:volatile
This change results in changes to all smoketests for Cortex-M based
chips: they get a bit smaller (32-48 bytes). I'm not sure why but
probably because the inliner made a different inlining decision. There
was a similar effect when files generated from SVD files switched to the
new volatile types so it's probably harmless.
2019-06-06 19:46:49 +02:00
Ayke van Laethem f2c205a008 machine: update ringbuffer to use runtime/volatile.Register8
This avoids the //go:volatile pragma, which will be removed soon.
There were no changes to the output of the smoke tests.
2019-06-06 19:46:49 +02:00
Ayke van Laethem c84c625585 runtime: update to avoid //go:volatile
There was exactly one change in the output of the smoke tests:
examples/test. However, it still runs just fine on a PCA10040.
2019-06-06 19:46:49 +02:00
Ayke van Laethem 9673ad3774 all: move Register{8,16,32} values into runtime/volatile
This avoids duplication of code. None of the smoke tests have changed
their output.
2019-06-06 19:46:49 +02:00
Ayke van Laethem 66aca428ba compiler: rename import path if it lies in TINYGOPATH
This allows importing (for example) both
"github.com/tinygo-org/tinygo/src/machine" and "machine" without issues.
The former is renamed to just "machine".
2019-06-06 16:01:25 +02:00
Ayke van Laethem ec87811420 compiler: do not panic on duplicate functions
Instead, show a regular error message. This is much more user-friendly.
2019-06-06 16:01:25 +02:00
Ayke van Laethem 776dc1e0d9 main: show a better error when version detection of GOROOT failed 2019-06-06 13:59:37 +02:00
Ron Evans beea2f1f30 docs: add note to current/future contributors to please start by opening a GH issue to avoid duplication of effort
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-06-06 12:09:17 +02:00
Ayke van Laethem 5f9c683abf main: return an error when running a command failed 2019-06-05 09:08:44 +02:00
Ayke van Laethem 0ce4d90779 cgo: add support for anonymous structs 2019-06-03 20:01:47 +02:00
Tomer Elmalem 1047c9bd05 reflect: stub out reflect.New and reflect.Zero 2019-06-03 19:26:47 +02:00
Ayke van Laethem 1d7cc2c242 cgo: add support for bitfields using generated getters and setters 2019-06-03 16:13:19 +02:00
Ayke van Laethem 23c8d15847 main: add -dev suffix to version 2019-06-03 16:09:29 +02:00
Ayke van Laethem da85710894 main: version 0.6.0 2019-05-29 16:38:04 +02:00
j7b 0ae467d3e2 llvm cpu features 2019-05-28 15:41:34 +02:00
Justin Clift 4442b1304e Trivial typo fix 2019-05-28 09:55:35 +02:00
Ayke van Laethem 83ab61e261 ci: install Go 1.12.5 on macOS
This should fix compatibility with the Go 1.12 stdlib:
https://github.com/tinygo-org/tinygo/issues/368
2019-05-27 19:47:12 +02:00
Ron Evans 2504754325 machine/samd21: use HasBits() method to simplify bit comparisons
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-27 18:43:11 +02:00
Ron Evans be491abc46 machine/stm32: use HasBits() method to simplify bit comparisons
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-27 18:43:11 +02:00
Ron Evans 31189deb3b machine/avr: use HasBits() method to simplify bit comparisons
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-27 18:43:11 +02:00
Ron Evans 90cd3f8ea5 tools: generate volatile HasBits() method in device wrappers to simplify bit comparison code
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-27 18:43:11 +02:00
Ayke van Laethem 2f95a5d452 ci: use go modules instead of dep
This also updates the LLVM build to include this commit:
https://github.com/llvm-mirror/llvm/commit/d519e424c503d2d2a723284664a509a6cd5401a8
2019-05-27 18:42:36 +02:00
Ayke van Laethem eb1d834dd4 wasm: add support for js.FuncOf 2019-05-27 13:35:59 +02:00
Ayke van Laethem 3313decb68 compiler,runtime: make panic functions camelCase
Rename panic functions to be runtime.nilPanic, runtime.lookupPanic, and
runtime.slicePanic.
2019-05-27 13:35:59 +02:00
Ron Evans 191a076956 docs: update list of supported MCU boards
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-27 09:43:24 +02:00
Ayke van Laethem 0f2dcba7b3 syscall: implement Exit call on unix 2019-05-27 07:50:13 +02:00
Ayke van Laethem 94b8214529 machine: refactor pins to be of Pin type 2019-05-26 20:48:50 +02:00
Ayke van Laethem 421ef04efb wasm: fix Makefile to avoid debuginfo 2019-05-25 18:40:56 +02:00
Ayke van Laethem f7687c43aa wasm: fix wasm-ld hang
See the following bugs for more information:
https://bugs.llvm.org/show_bug.cgi?id=41508
https://bugs.llvm.org/show_bug.cgi?id=37064
2019-05-25 18:25:46 +02:00
Ayke van Laethem 5a7bab8808 main: add the absolute path to clang-8 on macOS
This avoids the need to correctly set $PATH if LLVM 8 has been installed
using Homebrew.
2019-05-24 19:53:43 +02:00
Ron Evans 3a73e64557 tools/gen-device: complete refactor to new generator based on volatile package
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-24 16:28:47 +02:00
Ron Evans 9f8340a970 machine/nrf: refactor to use volatile package/API
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-24 15:44:33 +02:00
k-brk 0f6873cf02 machine/stm32f103xx: fix i2c 2 byte read 2019-05-24 15:26:06 +02:00
Ayke van Laethem f2cd4d12e8 compiler,runtime: fix multiple definitions of a single function
strings.IndexByte was implemented in the runtime up to Go 1.11. It is
implemented using a direct call to internal/bytealg.IndexByte since Go
1.12.

Make sure we remain compatible with both.
2019-05-24 14:51:40 +02:00
Ayke van Laethem 7e6a54ac62 main: add build tags for the Go stdlib version 2019-05-24 14:51:40 +02:00
Ayke van Laethem 7156afca9e interp: support some more expressions in const icmp 2019-05-24 14:00:23 +02:00
Ayke van Laethem 87ac804642 ci: make sure that all examples are included in the smoketests 2019-05-24 13:51:20 +02:00
Ayke van Laethem edcb11f9f6 ci: move tests from CircleCI config to Makefile
This makes it easier to run smoke tests locally.
2019-05-24 13:51:20 +02:00
Ayke van Laethem 3568254593 machine/atsamd21: fix analog pin mode 2019-05-23 20:37:10 +02:00
Ayke van Laethem 3bf4c06c99 interp: work around limitation of constfolding in IR builder
The IR builder does not appear to fold comparisons of constant inttoptr
instructions to const null pointer. So do it manually during
interpretatin, as a somewhat ugly hack.

This fixes https://github.com/tinygo-org/tinygo/issues/363
2019-05-21 19:33:19 +02:00
Ayke van Laethem f1d9e7b75e interp: make errors during branches more reliable
Previously, there was a suble error in that .IsConstant() is not always
allowed to be called, resulting in a i1 that was not 0 or 1. Fix this by
checking for the constants directly and adding some more diagnostics to
catch more cases.
2019-05-21 19:33:19 +02:00
Ron Evans e4d53daa02 machine/stm32: refactor to use new volatile package for all register access
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-21 15:08:43 +02:00
Ayke van Laethem 98a3047b58 all: add go.mod and go.sum files for vgo support
Add required files for vgo support. The new vgo system defaults to on
since Go 1.13 so we should be prepared for that once the release hits.
It is also useful for testing Go 1.13 before the release.
2019-05-20 11:40:29 +02:00
Ayke van Laethem ba85c82fbb arm: print an error when a HardFault occurs
This is very useful for debugging. It differentiates between a stack
overflow and other errors (because it's easy to see when a stack
overflow occurs) and prints the old stack pointer and program counter if
available.
2019-05-20 11:39:17 +02:00
Ayke van Laethem 7b6ef65fe7 compiler: create temporary allocas with appropriate lifetimes
Make sure all allocas are created in the entry block and are given the
right lifetimes. This is good for code quality:

  * Moving allocas to the entry block makes sure they are always
    allocated statically (avoiding the need for a frame pointer) and do
    not grow the stack on each new alloca instruction. This is
    especially useful in loops where it could otherwise lead to a stack
    overflow even though there is no recursion.
  * Adding lifetime markers allows LLVM to reuse stack areas for
    different allocas as long as their lifetimes do not overlap.

All in all, this reduces code size in all tested cases for the BBC
micro:bit, and reduces code size for most cases for WebAssembly.
2019-05-20 09:52:42 +02:00
Ron Evans de032cddd2 machine/sam: Refactor all machine/runtime code to use new volatile package/API
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-19 19:53:11 +02:00
Ron Evans 51c6b972bf machine/feather-m0: correct board build tag and add placeholders for I2S interface
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-19 19:52:14 +02:00
Anthony Elder 4cd151faf5 Add Feather M0 Board (#356)
* Add Feather M0 Board
2019-05-19 17:10:17 +02:00
Ayke van Laethem 5342d392aa interp: improve scan for loads
During a scan, consider loads from dirty globals to be dirty and check
whether they have any local side effects.

This fixes a problem with the new volatile operations that are now in
methods on registers instead of being emitted inline as volatile
instructions.
2019-05-18 18:30:22 +02:00
Ayke van Laethem f94af9f61e compiler: avoid some obviously false nil checks
Pointers to globals are never nil.
2019-05-18 18:30:22 +02:00
Ayke van Laethem 7ada00790c cgo: print better error messages for unknown types
Types used in a program may not be implemented. Print a nice error
message explaining the situation, instead of just prepending C. to the
type spelling (and hoping the user knows what that undefined reference
means).
2019-05-17 19:37:20 +02:00
Ayke van Laethem dfa713040a cgo: add support for enum types
Enum types are implemented as named types (with possible accompanying
typedefs as type aliases). The constants inside the enums are treated as
Go constants like in the Go toolchain.
2019-05-17 19:37:20 +02:00
Ron Evans 82dc14b741 docker: do not remove make from tinygo-dev docker image, to make it easier to run drivers CI build, which uses the tinygo-dev image
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-15 11:51:26 +02:00
Ron Evans d3f2237d44 docs: update README with new boards Adafruit Trinket M0, and STM32F407 Discovery
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-14 19:38:39 +02:00
Martin Treml fc2ed2bdd0 [Board] Adafruit Trinket (#333)
* Add support for Adafruit Trinket-M0 board
2019-05-14 19:30:39 +02:00
Ayke van Laethem e0cf74e638 avr: use register wrappers that use runtime/volatile.*Uint8 calls
This avoids the //go:volatile pragma on types in Go source code, at
least for AVR targets.
2019-05-14 12:24:01 +02:00
Ayke van Laethem 6f6afb0515 compiler: add //go:inline pragma 2019-05-14 12:24:01 +02:00
Ayke van Laethem 397b90753c compiler: implement volatile operations as compiler builtins
The long term goal is to remove the //go:volatile hack.
2019-05-14 12:24:01 +02:00
Ayke van Laethem 3c2639ad55 compiler: insert nil checks when storing to a pointer
This does increase code size, but it is necessary to avoid undefined
behavior.
2019-05-14 12:24:01 +02:00
Ayke van Laethem 371c468e8e compiler: add debug info for function arguments
This commit adds debug info to function arguments, so that in many cases
you can see them when compiling with less optimizations enabled.
Unfortunately, due to the way Go SSA works, it is hard to preserve them
in many cases.
Local variables are not yet saved.

Also, change the language type to C, to make sure lldb shows function
arguments. The previous language was Modula 3, apparently due to a
off-by-one error somewhere.
2019-05-14 11:18:38 +02:00
Ayke van Laethem 0a40219680 compiler: implement comparing channel values 2019-05-14 11:18:38 +02:00
Ayke van Laethem c981f14e61 compiler: simplify some interface code
No error is produced, so no error needs to be returned. It was missed in
https://github.com/tinygo-org/tinygo/pull/294.

Also, it fixes this smelly code:

    if err != nil {
        return <something>, nil
    }

There could never be an error, so the code was already dead.
2019-05-14 09:59:00 +02:00
Ayke van Laethem 763b9d7d10 runtime: implement growing hashmaps
Add support for growing hashmaps beyond their initial size.
2019-05-14 09:59:00 +02:00
Ayke van Laethem 55fc7b904a compiler,runtime: use the size hint when creating a new map
It defaults to hint/8 number of buckets. This number may be tuned in the
future.
2019-05-14 09:59:00 +02:00
Ayke van Laethem 17c42810d0 compiler: improve hashmaps by avoiding dynamic allocas
By moving all allocas used in hashmap operations to the entry block, the
stack frame remains at a fixed size known at compile time. This avoids
stack overflows when doing map operations in loops and in general
improves code quality: the compiled size of testdata/map.go went from
3776 to 3632 in .text size.
2019-05-14 09:59:00 +02:00
Justin Clift 064d001550 Trivial typo fixes 2019-05-13 17:11:19 +02:00
seph a4cd3bb77c Test for functional argument passing (#336)
* Test for functional argument passing
2019-05-13 14:40:58 +02:00
Anthony Elder 8d3f19bc84 Fix I2C signalStop in readLastByte for Microbit (#344)
* Fix I2C signalStop in readLastByte for Microbit
2019-05-13 14:30:25 +02:00
Ron Evans d90f1947d9 machine/samd21: Initial implementation of I2S hardware interface using Circuit Playground Express
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-12 21:51:07 +02:00
Ayke van Laethem 11567c62d4 cgo: refactor; support multiple cgo files in a single package
This is a big commit that does a few things:

  * It moves CGo processing into a separate package. It never really
    belonged in the loader package, and certainly not now that the
    loader package may be refactored into a driver package.
  * It adds support for multiple CGo files (files that import package
    "C") in a single package. Previously, this led to multiple
    definition errors in the Go typecheck phase because certain C
    symbols were defined multiple times in all the files. Now it
    generates a new fake AST that defines these, to avoid multiple
    definition errors.
  * It improves debug info in a few edge cases that are probably not
    relevant outside of bugs in cgo itself.
2019-05-12 10:49:15 +02:00
Ayke van Laethem 4619207f99 cgo: don't crash on import "C" without comment
This doesn't make a lot of sense, but we shouldn't crash on it.
2019-05-12 10:49:15 +02:00
Ayke van Laethem 99587fe073 cgo: add support for #define constants
These are converted to Go constants where possible.
2019-05-12 10:49:15 +02:00
Ayke van Laethem eb0ce8a298 Makefile: avoid libtinfo and libz dependency of LLVM
These two dependencies are optional but enabled by default when
available. Disable them in the Makefile so that the tinygo binary is
portable to systems that don't have them or have a different version
(for example, Arch has a newer version of libcurses and thus libtinfo).
2019-05-11 15:47:15 +02:00
Ayke van Laethem 4ae4ef5e12 compiler: implement complex division
This is hard to do correctly, so copy the relevant files from the Go
compiler itself.

For related discussions:
* https://github.com/golang/go/issues/14644
* https://github.com/golang/go/issues/29846
2019-05-11 15:33:37 +02:00
Ayke van Laethem d7460b945e compiler: implement complex multiplication 2019-05-11 15:33:37 +02:00
Ayke van Laethem 638bc17eeb compiler: add support for complex add and sub
This is fairly trivial to add and follows the implementation of gc:
https://github.com/golang/go/blob/170b8b4b12be50eeccbcdadb8523fb4fc670ca72/src/cmd/compile/internal/gc/ssa.go#L2179-L2192
2019-05-11 15:33:37 +02:00
Justin Clift 1113f9ec0c main: comment the TinyGo IR header line
Without this, clang tries to process the header line as part of
its valid input. eg:

  main.ll:1:1: error: expected top-level entity
  Generated LLVM IR:
  ^
2019-05-10 22:50:18 +02:00
seph 019331e8af Add llvm directorys to gitignore
These are build artifacts
2019-05-09 19:20:39 +02:00
Justin Clift 4c8c048c49 example: just using 'Cache-Control': 'no-cache' should be good enough 2019-05-09 09:23:36 +02:00
Ayke van Laethem 08ee1916f5 main: fix multiple errors being reported as one 2019-05-08 19:37:08 +02:00
Ayke van Laethem 141a70f401 main: make $GOROOT more robust and configurable
Check various locations that $GOROOT may live, including the location of
the go binary. But make it possible to override this autodetection by
setting GOROOT manually as an environment variable.
2019-05-07 09:14:43 +02:00
Ayke van Laethem a79edf416c cgo: do not allow capturing of external/exported functions
Instead of assuming all declared (but not defined) functions are CGo
functions, mark all pointer params of externally visible symbols
'nocapture'. This means you may not store pointers between function
calls.

This is already the case when calling CGo functions upstream:
https://golang.org/cmd/cgo/#hdr-Passing_pointers
2019-05-05 20:56:35 +02:00
Ron Evans 2511aefac0 docker: perform a hard submodule reset after having moved the git repos directory
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-05-05 17:30:26 +02:00
Ayke van Laethem 4978065c9c cgo: avoid file/lineno hack for error locations 2019-05-05 17:07:35 +02:00
Ayke van Laethem 78a26fec13 cgo: be able to deal with nil files
I'm not sure where they come from but they lead to a crash, so turn them
into token.NoPos.
2019-05-05 17:07:35 +02:00
Ayke van Laethem 9cad8bd0c8 main: add fallback mechanism for LLVM commands
On Debian, all LLVM commands have a version suffix (clang-8, ld.lld-8,
wasm-ld-8, etc.). However. Most other distributions only provide a
version prefix for Clang and not for all the other commands.

This commit fixes the issue by trying the command with the version
suffix first and falling back to one without if needed.
2019-05-05 17:00:33 +02:00
Ayke van Laethem 9a54ee4241 compiler: allow larger-than-int values to be sent across a channel
Instead of storing the value to send/receive in the coroutine promise,
store only a pointer in the promise. This simplifies the code a lot and
allows larger value sizes to be sent across a channel.

Unfortunately, this new system has a code size impact. For example,
compiling testdata/channel.go for the BBC micro:bit, there is an
increase in code size from 4776 bytes to 4856 bytes. However, the
improved flexibility and simplicity of the code should be worth it. If
this becomes an issue, we can always refactor the code at a later time.
2019-05-05 16:46:50 +02:00
Ayke van Laethem 46d5ea8cf6 compiler: support returning values from async functions
This is implemented as follows:

  * The parent coroutine allocates space for the return value in its
    frame and stores a pointer to this frame in the parent coroutine
    handle.
  * The child coroutine obtains the alloca from its parent using the
    parent coroutine handle. It then stores the result value there.
  * The parent value reads the data from the alloca on resumption.
2019-05-05 16:46:50 +02:00
Daniel Esteban fb952a722a Remove microbit matrix (#319)
* Remove matrix code from bbc:microbit, and move it to a driver
2019-05-05 16:25:50 +02:00
Michael Teichgräber 7e46c1766d compiler: fix comp. of func calls for func values of a defined type
When compiling a piece of code where a function value is called,
the compiler panics if the function value's type is a defined type,
and not just a type literal (function signature): The type assertion
(*types.Signature) fails, because the type of the func value is a
*types.Named.

This patch fixes this by using the type's underlying type, so that a
types.Named is properly turned into its underlying types.Signature,
before the type assertion takes place.
It takes advantage of the property that all types have an underlying type
(both are the same, if a type is not named).

Fixes #320
2019-05-03 15:41:00 +02:00
Ayke van Laethem 1f0595438e main: do not set working directory for Clang invocation
This commit avoids setting the working directory to the TinyGo root when
invocating Clang. This helps to weed out issues before we add support
for bundling Clang in a release.
2019-05-03 11:36:24 +02:00
Justin Clift d594342642 examples: tell browsers to not cache wasm files from the example server 2019-05-02 14:13:50 +01:00
Ayke van Laethem 99da328453 compiler: avoid bitcast when replacing a method call with a direct call
A bitcast was inserted when the receiver of the call wasn't a *i8. This
is a pretty common case, and did not play well with goroutines.
Avoid this bitcast by changing each call to a direct call, after
unpacking the receiver type from the *i8 parameter. This might also fix
some undefined behavior in the resulting program, as it is technically
not allowed to call a function with a different signature (even if the
signature is compatible).
2019-05-01 12:12:30 +02:00
Ayke van Laethem 387e1340bf compiler: refactor packing of word-sized values in integers
There are two places that try to store values directly in pointers, if
possible: closures and interfaces. Use the same functions for both.
2019-05-01 12:12:30 +02:00
Ayke van Laethem b1ed8a46b7 cgo: only include the symbols that are necessary (recursively)
Only try to convert the C symbols to their Go equivalents that are
actually referenced by the Go code with C.<somesymbol>. This avoids
having to support all possible C types, which is difficult because of
oddities like `typedef void` or `__builtin_va_list`. Especially
__builtin_va_list, which varies between targets.
2019-05-01 11:33:18 +02:00
Ayke van Laethem 35af33ead7 cgo: improve typedef/struct/enum support
Typedefs are now Go type aliases. And C.struct_ and C.union_ prefixed
records work correctly now, even when they're not in a typedef.
2019-05-01 11:33:18 +02:00
Justin Clift 4bd1b9e53d wasm: use println instead of fmt
The generated wasm is 575 bytes when compiled with -no-debug (and
works), which is a much better first experience for new users than
the 20KB+ added (atm) just from including fmt.
2019-05-01 10:35:18 +02:00
Ayke van Laethem 80ee343e6d main: make tests more portable
Windows uses backward slashes instead of forward slashes, so be
compatible with that.
2019-04-30 20:04:04 +02:00
Ayke van Laethem 1d59a960bc main: allow changing the clang command name 2019-04-30 20:04:04 +02:00
Ayke van Laethem 5ca2e1322c main: close ar file before moving it
Moving a file is not allowed on Windows when a program still has the
file open.
2019-04-30 20:04:04 +02:00
Ayke van Laethem 5b0b35f9e4 main: use os.UserCacheDir to get a cache directory
This is more portable than assuming the cache directory lies at
~/.cache.
2019-04-30 20:04:04 +02:00
Ayke van Laethem 9a3d0683b3 compiler: mark all GEPs as inbounds
In Go, it is not possible to construct pointers that are out of bounds
(and not null), so let LLVM know about this fact.

This leads to a significant code size reduction, around 3% in many
cases.
2019-04-26 09:17:52 +02:00
Ayke van Laethem d155e31b64 all: improve compiler error handling
Most of these errors are actually "todo" or "unimplemented" errors, so
the return type is known. This means that compilation can proceed (with
errors) even though the output will be incorrect. This is useful because
this way, all errors in a compilation unit can be shown together to the
user.
2019-04-26 08:52:10 +02:00
Ayke van Laethem 45cacda7b3 compiler: refactor parseExpr
This commit adds getValue which gets a const, global, or result of a
local SSA expression and replaces (almost) all uses of parseExpr with
getValue. The only remaining use is in parseInstr, which makes sure an
instruction is only evaluated once.
2019-04-26 08:52:10 +02:00
Ayke van Laethem c25fe609a9 compiler: do not return an error from getLLVMType
This commit replaces "unknown type" errors in getLLVMType with panics.

The main reason this is done is that it simplifies the code *a lot*.
Many `if err != nil` lines were there just because of type information.
Additionally, simply panicking is probably a better approach as the only
way this error can be produced is either with big new language features
or a serious compiler bug. Panicking is probably a better way to handle
this error anyway.
2019-04-26 08:52:10 +02:00
Ayke van Laethem 6d23809218 compiler: simplify code around getZeroValue
The LLVM library we use does not (yet) provide a llvm.Zero (like it
provides a llvm.Undef) so we have implemented our own. However, in
theory it might return an error in some cases.

No real-world errors have been seen in a while and errors would likely
indicate a serious compiler bug anyway (not an external error), so make
it panic instead of returning an error.
2019-04-26 08:52:10 +02:00
Ayke van Laethem 024eceb476 runtime: print error when panicking with error interface type 2019-04-25 14:06:34 +02:00
Ayke van Laethem 0fd90c49cc compiler: make panic configurable
Currently defined: abort and trap. -panic=unwind should be implemented
in the future.
2019-04-25 13:56:19 +02:00
Ayke van Laethem d1efffe96b test: print better error messages on compilation failure 2019-04-25 12:55:52 +02:00
Ayke van Laethem 8e7ea92d44 cgo: improve error locations for cgo-constructed AST
This is mostly useful for debugging missing type conversions in CGo.
With this change, errors will have the correct source location in C
files.
2019-04-25 12:55:52 +02:00
Ayke van Laethem 2f2d62cc0c cgo: support builtin #include headers
Add support for header files bundled with the compiler by copying them
into the release tarball.
2019-04-25 12:55:52 +02:00
Ayke van Laethem d396abb690 cgo: add dummy implementation of __builtin_va_list
Every ABI has a slightly different implementation. Ideally, we would use
something like Clang TargetInfo or extract it by compiling some C code
and checking the IR, but this is a useful workaround for now.
2019-04-25 10:48:56 +02:00
Ayke van Laethem b815d3f760 cgo: implement void* pointer type
void* is translated to unsafe.Pointer on the Go side.
2019-04-25 10:48:56 +02:00
Ayke van Laethem 9c46ac4eed cgo: implement char type
This type is a bit more difficult because it can be signed or unsigned
depending on the target platform.
2019-04-25 10:48:56 +02:00
Ron Evans b2e96fc35a machine/atsamd21: select internal ground for ADC and scale result correctly to 16-bit
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-22 07:59:35 +02:00
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
236 changed files with 14236 additions and 5518 deletions
+256
View File
@@ -0,0 +1,256 @@
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
llvm-source-linux:
steps:
- restore_cache:
keys:
- llvm-source-8-v5
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-8-v5
paths:
- llvm-project
test-linux:
steps:
- checkout
- submodules
- apt-dependencies:
llvm: "-8"
- install-node
- restore_cache:
keys:
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- llvm-source-linux
- run: go install .
- run: go test -v
- run: make gen-device -j4
- run: make smoketest RISCV=0
- 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 \
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" }}
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-8-linux-v7
- 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-linux-v7
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
- 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
- run:
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
- run: make smoketest
build-macos:
steps:
- checkout
- submodules
- run:
name: "Install dependencies"
command: |
curl https://dl.google.com/go/go1.12.5.darwin-amd64.tar.gz -o go1.12.5.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.12.5.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- restore_cache:
keys:
- llvm-source-8-macos-v5
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-8-macos-v5
paths:
- llvm-project
- restore_cache:
keys:
- llvm-build-8-macos-v6
- 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-v6
paths:
llvm-build
- run:
name: "Create LLVM symlinks"
command: |
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
- 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
- run:
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
- run: make smoketest AVR=0
jobs:
test-llvm8-go111:
docker:
- image: circleci/golang:1.11
steps:
- test-linux
test-llvm8-go112:
docker:
- image: circleci/golang:1.12
steps:
- test-linux
build-linux:
docker:
- image: circleci/golang:1.12
steps:
- build-linux
build-macos:
macos:
xcode: "10.1.0"
steps:
- build-macos
workflows:
test-all:
jobs:
- test-llvm8-go111
- test-llvm8-go112
- build-linux
- build-macos
+6 -2
View File
@@ -5,8 +5,12 @@ src/device/avr/*.ld
src/device/avr/*.s
src/device/nrf/*.go
src/device/nrf/*.s
src/device/stm32/*.go
src/device/stm32/*.s
src/device/sam/*.go
src/device/sam/*.s
src/device/sifive/*.go
src/device/sifive/*.s
src/device/stm32/*.go
src/device/stm32/*.s
vendor
llvm
llvm-build
+2 -2
View File
@@ -9,8 +9,8 @@
url = https://github.com/avr-rust/avr-mcu.git
[submodule "lib/cmsis-svd"]
path = lib/cmsis-svd
url = https://github.com/posborne/cmsis-svd
url = https://github.com/tinygo-org/cmsis-svd
[submodule "lib/compiler-rt"]
path = lib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt.git
branch = release_70
branch = release_80
-61
View File
@@ -1,61 +0,0 @@
language: go
matrix:
include:
- dist: xenial
go: "1.11"
- os: osx
go: "1.11"
env: PATH="/usr/local/opt/llvm/bin:$PATH"
addons:
apt:
sources:
- sourceline: 'ppa:ubuntu-toolchain-r'
- sourceline: 'deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-7 main'
key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key'
packages:
- llvm-7-dev
- clang-7
- libclang-7-dev
- gcc-arm-linux-gnueabi
- 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
homebrew:
update: true
taps: ArmMbed/homebrew-formulae
packages:
- llvm@7
- qemu
- arm-none-eabi-gcc
install:
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then mkdir -p /Users/travis/gopath/bin; fi
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
- dep ensure --vendor-only
script:
- go install github.com/tinygo-org/tinygo
- go test -v .
- make gen-device
- tinygo build -size short -o blinky1.nrf.elf -target=pca10040 examples/blinky1
- tinygo build -size short -o blinky2.nrf.elf -target=pca10040 examples/blinky2
- tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
- tinygo build -size short -o test.nrf.elf -target=pca10040 examples/test
- tinygo build -size short -o blinky1.nrf51.elf -target=microbit examples/echo
- tinygo build -size short -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
- tinygo build -size short -o blinky1.nrf51d.elf -target=pca10031 examples/blinky1
- tinygo build -size short -o blinky1.stm32.elf -target=bluepill examples/blinky1
- if [ "$TRAVIS_OS_NAME" == "linux" ]; then tinygo build -size short -o blinky1.avr.elf -target=arduino examples/blinky1; fi
- if [ "$TRAVIS_OS_NAME" == "linux" ]; then tinygo build -size short -o blinky1.avr.elf -target=digispark examples/blinky1; fi
- tinygo build -size short -o blinky1.reel.elf -target=reelboard examples/blinky1
- tinygo build -size short -o blinky2.reel.elf -target=reelboard examples/blinky2
- tinygo build -size short -o blinky1.pca10056.elf -target=pca10056 examples/blinky1
- tinygo build -size short -o blinky2.pca10056.elf -target=pca10056 examples/blinky2
- tinygo build -size short -o blinky1.samd21.elf -target=itsybitsy-m0 examples/blinky1
+26 -60
View File
@@ -2,9 +2,9 @@
TinyGo depends on LLVM and libclang, which are both big C++ libraries. It can
also optionally use a built-in lld to ease cross compiling. There are two ways
these can be linked: dynamically and statically. The default is dynamic linking
because it is fast and works almost out of the box on Debian-based systems with
the right libraries installed.
these can be linked: dynamically and statically. An install with `go install` is
dynamic linking because it is fast and works almost out of the box on
Debian-based systems with the right packages installed.
This guide describes how to statically link TinyGo against LLVM, libclang and
lld so that the binary can be easily moved between systems. It also shows how to
@@ -18,81 +18,49 @@ build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.11+)
* [dep](https://golang.github.io/dep/)
* Standard build tools (gcc/clang)
* git or subversion
* git
* CMake
* [Ninja](https://ninja-build.org/) or make (preferably Ninja)
* [Ninja](https://ninja-build.org/)
The rest of this guide assumes you're running Linux, but it should be equivalent
on a different system like Mac.
## Download the source
The first step is to get the source code. Place it in some directory, assuming
`$HOME/src` here, but you can pick a different one of course:
The first step is to download the TinyGo sources (use `--recursive` if you clone
the git repository). Then, inside the directory, perform these steps:
git clone -b release_70 https://github.com/llvm-mirror/llvm.git $HOME/src/llvm
git clone -b release_70 https://github.com/llvm-mirror/clang.git $HOME/src/llvm/tools/clang
git clone -b release_70 https://github.com/llvm-mirror/lld.git $HOME/src/llvm/tools/lld
go get -d github.com/tinygo-org/tinygo
cd $HOME/go/src/github.com/tinygo-org/tinygo
dep ensure -vendor-only # download dependencies
dep ensure -vendor-only # download Go dependencies
make llvm-source # download LLVM
Note that Clang and LLD must be placed inside the tools subdirectory of LLVM to
be automatically built with the rest of the system.
You can also store LLVM outside of the TinyGo root directory by setting the
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
covered by this guide.
## Build LLVM, Clang, LLD
Building LLVM is quite easy compared to some other software packages. However,
the default configuration is _not_ optimized for distribution. It is optimized
for development, meaning that binaries produce accurate error messages at the
cost of huge binaries and slow compiles.
Before configuring, you may want to set the following environment variables to
speed up the build. Most Linux distributions ship with GCC as the default
compiler, but Clang is significantly faster and uses much less memory while
producing binaries that are about as fast.
Before starting the build, you may want to set the following environment
variables to speed up the build. Most Linux distributions ship with GCC as the
default compiler, but Clang is significantly faster and uses much less memory
while producing binaries that are about as fast.
export CC=clang
export CXX=clang++
Make a build directory. LLVM requires out-of-tree builds:
The Makefile includes a default configuration that is good for most users. It
builds a release version of LLVM (optimized, no asserts) and includes all
targets supported by TinyGo:
mkdir $HOME/src/llvm-build
cd $HOME/src/llvm-build
Configure LLVM with CMake:
cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;WebAssembly" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON
You can also choose a different build system than Ninja, but Ninja is fast.
There are various options you can tune here, but the options given above are
preferable for releases. Here is what they do:
* `LLVM_TARGETS_TO_BUILD` and `LLVM_EXPERIMENTAL_TARGETS_TO_BUILD`: the
targets that are natively supported by the LLVM code generators. The targets
listed here are the ones supported by TinyGo. Note that LLVM is a cross
compiler by default, unlike some other compilers.
* `CMAKE_BUILD_TYPE`: the default is Debug, which produces large inefficient
binaries that are easy to debug. We want small and fast binaries.
* `LLVM_ENABLE_ASSERTIONS`: the default is ON, which greatly slows down LLVM
and is only really useful during development. Disable them here.
* `LIBCLANG_BUILD_STATIC`: unlike LLVM, libclang is built as a shared library
by default. We want a static library for easy distribution.
Now build it:
ninja # or make, if you choose make in the previous step
make llvm-build
This can take over an hour depending on the speed of your system.
## Build TinyGo
Now that you have a working version of LLVM, build TinyGo using it. You need to
specify the directories to the LLVM build directory and to the Clang and LLD source.
The last step of course is to build TinyGo itself. This can again be done with
make:
cd $HOME/go/src/github.com/tinygo-org/tinygo
make static LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
make
## Verify TinyGo
@@ -109,14 +77,12 @@ The result should not contain libclang or libLLVM.
## Make a release tarball
Now that we have a working static build, it's time to make a release tarball.
This is just a slight change from the command to build TinyGo:
Now that we have a working static build, it's time to make a release tarball:
cd $HOME/go/src/github.com/tinygo-org/tinygo
make release LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
make release
The release tarball is stored in build/release.tar.gz, and can be extracted with
the following command:
the following command (for example in ~/lib):
tar -xvf path/to/release.tar.gz
+151
View File
@@ -1,3 +1,154 @@
0.7.0
---
* **command line**
- try more locations to find Clang built-in headers
- add support for `tinygo test`
- build current directory if no package is specified
- support custom .json target spec with `-target` flag
- use zversion.go to detect version of GOROOT version
- make initial heap size configurable for some targets (currently WebAssembly
only)
* **cgo**
- add support for bitfields using generated getters and setters
- add support for anonymous structs
* **compiler**
- show an error instead of panicking on duplicate function definitions
- allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it
- remove `//go:volatile` support
It has been replaced with the runtime/volatile package.
- allow poiners in map keys
- support non-constant syscall numbers
- implement non-blocking selects
- add support for the `-tags` flag
- add support for `string` to `[]rune` conversion
- implement a portable conservative garbage collector (with support for wasm)
- add the `//go:noinline` pragma
* **standard library**
- `os`: add `os.Exit` and `syscall.Exit`
- `os`: add several stubs
- `runtime`: fix heap corruption in conservative GC
- `runtime`: add support for math intrinsics where supported, massively
speeding up some benchmarks
- `testing`: add basic support for testing
* **targets**
- add support for a generic target that calls `__tinygo_*` functions for
peripheral access
- `arduino-nano33`: add support for this board
- `hifive1`: add support for this RISC-V board
- `reelboard`: add e-paper pins
- `reelboard`: add `PowerSupplyActive` to enable voltage for on-board devices
- `wasm`: put the stack at the start of linear memory, to detect stack
overflows
0.6.0
---
* **command line**
- some portability improvements
- make `$GOROOT` more robust and configurable
- check for Clang at the Homebrew install location as fallback
* **compiler driver**
- support multiple variations of LLVM commands, for non-Debian distributions
* **compiler**
- improve code quality in multiple ways
- make panic configurable, adding trap on panic
- refactor many internal parts of the compiler
- print all errors encountered during compilation
- implement calling function values of a named type
- implement returning values from blocking functions
- allow larger-than-int values to be sent across a channel
- implement complex arithmetic
- improve hashmap support
- add debuginfo for function arguments
- insert nil checks on stores (increasing code size)
- implement volatile operations as compiler builtins
- add `//go:inline` pragma
- add build tags for the Go stdlib version
* **cgo**
- implement `char`, `enum` and `void*` types
- support `#include` for builtin headers
- improve typedef/struct/enum support
- only include symbols that are necessary, for broader support
- mark external function args as `nocapture`
- implement support for some `#define` constants
- implement support for multiple CGo files in a single package
- **standard library**
- `machine`: remove microbit matrix (moved to drivers repository)
- `machine`: refactor pins to use `Pin` type instead of `GPIO`
- `runtime`: print more interface types on panic, including `error`
* **targets**
- `arm`: print an error on HardFault (including stack overflows)
- `atsamd21`: fix a bug in the ADC peripheral
- `atsamd21`: add support for I2S
- `feather-m0`: add support for this board
- `nrf51`: fix a bug in I2C
- `stm32f103xx`: fix a bug in I2C
- `syscall`: implement `Exit` on unix
- `trinket-m0`: add support for this board
- `wasm`: make _main_ example smaller
- `wasm`: don't cache wasm file in the server, for ease of debugging
- `wasm`: work around bug #41508 that caused a deadlock while linking
- `wasm`: add support for `js.FuncOf`
0.5.0
---
- **compiler driver**
- 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**
+4
View File
@@ -16,12 +16,16 @@ Please open a Github issue with your problem, and we will be happy to assist.
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
### Some specific hardware you want to use does not appear to be in TinyGo
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
Please start by opening a Github issue. We want to help you to help us to help you.
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
+14 -11
View File
@@ -1,17 +1,20 @@
# TinyGo base stage just installs LLVM 7 and the TinyGo compiler itself.
# TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
FROM golang:latest AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-7-dev libclang-7-dev git
apt-get install -y llvm-8-dev libclang-8-dev git
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
COPY . /go/src/github.com/tinygo-org/tinygo
# remove submodules directories and re-init them to fix any hard-coded paths
# after copying the tinygo directory in the previous step.
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
git submodule update --init
rm -rf ./lib/* && \
git submodule update --init --recursive --force
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
dep ensure --vendor-only && \
@@ -25,9 +28,9 @@ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y libllvm7 lld-7
apt-get install -y libllvm8 lld-8
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
FROM tinygo-base AS tinygo-avr
@@ -43,7 +46,7 @@ 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 && \
apt-get remove -y python3 make && \
apt-get remove -y python3 && \
apt-get autoremove -y && \
apt-get clean
@@ -59,9 +62,9 @@ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 && \
apt-get install -y apt-utils python3 make clang-8 && \
make gen-device-nrf && make gen-device-stm32 && \
apt-get remove -y python3 make && \
apt-get remove -y python3 && \
apt-get autoremove -y && \
apt-get clean
@@ -74,9 +77,9 @@ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 binutils-avr gcc-avr avr-libc && \
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
make gen-device && \
apt-get remove -y python3 make && \
apt-get remove -y python3 && \
apt-get autoremove -y && \
apt-get clean
Generated
+22 -4
View File
@@ -1,6 +1,22 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
name = "github.com/blakesmith/ar"
packages = ["."]
pruneopts = "UT"
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
[[projects]]
branch = "master"
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
name = "github.com/marcinbor85/gohex"
packages = ["."]
pruneopts = "UT"
revision = "7a43cd876e46e0f6ddc553f10f91731a78e6e949"
[[projects]]
branch = "master"
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
@@ -11,20 +27,22 @@
"go/types/typeutil",
]
pruneopts = "UT"
revision = "3744606dbb67b99c60d3f11cb10bd3f9e6dad472"
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
[[projects]]
branch = "master"
digest = "1:a6a25fd8906c74978f1ed811bc9fd3422da8093be863b458874b02a782b6ae3e"
branch = "llvm8"
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
name = "tinygo.org/x/go-llvm"
packages = ["."]
pruneopts = "UT"
revision = "d5f730401f5069618b275a5241c6417eb0c38a65"
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/blakesmith/ar",
"github.com/marcinbor85/gohex",
"golang.org/x/tools/go/ast/astutil",
"golang.org/x/tools/go/ssa",
"tinygo.org/x/go-llvm",
+1 -1
View File
@@ -1,5 +1,5 @@
[[constraint]]
branch = "master"
branch = "llvm8"
name = "tinygo.org/x/go-llvm"
[[constraint]]
+4 -1
View File
@@ -1,4 +1,7 @@
Copyright (c) 2018 Ayke van Laethem. All rights reserved.
Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library.
Copyright (c) 2009-2019 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
+97 -87
View File
@@ -3,92 +3,45 @@
all: tinygo
tinygo: build/tinygo
.PHONY: all tinygo static run-test run-blinky run-blinky2 clean fmt gen-device gen-device-nrf gen-device-avr
# Default build and source directories, as created by `make llvm-build`.
LLVM_BUILDDIR ?= llvm-build
CLANG_SRC ?= llvm-project/clang
LLD_SRC ?= llvm-project/lld
TARGET ?= unix
ifeq ($(TARGET),unix)
# Regular *nix system.
else ifeq ($(TARGET),pca10040)
# PCA10040: nRF52832 development board
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),microbit)
# BBC micro:bit
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),reelboard)
# reel board
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),bluepill)
# "blue pill" development board
# See: https://wiki.stm32duino.com/index.php?title=Blue_Pill
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),arduino)
OBJCOPY = avr-objcopy
TGOFLAGS += -target $(TARGET)
else
$(error Unknown target)
endif
.PHONY: all tinygo build/tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
CLANG_LIBS = -Wl,--start-group $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor -Wl,--end-group -lstdc++
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
endif
LLD_LIBS = -Wl,--start-group -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML -Wl,--end-group
CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
# 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-test: build/test
./build/test
run-blinky: run-blinky2
run-blinky2: build/blinky2
./build/blinky2
ifeq ($(TARGET),pca10040)
flash-%: build/%.hex
nrfjprog -f nrf52 --sectorerase --program $< --reset
else ifeq ($(TARGET),microbit)
flash-%: build/%.hex
openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -c 'program $< reset exit'
else ifeq ($(TARGET),reelboard)
flash-%: build/%.hex
openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -c 'program $< reset exit'
else ifeq ($(TARGET),arduino)
flash-%: build/%.hex
avrdude -c arduino -p atmega328p -P /dev/ttyACM0 -U flash:w:$<
else ifeq ($(TARGET),bluepill)
flash-%: build/%.hex
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c 'program $< reset exit'
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config)","")
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))
endif
clean:
@rm -rf build
FMT_PATHS = ./*.go cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
fmt:
@go fmt . ./compiler ./interp ./loader ./ir ./src/device/arm ./src/examples/* ./src/machine ./src/os ./src/runtime ./src/sync
@go fmt ./testdata/*.go
@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
test:
@go test -v .
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
gen-device-avr:
./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
@@ -103,27 +56,96 @@ gen-device-sam:
./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
go fmt ./src/device/sam
gen-device-sifive:
./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/AdaCore/svd2ada/tree/master/CMSIS-SVD/SiFive-Community
go fmt ./src/device/sifive
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-project/README.md:
git clone -b release/8.x https://github.com/llvm/llvm-project
llvm-source: llvm-project/README.md
# Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;RISCV" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF
# Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
cd $(LLVM_BUILDDIR); ninja
# Build the Go compiler.
build/tinygo:
@mkdir -p build
go build -o build/tinygo .
static:
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
release: static gen-device
test:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
tinygo-test:
cd tests/tinygotest && tinygo test
.PHONY: smoketest
smoketest:
# test all examples
tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
tinygo build -size short -o test.elf -target=pca10040 examples/adc
tinygo build -size short -o test.elf -target=pca10040 examples/blinkm
tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
tinygo build -size short -o test.elf -target=pca10040 examples/button
tinygo build -size short -o test.elf -target=pca10040 examples/button2
tinygo build -size short -o test.elf -target=pca10040 examples/echo
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
tinygo build -size short -o test.elf -target=pca10040 examples/mcp3008
tinygo build -size short -o test.elf -target=microbit examples/microbit-blink
tinygo build -size short -o test.elf -target=pca10040 examples/pwm
tinygo build -size short -o test.elf -target=pca10040 examples/serial
tinygo build -size short -o test.elf -target=pca10040 examples/test
# test all targets/boards
tinygo build -o test.wasm -tags=pca10040 examples/blinky2
tinygo build -size short -o test.elf -target=microbit examples/echo
tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
tinygo build -size short -o test.elf -target=bluepill examples/blinky1
tinygo build -size short -o test.elf -target=reelboard examples/blinky1
tinygo build -size short -o test.elf -target=reelboard examples/blinky2
tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
tinygo build -size short -o test.elf -target=feather-m0 examples/blinky1
tinygo build -size short -o test.elf -target=trinket-m0 examples/blinky1
tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
ifneq ($(AVR), 0)
tinygo build -size short -o test.elf -target=arduino examples/blinky1
tinygo build -size short -o test.elf -target=digispark examples/blinky1
endif
ifneq ($(RISCV), 0)
tinygo build -size short -o test.elf -target=hifive1b examples/blinky1
endif
tinygo build -o wasm.wasm -target=wasm examples/wasm/export
tinygo build -o wasm.wasm -target=wasm examples/wasm/main
release: build/tinygo gen-device
@mkdir -p build/release/tinygo/bin
@mkdir -p build/release/tinygo/lib/clang/include
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
@mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
@echo copying source files
@cp -p build/tinygo build/release/tinygo/bin
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
@@ -136,15 +158,3 @@ release: static gen-device
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
tar -czf build/release.tar.gz -C build/release tinygo
# Binary that can run on the host.
build/%: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
# ELF file that can run on a microcontroller.
build/%.elf: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
# Convert executable to Intel hex file (for flashing).
build/%.hex: build/%.elf
$(OBJCOPY) -O ihex $^ $@
+10 -5
View File
@@ -1,6 +1,6 @@
# TinyGo - Go compiler for small places
[![Build Status](https://travis-ci.com/tinygo-org/tinygo.svg?branch=dev)](https://travis-ci.com/tinygo-org/tinygo)
[![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
@@ -17,8 +17,8 @@ import (
)
func main() {
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
for {
led.Low()
time.Sleep(time.Millisecond * 1000)
@@ -43,12 +43,17 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following microcontroller boards are currently supported:
The following 15 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [BBC:Microbit](https://microbit.org/)
* [BBC micro:bit](https://microbit.org/)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
* [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)
+5 -3
View File
@@ -9,9 +9,11 @@ import (
// Get the cache directory, usually ~/.cache/tinygo
func cacheDir() string {
home := getHomeDir()
dir := filepath.Join(home, ".cache", "tinygo")
return dir
dir, err := os.UserCacheDir()
if err != nil {
panic("could not find cache dir: " + err.Error())
}
return filepath.Join(dir, "tinygo")
}
// Return the newest timestamp of all the file paths passed in. Used to check
+46 -14
View File
@@ -1,11 +1,15 @@
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.
@@ -187,13 +191,13 @@ func loadBuiltins(target string) (path string, err error) {
srcs[i] = filepath.Join(builtinsDir, name)
}
if path, err := cacheLoad(outfile, commands["clang"], srcs); path != "" || err != nil {
if path, err := cacheLoad(outfile, commands["clang"][0], srcs); path != "" || err != nil {
return path, err
}
var cachepath string
err = compileBuiltins(target, func(path string) error {
path, err := cacheStore(path, outfile, commands["clang"], srcs)
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
cachepath = path
return err
})
@@ -235,28 +239,56 @@ func compileBuiltins(target string, callback func(path string) error) error {
// 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()
err := execCommand(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
}
// Put all builtins in an archive to link as a static library.
// Note: this does not create a symbol index, but ld.lld doesn't seem to
// care.
arpath := filepath.Join(dir, "librt.a")
cmd := exec.Command(commands["ar"], append([]string{"cr", arpath}, objs...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
err = cmd.Run()
arfile, err := os.Create(arpath)
if err != nil {
return &commandError{"failed to make static library", arpath, err}
return err
}
defer arfile.Close()
arwriter := ar.NewWriter(arfile)
err = arwriter.WriteGlobalHeader()
if err != nil {
return &os.PathError{"write ar header", arpath, err}
}
for _, objpath := range objs {
name := filepath.Base(objpath)
objfile, err := os.Open(objpath)
if err != nil {
return err
}
defer objfile.Close()
st, err := objfile.Stat()
if err != nil {
return err
}
arwriter.WriteHeader(&ar.Header{
Name: name,
ModTime: time.Unix(0, 0),
Uid: 0,
Gid: 0,
Mode: 0644,
Size: st.Size(),
})
n, err := io.Copy(arwriter, objfile)
if err != nil {
return err
}
if n != st.Size() {
return errors.New("file modified during ar creation: " + arpath)
}
}
// Give the caller the resulting file. The callback must copy the file,
// because after it returns the temporary directory will be removed.
arfile.Close()
return callback(arpath)
}
+997
View File
@@ -0,0 +1,997 @@
// Package cgo implements CGo by modifying a loaded AST. It does this by parsing
// the `import "C"` statements found in the source code with libclang and
// generating stub function and global declarations.
//
// There are a few advantages to modifying the AST directly instead of doing CGo
// as a preprocessing step, with the main advantage being that debug information
// is kept intact as much as possible.
package cgo
// This file extracts the `import "C"` statement from the source and modifies
// the AST for CGo. It does not use libclang directly: see libclang.go for the C
// source file parsing.
import (
"go/ast"
"go/token"
"sort"
"strconv"
"strings"
"golang.org/x/tools/go/ast/astutil"
)
// cgoPackage holds all CGo-related information of a package.
type cgoPackage struct {
generated *ast.File
generatedPos token.Pos
errors []error
dir string
fset *token.FileSet
tokenFiles map[string]*token.File
missingSymbols map[string]struct{}
constants map[string]constantInfo
functions map[string]*functionInfo
globals map[string]globalInfo
typedefs map[string]*typedefInfo
elaboratedTypes map[string]*elaboratedTypeInfo
enums map[string]enumInfo
anonStructNum int
}
// constantInfo stores some information about a CGo constant found by libclang
// and declared in the Go AST.
type constantInfo struct {
expr *ast.BasicLit
pos token.Pos
}
// functionInfo stores some information about a CGo function found by libclang
// and declared in the AST.
type functionInfo struct {
args []paramInfo
results *ast.FieldList
pos token.Pos
}
// paramInfo is a parameter of a CGo function (see functionInfo).
type paramInfo struct {
name string
typeExpr ast.Expr
}
// typedefInfo contains information about a single typedef in C.
type typedefInfo struct {
typeExpr ast.Expr
pos token.Pos
}
// elaboratedTypeInfo contains some information about an elaborated type
// (struct, union) found in the C AST.
type elaboratedTypeInfo struct {
typeExpr *ast.StructType
pos token.Pos
bitfields []bitfieldInfo
}
// bitfieldInfo contains information about a single bitfield in a struct. It
// keeps information about the start, end, and the special (renamed) base field
// of this bitfield.
type bitfieldInfo struct {
field *ast.Field
name string
pos token.Pos
startBit int64
endBit int64 // may be 0 meaning "until the end of the field"
}
// enumInfo contains information about an enum in the C.
type enumInfo struct {
typeExpr ast.Expr
pos token.Pos
}
// globalInfo contains information about a declared global variable in C.
type globalInfo struct {
typeExpr ast.Expr
pos token.Pos
}
// cgoAliases list type aliases between Go and C, for types that are equivalent
// in both languages. See addTypeAliases.
var cgoAliases = map[string]string{
"C.int8_t": "int8",
"C.int16_t": "int16",
"C.int32_t": "int32",
"C.int64_t": "int64",
"C.uint8_t": "uint8",
"C.uint16_t": "uint16",
"C.uint32_t": "uint32",
"C.uint64_t": "uint64",
"C.uintptr_t": "uintptr",
}
// builtinAliases are handled specially because they only exist on the Go side
// of CGo, not on the CGo side (they're prefixed with "_Cgo_" there).
var builtinAliases = map[string]struct{}{
"char": struct{}{},
"schar": struct{}{},
"uchar": struct{}{},
"short": struct{}{},
"ushort": struct{}{},
"int": struct{}{},
"uint": struct{}{},
"long": struct{}{},
"ulong": struct{}{},
"longlong": struct{}{},
"ulonglong": struct{}{},
}
// cgoTypes lists some C types with ambiguous sizes that must be retrieved
// somehow from C. This is done by adding some typedefs to get the size of each
// type.
const cgoTypes = `
typedef char _Cgo_char;
typedef signed char _Cgo_schar;
typedef unsigned char _Cgo_uchar;
typedef short _Cgo_short;
typedef unsigned short _Cgo_ushort;
typedef int _Cgo_int;
typedef unsigned int _Cgo_uint;
typedef long _Cgo_long;
typedef unsigned long _Cgo_ulong;
typedef long long _Cgo_longlong;
typedef unsigned long long _Cgo_ulonglong;
`
// Process extracts `import "C"` statements from the AST, parses the comment
// with libclang, and modifies the AST to use this information. It returns a
// newly created *ast.File that should be added to the list of to-be-parsed
// files. If there is one or more error, it returns these in the []error slice
// but still modifies the AST.
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) {
p := &cgoPackage{
dir: dir,
fset: fset,
tokenFiles: map[string]*token.File{},
missingSymbols: map[string]struct{}{},
constants: map[string]constantInfo{},
functions: map[string]*functionInfo{},
globals: map[string]globalInfo{},
typedefs: map[string]*typedefInfo{},
elaboratedTypes: map[string]*elaboratedTypeInfo{},
enums: map[string]enumInfo{},
}
// Add a new location for the following file.
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
generatedTokenPos.SetLines([]int{0})
p.generatedPos = generatedTokenPos.Pos(0)
// Construct a new in-memory AST for CGo declarations of this package.
unsafeImport := &ast.ImportSpec{
Path: &ast.BasicLit{
ValuePos: p.generatedPos,
Kind: token.STRING,
Value: "\"unsafe\"",
},
EndPos: p.generatedPos,
}
p.generated = &ast.File{
Package: p.generatedPos,
Name: &ast.Ident{
NamePos: p.generatedPos,
Name: files[0].Name.Name,
},
Decls: []ast.Decl{
&ast.GenDecl{
TokPos: p.generatedPos,
Tok: token.IMPORT,
Specs: []ast.Spec{
unsafeImport,
},
},
},
Imports: []*ast.ImportSpec{unsafeImport},
}
// Find all C.* symbols.
for _, f := range files {
astutil.Apply(f, p.findMissingCGoNames, nil)
}
for name := range builtinAliases {
p.missingSymbols["_Cgo_"+name] = struct{}{}
}
// Find `import "C"` statements in the file.
for _, f := range files {
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
if len(genDecl.Specs) != 1 {
continue
}
spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
if !ok {
continue
}
path, err := strconv.Unquote(spec.Path.Value)
if err != nil {
panic("could not parse import path: " + err.Error())
}
if path != "C" {
continue
}
cgoComment := genDecl.Doc.Text()
pos := genDecl.Pos()
if genDecl.Doc != nil {
pos = genDecl.Doc.Pos()
}
position := fset.PositionFor(pos, true)
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
// Remove this import declaration.
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
i--
}
// Print the AST, for debugging.
//ast.Print(fset, f)
}
// Declare functions found by libclang.
p.addFuncDecls()
// Declare stub function pointer values found by libclang.
p.addFuncPtrDecls()
// Declare globals found by libclang.
p.addConstDecls()
// Declare globals found by libclang.
p.addVarDecls()
// Forward C types to Go types (like C.uint32_t -> uint32).
p.addTypeAliases()
// Add type declarations for C types, declared using typedef in C.
p.addTypedefs()
// Add elaborated types for C structs and unions.
p.addElaboratedTypes()
// Add enum types and enum constants for C enums.
p.addEnumTypes()
// Patch the AST to use the declared types and functions.
for _, f := range files {
astutil.Apply(f, p.walker, nil)
}
// Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated)
return p.generated, p.errors
}
// addFuncDecls adds the C function declarations found by libclang in the
// comment above the `import "C"` statement.
func (p *cgoPackage) addFuncDecls() {
names := make([]string, 0, len(p.functions))
for name := range p.functions {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fn := p.functions[name]
obj := &ast.Object{
Kind: ast.Fun,
Name: "C." + name,
}
args := make([]*ast.Field, len(fn.args))
decl := &ast.FuncDecl{
Name: &ast.Ident{
NamePos: fn.pos,
Name: "C." + name,
Obj: obj,
},
Type: &ast.FuncType{
Func: fn.pos,
Params: &ast.FieldList{
Opening: fn.pos,
List: args,
Closing: fn.pos,
},
Results: fn.results,
},
}
obj.Decl = decl
for i, arg := range fn.args {
args[i] = &ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: fn.pos,
Name: arg.name,
Obj: &ast.Object{
Kind: ast.Var,
Name: arg.name,
Decl: decl,
},
},
},
Type: arg.typeExpr,
}
}
p.generated.Decls = append(p.generated.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 (p *cgoPackage) addFuncPtrDecls() {
if len(p.functions) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.VAR,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
names := make([]string, 0, len(p.functions))
for name := range p.functions {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
fn := p.functions[name]
obj := &ast.Object{
Kind: ast.Typ,
Name: "C." + name + "$funcaddr",
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
NamePos: fn.pos,
Name: "C." + name + "$funcaddr",
Obj: obj,
}},
Type: &ast.SelectorExpr{
X: &ast.Ident{
NamePos: fn.pos,
Name: "unsafe",
},
Sel: &ast.Ident{
NamePos: fn.pos,
Name: "Pointer",
},
},
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// addConstDecls declares external C constants in the Go source.
// It adds code like the following to the AST:
//
// const (
// C.CONST_INT = 5
// C.CONST_FLOAT = 5.8
// // ...
// )
func (p *cgoPackage) addConstDecls() {
if len(p.constants) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.CONST,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
names := make([]string, 0, len(p.constants))
for name := range p.constants {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
constVal := p.constants[name]
obj := &ast.Object{
Kind: ast.Con,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
NamePos: constVal.pos,
Name: "C." + name,
Obj: obj,
}},
Values: []ast.Expr{constVal.expr},
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
}
p.generated.Decls = append(p.generated.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 (p *cgoPackage) addVarDecls() {
if len(p.globals) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.VAR,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
names := make([]string, 0, len(p.globals))
for name := range p.globals {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
global := p.globals[name]
obj := &ast.Object{
Kind: ast.Var,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
NamePos: global.pos,
Name: "C." + name,
Obj: obj,
}},
Type: global.typeExpr,
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// addTypeAliases aliases some built-in Go types with their equivalent C types.
// It adds code like the following to the AST:
//
// type (
// C.int8_t = int8
// C.int16_t = int16
// // ...
// )
func (p *cgoPackage) addTypeAliases() {
aliasKeys := make([]string, 0, len(cgoAliases))
for key := range cgoAliases {
aliasKeys = append(aliasKeys, key)
}
sort.Strings(aliasKeys)
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
for _, typeName := range aliasKeys {
goTypeName := cgoAliases[typeName]
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: token.NoPos,
Name: typeName,
Obj: obj,
},
Assign: p.generatedPos,
Type: &ast.Ident{
NamePos: token.NoPos,
Name: goTypeName,
},
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
func (p *cgoPackage) addTypedefs() {
if len(p.typedefs) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
names := make([]string, 0, len(p.typedefs))
for name := range p.typedefs {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
typedef := p.typedefs[name]
typeName := "C." + name
isAlias := true
if strings.HasPrefix(name, "_Cgo_") {
typeName = "C." + name[len("_Cgo_"):]
isAlias = false // C.short etc. should not be aliased to the equivalent Go type (not portable)
}
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: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: typedef.pos,
Name: typeName,
Obj: obj,
},
Type: typedef.typeExpr,
}
if isAlias {
typeSpec.Assign = typedef.pos
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
// foo" or "union foo" types, often used in a typedef.
//
// See also:
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
func (p *cgoPackage) addElaboratedTypes() {
if len(p.elaboratedTypes) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
names := make([]string, 0, len(p.elaboratedTypes))
for name := range p.elaboratedTypes {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
typ := p.elaboratedTypes[name]
typeName := "C." + name
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: typ.pos,
Name: typeName,
Obj: obj,
},
Type: typ.typeExpr,
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
// If this struct has bitfields, create getters for them.
for _, bitfield := range typ.bitfields {
p.createBitfieldGetter(bitfield, typeName)
p.createBitfieldSetter(bitfield, typeName)
}
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// createBitfieldGetter creates a bitfield getter function like the following:
//
// func (s *C.struct_foo) bitfield_b() byte {
// return (s.__bitfield_1 >> 5) & 0x1
// }
func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string) {
// The value to return from the getter.
// Not complete: this is just an expression to get the complete field.
var result ast.Expr = &ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
}
if bitfield.startBit != 0 {
// Shift to the right by .startBit so that fields that come before are
// shifted off.
result = &ast.BinaryExpr{
X: result,
OpPos: bitfield.pos,
Op: token.SHR,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: strconv.FormatInt(bitfield.startBit, 10),
},
}
}
if bitfield.endBit != 0 {
// Mask off the high bits so that fields that come after this field are
// masked off.
and := (uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1
result = &ast.BinaryExpr{
X: result,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(and, 16),
},
}
}
// Create the getter function.
getter := &ast.FuncDecl{
Recv: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
},
},
Type: &ast.StarExpr{
Star: bitfield.pos,
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
},
Closing: bitfield.pos,
},
Name: &ast.Ident{
NamePos: bitfield.pos,
Name: "bitfield_" + bitfield.name,
},
Type: &ast.FuncType{
Func: bitfield.pos,
Params: &ast.FieldList{
Opening: bitfield.pos,
Closing: bitfield.pos,
},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: bitfield.field.Type,
},
},
},
},
Body: &ast.BlockStmt{
Lbrace: bitfield.pos,
List: []ast.Stmt{
&ast.ReturnStmt{
Return: bitfield.pos,
Results: []ast.Expr{
result,
},
},
},
Rbrace: bitfield.pos,
},
}
p.generated.Decls = append(p.generated.Decls, getter)
}
// createBitfieldSetter creates a bitfield setter function like the following:
//
// func (s *C.struct_foo) set_bitfield_b(value byte) {
// s.__bitfield_1 = s.__bitfield_1 ^ 0x60 | ((value & 1) << 5)
// }
//
// Or the following:
//
// func (s *C.struct_foo) set_bitfield_c(value byte) {
// s.__bitfield_1 = s.__bitfield_1 & 0x3f | (value << 6)
// }
func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string) {
// The full field with all bitfields.
var field ast.Expr = &ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
}
// The value to insert into the field.
var valueToInsert ast.Expr = &ast.Ident{
NamePos: bitfield.pos,
Name: "value",
}
if bitfield.endBit != 0 {
// Make sure the value is in range with a mask.
valueToInsert = &ast.BinaryExpr{
X: valueToInsert,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint((uint64(1)<<uint64(bitfield.endBit-bitfield.startBit))-1, 16),
},
}
// Create a mask for the AND NOT operation.
mask := ((uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1) << uint64(bitfield.startBit)
// Zero the bits in the field that will soon be inserted.
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.AND_NOT,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(mask, 16),
},
}
} else { // bitfield.endBit == 0
// We don't know exactly how many high bits should be zeroed. So we do
// something different: keep the low bits with a mask and OR the new
// value with it.
mask := (uint64(1) << uint64(bitfield.startBit)) - 1
// Extract the lower bits.
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(mask, 16),
},
}
}
// Bitwise OR with the new value (after the new value has been shifted).
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.OR,
Y: &ast.BinaryExpr{
X: valueToInsert,
OpPos: bitfield.pos,
Op: token.SHL,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: strconv.FormatInt(bitfield.startBit, 10),
},
},
}
// Create the setter function.
setter := &ast.FuncDecl{
Recv: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
},
},
Type: &ast.StarExpr{
Star: bitfield.pos,
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
},
Closing: bitfield.pos,
},
Name: &ast.Ident{
NamePos: bitfield.pos,
Name: "set_bitfield_" + bitfield.name,
},
Type: &ast.FuncType{
Func: bitfield.pos,
Params: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "value",
Obj: nil,
},
},
Type: bitfield.field.Type,
},
},
Closing: bitfield.pos,
},
},
Body: &ast.BlockStmt{
Lbrace: bitfield.pos,
List: []ast.Stmt{
&ast.AssignStmt{
Lhs: []ast.Expr{
&ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
},
},
TokPos: bitfield.pos,
Tok: token.ASSIGN,
Rhs: []ast.Expr{
field,
},
},
},
Rbrace: bitfield.pos,
},
}
p.generated.Decls = append(p.generated.Decls, setter)
}
// addEnumTypes adds C enums to the AST. For example, the following C code:
//
// enum option {
// optionA,
// optionB = 5,
// };
//
// is translated to the following Go code equivalent:
//
// type C.enum_option int32
//
// The constants are treated just like macros so are inserted into the AST by
// addConstDecls.
// See also: https://en.cppreference.com/w/c/language/enum
func (p *cgoPackage) addEnumTypes() {
if len(p.enums) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
names := make([]string, 0, len(p.enums))
for name := range p.enums {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
typ := p.enums[name]
typeName := "C.enum_" + name
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: typ.pos,
Name: typeName,
Obj: obj,
},
Type: typ.typeExpr,
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// findMissingCGoNames traverses the AST and finds all C.something names. Only
// these symbols are extracted from the parsed C AST and converted to the Go
// equivalent.
func (p *cgoPackage) findMissingCGoNames(cursor *astutil.Cursor) bool {
switch node := cursor.Node().(type) {
case *ast.SelectorExpr:
x, ok := node.X.(*ast.Ident)
if !ok {
return true
}
if x.Name == "C" {
name := node.Sel.Name
if _, ok := builtinAliases[name]; ok {
name = "_Cgo_" + name
}
p.missingSymbols[name] = struct{}{}
}
}
return true
}
// 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 (p *cgoPackage) walker(cursor *astutil.Cursor) bool {
switch node := cursor.Node().(type) {
case *ast.CallExpr:
fun, ok := node.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
x, ok := fun.X.(*ast.Ident)
if !ok {
return true
}
if _, ok := p.functions[fun.Sel.Name]; ok && x.Name == "C" {
node.Fun = &ast.Ident{
NamePos: x.NamePos,
Name: "C." + fun.Sel.Name,
}
}
case *ast.SelectorExpr:
x, ok := node.X.(*ast.Ident)
if !ok {
return true
}
if x.Name == "C" {
name := "C." + node.Sel.Name
if _, ok := p.functions[node.Sel.Name]; ok {
name += "$funcaddr"
}
cursor.Replace(&ast.Ident{
NamePos: x.NamePos,
Name: name,
})
}
}
return true
}
+763
View File
@@ -0,0 +1,763 @@
package cgo
// This file parses a fragment of C with libclang and stores the result for AST
// modification. It does not touch the AST itself.
import (
"fmt"
"go/ast"
"go/scanner"
"go/token"
"path/filepath"
"strconv"
"strings"
"unsafe"
)
/*
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
#include <stdlib.h>
#include <stdint.h>
// 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);
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
*/
import "C"
// storedRefs stores references to types, used for clang_visitChildren.
var storedRefs refMap
var diagnosticSeverity = [...]string{
C.CXDiagnostic_Ignored: "ignored",
C.CXDiagnostic_Note: "note",
C.CXDiagnostic_Warning: "warning",
C.CXDiagnostic_Error: "error",
C.CXDiagnostic_Fatal: "fatal",
}
func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) {
index := C.clang_createIndex(0, 0)
defer C.clang_disposeIndex(index)
// pretend to be a .c file
filenameC := C.CString(posFilename + "!cgo.c")
defer C.free(unsafe.Pointer(filenameC))
// fix up error locations
fragment = fmt.Sprintf("# %d %#v\n", posLine+1, posFilename) + fragment
fragmentC := C.CString(fragment)
defer C.free(unsafe.Pointer(fragmentC))
unsavedFile := C.struct_CXUnsavedFile{
Filename: filenameC,
Length: C.ulong(len(fragment)),
Contents: fragmentC,
}
// 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 << 16]*C.char)(cmdargsC)
for i, cflag := range cflags {
s := C.CString(cflag)
cmdargs[i] = s
defer C.free(unsafe.Pointer(s))
}
var unit C.CXTranslationUnit
errCode := C.clang_parseTranslationUnit2(
index,
filenameC,
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
&unsavedFile, 1, // unsaved files
C.CXTranslationUnit_DetailedPreprocessingRecord,
&unit)
if errCode != 0 {
panic("loader: failed to parse source with libclang")
}
defer C.clang_disposeTranslationUnit(unit)
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
addDiagnostic := func(diagnostic C.CXDiagnostic) {
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
location := C.clang_getDiagnosticLocation(diagnostic)
var libclangFilename C.CXString
var line C.unsigned
var column C.unsigned
C.clang_getPresumedLocation(location, &libclangFilename, &line, &column)
filename := getString(libclangFilename)
if filepath.IsAbs(filename) {
// Relative paths for readability, like other Go parser errors.
relpath, err := filepath.Rel(p.dir, filename)
if err == nil {
filename = relpath
}
}
p.errors = append(p.errors, &scanner.Error{
Pos: token.Position{
Filename: filename,
Offset: 0, // not provided by clang_getPresumedLocation
Line: int(line),
Column: int(column),
},
Msg: severity + ": " + spelling,
})
}
for i := 0; i < numDiagnostics; i++ {
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
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
}
ref := storedRefs.Put(p)
defer storedRefs.Remove(ref)
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
}
//export tinygo_clang_globals_visitor
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
kind := C.tinygo_clang_getCursorKind(c)
pos := p.getCursorPosition(c)
switch kind {
case C.CXCursor_FunctionDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
cursorType := C.tinygo_clang_getCursorType(c)
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
return C.CXChildVisit_Continue // not supported
}
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
fn := &functionInfo{
pos: pos,
}
p.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))
if argName == "" {
argName = "$" + strconv.Itoa(i)
}
fn.args = append(fn.args, paramInfo{
name: argName,
typeExpr: p.makeASTType(argType, pos),
})
}
resultType := C.tinygo_clang_getCursorResultType(c)
if resultType.kind != C.CXType_Void {
fn.results = &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: p.makeASTType(resultType, pos),
},
},
}
}
case C.CXCursor_StructDecl:
typ := C.tinygo_clang_getCursorType(c)
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := p.missingSymbols["struct_"+name]; !required {
return C.CXChildVisit_Continue
}
p.makeASTType(typ, pos)
case C.CXCursor_TypedefDecl:
typedefType := C.tinygo_clang_getCursorType(c)
name := getString(C.clang_getTypedefName(typedefType))
if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
p.makeASTType(typedefType, pos)
case C.CXCursor_VarDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
cursorType := C.tinygo_clang_getCursorType(c)
p.globals[name] = globalInfo{
typeExpr: p.makeASTType(cursorType, pos),
pos: pos,
}
case C.CXCursor_MacroDefinition:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
sourceRange := C.tinygo_clang_getCursorExtent(c)
start := C.clang_getRangeStart(sourceRange)
end := C.clang_getRangeEnd(sourceRange)
var file, endFile C.CXFile
var startOffset, endOffset C.unsigned
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
if file == nil {
panic("could not find file where macro is defined")
}
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
if file != endFile {
panic("expected start and end location of a #define to be in the same file")
}
if startOffset > endOffset {
panic("startOffset > endOffset")
}
// read file contents and extract the relevant byte range
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
if endOffset >= C.uint(size) {
panic("endOffset lies after end of file")
}
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
if !strings.HasPrefix(source, name) {
panic(fmt.Sprintf("expected #define value to start with %#v, got %#v", name, source))
}
value := strings.TrimSpace(source[len(name):])
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
value = strings.TrimSpace(value[1 : len(value)-1])
}
if len(value) == 0 {
// Pretend it doesn't exist at all.
return C.CXChildVisit_Continue
}
// For information about integer literals:
// https://en.cppreference.com/w/cpp/language/integer_literal
if value[0] == '"' {
// string constant
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.STRING, value}, pos}
return C.CXChildVisit_Continue
}
if value[0] == '\'' {
// char constant
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.CHAR, value}, pos}
return C.CXChildVisit_Continue
}
// assume it's a number (int or float)
value = strings.Replace(value, "'", "", -1) // remove ' chars
value = strings.TrimRight(value, "lu") // remove llu suffixes etc.
// find the first non-number
nonnum := byte(0)
for i := 0; i < len(value); i++ {
if value[i] < '0' || value[i] > '9' {
nonnum = value[i]
break
}
}
// determine number type based on the first non-number
switch nonnum {
case 0:
// no non-number found, must be an integer
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
case 'x', 'X':
// hex integer constant
// TODO: may also be a floating point number per C++17.
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
case '.', 'e':
// float constant
value = strings.TrimRight(value, "fFlL")
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.FLOAT, value}, pos}
default:
// unknown type, ignore
}
}
return C.CXChildVisit_Continue
}
func getString(clangString C.CXString) (s string) {
rawString := C.clang_getCString(clangString)
s = C.GoString(rawString)
C.clang_disposeString(clangString)
return
}
// getCursorPosition returns a usable token.Pos from a libclang cursor. If the
// file for this cursor has not been seen before, it is read from libclang
// (which already has the file in memory) and added to the token.FileSet.
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
location := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile
var line C.unsigned
var column C.unsigned
var offset C.unsigned
C.clang_getExpansionLocation(location, &file, &line, &column, &offset)
if line == 0 || file == nil {
// Invalid token.
return token.NoPos
}
filename := getString(C.clang_getFileName(file))
if _, ok := p.tokenFiles[filename]; !ok {
// File has not been seen before in this package, add line information
// now by reading the file from libclang.
tu := C.tinygo_clang_Cursor_getTranslationUnit(cursor)
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
lines := []int{0}
for i := 0; i < len(source)-1; i++ {
if source[i] == '\n' {
lines = append(lines, i+1)
}
}
f := p.fset.AddFile(filename, -1, int(size))
f.SetLines(lines)
p.tokenFiles[filename] = f
}
return p.tokenFiles[filename].Pos(int(offset))
}
// 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 (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
var typeName string
switch typ.kind {
case C.CXType_Char_S, C.CXType_Char_U:
typeName = "C.char"
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:
pointeeType := C.clang_getPointeeType(typ)
if pointeeType.kind == C.CXType_Void {
// void* type is translated to Go as unsafe.Pointer
return &ast.SelectorExpr{
X: &ast.Ident{
NamePos: pos,
Name: "unsafe",
},
Sel: &ast.Ident{
NamePos: pos,
Name: "Pointer",
},
}
}
return &ast.StarExpr{
Star: pos,
X: p.makeASTType(pointeeType, pos),
}
case C.CXType_ConstantArray:
return &ast.ArrayType{
Lbrack: pos,
Len: &ast.BasicLit{
ValuePos: pos,
Kind: token.INT,
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
},
Elt: p.makeASTType(C.clang_getElementType(typ), pos),
}
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: pos,
Len: &ast.BasicLit{
ValuePos: pos,
Kind: token.INT,
Value: "0",
},
Elt: &ast.Ident{
NamePos: pos,
Name: "byte",
},
}
case C.CXType_Typedef:
name := getString(C.clang_getTypedefName(typ))
if _, ok := p.typedefs[name]; !ok {
p.typedefs[name] = nil // don't recurse
c := C.tinygo_clang_getTypeDeclaration(typ)
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
expr := p.makeASTType(underlyingType, pos)
if strings.HasPrefix(name, "_Cgo_") {
expr := expr.(*ast.Ident)
typeSize := C.clang_Type_getSizeOf(underlyingType)
switch expr.Name {
case "C.char":
if typeSize != 1 {
// This happens for some very special purpose architectures
// (DSPs etc.) that are not currently targeted.
// https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
panic("unknown char width")
}
switch underlyingType.kind {
case C.CXType_Char_S:
expr.Name = "int8"
case C.CXType_Char_U:
expr.Name = "uint8"
}
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"
}
}
}
p.typedefs[name] = &typedefInfo{
typeExpr: expr,
pos: pos,
}
}
return &ast.Ident{
NamePos: pos,
Name: "C." + name,
}
case C.CXType_Elaborated:
underlying := C.clang_Type_getNamedType(typ)
switch underlying.kind {
case C.CXType_Record:
return p.makeASTType(underlying, pos)
case C.CXType_Enum:
return p.makeASTType(underlying, pos)
default:
panic("unknown elaborated type")
}
case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
if name == "" {
// Anonymous record, probably inside a typedef.
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
if bitfieldList != nil {
// This struct has bitfields, so we have to declare it as a
// named type (for bitfield getters/setters to work).
p.anonStructNum++
cgoName := "struct_" + strconv.Itoa(p.anonStructNum)
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: typeExpr,
pos: pos,
bitfields: bitfieldList,
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
}
return typeExpr
} else {
var cgoName string
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
cgoName = "struct_" + name
case C.CXCursor_UnionDecl:
cgoName = "union_" + name
default:
panic("unknown record declaration")
}
if _, ok := p.elaboratedTypes[cgoName]; !ok {
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: typeExpr,
pos: pos,
bitfields: bitfieldList,
}
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
}
case C.CXType_Enum:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
underlying := C.tinygo_clang_getEnumDeclIntegerType(cursor)
if name == "" {
// anonymous enum
ref := storedRefs.Put(p)
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
return p.makeASTType(underlying, pos)
} else {
// named enum
if _, ok := p.enums[name]; !ok {
ref := storedRefs.Put(p)
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
p.enums[name] = enumInfo{
typeExpr: p.makeASTType(underlying, pos),
pos: pos,
}
}
return &ast.Ident{
NamePos: pos,
Name: "C.enum_" + name,
}
}
}
if typeName == "" {
// Report this as an error.
spelling := getString(C.clang_getTypeSpelling(typ))
p.errors = append(p.errors, scanner.Error{
Pos: p.fset.PositionFor(pos, true),
Msg: fmt.Sprintf("unknown C type: %v (libclang type kind %d)", spelling, typ.kind),
})
// Fallback, probably incorrect but at least the error points to an odd
// type name.
typeName = "C." + spelling
}
return &ast.Ident{
NamePos: pos,
Name: typeName,
}
}
// makeASTRecordType parses a C record (struct or union) and translates it into
// a Go struct type. Unions are implemented by setting the first field to a
// zero-lengt "C union" field, which cannot be written in Go directly.
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast.StructType, []bitfieldInfo) {
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
}
var bitfieldList []bitfieldInfo
inBitfield := false
bitfieldNum := 0
ref := storedRefs.Put(struct {
fieldList *ast.FieldList
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
}{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList})
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
return &ast.StructType{
Struct: pos,
Fields: fieldList,
}, bitfieldList
case C.CXCursor_UnionDecl:
if bitfieldList != nil {
// This is valid C... but please don't do this.
p.errors = append(p.errors, scanner.Error{
Pos: p.fset.PositionFor(pos, true),
Msg: fmt.Sprintf("bitfield in a union is not supported"),
})
}
if len(fieldList.List) > 1 {
// Insert a special field at the front (of zero width) as a
// marker that this is struct is actually a union. This is done
// by giving the field a name that cannot be expressed directly
// in Go.
// Other parts of the compiler look at the first element in a
// struct (of size > 2) to know whether this is a union.
// Note that we don't have to insert it for single-element
// unions as they're basically equivalent to a struct.
unionMarker := &ast.Field{
Type: &ast.StructType{
Struct: pos,
},
}
unionMarker.Names = []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: "C union",
Obj: &ast.Object{
Kind: ast.Var,
Name: "C union",
Decl: unionMarker,
},
},
}
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
}
return &ast.StructType{
Struct: pos,
Fields: fieldList,
}, bitfieldList
default:
panic("unknown record declaration")
}
}
//export tinygo_clang_struct_visitor
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
fieldList *ast.FieldList
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
})
fieldList := passed.fieldList
p := passed.pkg
inBitfield := passed.inBitfield
bitfieldNum := passed.bitfieldNum
bitfieldList := passed.bitfieldList
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
panic("expected field inside cursor")
}
name := getString(C.tinygo_clang_getCursorSpelling(c))
if name == "" {
// Assume this is a bitfield of 0 bits.
// Warning: this is not necessarily true!
return C.CXChildVisit_Continue
}
typ := C.tinygo_clang_getCursorType(c)
pos := p.getCursorPosition(c)
field := &ast.Field{
Type: p.makeASTType(typ, p.getCursorPosition(c)),
}
offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name)))
alignOf := int64(C.clang_Type_getAlignOf(typ) * 8)
bitfieldOffset := offsetof % alignOf
if bitfieldOffset != 0 {
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
panic("expected a bitfield")
}
if !*inBitfield {
*bitfieldNum++
}
bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum)
prevField := fieldList.List[len(fieldList.List)-1]
if !*inBitfield {
// The previous element also was a bitfield, but wasn't noticed
// then. Add it now.
*inBitfield = true
*bitfieldList = append(*bitfieldList, bitfieldInfo{
field: prevField,
name: prevField.Names[0].Name,
startBit: 0,
pos: prevField.Names[0].NamePos,
})
prevField.Names[0].Name = bitfieldName
prevField.Names[0].Obj.Name = bitfieldName
}
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
prevBitfield.endBit = bitfieldOffset
*bitfieldList = append(*bitfieldList, bitfieldInfo{
field: prevField,
name: name,
startBit: bitfieldOffset,
pos: pos,
})
return C.CXChildVisit_Continue
}
*inBitfield = false
field.Names = []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: name,
Obj: &ast.Object{
Kind: ast.Var,
Name: name,
Decl: field,
},
},
}
fieldList.List = append(fieldList.List, field)
return C.CXChildVisit_Continue
}
//export tinygo_clang_enum_visitor
func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
name := getString(C.tinygo_clang_getCursorSpelling(c))
pos := p.getCursorPosition(c)
value := C.tinygo_clang_getEnumConstantDeclValue(c)
p.constants[name] = constantInfo{
expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)},
pos: pos,
}
return C.CXChildVisit_Continue
}
@@ -1,11 +1,11 @@
// +build !byollvm
package loader
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-7/include
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
#cgo linux LDFLAGS: -L/usr/lib/llvm-8/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm/lib -lclang -lffi
*/
import "C"
+70
View File
@@ -0,0 +1,70 @@
// 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);
}
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
return clang_getCursorLocation(c);
}
CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
return clang_getCursorExtent(c);
}
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
return clang_Cursor_getTranslationUnit(c);
}
long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) {
return clang_getEnumConstantDeclValue(c);
}
CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
return clang_getEnumDeclIntegerType(c);
}
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
return clang_Cursor_isBitField(c);
}
+46
View File
@@ -0,0 +1,46 @@
package cgo
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)
}
+45
View File
@@ -0,0 +1,45 @@
package main
import (
"errors"
"os"
"os/exec"
"runtime"
"strings"
)
// Commands used by the compilation process might have different file names
// across operating systems and distributions.
var commands = map[string][]string{
"clang": {"clang-8"},
"ld.lld": {"ld.lld-8", "ld.lld"},
"wasm-ld": {"wasm-ld-8", "wasm-ld"},
}
func init() {
// Add the path to a Homebrew-installed LLVM 8 for ease of use (no need to
// manually set $PATH).
if runtime.GOOS == "darwin" {
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm/bin/clang-8")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm/bin/wasm-ld")
}
}
func execCommand(cmdNames []string, args ...string) error {
for _, cmdName := range cmdNames {
cmd := exec.Command(cmdName, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.Error); ok && err.Err == exec.ErrNotFound {
// this command was not found, try the next
continue
}
return err
}
return nil
}
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
}
+154
View File
@@ -0,0 +1,154 @@
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 we need to emit this check at all.
if !ptr.IsAGlobalValue().IsNil() {
return
}
// 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)
}
+42 -5
View File
@@ -38,7 +38,7 @@ func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llv
}
// Expand an argument type to a list that can be used in a function call
// paramter list.
// parameter list.
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
switch t.TypeKind() {
case llvm.StructTypeKind:
@@ -55,6 +55,25 @@ func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
}
}
// Expand an argument type to a list of offsets from the start of the object.
// Used together with expandFormalParam to get the offset of each value from the
// start of the non-expanded value.
func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := c.flattenAggregateTypeOffsets(t)
if len(fields) <= MaxFieldsPerParam {
return fields
} else {
// failed to lower
return []uint64{0}
}
default:
// TODO: split small arrays
return []uint64{0}
}
}
// Equivalent of expandFormalParamType for parameter values.
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() {
@@ -92,6 +111,27 @@ func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type {
}
}
// Return the offsets from the start of the object if this object type were
// flattened like in flattenAggregate. Used together with flattenAggregate to
// know the start indices of each value in the non-flattened object.
func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := make([]uint64, 0, t.StructElementTypesCount())
for fieldIndex, field := range t.StructElementTypes() {
suboffsets := c.flattenAggregateTypeOffsets(field)
offset := c.targetData.ElementOffset(t, fieldIndex)
for i := range suboffsets {
suboffsets[i] += offset
}
fields = append(fields, suboffsets...)
}
return fields
default:
return []uint64{0}
}
}
// Break down a struct into its elementary types for argument passing. The value
// equivalent of flattenAggregateType
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
@@ -123,10 +163,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
switch t.TypeKind() {
case llvm.StructTypeKind:
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
value, err := c.getZeroValue(t)
if err != nil {
panic("could not get zero value of struct: " + err.Error())
}
value := c.getZeroValue(t)
for i, subtyp := range t.StructElementTypes() {
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
fields = remaining
+201 -60
View File
@@ -4,6 +4,7 @@ package compiler
// or pseudo-operations that are lowered during goroutine lowering.
import (
"fmt"
"go/types"
"golang.org/x/tools/go/ssa"
@@ -12,86 +13,226 @@ import (
// 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)
chanType := c.getLLVMType(expr.Type())
size := c.targetData.TypeAllocSize(chanType.ElementType())
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")
ptr = c.builder.CreateBitCast(ptr, chanType, "chan")
// Set the elementSize field
elementSizePtr := c.builder.CreateGEP(ptr, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "")
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
if elementSize > 0xffff {
return ptr, c.makeError(expr.Pos(), fmt.Sprintf("element size is %d bytes, which is bigger than the maximum of %d bytes", elementSize, 0xffff))
}
elementSizeValue := llvm.ConstInt(c.ctx.Int16Type(), elementSize, false)
c.builder.CreateStore(elementSizeValue, elementSizePtr)
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")
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
ch := c.getValue(frame, instr.Chan)
chanValue := c.getValue(frame, instr.X)
// store value-to-send
valueType := c.getLLVMType(instr.X.Type())
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(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
// Do the send.
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
// End the lifetime of the alloca.
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
// 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}, "")
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
ch := c.getValue(frame, unop.X)
// Allocate memory to receive into.
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
// Do the receive.
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received")
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
commaOk := c.builder.CreateLoad(valueOk, "chan.comma-ok")
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
return tuple, nil
return tuple
} else {
return received, nil
return received
}
}
// 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
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
ch := c.getValue(frame, param)
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
}
// emitSelect emits all IR necessary for a select statements. That's a
// non-trivial amount of code because select is very complex to implement.
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := c.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
c.createRuntimeCall("deadlockStub", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:
// select {
// default:
// }
retval := llvm.Undef(llvmType)
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
return retval // {-1, false}
}
}
// This code create a (stack-allocated) slice containing all the select
// cases and then calls runtime.chanSelect to perform the actual select
// statement.
// Simple selects (blocking and with just one case) are already transformed
// into regular chan operations during SSA construction so we don't have to
// optimize such small selects.
// Go through all the cases. Create the selectStates slice and and
// determine the receive buffer size and alignment.
recvbufSize := uint64(0)
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := c.getValue(frame, state.Chan)
selectState := c.getZeroValue(chanSelectStateType)
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
switch state.Dir {
case types.RecvOnly:
// Make sure the receive buffer is big enough and has the correct alignment.
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
recvbufSize = size
}
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align
}
hasReceives = true
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
sendValue := c.getValue(frame, state.Send)
alloca := c.createEntryBlockAlloca(sendValue.Type(), "select.send.value")
c.builder.CreateStore(sendValue, alloca)
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
default:
panic("unreachable")
}
selectStates = append(selectStates, selectState)
}
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(c.i8ptrType)
if hasReceives {
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca := c.builder.CreateAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.recvbuf")
}
// Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
statesAlloca := c.builder.CreateAlloca(statesAllocaType, "select.states.alloca")
for i, state := range selectStates {
// Set each slice element to the appropriate channel.
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}, "")
c.builder.CreateStore(state, gep)
}
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.states")
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
// Convert the 'blocking' flag on this select into a LLVM value.
blockingInt := uint64(0)
if expr.Blocking {
blockingInt = 1
}
blockingValue := llvm.ConstInt(c.ctx.Int1Type(), blockingInt, false)
// Do the select in the runtime.
results := c.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
blockingValue,
}, "")
// The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract
// instruction will treat a *ssa.Select specially and load it there inline.
// Store the receive alloca in a sidetable until we hit this extract
// instruction.
if frame.selectRecvBuf == nil {
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
}
frame.selectRecvBuf[expr] = recvbuf
return results
}
// getChanSelectResult returns the special values from a *ssa.Extract expression
// when extracting a value from a select statement (*ssa.Select). Because
// *ssa.Select cannot load all values in advance, it does this later in the
// *ssa.Extract expression.
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
if expr.Index == 0 {
// index
value := c.getValue(frame, expr.Tuple)
index := c.builder.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
index = c.builder.CreateSExt(index, c.intType, "")
}
return index
} else if expr.Index == 1 {
// comma-ok
value := c.getValue(frame, expr.Tuple)
return c.builder.CreateExtractValue(value, expr.Index, "")
} else {
// Select statements are (index, ok, ...) where ... is a number of
// received values, depending on how many receive statements there
// are. They are all combined into one alloca (because only one
// receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it.
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
return c.builder.CreateLoad(ptr, "")
}
}
+916 -1060
View File
File diff suppressed because it is too large Load Diff
+26 -57
View File
@@ -29,14 +29,14 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
frame.deferClosureFuncs = make(map[*ir.Function]int)
// Create defer list pointer.
deferType := llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
}
// emitDefer emits a single defer instruction, to be run when this function
// returns.
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
// The pointer to the previous defer struct, which we will replace to
// make a linked list.
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
@@ -56,18 +56,12 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by the call parameters).
itf, err := c.parseExpr(frame, instr.Call.Value) // interface
if err != nil {
return err
}
itf := c.getValue(frame, instr.Call.Value) // interface
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, receiverValue}
valueTypes = append(valueTypes, c.i8ptrType)
for _, arg := range instr.Call.Args {
val, err := c.parseExpr(frame, arg)
if err != nil {
return err
}
val := c.getValue(frame, arg)
values = append(values, val)
valueTypes = append(valueTypes, val.Type())
}
@@ -86,10 +80,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
// runtime._defer fields).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam, err := c.parseExpr(frame, param)
if err != nil {
return err
}
llvmParam := c.getValue(frame, param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -101,10 +92,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
// pointer.
// TODO: ignore this closure entirely and put pointers to the free
// variables directly in the defer struct, avoiding a memory allocation.
closure, err := c.parseExpr(frame, instr.Call.Value)
if err != nil {
return err
}
closure := c.getValue(frame, instr.Call.Value)
context := c.builder.CreateExtractValue(closure, 0, "")
// Get the callback number.
@@ -120,10 +108,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
// context pointer).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam, err := c.parseExpr(frame, param)
if err != nil {
return err
}
llvmParam := c.getValue(frame, param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -131,15 +116,13 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
valueTypes = append(valueTypes, context.Type())
} else {
return c.makeError(instr.Pos(), "todo: defer on uncommon function call type")
c.addError(instr.Pos(), "todo: defer on uncommon function call type")
return
}
// Make a struct out of the collected values to put in the defer frame.
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFrame, err := c.getZeroValue(deferFrameType)
if err != nil {
return err
}
deferFrame := c.getZeroValue(deferFrameType)
for i, value := range values {
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
}
@@ -147,15 +130,17 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
// Put this struct in an alloca.
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
c.builder.CreateStore(deferFrame, alloca)
if c.needsStackObjects() {
c.trackPointer(alloca)
}
// Push it on top of the linked list by replacing deferPtr.
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
c.builder.CreateStore(allocaCast, frame.deferPtr)
return nil
}
// emitRunDefers emits code to run all deferred functions.
func (c *Compiler) emitRunDefers(frame *Frame) error {
func (c *Compiler) emitRunDefers(frame *Frame) {
// Add a loop like the following:
// for stack != nil {
// _stack := stack
@@ -190,13 +175,13 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// stack = stack.next
// switch stack.callback {
c.builder.SetInsertPointAtEnd(loop)
nextStackGEP := c.builder.CreateGEP(deferData, []llvm.Value{
nextStackGEP := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
}, "stack.next.gep")
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
c.builder.CreateStore(nextStack, frame.deferPtr)
gep := c.builder.CreateGEP(deferData, []llvm.Value{
gep := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
}, "callback.gep")
@@ -218,13 +203,9 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
}
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType}
for _, arg := range callback.Args {
llvmType, err := c.getLLVMType(arg.Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
}
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
@@ -233,7 +214,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
@@ -246,23 +227,16 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
fnPtr, _, err := c.getInvokeCall(frame, callback)
if err != nil {
return err
}
fnPtr, _ := c.getInvokeCall(frame, callback)
c.createCall(fnPtr, forwardParams, "")
case *ir.Function:
// Direct call.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
for _, param := range callback.Params {
llvmType, err := c.getLLVMType(param.Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
}
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
@@ -271,7 +245,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
for i := range callback.Params {
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
@@ -289,14 +263,10 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
llvmType, err := c.getLLVMType(params.At(i).Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
}
valueTypes = append(valueTypes, c.i8ptrType) // closure
deferFrameType := c.ctx.StructType(valueTypes, false)
@@ -306,7 +276,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := c.builder.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
@@ -334,5 +304,4 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// End of loop.
c.builder.SetInsertPointAtEnd(end)
return nil
}
+4
View File
@@ -12,3 +12,7 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
Msg: msg,
}
}
func (c *Compiler) addError(pos token.Pos, msg string) {
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
}
+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.getLLVMRuntimeType("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()
}
}
}
}
+202
View File
@@ -0,0 +1,202 @@
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 {
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.getLLVMRuntimeType("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 := c.getFuncType(sig)
funcValue := llvm.Undef(funcValueType)
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
return funcValue
}
// getFuncSignature returns a global for identification of a particular function
// signature. It is used in runtime.funcValueWithSignature and in calls to
// getFuncPtr.
func (c *Compiler) getFuncSignature(sig *types.Signature) llvm.Value {
typeCodeName := getTypeCodeName(sig)
sigGlobalName := "reflect/types.type:" + typeCodeName
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetInitializer(llvm.Undef(c.ctx.Int8Type()))
sigGlobal.SetGlobalConstant(true)
sigGlobal.SetLinkage(llvm.InternalLinkage)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation.
func (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) {
context = c.builder.CreateExtractValue(funcValue, 0, "")
switch c.funcImplementation() {
case funcValueDoubleword:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch:
llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getFuncSignature(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
default:
panic("unimplemented func value variant")
}
return
}
// getFuncType returns the type of a func value given a signature.
func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
switch c.funcImplementation() {
case funcValueDoubleword:
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case funcValueSwitch:
return c.getLLVMRuntimeType("funcValue")
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 {
// Get the return type.
var returnType llvm.Type
switch typ.Results().Len() {
case 0:
// No return values.
returnType = c.ctx.VoidType()
case 1:
// Just one return value.
returnType = c.getLLVMType(typ.Results().At(0).Type())
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++ {
members[i] = c.getLLVMType(typ.Results().At(i).Type())
}
returnType = c.ctx.StructType(members, false)
}
// Get the parameter types.
var paramTypes []llvm.Type
if typ.Recv() != nil {
recv := c.getLLVMType(typ.Recv().Type())
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 := c.getLLVMType(typ.Params().At(i).Type())
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)
}
// 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, len(expr.Bindings))
for i, binding := range expr.Bindings {
// The context stores the bound variables.
llvmBoundVar := c.getValue(frame, binding)
boundVars[i] = llvmBoundVar
}
// Store the bound variables in a single object, allocating it on the heap
// if necessary.
context := c.emitPointerPack(boundVars)
// Create the closure.
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
}
+471
View File
@@ -0,0 +1,471 @@
package compiler
// This file provides IR transformations necessary for precise and portable
// garbage collectors.
import (
"go/token"
"math/big"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// needsStackObjects returns true if the compiler should insert stack objects
// that can be traced by the garbage collector.
func (c *Compiler) needsStackObjects() bool {
if c.selectGC() != "conservative" {
return false
}
for _, tag := range c.BuildTags {
if tag == "cortexm" || tag == "tinygo.riscv" {
return false
}
}
return true
}
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
// one of the expressions that need this.
func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
// There are uses of this expression, Make sure the pointers
// are tracked during GC.
switch expr := expr.(type) {
case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap:
// These values are always of pointer type in IR.
c.trackPointer(value)
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
if !value.IsNil() {
c.trackValue(value)
}
case *ssa.Select:
if alloca, ok := frame.selectRecvBuf[expr]; ok {
if alloca.IsAUndefValue().IsNil() {
c.trackPointer(alloca)
}
}
case *ssa.UnOp:
switch expr.Op {
case token.MUL:
// Pointer dereference.
c.trackValue(value)
case token.ARROW:
// Channel receive operator.
// It's not necessary to look at commaOk here, because in that
// case it's just an aggregate and trackValue will extract the
// pointer in there (if there is one).
c.trackValue(value)
}
}
}
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
// individual pointers
func (c *Compiler) trackValue(value llvm.Value) {
typ := value.Type()
switch typ.TypeKind() {
case llvm.PointerTypeKind:
c.trackPointer(value)
case llvm.StructTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.StructElementTypesCount()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
case llvm.ArrayTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
}
}
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
// first if needed. The input value must be of LLVM pointer type.
func (c *Compiler) trackPointer(value llvm.Value) {
if value.Type() != c.i8ptrType {
value = c.builder.CreateBitCast(value, c.i8ptrType, "")
}
c.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
}
// typeHasPointers returns whether this type is a pointer or contains pointers.
// If the type is an aggregate type, it will check whether there is a pointer
// inside.
func typeHasPointers(t llvm.Type) bool {
switch t.TypeKind() {
case llvm.PointerTypeKind:
return true
case llvm.StructTypeKind:
for _, subType := range t.StructElementTypes() {
if typeHasPointers(subType) {
return true
}
}
return false
case llvm.ArrayTypeKind:
if typeHasPointers(t.ElementType()) {
return true
}
return false
default:
return false
}
}
// makeGCStackSlots converts all calls to runtime.trackPointer to explicit
// stores to stack slots that are scannable by the GC.
func (c *Compiler) makeGCStackSlots() bool {
// Check whether there are allocations at all.
alloc := c.mod.NamedFunction("runtime.alloc")
if alloc.IsNil() {
// Nothing to. Make sure all remaining bits and pieces for stack
// chains are neutralized.
for _, call := range getUses(c.mod.NamedFunction("runtime.trackPointer")) {
call.EraseFromParentAsInstruction()
}
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
if !stackChainStart.IsNil() {
stackChainStart.SetInitializer(c.getZeroValue(stackChainStart.Type().ElementType()))
stackChainStart.SetGlobalConstant(true)
}
}
trackPointer := c.mod.NamedFunction("runtime.trackPointer")
if trackPointer.IsNil() || trackPointer.FirstUse().IsNil() {
return false // nothing to do
}
// Look at *all* functions to see whether they are free of function pointer
// calls.
// This takes less than 5ms for ~100kB of WebAssembly but would perhaps be
// faster when written in C++ (to avoid the CGo overhead).
funcsWithFPCall := map[llvm.Value]struct{}{}
n := 0
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
n++
if _, ok := funcsWithFPCall[fn]; ok {
continue // already found
}
done := false
for bb := fn.FirstBasicBlock(); !bb.IsNil() && !done; bb = llvm.NextBasicBlock(bb) {
for call := bb.FirstInstruction(); !call.IsNil() && !done; call = llvm.NextInstruction(call) {
if call.IsACallInst().IsNil() {
continue // only looking at calls
}
called := call.CalledValue()
if !called.IsAFunction().IsNil() {
continue // only looking for function pointers
}
funcsWithFPCall[fn] = struct{}{}
markParentFunctions(funcsWithFPCall, fn)
done = true
}
}
}
// Determine which functions need stack objects. Many leaf functions don't
// need it: it only causes overhead for them.
// Actually, in one test it was only able to eliminate stack object from 12%
// of functions that had a call to runtime.trackPointer (8 out of 68
// functions), so this optimization is not as big as it may seem.
allocatingFunctions := map[llvm.Value]struct{}{} // set of allocating functions
// Work from runtime.alloc and trace all parents to check which functions do
// a heap allocation (and thus which functions do not).
markParentFunctions(allocatingFunctions, alloc)
// Also trace all functions that call a function pointer.
for fn := range funcsWithFPCall {
// Assume that functions that call a function pointer do a heap
// allocation as a conservative guess because the called function might
// do a heap allocation.
allocatingFunctions[fn] = struct{}{}
markParentFunctions(allocatingFunctions, fn)
}
// Collect some variables used below in the loop.
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
if stackChainStart.IsNil() {
panic("stack chain start not found!")
}
stackChainStartType := stackChainStart.Type().ElementType()
stackChainStart.SetInitializer(c.getZeroValue(stackChainStartType))
// Iterate until runtime.trackPointer has no uses left.
for use := trackPointer.FirstUse(); !use.IsNil(); use = trackPointer.FirstUse() {
// Pick the first use of runtime.trackPointer.
call := use.User()
if call.IsACallInst().IsNil() {
panic("expected runtime.trackPointer use to be a call")
}
// Pick the parent function.
fn := call.InstructionParent().Parent()
if _, ok := allocatingFunctions[fn]; !ok {
// This function nor any of the functions it calls (recursively)
// allocate anything from the heap, so it will not trigger a garbage
// collection cycle. Thus, it does not need to track local pointer
// values.
// This is a useful optimization but not as big as you might guess,
// as described above (it avoids stack objects for ~12% of
// functions).
call.EraseFromParentAsInstruction()
continue
}
// Find all calls to runtime.trackPointer in this function.
var calls []llvm.Value
var returns []llvm.Value
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
switch inst.InstructionOpcode() {
case llvm.Call:
if inst.CalledValue() == trackPointer {
calls = append(calls, inst)
}
case llvm.Ret:
returns = append(returns, inst)
}
}
}
// Determine what to do with each call.
var allocas, pointers []llvm.Value
for _, call := range calls {
ptr := call.Operand(0)
call.EraseFromParentAsInstruction()
if ptr.IsAInstruction().IsNil() {
continue
}
// Some trivial optimizations.
if ptr.IsAInstruction().IsNil() {
continue
}
switch ptr.InstructionOpcode() {
case llvm.PHI, llvm.GetElementPtr:
// These values do not create new values: the values already
// existed locally in this function so must have been tracked
// already.
continue
case llvm.ExtractValue, llvm.BitCast:
// These instructions do not create new values, but their
// original value may not be tracked. So keep tracking them for
// now.
// With more analysis, it should be possible to optimize a
// significant chunk of these away.
case llvm.Call, llvm.Load, llvm.IntToPtr:
// These create new values so must be stored locally. But
// perhaps some of these can be fused when they actually refer
// to the same value.
default:
// Ambiguous. These instructions are uncommon, but perhaps could
// be optimized if needed.
}
if !ptr.IsAAllocaInst().IsNil() {
if typeHasPointers(ptr.Type().ElementType()) {
allocas = append(allocas, ptr)
}
} else {
pointers = append(pointers, ptr)
}
}
if len(allocas) == 0 && len(pointers) == 0 {
// This function does not need to keep track of stack pointers.
continue
}
// Determine the type of the required stack slot.
fields := []llvm.Type{
stackChainStartType, // Pointer to parent frame.
c.uintptrType, // Number of elements in this frame.
}
for _, alloca := range allocas {
fields = append(fields, alloca.Type().ElementType())
}
for _, ptr := range pointers {
fields = append(fields, ptr.Type())
}
stackObjectType := c.ctx.StructType(fields, false)
// Create the stack object at the function entry.
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
stackObject := c.builder.CreateAlloca(stackObjectType, "gc.stackobject")
initialStackObject := c.getZeroValue(stackObjectType)
numSlots := (c.targetData.TypeAllocSize(stackObjectType) - c.targetData.TypeAllocSize(c.i8ptrType)*2) / uint64(c.targetData.ABITypeAlignment(c.uintptrType))
numSlotsValue := llvm.ConstInt(c.uintptrType, numSlots, false)
initialStackObject = llvm.ConstInsertValue(initialStackObject, numSlotsValue, []uint32{1})
c.builder.CreateStore(initialStackObject, stackObject)
// Update stack start.
parent := c.builder.CreateLoad(stackChainStart, "")
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "")
c.builder.CreateStore(parent, gep)
stackObjectCast := c.builder.CreateBitCast(stackObject, stackChainStartType, "")
c.builder.CreateStore(stackObjectCast, stackChainStart)
// Replace all independent allocas with GEPs in the stack object.
for i, alloca := range allocas {
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+i), false),
}, "")
alloca.ReplaceAllUsesWith(gep)
alloca.EraseFromParentAsInstruction()
}
// Do a store to the stack object after each new pointer that is created.
for i, ptr := range pointers {
c.builder.SetInsertPointBefore(llvm.NextInstruction(ptr))
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+len(allocas)+i), false),
}, "")
c.builder.CreateStore(ptr, gep)
}
// Make sure this stack object is popped from the linked list of stack
// objects at return.
for _, ret := range returns {
c.builder.SetInsertPointBefore(ret)
c.builder.CreateStore(parent, stackChainStart)
}
}
return true
}
func (c *Compiler) addGlobalsBitmap() bool {
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
return false // nothing to do: no GC in use
}
var trackedGlobals []llvm.Value
var trackedGlobalTypes []llvm.Type
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.IsDeclaration() {
continue
}
typ := global.Type().ElementType()
ptrs := c.getPointerBitmap(typ, global.Name())
if ptrs.BitLen() == 0 {
continue
}
trackedGlobals = append(trackedGlobals, global)
trackedGlobalTypes = append(trackedGlobalTypes, typ)
}
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
globalsBundle := llvm.AddGlobal(c.mod, globalsBundleType, "tinygo.trackedGlobals")
globalsBundle.SetLinkage(llvm.InternalLinkage)
globalsBundle.SetUnnamedAddr(true)
initializer := llvm.Undef(globalsBundleType)
for i, global := range trackedGlobals {
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
})
global.ReplaceAllUsesWith(gep)
global.EraseFromParentAsGlobal()
}
globalsBundle.SetInitializer(initializer)
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
bitmapValues := make([]llvm.Value, len(bitmapBytes))
for i, b := range bitmapBytes {
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
}
bitmapArray := llvm.ConstArray(llvm.ArrayType(c.ctx.Int8Type(), len(bitmapBytes)), bitmapValues)
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
bitmapOld.ReplaceAllUsesWith(bitmapNew)
bitmapNew.SetInitializer(bitmapArray)
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
return true // the IR was changed
}
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0)
case llvm.PointerTypeKind:
return big.NewInt(1)
case llvm.StructTypeKind:
ptrs := big.NewInt(0)
for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, name)
if subptrs.BitLen() == 0 {
continue
}
offset := c.targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
panic("precise GC: global contains unaligned pointer: " + name)
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
case llvm.ArrayTypeKind:
subtyp := typ.ElementType()
subptrs := c.getPointerBitmap(subtyp, name)
ptrs := big.NewInt(0)
if subptrs.BitLen() == 0 {
return ptrs
}
elementSize := c.targetData.TypeAllocSize(subtyp)
for i := 0; i < typ.ArrayLength(); i++ {
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
default:
panic("unknown type kind of global: " + name)
}
}
// markParentFunctions traverses all parent function calls (recursively) and
// adds them to the set of marked functions. It only considers function calls:
// any other uses of such a function is ignored.
func markParentFunctions(marked map[llvm.Value]struct{}, fn llvm.Value) {
worklist := []llvm.Value{fn}
for len(worklist) != 0 {
fn := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, use := range getUses(fn) {
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
// Not the parent function.
continue
}
parent := use.InstructionParent().Parent()
if _, ok := marked[parent]; !ok {
marked[parent] = struct{}{}
worklist = append(worklist, parent)
}
}
}
}
+135 -122
View File
@@ -10,8 +10,8 @@ package compiler
// go foo()
// time.Sleep(2 * time.Second)
// println("some other operation")
// bar()
// println("done")
// i := bar()
// println("done", *i)
// }
//
// func foo() {
@@ -21,9 +21,10 @@ package compiler
// }
// }
//
// func bar() {
// func bar() *int {
// time.Sleep(time.Second)
// println("blocking operation completed)
// return new(int)
// }
//
// It is transformed by the IR generator in compiler.go into the following
@@ -34,8 +35,8 @@ package compiler
// fn()
// time.Sleep(2 * time.Second)
// println("some other operation")
// bar() // imagine an 'await' keyword in front of this call
// println("done")
// i := bar() // imagine an 'await' keyword in front of this call
// println("done", *i)
// }
//
// func foo() {
@@ -45,9 +46,10 @@ package compiler
// }
// }
//
// func bar() {
// func bar() *int {
// time.Sleep(time.Second)
// println("blocking operation completed)
// return new(int)
// }
//
// The pass in this file transforms this code even further, to the following
@@ -59,9 +61,11 @@ package compiler
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
// llvm.suspend(hdl) // suspend point
// println("some other operation")
// var i *int // allocate space on the stack for the return value
// runtime.setTaskPromisePtr(hdl, &i) // store return value alloca in our coroutine promise
// bar(hdl) // await, pass a continuation (hdl) to bar
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
// println("done")
// println("done", *i)
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
// }
//
@@ -142,10 +146,9 @@ func (c *Compiler) LowerGoroutines() error {
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.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
return nil
@@ -170,13 +173,17 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
if !sleep.IsNil() {
worklist = append(worklist, sleep)
}
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
if !chanSendStub.IsNil() {
worklist = append(worklist, chanSendStub)
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
if !deadlockStub.IsNil() {
worklist = append(worklist, deadlockStub)
}
chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub")
if !chanRecvStub.IsNil() {
worklist = append(worklist, chanRecvStub)
chanSend := c.mod.NamedFunction("runtime.chanSend")
if !chanSend.IsNil() {
worklist = append(worklist, chanSend)
}
chanRecv := c.mod.NamedFunction("runtime.chanRecv")
if !chanRecv.IsNil() {
worklist = append(worklist, chanRecv)
}
if len(worklist) == 0 {
@@ -274,9 +281,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
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)
@@ -288,7 +292,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Transform all async functions into coroutines.
for _, f := range asyncList {
if f == sleep || f == chanSendStub || f == chanRecvStub {
if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
continue
}
@@ -305,7 +309,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if !inst.IsACallInst().IsNil() {
callee := inst.CalledValue()
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == chanSendStub || callee == chanRecvStub {
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSend || callee == chanRecv {
continue
}
asyncCalls = append(asyncCalls, inst)
@@ -317,7 +321,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Coroutine setup.
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
taskState := c.builder.CreateAlloca(c.getLLVMRuntimeType("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),
@@ -332,6 +336,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
}
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
if c.needsStackObjects() {
c.trackPointer(data)
}
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
// Modify async calls so this function suspends right after the child
@@ -343,10 +350,18 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// 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())
// Allocate space for the return value.
var retvalAlloca llvm.Value
if inst.Type().TypeKind() != llvm.VoidTypeKind {
c.builder.SetInsertPointBefore(inst.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
c.builder.SetInsertPointBefore(inst)
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "")
}
// Suspend.
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
@@ -354,44 +369,76 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
if inst.Type().TypeKind() != llvm.VoidTypeKind {
// Load the return value from the alloca. The callee has
// written the return value to it.
c.builder.SetInsertPointBefore(await.FirstInstruction())
retval := c.builder.CreateLoad(retvalAlloca, "coro.retval")
inst.ReplaceAllUsesWith(retval)
}
}
// Replace return instructions with suspend points that should
// reactivate the parent coroutine.
for _, inst := range returns {
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")
// These properties were added by the functionattrs pass. Remove
// them, because now we start using the parameter.
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
for _, kind := range []string{"nocapture", "readnone"} {
kindID := llvm.AttributeKindID(kind)
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
}
c.builder.SetInsertPointBefore(inst)
var parentHandle llvm.Value
if f.Linkage() == llvm.ExternalLinkage {
// Exported function.
// Note that getTaskPromisePtr will panic if it is called with
// a nil pointer, so blocking exported functions that try to
// return anything will not work.
parentHandle = llvm.ConstPointerNull(c.i8ptrType)
} else {
parentHandle = f.LastParam()
if parentHandle.IsNil() || parentHandle.Name() != "parentHandle" {
// sanity check
panic("trying to make exported function async")
}
}
// Store return values.
switch inst.OperandsCount() {
case 0:
// Nothing to return.
case 1:
// Return this value by writing to the pointer stored in the
// parent handle. The parent coroutine has made an alloca that
// we can write to to store our return value.
returnValuePtr := c.createRuntimeCall("getTaskPromisePtr", []llvm.Value{parentHandle}, "coro.parentData")
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
c.builder.CreateStore(inst.Operand(0), alloca)
default:
panic("unreachable")
}
// Reactivate the parent coroutine. This adds it back to the run
// queue, so it is started again by the scheduler when possible
// (possibly right after the following suspend).
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
// Suspend this coroutine.
// It would look like this is unnecessary, but if this
// suspend point is left out, it leads to undefined
// behavior somehow (with the unreachable instruction).
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
}, "ret")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
inst.EraseFromParentAsInstruction()
}
// Coroutine cleanup. Free resources associated with this coroutine.
@@ -416,6 +463,14 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
c.builder.CreateUnreachable()
}
// Replace calls to runtime.getCoroutineCall with the coroutine of this
// frame.
for _, getCoroutineCall := range getUses(c.mod.NamedFunction("runtime.getCoroutine")) {
frame := asyncFuncs[getCoroutineCall.InstructionParent().Parent()]
getCoroutineCall.ReplaceAllUsesWith(frame.taskHandle)
getCoroutineCall.EraseFromParentAsInstruction()
}
// Transform calls to time.Sleep() into coroutine suspend points.
for _, sleepCall := range getUses(sleep) {
// sleepCall must be a call instruction.
@@ -439,37 +494,31 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
sleepCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.chanSendStub into channel send operations.
for _, sendOp := range getUses(chanSendStub) {
// 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(), 0, false),
}, "")
c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead")
c.builder.SetInsertPointBefore(deadlockCall)
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
deadlockCall.EraseFromParentAsInstruction()
}
// Transform calls to runtime.chanSend into channel send operations.
for _, sendOp := range getUses(chanSend) {
// sendOp must be a call instruction.
frame := asyncFuncs[sendOp.InstructionParent().Parent()]
// 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{
@@ -482,21 +531,11 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
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) {
// Transform calls to runtime.chanRecv into channel receive operations.
for _, recvOp := range getUses(chanRecv) {
// recvOp must be a call instruction.
frame := asyncFuncs[recvOp.InstructionParent().Parent()]
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{
@@ -508,32 +547,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
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()
+164
View File
@@ -0,0 +1,164 @@
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(name string, args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
var asm string
switch name {
case "device/arm.ReadRegister":
asm = "mov $0, " + regname
case "device/riscv.ReadRegister":
asm = "mv $0, " + regname
default:
panic("unknown architecture")
}
target := llvm.InlineAsm(fnType, asm, "=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())
registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X)
case *ssa.Call:
if r.Common() == instr {
break
}
default:
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
}
}
// TODO: handle dollar signs in asm string
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 := c.getValue(frame, arg)
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
}
+93 -89
View File
@@ -4,7 +4,6 @@ package compiler
// form, optimizing them in the process.
//
// During SSA construction, the following pseudo-calls are created:
// runtime.makeInterface(typecode, methodSet)
// runtime.typeAssert(typecode, assertedType)
// runtime.interfaceImplements(typecode, interfaceMethodSet)
// runtime.interfaceMethod(typecode, interfaceMethodSet, signature)
@@ -14,16 +13,13 @@ package compiler
//
// This pass lowers the above functions to their final form:
//
// makeInterface:
// Replaced with a constant typecode.
//
// typeAssert:
// Replaced with an icmp instruction so it can be directly used in a type
// switch. This is very easy to optimize for LLVM: it will often translate a
// type switch into a regular switch statement.
// When this type assert is not possible (the type is never used in an
// interface with makeInterface), this call is replaced with a constant
// false to optimize the type assert away completely.
// interface), this call is replaced with a constant false to optimize the
// type assert away completely.
//
// interfaceImplements:
// This call is translated into a call that checks whether the underlying
@@ -166,25 +162,36 @@ func (c *Compiler) LowerInterfaces() {
// run runs the pass itself.
func (p *lowerInterfacesPass) run() {
// Count per type how often it is put in an interface. Also, collect all
// methods this type has (if it is named).
makeInterface := p.mod.NamedFunction("runtime.makeInterface")
makeInterfaceUses := getUses(makeInterface)
for _, use := range makeInterfaceUses {
typecode := use.Operand(0)
name := typecode.Name()
if t, ok := p.types[name]; !ok {
// This is the first time this type has been seen, add it to the
// list of types.
t = p.addType(typecode)
p.addTypeMethods(t, use.Operand(1))
} else {
p.addTypeMethods(t, use.Operand(1))
}
// Collect all type codes.
typecodeIDPtr := llvm.PointerType(p.getLLVMRuntimeType("typecodeID"), 0)
typeInInterfacePtr := llvm.PointerType(p.getLLVMRuntimeType("typeInInterface"), 0)
var typesInInterfaces []llvm.Value
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
switch global.Type() {
case typecodeIDPtr:
// Retrieve Go type information based on an opaque global variable.
// Only the name of the global is relevant, the object itself is
// discarded afterwards.
name := global.Name()
t := &typeInfo{
name: name,
typecode: global,
}
p.types[name] = t
case typeInInterfacePtr:
// Count per type how often it is put in an interface. Also, collect
// all methods this type has (if it is named).
typesInInterfaces = append(typesInInterfaces, global)
initializer := global.Initializer()
typecode := llvm.ConstExtractValue(initializer, []uint32{0})
methodSet := llvm.ConstExtractValue(initializer, []uint32{1})
t := p.types[typecode.Name()]
p.addTypeMethods(t, methodSet)
// Count the number of MakeInterface instructions, for sorting the
// typecodes later.
p.types[name].countMakeInterfaces++
// Count the number of MakeInterface instructions, for sorting the
// typecodes later.
t.countMakeInterfaces += len(getUses(global))
}
}
// Count per type how often it is type asserted on (e.g. in a switch
@@ -194,9 +201,6 @@ func (p *lowerInterfacesPass) run() {
for _, use := range typeAssertUses {
typecode := use.Operand(1)
name := typecode.Name()
if _, ok := p.types[name]; !ok {
p.addType(typecode)
}
p.types[name].countTypeAsserts++
}
@@ -286,16 +290,6 @@ func (p *lowerInterfacesPass) run() {
typecode := use.Operand(0)
signature := p.signatures[use.Operand(2).Name()]
// If the interface was created in the same function, we can insert a
// direct call. This may not happen often but it is an easy
// optimization so let's do it anyway.
if !typecode.IsACallInst().IsNil() && typecode.CalledValue() == makeInterface {
name := typecode.Operand(0).Name()
typ := p.types[name]
p.replaceInvokeWithCall(use, typ, signature)
continue
}
methodSet := use.Operand(1).Operand(0) // global variable
itf := p.interfaces[methodSet.Name()]
if len(itf.types) == 0 {
@@ -304,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.
@@ -314,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")
}
@@ -340,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()
}
}
@@ -356,20 +350,6 @@ func (p *lowerInterfacesPass) run() {
// types, if possible.
for _, use := range interfaceImplementsUses {
actualType := use.Operand(0)
if !actualType.IsACallInst().IsNil() && actualType.CalledValue() == makeInterface {
// Type assert is in the same function that creates the interface
// value. This means the underlying type is already known so match
// on that.
// This may not happen often but it is an easy optimization.
name := actualType.Operand(0).Name()
typ := p.types[name]
p.builder.SetInsertPointBefore(use)
assertedType := p.builder.CreatePtrToInt(typ.typecode, p.uintptrType, "typeassert.typecode")
commaOk := p.builder.CreateICmp(llvm.IntEQ, assertedType, actualType, "typeassert.ok")
use.ReplaceAllUsesWith(commaOk)
use.EraseFromParentAsInstruction()
continue
}
methodSet := use.Operand(1).Operand(0) // global variable
itf := p.interfaces[methodSet.Name()]
@@ -416,12 +396,14 @@ func (p *lowerInterfacesPass) run() {
// Assign a type code for each type.
p.assignTypeCodes(typeSlice)
// Replace each call to runtime.makeInterface with the constant type code.
for _, use := range makeInterfaceUses {
global := use.Operand(0)
t := p.types[global.Name()]
use.ReplaceAllUsesWith(llvm.ConstPtrToInt(t.typecode, p.uintptrType))
use.EraseFromParentAsInstruction()
// Replace each use of a runtime.typeInInterface with the constant type
// code.
for _, global := range typesInInterfaces {
for _, use := range getUses(global) {
t := p.types[llvm.ConstExtractValue(global.Initializer(), []uint32{0}).Name()]
typecode := llvm.ConstInt(p.uintptrType, t.num, false)
use.ReplaceAllUsesWith(typecode)
}
}
// Replace each type assert with an actual type comparison or (if the type
@@ -458,12 +440,18 @@ func (p *lowerInterfacesPass) run() {
// numbers.
for _, typ := range p.types {
for _, use := range getUses(typ.typecode) {
if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
if !use.IsAConstantExpr().IsNil() && use.Opcode() == llvm.PtrToInt {
use.ReplaceAllUsesWith(llvm.ConstInt(p.uintptrType, typ.num, false))
}
}
}
// Remove stray runtime.typeInInterface globals. Required for the following
// cleanup.
for _, global := range typesInInterfaces {
global.EraseFromParentAsGlobal()
}
// Remove method sets of types. Unnecessary, but cleans up the IR for
// inspection.
for _, typ := range p.types {
@@ -474,19 +462,6 @@ func (p *lowerInterfacesPass) run() {
}
}
// addType retrieves Go type information based on a i16 global variable.
// Only the name of the i16 is relevant, the object itself is const-propagated
// and discared afterwards.
func (p *lowerInterfacesPass) addType(typecode llvm.Value) *typeInfo {
name := typecode.Name()
t := &typeInfo{
name: name,
typecode: typecode,
}
p.types[name] = t
return t
}
// addTypeMethods reads the method set of the given type info struct. It
// retrieves the signatures and the references to the method functions
// themselves for later type<->interface matching.
@@ -542,22 +517,51 @@ 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() {
p.builder.SetInsertPointBefore(use)
function = p.builder.CreateBitCast(function, bitcast.Type(), "")
if inttoptr.Type() == function.Type() {
// Easy case: the types are the same. Simply replace the inttoptr
// result (which is directly called) with the actual function.
inttoptr.ReplaceAllUsesWith(function)
} else {
// Harder case: the type is not actually the same. Go through each call
// (of which there should be only one), extract the receiver params for
// this call and replace the call with a direct call to the target
// function.
for _, call := range getUses(inttoptr) {
if call.IsACallInst().IsNil() || call.CalledValue() != inttoptr {
panic("expected the inttoptr to be called as a method, this is not a method call")
}
operands := make([]llvm.Value, call.OperandsCount()-1)
for i := range operands {
operands[i] = call.Operand(i)
}
paramTypes := function.Type().ElementType().ParamTypes()
receiverParamTypes := paramTypes[:len(paramTypes)-(len(operands)-1)]
methodParamTypes := paramTypes[len(paramTypes)-(len(operands)-1):]
for i, methodParamType := range methodParamTypes {
if methodParamType != operands[i+1].Type() {
panic("expected method call param type and function param type to be the same")
}
}
p.builder.SetInsertPointBefore(call)
receiverParams := p.emitPointerUnpack(operands[0], receiverParamTypes)
result := p.builder.CreateCall(function, append(receiverParams, operands[1:]...), "")
if result.Type().TypeKind() != llvm.VoidTypeKind {
call.ReplaceAllUsesWith(result)
}
call.EraseFromParentAsInstruction()
}
}
bitcast.ReplaceAllUsesWith(function)
bitcast.EraseFromParentAsInstruction()
inttoptr.EraseFromParentAsInstruction()
use.EraseFromParentAsInstruction()
}
+46 -149
View File
@@ -22,48 +22,23 @@ 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, pos token.Pos) (llvm.Value, error) {
var itfValue llvm.Value
size := c.targetData.TypeAllocSize(val.Type())
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
// 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 {
// Directly place the value in the interface.
switch val.Type().TypeKind() {
case llvm.IntegerTypeKind:
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, 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.
mem := c.builder.CreateAlloca(c.i8ptrType, "makeinterface.cast.struct")
memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(val.Type(), 0), "makeinterface.cast.struct.cast")
c.builder.CreateStore(val, memStructPtr)
itfValue = c.builder.CreateLoad(mem, "makeinterface.cast.load")
default:
return llvm.Value{}, c.makeError(pos, "todo: makeinterface: cast small type to i8*")
}
}
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := c.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := c.getTypeCode(typ)
itfMethodSetGlobal, err := c.getTypeMethodSet(typ)
if err != nil {
return llvm.Value{}, nil
itfMethodSetGlobal := c.getTypeMethodSet(typ)
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := c.getLLVMRuntimeType("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.createRuntimeCall("makeInterface", []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}, "makeinterface.typecode")
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
return itf, nil
return itf
}
// getTypeCode returns a reference to a type code.
@@ -73,7 +48,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
globalName := "type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
global.SetGlobalConstant(true)
}
return global
@@ -162,6 +137,10 @@ func getTypeCodeName(t types.Type) string {
return "slice:" + name + getTypeCodeName(t.Elem())
case *types.Struct:
elems := make([]string, t.NumFields())
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
// TODO: report this as a normal error instead of panicking.
panic("cgo unions are not allowed in interfaces")
}
for i := 0; i < t.NumFields(); i++ {
elems[i] = getTypeCodeName(t.Field(i).Type())
}
@@ -173,22 +152,22 @@ func getTypeCodeName(t types.Type) string {
// getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass.
func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$methodset")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
// the method set already exists
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
ms := c.ir.Program.MethodSets.MethodSet(typ)
if ms.Len() == 0 {
// no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0)), nil
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
}
methods := make([]llvm.Value, ms.Len())
interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
@@ -197,13 +176,10 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
// compiler error, so panic
panic("cannot find function: " + f.LinkName())
}
fn, err := c.getInterfaceInvokeWrapper(f)
if err != nil {
return llvm.Value{}, err
}
fn := c.getInterfaceInvokeWrapper(f)
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal,
llvm.ConstBitCast(fn, c.i8ptrType),
llvm.ConstPtrToInt(fn, c.uintptrType),
})
methods[i] = methodInfo
}
@@ -213,7 +189,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
// getInterfaceMethodSet returns a global variable with the method set of the
@@ -262,19 +238,9 @@ func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
//
// Type asserts on concrete types are trivial: just compare type numbers. Type
// asserts on interfaces are more difficult, see the comments in the function.
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Value, error) {
itf, err := c.parseExpr(frame, expr.X)
if err != nil {
return llvm.Value{}, err
}
assertedType, err := c.getLLVMType(expr.AssertedType)
if err != nil {
return llvm.Value{}, err
}
valueNil, err := c.getZeroValue(assertedType)
if err != nil {
return llvm.Value{}, err
}
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
itf := c.getValue(frame, expr.X)
assertedType := c.getLLVMType(expr.AssertedType)
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
commaOk := llvm.Value{}
@@ -327,71 +293,35 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
// Type assert on concrete type. Extract the underlying type from
// the interface (but only after checking it matches).
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
size := c.targetData.TypeAllocSize(assertedType)
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
// Value was stored in an allocated buffer, load it from there.
valuePtrCast := c.builder.CreateBitCast(valuePtr, llvm.PointerType(assertedType, 0), "")
valueOk = c.builder.CreateLoad(valuePtrCast, "typeassert.value.ok")
} else if size == 0 {
valueOk, err = c.getZeroValue(assertedType)
if err != nil {
return llvm.Value{}, err
}
} else {
// Value was stored directly in the interface.
switch assertedType.TypeKind() {
case llvm.IntegerTypeKind:
valueOk = c.builder.CreatePtrToInt(valuePtr, assertedType, "typeassert.value.ok")
case llvm.PointerTypeKind:
valueOk = c.builder.CreateBitCast(valuePtr, assertedType, "typeassert.value.ok")
default: // struct, float, etc.
// 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.
mem := c.builder.CreateAlloca(c.i8ptrType, "")
c.builder.CreateStore(valuePtr, mem)
memCast := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "")
valueOk = c.builder.CreateLoad(memCast, "typeassert.value.ok")
}
}
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
}
c.builder.CreateBr(nextBlock)
// Continue after the if statement.
c.builder.SetInsertPointAtEnd(nextBlock)
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{valueNil, valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
phi.AddIncoming([]llvm.Value{c.getZeroValue(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
if expr.CommaOk {
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
return tuple, nil
return tuple
} else {
// This is kind of dirty as the branch above becomes mostly useless,
// but hopefully this gets optimized away.
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
return phi, nil
return phi
}
}
// getInvokeCall creates and returns the function pointer and parameters of an
// interface call. It can be used in a call or defer instruction.
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value, error) {
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf, err := c.parseExpr(frame, instr.Value) // interface
if err != nil {
return llvm.Value{}, nil, err
}
itf := c.getValue(frame, instr.Value) // interface
llvmFnType, err := c.getLLVMType(instr.Method.Type())
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]
llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature))
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
values := []llvm.Value{
@@ -400,16 +330,12 @@ 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}
for _, arg := range instr.Args {
val, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, nil, err
}
args = append(args, val)
args = append(args, c.getValue(frame, arg))
}
// Add the context parameter. An interface call never takes a context but we
// have to supply the parameter anyway.
@@ -417,7 +343,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
// Add the parent goroutine handle.
args = append(args, llvm.Undef(c.i8ptrType))
return fnCast, args, nil
return fnCast, args
}
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
@@ -433,19 +359,16 @@ type interfaceInvokeWrapper struct {
// the underlying value, dereferences it, and calls the real method. This
// wrapper is only needed when the interface value actually doesn't fit in a
// pointer and a pointer to the value must be created.
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error) {
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
wrapperName := f.LinkName() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() {
// Wrapper already created. Return it directly.
return wrapper, nil
return wrapper
}
// Get the expanded receiver type.
receiverType, err := c.getLLVMType(f.Params[0].Type())
if err != nil {
return llvm.Value{}, err
}
receiverType := c.getLLVMType(f.Params[0].Type())
expandedReceiverType := c.expandFormalParamType(receiverType)
// Does this method even need any wrapping?
@@ -454,7 +377,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
// Casting a function signature to a different signature and calling it
// with a receiver pointer bitcasted to *i8 (as done in calls on an
// interface) is hopefully a safe (defined) operation.
return f.LLVMFn, nil
return f.LLVMFn
}
// create wrapper function
@@ -467,12 +390,12 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
wrapper: wrapper,
receiverType: receiverType,
})
return wrapper, nil
return wrapper
}
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
// see that function for details.
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) error {
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
wrapper := state.wrapper
fn := state.fn
receiverType := state.receiverType
@@ -482,10 +405,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
// add debug info if needed
if c.Debug {
pos := c.ir.Program.Fset.Position(fn.Pos())
difunc, err := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
if err != nil {
return err
}
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
@@ -493,28 +413,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
block := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(block)
var receiverPtr llvm.Value
if c.targetData.TypeAllocSize(receiverType) > c.targetData.TypeAllocSize(c.i8ptrType) {
// The receiver is passed in using a pointer. We have to load it here
// and pass it by value to the real function.
// Load the underlying value.
receiverPtrType := llvm.PointerType(receiverType, 0)
receiverPtr = c.builder.CreateBitCast(wrapper.Param(0), receiverPtrType, "receiver.ptr")
} else {
// The value is stored in the interface, but it is of type struct which
// is expanded to multiple parameters (e.g. {i8, i8}). So we have to
// receive the struct as parameter, expand it, and pass it on to the
// real function.
// Cast the passed-in i8* to the struct value (using an alloca) and
// extract its values.
alloca := c.builder.CreateAlloca(c.i8ptrType, "receiver.alloca")
c.builder.CreateStore(wrapper.Param(0), alloca)
receiverPtr = c.builder.CreateBitCast(alloca, llvm.PointerType(receiverType, 0), "receiver.ptr")
}
receiverValue := c.builder.CreateLoad(receiverPtr, "receiver")
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
c.builder.CreateCall(fn.LLVMFn, params, "")
@@ -523,6 +422,4 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
c.builder.CreateRet(ret)
}
return nil
}
+59
View File
@@ -22,6 +22,65 @@ func getUses(value llvm.Value) []llvm.Value {
return uses
}
// createEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is
// at the end of the current block.
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value {
currentBlock := c.builder.GetInsertBlock()
entryBlock := currentBlock.Parent().EntryBasicBlock()
if entryBlock.FirstInstruction().IsNil() {
c.builder.SetInsertPointAtEnd(entryBlock)
} else {
c.builder.SetInsertPointBefore(entryBlock.FirstInstruction())
}
alloca := c.builder.CreateAlloca(t, name)
c.builder.SetInsertPointAtEnd(currentBlock)
return alloca
}
// createTemporaryAlloca creates a new alloca in the entry block and adds
// lifetime start infromation in the IR signalling that the alloca won't be used
// before this point.
//
// This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it.
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
alloca = c.createEntryBlockAlloca(t, name)
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
return
}
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
// llvm.lifetime.end intrinsic. It is commonly used together with
// createTemporaryAlloca.
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, ptr}, "")
}
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
// first if it doesn't exist yet.
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
fn := c.mod.NamedFunction("llvm.lifetime.start.p0i8")
if fn.IsNil() {
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
fn = llvm.AddFunction(c.mod, "llvm.lifetime.start.p0i8", fnType)
}
return fn
}
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
// first if it doesn't exist yet.
func (c *Compiler) getLifetimeEndFunc() llvm.Value {
fn := c.mod.NamedFunction("llvm.lifetime.end.p0i8")
if fn.IsNil() {
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
fn = llvm.AddFunction(c.mod, "llvm.lifetime.end.p0i8", fnType)
}
return fn
}
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
// after afterInst are moved into a new basic block (created right after the
// current one) with the given name.
+33 -22
View File
@@ -10,12 +10,14 @@ import (
)
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType, err := c.getLLVMType(valueType)
if err != nil {
return llvm.Value{}, err
}
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value")
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr")
llvmValueType := c.getLLVMType(valueType)
// Allocate the memory for the resulting type. Do not zero this memory: it
// will be zeroed by the hashmap get implementation if the key is not
// present in the map.
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value")
// Do the lookup. How it is done depends on the key type.
var commaOkValue llvm.Value
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
@@ -23,15 +25,24 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
params := []llvm.Value{m, keyPtr, mapValuePtr}
// Store the key in an alloca, in the entry block to avoid dynamic stack
// growth.
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, mapKeyAlloca)
// Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} else {
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// Not trivially comparable using memcmp.
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
// Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap.
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
if commaOk {
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
@@ -42,27 +53,25 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
}
}
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) error {
valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value")
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value")
c.builder.CreateStore(value, valueAlloca)
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr")
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, valuePtr}
c.createRuntimeCall("hashmapStringSet", params, "")
return nil
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
params := []llvm.Value{m, keyPtr, valuePtr}
c.createRuntimeCall("hashmapBinarySet", params, "")
return nil
c.emitLifetimeEnd(keyPtr, keySize)
} else {
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
c.emitLifetimeEnd(valuePtr, valueSize)
}
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
@@ -73,14 +82,14 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
c.createRuntimeCall("hashmapStringDelete", params, "")
return nil
} else if hashmapIsBinaryKey(keyType) {
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
params := []llvm.Value{m, keyPtr}
c.createRuntimeCall("hashmapBinaryDelete", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
return nil
} else {
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
}
@@ -112,6 +121,8 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
switch keyType := keyType.(type) {
case *types.Basic:
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
case *types.Pointer:
return true
case *types.Struct:
for i := 0; i < keyType.NumFields(); i++ {
fieldType := keyType.Field(i).Type().Underlying()
+53 -13
View File
@@ -18,6 +18,10 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
}
builder.AddCoroutinePassesToExtensionPoints()
if c.PanicStrategy == "trap" {
c.replacePanicsWithTrap() // -panic=trap
}
// Run function passes for each function.
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
defer funcPasses.Dispose()
@@ -43,6 +47,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
c.OptimizeStringToBytes()
c.OptimizeAllocs()
c.LowerInterfaces()
c.LowerFuncValues()
// After interfaces are lowered, there are many more opportunities for
// interprocedural optimizations. To get them to work, function
@@ -53,6 +58,22 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
c.OptimizeAllocs()
c.OptimizeStringToBytes()
// Lower runtime.isnil calls to regular nil comparisons.
isnil := c.mod.NamedFunction("runtime.isnil")
if !isnil.IsNil() {
for _, use := range getUses(isnil) {
c.builder.SetInsertPointBefore(use)
ptr := use.Operand(0)
if !ptr.IsABitCastInst().IsNil() {
ptr = ptr.Operand(0)
}
nilptr := llvm.ConstPointerNull(ptr.Type())
icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
use.ReplaceAllUsesWith(icmp)
use.EraseFromParentAsInstruction()
}
}
err := c.LowerGoroutines()
if err != nil {
return err
@@ -60,6 +81,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
} else {
// Must be run at any optimization level.
c.LowerInterfaces()
c.LowerFuncValues()
err := c.LowerGoroutines()
if err != nil {
return err
@@ -92,9 +114,36 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
builder.Populate(modPasses)
modPasses.Run(c.mod)
hasGCPass := c.addGlobalsBitmap()
hasGCPass = c.makeGCStackSlots() || hasGCPass
if hasGCPass {
if err := c.Verify(); err != nil {
return errors.New("GC pass caused a verification failure")
}
}
return nil
}
// Replace panic calls with calls to llvm.trap, to reduce code size. This is the
// -panic=trap intrinsic.
func (c *Compiler) replacePanicsWithTrap() {
trap := c.mod.NamedFunction("llvm.trap")
for _, name := range []string{"runtime._panic", "runtime.runtimePanic"} {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
for _, use := range getUses(fn) {
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
panic("expected use of a panic function to be a call")
}
c.builder.SetInsertPointBefore(use)
c.builder.CreateCall(trap, nil, "")
}
}
}
// Eliminate created but not used maps.
//
// In the future, this should statically allocate created but never modified
@@ -231,7 +280,7 @@ func (c *Compiler) OptimizeAllocs() {
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
zero, _ := c.getZeroValue(alloca.Type().ElementType())
zero := c.getZeroValue(alloca.Type().ElementType())
c.builder.CreateStore(zero, alloca)
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
bitcast.ReplaceAllUsesWith(stackalloc)
@@ -266,21 +315,12 @@ func (c *Compiler) doesEscape(value llvm.Value) bool {
return true
}
} else if use.IsACallInst() != nilValue {
// Call only escapes when the (pointer) parameter is not marked
// "nocapture". This flag means that the parameter does not escape
// the give function.
if use.CalledValue().IsAFunction() != nilValue {
if use.CalledValue().IsDeclaration() {
// Kind of dirty: assume external functions don't let
// pointers escape.
// TODO: introduce //go:noescape that sets the 'nocapture'
// flag on each input parameter.
continue
}
}
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
+39 -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())
@@ -107,6 +113,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
if k == types.Uintptr {
return s.PtrSize
}
if k == types.UnsafePointer {
return s.PtrSize
}
panic("unknown basic type: " + t.String())
case *types.Array:
n := t.Len()
@@ -125,11 +134,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:
+107
View File
@@ -0,0 +1,107 @@
package compiler
// This file manages symbols, that is, functions and globals. It reads their
// pragmas, determines the link name, etc.
import (
"go/ast"
"go/token"
"go/types"
"strings"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// globalInfo contains some information about a specific global. By default,
// linkName is equal to .RelString(nil) on a global and extern is false, but for
// some symbols this is different (due to //go:extern for example).
type globalInfo struct {
linkName string // go:extern
extern bool // go:extern
}
// loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals.
func (c *Compiler) loadASTComments(lprogram *loader.Program) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
c.astComments[id] = decl.Doc
}
}
}
}
}
}
}
}
}
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
// the LLVM IR if it has not been added already.
func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
info := c.getGlobalInfo(g)
llvmGlobal := c.mod.NamedGlobal(info.linkName)
if llvmGlobal.IsNil() {
llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem())
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
if !info.extern {
llvmGlobal.SetInitializer(c.getZeroValue(llvmType))
llvmGlobal.SetLinkage(llvm.InternalLinkage)
}
}
return llvmGlobal
}
// getGlobalInfo returns some information about a specific global.
func (c *Compiler) getGlobalInfo(g *ssa.Global) globalInfo {
info := globalInfo{}
if strings.HasPrefix(g.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
info.linkName = g.Name()[2:]
info.extern = true
} else {
// Pick the default linkName.
info.linkName = g.RelString(nil)
// Check for //go: pragmas, which may change the link name (among
// others).
doc := c.astComments[info.linkName]
if doc != nil {
info.parsePragmas(doc)
}
}
return info
}
// Parse //go: pragma comments from the source. In particular, it parses the
// //go:extern pragma on globals.
func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
for _, comment := range doc.List {
if !strings.HasPrefix(comment.Text, "//go:") {
continue
}
parts := strings.Fields(comment.Text)
switch parts[0] {
case "//go:extern":
info.extern = true
if len(parts) == 2 {
info.linkName = parts[1]
}
}
}
}
+8 -18
View File
@@ -4,7 +4,6 @@ package compiler
// compiler builtins.
import (
"go/constant"
"strconv"
"golang.org/x/tools/go/ssa"
@@ -14,7 +13,7 @@ import (
// 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)
num := c.getValue(frame, call.Args[0])
var syscallResult llvm.Value
switch {
case c.GOARCH == "amd64":
@@ -29,12 +28,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
// > 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
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
}
// 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)}
args := []llvm.Value{num}
argTypes := []llvm.Type{c.uintptrType}
// Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
@@ -51,10 +50,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{r12}",
"{r13}",
}[i]
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
@@ -80,14 +76,11 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{r5}",
"{r6}",
}[i]
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
args = append(args, num)
argTypes = append(argTypes, c.uintptrType)
constraints += ",{r7}" // syscall number
for i := len(call.Args) - 1; i < 4; i++ {
@@ -113,14 +106,11 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{x4}",
"{x5}",
}[i]
llvmValue, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, err
}
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
args = append(args, num)
argTypes = append(argTypes, c.uintptrType)
constraints += ",{x8}" // syscall number
for i := len(call.Args) - 1; i < 8; i++ {
+26
View File
@@ -0,0 +1,26 @@
package compiler
// This file implements volatile loads/stores in runtime/volatile.LoadT and
// runtime/volatile.StoreT as compiler builtins.
import (
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
c.emitNilCheck(frame, addr, "deref")
val := c.builder.CreateLoad(addr, "")
val.SetVolatile(true)
return val, nil
}
func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
val := c.getValue(frame, instr.Args[1])
c.emitNilCheck(frame, addr, "deref")
store := c.builder.CreateStore(val, addr)
store.SetVolatile(true)
return llvm.Value{}, nil
}
+120
View File
@@ -0,0 +1,120 @@
package compiler
// This file contains utility functions to pack and unpack sets of values. It
// can take in a list of values and tries to store it efficiently in the pointer
// itself if possible and legal.
import (
"tinygo.org/x/go-llvm"
)
// emitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
valueTypes := make([]llvm.Type, len(values))
for i, value := range values {
valueTypes[i] = value.Type()
}
packedType := c.ctx.StructType(valueTypes, false)
// Allocate memory for the packed data.
var packedAlloc, packedHeapAlloc llvm.Value
size := c.targetData.TypeAllocSize(packedType)
if size == 0 {
return llvm.ConstPointerNull(c.i8ptrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
// Packed data fits in a pointer, so store it directly inside the
// pointer.
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
// Try to keep this cast in SSA form.
return c.builder.CreateIntToPtr(values[0], c.i8ptrType, "pack.int")
}
// Because packedType is a struct and we have to cast it to a *i8, store
// it in an alloca first for bitcasting (store+bitcast+load).
packedAlloc, _, _ = c.createTemporaryAlloca(packedType, "")
} else {
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
if c.needsStackObjects() {
c.trackPointer(packedHeapAlloc)
}
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
}
// Store all values in the alloca or heap pointer.
for i, value := range values {
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
c.builder.CreateStore(value, gep)
}
if packedHeapAlloc.IsNil() {
// Load value (as *i8) from the alloca.
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
result := c.builder.CreateLoad(packedAlloc, "")
packedPtr := c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
packedSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(packedAlloc.Type()), false)
c.emitLifetimeEnd(packedPtr, packedSize)
return result
} else {
// Get the original heap allocation pointer, which already is an *i8.
return packedHeapAlloc
}
}
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
packedType := c.ctx.StructType(valueTypes, false)
// Get a correctly-typed pointer to the packed data.
var packedAlloc, packedRawAlloc llvm.Value
size := c.targetData.TypeAllocSize(packedType)
if size == 0 {
// No data to unpack.
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly.
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
// Packed data stored directly in pointer.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form.
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
}
// Fallback: load it using an alloca.
packedRawAlloc, _, _ = c.createTemporaryAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
c.builder.CreateStore(packedRawValue, packedRawAlloc)
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
} else {
// Packed data stored on the heap. Bitcast the passed-in pointer to the
// correct pointer type.
packedAlloc = c.builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
}
// Load each value from the packed data.
values := make([]llvm.Value, len(valueTypes))
for i, valueType := range valueTypes {
if c.targetData.TypeAllocSize(valueType) == 0 {
// This value has length zero, so there's nothing to load.
values[i] = c.getZeroValue(valueType)
continue
}
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
values[i] = c.builder.CreateLoad(gep, "")
}
if !packedRawAlloc.IsNil() {
allocPtr := c.builder.CreateBitCast(packedRawAlloc, c.i8ptrType, "")
allocSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(c.uintptrType), false)
c.emitLifetimeEnd(allocPtr, allocSize)
}
return values
}
+10
View File
@@ -0,0 +1,10 @@
module github.com/tinygo-org/tinygo
go 1.11
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261
)
+14
View File
@@ -0,0 +1,14 @@
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
+106 -28
View File
@@ -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,17 +176,47 @@ 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
if _, ok := fr.getLocal(operand).(*MapValue); ok {
// Special case for runtime.trackPointer calls.
// Note: this might not be entirely sound in some rare cases
// where the map is stored in a dirty global.
uses := getUses(inst)
if len(uses) == 1 {
use := uses[0]
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
continue
}
}
// It is not possible in Go to bitcast a map value to a pointer.
panic("unimplemented: bitcast of map")
}
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():
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
predicate := inst.IntPredicate()
if predicate == llvm.IntEQ && lhs.Type().TypeKind() == llvm.PointerTypeKind {
// Unfortunately, the const propagation in the IR builder
// doesn't handle pointer compares of inttoptr values. So we
// implement it manually here.
lhsNil, ok1 := isPointerNil(lhs)
rhsNil, ok2 := isPointerNil(rhs)
if ok1 && ok2 {
if lhsNil && rhsNil {
// Both are nil, so this icmp is always evaluated to true.
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
continue
}
if lhsNil != rhsNil {
// Only one of them is nil, so this comparison must return false.
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false)}
continue
}
}
}
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateICmp(predicate, lhs, rhs, "")}
case !inst.IsAFCmpInst().IsNil():
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
@@ -222,7 +255,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,
}
@@ -246,15 +279,15 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
m := fr.getLocal(inst.Operand(0)).(*MapValue)
// "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters.
keyBuf := fr.getLocal(inst.Operand(1))
keyLen := fr.getLocal(inst.Operand(2))
valPtr := fr.getLocal(inst.Operand(3))
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
m := fr.getLocal(inst.Operand(0)).(*MapValue)
keyBuf := fr.getLocal(inst.Operand(1))
valPtr := fr.getLocal(inst.Operand(2))
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
@@ -307,9 +340,50 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
fr.locals[inst] = &LocalValue{fr.Eval, ret}
case callee.Name() == "runtime.makeInterface":
uintptrType := callee.Type().Context().IntType(fr.TargetData.PointerSize() * 8)
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), uintptrType)}
case callee.Name() == "runtime.interfaceImplements":
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
panic("interp: expected typecode to be a ptrtoint")
}
typecode = typecode.Operand(0)
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
panic("interp: expected method set in runtime.interfaceImplements to be a constant gep")
}
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
panic("interp: expected method set to be a constant gep")
}
methodSet = methodSet.Operand(0).Initializer()
// Make a set of all the methods on the concrete type, for
// easier checking in the next step.
definedMethods := map[string]struct{}{}
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
methodInfo := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)})
name := llvm.ConstExtractValue(methodInfo, []uint32{0}).Name()
definedMethods[name] = struct{}{}
}
// Check whether all interface methods are also in the list
// of defined methods calculated above.
implements := uint64(1) // i1 true
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
if _, ok := definedMethods[name]; !ok {
// There is a method on the interface that is not
// implemented by the type.
implements = 0 // i1 false
break
}
}
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)}
case callee.Name() == "runtime.nanotime":
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
case callee.Name() == "llvm.dbg.value":
// do nothing
case callee.Name() == "runtime.trackPointer":
// do nothing
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
// This are all print instructions, which necessarily have side
// effects but no results.
@@ -422,17 +496,21 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
thenBB := inst.Operand(1)
elseBB := inst.Operand(2)
if !cond.IsConstant() {
if !cond.IsAInstruction().IsNil() {
return nil, nil, errors.New("interp: branch on a non-constant")
} else {
switch cond.ZExtValue() {
case 0: // false
return nil, []llvm.Value{thenBB}, nil // then
case 1: // true
return nil, []llvm.Value{elseBB}, nil // else
default:
panic("branch was not true or false")
}
}
if !cond.IsAConstantExpr().IsNil() {
// This may happen when the instruction builder could not
// const-fold some instructions.
return nil, nil, errors.New("interp: branch on a non-const-propagated constant expression")
}
switch cond {
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
return nil, []llvm.Value{thenBB}, nil // then
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
return nil, []llvm.Value{elseBB}, nil // else
default:
panic("branch was not true or false")
}
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
// unconditional branch (goto)
+11 -9
View File
@@ -18,7 +18,6 @@ type Eval struct {
TargetData llvm.TargetData
Debug bool
builder llvm.Builder
dibuilder *llvm.DIBuilder
dirtyGlobals map[llvm.Value]struct{}
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
}
@@ -38,14 +37,21 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
dirtyGlobals: map[llvm.Value]struct{}{},
}
e.builder = mod.Context().NewBuilder()
e.dibuilder = llvm.NewDIBuilder(mod)
initAll := mod.NamedFunction(name)
bb := initAll.EntryBasicBlock()
e.builder.SetInsertPointBefore(bb.LastInstruction())
e.builder.SetInstDebugLocation(bb.FirstInstruction())
// 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)
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
}
@@ -115,11 +121,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,
+19 -6
View File
@@ -28,8 +28,17 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
case "runtime.alloc":
// Cannot be scanned but can be interpreted.
return &sideEffectResult{severity: sideEffectNone}
case "runtime.nanotime":
// Fixed value at compile time.
return &sideEffectResult{severity: sideEffectNone}
case "runtime._panic":
return &sideEffectResult{severity: sideEffectLimited}
case "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone}
case "runtime.trackPointer":
return &sideEffectResult{severity: sideEffectNone}
case "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}
}
if e.sideEffectFuncs == nil {
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
@@ -81,11 +90,6 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
continue
}
if child.IsDeclaration() {
switch child.Name() {
case "runtime.makeInterface":
// Can be interpreted so does not have side effects.
continue
}
// External function call. Assume only limited side effects
// (no affected globals, etc.).
if e.hasLocalSideEffects(dirtyLocals, inst) {
@@ -107,7 +111,16 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
default:
panic("unreachable")
}
case llvm.Load, llvm.Store:
case llvm.Load:
if inst.IsVolatile() {
result.updateSeverity(sideEffectLimited)
}
if _, ok := e.dirtyGlobals[inst.Operand(0)]; ok {
if e.hasLocalSideEffects(dirtyLocals, inst) {
result.updateSeverity(sideEffectLimited)
}
}
case llvm.Store:
if inst.IsVolatile() {
result.updateSeverity(sideEffectLimited)
}
+26
View File
@@ -94,3 +94,29 @@ func isScalar(t llvm.Type) bool {
return false
}
}
// isPointerNil returns whether this is a nil pointer or not. The ok value
// indicates whether the result is certain: if it is false the result boolean is
// not valid.
func isPointerNil(v llvm.Value) (result bool, ok bool) {
if !v.IsAConstantExpr().IsNil() {
switch v.Opcode() {
case llvm.IntToPtr:
// Whether a constant inttoptr is nil is easy to
// determine.
operand := v.Operand(0)
if operand.IsConstant() {
return operand.ZExtValue() == 0, true
}
case llvm.BitCast, llvm.GetElementPtr:
// These const instructions are just a kind of wrappers for the
// underlying pointer.
return isPointerNil(v.Operand(0))
}
}
if !v.IsAConstantPointerNull().IsNil() {
// A constant pointer null is always null, of course.
return true, true
}
return false, false // not valid
}
+72 -309
View File
@@ -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 {
@@ -534,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")
@@ -569,31 +321,42 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr Value) {
}
// PutBinary does a map assign operation.
func (v *MapValue) PutBinary(keyPtr, valPtr Value) {
func (v *MapValue) PutBinary(keyPtr, 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")
}
key := keyPtr.(*PointerCastValue).Underlying.Load()
v.KeyType = key.Type()
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})
+36 -143
View File
@@ -2,7 +2,6 @@ package ir
import (
"go/ast"
"go/token"
"go/types"
"sort"
"strings"
@@ -23,43 +22,18 @@ type Program struct {
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
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
}
// Type with a name and possibly methods.
type NamedType struct {
*ssa.Type
LLVMType llvm.Type
}
// Type that is at some point put in an interface.
type TypeWithMethods struct {
t types.Type
Num int
Methods map[string]*types.Selection
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
inline InlineType // go:inline
}
// Interface type that is at some point used in a type assert (to check whether
@@ -69,37 +43,27 @@ type Interface struct {
Type *types.Interface
}
// Create and intialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
comments := map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.TYPE, token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.TypeSpec: // decl.Tok == token.TYPE
id := pkgInfo.Pkg.Path() + "." + spec.Name.Name
comments[id] = decl.Doc
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
comments[id] = decl.Doc
}
}
}
}
}
}
}
}
type InlineType int
// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
InlineDefault InlineType = iota
// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
InlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
InlineNone
)
// Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
program := lprogram.LoadSSA()
program.Build()
@@ -171,8 +135,6 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
LoaderProgram: lprogram,
mainPkg: mainPkg,
functionMap: make(map[*ssa.Function]*Function),
globalMap: make(map[*ssa.Global]*Global),
comments: comments,
}
for _, pkg := range packageList {
@@ -197,8 +159,6 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
case *ssa.Function:
p.addFunction(member)
case *ssa.Type:
t := &NamedType{Type: member}
p.NamedTypes = append(p.NamedTypes, t)
methods := getAllMethods(pkg.Prog, member.Type())
if !types.IsInterface(member.Type()) {
// named type
@@ -207,13 +167,7 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
}
}
case *ssa.Global:
g := &Global{program: p, Global: member}
doc := p.comments[g.RelString(nil)]
if doc != nil {
g.parsePragmas(doc)
}
p.Globals = append(p.Globals, g)
p.globalMap[member] = g
// Ignore. Globals are not handled here.
case *ssa.NamedConst:
// Ignore: these are already resolved.
default:
@@ -247,10 +201,6 @@ func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
return p.functionMap[ssaFn]
}
func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
return p.globalMap[ssaGlobal]
}
func (p *Program) MainPkg() *ssa.Package {
return p.mainPkg
}
@@ -279,6 +229,10 @@ func (f *Function) parsePragmas() {
}
f.linkName = parts[1]
f.exported = true
case "//go:inline":
f.inline = InlineHint
case "//go:noinline":
f.inline = InlineNone
case "//go:interrupt":
if len(parts) != 2 {
continue
@@ -332,6 +286,11 @@ func (f *Function) IsInterrupt() bool {
return f.interrupt
}
// Return the inline directive of this function.
func (f *Function) Inline() InlineType {
return f.inline
}
// Return the link name for this function.
func (f *Function) LinkName() string {
if f.linkName != "" {
@@ -366,72 +325,6 @@ func (f *Function) CName() string {
return ""
}
// Parse //go: pragma comments from the source.
func (g *Global) parsePragmas(doc *ast.CommentGroup) {
for _, comment := range doc.List {
if !strings.HasPrefix(comment.Text, "//go:") {
continue
}
parts := strings.Fields(comment.Text)
switch parts[0] {
case "//go:extern":
g.extern = true
if len(parts) == 2 {
g.linkName = parts[1]
}
}
}
}
// Return the link name for this global.
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 || g.CName() != ""
}
// 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,
// for volatile loads and stores.
func (p *Program) IsVolatile(t types.Type) bool {
if t, ok := t.(*types.Named); !ok {
return false
} else {
if t.Obj().Pkg() == nil {
return false
}
id := t.Obj().Pkg().Path() + "." + t.Obj().Name()
doc := p.comments[id]
if doc == nil {
return false
}
for _, line := range doc.List {
if strings.TrimSpace(line.Text) == "//go:volatile" {
return true
}
}
return false
}
}
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
+7 -4
View File
@@ -22,9 +22,9 @@ import "C"
// Link invokes a linker with the given name and flags.
//
// This version uses the built-in linker when trying to use lld.
func Link(dir, linker string, flags ...string) error {
func Link(linker string, flags ...string) error {
switch linker {
case "ld.lld", commands["ld.lld"]:
case "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)))
@@ -39,7 +39,7 @@ func Link(dir, linker string, flags ...string) error {
return errors.New("failed to link using built-in ld.lld")
}
return nil
case "wasm-ld", commands["wasm-ld"]:
case "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)))
@@ -57,10 +57,13 @@ func Link(dir, linker string, flags ...string) error {
return nil
default:
// Fall back to external command.
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
cmd.Dir = sourceDir()
return cmd.Run()
}
}
+5 -2
View File
@@ -13,10 +13,13 @@ import (
// Link invokes a linker with the given name and arguments.
//
// This version always runs the linker as an external command.
func Link(dir, linker string, flags ...string) error {
func Link(linker string, flags ...string) error {
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
cmd.Dir = sourceDir()
return cmd.Run()
}
-414
View File
@@ -1,414 +0,0 @@
package loader
// This file extracts the `import "C"` statement from the source and modifies
// the AST for Cgo. It does not use libclang directly (see libclang.go).
import (
"go/ast"
"go/token"
"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 map[string]*functionInfo
globals map[string]*globalInfo
typedefs map[string]*typedefInfo
importCPos token.Pos
}
// functionInfo stores some information about a Cgo function found by libclang
// and declared in the AST.
type functionInfo struct {
args []paramInfo
results *ast.FieldList
}
// paramInfo is a parameter of a Cgo function (see functionInfo).
type paramInfo struct {
name string
typeExpr ast.Expr
}
// typedefInfo contains information about a single typedef in C.
type typedefInfo struct {
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
// in both languages. See addTypeAliases.
var cgoAliases = map[string]string{
"C.int8_t": "int8",
"C.int16_t": "int16",
"C.int32_t": "int32",
"C.int64_t": "int64",
"C.uint8_t": "uint8",
"C.uint16_t": "uint16",
"C.uint32_t": "uint32",
"C.uint64_t": "uint64",
"C.uintptr_t": "uintptr",
}
// cgoTypes lists some C types with ambiguous sizes that must be retrieved
// somehow from C. This is done by adding some typedefs to get the size of each
// type.
const cgoTypes = `
typedef signed char _Cgo_schar;
typedef unsigned char _Cgo_uchar;
typedef short _Cgo_short;
typedef unsigned short _Cgo_ushort;
typedef int _Cgo_int;
typedef unsigned int _Cgo_uint;
typedef long _Cgo_long;
typedef unsigned long _Cgo_ulong;
typedef long long _Cgo_longlong;
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 {
info := &fileInfo{
File: f,
filename: filename,
functions: map[string]*functionInfo{},
globals: map[string]*globalInfo{},
typedefs: map[string]*typedefInfo{},
}
// Find `import "C"` statements in the file.
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
genDecl, ok := decl.(*ast.GenDecl)
if !ok {
continue
}
if len(genDecl.Specs) != 1 {
continue
}
spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
if !ok {
continue
}
path, err := strconv.Unquote(spec.Path.Value)
if err != nil {
panic("could not parse import path: " + err.Error())
}
if path != "C" {
continue
}
cgoComment := genDecl.Doc.Text()
// Stored for later use by generated functions, to use a somewhat sane
// source location.
info.importCPos = spec.Path.ValuePos
err = info.parseFragment(cgoComment+cgoTypes, cflags)
if err != nil {
return err
}
// Remove this import declaration.
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
i--
}
// Print the AST, for debugging.
//ast.Print(p.fset, f)
// 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.
info.addTypedefs()
// Patch the AST to use the declared types and functions.
f = astutil.Apply(f, info.walker, nil).(*ast.File)
return nil
}
// addFuncDecls adds the C function declarations found by libclang in the
// comment above the `import "C"` statement.
func (info *fileInfo) addFuncDecls() {
// TODO: replace all uses of importCPos with the real locations from
// libclang.
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: "C." + name,
}
args := make([]*ast.Field, len(fn.args))
decl := &ast.FuncDecl{
Name: &ast.Ident{
NamePos: info.importCPos,
Name: "C." + name,
Obj: obj,
},
Type: &ast.FuncType{
Func: info.importCPos,
Params: &ast.FieldList{
Opening: info.importCPos,
List: args,
Closing: info.importCPos,
},
Results: fn.results,
},
}
obj.Decl = decl
for i, arg := range fn.args {
args[i] = &ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: info.importCPos,
Name: arg.name,
Obj: &ast.Object{
Kind: ast.Var,
Name: arg.name,
Decl: decl,
},
},
},
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:
//
// type (
// C.int8_t = int8
// C.int16_t = int16
// // ...
// )
func (info *fileInfo) addTypeAliases() {
aliasKeys := make([]string, 0, len(cgoAliases))
for key := range cgoAliases {
aliasKeys = append(aliasKeys, key)
}
sort.Strings(aliasKeys)
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.TYPE,
Lparen: info.importCPos,
Rparen: info.importCPos,
}
for _, typeName := range aliasKeys {
goTypeName := cgoAliases[typeName]
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: info.importCPos,
Name: typeName,
Obj: obj,
},
Assign: info.importCPos,
Type: &ast.Ident{
NamePos: info.importCPos,
Name: goTypeName,
},
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
}
info.Decls = append(info.Decls, gen)
}
func (info *fileInfo) addTypedefs() {
gen := &ast.GenDecl{
TokPos: info.importCPos,
Tok: token.TYPE,
}
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 _, 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: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: info.importCPos,
Name: typeName,
Obj: obj,
},
Type: typedef.typeExpr,
}
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 {
return true
}
x, ok := fun.X.(*ast.Ident)
if !ok {
return true
}
if _, ok := info.functions[fun.Sel.Name]; ok && x.Name == "C" {
node.Fun = &ast.Ident{
NamePos: x.NamePos,
Name: "C." + fun.Sel.Name,
}
}
case *ast.SelectorExpr:
x, ok := node.X.(*ast.Ident)
if !ok {
return true
}
if x.Name == "C" {
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
}
-250
View File
@@ -1,250 +0,0 @@
package loader
// This file parses a fragment of C with libclang and stores the result for AST
// modification. It does not touch the AST itself.
import (
"errors"
"go/ast"
"go/token"
"strconv"
"strings"
"unsafe"
)
/*
#include <clang-c/Index.h> // if this fails, install libclang-7-dev
#include <stdlib.h>
int tinygo_clang_visitor(CXCursor c, CXCursor parent, CXClientData client_data);
*/
import "C"
var globalFileInfo *fileInfo
func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
index := C.clang_createIndex(0, 1)
defer C.clang_disposeIndex(index)
filenameC := C.CString("cgo-fake.c")
defer C.free(unsafe.Pointer(filenameC))
fragmentC := C.CString(fragment)
defer C.free(unsafe.Pointer(fragmentC))
unsavedFile := C.struct_CXUnsavedFile{
Filename: filenameC,
Length: C.ulong(len(fragment)),
Contents: fragmentC,
}
// 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 << 16]*C.char)(cmdargsC)
for i, cflag := range cflags {
s := C.CString(cflag)
cmdargs[i] = s
defer C.free(unsafe.Pointer(s))
}
var unit C.CXTranslationUnit
errCode := C.clang_parseTranslationUnit2(
index,
filenameC,
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
&unsavedFile, 1, // unsaved files
C.CXTranslationUnit_None,
&unit)
if errCode != 0 {
panic("loader: failed to parse source with libclang")
}
defer C.clang_disposeTranslationUnit(unit)
if C.clang_getNumDiagnostics(unit) != 0 {
return errors.New("cgo: libclang cannot parse fragment")
}
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)))
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)
switch kind {
case C.CXCursor_FunctionDecl:
name := getString(C.clang_getCursorSpelling(c))
cursorType := C.clang_getCursorType(c)
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
return C.CXChildVisit_Continue // not supported
}
numArgs := int(C.clang_Cursor_getNumArguments(c))
fn := &functionInfo{}
info.functions[name] = fn
for i := 0; i < numArgs; i++ {
arg := C.clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.clang_getCursorSpelling(arg))
argType := C.clang_getArgType(cursorType, C.uint(i))
if argName == "" {
argName = "$" + strconv.Itoa(i)
}
fn.args = append(fn.args, paramInfo{
name: argName,
typeExpr: info.makeASTType(argType),
})
}
resultType := C.clang_getCursorResultType(c)
if resultType.kind != C.CXType_Void {
fn.results = &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: info.makeASTType(resultType),
},
},
}
}
case C.CXCursor_TypedefDecl:
typedefType := C.clang_getCursorType(c)
name := getString(C.clang_getTypedefName(typedefType))
underlyingType := C.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.clang_getCursorSpelling(c))
cursorType := C.clang_getCursorType(c)
info.globals[name] = &globalInfo{
typeExpr: info.makeASTType(cursorType),
}
}
return C.CXChildVisit_Continue
}
func getString(clangString C.CXString) (s string) {
rawString := C.clang_getCString(clangString)
s = C.GoString(rawString)
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_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",
},
}
}
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,
}
}
+158 -24
View File
@@ -1,6 +1,7 @@
package loader
import (
"bytes"
"errors"
"go/ast"
"go/build"
@@ -10,17 +11,26 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"text/template"
"github.com/tinygo-org/tinygo/cgo"
)
// 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
mainPkg string
Build *build.Context
OverlayBuild *build.Context
OverlayPath func(path string) string
Packages map[string]*Package
sorted []*Package
fset *token.FileSet
TypeChecker types.Config
Dir string // current working directory (for error reporting)
TINYGOROOT string // root of the TinyGo installation or root of the source code
CFlags []string
ClangHeaders string
}
// Package holds a loaded package, its imports, and its parsed files.
@@ -42,7 +52,12 @@ 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 newPath := p.OverlayPath(path); newPath != "" {
ctx = p.OverlayBuild
path = newPath
}
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
if err != nil {
return nil, err
}
@@ -53,6 +68,11 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
return pkg, nil
}
@@ -82,6 +102,11 @@ func (p *Program) ImportFile(path string) (*Package, error) {
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
return pkg, nil
}
@@ -160,10 +185,12 @@ func (p *Program) sort() {
// The returned error may be an Errors error, which contains a list of errors.
//
// Idempotent.
func (p *Program) Parse() error {
func (p *Program) Parse(compileTestBinary bool) error {
includeTests := compileTestBinary
// Load all imports
for _, pkg := range p.Sorted() {
err := pkg.importRecursively()
err := pkg.importRecursively(includeTests)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
if pkg.ImportPath != err.Packages[0] {
@@ -176,7 +203,14 @@ func (p *Program) Parse() error {
// Parse all packages.
for _, pkg := range p.Sorted() {
err := pkg.Parse()
err := pkg.Parse(includeTests)
if err != nil {
return err
}
}
if compileTestBinary {
err := p.SwapTestMain()
if err != nil {
return err
}
@@ -193,6 +227,83 @@ func (p *Program) Parse() error {
return nil
}
func (p *Program) SwapTestMain() error {
var tests []string
isTestFunc := func(f *ast.FuncDecl) bool {
// TODO: improve signature check
if strings.HasPrefix(f.Name.Name, "Test") && f.Name.Name != "TestMain" {
return true
}
return false
}
mainPkg := p.Packages[p.mainPkg]
for _, f := range mainPkg.Files {
for i, d := range f.Decls {
switch v := d.(type) {
case *ast.FuncDecl:
if isTestFunc(v) {
tests = append(tests, v.Name.Name)
}
if v.Name.Name == "main" {
// Remove main
if len(f.Decls) == 1 {
f.Decls = make([]ast.Decl, 0)
} else {
f.Decls[i] = f.Decls[len(f.Decls)-1]
f.Decls = f.Decls[:len(f.Decls)-1]
}
}
}
}
}
// TODO: Check if they defined a TestMain and call it instead of testing.TestMain
const mainBody = `package main
import (
"testing"
)
func main () {
m := &testing.M{
Tests: []testing.TestToCall{
{{range .TestFunctions}}
{Name: "{{.}}", Func: {{.}}},
{{end}}
},
}
testing.TestMain(m)
}
`
tmpl := template.Must(template.New("testmain").Parse(mainBody))
b := bytes.Buffer{}
tmplData := struct {
TestFunctions []string
}{
TestFunctions: tests,
}
err := tmpl.Execute(&b, tmplData)
if err != nil {
return err
}
path := filepath.Join(p.mainPkg, "$testmain.go")
if p.fset == nil {
p.fset = token.NewFileSet()
}
newMain, err := parser.ParseFile(p.fset, path, b.Bytes(), parser.AllErrors)
if err != nil {
return err
}
mainPkg.Files = append(mainPkg.Files, newMain)
return nil
}
// parseFile is a wrapper around parser.ParseFile.
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
if p.fset == nil {
@@ -217,7 +328,7 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
// Parse parses and typechecks this package.
//
// Idempotent.
func (p *Package) Parse() error {
func (p *Package) Parse(includeTests bool) error {
if len(p.Files) != 0 {
return nil
}
@@ -231,7 +342,7 @@ func (p *Package) Parse() error {
return nil
}
files, err := p.parseFiles()
files, err := p.parseFiles(includeTests)
if err != nil {
return err
}
@@ -270,11 +381,21 @@ func (p *Package) Check() error {
}
// parseFiles parses the loaded list of files and returns this list.
func (p *Package) parseFiles() ([]*ast.File, error) {
func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
// TODO: do this concurrently.
var files []*ast.File
var fileErrs []error
for _, file := range p.GoFiles {
var gofiles []string
if includeTests {
gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
gofiles = append(gofiles, p.GoFiles...)
gofiles = append(gofiles, p.TestGoFiles...)
} else {
gofiles = p.GoFiles
}
for _, file := range gofiles {
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
@@ -293,16 +414,23 @@ 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)
continue
}
files = append(files, f)
}
if len(p.CgoFiles) != 0 {
cflags := append(p.CFlags, "-I"+p.Package.Dir)
if p.ClangHeaders != "" {
cflags = append(cflags, "-I"+p.ClangHeaders)
}
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags)
if errs != nil {
fileErrs = append(fileErrs, errs...)
}
files = append(files, generated)
}
if len(fileErrs) != 0 {
return nil, Errors{p, fileErrs}
}
return files, nil
}
@@ -323,11 +451,17 @@ func (p *Package) Import(to string) (*types.Package, error) {
// importRecursively() on the imported packages as well.
//
// Idempotent.
func (p *Package) importRecursively() error {
func (p *Package) importRecursively(includeTests bool) error {
p.Importing = true
for _, to := range p.Package.Imports {
imports := p.Package.Imports
if includeTests {
imports = append(imports, p.Package.TestImports...)
}
for _, to := range imports {
if to == "C" {
// Do Cgo processing in a later stage.
// Do CGo processing in a later stage.
continue
}
if _, ok := p.Imports[to]; ok {
@@ -343,7 +477,7 @@ func (p *Package) importRecursively() error {
if importedPkg.Importing {
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
}
err = importedPkg.importRecursively()
err = importedPkg.importRecursively(false)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
+218 -77
View File
@@ -21,13 +21,6 @@ import (
"github.com/tinygo-org/tinygo/loader"
)
var commands = map[string]string{
"ar": "llvm-ar",
"clang": "clang-7",
"ld.lld": "ld.lld-7",
"wasm-ld": "wasm-ld-7",
}
// commandError is an error type to wrap os/exec.Command errors. This provides
// some more information regarding what went wrong while running a command.
type commandError struct {
@@ -40,16 +33,30 @@ func (e *commandError) Error() string {
return e.Msg + " " + e.File + ": " + e.Err.Error()
}
// multiError is a list of multiple errors (actually: diagnostics) returned
// during LLVM IR generation.
type multiError struct {
Errs []error
}
func (e *multiError) Error() string {
return e.Errs[0].Error()
}
type BuildConfig struct {
opt string
gc string
printIR bool
dumpSSA bool
debug bool
printSizes string
cFlags []string
ldFlags []string
wasmAbi string
opt string
gc string
panicStrategy string
printIR bool
dumpSSA bool
debug bool
printSizes string
cFlags []string
ldFlags []string
tags string
wasmAbi string
heapSize int64
testConfig compiler.TestConfig
}
// Helper function for Compiler object.
@@ -58,23 +65,56 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
config.gc = spec.GC
}
// Append command line passed CFlags and LDFlags
spec.CFlags = append(spec.CFlags, config.cFlags...)
spec.LDFlags = append(spec.LDFlags, config.ldFlags...)
root := sourceDir()
// Merge and adjust CFlags.
cflags := append([]string{}, config.cFlags...)
for _, flag := range spec.CFlags {
cflags = append(cflags, strings.Replace(flag, "{root}", root, -1))
}
// Merge and adjust LDFlags.
ldflags := append([]string{}, config.ldFlags...)
for _, flag := range spec.LDFlags {
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
}
goroot := getGoroot()
if goroot == "" {
return errors.New("cannot locate $GOROOT, please set it manually")
}
tags := spec.BuildTags
major, minor, err := getGorootVersion(goroot)
if err != nil {
return fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 {
return fmt.Errorf("expected major version 1, got go%d.%d", major, minor)
}
for i := 1; i <= minor; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
if extraTags := strings.Fields(config.tags); len(extraTags) != 0 {
tags = append(tags, extraTags...)
}
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,
Triple: spec.Triple,
CPU: spec.CPU,
Features: spec.Features,
GOOS: spec.GOOS,
GOARCH: spec.GOARCH,
GC: config.gc,
PanicStrategy: config.panicStrategy,
CFlags: cflags,
LDFlags: ldflags,
ClangHeaders: getClangHeaderPath(root),
Debug: config.debug,
DumpSSA: config.dumpSSA,
TINYGOROOT: root,
GOROOT: goroot,
GOPATH: getGopath(),
BuildTags: tags,
TestConfig: config.testConfig,
}
c, err := compiler.NewCompiler(pkgName, compilerConfig)
if err != nil {
@@ -82,12 +122,15 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
}
// Compile Go code to IR.
err = c.Compile(pkgName)
if err != nil {
return err
errs := c.Compile(pkgName)
if len(errs) != 0 {
if len(errs) == 1 {
return errs[0]
}
return &multiError{errs}
}
if config.printIR {
fmt.Println("Generated LLVM IR:")
fmt.Println("; Generated LLVM IR:")
fmt.Println(c.IR())
}
if err := c.Verify(); err != nil {
@@ -198,19 +241,26 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
// Prepare link command.
executable := filepath.Join(dir, "main")
tmppath := executable // final file
ldflags := append(spec.LDFlags, "-o", executable, objfile)
ldflags = append(ldflags, "-o", executable, objfile, "-L", root)
if spec.RTLib == "compiler-rt" {
ldflags = append(ldflags, librt)
}
if spec.GOARCH == "wasm" {
// Round heap size to next multiple of 65536 (the WebAssembly page
// size).
heapSize := (config.heapSize + (65536 - 1)) &^ (65536 - 1)
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
}
// Compile extra files.
for i, path := range spec.ExtraFiles {
abspath := filepath.Join(root, path)
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
err := cmd.Run()
cmdNames := []string{spec.Compiler}
if names, ok := commands[spec.Compiler]; ok {
cmdNames = names
}
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, abspath)...)
if err != nil {
return &commandError{"failed to build", path, err}
}
@@ -222,11 +272,11 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
for _, file := range pkg.CFiles {
path := filepath.Join(pkg.Package.Dir, file)
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
err := cmd.Run()
cmdNames := []string{spec.Compiler}
if names, ok := commands[spec.Compiler]; ok {
cmdNames = names
}
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, path)...)
if err != nil {
return &commandError{"failed to build", path, err}
}
@@ -235,7 +285,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
}
// Link the object files together.
err = Link(sourceDir(), spec.Linker, ldflags...)
err = Link(spec.Linker, ldflags...)
if err != nil {
return &commandError{"failed to link", executable, err}
}
@@ -259,19 +309,19 @@ 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 &commandError{"failed to extract " + format + " from", executable, err}
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
}
}
return action(tmppath)
@@ -312,6 +362,33 @@ func Build(pkgName, outpath, target string, config *BuildConfig) error {
})
}
func Test(pkgName, target string, config *BuildConfig) error {
spec, err := LoadTarget(target)
if err != nil {
return err
}
spec.BuildTags = append(spec.BuildTags, "test")
config.testConfig.CompileTestBinary = true
return Compile(pkgName, ".elf", spec, config, func(tmppath string) error {
cmd := exec.Command(tmppath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
// Propagate the exit code
if err, ok := err.(*exec.ExitError); ok {
if status, ok := err.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
}
os.Exit(1)
}
return &commandError{"failed to run compiled binary", tmppath, err}
}
return nil
})
}
func Flash(pkgName, target, port string, config *BuildConfig) error {
spec, err := LoadTarget(target)
if err != nil {
@@ -328,6 +405,8 @@ func Flash(pkgName, target, port string, config *BuildConfig) error {
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?")
}
@@ -468,6 +547,30 @@ func Run(pkgName, target string, config *BuildConfig) error {
})
}
// parseSize converts a human-readable size (with k/m/g suffix) into a plain
// number.
func parseSize(s string) (int64, error) {
s = strings.ToLower(strings.TrimSpace(s))
if len(s) == 0 {
return 0, errors.New("no size provided")
}
multiply := int64(1)
switch s[len(s)-1] {
case 'k':
multiply = 1 << 10
case 'm':
multiply = 1 << 20
case 'g':
multiply = 1 << 30
}
if multiply != 1 {
s = s[:len(s)-1]
}
n, err := strconv.ParseInt(s, 0, 64)
n *= multiply
return n, err
}
func usage() {
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
fmt.Fprintln(os.Stderr, "version:", version)
@@ -475,6 +578,7 @@ func usage() {
fmt.Fprintln(os.Stderr, "\ncommands:")
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
fmt.Fprintln(os.Stderr, " run: compile and run immediately")
fmt.Fprintln(os.Stderr, " test: test packages")
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+cacheDir()+")")
@@ -485,19 +589,24 @@ func usage() {
func handleCompilerError(err error) {
if err != nil {
if errUnsupported, ok := err.(*interp.Unsupported); ok {
switch err := err.(type) {
case *interp.Unsupported:
// hit an unknown/unsupported instruction
fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:")
errUnsupported.Inst.Dump()
err.Inst.Dump()
fmt.Fprintln(os.Stderr)
} else if errCompiler, ok := err.(types.Error); ok {
fmt.Fprintln(os.Stderr, errCompiler)
} else if errLoader, ok := err.(loader.Errors); ok {
fmt.Fprintln(os.Stderr, "#", errLoader.Pkg.ImportPath)
for _, err := range errLoader.Errs {
case types.Error:
fmt.Fprintln(os.Stderr, err)
case loader.Errors:
fmt.Fprintln(os.Stderr, "#", err.Pkg.ImportPath)
for _, err := range err.Errs {
fmt.Fprintln(os.Stderr, err)
}
} else {
case *multiError:
for _, err := range err.Errs {
fmt.Fprintln(os.Stderr, err)
}
default:
fmt.Fprintln(os.Stderr, "error:", err)
}
os.Exit(1)
@@ -507,10 +616,12 @@ func handleCompilerError(err error) {
func main() {
outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, dumb, marksweep)")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
target := flag.String("target", "", "LLVM target")
tags := flag.String("tags", "", "a space-separated list of extra build tags")
target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
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")
@@ -518,6 +629,7 @@ func main() {
cFlags := flag.String("cflags", "", "additional cflags for compiler")
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
@@ -528,13 +640,15 @@ func main() {
flag.CommandLine.Parse(os.Args[2:])
config := &BuildConfig{
opt: *opt,
gc: *gc,
printIR: *printIR,
dumpSSA: *dumpSSA,
debug: !*nodebug,
printSizes: *printSize,
wasmAbi: *wasmAbi,
opt: *opt,
gc: *gc,
panicStrategy: *panicStrategy,
printIR: *printIR,
dumpSSA: *dumpSSA,
debug: !*nodebug,
printSizes: *printSize,
tags: *tags,
wasmAbi: *wasmAbi,
}
if *cFlags != "" {
@@ -545,6 +659,19 @@ func main() {
config.ldFlags = strings.Split(*ldFlags, " ")
}
if *panicStrategy != "print" && *panicStrategy != "trap" {
fmt.Fprintln(os.Stderr, "Panic strategy must be either print or trap.")
usage()
os.Exit(1)
}
var err error
if config.heapSize, err = parseSize(*heapSize); err != nil {
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
usage()
os.Exit(1)
}
os.Setenv("CC", "clang -target="+*target)
switch command {
@@ -554,8 +681,11 @@ func main() {
usage()
os.Exit(1)
}
if flag.NArg() != 1 {
fmt.Fprintln(os.Stderr, "No package specified.")
pkgName := "."
if flag.NArg() == 1 {
pkgName = flag.Arg(0)
} else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified")
usage()
os.Exit(1)
}
@@ -563,7 +693,7 @@ func main() {
if target == "" && filepath.Ext(*outpath) == ".wasm" {
target = "wasm"
}
err := Build(flag.Arg(0), *outpath, target, config)
err := Build(pkgName, *outpath, target, config)
handleCompilerError(err)
case "build-builtins":
// Note: this command is only meant to be used while making a release!
@@ -605,6 +735,17 @@ func main() {
}
err := Run(flag.Arg(0), *target, config)
handleCompilerError(err)
case "test":
pkgName := "."
if flag.NArg() == 1 {
pkgName = flag.Arg(0)
} else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
usage()
os.Exit(1)
}
err := Test(pkgName, *target, config)
handleCompilerError(err)
case "clean":
// remove cache directory
dir := cacheDir()
+27 -8
View File
@@ -13,17 +13,19 @@ import (
"runtime"
"sort"
"testing"
"github.com/tinygo-org/tinygo/loader"
)
const TESTDATA = "testdata"
func TestCompiler(t *testing.T) {
matches, err := filepath.Glob(TESTDATA + "/*.go")
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
if err != nil {
t.Fatal("could not read test files:", err)
}
dirMatches, err := filepath.Glob(TESTDATA + "/*/main.go")
dirMatches, err := filepath.Glob(filepath.Join(TESTDATA, "*", "main.go"))
if err != nil {
t.Fatal("could not read test packages:", err)
}
@@ -64,21 +66,31 @@ func TestCompiler(t *testing.T) {
if runtime.GOOS == "linux" {
t.Log("running tests for linux/arm...")
for _, path := range matches {
if path == "testdata/cgo/" {
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
continue // TODO: improve CGo
}
t.Run(path, func(t *testing.T) {
runTest(path, tmpdir, "arm--linux-gnueabi", t)
runTest(path, tmpdir, "arm--linux-gnueabihf", t)
})
}
t.Log("running tests for linux/arm64...")
for _, path := range matches {
if path == "testdata/cgo/" {
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
continue // TODO: improve CGo
}
t.Run(path, func(t *testing.T) {
runTest(path, tmpdir, "aarch64--linux-gnueabi", t)
runTest(path, tmpdir, "aarch64--linux-gnu", t)
})
}
t.Log("running tests for WebAssembly...")
for _, path := range matches {
if path == filepath.Join("testdata", "gc.go") {
continue // known to fail
}
t.Run(path, func(t *testing.T) {
runTest(path, tmpdir, "wasm", t)
})
}
}
@@ -87,7 +99,7 @@ func TestCompiler(t *testing.T) {
func runTest(path, tmpdir string, target string, t *testing.T) {
// Get the expected output for this test.
txtpath := path[:len(path)-3] + ".txt"
if path[len(path)-1] == '/' {
if path[len(path)-1] == os.PathSeparator {
txtpath = path + "out.txt"
}
f, err := os.Open(txtpath)
@@ -106,11 +118,18 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
dumpSSA: false,
debug: false,
printSizes: "",
wasmAbi: "js",
}
binary := filepath.Join(tmpdir, "test")
err = Build("./"+path, binary, target, config)
if err != nil {
t.Log("failed to build:", err)
if errLoader, ok := err.(loader.Errors); ok {
for _, err := range errLoader.Errs {
t.Log("failed to build:", err)
}
} else {
t.Log("failed to build:", err)
}
t.Fail()
return
}
+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")
}
}
+9 -11
View File
@@ -30,6 +30,7 @@
package arm
import (
"runtime/volatile"
"unsafe"
)
@@ -69,9 +70,6 @@ func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
// Run the following system call (SVCall) with 4 arguments.
func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
//go:volatile
type RegValue uint32
const (
SCS_BASE = 0xE000E000
NVIC_BASE = SCS_BASE + 0x0100
@@ -82,24 +80,24 @@ const (
// Source:
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CIHIGCIF.html
type NVIC_Type struct {
ISER [8]RegValue // Interrupt Set-enable Registers
ISER [8]volatile.Register32 // Interrupt Set-enable Registers
_ [24]uint32
ICER [8]RegValue // Interrupt Clear-enable Registers
ICER [8]volatile.Register32 // Interrupt Clear-enable Registers
_ [24]uint32
ISPR [8]RegValue // Interrupt Set-pending Registers
ISPR [8]volatile.Register32 // Interrupt Set-pending Registers
_ [24]uint32
ICPR [8]RegValue // Interrupt Clear-pending Registers
ICPR [8]volatile.Register32 // Interrupt Clear-pending Registers
_ [24]uint32
IABR [8]RegValue // Interrupt Active Bit Registers
IABR [8]volatile.Register32 // Interrupt Active Bit Registers
_ [56]uint32
IPR [60]RegValue // Interrupt Priority Registers
IPR [60]volatile.Register32 // Interrupt Priority Registers
}
var NVIC = (*NVIC_Type)(unsafe.Pointer(uintptr(NVIC_BASE)))
// Enable the given interrupt number.
func EnableIRQ(irq uint32) {
NVIC.ISER[irq>>5] = 1 << (irq & 0x1F)
NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F))
}
// Set the priority of the given interrupt number.
@@ -116,7 +114,7 @@ func SetPriority(irq uint32, priority uint32) {
regpos := irq % 4
mask := uint32(0xff) << (regpos * 8) // bits to clear
priority = priority << (regpos * 8) // bits to set
NVIC.IPR[regnum] = RegValue((uint32(NVIC.IPR[regnum]) &^ mask) | priority)
NVIC.IPR[regnum].Set((uint32(NVIC.IPR[regnum].Get()) &^ mask) | priority)
}
// DisableInterrupts disables all interrupts, and returns the old state.
+21
View File
@@ -0,0 +1,21 @@
.syntax unified
.section .text.HardFault_Handler
.global HardFault_Handler
.type HardFault_Handler, %function
HardFault_Handler:
// Put the old stack pointer in the first argument, for easy debugging. This
// is especially useful on Cortex-M0, which supports far fewer debug
// facilities.
mov r0, sp
// Load the default stack pointer from address 0 so that we can call normal
// functions again that expect a working stack. However, it will corrupt the
// old stack so the function below must not attempt to recover from this
// fault.
movs r3, #0
ldr r3, [r3]
mov sp, r3
// Continue handling this error in Go.
bl handleHardFault
+10
View File
@@ -0,0 +1,10 @@
package riscv
// Run the given assembly code. The code will be marked as having side effects,
// as it doesn't produce output and thus would normally be eliminated by the
// optimizer.
func Asm(asm string)
// ReadRegister returns the contents of the specified register. The register
// must be a processor register, reachable with the "mov" instruction.
func ReadRegister(name string) uintptr
+13
View File
@@ -0,0 +1,13 @@
.section .init
.global _start
.type _start,@function
_start:
// Workaround for missing support of the la pseudo-instruction in Clang 8:
// https://reviews.llvm.org/D55325
lui sp, %hi(_stack_top)
addi sp, sp, %lo(_stack_top)
// see https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register
lui gp, %hi(__global_pointer$)
addi gp, gp, %lo(__global_pointer$)
call main
+2 -2
View File
@@ -11,8 +11,8 @@ import (
func main() {
machine.InitADC()
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
sensor := machine.ADC{machine.ADC2}
sensor.Configure()
+2 -2
View File
@@ -8,8 +8,8 @@ import (
)
func main() {
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
for {
led.Low()
time.Sleep(time.Millisecond * 500)
+4 -4
View File
@@ -16,8 +16,8 @@ func main() {
}
func led1() {
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led := machine.LED1
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
for {
println("+")
led.Low()
@@ -30,8 +30,8 @@ func led1() {
}
func led2() {
led := machine.GPIO{machine.LED2}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led := machine.LED2
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
for {
println(" +")
led.Low()
+6 -6
View File
@@ -7,14 +7,14 @@ import (
// This example assumes that the button is connected to pin 8. Change the value
// below to use a different pin.
const buttonPin = 8
const (
led = machine.LED
button = machine.Pin(8)
)
func main() {
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
button := machine.GPIO{buttonPin}
button.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT})
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
button.Configure(machine.PinConfig{Mode: machine.PinInput})
for {
if button.Get() {
+16 -16
View File
@@ -8,29 +8,29 @@ import (
// This example assumes that you are using the pca10040 board
func main() {
led1 := machine.GPIO{machine.LED1}
led1.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led1 := machine.LED1
led1.Configure(machine.PinConfig{Mode: machine.PinOutput})
led2 := machine.GPIO{machine.LED2}
led2.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led2 := machine.LED2
led2.Configure(machine.PinConfig{Mode: machine.PinOutput})
led3 := machine.GPIO{machine.LED3}
led3.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led3 := machine.LED3
led3.Configure(machine.PinConfig{Mode: machine.PinOutput})
led4 := machine.GPIO{machine.LED4}
led4.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led4 := machine.LED4
led4.Configure(machine.PinConfig{Mode: machine.PinOutput})
button1 := machine.GPIO{machine.BUTTON1}
button1.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
button1 := machine.BUTTON1
button1.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
button2 := machine.GPIO{machine.BUTTON2}
button2.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
button2 := machine.BUTTON2
button2.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
button3 := machine.GPIO{machine.BUTTON3}
button3.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
button3 := machine.BUTTON3
button3.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
button4 := machine.GPIO{machine.BUTTON4}
button4.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
button4 := machine.BUTTON4
button4.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
for {
led1.Set(button1.Get())
-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)
}
}
+3 -3
View File
@@ -9,9 +9,9 @@ import (
// 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
uart = machine.UART0
tx = machine.UART_TX_PIN
rx = machine.UART_RX_PIN
)
func main() {
+25
View File
@@ -0,0 +1,25 @@
// Example using the i2s hardware interface on the Adafruit Circuit Playground Express
// to read data from the onboard MEMS microphone.
//
package main
import (
"machine"
)
func main() {
machine.I2S0.Configure(machine.I2SConfig{
Mode: machine.I2SModePDM,
ClockSource: machine.I2SClockSourceExternal,
Stereo: true,
})
data := make([]uint32, 64)
for {
// get the next group of samples
machine.I2S0.Read(data)
println("data", data[0], data[1], data[2], data[4], "...")
}
}
+3 -5
View File
@@ -8,19 +8,17 @@ import (
"time"
)
// CS_PIN is the pin used for Chip Select (CS). Change to whatever is in use on your board.
const CS_PIN = 3
// cs is the pin used for Chip Select (CS). Change to whatever is in use on your board.
const cs = machine.Pin(3)
var (
tx []byte
rx []byte
val, result uint16
cs machine.GPIO
)
func main() {
cs = machine.GPIO{CS_PIN}
cs.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.SPI0.Configure(machine.SPIConfig{
Frequency: 4000000,
@@ -0,0 +1,24 @@
// blink program for the BBC micro:bit
package main
import (
"machine"
"time"
)
// The LED matrix in the micro:bit is a multiplexed display: https://en.wikipedia.org/wiki/Multiplexed_display
// Driver for easier control: https://github.com/tinygo-org/drivers/tree/master/microbitmatrix
func main() {
ledrow := machine.LED_ROW_1
ledrow.Configure(machine.PinConfig{Mode: machine.PinOutput})
ledcol := machine.LED_COL_1
ledcol.Configure(machine.PinConfig{Mode: machine.PinOutput})
ledcol.Low()
for {
ledrow.Low()
time.Sleep(time.Millisecond * 500)
ledrow.High()
time.Sleep(time.Millisecond * 500)
}
}
+1
View File
@@ -0,0 +1 @@
html/*
+20
View File
@@ -0,0 +1,20 @@
export: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./export/wasm.go
cp ./export/wasm.js ./html/
cp ./export/index.html ./html/
callback: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm ./callback/wasm.go
cp ./callback/wasm.js ./html/
cp ./callback/index.html ./html/
main: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./main/main.go
cp ./main/index.html ./html/
wasm_exec:
cp ../../../targets/wasm_exec.js ./html/
clean:
rm -rf ./html
mkdir ./html
+131
View File
@@ -0,0 +1,131 @@
# 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 web server:
```bash
$ go run main.go
Serving ./html on http://localhost:8080
```
Use your web browser to visit http://localhost:8080.
* The wasm "export" example displays a simple math equation using HTML, with
the result calculated dynamically using WebAssembly. Changing any of the
values on the left hand side triggers the exported wasm `update` function to
recalculate the result.
* The wasm "main" example uses `println` to write to your browser JavaScript
console. You may need to open the browser development tools console to see it.
## How it works
Execution of the contents require a few JavaScript 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 the JavaScript, it is important the wasm file is served with the
[`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)
header set to `application/wasm`. Without it, most browsers won't run it.
```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) {
resp.Header().Add("Cache-Control", "no-cache")
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.
For development purposes (**only!**), it also sets the `Cache-Control` header
so your browser doesn't cache the files. This is useful while developing, to
ensure your browser displays the newest wasm when you recompile.
In a production environment you **probably wouldn't** want to set the
`Cache-Control` header like this. Caching is generally beneficial for end
users.
Further information on the `Cache-Control` header can be found here:
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
+19
View File
@@ -0,0 +1,19 @@
<!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="0" /> + <input type="number" id="b" value="0" /> = <input type="number" id="result" readonly />
</body>
</html>
+27
View File
@@ -0,0 +1,27 @@
package main
import (
"strconv"
"syscall/js"
)
var a, b int
func main() {
document := js.Global().Get("document")
document.Call("getElementById", "a").Set("oninput", updater(&a))
document.Call("getElementById", "b").Set("oninput", updater(&b))
update()
}
func updater(n *int) js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
*n, _ = strconv.Atoi(this.Get("value").String())
update()
return nil
})
}
func update() {
js.Global().Get("document").Call("getElementById", "result").Set("value", a+b)
}
+26
View File
@@ -0,0 +1,26 @@
'use strict';
const WASM_URL = 'wasm.wasm';
var wasm;
function init() {
const go = new Go();
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);
})
)
}
}
init();
+20
View File
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Go WebAssembly</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="wasm_exec.js" defer></script>
<script src="wasm.js" defer></script>
</head>
<body>
<h1>WebAssembly</h1>
<p>Add two numbers, using WebAssembly:</p>
<input type="number" id="a" value="2" /> + <input type="number" id="b" value="2" /> = <input type="number"
id="result" readonly />
</body>
</html>
@@ -16,10 +16,10 @@ func add(a, b int) int {
//go:export update
func update() {
document := js.Global().Get("document")
a_str := document.Call("getElementById", "a").Get("value").String()
b_str := document.Call("getElementById", "b").Get("value").String()
a, _ := strconv.Atoi(a_str)
b, _ := strconv.Atoi(b_str)
result := a + b
aStr := document.Call("getElementById", "a").Get("value").String()
bStr := document.Call("getElementById", "b").Get("value").String()
a, _ := strconv.Atoi(aStr)
b, _ := strconv.Atoi(bStr)
result := add(a, b)
document.Call("getElementById", "result").Set("value", result)
}
@@ -1,6 +1,6 @@
'use strict';
const WASM_URL = '../../../wasm.wasm';
const WASM_URL = 'wasm.wasm';
var wasm;
@@ -14,7 +14,7 @@ function init() {
const go = new Go();
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function(obj) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
updateResult();
@@ -23,7 +23,7 @@ function init() {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function(obj) {
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
updateResult();
+8
View File
@@ -0,0 +1,8 @@
# WebAssembly main execution example
A simple hello world that prints to the browser console.
## License
Note that `index.html` is copied almost verbatim from the Go 1.12 source at
`$GOROOT/misc/wasm/wasm_exec.html`. Its license applies to this file.
+49
View File
@@ -0,0 +1,49 @@
<!doctype html>
<!--
Copyright 2018 The Go Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<html>
<head>
<meta charset="utf-8">
<title>Go wasm</title>
</head>
<body>
<!--
Add the following polyfill for Microsoft Edge 17/18 support:
<script src="https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js"></script>
(see https://caniuse.com/#feat=textencoder)
-->
<script src="wasm_exec.js"></script>
<script>
if (!WebAssembly.instantiateStreaming) { // polyfill
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}
const go = new Go();
let mod, inst;
WebAssembly.instantiateStreaming(fetch("wasm.wasm"), go.importObject).then((result) => {
mod = result.module;
inst = result.instance;
document.getElementById("runButton").disabled = false;
}).catch((err) => {
console.error(err);
});
async function run() {
console.clear();
await go.run(inst);
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
}
</script>
<button onClick="run();" id="runButton" disabled>Run</button>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
package main
func main() {
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) {
resp.Header().Add("Cache-Control", "no-cache")
if strings.HasSuffix(req.URL.Path, ".wasm") {
resp.Header().Set("content-type", "application/wasm")
}
fs.ServeHTTP(resp, req)
}))
}
-16
View File
@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Go WebAssembly</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<script src="../../../targets/wasm_exec.js" defer></script>
<script src="wasm.js" defer></script>
</head>
<body>
<h1>WebAssembly</h1>
<p>Add two numbers, using WebAssembly:</p>
<input type="number" id="a" value="2"/> + <input type="number" id="b" value="2"/> = <input type="number" id="result" readonly/>
</body>
</html>
+10 -10
View File
@@ -1,24 +1,24 @@
// +build avr,arduino
// +build arduino
package machine
const CPU_FREQUENCY = 16000000
// LED on the Arduino
const LED = 13
const LED Pin = 13
// ADC on the Arduino
const (
ADC0 = 0
ADC1 = 1
ADC2 = 2
ADC3 = 3
ADC4 = 4 // Used by TWI for SDA
ADC5 = 5 // Used by TWI for SCL
ADC0 Pin = 0
ADC1 Pin = 1
ADC2 Pin = 2
ADC3 Pin = 3
ADC4 Pin = 4 // Used by TWI for SDA
ADC5 Pin = 5 // Used by TWI for SCL
)
// UART pins
const (
UART_TX_PIN = 1
UART_RX_PIN = 0
UART_TX_PIN Pin = 1
UART_RX_PIN Pin = 0
)
+138
View File
@@ -0,0 +1,138 @@
// +build sam,atsamd21,arduino_nano33
// This contains the pin mappings for the Arduino Nano33 IoT board.
//
// For more information, see: https://store.arduino.cc/nano-33-iot
//
package machine
import "device/sam"
// GPIO Pins
const (
RX0 Pin = PB23 // UART2 RX
TX1 Pin = PB22 // UART2 TX
D2 Pin = PB10 // PWM available
D3 Pin = PB11 // PWM available
D4 Pin = PA07
D5 Pin = PA05 // PWM available
D6 Pin = PA04 // PWM available
D7 Pin = PA06
D8 Pin = PA18
D9 Pin = PA20 // PWM available
D10 Pin = PA21 // PWM available
D11 Pin = PA16 // PWM available
D12 Pin = PA19 // PWM available
D13 Pin = PA17
)
// Analog pins
const (
A0 Pin = PA02 // ADC/AIN[0]
A1 Pin = PB02 // ADC/AIN[10]
A2 Pin = PA11 // ADC/AIN[19]
A3 Pin = PA10 // ADC/AIN[18]
A4 Pin = PB08 // ADC/AIN[2], SCL: SERCOM2/PAD[1]
A5 Pin = PB09 // ADC/AIN[3], SDA: SERCOM2/PAD[1]
A6 Pin = PA09 // ADC/AIN[17]
A7 Pin = PB03 // ADC/AIN[11]
)
const (
LED = D13
)
// NINA-W102 Pins
const (
NINA_MOSI Pin = PA12
NINA_MISO Pin = PA13
NINA_CS Pin = PA14
NINA_SCK Pin = PA15
NINA_GPIO0 Pin = PA27
NINA_RESETN Pin = PA08
NINA_ACK Pin = PA28
)
// UART0 aka USBCDC pins
const (
USBCDC_DM_PIN Pin = PA24
USBCDC_DP_PIN Pin = PA25
)
// UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip.
var (
UART1 = UART{Bus: sam.SERCOM5_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOMAlt,
IRQVal: sam.IRQ_SERCOM5,
}
)
// UART1 pins
const (
UART_TX_PIN Pin = PA22
UART_RX_PIN Pin = PA23
)
//go:export SERCOM5_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// UART2 on the Arduino Nano 33 connects to the normal TX/RX pins.
var (
UART2 = UART{Bus: sam.SERCOM3_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOMAlt,
IRQVal: sam.IRQ_SERCOM3,
}
)
//go:export SERCOM3_IRQHandler
func handleUART2() {
// should reset IRQ
UART2.Receive(byte((UART2.Bus.DATA.Get() & 0xFF)))
UART2.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
}
// I2C pins
const (
SDA_PIN Pin = A4 // SDA: SERCOM4/PAD[1]
SCL_PIN Pin = A5 // SCL: SERCOM4/PAD[1]
)
// I2C on the Arduino Nano 33.
var (
I2C0 = I2C{Bus: sam.SERCOM4_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: PinSERCOMAlt}
)
// SPI pins
const (
SPI0_SCK_PIN Pin = PB11 // SCK: SERCOM4/PAD[3]
SPI0_MOSI_PIN Pin = PB10 // MOSI: SERCOM4/PAD[2]
SPI0_MISO_PIN Pin = PA12 // MISO: SERCOM4/PAD[0]
)
// SPI on the Arduino Nano 33.
var (
SPI0 = SPI{Bus: sam.SERCOM1_SPI}
)
// I2S pins
const (
I2S_SCK_PIN Pin = PA10
I2S_SD_PIN Pin = PA08
I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33.
)
// I2S on the Arduino Nano 33.
var (
I2S0 = I2S{Bus: sam.I2S}
)
+1 -1
View File
@@ -1,4 +1,4 @@
// +build stm32,bluepill
// +build bluepill
package machine
+62 -5
View File
@@ -2,6 +2,8 @@
package machine
import "device/sam"
// GPIO Pins
const (
D0 = PB09
@@ -15,7 +17,7 @@ const (
D8 = PB23
D9 = PA06
D10 = PA07
D11 = 0xff // does not seem to exist
D11 = NoPin // does not seem to exist
D12 = PA02
D13 = PA17 // PWM available
)
@@ -51,20 +53,75 @@ const (
PROXIMITY = A10
)
// USBCDC pins
// USBCDC pins (logical UART0)
const (
USBCDC_DM_PIN = PA24
USBCDC_DP_PIN = PA25
)
// UART0 pins
// UART0 pins (logical UART1)
const (
UART_TX_PIN = PB08 // PORTB
UART_RX_PIN = PB09 // PORTB
)
// UART1 on the Circuit Playground Express.
var (
UART1 = UART{Bus: sam.SERCOM1_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOM,
IRQVal: sam.IRQ_SERCOM1,
}
)
//go:export SERCOM1_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// I2C pins
const (
SDA_PIN = PA00 // SDA: SERCOM3/PAD[0]
SCL_PIN = PA01 // SCL: SERCOM3/PAD[1]
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: PinSERCOM}
// internal device
I2C1 = I2C{Bus: sam.SERCOM1_I2CM,
SDA: SDA1_PIN,
SCL: SCL1_PIN,
PinMode: PinSERCOMAlt}
)
// 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}
)
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_WS_PIN = NoPin // no WS, instead uses SCK to sync
)
// I2S on the Circuit Playground Express.
var (
I2S0 = I2S{Bus: sam.I2S}
)
+2 -2
View File
@@ -1,7 +1,7 @@
// +build attiny85,digispark
// +build digispark
package machine
const (
LED = 1
LED Pin = 1
)
+38
View File
@@ -0,0 +1,38 @@
// +build hifive1b
package machine
const (
P00 Pin = 0
P01 Pin = 1
P02 Pin = 2
P03 Pin = 3
P04 Pin = 4
P05 Pin = 5
P06 Pin = 6
P07 Pin = 7
P08 Pin = 8
P09 Pin = 9
P10 Pin = 10
P11 Pin = 11
P12 Pin = 12
P13 Pin = 13
P14 Pin = 14
P15 Pin = 15
P16 Pin = 16
P17 Pin = 17
P18 Pin = 18
P19 Pin = 19
P20 Pin = 20
P21 Pin = 21
P22 Pin = 22
P23 Pin = 23
P24 Pin = 24
P25 Pin = 25
P26 Pin = 26
P27 Pin = 27
P28 Pin = 28
P29 Pin = 29
P30 Pin = 30
P31 Pin = 31
)
+96
View File
@@ -0,0 +1,96 @@
// +build sam,atsamd21,feather_m0
package machine
import "device/sam"
// GPIO Pins
const (
D0 = PA11 // UART0 RX
D1 = PA10 // UART0 TX
D2 = NoPin // does not seem to exist
D3 = PA09
D4 = PA08
D5 = PA15 // PWM available
D6 = PA20 // PWM available
D7 = NoPin // does not seem to exist
D8 = PA06
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
)
// UART1 on the Feather M0.
var (
UART1 = UART{Bus: sam.SERCOM1_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOM,
IRQVal: sam.IRQ_SERCOM1,
}
)
//go:export SERCOM1_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// I2C pins
const (
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
SCL_PIN = PA23 // SCL: SERCOM3/PAD[1]
)
// I2C on the Feather M0.
var (
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: PinSERCOM}
)
// 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 Feather M0.
var (
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
)
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0.
)
+19
View File
@@ -0,0 +1,19 @@
// +build hifive1b
package machine
const (
LED = LED1
LED1 = LED_RED
LED2 = LED_GREEN
LED3 = LED_BLUE
LED_RED = P22
LED_GREEN = P19
LED_BLUE = P21
)
const (
// TODO: figure out the pin numbers for these.
UART_TX_PIN = NoPin
UART_RX_PIN = NoPin
)
+50 -2
View File
@@ -2,6 +2,8 @@
package machine
import "device/sam"
// GPIO Pins
const (
D0 = PA11 // UART0 RX
@@ -42,12 +44,58 @@ const (
// UART1 pins
const (
UART_TX_PIN = D1
UART_RX_PIN = D0
UART_TX_PIN = D10
UART_RX_PIN = D11
)
// UART1 on the ItsyBitsy M0.
var (
UART1 = UART{Bus: sam.SERCOM1_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOM,
IRQVal: sam.IRQ_SERCOM1,
}
)
//go:export SERCOM1_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// 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: PinSERCOM}
)
// 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}
)
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_WS_PIN = NoPin // TODO: figure out what this is on ItsyBitsy M0.
)
// I2S on the ItsyBitsy M0.
var (
I2S0 = I2S{Bus: sam.I2S}
)
+43 -110
View File
@@ -1,143 +1,76 @@
// +build nrf51,microbit
// +build microbit
package machine
import (
"device/nrf"
"errors"
)
// The micro:bit does not have a 32kHz crystal on board.
const HasLowFrequencyCrystal = false
// Buttons on the micro:bit (A and B)
const (
BUTTON = BUTTONA
BUTTONA = 17
BUTTONB = 26
BUTTON Pin = BUTTONA
BUTTONA Pin = 17
BUTTONB Pin = 26
)
// UART pins
const (
UART_TX_PIN = 24
UART_RX_PIN = 25
UART_TX_PIN Pin = 24
UART_RX_PIN Pin = 25
)
// ADC pins
const (
ADC0 = 3 // P0 on the board
ADC1 = 2 // P1 on the board
ADC2 = 1 // P2 on the board
ADC0 Pin = 3 // P0 on the board
ADC1 Pin = 2 // P1 on the board
ADC2 Pin = 1 // P2 on the board
)
// I2C pins
const (
SDA_PIN = 30 // P20 on the board
SCL_PIN = 0 // P19 on the board
SDA_PIN Pin = 30 // P20 on the board
SCL_PIN Pin = 0 // P19 on the board
)
// SPI pins
const (
SPI0_SCK_PIN = 23 // P13 on the board
SPI0_MOSI_PIN = 21 // P15 on the board
SPI0_MISO_PIN = 22 // P14 on the board
SPI0_SCK_PIN Pin = 23 // P13 on the board
SPI0_MOSI_PIN Pin = 21 // P15 on the board
SPI0_MISO_PIN 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
P0 Pin = 3
P1 Pin = 2
P2 Pin = 1
P3 Pin = 4
P4 Pin = 5
P5 Pin = 17
P6 Pin = 12
P7 Pin = 11
P8 Pin = 18
P9 Pin = 10
P10 Pin = 6
P11 Pin = 26
P12 Pin = 20
P13 Pin = 23
P14 Pin = 22
P15 Pin = 21
P16 Pin = 16
)
// LED matrix pins
const (
LED_COL_1 = 4
LED_COL_2 = 5
LED_COL_3 = 6
LED_COL_4 = 7
LED_COL_5 = 8
LED_COL_6 = 9
LED_COL_7 = 10
LED_COL_8 = 11
LED_COL_9 = 12
LED_ROW_1 = 13
LED_ROW_2 = 14
LED_ROW_3 = 15
LED_COL_1 Pin = 4
LED_COL_2 Pin = 5
LED_COL_3 Pin = 6
LED_COL_4 Pin = 7
LED_COL_5 Pin = 8
LED_COL_6 Pin = 9
LED_COL_7 Pin = 10
LED_COL_8 Pin = 11
LED_COL_9 Pin = 12
LED_ROW_1 Pin = 13
LED_ROW_2 Pin = 14
LED_ROW_3 Pin = 15
)
// matrixSettings has the legs of the LED grid in the form {row, column} for each LED position.
var matrixSettings = [5][5][2]uint8{
{{LED_ROW_1, LED_COL_1}, {LED_ROW_2, LED_COL_4}, {LED_ROW_1, LED_COL_2}, {LED_ROW_2, LED_COL_5}, {LED_ROW_1, LED_COL_3}},
{{LED_ROW_3, LED_COL_4}, {LED_ROW_3, LED_COL_5}, {LED_ROW_3, LED_COL_6}, {LED_ROW_3, LED_COL_7}, {LED_ROW_3, LED_COL_8}},
{{LED_ROW_2, LED_COL_2}, {LED_ROW_1, LED_COL_9}, {LED_ROW_2, LED_COL_3}, {LED_ROW_3, LED_COL_9}, {LED_ROW_2, LED_COL_1}},
{{LED_ROW_1, LED_COL_8}, {LED_ROW_1, LED_COL_7}, {LED_ROW_1, LED_COL_6}, {LED_ROW_1, LED_COL_5}, {LED_ROW_1, LED_COL_4}},
{{LED_ROW_3, LED_COL_3}, {LED_ROW_2, LED_COL_7}, {LED_ROW_3, LED_COL_1}, {LED_ROW_2, LED_COL_6}, {LED_ROW_3, LED_COL_2}}}
// InitLEDMatrix initializes the LED matrix, by setting all of the row/col pins to output
// then calling ClearLEDMatrix.
func InitLEDMatrix() {
set := 0
for i := LED_COL_1; i <= LED_ROW_3; i++ {
set |= 1 << uint8(i)
}
nrf.GPIO.DIRSET = nrf.RegValue(set)
ClearLEDMatrix()
}
// ClearLEDMatrix clears the entire LED matrix.
func ClearLEDMatrix() {
set := 0
for i := LED_COL_1; i <= LED_COL_9; i++ {
set |= 1 << uint8(i)
}
nrf.GPIO.OUTSET = nrf.RegValue(set)
nrf.GPIO.OUTCLR = (1 << LED_ROW_1) | (1 << LED_ROW_2) | (1 << LED_ROW_3)
}
// SetLEDMatrix turns on a single LED on the LED matrix.
// Currently limited to a single LED at a time, it will clear the matrix before setting it.
func SetLEDMatrix(x, y uint8) error {
if x > 4 || y > 4 {
return errors.New("Invalid LED matrix row or column")
}
// Clear matrix
ClearLEDMatrix()
nrf.GPIO.OUTSET = (1 << matrixSettings[y][x][0])
nrf.GPIO.OUTCLR = (1 << matrixSettings[y][x][1])
return nil
}
// SetEntireLEDMatrixOn turns on all of the LEDs on the LED matrix.
func SetEntireLEDMatrixOn() error {
set := 0
for i := LED_ROW_1; i <= LED_ROW_3; i++ {
set |= 1 << uint8(i)
}
nrf.GPIO.OUTSET = nrf.RegValue(set)
set = 0
for i := LED_COL_1; i <= LED_COL_9; i++ {
set |= 1 << uint8(i)
}
nrf.GPIO.OUTCLR = nrf.RegValue(set)
return nil
}
+11 -11
View File
@@ -6,27 +6,27 @@ const HasLowFrequencyCrystal = true
// LEDs on the nrf52840-mdk (nRF52840 dev board)
const (
LED = LED_GREEN
LED_GREEN = 22
LED_RED = 23
LED_BLUE = 24
LED Pin = LED_GREEN
LED_GREEN Pin = 22
LED_RED Pin = 23
LED_BLUE Pin = 24
)
// UART pins
const (
UART_TX_PIN = 20
UART_RX_PIN = 19
UART_TX_PIN Pin = 20
UART_RX_PIN Pin = 19
)
// I2C pins (unused)
const (
SDA_PIN = 0xff
SCL_PIN = 0xff
SDA_PIN = NoPin
SCL_PIN = NoPin
)
// SPI pins (unused)
const (
SPI0_SCK_PIN = 0
SPI0_MOSI_PIN = 0
SPI0_MISO_PIN = 0
SPI0_SCK_PIN = NoPin
SPI0_MOSI_PIN = NoPin
SPI0_MISO_PIN = NoPin
)
+15 -15
View File
@@ -1,4 +1,4 @@
// +build nrf51,pca10031
// +build pca10031
// pca10031 is a nrf51 based dongle, intended for use in wireless applications.
//
@@ -10,30 +10,30 @@ const HasLowFrequencyCrystal = true
// LED on the pca10031
const (
LED = LED_RED
LED1 = LED_RED
LED2 = LED_GREEN
LED3 = LED_BLUE
LED_RED = 21
LED_GREEN = 22
LED_BLUE = 23
LED Pin = LED_RED
LED1 Pin = LED_RED
LED2 Pin = LED_GREEN
LED3 Pin = LED_BLUE
LED_RED Pin = 21
LED_GREEN Pin = 22
LED_BLUE Pin = 23
)
// UART pins
const (
UART_TX_PIN = 9
UART_RX_PIN = 11
UART_TX_PIN Pin = 9
UART_RX_PIN Pin = 11
)
// I2C pins (disabled)
const (
SDA_PIN = 0xff
SCL_PIN = 0xff
SDA_PIN = NoPin
SCL_PIN = NoPin
)
// SPI pins (unused)
const (
SPI0_SCK_PIN = 0
SPI0_MOSI_PIN = 0
SPI0_MISO_PIN = 0
SPI0_SCK_PIN = NoPin
SPI0_MOSI_PIN = NoPin
SPI0_MISO_PIN = NoPin
)

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