Compare commits

...

125 Commits

Author SHA1 Message Date
sago35 20883b9fac build: make msd flash faster 2021-03-15 23:07:27 +09:00
ardnew 99b129d366 Add board support for Adafruit Grand Central M4 (SAMD51) (#1714)
machine/grandcentral-m4: implementation for Adafruit Grand Central M4 board.
2021-03-15 14:06:09 +01:00
Federico G. Schwindt 303acf5ed6 Upgrade WASI version to wasi_snapshot_preview1 (#1691)
wasm: Upgrade WASI version to wasi_snapshot_preview1

Addresses #1647.
2021-03-15 12:41:37 +01:00
ardnew 7d1ce24be5 fix data shift/mask in func newUSBSetup 2021-03-14 23:58:25 +01:00
ardnew 774c86e21b goenv: use physical path instead of cached GOROOT in function getGoroot (fixes #433, #1658) 2021-03-14 22:26:44 +01:00
sago35 0cabd4de69 atsamd5x: improve SPI 2021-03-14 15:40:53 +01:00
Thomas Tromp 56bbc5bf6d Fix fe310 SPI read 2021-03-13 08:56:15 +01:00
ardnew 0b44d0bcc5 add UART0 as alias for UART1 2021-03-13 07:34:44 +01:00
ardnew 6275b3a8d1 teensy40: move txBuffer allocation to UART declaration 2021-03-13 07:34:44 +01:00
Ayke van Laethem 34b50efdcd interp: support GEP on fixed (MMIO) addresses
GetElementPtr would not work on values that weren't pointers. Because
fixed addresses (often used in memory-mapped I/O) are integers rather
than pointers in interp, it would return an error.

This resulted in the teensy40 target not compiling correctly since the
interp package rewrite. This commit should fix that.
2021-03-12 12:35:06 +01:00
Ayke van Laethem 42088f938e attiny: remove dummy UART
I think it's better not to provide a UART0 global at all than one that
does nothing.
2021-03-10 22:28:58 +01:00
Ayke van Laethem c60c36f0a8 all: use ExitCode() call to get exit code of exited process
This is an addition that landed in Go 1.12 but we couldn't use before
because we were supporting Go back until Go 1.11. It simplifies the code
around processes a bit.
2021-03-09 18:15:49 +01:00
Ayke van Laethem e3aa13c2a6 all: replace strings.Replace with strings.ReplaceAll
This was an addition to Go 1.13 and results in slightly easier to read
code.
2021-03-09 18:15:49 +01:00
Ayke van Laethem b0366743cd all: remove support for Go 1.11 and 1.12
Go 1.13 has some important improvements that we would like to use, such
as the new integer literals and `errors.Is` which both arrived in Go
1.13.
2021-03-09 18:15:49 +01:00
Ayke van Laethem c54bdf8b51 runtime: add dummy debug package
This package does not implement any methods, which is of course not
useful. However, by creating this package in advance it's possible to
see the next issue that's preventing something from building in TinyGo.

Motivated by: https://github.com/tinygo-org/tinygo/issues/1634
2021-03-09 16:09:51 +01:00
sago35 1fd4669bee docs: add QT Py and Teensy 4.0 2021-03-09 16:07:50 +01:00
sago35 e36dfe58ac version: update TinyGo version to 0.18.0-dev 2021-03-07 08:53:48 +09:00
Ayke van Laethem 138befd8a2 all: release v0.17.0 2021-03-05 17:41:06 +01:00
Ayke van Laethem 3d13f9cfe1 transform: show better error message in coroutines lowering
A common error is when someone tries to export a blocking function. This
is not possible with the coroutines scheduler. Previously, it resulted
in an error like this:

    panic: trying to make exported function async: messageHandler

With this change, the error is much better and shows where it comes from
exactly:

    /home/ayke/tmp/export-async.go:8: blocking operation in exported function: messageHandler

    traceback:
    messageHandler
            /home/ayke/tmp/export-async.go:9:5
    main.foo
            /home/ayke/tmp/export-async.go:15:2
    runtime.chanSend
            /home/ayke/src/github.com/tinygo-org/tinygo/src/runtime/chan.go:494:12

This should make it easier to identify and fix the problem. And it
avoids a compiler panic, which is a really bad way of showing
diagnostics.
2021-03-05 14:42:43 +01:00
sago35 c4191da2a5 gdb: support -ocd-output on windows 2021-03-05 13:02:01 +01:00
sago35 3fdd1a9249 gdb: kill openocd if it does not exit 2021-03-05 11:16:42 +01:00
Ayke van Laethem 869baca117 wasi: specify wasi-libc in a different way
This way is more consistent with how picolibc is specified and allows
generating a helpful error message. This error message should never be
generated for TinyGo binary releases, only when doing local development.
2021-03-04 17:25:22 +01:00
Ayke van Laethem 9c3e479432 all: remove support for LLVM 9
This LLVM version breaks CI and is now relatively rather old anyway, so
remove support for it.

This also reverts a workaround for LLVM 9, see a9568932b ("maixbit:
workaround to avoid medium code model").
2021-03-04 15:46:05 +01:00
sago35 6e480e189d gdb: support daemonization on windows 2021-03-04 14:46:10 +01:00
Aaron Turner dcff8b6478 Namespaced Wasm Imports so they don't conflict across modules, or reserved LLVM IR (#1661)
wasm: namespaced all of the wasm import functions
2021-03-03 07:55:53 +01:00
deadprogram 6862942c99 docs: update license for 2021
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-03-01 23:31:34 +01:00
deadprogram abc478c294 Revert "Allow interrupts in stm32f103xx (#1466)" as discussed in #1608
This reverts commit 3bb994da9f.
2021-02-27 20:53:16 +01:00
Kenneth Bell 66f76833ad stm32: fix i2c and add stm32f407 i2c 2021-02-27 20:35:16 +01:00
deadprogram 24976a6974 machine/nrf52840+nxpmk66f18: rename files to match naming format for all other boards/machines
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-02-26 00:32:33 +01:00
sago35 e76729c6f3 fix 2021-02-25 13:39:33 +01:00
sago35 35c8707867 nrf52840: improve USBCDC 2021-02-25 13:39:33 +01:00
deadprogram 16e30c97d8 docs: update README for all boards added since last release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-02-25 00:48:22 +01:00
Takeshi Yoneda 9841ac4acd WASI & darwin: support env variables based on libc
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
2021-02-24 14:22:17 +01:00
Ramon c299386906 Add nrf52840-mdk-usb-dongle target (#1654)
machine/nrf52840: add support for nrf52840-mdk-usb-dongle target
2021-02-24 10:25:38 +01:00
deadprogram afcc9d6608 build: add apt update that seems needed for when dependencies update or packages are removed
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-02-23 15:15:12 +01:00
Yongbin Kim 9f06798cb9 Fix typo in wasm_exec.js, syscall/js.valueLoadString() 2021-02-21 09:18:44 +01:00
Olaf Flebbe ce407ddf1b fix/no-libxml2 2021-02-21 09:02:30 +01:00
Elliott Sales de Andrade b689f14bb2 Add support for Go 1.16. 2021-02-19 23:46:55 +01:00
Tobias Kohlbau 4141a9f431 bump go.bug.st/serial to version 1.1.2
Bump version to 1.1.2 in order to support darwin/arm64 within tinygo.
See bugst/go-serial#96 for more information.

Signed-off-by: Tobias Kohlbau <tobias@kohlbau.de>
2021-02-19 21:49:42 +01:00
sago35 52a20cf3bb atsamd2x: avoid infinite loop when USBCDC is disconnected 2021-02-19 17:21:05 +01:00
sago35 1aac8a0cb1 atsamd5x: avoid infinite loop when USBCDC is disconnected 2021-02-19 17:20:15 +01:00
sago35 ba634c4cc7 atsamd2x: improve USBCDC 2021-02-16 14:52:05 +01:00
ardnew 681be2d4e3 STM32 I2C: replace addressable interface with simpler type (#1609)
machine/stm32: replace STM32 I2C addressable interface with simpler type
2021-02-16 13:03:29 +01:00
sago35 01b917fb11 atsamd5x: improve USBCDC 2021-02-16 12:48:52 +01:00
Kenneth Bell d274e299ac stm32: update stm32-svd lib 2021-02-16 12:48:52 +01:00
Kenneth Bell fcdaa83368 stm32-l5: corrected alt function and other review comments 2021-02-16 12:48:52 +01:00
Kenneth Bell 289e1aa197 stm32: Harmonization of UART logic 2021-02-16 12:48:52 +01:00
Kenneth Bell af02c09b56 nucleol552ze: implementation with CLOCK, LED, and UART 2021-02-16 12:48:52 +01:00
Ayke van Laethem 801bd2a7ff avr: use Clang for compiling C and assembly files
This is one step towards an avr-gcc free TinyGo toolchain, something
that will make it easier to use AVR targets in TinyGo.
2021-02-12 01:10:06 +01:00
Ayke van Laethem 9c8ef5c48f digispark: split off attiny85 target
This split is similar to the atmega328p/arduino split and may be helpful
for adding other attiny85 boards or using the attiny85 without a board.
2021-02-12 01:10:06 +01:00
Ayke van Laethem ab41c79de7 builder: wait for running jobs to finish
This avoids external commands from finishing after the TinyGo command
exits. For example, when testing out compiler-rt on AVR, I got the
following error:

    $ go install; and tinygo run -target=atmega1284p ./testdata/calls.go
    [... Clang error removed ...]
    error: failed to build /home/ayke/src/github.com/tinygo-org/tinygo/lib/compiler-rt/lib/builtins/extendsfdf2.c: exit status 1
    error: unable to open output file '/tmp/tinygo361380649/build-lib-compiler-rt/ffsdi2.c.o': 'No such file or directory'
    1 error generated.

That last error ("unable to open output file") is a spurious error
because the temporary directory has already been removed. This commit
waits until all running jobs have finished before returning, so that
these errors won't happen.
2021-02-11 19:43:46 +01:00
Ayke van Laethem d3d4acaadf esp: add picolibc
With this change, it is possible to fully use CGo on ESP32/ESP8266
chips. The following will work:

    tinygo flash -target=d1mini       -port=/dev/ttyUSB0 ./testdata/cgo/
    tinygo flash -target=esp32-mini32 -port=/dev/ttyUSB0 ./testdata/cgo/

Previously it would produce output like the following:

    /tmp/tinygo905539688/main.o:(.literal.runtime.run$1$gowrapper+0x150): undefined reference to `strcpy'
    /tmp/tinygo905539688/main.o:(.literal.runtime.run$1$gowrapper+0x154): undefined reference to `strlen'
2021-02-11 10:50:29 +01:00
Ayke van Laethem 676a78776a esp8266: add compiler-rt library
With this change, it is possible to compile ./testdata/float.go for the
ESP8266 and run it successfully. Previously it would result in many
linker error like this:

    /tmp/tinygo494494333/main.o:(.literal.runtime.printfloat64+0x0): undefined reference to `__unorddf2'
    /tmp/tinygo494494333/main.o:(.literal.runtime.printfloat64+0x4): undefined reference to `__gtdf2'
    /tmp/tinygo494494333/main.o:(.literal.runtime.printfloat64+0xc): undefined reference to `__nedf2'
    /tmp/tinygo494494333/main.o:(.literal.runtime.printfloat64+0x10): undefined reference to `__ltdf2'
    /tmp/tinygo494494333/main.o:(.literal.runtime.printfloat64+0x1c): undefined reference to `__gedf2'

I have verified that the output on the serial console matches
./testdata/float.txt when run on the ESP8266.
2021-02-11 10:50:29 +01:00
Ayke van Laethem 8a54615a09 builder: add -mcpu flag while building libraries
This flag is important for the Xtensa backend because by default a more
powerful backend (ESP32) is assumed. Therefore, compiling for the
ESP8266 won't work by default and needs the -mcpu flag.
2021-02-11 10:50:29 +01:00
Ayke van Laethem 74fe213b15 builder: remove unused cacheKey parameter
This key was intended as some sort of cache key (as the name indicates)
but that never happened. Let's remove it to avoid clutter. The cacheLoad
and cacheStore functions are only used for C libraries (libc,
compiler-rt) so their caching behavior is a bit different from other
things worth caching.
2021-02-11 10:50:29 +01:00
Ayke van Laethem 2e9c3a1d8d cgo: add support for variadic functions
This doesn't yet add support for actually making use of variadic
functions, but at least allows (unintended) variadic functions like the
following to work:

    void foo();
2021-02-11 09:51:15 +01:00
Ayke van Laethem 5502182642 ci: remove bad symlink workaround on Windows
This workaround is no longer needed as the symlink in the LLVM source
tree has been removed.
2021-02-11 09:05:05 +01:00
Weston Schmidt f4b4dd8d62 Add SPI support for Atmega based chips.
This is based on @Nerzal's #1398 PR, but is a bit of a refactor and
expansion to support all the Atmega based chips present in tinygo.
2021-02-10 12:56:48 +01:00
Ayke van Laethem 9f5bd2c460 ci: only build the necessary LLVM libraries and tools
This should improve rebuild time, but perhaps more importantly massively
reduces cache size which then reduces incremental build time.
2021-02-09 08:03:54 +01:00
Ayke van Laethem d6cdf8ca28 machine: make I2C.Configure signature consistent
It's better to always return an error value (even if it is nil) for
consistency.
2021-02-07 14:06:49 +01:00
Ayke van Laethem 6c5409bd17 all: update go-llvm to use LLVM 11 on macOS 2021-02-07 10:09:09 +01:00
Andre Sencioles cca0eab3da Fix multiline descriptions
Move element description formatting to a function

Export struct fields for use in the template

Add template helper functions

Multiline comments for interrupts and peripherals

Export more fields

Move comments to the top of each element

Do not remove line breaks from descriptions

The template code should gracefully handle line breaks now

go fmt gen-device-svd.go
2021-02-03 21:39:56 +01:00
Ayke van Laethem e161d5a82c compiler: work around an ARM backend bug in LLVM
Because of a bug in the ARM backend of LLVM, the cmpxchg instruction is
lowered using ldrexd/strexd instructions which don't exist on Cortex-M
cores. This leads to an "undefined instruction" exception at runtime.
Therefore, this patch works around this by lowering directly to a call
to the __sync_val_compare_and_swap_8 function, which is what the backend
should be doing.

For details, see: https://reviews.llvm.org/D95891

To test this patch, you can run the code on a Cortex-M3 or higher
microcontroller, for example:

    tinygo flash -target=pca10040 ./testdata/atomic.go

Before this patch, this would trigger an error. With this patch, the
behavior is correct. The error (without this patch) could look like
this:

    fatal error: undefined instruction with sp=0x200007cc pc=nil
2021-02-03 14:49:41 +01:00
Ayke van Laethem 1fb47a2670 all: go mod tidy
Clean up lots of unnecessary dependencies.
2021-02-01 20:19:26 +01:00
deadprogram f1210caba8 machine/clue: correct for lack of low frequency crystal
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-02-01 12:25:18 +01:00
ardnew 5279bebf57 move nRF52 ADC methods to common machine file 2021-02-01 12:25:18 +01:00
ardnew 7842e6940d Fix typo in ADC switch on config field Samples 2021-02-01 12:25:18 +01:00
ardnew 06f231468d accept configuration struct for ADC parameters (#1533) 2021-01-31 14:54:27 -06:00
deadprogram 3d6921b0e1 machine/circuitplay-bluefruit: correct internal I2C pin mapping
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-31 14:10:18 +01:00
Ayke van Laethem 9612af466b compiler: move settings to a separate Config struct
Moving settings to a separate config struct has two benefits:
  - It decouples the compiler a bit from other packages, most
    importantly the compileopts package. Decoupling is generally a good
    thing.
  - Perhaps more importantly, it precisely specifies which settings are
    used while compiling and affect the resulting LLVM module. This will
    be necessary for caching the LLVM module.
    While it would have been possible to cache without this refactor, it
    would have been very easy to miss a setting and thus let the
    compiler work with invalid/stale data.
2021-01-29 14:49:58 +01:00
deadprogram 868933e67c machine/microbit-v2: correct mapping for all LED matrix pins
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-26 10:29:59 +01:00
Elliott Sales de Andrade f3bdebe2a6 Use httptest to serve wasm test files.
This picks a port automatically, so avoids any conflicts that might
arise from running the tests in parallel.
2021-01-25 19:12:31 +01:00
Ayke van Laethem 5bae55d755 compiler: create runtime types lazily when needed
This fixes a longstanding TODO comment and similar to
https://github.com/tinygo-org/tinygo/pull/1593 it removes some code out
of the compiler.CompileProgram function that doesn't need to be there.
2021-01-25 17:14:02 +01:00
Ayke van Laethem 0bad2c9ff2 compiler: move the setting of attributes to getFunction
This is a small refactor to move code away from compiler.CompilePackage,
with the goal that compiler.CompilePackage will eventually be removed
entirely in favor of compiler.CompilePackage.
2021-01-25 16:28:30 +01:00
Elliott Sales de Andrade 5642d72fbe Update to current chromedp. 2021-01-24 23:13:42 +01:00
Ayke van Laethem 92ed645a11 compiler: remove unnecessary main.main call workaround
Since https://github.com/tinygo-org/tinygo/pull/1571 (in particular, the first
commit that sets the main package path), the main package is always named
"main". This makes the callMain() workaround in the runtime unnecessary and
allows directly calling the main.main function with a //go:linkname pragma.
2021-01-24 22:53:40 +01:00
Přemek Vyhnal 32a5d46c57 nice!nano board support (#1499)
machine/nice\!nano: add board support
2021-01-24 16:46:21 +01:00
Ayke van Laethem d8cc48b09b compiler: remove ir package
This package was long making the design of the compiler more complicated
than it needs to be. Previously this package implemented several
optimization passes, but those passes have since moved to work directly
with LLVM IR instead of Go SSA. The only remaining pass is the SimpleDCE
pass.

This commit removes the *ir.Function type that permeated the whole
compiler and instead switches to use *ssa.Function directly. The
SimpleDCE pass is kept but is far less tightly coupled to the rest of
the compiler so that it can easily be removed once the switch to
building and caching packages individually happens.
2021-01-24 15:39:15 +01:00
Ayke van Laethem 9bd36597d6 compiler: support all kinds of deferred builtins
This change extends defer support to all supported builitin functions.
Not all of them make sense (such as len, cap, real, imag, etc) but this
change for example adds support for `defer(delete(m, key))` which is
used in the Go 1.15 encoding/json package.
2021-01-24 09:28:09 +01:00
Ayke van Laethem d85ac4b3cc builder: parallelize most of the build
This commit parallelizes almost everything that can currently be
parallelized. With that, it also introduces a framework for easily
parallelizing other parts of the compiler.

Code for baremetal targets already compiles slightly faster because it
can parallelize the compilation of supporting assembly files. However,
the speedup is especially noticeable when libraries (compiler-rt,
picolibc) also need to be compiled: they will be compiled in parallel
next to the Go files using all available cores. On my dual core laptop
(4 cores if you count hyperthreading) this cuts compilation time roughly
in half when compiling something for a Cortex-M board after running
`tinygo clean`.
2021-01-24 09:13:02 +01:00
Ayke van Laethem 3010466c55 reflect: implement PtrTo 2021-01-23 10:55:46 +01:00
Ayke van Laethem 36db75b366 loader: support imports from vendor directories
This fixes https://github.com/tinygo-org/tinygo/issues/1518.
2021-01-21 19:06:02 +01:00
deadprogram a5e2b27884 docker: update dev docker image to use llvm11
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-19 21:26:06 +01:00
Nia Weiss a5cf704d83 compiler: test float to int conversions and fix upper-bound calculation 2021-01-19 14:02:32 +01:00
Ayke van Laethem a90865506d main: use LLVM 11 by default when linking LLVM dynamically
This doesn't affect the release builds but it is helpful for TinyGo
developers.
2021-01-19 08:55:57 +01:00
Nia Weiss a867b56e5f compiler: saturate float-to-int conversions
This works around some UB in LLVM, where an out-of-bounds conversion would produce a poison value.
The selected behavior is saturating, except that NaN is mapped to the minimum value.
2021-01-16 19:12:36 +01:00
Nia Weiss f159429152 compiler: emit a nil check when slicing an array pointer 2021-01-15 21:35:49 +01:00
Ayke van Laethem a848d720db compiler: refactor and add tests
This commit finally introduces unit tests for the compiler, to check
whether input Go code is converted to the expected output IR.

To make this necessary, a few refactors were needed. Hopefully these
refactors (to compile a program package by package instead of all at
once) will eventually become standard, so that packages can all be
compiled separate from each other and be cached between compiles.
2021-01-15 14:43:43 +01:00
Ayke van Laethem dbc438b2ae loader: use name "main" for the main package
This should make exported names a bit more consistent.

I believe there was a bug report for this issue, but I can't easily find
it. In any case, I think it's an important improvement to match the
behavior of the Go toolchain.
2021-01-15 14:43:43 +01:00
Fauchon c4d642007e New tinygo -x option to print commands (#1572)
main: add '-x' runtime flag that displays the executed external commands when performing operations like flashing.
2021-01-14 21:35:01 +01:00
Ayke van Laethem da0161d6ab wasm: implement a growable heap
On WebAssembly it is possible to grow the heap with the memory.grow
instruction. This commit implements this feature and with that also
removes the -heap-size flag that was reportedly broken (I haven't
verified that). This should make it easier to use TinyGo for
WebAssembly, where there was no good reason to use a fixed heap size.

This commit has no effect on baremetal targets with optimizations
enabled.
2021-01-10 21:08:52 +01:00
Ayke van Laethem 5af4c073cd runtime: put metadata at the top end of the heap
This commit swaps the layout of the heap. Previously, the metadata was
at the start and the data blocks (the actual heap memory) followed
after. This commit swaps those, so that the heap area starts with the
data blocks followed by the heap metadata.

This arrangement is not very relevant for baremetal targets that always
have all RAM allocated, but it is an important improvement for other
targets such as WebAssembly where growing the heap is possible but
starting with a small heap is a good idea. Because the metadata lives at
the end, and because the metadata does not contain pointers, it can
easily be moved. The data itself cannot be moved as the conservative GC
does not know all the pointer locations, plus moving the data could be
very expensive.
2021-01-10 21:08:52 +01:00
Ayke van Laethem 154c7c691b stm32: use stm32-rs SVDs which are of much higher quality
This commit changes the number of wait states for the stm32f103 chip to
2 instead of 4. This gets it back in line with the datasheet, but it
also has the side effect of breaking I2C. Therefore, another (seemingly
unrelated) change is needed: the i2cTimeout constant must be increased
to a higher value to adjust to the lower flash wait states - presumably
because the lower number of wait states allows the chip to run code
faster.
2021-01-09 21:45:07 +01:00
Fauchon 65caf777dd Support for STM32L0 MCUs and Dragino LGT92 device (#1561)
machine/stm32l0: add support for stm32l0 family and Dragino LGT92 Board
2021-01-08 22:27:25 +01:00
Ayke van Laethem a4d0877cf0 stacksize: add support for DW_CFA_offset_extended
It should be possible to ignore this directive, but we still have to
consume the two operands.
2021-01-06 19:41:47 +01:00
deadprogram 6ec868710b machine/microbit-v2: add initial support based on work done by @alankrantas thank you!
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-06 18:50:46 +01:00
deadprogram 086645153e tools/gen: ignore cluster registers with no actual clusters in them, and handle parsing binary integer fields in versions of Go before 1.13
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-06 18:50:46 +01:00
deadprogram cf930f652e nrfx: updare submodule to latest commit
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-01-06 18:50:46 +01:00
Quentin Smith 0dcff35d3a machine/p1am-100: rename MISO/MOSI
This makes p1am-100 work again after commit d1c4ed6.
2021-01-06 13:35:30 +01:00
Quentin Smith 75f13491b6 Add support for the P1AM-100 (similar to Arduino MKR) 2021-01-06 00:36:57 +01:00
Wu Han 533d2c9ce0 README: fix bluepill link
STM32Duino wiki url has been updated.
2021-01-05 23:59:03 +01:00
Jacques Supcik 2f4200a01b Add support for additional openocd commands (#1492)
main: add ability to define specific OpenOCD commands to be executed for a target.
2020-12-30 09:19:41 +01:00
deadprogram 939b393325 machine/clue: correct volume name and add alias for release version of Adafruit Clue board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-12-29 15:17:38 +01:00
deadprogram a5ee1ba4b3 machine/nrf52840: ensure that USB CDC interface is only initialized once
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-12-29 12:20:33 +01:00
Ayke van Laethem fb0bb69f62 compiler: fix non-int integer constants
Before this change, the compiler could panic with the following message:

    panic: 20 not an Int

That of course doesn't make much sense. But it apparently is expected
behavior, see https://github.com/golang/go/issues/43165 for details.

This commit fixes this issue by converting the constant to an integer if
needed.
2020-12-27 16:13:36 +01:00
Ayke van Laethem 5917b8baa2 interp: fix alignment of untyped globals
During a run of interp, some memory (for example, memory allocated
through runtime.alloc) may not have a known LLVM type. This memory is
alllocated by creating an i8 array.
This does not necessarily work, as i8 has no alignment requirements
while the allocated object may have allocation requirements. Therefore,
the resulting global may have an alignment that is too loose.
This works on some microcontrollers but notably does not work on a
Cortex-M0 or Cortex-M0+, as all load/store operations must be aligned.

This commit fixes this by setting the alignment of untyped memory to the
maximum alignment. The determination of "maximum alignment" is not
great but should get the job done on most architectures.
2020-12-27 11:21:35 +01:00
Ayke van Laethem 30df912565 interp: rewrite entire package
For a full explanation, see interp/README.md. In short, this rewrite is
a redesign of the partial evaluator which improves it over the previous
partial evaluator. The main functional difference is that when
interpreting a function, the interpretation can be rolled back when an
unsupported instruction is encountered (for example, an actual unknown
instruction or a branch on a value that's only known at runtime). This
also means that it is no longer necessary to scan functions to see
whether they can be interpreted: instead, this package now just tries to
interpret it and reverts when it can't go further.

This new design has several benefits:

  * Most errors coming from the interp package are avoided, as it can
    simply skip the code it can't handle. This has long been an issue.
  * The memory model has been improved, which means some packages now
    pass all tests that previously didn't pass them.
  * Because of a better design, it is in fact a bit faster than the
    previous version.

This means the following packages now pass tests with `tinygo test`:

  * hash/adler32: previously it would hang in an infinite loop
  * math/cmplx: previously it resulted in errors

This also means that the math/big package can be imported. It would
previously fail with a "interp: branch on a non-constant" error.
2020-12-22 15:54:23 +01:00
Ayke van Laethem e9d549d211 compiler: fix incorrect "exported function" panic
Because the parentHandle parameter wasn't always set to the right value,
the coroutine lowering pass would sometimes panic with "trying to make
exported function async" even though there was no exported function
involved. Therefore, it should unconditionally be set to avoid this.

The parent function doesn't always have the parentHandle function
parameter set because it can only be set after defining a function, not
when it is only declared.
2020-12-22 15:54:23 +01:00
Ayke van Laethem 6ad631539d compiler: fix undefined behavior in wordpack
Previously, EmitPointerPack would generate an out-of-bounds read from an
alloca. This commit fixes that by creating an alloca of the appropriate
size instead of using the size of the to-be-packed data (which might be
smaller than a pointer).

I discovered this error while working on a rewrite of the interp
package, which checks for out-of-bounds reads and writes. There I
discovered this issue when the image package was compiled.
2020-12-22 15:54:23 +01:00
Ayke van Laethem cda5fffd98 nrf: use SPIM peripheral instead of the legacy SPI peripheral
This newer peripheral supports DMA (through EasyDMA) and should
generally be faster. Importantly for some operations: interrupts (within
255 byte buffers) will not interfere with the SPI transfer.
2020-12-22 14:41:06 +01:00
Ayke van Laethem ce539ce583 nrf: refactor code a bit to reduce duplication
The nrf52 series is all very similar and copying the code only makes it
harder to maintain the code or to add more chips in the nrf52 series
(for example, the nrf52833 as used in the micro:bit v2).

This commit also has a small improvement regarding pins: it now includes
chip-level pin names (P0.00, P0.01, etc) to the machine package.
2020-12-22 14:41:06 +01:00
kenbell 43a31467d3 Nucleo f722ze (#1526)
machine/nucleo-f722ze: Add support for ST Micro NUCLEO-F722ZE
2020-12-15 06:51:35 +01:00
Ayke van Laethem ae92ea149c esp32: enable the FPU
This allows working with float32 values, for example it allows
testdata/float.go to work correctly (assuming an Xtensa backend bug is
fixed, see https://github.com/espressif/llvm-project/issues/41).
2020-12-11 12:11:46 +01:00
Ayke van Laethem 4568de556e esp32: use the compiler-rt library for extra routines
These routines are necessary to compile testdata/float.go.
2020-12-11 12:11:46 +01:00
ardnew 7a4ccd916f matrixportal-m4: Add support for board Adafruit Matrix Portal M4 (#1529)
machine/matrixportal-m4: add Adafruit Matrix Portal M4 board definition
2020-12-11 10:00:41 +01:00
Ayke van Laethem 4cc1cdf672 main: support gdb debugging with AVR
Be able to run `tinygo gdb -target=arduino examples/serial` and debug a
program with the power of a real debugger.

Note that this only works on LLVM 11 because older versions have a bug
in the AVR backend that cause it to produce invalid debug information:
https://reviews.llvm.org/D74213.
2020-12-10 16:44:27 +01:00
Ayke van Laethem bb27bbcb41 all: switch to LLVM 11 for static builds
This commit switches to LLVM 11 for builds with LLVM linked statically
(e.g. `make`). It does not yet switch the default for builds dynamically
linked to LLVM, that should be done in a later change.

This commit also changes to use the default host toolchain (probably
GCC) instead of Clang as the default compiler in CI. There were some
issues with Clang 3.8 in CI and hopefully this will fix it.

Additionally it updates the way LLVM is built on Windows, with
-DLLVM_ENABLE_PIC=OFF (which should have been used all along). This
change makes it possible to revert a hack to build libclang manually and
instead uses the libclang static library like on all other operating
systems, simplifying the Makefile.
2020-12-10 07:01:32 +01:00
deadprogram 9c2d2b662b build: remove release build job for arch release until it can be debugged
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-12-07 17:12:19 +01:00
Ayke van Laethem 098f900363 esp8266: implement task based scheduler
I have chosed to call this implementation `esp8266` instead of `xtensa`
as it has been written specifically for the ESP8266 and there are no
other Xtensa chips with the CALL0 ABI (no windowing) that I know of. The
only other related chip is the ESP32, which does implement register
windowing and thus needs a very different implementation.
2020-12-05 11:09:46 +01:00
Ayke van Laethem caf35cfc41 esp32: implement task based scheduler
This has been a *lot* of work, trying to understand the Xtensa windowed
registers ABI. But in the end I managed to come up with a very simple
implementation that so far seems to work very well.

I tested this with both blinky examples (with blinky2 slightly edited)
and ./testdata/coroutines.go to verify that it actually works.
Most development happened on the ESP32 QEMU fork from Espressif
(https://github.com/espressif/qemu/wiki) but I also verified that it
works on a real ESP32.
2020-12-05 09:02:11 +01:00
Ayke van Laethem abb09e869e runtime, internal/task: refactor to simplify stack switching
The Cortex-M target isn't much changed, but much of the logic for the
AVR stack switcher that was previously in assembly has now been moved to
Go to make it more maintainable and in fact smaller in code size. Three
functions (tinygo_getCurrentStackPointer, tinygo_switchToTask,
tinygo_switchToScheduler) have been changed to one: tinygo_swapTask.

This reduction in assembly code should make the code more maintainable
and should make it easier to port stack switching to other
architectures.

I've also moved the assembly files to src/internal/task, which seems
like a more appropriate location to me.
2020-12-05 09:02:11 +01:00
Ayke van Laethem bb58783158 ci: switch to Go 1.15 for MacOS builds
Unfortunately, CircleCI doesn't seem to provide Debian stretch builds
with Go 1.15. We should be using Debian stretch (an older distro) to
make sure the tinygo binary runs on as many Linux systems as possible
(including older ones), and I think using Go 1.14 for these builds is
unfortunate but the better tradeoff.
2020-12-03 08:42:04 +01:00
fleshin 7abc67107d sam: add support for the MKR1000 board 2020-12-03 00:33:23 +01:00
sago35 2540172cc5 atsam: add a length check to findPinPadMapping 2020-12-02 01:21:38 +01:00
sago35 0c4f0b1ebf version: update TinyGo version to 0.17.0-dev 2020-11-27 18:55:02 +01:00
256 changed files with 12024 additions and 5163 deletions
+30 -110
View File
@@ -68,24 +68,24 @@ commands:
steps:
- restore_cache:
keys:
- llvm-source-10-v1
- llvm-source-11-v1
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-v1
key: llvm-source-11-v1
paths:
- llvm-project
build-wasi-libc:
steps:
- restore_cache:
keys:
- wasi-libc-sysroot-v3
- wasi-libc-sysroot-v4
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-v3
key: wasi-libc-sysroot-v4
paths:
- lib/wasi-libc/sysroot
test-linux:
@@ -108,13 +108,13 @@ commands:
- run: go install -tags=llvm<<parameters.llvm>> .
- restore_cache:
keys:
- wasi-libc-sysroot-systemclang-v2
- wasi-libc-sysroot-systemclang-v3
- run: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-systemclang-v2
key: wasi-libc-sysroot-systemclang-v3
paths:
- lib/wasi-libc/sysroot
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./interp ./transform .
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform .
- run: make gen-device -j4
- run: make smoketest XTENSA=0
- run: make tinygo-test
@@ -132,6 +132,7 @@ commands:
- run:
name: "Install apt dependencies"
command: |
sudo apt-get update
sudo apt-get install \
gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \
@@ -153,17 +154,14 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-10-linux-v1-assert
- llvm-build-11-linux-v2-assert
- 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++
sudo apt-get install cmake ninja-build
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
@@ -171,7 +169,7 @@ commands:
make ASSERT=1 llvm-build
fi
- save_cache:
key: llvm-build-10-linux-v1-assert
key: llvm-build-11-linux-v2-assert
paths:
llvm-build
- run: make ASSERT=1
@@ -193,6 +191,7 @@ commands:
- run:
name: "Install apt dependencies"
command: |
sudo apt-get update
sudo apt-get install \
gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \
@@ -214,17 +213,14 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-10-linux-v1
- llvm-build-11-linux-v2-noassert
- 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++
sudo apt-get install cmake ninja-build
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
@@ -232,7 +228,7 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-10-linux-v1
key: llvm-build-11-linux-v2-noassert
paths:
llvm-build
- build-wasi-libc
@@ -274,8 +270,8 @@ commands:
- run:
name: "Install dependencies"
command: |
curl https://dl.google.com/go/go1.14.darwin-amd64.tar.gz -o go1.14.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz
curl https://dl.google.com/go/go1.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.16.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- install-xtensa-toolchain:
@@ -286,17 +282,17 @@ commands:
- go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-10-macos-v1
- llvm-source-11-macos-v1
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-macos-v1
key: llvm-source-11-macos-v1
paths:
- llvm-project
- restore_cache:
keys:
- llvm-build-10-macos-v1
- llvm-build-11-macos-v2
- run:
name: "Build LLVM"
command: |
@@ -308,17 +304,17 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-10-macos-v1
key: llvm-build-11-macos-v2
paths:
llvm-build
- restore_cache:
keys:
- wasi-libc-sysroot-macos-v2
- wasi-libc-sysroot-macos-v3
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-macos-v2
key: wasi-libc-sysroot-macos-v3
paths:
- lib/wasi-libc/sysroot
- run:
@@ -344,74 +340,8 @@ commands:
paths:
- ~/.cache/go-build
- /go/pkg/mod
arch-release:
steps:
- run:
name: Install dependencies
command: pacman -Sy --noconfirm openssh git pacman-contrib binutils
- run:
name: Create TinyGo user
command: useradd -m tinygo
- run:
name: Start SSH Agent
user: tinygo
command: eval $(ssh-agent -s)
- run:
name: Add ARCH_RELEASE_SSH_PRIVATE_KEY identity
user: tinygo
command: echo "${ARCH_RELEASE_SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add -
- run:
name: Create SSH directory
user: tinygo
command: mkdir -p ~/.ssh && chmod 700 ~/.ssh
- run:
name: Add aur.archlinux.org to known hosts
user: tinygo
command: ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts
- run:
name: Clone tinygo-bin repo
user: tinygo
command: git clone ssh://aur@aur.archlinux.org/tinygo-bin.git ~/tinygo-bin
- run:
name: Update package version
user: tinygo
command: sed -i -E "s/(pkgver=)(.*)$/\1${CIRCLE_TAG}/" ~/tinygo-bin/PKGBUILD
- run:
name: Update file checksums
user: tinygo
command: cd ~/tinygo-bin && updpkgsums
- run:
name: Update .SRCINFO
user: tinygo
command: cd ~/tinygo-bin && makepkg --printsrcinfo > .SRCINFO
# Commit the update
- run:
name: Set git commit config
user: tinygo
command: |
git config --global user.email "tinygo-bot@tinygo.org" &&
git config --global user.name "TinyGo Release Bot"
- run:
name: Commit and push changes
user: tinygo
command: |
cd ~/tinygo-bin &&
git commit -a -m "Update tinygo-bin to v${CIRCLE_TAG}" &&
git push origin master
jobs:
test-llvm9-go111:
docker:
- image: circleci/golang:1.11-buster
steps:
- test-linux:
llvm: "9"
test-llvm10-go112:
docker:
- image: circleci/golang:1.12-buster
steps:
- test-linux:
llvm: "10"
test-llvm10-go113:
docker:
- image: circleci/golang:1.13-buster
@@ -430,6 +360,12 @@ jobs:
steps:
- test-linux:
llvm: "11"
test-llvm11-go116:
docker:
- image: circleci/golang:1.16-buster
steps:
- test-linux:
llvm: "11"
assert-test-linux:
docker:
- image: circleci/golang:1.14-stretch
@@ -445,32 +381,16 @@ jobs:
xcode: "10.1.0"
steps:
- build-macos
arch-release:
docker:
- image: archlinux:latest
steps:
- arch-release
workflows:
test-all:
jobs:
- test-llvm9-go111
- test-llvm10-go112
- test-llvm10-go113
- test-llvm10-go114
- test-llvm11-go115
- test-llvm11-go116
- build-linux
- build-macos
- assert-test-linux
release:
jobs:
- arch-release:
filters:
branches:
ignore: /.*/
tags:
# Runs on every semver release
only: /v[0-9]+(\.[0-9]+)*(-.*)*/
+3
View File
@@ -20,3 +20,6 @@
[submodule "lib/picolibc"]
path = lib/picolibc
url = https://github.com/keith-packard/picolibc.git
[submodule "lib/stm32-svd"]
path = lib/stm32-svd
url = https://github.com/tinygo-org/stm32-svd
+1 -6
View File
@@ -23,7 +23,7 @@ different guide:
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.11+)
* Go (1.13+)
* Standard build tools (gcc/clang)
* git
* CMake
@@ -43,11 +43,6 @@ 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.
TinyGo uses Go modules, so if you clone TinyGo inside your GOPATH (and are using
Go below 1.13), make sure that Go modules are enabled:
export GO111MODULE=on
## Build LLVM, Clang, LLD
Before starting the build, you may want to set the following environment
+90
View File
@@ -1,3 +1,93 @@
0.17.0
---
* **command line**
- switch to LLVM 11 for static builds
- support gdb debugging with AVR
- add support for additional openocd commands
- add `-x` flag to print commands
- use LLVM 11 by default when linking LLVM dynamically
- update go-llvm to use LLVM 11 on macOS
- bump go.bug.st/serial to version 1.1.2
- do not build LLVM with libxml to work around a bugo on macOS
- add support for Go 1.16
- support gdb daemonization on Windows
- remove support for LLVM 9, to fix CI
- kill OpenOCD if it does not exit with a regular quit signal
- support `-ocd-output` on Windows
* **compiler**
- `builder`: parallelize most of the build
- `builder`: remove unused cacheKey parameter
- `builder`: add -mcpu flag while building libraries
- `builder`: wait for running jobs to finish
- `cgo`: add support for variadic functions
- `compiler`: fix undefined behavior in wordpack
- `compiler`: fix incorrect "exported function" panic
- `compiler`: fix non-int integer constants (fixing a crash)
- `compiler`: refactor and add tests
- `compiler`: emit a nil check when slicing an array pointer
- `compiler`: saturate float-to-int conversions
- `compiler`: test float to int conversions and fix upper-bound calculation
- `compiler`: support all kinds of deferred builtins
- `compiler`: remove ir package
- `compiler`: remove unnecessary main.main call workaround
- `compiler`: move the setting of attributes to getFunction
- `compiler`: create runtime types lazily when needed
- `compiler`: move settings to a separate Config struct
- `compiler`: work around an ARM backend bug in LLVM
- `interp`: rewrite entire package
- `interp`: fix alignment of untyped globals
- `loader`: use name "main" for the main package
- `loader`: support imports from vendor directories
- `stacksize`: add support for DW_CFA_offset_extended
- `transform`: show better error message in coroutines lowering
* **standard library**
- `machine`: accept configuration struct for ADC parameters
- `machine`: make I2C.Configure signature consistent
- `reflect`: implement PtrTo
- `runtime`: refactor to simplify stack switching
- `runtime`: put metadata at the top end of the heap
* **targets**
- `atsam`: add a length check to findPinPadMapping
- `atsam`: improve USBCDC
- `atsam`: avoid infinite loop when USBCDC is disconnected
- `avr`: add SPI support for Atmega based chips
- `avr`: use Clang for compiling C and assembly files
- `esp32`: implement task based scheduler
- `esp32`: enable the FPU
- `esp8266`: implement task based scheduler
- `esp`: add compiler-rt library
- `esp`: add picolibc
- `nrf`: refactor code a bit to reduce duplication
- `nrf`: use SPIM peripheral instead of the legacy SPI peripheral
- `nrf`: update nrfx submodule to latest commit
- `nrf52840`: ensure that USB CDC interface is only initialized once
- `nrf52840`: improve USBCDC
- `stm32`: use stm32-rs SVDs which are of much higher quality
- `stm32`: harmonization of UART logic
- `stm32`: replace I2C addressable interface with simpler type
- `stm32`: fix i2c and add stm32f407 i2c
- `stm32`: revert change that adds support for channels in interrupts
- `wasm`: implement a growable heap
- `wasm`: fix typo in wasm_exec.js, syscall/js.valueLoadString()
- `wasm`: Namespaced Wasm Imports so they don't conflict across modules, or reserved LLVM IR
- `wasi`: support env variables based on libc
- `wasi`: specify wasi-libc in a different way, to improve error message
* **boards**
- `matrixportal-m4`: add support for board Adafruit Matrix Portal M4
- `mkr1000`: add this board
- `nucleo-f722ze`: add this board
- `clue`: correct volume name and add alias for release version of Adafruit Clue board
- `p1am-100`: add support for the P1AM-100 (similar to Arduino MKR)
- `microbit-v2`: add initial support based on work done by @alankrantas thank you!
- `lgt92`: support for STM32L0 MCUs and Dragino LGT92 device
- `nicenano`: nice!nano board support
- `circuitplay-bluefruit`: correct internal I2C pin mapping
- `clue`: correct for lack of low frequency crystal
- `digispark`: split off attiny85 target
- `nucleo-l552ze`: implementation with CLOCK, LED, and UART
- `nrf52840-mdk-usb-dongle`: add this board
0.16.0
---
+6 -6
View File
@@ -1,10 +1,10 @@
# TinyGo base stage installs the most recent Go 1.15.x, LLVM 10 and the TinyGo compiler itself.
# TinyGo base stage installs the most recent Go 1.15.x, LLVM 11 and the TinyGo compiler itself.
FROM golang:1.15 AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-10-dev libclang-10-dev lld-10 git
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git
COPY . /tinygo
@@ -29,7 +29,7 @@ COPY --from=tinygo-base /tinygo/targets /tinygo/targets
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y make clang-10 libllvm10 lld-10 && \
apt-get install -y make clang-11 libllvm11 lld-11 && \
make wasi-libc
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
@@ -61,7 +61,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils make clang-10 && \
apt-get install -y apt-utils make clang-11 && \
make gen-device-nrf && make gen-device-stm32
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
@@ -73,7 +73,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils make clang-10 binutils-avr gcc-avr avr-libc && \
apt-get install -y apt-utils make clang-11 binutils-avr gcc-avr avr-libc && \
make gen-device
CMD ["tinygo"]
+2 -2
View File
@@ -1,7 +1,7 @@
Copyright (c) 2018-2020 TinyGo Authors. All rights reserved.
Copyright (c) 2018-2021 TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library.
Copyright (c) 2009-2020 The Go Authors. All rights reserved.
Copyright (c) 2009-2021 The Go Authors. All rights reserved.
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
+42 -26
View File
@@ -48,55 +48,59 @@ ifeq ($(OS),Windows_NT)
# LLVM compiled using MinGW on Windows appears to have problems with threads.
# Without this flag, linking results in errors like these:
# libLLVMSupport.a(Threading.cpp.obj):Threading.cpp:(.text+0x55): undefined reference to `std::thread::hardware_concurrency()'
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF -DLLVM_ENABLE_PIC=OFF
CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
CGO_LDFLAGS_EXTRA += -lversion
# Build libclang manually because the CMake-based build system on Windows
# doesn't allow building libclang as a static library.
LIBCLANG_PATH = $(abspath build/libclang-custom.a)
LIBCLANG_FILES = $(abspath $(wildcard $(LLVM_BUILDDIR)/tools/clang/tools/libclang/CMakeFiles/libclang.dir/*.cpp.obj))
# Add the libclang dependency to the tinygo binary target.
tinygo: $(LIBCLANG_PATH)
test: $(LIBCLANG_PATH)
# Build libclang.
$(LIBCLANG_PATH): $(LIBCLANG_FILES)
@mkdir -p build
ar rcs $(LIBCLANG_PATH) $^
LIBCLANG_NAME = libclang
else ifeq ($(shell uname -s),Darwin)
MD5SUM = md5
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
LIBCLANG_NAME = clang
else ifeq ($(shell uname -s),FreeBSD)
MD5SUM = md5
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
LIBCLANG_NAME = clang
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
else
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
LIBCLANG_NAME = clang
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
endif
CLANG_LIBS = $(START_GROUP) -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
# Libraries that should be linked in for the statically linked Clang.
CLANG_LIB_NAMES = clangAnalysis clangARCMigrate clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangStaticAnalyzerCheckers clangStaticAnalyzerCore clangStaticAnalyzerFrontend clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
# Libraries that should be linked in for the statically linked LLD.
LLD_LIB_NAMES = lldCOFF lldCommon lldCore lldDriver lldELF lldMachO lldMinGW lldReaderWriter lldWasm lldYAML
LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP)
# Other libraries that are needed to link TinyGo.
EXTRA_LIB_NAMES = LLVMInterpreter
# These build targets appear to be the only ones necessary to build all TinyGo
# dependencies. Only building a subset significantly speeds up rebuilding LLVM.
# The Makefile rules convert a name like lldELF to lib/liblldELF.a to match the
# library path (for ninja).
# This list also includes a few tools that are necessary as part of the full
# TinyGo build.
NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm $(addprefix lib/lib,$(addsuffix .a,$(LIBCLANG_NAME) $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES)))
# For static linking.
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CPPFLAGS+=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++14
CGO_LDFLAGS+=$(LIBCLANG_PATH) -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
CGO_LDFLAGS+=$(abspath $(LLVM_BUILDDIR))/lib/lib$(LIBCLANG_NAME).a -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
endif
clean:
@rm -rf build
FMT_PATHS = ./*.go builder cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
fmt:
@gofmt -l -w $(FMT_PATHS)
fmt-check:
@@ -140,23 +144,23 @@ gen-device-kendryte: build/gen-device-svd
GO111MODULE=off $(GO) fmt ./src/device/kendryte
gen-device-stm32: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro lib/cmsis-svd/data/STMicro/ src/device/stm32/
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/
GO111MODULE=off $(GO) fmt ./src/device/stm32
# Get LLVM sources.
$(LLVM_PROJECTDIR)/README.md:
git clone -b xtensa_release_10.0.1 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/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_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
# Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
cd $(LLVM_BUILDDIR); ninja
cd $(LLVM_BUILDDIR); ninja $(NINJA_BUILD_TARGETS)
# Build wasi-libc sysroot
@@ -173,7 +177,7 @@ tinygo:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" .
test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./interp ./transform .
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./compiler ./interp ./transform .
# Test known-working standard library packages.
# TODO: do this in one command, parallelize, and only show failing tests (no
@@ -187,9 +191,11 @@ tinygo-test:
$(TINYGO) test encoding/ascii85
$(TINYGO) test encoding/base32
$(TINYGO) test encoding/hex
$(TINYGO) test hash/adler32
$(TINYGO) test hash/fnv
$(TINYGO) test hash/crc64
$(TINYGO) test math
$(TINYGO) test math/cmplx
$(TINYGO) test text/scanner
$(TINYGO) test unicode/utf8
@@ -217,6 +223,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@@ -303,6 +311,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=lgt92 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
@@ -329,6 +339,12 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=teensy36 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1
@$(MD5SUM) test.hex
# test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex
+16 -5
View File
@@ -43,11 +43,11 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 44 microcontroller boards are currently supported:
The following 55 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit CLUE Alpha](https://www.adafruit.com/product/4500)
* [Adafruit CLUE](https://www.adafruit.com/product/4500)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
@@ -55,22 +55,29 @@ The following 44 microcontroller boards are currently supported:
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
* [Adafruit Matrix Portal M4](https://www.adafruit.com/product/4745)
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
* [Adafruit PyGamer](https://www.adafruit.com/product/4242)
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit QT Py](https://www.adafruit.com/product/4600)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
* [BBC micro:bit](https://microbit.org/)
* [BBC micro:bit v2](https://microbit.org/new-microbit/)
* [Digispark](http://digistump.com/products/1)
* [Dragino LoRaWAN GPS Tracker LGT-92](http://www.dragino.com/products/lora-lorawan-end-node/item/142-lgt-92.html)
* [ESP32](https://www.espressif.com/en/products/socs/esp32)
* [ESP8266](https://www.espressif.com/en/products/socs/esp8266)
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
* [Nintendo Switch](https://www.nintendo.com/switch/)
* [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)
@@ -81,12 +88,16 @@ The following 44 microcontroller boards are currently supported:
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
* [PineTime DevKit](https://www.pine64.org/pinetime/)
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
* [ST Micro "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html)
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
@@ -148,6 +159,6 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/10.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/11.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
Some code has been copied and/or ported from Paul Stoffregen's Teensy libraries and is therefore licensed under PJRC's license. This has been clearly indicated in the header of these files.
+9 -9
View File
@@ -12,27 +12,23 @@ jobs:
steps:
- task: GoTool@0
inputs:
version: '1.15'
version: '1.16'
- checkout: self
fetchDepth: 1
- task: Cache@2
displayName: Cache LLVM source
inputs:
key: llvm-source-10-windows-v1
key: llvm-source-11-windows-v1
path: llvm-project
- task: Bash@3
displayName: Download LLVM source
inputs:
targetType: inline
script: |
make llvm-source
# Workaround for bad symlinks:
# https://github.com/microsoft/azure-pipelines-tasks/issues/13418
rm -f llvm-project/libcxx/test/std/input.output/filesystems/Inputs/static_test_env/bad_symlink
script: make llvm-source
- task: CacheBeta@0
displayName: Cache LLVM build
inputs:
key: llvm-build-10-windows-v1
key: llvm-build-11-windows-v4
path: llvm-build
- task: Bash@3
displayName: Build LLVM
@@ -41,7 +37,11 @@ jobs:
script: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
choco install ninja
# hack ninja to use fewer jobs
echo -e 'C:\\ProgramData\\Chocolatey\\bin\\ninja -j4 %*' > /usr/bin/ninja.bat
# build!
make llvm-build
fi
- task: Bash@3
@@ -52,7 +52,7 @@ jobs:
- task: CacheBeta@0
displayName: Cache wasi-libc sysroot
inputs:
key: wasi-libc-sysroot-v3
key: wasi-libc-sysroot-v4
path: lib/wasi-libc/sysroot
- task: Bash@3
displayName: Build wasi-libc
+344 -193
View File
@@ -8,6 +8,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"go/types"
"io/ioutil"
"os"
"path/filepath"
@@ -19,6 +20,7 @@ import (
"github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader"
"github.com/tinygo-org/tinygo/stacksize"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
@@ -42,32 +44,359 @@ type BuildResult struct {
//
// The error value may be of type *MultiError. Callers will likely want to check
// for this case and print such errors individually.
func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error {
// Compile Go code to IR.
machine, err := compiler.NewTargetMachine(config)
func Build(pkgName, outpath string, config *compileopts.Config, preAction func() error, action func(BuildResult) error) error {
compilerConfig := &compiler.Config{
Triple: config.Triple(),
CPU: config.CPU(),
Features: config.Features(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.Target.DefaultStackSize,
NeedsStackObjects: config.NeedsStackObjects(),
Debug: config.Debug(),
}
// Load the target machine, which is the LLVM object that contains all
// details of a target (alignment restrictions, pointer size, default
// address spaces, etc).
machine, err := compiler.NewTargetMachine(compilerConfig)
if err != nil {
return err
}
buildOutput, errs := compiler.Compile(pkgName, machine, config)
if errs != nil {
return newMultiError(errs)
// Load entire program AST into memory.
lprogram, err := loader.Load(config, []string{pkgName}, config.ClangHeaders, types.Config{
Sizes: compiler.Sizes(machine),
})
if err != nil {
return err
}
err = lprogram.Parse()
if err != nil {
return err
}
// The slice of jobs that orchestrates most of the build.
// This is somewhat like an in-memory Makefile with each job being a
// Makefile target.
var jobs []*compileJob
if preAction != nil {
// Add job to preAction.
jobs = append(jobs, &compileJob{
description: "preAction",
run: preAction,
})
}
// Add job to compile and optimize all Go files at once.
// TODO: parallelize this.
var mod llvm.Module
var stackSizeLoads []string
programJob := &compileJob{
description: "compile Go files",
run: func() (err error) {
mod, err = compileWholeProgram(pkgName, config, compilerConfig, lprogram, machine)
if err != nil {
return
}
// Make sure stack sizes are loaded from a separate section so they can be
// modified after linking.
if config.AutomaticStackSize() {
stackSizeLoads = transform.CreateStackSizeLoads(mod, config)
}
return
},
}
jobs = append(jobs, programJob)
// Check whether we only need to create an object file.
// If so, we don't need to link anything and will be finished quickly.
outext := filepath.Ext(outpath)
if outext == ".o" || outext == ".bc" || outext == ".ll" {
// Run jobs to produce the LLVM module.
err := runJobs(jobs)
if err != nil {
return err
}
// Generate output.
switch outext {
case ".o":
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666)
case ".bc":
data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes()
return ioutil.WriteFile(outpath, data, 0666)
case ".ll":
data := []byte(mod.String())
return ioutil.WriteFile(outpath, data, 0666)
default:
panic("unreachable")
}
}
// Act as a compiler driver, as we need to produce a complete executable.
// First add all jobs necessary to build this object file, then afterwards
// run all jobs in parallel as far as possible.
// Create a temporary directory for intermediary files.
dir, err := ioutil.TempDir("", "tinygo")
if err != nil {
return err
}
defer os.RemoveAll(dir)
// Add job to write the output object file.
objfile := filepath.Join(dir, "main.o")
outputObjectFileJob := &compileJob{
description: "generate output file",
dependencies: []*compileJob{programJob},
run: func() error {
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
return ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
},
}
jobs = append(jobs, outputObjectFileJob)
// Prepare link command.
linkerDependencies := []*compileJob{outputObjectFileJob}
executable := filepath.Join(dir, "main")
tmppath := executable // final file
ldflags := append(config.LDFlags(), "-o", executable, objfile)
// Add compiler-rt dependency if needed. Usually this is a simple load from
// a cache.
if config.Target.RTLib == "compiler-rt" {
path, job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
if job != nil {
// The library was not loaded from cache so needs to be compiled
// (and then stored in the cache).
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
}
ldflags = append(ldflags, path)
}
// Add libc dependency if needed.
root := goenv.Get("TINYGOROOT")
switch config.Target.Libc {
case "picolibc":
path, job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
if job != nil {
// The library needs to be compiled (cache miss).
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
}
ldflags = append(ldflags, path)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
ldflags = append(ldflags, path)
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
// Add jobs to compile extra files. These files are in C or assembly and
// contain things like the interrupt vector table and low level operations
// such as stack switching.
for i, path := range config.ExtraFiles() {
abspath := filepath.Join(root, path)
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
job := &compileJob{
description: "compile extra file " + path,
run: func() error {
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
if err != nil {
return &commandError{"failed to build", path, err}
}
return nil
},
}
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
ldflags = append(ldflags, outpath)
}
// Add jobs to compile C files in all packages. This is part of CGo.
// TODO: do this as part of building the package to be able to link the
// bitcode files together.
for i, pkg := range lprogram.Sorted() {
for j, filename := range pkg.CFiles {
file := filepath.Join(pkg.Dir, filename)
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"."+strconv.Itoa(j)+"-"+filepath.Base(file)+".o")
job := &compileJob{
description: "compile CGo file " + file,
run: func() error {
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
if err != nil {
return &commandError{"failed to build", file, err}
}
return nil
},
}
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
ldflags = append(ldflags, outpath)
}
}
// Linker flags from CGo lines:
// #cgo LDFLAGS: foo
if len(lprogram.LDFlags) > 0 {
ldflags = append(ldflags, lprogram.LDFlags...)
}
// Create a linker job, which links all object files together and does some
// extra stuff that can only be done after linking.
jobs = append(jobs, &compileJob{
description: "link",
dependencies: linkerDependencies,
run: func() error {
err = link(config.Target.Linker, ldflags...)
if err != nil {
return &commandError{"failed to link", executable, err}
}
var calculatedStacks []string
var stackSizes map[string]functionStackSize
if config.Options.PrintStacks || config.AutomaticStackSize() {
// Try to determine stack sizes at compile time.
// Don't do this by default as it usually doesn't work on
// unsupported architectures.
calculatedStacks, stackSizes, err = determineStackSizes(mod, executable)
if err != nil {
return err
}
}
if config.AutomaticStackSize() {
// Modify the .tinygo_stacksizes section that contains a stack size
// for each goroutine.
err = modifyStackSizes(executable, stackSizeLoads, stackSizes)
if err != nil {
return fmt.Errorf("could not modify stack sizes: %w", err)
}
}
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable)
if err != nil {
return err
}
if config.Options.PrintSizes == "short" {
fmt.Printf(" code data bss | flash ram\n")
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
} else {
fmt.Printf(" code rodata data bss | flash ram | package\n")
for _, name := range sizes.sortedPackageNames() {
pkgSize := sizes.Packages[name]
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
}
fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
}
}
// Print goroutine stack sizes, as far as possible.
if config.Options.PrintStacks {
printStacks(calculatedStacks, stackSizes)
}
return nil
},
})
// Run all jobs to compile and link the program.
// Do this now (instead of after elf-to-hex and similar conversions) as it
// is simpler and cannot be parallelized.
err = runJobs(jobs)
if err != nil {
return err
}
// Get an Intel .hex file or .bin file from the .elf file.
outputBinaryFormat := config.BinaryFormat(outext)
switch outputBinaryFormat {
case "elf":
// do nothing, file is already in ELF format
case "hex", "bin":
// Extract raw binary, either encoding it as a hex file or as a raw
// firmware file.
tmppath = filepath.Join(dir, "main"+outext)
err := objcopy(executable, tmppath, outputBinaryFormat)
if err != nil {
return err
}
case "uf2":
// Get UF2 from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
if err != nil {
return err
}
case "esp32", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
tmppath = filepath.Join(dir, "main"+outext)
err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
}
return action(BuildResult{
Binary: tmppath,
MainDir: lprogram.MainPkg().Dir,
})
}
// compileWholeProgram compiles the entire *loader.Program to a LLVM module and
// applies most necessary optimizations and transformations.
func compileWholeProgram(pkgName string, config *compileopts.Config, compilerConfig *compiler.Config, lprogram *loader.Program, machine llvm.TargetMachine) (llvm.Module, error) {
// Compile AST to IR.
mod, errs := compiler.CompileProgram(lprogram, machine, compilerConfig, config.DumpSSA())
if errs != nil {
return mod, newMultiError(errs)
}
mod := buildOutput.Mod
if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:")
fmt.Println(mod.String())
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after IR construction")
return mod, errors.New("verification error after IR construction")
}
err = interp.Run(mod, config.DumpSSA())
err := interp.Run(mod, config.DumpSSA())
if err != nil {
return err
return mod, err
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after interpreting runtime.initAll")
return mod, errors.New("verification error after interpreting runtime.initAll")
}
if config.GOOS() != "darwin" {
@@ -82,7 +411,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
if config.WasmAbi() == "js" {
err := transform.ExternalInt64AsPtr(mod)
if err != nil {
return err
return mod, err
}
}
@@ -111,10 +440,10 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
}
if len(errs) > 0 {
return newMultiError(errs)
return mod, newMultiError(errs)
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification failure after LLVM optimization passes")
return mod, errors.New("verification failure after LLVM optimization passes")
}
// LLVM 11 by default tries to emit tail calls (even with the target feature
@@ -128,185 +457,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
transform.DisableTailCalls(mod)
}
// Make sure stack sizes are loaded from a separate section so they can be
// modified after linking.
var stackSizeLoads []string
if config.AutomaticStackSize() {
stackSizeLoads = transform.CreateStackSizeLoads(mod, config)
}
// Generate output.
outext := filepath.Ext(outpath)
switch outext {
case ".o":
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666)
case ".bc":
data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes()
return ioutil.WriteFile(outpath, data, 0666)
case ".ll":
data := []byte(mod.String())
return ioutil.WriteFile(outpath, data, 0666)
default:
// Act as a compiler driver.
// Create a temporary directory for intermediary files.
dir, err := ioutil.TempDir("", "tinygo")
if err != nil {
return err
}
defer os.RemoveAll(dir)
// Write the object file.
objfile := filepath.Join(dir, "main.o")
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
err = ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
if err != nil {
return err
}
// Prepare link command.
executable := filepath.Join(dir, "main")
tmppath := executable // final file
ldflags := append(config.LDFlags(), "-o", executable, objfile)
// Load builtins library from the cache, possibly compiling it on the
// fly.
if config.Target.RTLib == "compiler-rt" {
librt, err := CompilerRT.Load(config.Triple())
if err != nil {
return err
}
ldflags = append(ldflags, librt)
}
// Add libc.
if config.Target.Libc == "picolibc" {
libc, err := Picolibc.Load(config.Triple())
if err != nil {
return err
}
ldflags = append(ldflags, libc)
}
// Compile extra files.
root := goenv.Get("TINYGOROOT")
for i, path := range config.ExtraFiles() {
abspath := filepath.Join(root, path)
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
if err != nil {
return &commandError{"failed to build", path, err}
}
ldflags = append(ldflags, outpath)
}
// Compile C files in packages.
for i, file := range buildOutput.ExtraFiles {
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+filepath.Base(file)+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
if err != nil {
return &commandError{"failed to build", file, err}
}
ldflags = append(ldflags, outpath)
}
if len(buildOutput.ExtraLDFlags) > 0 {
ldflags = append(ldflags, buildOutput.ExtraLDFlags...)
}
// Link the object files together.
err = link(config.Target.Linker, ldflags...)
if err != nil {
return &commandError{"failed to link", executable, err}
}
var calculatedStacks []string
var stackSizes map[string]functionStackSize
if config.Options.PrintStacks || config.AutomaticStackSize() {
// Try to determine stack sizes at compile time.
// Don't do this by default as it usually doesn't work on
// unsupported architectures.
calculatedStacks, stackSizes, err = determineStackSizes(mod, executable)
if err != nil {
return err
}
}
if config.AutomaticStackSize() {
// Modify the .tinygo_stacksizes section that contains a stack size
// for each goroutine.
err = modifyStackSizes(executable, stackSizeLoads, stackSizes)
if err != nil {
return fmt.Errorf("could not modify stack sizes: %w", err)
}
}
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable)
if err != nil {
return err
}
if config.Options.PrintSizes == "short" {
fmt.Printf(" code data bss | flash ram\n")
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
} else {
fmt.Printf(" code rodata data bss | flash ram | package\n")
for _, name := range sizes.sortedPackageNames() {
pkgSize := sizes.Packages[name]
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
}
fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
}
}
// Print goroutine stack sizes, as far as possible.
if config.Options.PrintStacks {
printStacks(calculatedStacks, stackSizes)
}
// Get an Intel .hex file or .bin file from the .elf file.
outputBinaryFormat := config.BinaryFormat(outext)
switch outputBinaryFormat {
case "elf":
// do nothing, file is already in ELF format
case "hex", "bin":
// Extract raw binary, either encoding it as a hex file or as a raw
// firmware file.
tmppath = filepath.Join(dir, "main"+outext)
err := objcopy(executable, tmppath, outputBinaryFormat)
if err != nil {
return err
}
case "uf2":
// Get UF2 from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
if err != nil {
return err
}
case "esp32", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
tmppath = filepath.Join(dir, "main"+outext)
err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
}
return action(BuildResult{
Binary: tmppath,
MainDir: buildOutput.MainDir,
})
}
return mod, nil
}
// functionStackSizes keeps stack size information about a single function
+6 -19
View File
@@ -30,10 +30,7 @@ func cacheTimestamp(paths []string) (time.Time, error) {
// Try to load a given file from the cache. Return "", nil if no cached file can
// be found (or the file is stale), return the absolute path if there is a cache
// and return an error on I/O errors.
//
// TODO: the configKey is currently ignored. It is supposed to be used as extra
// data for the cache key, like the compiler version and arguments.
func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
func cacheLoad(name string, sourceFiles []string) (string, error) {
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
cacheStat, err := os.Stat(cachepath)
if os.IsNotExist(err) {
@@ -58,9 +55,7 @@ func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
// Store the file located at tmppath in the cache with the given name. The
// tmppath may or may not be gone afterwards.
//
// Note: the configKey is ignored, see cacheLoad.
func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string, error) {
func cacheStore(tmppath, name string, sourceFiles []string) (string, error) {
// get the last modified time
if len(sourceFiles) == 0 {
panic("cache: no source files")
@@ -74,24 +69,16 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,
return "", err
}
cachepath := filepath.Join(dir, name)
err = moveFile(tmppath, cachepath)
err = copyFile(tmppath, cachepath)
if err != nil {
return "", err
}
return cachepath, nil
}
// moveFile renames the file from src to dst. If renaming doesn't work (for
// example, the rename crosses a filesystem boundary), the file is copied and
// the old file is removed.
func moveFile(src, dst string) error {
err := os.Rename(src, dst)
if err == nil {
// Success!
return nil
}
// Failed to move, probably a different filesystem.
// Do a copy + remove.
// copyFile copies the given file from src to dst. It can copy over
// a possibly already existing file at the destination.
func copyFile(src, dst string) error {
inf, err := os.Open(src)
if err != nil {
return err
+21 -18
View File
@@ -101,7 +101,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
// Target Options
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
Opts.CPU = Args.getLastArgValue(OPT_target_cpu);
Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu));
Opts.Features = Args.getAllArgValues(OPT_target_feature);
// Use the default target triple if unspecified.
@@ -132,13 +132,19 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
Opts.DwarfDebugFlags = Args.getLastArgValue(OPT_dwarf_debug_flags);
Opts.DwarfDebugProducer = Args.getLastArgValue(OPT_dwarf_debug_producer);
Opts.DebugCompilationDir = Args.getLastArgValue(OPT_fdebug_compilation_dir);
Opts.MainFileName = Args.getLastArgValue(OPT_main_file_name);
Opts.DwarfDebugFlags =
std::string(Args.getLastArgValue(OPT_dwarf_debug_flags));
Opts.DwarfDebugProducer =
std::string(Args.getLastArgValue(OPT_dwarf_debug_producer));
Opts.DebugCompilationDir =
std::string(Args.getLastArgValue(OPT_fdebug_compilation_dir));
Opts.MainFileName = std::string(Args.getLastArgValue(OPT_main_file_name));
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ))
Opts.DebugPrefixMap.insert(StringRef(Arg).split('='));
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) {
auto Split = StringRef(Arg).split('=');
Opts.DebugPrefixMap.insert(
{std::string(Split.first), std::string(Split.second)});
}
// Frontend Options
if (Args.hasArg(OPT_INPUT)) {
@@ -154,8 +160,9 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
}
}
Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm);
Opts.OutputPath = Args.getLastArgValue(OPT_o);
Opts.SplitDwarfOutput = Args.getLastArgValue(OPT_split_dwarf_output);
Opts.OutputPath = std::string(Args.getLastArgValue(OPT_o));
Opts.SplitDwarfOutput =
std::string(Args.getLastArgValue(OPT_split_dwarf_output));
if (Arg *A = Args.getLastArg(OPT_filetype)) {
StringRef Name = A->getValue();
unsigned OutputType = StringSwitch<unsigned>(Name)
@@ -183,8 +190,9 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn);
Opts.RelocationModel = Args.getLastArgValue(OPT_mrelocation_model, "pic");
Opts.TargetABI = Args.getLastArgValue(OPT_target_abi);
Opts.RelocationModel =
std::string(Args.getLastArgValue(OPT_mrelocation_model, "pic"));
Opts.TargetABI = std::string(Args.getLastArgValue(OPT_target_abi));
Opts.IncrementalLinkerCompatible =
Args.hasArg(OPT_mincremental_linker_compatible);
Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym);
@@ -314,12 +322,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
// Build up the feature string from the target feature list.
std::string FS;
if (!Opts.Features.empty()) {
FS = Opts.Features[0];
for (unsigned i = 1, e = Opts.Features.size(); i != e; ++i)
FS += "," + Opts.Features[i];
}
std::string FS = llvm::join(Opts.Features, ",");
std::unique_ptr<MCStreamer> Str;
@@ -383,7 +386,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
MCSection *AsmLabel = Ctx.getMachOSection(
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
Str.get()->SwitchSection(AsmLabel);
Str.get()->EmitZeros(1);
Str.get()->emitZeros(1);
}
// Assembly to object compilation should leverage assembly info.
+1
View File
@@ -11,6 +11,7 @@
#include <clang/FrontendTool/Utils.h>
#include <llvm/ADT/IntrusiveRefCntPtr.h>
#include <llvm/Option/Option.h>
#include <llvm/Support/Host.h>
using namespace llvm;
using namespace clang;
+2 -2
View File
@@ -25,8 +25,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 || minor < 11 || minor > 15 {
return nil, fmt.Errorf("requires go version 1.11 through 1.15, got go%d.%d", major, minor)
if major != 1 || minor < 13 || minor > 16 {
return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor)
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{
+160
View File
@@ -0,0 +1,160 @@
package builder
// This file implements a job runner for the compiler, which runs jobs in
// parallel while taking care of dependencies.
import (
"fmt"
"runtime"
"time"
)
// Set to true to enable logging in the job runner. This may help to debug
// concurrency or performance issues.
const jobRunnerDebug = false
type jobState uint8
const (
jobStateQueued jobState = iota // not yet running
jobStateRunning // running
jobStateFinished // finished running
)
// compileJob is a single compiler job, comparable to a single Makefile target.
// It is used to orchestrate various compiler tasks that can be run in parallel
// but that have dependencies and thus have limitations in how they can be run.
type compileJob struct {
description string // description, only used for logging
dependencies []*compileJob
run func() error
state jobState
err error // error if finished
duration time.Duration // how long it took to run this job (only set after finishing)
}
// readyToRun returns whether this job is ready to run: it is itself not yet
// started and all dependencies are finished.
func (job *compileJob) readyToRun() bool {
if job.state != jobStateQueued {
// Already running or finished, so shouldn't be run again.
return false
}
// Check dependencies.
for _, dep := range job.dependencies {
if dep.state != jobStateFinished {
// A dependency is not finished, so this job has to wait until it
// is.
return false
}
}
// All conditions are satisfied.
return true
}
// runJobs runs all the jobs indicated in the jobs slice and returns the error
// of the first job that fails to run.
// It runs all jobs in the order of the slice, as long as all dependencies have
// already run. Therefore, if some jobs are preferred to run before others, they
// should be ordered as such in this slice.
func runJobs(jobs []*compileJob) error {
// Create channels to communicate with the workers.
doneChan := make(chan *compileJob)
workerChan := make(chan *compileJob)
defer close(workerChan)
// Start a number of workers.
for i := 0; i < runtime.NumCPU(); i++ {
if jobRunnerDebug {
fmt.Println("## starting worker", i)
}
go jobWorker(workerChan, doneChan)
}
// Send each job in the jobs slice to a worker, taking care of job
// dependencies.
numRunningJobs := 0
var totalTime time.Duration
start := time.Now()
for {
// If there are free workers, try starting a new job (if one is
// available). If it succeeds, try again to fill the entire worker pool.
if numRunningJobs < runtime.NumCPU() {
jobToRun := nextJob(jobs)
if jobToRun != nil {
// Start job.
if jobRunnerDebug {
fmt.Println("## start: ", jobToRun.description)
}
jobToRun.state = jobStateRunning
workerChan <- jobToRun
numRunningJobs++
continue
}
}
// When there are no jobs running, all jobs in the jobs slice must have
// been finished. Therefore, the work is done.
if numRunningJobs == 0 {
break
}
// Wait until a job is finished.
job := <-doneChan
job.state = jobStateFinished
numRunningJobs--
totalTime += job.duration
if jobRunnerDebug {
fmt.Println("## finished:", job.description, "(time "+job.duration.String()+")")
}
if job.err != nil {
// Wait for running jobs to finish.
for numRunningJobs != 0 {
<-doneChan
numRunningJobs--
}
// Return error of first failing job.
return job.err
}
}
// Some statistics, if debugging.
if jobRunnerDebug {
// Total duration of running all jobs.
duration := time.Since(start)
fmt.Println("## total: ", duration)
// The individual time of each job combined. On a multicore system, this
// should be lower than the total above.
fmt.Println("## job sum: ", totalTime)
}
return nil
}
// nextJob returns the first ready-to-run job.
// This is an implementation detail of runJobs.
func nextJob(jobs []*compileJob) *compileJob {
for _, job := range jobs {
if job.readyToRun() {
return job
}
}
return nil
}
// jobWorker is the goroutine that runs received jobs.
// This is an implementation detail of runJobs.
func jobWorker(workerChan, doneChan chan *compileJob) {
for job := range workerChan {
start := time.Now()
err := job.run()
if err != nil {
job.err = err
}
job.duration = time.Since(start)
doneChan <- job
}
}
+74 -29
View File
@@ -1,7 +1,6 @@
package builder
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -40,34 +39,64 @@ func (l *Library) sourcePaths(target string) []string {
}
// Load the library archive, possibly generating and caching it if needed.
func (l *Library) Load(target string) (path string, err error) {
// The resulting file is stored in the provided tmpdir, which is expected to be
// removed after the Load call.
func (l *Library) Load(target, tmpdir string) (path string, err error) {
path, job, err := l.load(target, "", tmpdir)
if err != nil {
return "", err
}
if job != nil {
jobs := append([]*compileJob{job}, job.dependencies...)
err = runJobs(jobs)
}
return path, err
}
// load returns a path to the library file for the given target, loading it from
// cache if possible. It will return a non-zero compiler job if the library
// wasn't cached, this job (and its dependencies) must be run before the library
// path is valid.
// The provided tmpdir will be used to store intermediary files and possibly the
// output archive file, it is expected to be removed after use.
func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob, err error) {
// Try to load a precompiled library.
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled library for this OS/architecture. Return the path
// directly.
return precompiledPath, nil
return precompiledPath, nil, nil
}
outfile := l.name + "-" + target + ".a"
var outfile string
if cpu != "" {
outfile = l.name + "-" + target + "-" + cpu + ".a"
} else {
outfile = l.name + "-" + target + ".a"
}
// Try to fetch this library from the cache.
if path, err := cacheLoad(outfile, commands["clang"][0], l.sourcePaths(target)); path != "" || err != nil {
if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil {
// Cache hit.
return path, err
return path, nil, err
}
// Cache miss, build it now.
dirPrefix := "tinygo-" + l.name
remapDir := filepath.Join(os.TempDir(), dirPrefix)
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
err = os.Mkdir(dir, 0777)
if err != nil {
return "", err
return "", nil, err
}
defer os.RemoveAll(dir)
// Precalculate the flags to the compiler invocation.
// Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the archive
// itself, which varies each run.
args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
if cpu != "" {
args = append(args, "-mcpu="+cpu)
}
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
}
@@ -78,28 +107,44 @@ func (l *Library) Load(target string) (path string, err error) {
args = append(args, "-march=rv64gc", "-mabi=lp64")
}
// Compile all sources.
// Create job to put all the object files in a single archive. This archive
// file is the (static) library file.
var objs []string
arpath := filepath.Join(dir, l.name+".a")
job = &compileJob{
description: "ar " + l.name + ".a",
run: func() error {
// Create an archive of all object files.
err := makeArchive(arpath, objs)
if err != nil {
return err
}
// Store this archive in the cache.
_, err = cacheStore(arpath, outfile, l.sourcePaths(target))
return err
},
}
// Create jobs to compile all sources. These jobs are depended upon by the
// archive job above, so must be run first.
for _, srcpath := range l.sourcePaths(target) {
srcpath := srcpath // avoid concurrency issues by redefining inside the loop
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
objs = append(objs, objpath)
// 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.
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
if err != nil {
return "", &commandError{"failed to build", srcpath, err}
}
job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath,
run: func() error {
var compileArgs []string
compileArgs = append(compileArgs, args...)
compileArgs = append(compileArgs, "-o", objpath, srcpath)
err := runCCompiler("clang", compileArgs...)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
return nil
},
})
}
// Put all the object files in a single archive. This archive file will be
// used to statically link this library.
arpath := filepath.Join(dir, l.name+".a")
err = makeArchive(arpath, objs)
if err != nil {
return "", err
}
// Store this archive in the cache.
return cacheStore(arpath, outfile, commands["clang"][0], l.sourcePaths(target))
return arpath, job, nil
}
+14 -3
View File
@@ -54,9 +54,10 @@ type constantInfo struct {
// 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
args []paramInfo
results *ast.FieldList
pos token.Pos
variadic bool
}
// paramInfo is a parameter of a CGo function (see functionInfo).
@@ -484,6 +485,16 @@ func (p *cgoPackage) addFuncDecls() {
Results: fn.results,
},
}
if fn.variadic {
decl.Doc = &ast.CommentGroup{
List: []*ast.Comment{
&ast.Comment{
Slash: fn.pos,
Text: "//go:variadic",
},
},
}
}
obj.Decl = decl
for i, arg := range fn.args {
args[i] = &ast.Field{
+18 -3
View File
@@ -5,6 +5,7 @@ import (
"flag"
"fmt"
"go/ast"
"go/build"
"go/format"
"go/parser"
"go/token"
@@ -23,7 +24,7 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
// normalizeResult normalizes Go source code that comes out of tests across
// platforms and Go versions.
func normalizeResult(result string) string {
actual := strings.Replace(result, "\r\n", "\n", -1)
actual := strings.ReplaceAll(result, "\r\n", "\n")
// Make sure all functions are wrapped, even those that would otherwise be
// single-line functions. This is necessary because Go 1.14 changed the way
@@ -41,6 +42,20 @@ func TestCGo(t *testing.T) {
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
name := name // avoid a race condition
t.Run(name, func(t *testing.T) {
// Skip tests that require specific Go version.
if name == "errors" {
ok := false
for _, version := range build.Default.ReleaseTags {
if version == "go1.16" {
ok = true
break
}
}
if !ok {
t.Skip("Results for errors test are only valid for Go 1.16+")
}
}
// Read the AST in memory.
path := filepath.Join("testdata", name+".go")
fset := token.NewFileSet()
@@ -98,7 +113,7 @@ func TestCGo(t *testing.T) {
if err != nil {
t.Fatalf("could not read expected output: %v", err)
}
expected := strings.Replace(string(expectedBytes), "\r\n", "\n", -1)
expected := strings.ReplaceAll(string(expectedBytes), "\r\n", "\n")
// Check whether the output is as expected.
if expected != actual {
@@ -139,7 +154,7 @@ func formatDiagnostic(err error) string {
msg := err.Error()
if runtime.GOOS == "windows" {
// Fix Windows path slashes.
msg = strings.Replace(msg, "testdata\\", "testdata/", -1)
msg = strings.ReplaceAll(msg, "testdata\\", "testdata/")
}
return "// " + msg + "\n"
}
+2 -4
View File
@@ -152,12 +152,10 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
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,
pos: pos,
variadic: C.clang_isFunctionTypeVariadic(cursorType) != 0,
}
p.functions[name] = fn
for i := 0; i < numArgs; i++ {
+7 -7
View File
@@ -1,14 +1,14 @@
// +build !byollvm
// +build !llvm9,!llvm11
// +build !llvm10
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
#cgo linux CFLAGS: -I/usr/lib/llvm-11/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@11/include
#cgo freebsd CFLAGS: -I/usr/local/llvm11/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-11/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@11/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm11/lib -lclang
*/
import "C"
+14
View File
@@ -0,0 +1,14 @@
// +build !byollvm
// +build llvm10
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
*/
import "C"
-14
View File
@@ -1,14 +0,0 @@
// +build !byollvm
// +build llvm11
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-11/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@11/include
#cgo freebsd CFLAGS: -I/usr/local/llvm11/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-11/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@11/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm11/lib -lclang
*/
import "C"
-14
View File
@@ -1,14 +0,0 @@
// +build !byollvm
// +build llvm9
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-9/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@9/include
#cgo freebsd CFLAGS: -I/usr/local/llvm9/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-9/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm9/lib -lclang
*/
import "C"
+2 -2
View File
@@ -4,10 +4,10 @@
// testdata/errors.go:13:23: unexpected token )
// Type checking errors after CGo processing:
// testdata/errors.go:102: 2 << 10 (untyped int constant 2048) overflows uint8
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as uint8 value in variable declaration (overflows)
// testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1
// testdata/errors.go:110: C.SOME_CONST_3 (untyped int constant 1234) overflows byte
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
package main
+10
View File
@@ -104,6 +104,10 @@ typedef struct {
unsigned char e : 3;
// Note that C++ allows bitfields bigger than the underlying type.
} bitfield_t;
// Function signatures.
void variadic0();
void variadic2(int x, int y, ...);
*/
import "C"
@@ -163,3 +167,9 @@ func accessUnion() {
var _ *C.int = union2d.unionfield_i()
var _ *[2]float64 = union2d.unionfield_d()
}
// Test function signatures.
func accessFunctions() {
C.variadic0()
C.variadic2(3, 5)
}
+5
View File
@@ -4,6 +4,11 @@ import "unsafe"
var _ unsafe.Pointer
func C.variadic0() //go:variadic
func C.variadic2(x C.int, y C.int) //go:variadic
var C.variadic0$funcaddr unsafe.Pointer
var C.variadic2$funcaddr unsafe.Pointer
const C.option2A = 20
const C.optionA = 0
const C.optionB = 1
+21 -38
View File
@@ -7,7 +7,6 @@ import (
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/goenv"
@@ -22,29 +21,6 @@ type Config struct {
TestConfig TestConfig
}
// FuncValueImplementation is an enum for the particular implementations of Go
// func values.
type FuncValueImplementation int
// These constants describe the various possible implementations of Go func
// values.
const (
FuncValueNone FuncValueImplementation = iota
// A func value is implemented as a pair of pointers:
// {context, function pointer}
// where the context may be a pointer to a heap-allocated struct containing
// the free variables, or it may be undef if the function being pointed to
// doesn't need a context. The function pointer is a regular function
// pointer.
FuncValueDoubleword
// As funcValueDoubleword, but with the function pointer replaced by a
// unique ID per function signature. Function values are called by using a
// switch statement and choosing which function to call.
FuncValueSwitch
)
// Triple returns the LLVM target triple, like armv6m-none-eabi.
func (c *Config) Triple() string {
return c.Target.Triple
@@ -144,14 +120,24 @@ func (c *Config) Scheduler() string {
// FuncImplementation picks an appropriate func value implementation for the
// target.
func (c *Config) FuncImplementation() FuncValueImplementation {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
func (c *Config) FuncImplementation() string {
switch c.Scheduler() {
case "none", "coroutines":
return FuncValueSwitch
case "tasks":
return FuncValueDoubleword
// A func value is implemented as a pair of pointers:
// {context, function pointer}
// where the context may be a pointer to a heap-allocated struct
// containing the free variables, or it may be undef if the function
// being pointed to doesn't need a context. The function pointer is a
// regular function pointer.
return "doubleword"
case "none", "coroutines":
// As "doubleword", 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.
// Pick the switch implementation with the coroutines scheduler, as it
// allows the use of blocking inside a function that is used as a func
// value.
return "switch"
default:
panic("unknown scheduler type")
}
@@ -179,7 +165,7 @@ func (c *Config) AutomaticStackSize() bool {
func (c *Config) CFlags() []string {
cflags := append([]string{}, c.Options.CFlags...)
for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.Replace(flag, "{root}", goenv.Get("TINYGOROOT"), -1))
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
}
if c.Target.Libc == "picolibc" {
root := goenv.Get("TINYGOROOT")
@@ -200,15 +186,9 @@ func (c *Config) LDFlags() []string {
// Merge and adjust LDFlags.
ldflags := append([]string{}, c.Options.LDFlags...)
for _, flag := range c.Target.LDFlags {
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root))
}
ldflags = append(ldflags, "-L", root)
if c.Target.GOARCH == "wasm" {
// Round heap size to next multiple of 65536 (the WebAssembly page
// size).
heapSize := (c.Options.HeapSize + (65536 - 1)) &^ (65536 - 1)
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
}
if c.Target.LinkerScript != "" {
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
}
@@ -303,6 +283,9 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
}
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
for _, cmd := range c.Target.OpenOCDCommands {
args = append(args, "-c", cmd)
}
if c.Target.OpenOCDTransport != "" {
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
}
+1 -1
View File
@@ -23,6 +23,7 @@ type Options struct {
PrintIR bool
DumpSSA bool
VerifyIR bool
PrintCommands bool
Debug bool
PrintSizes string
PrintStacks bool
@@ -30,7 +31,6 @@ type Options struct {
LDFlags []string
Tags string
WasmAbi string
HeapSize int64
TestConfig TestConfig
Programmer string
}
+1
View File
@@ -52,6 +52,7 @@ type TargetSpec struct {
OpenOCDInterface string `json:"openocd-interface"`
OpenOCDTarget string `json:"openocd-target"`
OpenOCDTransport string `json:"openocd-transport"`
OpenOCDCommands []string `json:"openocd-commands"`
JLinkDevice string `json:"jlink-device"`
CodeModel string `json:"code-model"`
RelocationModel string `json:"relocation-model"`
+6 -6
View File
@@ -16,7 +16,7 @@ import (
// slice. This is required by the Go language spec: an index out of bounds must
// cause a panic.
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -48,7 +48,7 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases.
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -104,7 +104,7 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
// createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake.
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -189,7 +189,7 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
// This function assumes that the shift value is signed.
func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
if b.fn.IsNoBounds() {
if b.info.nobounds {
// Function disabled bounds checking - skip shift check.
return
}
@@ -212,8 +212,8 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
}
}
faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next")
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block.
+27
View File
@@ -1,6 +1,8 @@
package compiler
import (
"strings"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -35,6 +37,31 @@ func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
ptr := b.getValue(call.Args[0])
old := b.getValue(call.Args[1])
newVal := b.getValue(call.Args[2])
if strings.HasSuffix(name, "64") {
arch := strings.Split(b.Triple, "-")[0]
if strings.HasPrefix(arch, "arm") && strings.HasSuffix(arch, "m") {
// Work around a bug in LLVM, at least LLVM 11:
// https://reviews.llvm.org/D95891
// Check for armv6m, armv7, armv7em, and perhaps others.
// See also: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
compareAndSwap := b.mod.NamedFunction("__sync_val_compare_and_swap_8")
if compareAndSwap.IsNil() {
// Declare the function if it isn't already declared.
i64Type := b.ctx.Int64Type()
fnType := llvm.FunctionType(i64Type, []llvm.Type{llvm.PointerType(i64Type, 0), i64Type, i64Type}, false)
compareAndSwap = llvm.AddFunction(b.mod, "__sync_val_compare_and_swap_8", fnType)
}
actualOldValue := b.CreateCall(compareAndSwap, []llvm.Value{ptr, old, newVal}, "")
// The __sync_val_compare_and_swap_8 function returns the old
// value. However, we shouldn't return the old value, we should
// return whether the compare/exchange was successful. This is
// easily done by comparing the returned (actual) old value with
// the expected old value passed to
// __sync_val_compare_and_swap_8.
swapped := b.CreateICmp(llvm.IntEQ, old, actualOldValue, "")
return swapped, true
}
}
tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true)
swapped := b.CreateExtractValue(tuple, 1, "")
return swapped, true
+6 -5
View File
@@ -4,6 +4,7 @@ import (
"go/types"
"strconv"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -34,14 +35,14 @@ const (
// createCall creates a new call to runtime.<fnName> with the given arguments.
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
fullName := "runtime." + fnName
fn := b.mod.NamedFunction(fullName)
if fn.IsNil() {
panic("trying to call non-existent function: " + fullName)
fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function)
llvmFn := b.getFunction(fn)
if llvmFn.IsNil() {
panic("trying to call non-existent function: " + fn.RelString(nil))
}
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
return b.createCall(fn, args, name)
return b.createCall(llvmFn, args, name)
}
// createCall creates a call to the given function with the arguments possibly
+1 -2
View File
@@ -79,8 +79,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
}
// createChanClose closes the given channel.
func (b *builder) createChanClose(param ssa.Value) {
ch := b.getValue(param)
func (b *builder) createChanClose(ch llvm.Value) {
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
}
+385 -382
View File
File diff suppressed because it is too large Load Diff
+172
View File
@@ -0,0 +1,172 @@
package compiler
import (
"flag"
"go/types"
"io/ioutil"
"regexp"
"strconv"
"strings"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/loader"
"tinygo.org/x/go-llvm"
)
// Pass -update to go test to update the output of the test files.
var flagUpdate = flag.Bool("update", false, "update tests based on test output")
// Basic tests for the compiler. Build some Go files and compare the output with
// the expected LLVM IR for regression testing.
func TestCompiler(t *testing.T) {
target, err := compileopts.LoadTarget("i686--linux")
if err != nil {
t.Fatal("failed to load target:", err)
}
config := &compileopts.Config{
Options: &compileopts.Options{},
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
}
tests := []string{
"basic.go",
"pointer.go",
"slice.go",
"float.go",
}
for _, testCase := range tests {
t.Run(testCase, func(t *testing.T) {
// Load entire program AST into memory.
lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, config.ClangHeaders, types.Config{
Sizes: Sizes(machine),
})
if err != nil {
t.Fatal("failed to create target machine:", err)
}
err = lprogram.Parse()
if err != nil {
t.Fatalf("could not parse test case %s: %s", testCase, err)
}
// Compile AST to IR.
pkg := lprogram.MainPkg()
mod, errs := CompilePackage(testCase, pkg, machine, compilerConfig, false)
if errs != nil {
for _, err := range errs {
t.Log("error:", err)
}
return
}
// Optimize IR a little.
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer funcPasses.Dispose()
funcPasses.AddInstructionCombiningPass()
funcPasses.InitializeFunc()
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll"
// Update test if needed. Do not check the result.
if *flagUpdate {
err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666)
if err != nil {
t.Error("failed to write updated output file:", err)
}
return
}
expected, err := ioutil.ReadFile(outfile)
if err != nil {
t.Fatal("failed to read golden file:", err)
}
if !fuzzyEqualIR(mod.String(), string(expected)) {
t.Errorf("output does not match expected output:\n%s", mod.String())
}
})
}
}
var alignRegexp = regexp.MustCompile(", align [0-9]+$")
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
// equal. That means, only relevant lines are compared (excluding comments
// etc.).
func fuzzyEqualIR(s1, s2 string) bool {
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
if len(lines1) != len(lines2) {
return false
}
for i, line1 := range lines1 {
line2 := lines2[i]
match1 := alignRegexp.MatchString(line1)
match2 := alignRegexp.MatchString(line2)
if match1 != match2 {
// Only one of the lines has the align keyword. Remove it.
// This is a change to make the test work in both LLVM 10 and LLVM
// 11 (LLVM 11 appears to automatically add alignment everywhere).
line1 = alignRegexp.ReplaceAllString(line1, "")
line2 = alignRegexp.ReplaceAllString(line2, "")
}
if line1 != line2 {
return false
}
}
return true
}
// filterIrrelevantIRLines removes lines from the input slice of strings that
// are not relevant in comparing IR. For example, empty lines and comments are
// stripped out.
func filterIrrelevantIRLines(lines []string) []string {
var out []string
llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0])
if err != nil {
// Note: this should never happen and if it does, it will always happen
// for a particular build because llvm.Version is a constant.
panic(err)
}
for _, line := range lines {
line = strings.Split(line, ";")[0] // strip out comments/info
line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments
if line == "" {
continue
}
if strings.HasPrefix(line, "source_filename = ") {
continue
}
if llvmVersion < 10 && strings.HasPrefix(line, "attributes ") {
// Ignore attribute groups. These may change between LLVM versions.
// Right now test outputs are for LLVM 10.
continue
}
if llvmVersion < 10 && strings.HasPrefix(line, "target datalayout ") {
// Ignore the target layout. This may change between LLVM versions.
continue
}
out = append(out, line)
}
return out
}
+38 -38
View File
@@ -14,9 +14,9 @@ package compiler
// frames.
import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -26,9 +26,9 @@ import (
// calls.
func (b *builder) deferInitFunc() {
// Some setup.
b.deferFuncs = make(map[*ir.Function]int)
b.deferFuncs = make(map[*ssa.Function]int)
b.deferInvokeFuncs = make(map[string]int)
b.deferClosureFuncs = make(map[*ir.Function]int)
b.deferClosureFuncs = make(map[*ssa.Function]int)
b.deferExprFuncs = make(map[ssa.Value]int)
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
@@ -107,13 +107,11 @@ func (b *builder) createDefer(instr *ssa.Defer) {
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call.
fn := b.ir.GetFunction(callee)
if _, ok := b.deferFuncs[fn]; !ok {
b.deferFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, fn)
if _, ok := b.deferFuncs[callee]; !ok {
b.deferFuncs[callee] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, callee)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false)
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
@@ -135,7 +133,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
context := b.CreateExtractValue(closure, 0, "")
// Get the callback number.
fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
fn := makeClosure.Fn.(*ssa.Function)
if _, ok := b.deferClosureFuncs[fn]; !ok {
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
@@ -155,19 +153,19 @@ func (b *builder) createDefer(instr *ssa.Defer) {
valueTypes = append(valueTypes, context.Type())
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
var funcName string
switch builtin.Name() {
case "close":
funcName = "chanClose"
default:
b.addError(instr.Pos(), "todo: Implement defer for "+builtin.Name())
return
var argTypes []types.Type
var argValues []llvm.Value
for _, arg := range instr.Call.Args {
argTypes = append(argTypes, arg.Type())
argValues = append(argValues, b.getValue(arg))
}
if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{
funcName,
len(b.allDeferFuncs),
callName: builtin.Name(),
pos: builtin.Pos(),
argTypes: argTypes,
callback: len(b.allDeferFuncs),
}
b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value)
}
@@ -176,10 +174,9 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
for _, param := range argValues {
values = append(values, param)
valueTypes = append(valueTypes, param.Type())
}
} else {
@@ -223,7 +220,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
}
if b.NeedsStackObjects() {
if b.NeedsStackObjects {
b.trackPointer(alloca)
}
b.CreateStore(deferFrame, alloca)
@@ -251,10 +248,10 @@ func (b *builder) createRunDefers() {
// }
// Create loop.
loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end")
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
b.CreateBr(loophead)
// Create loop head:
@@ -286,7 +283,7 @@ func (b *builder) createRunDefers() {
// Create switch case, for example:
// case 0:
// // run first deferred call
block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback")
block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
@@ -350,7 +347,7 @@ func (b *builder) createRunDefers() {
b.createCall(fnPtr, forwardParams, "")
case *ir.Function:
case *ssa.Function:
// Direct call.
// Get the real defer struct type and cast to it.
@@ -372,7 +369,7 @@ func (b *builder) createRunDefers() {
// Plain TinyGo functions add some extra parameters to implement async functionality and function recievers.
// These parameters should not be supplied when calling into an external C/ASM function.
if !callback.IsExported() {
if !b.getFunctionInfo(callback).exported {
// Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
@@ -382,11 +379,11 @@ func (b *builder) createRunDefers() {
}
// Call real function.
b.createCall(callback.LLVMFn, forwardParams, "")
b.createCall(b.getFunction(callback), forwardParams, "")
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := b.ir.GetFunction(callback.Fn.(*ssa.Function))
fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
@@ -409,7 +406,7 @@ func (b *builder) createRunDefers() {
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Call deferred function.
b.createCall(fn.LLVMFn, forwardParams, "")
b.createCall(b.getFunction(fn), forwardParams, "")
case *ssa.Builtin:
db := b.deferBuiltinFuncs[callback]
@@ -426,15 +423,18 @@ func (b *builder) createRunDefers() {
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
var forwardParams []llvm.Value
var argValues []llvm.Value
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < params.Len(); i++ {
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
argValues = append(argValues, forwardParam)
}
b.createRuntimeCall(db.funcName, forwardParams, "")
_, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos)
if err != nil {
b.diagnostics = append(b.diagnostics, err)
}
default:
panic("unknown deferred function type")
}
+1 -1
View File
@@ -14,7 +14,7 @@ import (
// makeError makes it easy to create an error from a token.Pos with a message.
func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
return types.Error{
Fset: c.ir.Program.Fset,
Fset: c.program.Fset,
Pos: pos,
Msg: msg,
}
+11 -12
View File
@@ -6,7 +6,6 @@ package compiler
import (
"go/types"
"github.com/tinygo-org/tinygo/compileopts"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -21,11 +20,11 @@ func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signat
// context.
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
var funcValueScalar llvm.Value
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
switch c.FuncImplementation {
case "doubleword":
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
case compileopts.FuncValueSwitch:
case "switch":
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
@@ -67,10 +66,10 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
// value. This may be an expensive operation.
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = b.CreateExtractValue(funcValue, 0, "")
switch b.FuncImplementation() {
case compileopts.FuncValueDoubleword:
switch b.FuncImplementation {
case "doubleword":
funcPtr = b.CreateExtractValue(funcValue, 1, "")
case compileopts.FuncValueSwitch:
case "switch":
llvmSig := b.getRawFuncType(sig)
sigGlobal := b.getTypeCode(sig)
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
@@ -83,11 +82,11 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
// getFuncType returns the type of a func value given a signature.
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
switch c.FuncImplementation {
case "doubleword":
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case compileopts.FuncValueSwitch:
case "switch":
return c.getLLVMRuntimeType("funcValue")
default:
panic("unimplemented func value variant")
@@ -149,7 +148,7 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables")
}
f := b.ir.GetFunction(expr.Fn.(*ssa.Function))
f := expr.Fn.(*ssa.Function)
// Collect all bound variables.
boundVars := make([]llvm.Value, len(expr.Bindings))
@@ -164,5 +163,5 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
context := b.emitPointerPack(boundVars)
// Create the closure.
return b.createFuncValue(f.LLVMFn, context, f.Signature), nil
return b.createFuncValue(b.getFunction(f), context, f.Signature), nil
}
+12 -9
View File
@@ -7,6 +7,7 @@ import (
"go/token"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -20,19 +21,20 @@ import (
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
paramBundle := b.emitPointerPack(params)
var callee, stackSize llvm.Value
switch b.Scheduler() {
switch b.Scheduler {
case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
if b.AutomaticStackSize() {
if b.AutomaticStackSize {
// The stack size is not known until after linking. Call a dummy
// function that will be replaced with a load from a special ELF
// section that contains the stack size (and is modified after
// linking).
stackSize = b.createCall(b.mod.NamedFunction("internal/task.getGoroutineStackSize"), []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize")
stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function))
stackSize = b.createCall(stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize")
} else {
// The stack size is fixed at compile time. By emitting it here as a
// constant, it can be optimized.
stackSize = llvm.ConstInt(b.uintptrType, b.Target.DefaultStackSize, false)
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
}
case "coroutines":
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
@@ -42,7 +44,8 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
default:
panic("unreachable")
}
b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
@@ -87,8 +90,8 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
entry := c.ctx.AddBasicBlock(wrapper, "entry")
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
if c.Debug {
pos := c.program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
@@ -144,8 +147,8 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
entry := c.ctx.AddBasicBlock(wrapper, "entry")
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
if c.Debug {
pos := c.program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
+69 -24
View File
@@ -11,7 +11,6 @@ import (
"strconv"
"strings"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -236,7 +235,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
ms := c.ir.Program.MethodSets.MethodSet(typ)
ms := c.program.MethodSets.MethodSet(typ)
if ms.Len() == 0 {
// no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
@@ -247,15 +246,16 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
if f.LLVMFn.IsNil() {
fn := c.program.MethodValue(method)
llvmFn := c.getFunction(fn)
if llvmFn.IsNil() {
// compiler error, so panic
panic("cannot find function: " + f.LinkName())
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
}
fn := c.getInterfaceInvokeWrapper(f)
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn)
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal,
llvm.ConstPtrToInt(fn, c.uintptrType),
llvm.ConstPtrToInt(wrapper, c.uintptrType),
})
methods[i] = methodInfo
}
@@ -303,7 +303,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
// external *i8 indicating the indicating the signature of this method. It is
// used during the interface lowering pass.
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
signature := ir.MethodSignature(method)
signature := methodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() {
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
@@ -357,8 +357,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
// value.
prevBlock := b.GetInsertBlock()
okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next")
okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)
@@ -436,8 +436,8 @@ func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value
// value, dereferences or unpacks it if necessary, and calls the real method.
// If the method to wrap has a pointer receiver, no wrapping is necessary and
// the function is returned directly.
func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
wrapperName := f.LinkName() + "$invoke"
func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value {
wrapperName := llvmFn.Name() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() {
// Wrapper already created. Return it directly.
@@ -445,7 +445,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
}
// Get the expanded receiver type.
receiverType := c.getLLVMType(f.Params[0].Type())
receiverType := c.getLLVMType(fn.Params[0].Type())
var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
@@ -457,17 +457,15 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// 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
return llvmFn
}
// create wrapper function
fnType := f.LLVMFn.Type().ElementType()
fnType := llvmFn.Type().ElementType()
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
if f.LLVMFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle")
}
wrapper.LastParam().SetName("parentHandle")
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
@@ -480,9 +478,9 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
defer b.Builder.Dispose()
// add debug info if needed
if c.Debug() {
pos := c.ir.Program.Fset.Position(f.Pos())
difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line)
if c.Debug {
pos := c.program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
@@ -492,13 +490,60 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(f.LLVMFn, params, "")
if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(llvmFn, params, "")
b.CreateRetVoid()
} else {
ret := b.CreateCall(f.LLVMFn, params, "ret")
ret := b.CreateCall(llvmFn, params, "ret")
b.CreateRet(ret)
}
return wrapper
}
// methodSignature creates a readable version of a method signature (including
// the function name, excluding the receiver name). This string is used
// internally to match interfaces and to call the correct method on an
// interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func methodSignature(method *types.Func) string {
return method.Name() + signature(method.Type().(*types.Signature))
}
// Make a readable version of a function (pointer) signature.
// Examples:
//
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
s := ""
if sig.Params().Len() == 0 {
s += "()"
} else {
s += "("
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Params().At(i).Type().String()
}
s += ")"
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + sig.Results().At(0).Type().String()
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Results().At(i).Type().String()
}
s += ")"
}
return s
}
+4 -4
View File
@@ -39,7 +39,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass.
globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
@@ -55,8 +55,8 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
global.SetInitializer(initializer)
// Add debug info to the interrupt global.
if b.Debug() {
pos := b.ir.Program.Fset.Position(instr.Pos())
if b.Debug {
pos := b.program.Fset.Position(instr.Pos())
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
LinkageName: globalName,
@@ -79,7 +79,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// PLIC where each interrupt must be enabled using the interrupt number, and
// thus keeps the Interrupt object alive.
// This call is removed during interrupt lowering.
if strings.HasPrefix(b.Triple(), "avr") {
if strings.HasPrefix(b.Triple, "avr") {
useFn := b.mod.NamedFunction("runtime/interrupt.use")
if useFn.IsNil() {
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
+1 -1
View File
@@ -44,7 +44,7 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.Config, values)
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.NeedsStackObjects, values)
}
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
+47 -28
View File
@@ -5,7 +5,6 @@ package llvmutil
// itself if possible and legal.
import (
"github.com/tinygo-org/tinygo/compileopts"
"tinygo.org/x/go-llvm"
)
@@ -13,7 +12,7 @@ import (
// 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.
// If the values are all constants, they are be stored in a constant global and deduplicated.
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.Config, values []llvm.Value) llvm.Value {
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bool, values []llvm.Value) llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
@@ -26,7 +25,6 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
packedType := ctx.StructType(valueTypes, false)
// Allocate memory for the packed data.
var packedAlloc, packedHeapAlloc llvm.Value
size := targetData.TypeAllocSize(packedType)
if size == 0 {
return llvm.ConstPointerNull(i8ptrType)
@@ -39,9 +37,39 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
// Try to keep this cast in SSA form.
return builder.CreateIntToPtr(values[0], 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, _, _ = CreateTemporaryAlloca(builder, mod, packedType, "")
// it in a *i8 alloca first and load the *i8 value from there. This is
// effectively a bitcast.
packedAlloc, _, _ := CreateTemporaryAlloca(builder, mod, i8ptrType, "")
if size < targetData.TypeAllocSize(i8ptrType) {
// The alloca is bigger than the value that will be stored in it.
// To avoid having some bits undefined, zero the alloca first.
// Hopefully this will get optimized away.
builder.CreateStore(llvm.ConstNull(i8ptrType), packedAlloc)
}
// Store all values in the alloca.
packedAllocCast := builder.CreateBitCast(packedAlloc, llvm.PointerType(packedType, 0), "")
for i, value := range values {
indices := []llvm.Value{
llvm.ConstInt(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
}
gep := builder.CreateInBoundsGEP(packedAllocCast, indices, "")
builder.CreateStore(value, gep)
}
// Load value (the *i8) from the alloca.
result := builder.CreateLoad(packedAlloc, "")
// End the lifetime of the alloca, to help the optimizer.
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
return result
} else {
// Check if the values are all constants.
constant := true
@@ -67,12 +95,12 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(uintptrType, size, false)
alloc := mod.NamedFunction("runtime.alloc")
packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{
packedHeapAlloc := builder.CreateCall(alloc, []llvm.Value{
sizeValue,
llvm.Undef(i8ptrType), // unused context parameter
llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "")
if config.NeedsStackObjects() {
if needsStackObjects {
trackPointer := mod.NamedFunction("runtime.trackPointer")
builder.CreateCall(trackPointer, []llvm.Value{
packedHeapAlloc,
@@ -80,28 +108,19 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "")
}
packedAlloc = 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(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
}
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
builder.CreateStore(value, gep)
}
packedAlloc := builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
if packedHeapAlloc.IsNil() {
// Load value (as *i8) from the alloca.
packedAlloc = builder.CreateBitCast(packedAlloc, llvm.PointerType(i8ptrType, 0), "")
result := builder.CreateLoad(packedAlloc, "")
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
return result
} else {
// Get the original heap allocation pointer, which already is an *i8.
// Store all values in the heap pointer.
for i, value := range values {
indices := []llvm.Value{
llvm.ConstInt(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
}
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
builder.CreateStore(value, gep)
}
// Return the original heap allocation pointer, which already is an *i8.
return packedHeapAlloc
}
}
+164
View File
@@ -0,0 +1,164 @@
package compiler
// This file implements a simple reachability analysis, to reduce compile time.
// This DCE pass used to be necessary for improving other passes but now it
// isn't necessary anymore.
import (
"errors"
"go/types"
"sort"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
)
type dceState struct {
*compilerContext
functions []*dceFunction
functionMap map[*ssa.Function]*dceFunction
}
type dceFunction struct {
*ssa.Function
functionInfo
flag bool // used by dead code elimination
}
func (p *dceState) addFunction(ssaFn *ssa.Function) {
if _, ok := p.functionMap[ssaFn]; ok {
return
}
f := &dceFunction{Function: ssaFn}
f.functionInfo = p.getFunctionInfo(ssaFn)
p.functions = append(p.functions, f)
p.functionMap[ssaFn] = f
for _, anon := range ssaFn.AnonFuncs {
p.addFunction(anon)
}
}
// simpleDCE returns a list of alive functions in the program. Compiling only
// these functions makes the compiler faster.
//
// This functionality will likely be replaced in the future with build caching.
func (c *compilerContext) simpleDCE(lprogram *loader.Program) ([]*ssa.Function, error) {
mainPkg := c.program.Package(lprogram.MainPkg().Pkg)
if mainPkg == nil {
panic("could not find main package")
}
p := &dceState{
compilerContext: c,
functionMap: make(map[*ssa.Function]*dceFunction),
}
for _, pkg := range lprogram.Sorted() {
pkg := c.program.Package(pkg.Pkg)
memberNames := make([]string, 0)
for name := range pkg.Members {
memberNames = append(memberNames, name)
}
sort.Strings(memberNames)
for _, name := range memberNames {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
p.addFunction(member)
case *ssa.Type:
methods := getAllMethods(pkg.Prog, member.Type())
if !types.IsInterface(member.Type()) {
// named type
for _, method := range methods {
p.addFunction(pkg.Prog.MethodValue(method))
}
}
case *ssa.Global:
// Ignore. Globals are not handled here.
case *ssa.NamedConst:
// Ignore: these are already resolved.
default:
panic("unknown member type: " + member.String())
}
}
}
// Initial set of live functions. Include main.main, *.init and runtime.*
// functions.
main, ok := mainPkg.Members["main"].(*ssa.Function)
if !ok {
if mainPkg.Members["main"] == nil {
return nil, errors.New("function main is undeclared in the main package")
} else {
return nil, errors.New("cannot declare main - must be func")
}
}
runtimePkg := c.program.ImportedPackage("runtime")
mathPkg := c.program.ImportedPackage("math")
taskPkg := c.program.ImportedPackage("internal/task")
p.functionMap[main].flag = true
worklist := []*ssa.Function{main}
for _, f := range p.functions {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
if f.flag {
continue
}
f.flag = true
worklist = append(worklist, f.Function)
}
}
// Mark all called functions recursively.
for len(worklist) != 0 {
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
if instr, ok := instr.(*ssa.MakeInterface); ok {
for _, sel := range getAllMethods(c.program, instr.X.Type()) {
fn := c.program.MethodValue(sel)
callee := p.functionMap[fn]
if callee == nil {
// TODO: why is this necessary?
p.addFunction(fn)
callee = p.functionMap[fn]
}
if !callee.flag {
callee.flag = true
worklist = append(worklist, callee.Function)
}
}
}
for _, operand := range instr.Operands(nil) {
if operand == nil || *operand == nil {
continue
}
switch operand := (*operand).(type) {
case *ssa.Function:
f := p.functionMap[operand]
if f == nil {
// FIXME HACK: this function should have been
// discovered already. It is not for bound methods.
p.addFunction(operand)
f = p.functionMap[operand]
}
if !f.flag {
f.flag = true
worklist = append(worklist, operand)
}
}
}
}
}
}
// Return all live functions.
liveFunctions := []*ssa.Function{}
for _, f := range p.functions {
if f.flag {
liveFunctions = append(liveFunctions, f.Function)
}
}
return liveFunctions, nil
}
+285 -2
View File
@@ -15,6 +15,269 @@ import (
"tinygo.org/x/go-llvm"
)
// functionInfo contains some information about a function or method. In
// particular, it contains information obtained from pragmas.
//
// The linkName value contains a valid link name, even if //go:linkname is not
// present.
type functionInfo struct {
module string // go:wasm-module
importName string // go:linkname, go:export - The name the developer assigns
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
exported bool // go:export, CGo
nobounds bool // go:nobounds
variadic bool // go:variadic (CGo only)
inline inlineType // go:inline
}
type inlineType int
// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
inlineDefault inlineType = iota
// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
inlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
inlineNone
)
// getFunction returns the LLVM function for the given *ssa.Function, creating
// it if needed. It can later be filled with compilerContext.createFunction().
func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
info := c.getFunctionInfo(fn)
llvmFn := c.mod.NamedFunction(info.linkName)
if !llvmFn.IsNil() {
return llvmFn
}
var retType llvm.Type
if fn.Signature.Results() == nil {
retType = c.ctx.VoidType()
} else if fn.Signature.Results().Len() == 1 {
retType = c.getLLVMType(fn.Signature.Results().At(0).Type())
} else {
results := make([]llvm.Type, 0, fn.Signature.Results().Len())
for i := 0; i < fn.Signature.Results().Len(); i++ {
results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type()))
}
retType = c.ctx.StructType(results, false)
}
var paramInfos []paramInfo
for _, param := range fn.Params {
paramType := c.getLLVMType(param.Type())
paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
paramInfos = append(paramInfos, paramFragmentInfos...)
}
// Add an extra parameter as the function context. This context is used in
// closures and bound methods, but should be optimized away when not used.
if !info.exported {
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0})
}
var paramTypes []llvm.Type
for _, info := range paramInfos {
paramTypes = append(paramTypes, info.llvmType)
}
fnType := llvm.FunctionType(retType, paramTypes, info.variadic)
llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
if strings.HasPrefix(c.Triple, "wasm") {
// C functions without prototypes like this:
// void foo();
// are actually variadic functions. However, it appears that it has been
// decided in WebAssembly that such prototype-less functions are not
// allowed in WebAssembly.
// In C, this can only happen when there are zero parameters, hence this
// check here. For more information:
// https://reviews.llvm.org/D48443
// https://github.com/WebAssembly/tool-conventions/issues/16
if info.variadic && len(fn.Params) == 0 {
attr := c.ctx.CreateStringAttribute("no-prototype", "")
llvmFn.AddFunctionAttr(attr)
}
}
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, info := range paramInfos {
if info.flags&paramIsDeferenceableOrNull == 0 {
continue
}
if info.llvmType.TypeKind() == llvm.PointerTypeKind {
el := info.llvmType.ElementType()
size := c.targetData.TypeAllocSize(el)
if size == 0 {
// dereferenceable_or_null(0) appears to be illegal in LLVM.
continue
}
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size)
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
}
}
// Set a number of function or parameter attributes, depending on the
// function. These functions are runtime functions that are known to have
// certain attributes that might not be inferred by the compiler.
switch info.linkName {
case "abort":
// On *nix systems, the "abort" functuion in libc is used to handle fatal panics.
// Mark it as noreturn so LLVM can optimize away code.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
case "runtime.alloc":
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
// returns values that are never null and never alias to an existing value.
for _, attrName := range []string{"noalias", "nonnull"} {
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
}
case "runtime.trackPointer":
// This function is necessary for tracking pointers on the stack in a
// portable way (see gc_stack_portable.go). Indicate to the optimizer
// that the only thing we'll do is read the pointer.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
}
// External/exported functions may not retain pointer values.
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
if info.exported {
// Set the wasm-import-module attribute if the function's module is set.
if info.module != "" {
// We need to add the wasm-import-module and the wasm-import-name
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", info.module)
llvmFn.AddFunctionAttr(wasmImportModuleAttr)
// Add the Wasm Import Name, if we are a named wasm import
if info.importName != "" {
wasmImportNameAttr := c.ctx.CreateStringAttribute("wasm-import-name", info.importName)
llvmFn.AddFunctionAttr(wasmImportNameAttr)
}
}
nocaptureKind := llvm.AttributeKindID("nocapture")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
for i, typ := range paramTypes {
if typ.TypeKind() == llvm.PointerTypeKind {
llvmFn.AddAttributeAtIndex(i+1, nocapture)
}
}
}
return llvmFn
}
// getFunctionInfo returns information about a function that is not directly
// present in *ssa.Function, such as the link name and whether it should be
// exported.
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
info := functionInfo{}
if strings.HasPrefix(f.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
info.linkName = f.Name()[2:]
info.exported = true
} else {
// Pick the default linkName.
info.linkName = f.RelString(nil)
}
// Check for //go: pragmas, which may change the link name (among others).
info.parsePragmas(f)
return info
}
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
// //export or //go:noinline.
func (info *functionInfo) parsePragmas(f *ssa.Function) {
if f.Syntax() == nil {
return
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
// Our importName for a wasm module (if we are compiling to wasm), or llvm link name
var importName string
for _, comment := range decl.Doc.List {
text := comment.Text
if strings.HasPrefix(text, "//export ") {
// Rewrite '//export' to '//go:export' for compatibility with
// gc.
text = "//go:" + text[2:]
}
if !strings.HasPrefix(text, "//go:") {
continue
}
parts := strings.Fields(text)
switch parts[0] {
case "//go:export":
if len(parts) != 2 {
continue
}
importName = parts[1]
info.exported = true
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
info.module = parts[1]
case "//go:inline":
info.inline = inlineHint
case "//go:noinline":
info.inline = inlineNone
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
info.nobounds = true
}
case "//go:variadic":
// The //go:variadic pragma is emitted by the CGo preprocessing
// pass for C variadic functions. This includes both explicit
// (with ...) and implicit (no parameters in signature)
// functions.
if strings.HasPrefix(f.Name(), "C.") {
// This prefix cannot naturally be created, it must have
// been created as a result of CGo preprocessing.
info.variadic = true
}
}
}
// Set the importName for our exported function if we have one
if importName != "" {
if info.module == "" {
info.linkName = importName
} else {
// WebAssembly import
info.importName = importName
}
}
}
}
// 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).
@@ -82,11 +345,11 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
}
}
if c.Debug() && !info.extern {
if c.Debug && !info.extern {
// Add debug info.
// TODO: this should be done for every global in the program, not just
// the ones that are referenced from some code.
pos := c.ir.Program.Fset.Position(g.Pos())
pos := c.program.Fset.Position(g.Pos())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: g.RelString(nil),
LinkageName: info.linkName,
@@ -145,3 +408,23 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
}
}
}
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
methods := make([]*types.Selection, ms.Len())
for i := 0; i < ms.Len(); i++ {
methods[i] = ms.At(i)
}
return methods
}
// Return true if this package imports "unsafe", false otherwise.
func hasUnsafeImport(pkg *types.Package) bool {
for _, imp := range pkg.Imports() {
if imp == types.Unsafe {
return true
}
}
return false
}
+8 -8
View File
@@ -16,8 +16,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
num := b.getValue(call.Args[0])
var syscallResult llvm.Value
switch {
case b.GOARCH() == "amd64":
if b.GOOS() == "darwin" {
case b.GOARCH == "amd64":
if b.GOOS == "darwin" {
// Darwin adds this magic number to system call numbers:
//
// > Syscall classes for 64-bit system call entry.
@@ -58,7 +58,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "386" && b.GOOS() == "linux":
case b.GOARCH == "386" && b.GOOS == "linux":
// Sources:
// syscall(2) man page
// https://stackoverflow.com/a/2538212
@@ -84,7 +84,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "arm" && b.GOOS() == "linux":
case b.GOARCH == "arm" && b.GOOS == "linux":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
args := []llvm.Value{}
@@ -116,7 +116,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "arm64" && b.GOOS() == "linux":
case b.GOARCH == "arm64" && b.GOOS == "linux":
// Source: syscall(2) man page.
args := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -149,9 +149,9 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "")
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
}
switch b.GOOS() {
switch b.GOOS {
case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno
// Pseudocode:
@@ -187,6 +187,6 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
retval = b.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
}
}
+57
View File
@@ -0,0 +1,57 @@
package main
// Basic tests that don't need to be split into a separate file.
func addInt(x, y int) int {
return x + y
}
func equalInt(x, y int) bool {
return x == y
}
func floatEQ(x, y float32) bool {
return x == y
}
func floatNE(x, y float32) bool {
return x != y
}
func floatLower(x, y float32) bool {
return x < y
}
func floatLowerEqual(x, y float32) bool {
return x <= y
}
func floatGreater(x, y float32) bool {
return x > y
}
func floatGreaterEqual(x, y float32) bool {
return x >= y
}
func complexReal(x complex64) float32 {
return real(x)
}
func complexImag(x complex64) float32 {
return imag(x)
}
func complexAdd(x, y complex64) complex64 {
return x + y
}
func complexSub(x, y complex64) complex64 {
return x - y
}
func complexMul(x, y complex64) complex64 {
return x * y
}
// TODO: complexDiv (requires runtime call)
+98
View File
@@ -0,0 +1,98 @@
; ModuleID = 'basic.go'
source_filename = "basic.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define internal i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = add i32 %x, %y
ret i32 %0
}
define internal i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp eq i32 %x, %y
ret i1 %0
}
define internal i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp oeq float %x, %y
ret i1 %0
}
define internal i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp une float %x, %y
ret i1 %0
}
define internal i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp olt float %x, %y
ret i1 %0
}
define internal i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp ole float %x, %y
ret i1 %0
}
define internal i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp ogt float %x, %y
ret i1 %0
}
define internal i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp oge float %x, %y
ret i1 %0
}
define internal float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float %x.r
}
define internal float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float %x.i
}
define internal { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fadd float %x.r, %y.r
%1 = fadd float %x.i, %y.i
%2 = insertvalue { float, float } undef, float %0, 0
%3 = insertvalue { float, float } %2, float %1, 1
ret { float, float } %3
}
define internal { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fsub float %x.r, %y.r
%1 = fsub float %x.i, %y.i
%2 = insertvalue { float, float } undef, float %0, 0
%3 = insertvalue { float, float } %2, float %1, 1
ret { float, float } %3
}
define internal { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fmul float %x.r, %y.r
%1 = fmul float %x.i, %y.i
%2 = fsub float %0, %1
%3 = fmul float %x.r, %y.i
%4 = fmul float %x.i, %y.r
%5 = fadd float %3, %4
%6 = insertvalue { float, float } undef, float %2, 0
%7 = insertvalue { float, float } %6, float %5, 1
ret { float, float } %7
}
+39
View File
@@ -0,0 +1,39 @@
package main
// Test converting floats to ints.
func f32tou32(v float32) uint32 {
return uint32(v)
}
func maxu32f() float32 {
return float32(^uint32(0))
}
func maxu32tof32() uint32 {
f := float32(^uint32(0))
return uint32(f)
}
func inftoi32() (uint32, uint32, int32, int32) {
inf := 1.0
inf /= 0.0
return uint32(inf), uint32(-inf), int32(inf), int32(-inf)
}
func u32tof32tou32(v uint32) uint32 {
return uint32(float32(v))
}
func f32tou32tof32(v float32) float32 {
return float32(uint32(v))
}
func f32tou8(v float32) uint8 {
return uint8(v)
}
func f32toi8(v float32) int8 {
return int8(v)
}
+80
View File
@@ -0,0 +1,80 @@
; ModuleID = 'float.go'
source_filename = "float.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define internal i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
%inbounds = and i1 %positive, %withinmax
%saturated = sext i1 %positive to i32
%normal = fptoui float %v to i32
%0 = select i1 %inbounds, i32 %normal, i32 %saturated
ret i32 %0
}
define internal float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float 0x41F0000000000000
}
define internal i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 -1
}
define internal { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 }
}
define internal i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = uitofp i32 %v to float
%withinmax = fcmp ole float %0, 0x41EFFFFFC0000000
%normal = fptoui float %0 to i32
%1 = select i1 %withinmax, i32 %normal, i32 -1
ret i32 %1
}
define internal float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
%inbounds = and i1 %positive, %withinmax
%saturated = sext i1 %positive to i32
%normal = fptoui float %v to i32
%0 = select i1 %inbounds, i32 %normal, i32 %saturated
%1 = uitofp i32 %0 to float
ret float %1
}
define internal i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 2.550000e+02
%inbounds = and i1 %positive, %withinmax
%saturated = sext i1 %positive to i8
%normal = fptoui float %v to i8
%0 = select i1 %inbounds, i8 %normal, i8 %saturated
ret i8 %0
}
define internal i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%abovemin = fcmp oge float %v, -1.280000e+02
%belowmax = fcmp ole float %v, 1.270000e+02
%inbounds = and i1 %abovemin, %belowmax
%saturated = select i1 %abovemin, i8 127, i8 -128
%isnan = fcmp uno float %v, 0.000000e+00
%remapped = select i1 %isnan, i8 0, i8 %saturated
%normal = fptosi float %v to i8
%0 = select i1 %inbounds, i8 %normal, i8 %remapped
ret i8 %0
}
+41
View File
@@ -0,0 +1,41 @@
package main
// This file tests various operations on pointers, such as pointer arithmetic
// and dereferencing pointers.
import "unsafe"
// Dereference pointers.
func pointerDerefZero(x *[0]int) [0]int {
return *x // This is a no-op, there is nothing to load.
}
// Unsafe pointer casts, they are sometimes a no-op.
func pointerCastFromUnsafe(x unsafe.Pointer) *int {
return (*int)(x)
}
func pointerCastToUnsafe(x *int) unsafe.Pointer {
return unsafe.Pointer(x)
}
func pointerCastToUnsafeNoop(x *byte) unsafe.Pointer {
return unsafe.Pointer(x)
}
// The compiler has support for a few special cast+add patterns that are
// transformed into a single GEP.
func pointerUnsafeGEPFixedOffset(ptr *byte) *byte {
return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + 10))
}
func pointerUnsafeGEPByteOffset(ptr *byte, offset uintptr) *byte {
return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + offset))
}
func pointerUnsafeGEPIntOffset(ptr *int32, offset uintptr) *int32 {
return (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + offset*4))
}
+49
View File
@@ -0,0 +1,49 @@
; ModuleID = 'pointer.go'
source_filename = "pointer.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define internal [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret [0 x i32] zeroinitializer
}
define internal i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = bitcast i8* %x to i32*
ret i32* %0
}
define internal i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = bitcast i32* %x to i8*
ret i8* %0
}
define internal i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i8* %x
}
define internal i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr inbounds i8, i8* %ptr, i32 10
ret i8* %0
}
define internal i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr inbounds i8, i8* %ptr, i32 %offset
ret i8* %0
}
define internal i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr i32, i32* %ptr, i32 %offset
ret i32* %0
}
+9
View File
@@ -0,0 +1,9 @@
package main
func sliceLen(ints []int) int {
return len(ints)
}
func sliceCap(ints []int) int {
return cap(ints)
}
+19
View File
@@ -0,0 +1,19 @@
; ModuleID = 'slice.go'
source_filename = "slice.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define internal i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %ints.len
}
define internal i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %ints.cap
}
+7 -5
View File
@@ -1,14 +1,16 @@
module github.com/tinygo-org/tinygo
go 1.11
go 1.13
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/chromedp/cdproto v0.0.0-20200709115526-d1f6fc58448b
github.com/chromedp/chromedp v0.5.4-0.20200303084119-2bb39134ab9e
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693
github.com/chromedp/chromedp v0.6.4
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
go.bug.st/serial v1.0.0
github.com/mattn/go-colorable v0.1.8
go.bug.st/serial v1.1.2
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
tinygo.org/x/go-llvm v0.0.0-20201104183921-570e7a6841d9
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f
)
+28 -19
View File
@@ -1,33 +1,39 @@
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/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/chromedp/cdproto v0.0.0-20200709115526-d1f6fc58448b h1:LF+GRwyzxrO3MUzPvejv+yBup0lNG+/QdIRrkxOPseA=
github.com/chromedp/cdproto v0.0.0-20200709115526-d1f6fc58448b/go.mod h1:E6LPWRdIJc11h/di5p0rwvRmUYbhGpBEH7ZbPfzDIOE=
github.com/chromedp/chromedp v0.5.4-0.20200303084119-2bb39134ab9e h1:Hv0JVyHhbIXb9NiYQe4NsrfgrSofAp0q2FnhhJOXgi8=
github.com/chromedp/chromedp v0.5.4-0.20200303084119-2bb39134ab9e/go.mod h1:vmQMRHFZrY3T+Jv51T0n87OK/i6bK+5P9a+Fg5jPwgQ=
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693 h1:11eq/RkpaotwdF6b1TRMcdgQUPNmyFEJOB7zLvh0O/Y=
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693/go.mod h1:55pim6Ht4LJKdVLlyFJV/g++HsEA1hQxPbB5JyNdZC0=
github.com/chromedp/chromedp v0.6.4 h1:Gx7ZkRyrSVmbbDDja/ieNgNGJIvElroPOyeqYQGVDSY=
github.com/chromedp/chromedp v0.6.4/go.mod h1:vodUdJf5dF/b8n0UBJv6NeM/QK28RjP3j+eM7fq4+84=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.3 h1:ZOigqf7iBxkA4jdQ3am7ATzdlOFp9YzA6NmuvEEZc9g=
github.com/gobwas/ws v1.0.3/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8=
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw=
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
go.bug.st/serial v1.1.2 h1:6xDpbta8KJ+VLRTeM8ghhxXRMLE/Lr8h9iDKwydarAY=
go.bug.st/serial v1.1.2/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
@@ -39,8 +45,11 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -48,5 +57,5 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbO
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
tinygo.org/x/go-llvm v0.0.0-20201104183921-570e7a6841d9 h1:l2kTQOhqEoeDTK3ckUnwReOQwMPUmURMIdjJbeAuDT4=
tinygo.org/x/go-llvm v0.0.0-20201104183921-570e7a6841d9/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f h1:FP5Do5omlQ/dLQ3Hfy7oyJo69VS5Hn46rZw004r0lGU=
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
+21 -2
View File
@@ -142,10 +142,11 @@ func getHomeDir() string {
// getGoroot returns an appropriate GOROOT from various sources. If it can't be
// found, it returns an empty string.
func getGoroot() string {
// An explicitly set GOROOT always has preference.
goroot := os.Getenv("GOROOT")
if goroot != "" {
// An explicitly set GOROOT always has preference.
return goroot
// Convert to the standard GOROOT being referenced, if it's a TinyGo cache.
return getStandardGoroot(goroot)
}
// Check for the location of the 'go' binary and base GOROOT on that.
@@ -195,3 +196,21 @@ func isGoroot(goroot string) bool {
_, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go"))
return err == nil
}
// getStandardGoroot returns the physical path to a real, standard Go GOROOT
// implied by the given path.
// If the given path appears to be a TinyGo cached GOROOT, it returns the path
// referenced by symlinks contained in the cache. Otherwise, it returns the
// given path as-is.
func getStandardGoroot(path string) string {
// Check if the "bin" subdirectory of our given GOROOT is a symlink, and then
// return the _parent_ directory of its destination.
if dest, err := os.Readlink(filepath.Join(path, "bin")); nil == err {
// Clean the destination to remove any trailing slashes, so that
// filepath.Dir will always return the parent.
// (because both "/foo" and "/foo/" are valid symlink destinations,
// but filepath.Dir would return "/" and "/foo", respectively)
return filepath.Dir(filepath.Clean(dest))
}
return path
}
+1 -1
View File
@@ -12,7 +12,7 @@ import (
// Version of TinyGo.
// Update this value before release of new version of software.
const Version = "0.16.0"
const Version = "0.18.0-dev"
// GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
+92 -37
View File
@@ -6,50 +6,81 @@ possible and only run unknown expressions (e.g. external calls) at runtime. This
is in practice a partial evaluator of the `runtime.initAll` function, which
calls each package initializer.
It works by directly interpreting LLVM IR:
This package is a rewrite of a previous partial evaluator that worked
directly on LLVM IR and used the module and LLVM constants as intermediate
values. This newer version instead uses a mostly Go intermediate form. It
compiles functions and extracts relevant data first (compiler.go), then
executes those functions (interpreter.go) in a memory space that can be
rolled back per function (memory.go). This means that it is not necessary to
scan functions to see whether they can be run at compile time, which was very
error prone. Instead it just tries to execute everything and if it hits
something it cannot interpret (such as a store to memory-mapped I/O) it rolls
back the execution of that function and runs the function at runtime instead.
All in all, this design provides several benefits:
* Almost all operations work directly on constants, and are implemented using
the llvm.Const* set of functions that are evaluated directly.
* External function calls and some other operations (inline assembly, volatile
load, volatile store) are seen as having limited side effects. Limited in
the sense that it is known at compile time which globals it affects, which
then are marked 'dirty' (meaning, further operations on it must be done at
runtime). These operations are emitted directly in the `runtime.initAll`
function. Return values are also considered 'dirty'.
* Such 'dirty' objects and local values must be executed at runtime instead of
at compile time. This dirtyness propagates further through the IR, for
example storing a dirty local value to a global also makes the global dirty,
meaning that the global may not be read or written at compile time as it's
contents at that point during interpretation is unknown.
* There are some heuristics in place to avoid doing too much with dirty
values. For example, a branch based on a dirty local marks the whole
function itself as having side effect (as if it is an external function).
However, all globals it touches are still taken into account and when a call
is inserted in `runtime.initAll`, all globals it references are also marked
dirty.
* Heap allocation (`runtime.alloc`) is emulated by creating new objects. The
value in the allocation is the initializer of the global, the zero value is
the zero initializer.
* Stack allocation (`alloca`) is often emulated using a fake alloca object,
until the address of the alloca is taken in which case it is also created as
a real `alloca` in `runtime.initAll` and marked dirty. This may be necessary
when calling an external function with the given alloca as paramter.
* Much better error handling. By being able to revert to runtime execution
without the need for scanning functions, this version is able to
automatically work around many bugs in the previous implementation.
* More correct memory model. This is not inherent to the new design, but the
new design also made the memory model easier to reason about.
* Faster execution of initialization code. While it is not much faster for
normal interpretation (maybe 25% or so) due to the compilation overhead,
it should be a whole lot faster for loops as it doesn't have to call into
LLVM (via CGo) for every operation.
As mentioned, this partial evaulator comes in three parts: a compiler, an
interpreter, and a memory manager.
## Compiler
The main task of the compiler is that it extracts all necessary data from
every instruction in a function so that when this instruction is interpreted,
no additional CGo calls are necessary. This is not currently done for all
instructions (`runtime.alloc` is a notable exception), but at least it does
so for the vast majority of instructions.
## Interpreter
The interpreter runs an instruction just like it would if it were executed
'for real'. The vast majority of instructions can be executed at compile
time. As indicated above, some instructions need to be executed at runtime
instead.
## Memory
Memory is represented as objects (the `object` type) that contains data that
will eventually be stored in a global and values (the `value` interface) that
can be worked with while running the interpreter. Values therefore are only
used locally and are always passed by value (just like most LLVM constants)
while objects represent the backing storage (like LLVM globals). Some values
are pointer values, and point to an object.
Importantly, this partial evaluator can roll back the execution of a
function. This is implemented by creating a new memory view per function
activation, which makes sure that any change to a global (such as a store
instruction) is stored in the memory view. It creates a copy of the object
and stores that in the memory view to be modified. Once the function has
executed successfully, all these modified objects are then copied into the
parent function, up to the root function invocation which (on successful
execution) writes the values back into the LLVM module. This way, function
invocations can be rolled back without leaving a trace.
Pointer values point to memory objects, but not to a particular memory
object. Every memory object is given an index, and pointers use that index to
look up the current active object for the pointer to load from or to copy
when storing to it.
Rolling back a function should roll back everyting, including the few
instructions emitted at runtime. This is done by treating instructions much
like memory objects and removing the created instructions when necessary.
## Why is this necessary?
A partial evaluator is hard to get right, so why go through all the trouble of
writing one?
The main reason is that the previous attempt wasn't complete and wasn't sound.
It simply tried to evaluate Go SSA directly, which was good but more difficult
than necessary. An IR based interpreter needs to understand fewer instructions
as the LLVM IR simply has less (complex) instructions than Go SSA. Also, LLVM
provides some useful tools like easily getting all uses of a function or global,
which Go SSA does not provide.
But why is it necessary at all? The answer is that globals with initializers are
much easier to optimize by LLVM than initialization code. Also, there are a few
other benefits:
The answer is that globals with initializers are much easier to optimize by
LLVM than initialization code. Also, there are a few other benefits:
* Dead globals are trivial to optimize away.
* Constant globals are easier to detect. Remember that Go does not have global
@@ -60,5 +91,29 @@ other benefits:
* Constants are much more efficent on microcontrollers, as they can be
allocated in flash instead of RAM.
The Go SSA package does not create constant initializers for globals.
Instead, it emits initialization functions, so if you write the following:
```go
var foo = []byte{1, 2, 3, 4}
```
It would generate something like this:
```go
var foo []byte
func init() {
foo = make([]byte, 4)
foo[0] = 1
foo[1] = 2
foo[2] = 3
foo[3] = 4
}
```
This is of course hugely wasteful, it's much better to create `foo` as a
global array instead of initializing it at runtime.
For more details, see [this section of the
documentation](https://tinygo.org/compiler-internals/differences-from-go/).
+410
View File
@@ -0,0 +1,410 @@
package interp
// This file compiles the LLVM IR to a form that's easy to efficiently
// interpret.
import (
"strings"
"tinygo.org/x/go-llvm"
)
// A function is a compiled LLVM function, which means that interpreting it
// avoids most CGo calls necessary. This is done in a separate step so the
// result can be cached.
// Functions are in SSA form, just like the LLVM version if it. The first block
// (blocks[0]) is the entry block.
type function struct {
llvmFn llvm.Value
name string // precalculated llvmFn.Name()
params []llvm.Value // precalculated llvmFn.Params()
blocks []*basicBlock
locals map[llvm.Value]int
}
// basicBlock represents a LLVM basic block and contains a slice of
// instructions. The last instruction must be a terminator instruction.
type basicBlock struct {
instructions []instruction
}
// instruction is a precompiled LLVM IR instruction. The operands can be either
// an already known value (such as literalValue or pointerValue) but can also be
// the special localValue, which means that the value is a function parameter or
// is produced by another instruction in the function. In that case, the
// interpreter will replace the operand with that local value.
type instruction struct {
opcode llvm.Opcode
localIndex int
operands []value
llvmInst llvm.Value
name string
}
// String returns a nice human-readable version of this instruction.
func (inst *instruction) String() string {
operands := make([]string, len(inst.operands))
for i, op := range inst.operands {
operands[i] = op.String()
}
name := instructionNameMap[inst.opcode]
if name == "" {
name = "<unknown op>"
}
return name + " " + strings.Join(operands, " ")
}
// compileFunction compiles a given LLVM function to an easier to interpret
// version of the function. As far as possible, all operands are preprocessed so
// that the interpreter doesn't have to call into LLVM.
func (r *runner) compileFunction(llvmFn llvm.Value) *function {
fn := &function{
llvmFn: llvmFn,
name: llvmFn.Name(),
params: llvmFn.Params(),
locals: make(map[llvm.Value]int),
}
if llvmFn.IsDeclaration() {
// Nothing to do.
return fn
}
for i, param := range fn.params {
fn.locals[param] = i
}
// Make a map of all the blocks, to quickly find the block number for a
// given branch instruction.
blockIndices := make(map[llvm.Value]int)
for llvmBB := llvmFn.FirstBasicBlock(); !llvmBB.IsNil(); llvmBB = llvm.NextBasicBlock(llvmBB) {
index := len(blockIndices)
blockIndices[llvmBB.AsValue()] = index
}
// Compile every block.
for llvmBB := llvmFn.FirstBasicBlock(); !llvmBB.IsNil(); llvmBB = llvm.NextBasicBlock(llvmBB) {
bb := &basicBlock{}
fn.blocks = append(fn.blocks, bb)
// Compile every instruction in the block.
for llvmInst := llvmBB.FirstInstruction(); !llvmInst.IsNil(); llvmInst = llvm.NextInstruction(llvmInst) {
// Create instruction skeleton.
opcode := llvmInst.InstructionOpcode()
inst := instruction{
opcode: opcode,
localIndex: len(fn.locals),
llvmInst: llvmInst,
}
fn.locals[llvmInst] = len(fn.locals)
// Add operands specific for this instruction.
switch opcode {
case llvm.Ret:
// Return instruction, which can either be a `ret void` (no
// return value) or return a value.
numOperands := llvmInst.OperandsCount()
if numOperands != 0 {
inst.operands = []value{
r.getValue(llvmInst.Operand(0)),
}
}
case llvm.Br:
// Branch instruction. Can be either a conditional branch (with
// 3 operands) or unconditional branch (with just one basic
// block operand).
numOperands := llvmInst.OperandsCount()
switch numOperands {
case 3:
// Conditional jump to one of two blocks. Comparable to an
// if/else in procedural languages.
thenBB := llvmInst.Operand(2)
elseBB := llvmInst.Operand(1)
inst.operands = []value{
r.getValue(llvmInst.Operand(0)),
literalValue{uint32(blockIndices[thenBB])},
literalValue{uint32(blockIndices[elseBB])},
}
case 1:
// Unconditional jump to a target basic block. Comparable to
// a jump in C and Go.
jumpBB := llvmInst.Operand(0)
inst.operands = []value{
literalValue{uint32(blockIndices[jumpBB])},
}
default:
panic("unknown number of operands")
}
case llvm.PHI:
inst.name = llvmInst.Name()
incomingCount := inst.llvmInst.IncomingCount()
for i := 0; i < incomingCount; i++ {
incomingBB := inst.llvmInst.IncomingBlock(i)
incomingValue := inst.llvmInst.IncomingValue(i)
inst.operands = append(inst.operands,
literalValue{uint32(blockIndices[incomingBB.AsValue()])},
r.getValue(incomingValue),
)
}
case llvm.Select:
// Select is a special instruction that is much like a ternary
// operator. It produces operand 1 or 2 based on the boolean
// that is operand 0.
inst.name = llvmInst.Name()
inst.operands = []value{
r.getValue(llvmInst.Operand(0)),
r.getValue(llvmInst.Operand(1)),
r.getValue(llvmInst.Operand(2)),
}
case llvm.Call:
// Call is a regular function call but could also be a runtime
// intrinsic. Some runtime intrinsics are treated specially by
// the interpreter, such as runtime.alloc. We don't
// differentiate between them here because these calls may also
// need to be run at runtime, in which case they should all be
// created in the same way.
llvmCalledValue := llvmInst.CalledValue()
if !llvmCalledValue.IsAFunction().IsNil() {
name := llvmCalledValue.Name()
if name == "llvm.dbg.value" || strings.HasPrefix(name, "llvm.lifetime.") {
// These intrinsics should not be interpreted, they are not
// relevant to the execution of this function.
continue
}
}
inst.name = llvmInst.Name()
numOperands := llvmInst.OperandsCount()
inst.operands = append(inst.operands, r.getValue(llvmCalledValue))
for i := 0; i < numOperands-1; i++ {
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i)))
}
case llvm.Load:
// Load instruction. The interpreter will load from the
// appropriate memory view.
// Also provide the memory size to be loaded, which is necessary
// with a lack of type information.
inst.name = llvmInst.Name()
inst.operands = []value{
r.getValue(llvmInst.Operand(0)),
literalValue{r.targetData.TypeAllocSize(llvmInst.Type())},
}
case llvm.Store:
// Store instruction. The interpreter will create a new object
// in the memory view of the function invocation and store to
// that, to make it possible to roll back this store.
inst.operands = []value{
r.getValue(llvmInst.Operand(0)),
r.getValue(llvmInst.Operand(1)),
}
case llvm.Alloca:
// Alloca allocates stack space for local variables.
numElements := r.getValue(inst.llvmInst.Operand(0)).(literalValue).value.(uint32)
elementSize := r.targetData.TypeAllocSize(inst.llvmInst.Type().ElementType())
inst.operands = []value{
literalValue{elementSize * uint64(numElements)},
}
case llvm.GetElementPtr:
// GetElementPtr does pointer arithmetic.
inst.name = llvmInst.Name()
ptr := llvmInst.Operand(0)
n := llvmInst.OperandsCount()
elementType := ptr.Type().ElementType()
// gep: [source ptr, dest value size, pairs of indices...]
inst.operands = []value{
r.getValue(ptr),
literalValue{r.targetData.TypeAllocSize(llvmInst.Type().ElementType())},
r.getValue(llvmInst.Operand(1)),
literalValue{r.targetData.TypeAllocSize(elementType)},
}
for i := 2; i < n; i++ {
operand := r.getValue(llvmInst.Operand(i))
if elementType.TypeKind() == llvm.StructTypeKind {
index := operand.(literalValue).value.(uint32)
elementOffset := r.targetData.ElementOffset(elementType, int(index))
// Encode operands in a special way. The elementOffset
// is just the offset in bytes. The elementSize is a
// negative number (when cast to a int64) by flipping
// all the bits. This allows the interpreter to detect
// this is a struct field and that it should not
// multiply it with the elementOffset to get the offset.
// It is important for the interpreter to know the
// struct field index for when the GEP must be done at
// runtime.
inst.operands = append(inst.operands, literalValue{elementOffset}, literalValue{^uint64(index)})
elementType = elementType.StructElementTypes()[index]
} else {
elementType = elementType.ElementType()
elementSize := r.targetData.TypeAllocSize(elementType)
elementSizeOperand := literalValue{elementSize}
// Add operand * elementSizeOperand bytes to the pointer.
inst.operands = append(inst.operands, operand, elementSizeOperand)
}
}
case llvm.BitCast, llvm.IntToPtr, llvm.PtrToInt:
// Bitcasts are ususally used to cast a pointer from one type to
// another leaving the pointer itself intact.
inst.name = llvmInst.Name()
inst.operands = []value{
r.getValue(llvmInst.Operand(0)),
}
case llvm.ExtractValue:
inst.name = llvmInst.Name()
agg := llvmInst.Operand(0)
var offset uint64
indexingType := agg.Type()
for _, index := range inst.llvmInst.Indices() {
switch indexingType.TypeKind() {
case llvm.StructTypeKind:
offset += r.targetData.ElementOffset(indexingType, int(index))
indexingType = indexingType.StructElementTypes()[index]
default: // ArrayTypeKind
indexingType = indexingType.ElementType()
elementSize := r.targetData.TypeAllocSize(indexingType)
offset += elementSize * uint64(index)
}
}
size := r.targetData.TypeAllocSize(inst.llvmInst.Type())
// extractvalue [agg, byteOffset, byteSize]
inst.operands = []value{
r.getValue(agg),
literalValue{offset},
literalValue{size},
}
case llvm.InsertValue:
inst.name = llvmInst.Name()
agg := llvmInst.Operand(0)
var offset uint64
indexingType := agg.Type()
for _, index := range inst.llvmInst.Indices() {
switch indexingType.TypeKind() {
case llvm.StructTypeKind:
offset += r.targetData.ElementOffset(indexingType, int(index))
indexingType = indexingType.StructElementTypes()[index]
default: // ArrayTypeKind
indexingType = indexingType.ElementType()
elementSize := r.targetData.TypeAllocSize(indexingType)
offset += elementSize * uint64(index)
}
}
// insertvalue [agg, elt, byteOffset]
inst.operands = []value{
r.getValue(agg),
r.getValue(llvmInst.Operand(1)),
literalValue{offset},
}
case llvm.ICmp:
inst.name = llvmInst.Name()
inst.operands = []value{
r.getValue(llvmInst.Operand(0)),
r.getValue(llvmInst.Operand(1)),
literalValue{uint8(llvmInst.IntPredicate())},
}
case llvm.FCmp:
inst.name = llvmInst.Name()
inst.operands = []value{
r.getValue(llvmInst.Operand(0)),
r.getValue(llvmInst.Operand(1)),
literalValue{uint8(llvmInst.FloatPredicate())},
}
case llvm.Add, llvm.Sub, llvm.Mul, llvm.UDiv, llvm.SDiv, llvm.URem, llvm.SRem, llvm.Shl, llvm.LShr, llvm.AShr, llvm.And, llvm.Or, llvm.Xor:
// Integer binary operations.
inst.name = llvmInst.Name()
inst.operands = []value{
r.getValue(llvmInst.Operand(0)),
r.getValue(llvmInst.Operand(1)),
}
case llvm.SExt, llvm.ZExt, llvm.Trunc:
// Extend or shrink an integer size.
// No sign extension going on so easy to do.
// zext: [value, bitwidth]
// trunc: [value, bitwidth]
inst.name = llvmInst.Name()
inst.operands = []value{
r.getValue(llvmInst.Operand(0)),
literalValue{uint64(llvmInst.Type().IntTypeWidth())},
}
case llvm.SIToFP, llvm.UIToFP:
// Convert an integer to a floating point instruction.
// opcode: [value, bitwidth]
inst.name = llvmInst.Name()
inst.operands = []value{
r.getValue(llvmInst.Operand(0)),
literalValue{uint64(r.targetData.TypeAllocSize(llvmInst.Type()) * 8)},
}
default:
// Unknown instruction, which is already set in inst.opcode so
// is detectable.
// This error is handled when actually trying to interpret this
// instruction (to not trigger on code that won't be executed).
}
bb.instructions = append(bb.instructions, inst)
}
}
return fn
}
// instructionNameMap maps from instruction opcodes to instruction names. This
// can be useful for debug logging.
var instructionNameMap = [...]string{
llvm.Ret: "ret",
llvm.Br: "br",
llvm.Switch: "switch",
llvm.IndirectBr: "indirectbr",
llvm.Invoke: "invoke",
llvm.Unreachable: "unreachable",
// Standard Binary Operators
llvm.Add: "add",
llvm.FAdd: "fadd",
llvm.Sub: "sub",
llvm.FSub: "fsub",
llvm.Mul: "mul",
llvm.FMul: "fmul",
llvm.UDiv: "udiv",
llvm.SDiv: "sdiv",
llvm.FDiv: "fdiv",
llvm.URem: "urem",
llvm.SRem: "srem",
llvm.FRem: "frem",
// Logical Operators
llvm.Shl: "shl",
llvm.LShr: "lshr",
llvm.AShr: "ashr",
llvm.And: "and",
llvm.Or: "or",
llvm.Xor: "xor",
// Memory Operators
llvm.Alloca: "alloca",
llvm.Load: "load",
llvm.Store: "store",
llvm.GetElementPtr: "getelementptr",
// Cast Operators
llvm.Trunc: "trunc",
llvm.ZExt: "zext",
llvm.SExt: "sext",
llvm.FPToUI: "fptoui",
llvm.FPToSI: "fptosi",
llvm.UIToFP: "uitofp",
llvm.SIToFP: "sitofp",
llvm.FPTrunc: "fptrunc",
llvm.FPExt: "fpext",
llvm.PtrToInt: "ptrtoint",
llvm.IntToPtr: "inttoptr",
llvm.BitCast: "bitcast",
// Other Operators
llvm.ICmp: "icmp",
llvm.FCmp: "fcmp",
llvm.PHI: "phi",
llvm.Call: "call",
llvm.Select: "select",
llvm.VAArg: "vaarg",
llvm.ExtractElement: "extractelement",
llvm.InsertElement: "insertelement",
llvm.ShuffleVector: "shufflevector",
llvm.ExtractValue: "extractvalue",
llvm.InsertValue: "insertvalue",
}
+14 -12
View File
@@ -11,15 +11,17 @@ import (
"tinygo.org/x/go-llvm"
)
// errUnreachable is returned when an unreachable instruction is executed. This
// error should not be visible outside of the interp package.
var errUnreachable = &Error{Err: errors.New("interp: unreachable executed")}
// These errors are expected during normal execution and can be recovered from
// by running the affected function at runtime instead of compile time.
var (
errIntegerAsPointer = errors.New("interp: trying to use an integer as a pointer (memory-mapped I/O?)")
errUnsupportedInst = errors.New("interp: unsupported instruction")
errUnsupportedRuntimeInst = errors.New("interp: unsupported instruction (to be emitted at runtime)")
errMapAlreadyCreated = errors.New("interp: map already created")
)
// unsupportedInstructionError returns a new "unsupported instruction" error for
// the given instruction. It includes source location information, when
// available.
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Error {
return e.errorAt(inst, errors.New("interp: unsupported instruction"))
func isRecoverableError(err error) bool {
return err == errIntegerAsPointer || err == errUnsupportedInst || err == errUnsupportedRuntimeInst || err == errMapAlreadyCreated
}
// ErrorLine is one line in a traceback. The position may be missing.
@@ -46,13 +48,13 @@ func (e *Error) Error() string {
// errorAt returns an error value for the currently interpreted package at the
// location of the instruction. The location information may not be complete as
// it depends on debug information in the IR.
func (e *evalPackage) errorAt(inst llvm.Value, err error) *Error {
pos := getPosition(inst)
func (r *runner) errorAt(inst instruction, err error) *Error {
pos := getPosition(inst.llvmInst)
return &Error{
ImportPath: e.packagePath,
ImportPath: r.pkgName,
Pos: pos,
Err: err,
Traceback: []ErrorLine{{pos, inst}},
Traceback: []ErrorLine{{pos, inst.llvmInst}},
}
}
-708
View File
@@ -1,708 +0,0 @@
package interp
// This file implements the core interpretation routines, interpreting single
// functions.
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
)
type frame struct {
*evalPackage
fn llvm.Value
locals map[llvm.Value]Value
}
// evalBasicBlock evaluates a single basic block, returning the return value (if
// ending with a ret instruction), a list of outgoing basic blocks (if not
// ending with a ret instruction), or an error on failure.
// Most of it works at compile time. Some calls get translated into calls to be
// executed at runtime: calls to functions with side effects, external calls,
// and operations on the result of such instructions.
func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (retval Value, outgoing []llvm.Value, err *Error) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if fr.Debug {
print(indent)
inst.Dump()
println()
}
switch {
case !inst.IsABinaryOperator().IsNil():
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
switch inst.InstructionOpcode() {
// Standard binary operators
case llvm.Add:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateAdd(lhs, rhs, "")}
case llvm.FAdd:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFAdd(lhs, rhs, "")}
case llvm.Sub:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSub(lhs, rhs, "")}
case llvm.FSub:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFSub(lhs, rhs, "")}
case llvm.Mul:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateMul(lhs, rhs, "")}
case llvm.FMul:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFMul(lhs, rhs, "")}
case llvm.UDiv:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateUDiv(lhs, rhs, "")}
case llvm.SDiv:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSDiv(lhs, rhs, "")}
case llvm.FDiv:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFDiv(lhs, rhs, "")}
case llvm.URem:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateURem(lhs, rhs, "")}
case llvm.SRem:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSRem(lhs, rhs, "")}
case llvm.FRem:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFRem(lhs, rhs, "")}
// Logical operators
case llvm.Shl:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateShl(lhs, rhs, "")}
case llvm.LShr:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateLShr(lhs, rhs, "")}
case llvm.AShr:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateAShr(lhs, rhs, "")}
case llvm.And:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateAnd(lhs, rhs, "")}
case llvm.Or:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateOr(lhs, rhs, "")}
case llvm.Xor:
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateXor(lhs, rhs, "")}
default:
return nil, nil, fr.unsupportedInstructionError(inst)
}
// Memory operators
case !inst.IsAAllocaInst().IsNil():
allocType := inst.Type().ElementType()
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloca")
alloca.SetInitializer(llvm.ConstNull(allocType))
alloca.SetLinkage(llvm.InternalLinkage)
fr.locals[inst] = &LocalValue{
Underlying: alloca,
Eval: fr.Eval,
}
case !inst.IsALoadInst().IsNil():
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
var value llvm.Value
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
} else {
var err error
value, err = operand.Load()
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
}
if value.Type() != inst.Type() {
return nil, nil, fr.errorAt(inst, errors.New("interp: load: type does not match"))
}
fr.locals[inst] = fr.getValue(value)
case !inst.IsAStoreInst().IsNil():
value := fr.getLocal(inst.Operand(0))
ptr := fr.getLocal(inst.Operand(1))
if inst.IsVolatile() {
fr.builder.CreateStore(value.Value(), ptr.Value())
} else {
err := ptr.Store(value.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
}
case !inst.IsAGetElementPtrInst().IsNil():
value := fr.getLocal(inst.Operand(0))
llvmIndices := make([]llvm.Value, inst.OperandsCount()-1)
for i := range llvmIndices {
llvmIndices[i] = inst.Operand(i + 1)
}
indices := make([]uint32, len(llvmIndices))
for i, llvmIndex := range llvmIndices {
operand := fr.getLocal(llvmIndex)
if !operand.IsConstant() {
// Not a constant operation.
// This should be detected by the scanner, but isn't at the
// moment.
return nil, nil, fr.errorAt(inst, errors.New("todo: non-const gep"))
}
indices[i] = uint32(operand.Value().ZExtValue())
}
result, err := value.GetElementPtr(indices)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
if result.Type() != inst.Type() {
return nil, nil, fr.errorAt(inst, errors.New("interp: gep: type does not match"))
}
fr.locals[inst] = result
// Cast operators
case !inst.IsATruncInst().IsNil():
value := fr.getLocal(inst.Operand(0))
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateTrunc(value.(*LocalValue).Value(), inst.Type(), "")}
case !inst.IsAZExtInst().IsNil():
value := fr.getLocal(inst.Operand(0))
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateZExt(value.(*LocalValue).Value(), inst.Type(), "")}
case !inst.IsASExtInst().IsNil():
value := fr.getLocal(inst.Operand(0))
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSExt(value.(*LocalValue).Value(), inst.Type(), "")}
case !inst.IsAFPToUIInst().IsNil():
value := fr.getLocal(inst.Operand(0))
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPToUI(value.(*LocalValue).Value(), inst.Type(), "")}
case !inst.IsAFPToSIInst().IsNil():
value := fr.getLocal(inst.Operand(0))
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPToSI(value.(*LocalValue).Value(), inst.Type(), "")}
case !inst.IsAUIToFPInst().IsNil():
value := fr.getLocal(inst.Operand(0))
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateUIToFP(value.(*LocalValue).Value(), inst.Type(), "")}
case !inst.IsASIToFPInst().IsNil():
value := fr.getLocal(inst.Operand(0))
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSIToFP(value.(*LocalValue).Value(), inst.Type(), "")}
case !inst.IsAFPTruncInst().IsNil():
value := fr.getLocal(inst.Operand(0))
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPTrunc(value.(*LocalValue).Value(), inst.Type(), "")}
case !inst.IsAFPExtInst().IsNil():
value := fr.getLocal(inst.Operand(0))
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPExt(value.(*LocalValue).Value(), inst.Type(), "")}
case !inst.IsAPtrToIntInst().IsNil():
value := fr.getLocal(inst.Operand(0))
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreatePtrToInt(value.Value(), inst.Type(), "")}
case !inst.IsABitCastInst().IsNil() && inst.Type().TypeKind() == llvm.PointerTypeKind:
operand := inst.Operand(0)
if !operand.IsACallInst().IsNil() {
fn := operand.CalledValue()
if !fn.IsAFunction().IsNil() && fn.Name() == "runtime.alloc" {
continue // special case: bitcast of alloc
}
}
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.
return nil, nil, fr.errorAt(inst, errors.New("unimplemented: bitcast of map"))
}
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 {
var lhsZero, rhsZero bool
var ok1, ok2 bool
if 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.
lhsZero, ok1 = isPointerNil(lhs)
rhsZero, ok2 = isPointerNil(rhs)
}
if lhs.Type().TypeKind() == llvm.IntegerTypeKind {
lhsZero, ok1 = isZero(lhs)
rhsZero, ok2 = isZero(rhs)
}
if ok1 && ok2 {
if lhsZero && rhsZero {
// Both are zero, so this icmp is always evaluated to true.
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
continue
}
if lhsZero != rhsZero {
// Only one of them is zero, 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
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
predicate := inst.FloatPredicate()
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFCmp(predicate, lhs, rhs, "")}
case !inst.IsAPHINode().IsNil():
for i := 0; i < inst.IncomingCount(); i++ {
if inst.IncomingBlock(i) == incoming {
fr.locals[inst] = fr.getLocal(inst.IncomingValue(i))
}
}
case !inst.IsACallInst().IsNil():
callee := inst.CalledValue()
switch {
case callee.Name() == "runtime.alloc":
// heap allocation
users := getUses(inst)
var resultInst = inst
if len(users) == 1 && !users[0].IsABitCastInst().IsNil() {
// happens when allocating something other than i8*
resultInst = users[0]
}
size := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying.ZExtValue()
allocType := resultInst.Type().ElementType()
typeSize := fr.TargetData.TypeAllocSize(allocType)
elementCount := 1
if size != typeSize {
// allocate an array
if size%typeSize != 0 {
return nil, nil, fr.unsupportedInstructionError(inst)
}
elementCount = int(size / typeSize)
allocType = llvm.ArrayType(allocType, elementCount)
}
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloc")
alloc.SetInitializer(llvm.ConstNull(allocType))
alloc.SetLinkage(llvm.InternalLinkage)
result := &LocalValue{
Underlying: alloc,
Eval: fr.Eval,
}
if elementCount == 1 {
fr.locals[resultInst] = result
} else {
result, err := result.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
fr.locals[resultInst] = result
}
case callee.Name() == "runtime.hashmapMake":
// create a map
keySize := inst.Operand(0).ZExtValue()
valueSize := inst.Operand(1).ZExtValue()
fr.locals[inst] = &MapValue{
Eval: fr.Eval,
PkgName: fr.packagePath,
KeySize: int(keySize),
ValueSize: int(valueSize),
}
case callee.Name() == "runtime.hashmapStringSet":
// set a string key in the map
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
keyLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
valPtr := fr.getLocal(inst.Operand(3)).(*LocalValue)
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
if !ok || !keyBuf.IsConstant() || !keyLen.IsConstant() || !valPtr.IsConstant() {
// The mapassign operation could not be done at compile
// time. Do it at runtime instead.
m := fr.getLocal(inst.Operand(0)).Value()
fr.markDirty(m)
llvmParams := []llvm.Value{
m, // *runtime.hashmap
fr.getLocal(inst.Operand(1)).Value(), // key.ptr
fr.getLocal(inst.Operand(2)).Value(), // key.len
fr.getLocal(inst.Operand(3)).Value(), // value (unsafe.Pointer)
fr.getLocal(inst.Operand(4)).Value(), // context
fr.getLocal(inst.Operand(5)).Value(), // parentHandle
}
fr.builder.CreateCall(callee, llvmParams, "")
continue
}
// "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters.
err := m.PutString(keyBuf, keyLen, valPtr)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
case callee.Name() == "runtime.hashmapBinarySet":
// set a binary (int etc.) key in the map
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
if !ok || !keyBuf.IsConstant() || !valPtr.IsConstant() {
// The mapassign operation could not be done at compile
// time. Do it at runtime instead.
m := fr.getLocal(inst.Operand(0)).Value()
fr.markDirty(m)
llvmParams := []llvm.Value{
m, // *runtime.hashmap
fr.getLocal(inst.Operand(1)).Value(), // key
fr.getLocal(inst.Operand(2)).Value(), // value
fr.getLocal(inst.Operand(3)).Value(), // context
fr.getLocal(inst.Operand(4)).Value(), // parentHandle
}
fr.builder.CreateCall(callee, llvmParams, "")
continue
}
err := m.PutBinary(keyBuf, valPtr)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
case callee.Name() == "runtime.stringConcat":
// adding two strings together
buf1Ptr := fr.getLocal(inst.Operand(0))
buf1Len := fr.getLocal(inst.Operand(1))
buf2Ptr := fr.getLocal(inst.Operand(2))
buf2Len := fr.getLocal(inst.Operand(3))
buf1, err := getStringBytes(buf1Ptr, buf1Len.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
buf2, err := getStringBytes(buf2Ptr, buf2Len.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
result := []byte(string(buf1) + string(buf2))
vals := make([]llvm.Value, len(result))
for i := range vals {
vals[i] = llvm.ConstInt(fr.Mod.Context().Int8Type(), uint64(result[i]), false)
}
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$stringconcat")
global.SetInitializer(globalValue)
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
stringType := fr.Mod.GetTypeByName("runtime._string")
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
ret := llvm.ConstNull(stringType)
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0})
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1})
fr.locals[inst] = &LocalValue{fr.Eval, ret}
case callee.Name() == "runtime.sliceCopy":
elementSize := fr.getLocal(inst.Operand(4)).(*LocalValue).Value().ZExtValue()
dstArray := fr.getLocal(inst.Operand(0)).(*LocalValue).stripPointerCasts()
srcArray := fr.getLocal(inst.Operand(1)).(*LocalValue).stripPointerCasts()
dstLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
srcLen := fr.getLocal(inst.Operand(3)).(*LocalValue)
if elementSize != 1 && dstArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind && srcArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind {
// Slice data pointers are created by adding a global array
// and getting the address of the first element using a GEP.
// However, before the compiler can pass it to
// runtime.sliceCopy, it has to perform a bitcast to a *i8,
// to make it a unsafe.Pointer. Now, when the IR builder
// sees a bitcast of a GEP with zero indices, it will make
// a bitcast of the original array instead of the GEP,
// which breaks our assumptions.
// Re-add this GEP, in the hope that it it is then of the correct type...
dstArrayValue, err := dstArray.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
dstArray = dstArrayValue.(*LocalValue)
srcArrayValue, err := srcArray.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
srcArray = srcArrayValue.(*LocalValue)
}
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
return nil, nil, fr.errorAt(inst, errors.New("interp: slice dst element size does not match pointer type"))
}
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
return nil, nil, fr.errorAt(inst, errors.New("interp: slice src element size does not match pointer type"))
}
if dstArray.Type() != srcArray.Type() {
return nil, nil, fr.errorAt(inst, errors.New("interp: slice element types don't match"))
}
length := dstLen.Value().SExtValue()
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
length = srcLength
}
if length < 0 {
return nil, nil, fr.errorAt(inst, errors.New("interp: trying to copy a slice with negative length?"))
}
for i := int64(0); i < length; i++ {
// *dst = *src
val, err := srcArray.Load()
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
err = dstArray.Store(val)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
// dst++
dstArrayValue, err := dstArray.GetElementPtr([]uint32{1})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
dstArray = dstArrayValue.(*LocalValue)
// src++
srcArrayValue, err := srcArray.GetElementPtr([]uint32{1})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
srcArray = srcArrayValue.(*LocalValue)
}
case callee.Name() == "runtime.stringToBytes":
// convert a string to a []byte
bufPtr := fr.getLocal(inst.Operand(0))
bufLen := fr.getLocal(inst.Operand(1))
result, err := getStringBytes(bufPtr, bufLen.Value())
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
vals := make([]llvm.Value, len(result))
for i := range vals {
vals[i] = llvm.ConstInt(fr.Mod.Context().Int8Type(), uint64(result[i]), false)
}
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$bytes")
global.SetInitializer(globalValue)
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
sliceType := inst.Type()
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
ret := llvm.ConstNull(sliceType)
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr
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.typeAssert":
actualTypeInt := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
assertedType := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
if actualTypeInt.IsAConstantExpr().IsNil() || actualTypeInt.Opcode() != llvm.PtrToInt {
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode in runtime.typeAssert to be a ptrtoint"))
}
actualType := actualTypeInt.Operand(0)
if actualType.IsAConstant().IsNil() || assertedType.IsAConstant().IsNil() {
return nil, nil, fr.errorAt(inst, errors.New("interp: unimplemented: type assert with non-constant interface value"))
}
assertOk := uint64(0)
if llvm.ConstExtractValue(actualType.Initializer(), []uint32{0}) == assertedType {
assertOk = 1
}
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), assertOk, false)}
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 {
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode to be a ptrtoint"))
}
typecode = typecode.Operand(0)
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
return nil, nil, fr.errorAt(inst, errors.New("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 {
return nil, nil, fr.errorAt(inst, errors.New("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 strings.HasPrefix(callee.Name(), "llvm.lifetime."):
// 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.
// TODO: print an error when executing runtime._panic (with the
// exact error message it would print at runtime).
var params []llvm.Value
for i := 0; i < inst.OperandsCount()-1; i++ {
operand := fr.getLocal(inst.Operand(i)).Value()
fr.markDirty(operand)
params = append(params, operand)
}
// TODO: accurate debug info, including call chain
fr.builder.CreateCall(callee, params, inst.Name())
case !callee.IsAFunction().IsNil() && callee.IsDeclaration():
// external functions
var params []llvm.Value
for i := 0; i < inst.OperandsCount()-1; i++ {
operand := fr.getLocal(inst.Operand(i)).Value()
fr.markDirty(operand)
params = append(params, operand)
}
// TODO: accurate debug info, including call chain
result := fr.builder.CreateCall(callee, params, inst.Name())
if inst.Type().TypeKind() != llvm.VoidTypeKind {
fr.markDirty(result)
fr.locals[inst] = &LocalValue{fr.Eval, result}
}
case !callee.IsAFunction().IsNil():
// regular function
var params []Value
dirtyParams := false
for i := 0; i < inst.OperandsCount()-1; i++ {
local := fr.getLocal(inst.Operand(i))
if !local.IsConstant() {
dirtyParams = true
}
params = append(params, local)
}
var ret Value
scanResult, err := fr.hasSideEffects(callee)
if err != nil {
return nil, nil, err
}
if scanResult.severity == sideEffectLimited || dirtyParams && scanResult.severity != sideEffectAll {
// Side effect is bounded. This means the operation invokes
// side effects (like calling an external function) but it
// is known at compile time which side effects it invokes.
// This means the function can be called at runtime and the
// affected globals can be marked dirty at compile time.
llvmParams := make([]llvm.Value, len(params))
for i, param := range params {
llvmParams[i] = param.Value()
}
result := fr.builder.CreateCall(callee, llvmParams, inst.Name())
ret = &LocalValue{fr.Eval, result}
// mark all mentioned globals as dirty
for global := range scanResult.mentionsGlobals {
fr.markDirty(global)
}
} else {
// Side effect is one of:
// * None: no side effects, can be fully interpreted at
// compile time.
// * Unbounded: cannot call at runtime so we'll try to
// interpret anyway and hope for the best.
ret, err = fr.function(callee, params, indent+" ")
if err != nil {
// Record this function call in the backtrace.
err.Traceback = append(err.Traceback, ErrorLine{
Pos: getPosition(inst),
Inst: inst,
})
return nil, nil, err
}
}
if inst.Type().TypeKind() != llvm.VoidTypeKind {
fr.locals[inst] = ret
}
default:
// function pointers, etc.
return nil, nil, fr.unsupportedInstructionError(inst)
}
case !inst.IsAExtractValueInst().IsNil():
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
indices := inst.Indices()
if agg.Underlying.IsConstant() {
newValue := llvm.ConstExtractValue(agg.Underlying, indices)
fr.locals[inst] = fr.getValue(newValue)
} else {
if len(indices) != 1 {
return nil, nil, fr.errorAt(inst, errors.New("interp: cannot handle extractvalue with not exactly 1 index"))
}
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateExtractValue(agg.Underlying, int(indices[0]), inst.Name())}
}
case !inst.IsAInsertValueInst().IsNil():
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
val := fr.getLocal(inst.Operand(1))
indices := inst.Indices()
if agg.IsConstant() && val.IsConstant() {
newValue := llvm.ConstInsertValue(agg.Underlying, val.Value(), indices)
fr.locals[inst] = &LocalValue{fr.Eval, newValue}
} else {
if len(indices) != 1 {
return nil, nil, fr.errorAt(inst, errors.New("interp: cannot handle insertvalue with not exactly 1 index"))
}
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateInsertValue(agg.Underlying, val.Value(), int(indices[0]), inst.Name())}
}
case !inst.IsASelectInst().IsNil():
// var result T
// if cond {
// result = x
// } else {
// result = y
// }
// return result
cond := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
x := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
y := fr.getLocal(inst.Operand(2)).(*LocalValue).Underlying
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSelect(cond, x, y, "")}
case !inst.IsAReturnInst().IsNil() && inst.OperandsCount() == 0:
return nil, nil, nil // ret void
case !inst.IsAReturnInst().IsNil() && inst.OperandsCount() == 1:
return fr.getLocal(inst.Operand(0)), nil, nil
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 3:
// conditional branch (if/then/else)
cond := fr.getLocal(inst.Operand(0)).Value()
if cond.Type() != fr.Mod.Context().Int1Type() {
return nil, nil, fr.errorAt(inst, errors.New("expected an i1 in a branch instruction"))
}
thenBB := inst.Operand(1)
elseBB := inst.Operand(2)
if !cond.IsAInstruction().IsNil() {
return nil, nil, fr.errorAt(inst, errors.New("interp: branch on a non-constant"))
}
if !cond.IsAConstantExpr().IsNil() {
// This may happen when the instruction builder could not
// const-fold some instructions.
return nil, nil, fr.errorAt(inst, 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:
return nil, nil, fr.errorAt(inst, errors.New("branch was not true or false"))
}
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
// unconditional branch (goto)
return nil, []llvm.Value{inst.Operand(0)}, nil
case !inst.IsAUnreachableInst().IsNil():
// Unreachable was reached (e.g. after a call to panic()).
// Report this as an error, as it is not supposed to happen.
// This is a sentinel error value.
return nil, nil, errUnreachable
default:
return nil, nil, fr.unsupportedInstructionError(inst)
}
}
panic("interp: reached end of basic block without terminator")
}
// Get the Value for an operand, which is a constant value of some sort.
func (fr *frame) getLocal(v llvm.Value) Value {
if ret, ok := fr.locals[v]; ok {
return ret
} else if value := fr.getValue(v); value != nil {
return value
} else {
// This should not happen under normal circumstances.
panic("cannot find value")
}
}
+100 -114
View File
@@ -1,59 +1,77 @@
// Package interp interprets Go package initializers as much as possible. This
// avoid running them at runtime, improving code size and making other
// optimizations possible.
// Package interp is a partial evaluator of code run at package init time. See
// the README in this package for details.
package interp
// This file provides the overarching Eval object with associated (utility)
// methods.
import (
"fmt"
"os"
"strings"
"time"
"tinygo.org/x/go-llvm"
)
type Eval struct {
Mod llvm.Module
TargetData llvm.TargetData
Debug bool
builder llvm.Builder
dirtyGlobals map[llvm.Value]struct{}
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
// Enable extra checks, which should be disabled by default.
// This may help track down bugs by adding a few more sanity checks.
const checks = true
// runner contains all state related to one interp run.
type runner struct {
mod llvm.Module
targetData llvm.TargetData
builder llvm.Builder
pointerSize uint32 // cached pointer size from the TargetData
i8ptrType llvm.Type // often used type so created in advance
maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result
debug bool // log debug messages
pkgName string // package name of the currently executing package
functionCache map[llvm.Value]*function // cache of compiled functions
objects []object // slice of objects in memory
globals map[llvm.Value]int // map from global to index in objects slice
start time.Time
callsExecuted uint64
}
// evalPackage encapsulates the Eval type for just a single package. The Eval
// type keeps state across the whole program, the evalPackage type keeps extra
// state for the currently interpreted package.
type evalPackage struct {
*Eval
packagePath string
}
// Run evaluates the function with the given name and then eliminates all
// callers.
// Run evaluates runtime.initAll function as much as possible at compile time.
// Set debug to true if it should print output while running.
func Run(mod llvm.Module, debug bool) error {
if debug {
println("\ncompile-time evaluation:")
r := runner{
mod: mod,
targetData: llvm.NewTargetData(mod.DataLayout()),
debug: debug,
functionCache: make(map[llvm.Value]*function),
objects: []object{{}},
globals: make(map[llvm.Value]int),
start: time.Now(),
}
r.pointerSize = uint32(r.targetData.PointerSize())
r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0)
r.maxAlign = r.targetData.PrefTypeAlignment(r.i8ptrType) // assume pointers are maximally aligned (this is not always the case)
name := "runtime.initAll"
e := &Eval{
Mod: mod,
TargetData: llvm.NewTargetData(mod.DataLayout()),
Debug: debug,
dirtyGlobals: map[llvm.Value]struct{}{},
}
e.builder = mod.Context().NewBuilder()
initAll := mod.NamedFunction(name)
initAll := mod.NamedFunction("runtime.initAll")
bb := initAll.EntryBasicBlock()
// Create a builder, to insert instructions that could not be evaluated at
// compile time.
r.builder = mod.Context().NewBuilder()
defer r.builder.Dispose()
// 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)
r.builder.SetInsertPointBefore(bb.FirstInstruction())
dummy := r.builder.CreateAlloca(r.mod.Context().Int8Type(), "dummy")
r.builder.SetInsertPointBefore(dummy)
defer dummy.EraseFromParentAsInstruction()
// Get a list if init calls. A runtime.initAll might look something like this:
// func initAll() {
// unsafe.init()
// machine.init()
// runtime.init()
// }
// This function gets a list of these call instructions.
var initCalls []llvm.Value
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst == dummy {
@@ -63,99 +81,67 @@ func Run(mod llvm.Module, debug bool) error {
break // ret void
}
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
return errorAt(inst, "interp: expected all instructions in "+name+" to be direct calls")
return errorAt(inst, "interp: expected all instructions in "+initAll.Name()+" to be direct calls")
}
initCalls = append(initCalls, inst)
}
// Do this in a separate step to avoid corrupting the iterator above.
undefPtr := llvm.Undef(llvm.PointerType(mod.Context().Int8Type(), 0))
// Run initializers for each package. Once the package initializer is
// finished, the call to the package initializer can be removed.
for _, call := range initCalls {
initName := call.CalledValue().Name()
if !strings.HasSuffix(initName, ".init") {
return errorAt(call, "interp: expected all instructions in "+name+" to be *.init() calls")
return errorAt(call, "interp: expected all instructions in "+initAll.Name()+" to be *.init() calls")
}
pkgName := initName[:len(initName)-5]
r.pkgName = initName[:len(initName)-len(".init")]
fn := call.CalledValue()
if r.debug {
fmt.Fprintln(os.Stderr, "call:", fn.Name())
}
_, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
if callErr != nil {
if isRecoverableError(callErr.Err) {
if r.debug {
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
}
mem.revert()
continue
}
return callErr
}
call.EraseFromParentAsInstruction()
evalPkg := evalPackage{
Eval: e,
packagePath: pkgName,
for index, obj := range mem.objects {
r.objects[index] = obj
}
_, err := evalPkg.function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, "")
if err == errUnreachable {
break
}
r.pkgName = ""
// Update all global variables in the LLVM module.
mem := memoryView{r: &r}
for _, obj := range r.objects {
if obj.llvmGlobal.IsNil() {
continue
}
if err != nil {
return err
if obj.buffer == nil {
continue
}
initializer := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
panic("initializer type mismatch")
}
obj.llvmGlobal.SetInitializer(initializer)
}
return nil
}
// function interprets the given function. The params are the function params
// and the indent is the string indentation to use when dumping all interpreted
// instructions.
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, *Error) {
fr := frame{
evalPackage: e,
fn: fn,
locals: make(map[llvm.Value]Value),
}
for i, param := range fn.Params() {
fr.locals[param] = params[i]
}
bb := fn.EntryBasicBlock()
var lastBB llvm.BasicBlock
for {
retval, outgoing, err := fr.evalBasicBlock(bb, lastBB, indent)
if outgoing == nil {
// returned something (a value or void, or an error)
return retval, err
}
if len(outgoing) > 1 {
panic("unimplemented: multiple outgoing blocks")
}
next := outgoing[0]
if next.IsABasicBlock().IsNil() {
panic("did not switch to a basic block")
}
lastBB = bb
bb = next.AsBasicBlock()
}
}
// getValue determines what kind of LLVM value it gets and returns the
// appropriate Value type.
func (e *Eval) getValue(v llvm.Value) Value {
return &LocalValue{e, v}
}
// markDirty marks the passed-in LLVM value dirty, recursively. For example,
// when it encounters a constant GEP on a global, it marks the global dirty.
func (e *Eval) markDirty(v llvm.Value) {
if !v.IsAGlobalVariable().IsNil() {
if v.IsGlobalConstant() {
return
}
if _, ok := e.dirtyGlobals[v]; !ok {
e.dirtyGlobals[v] = struct{}{}
e.sideEffectFuncs = nil // re-calculate all side effects
}
} else if v.IsConstant() {
if v.OperandsCount() >= 2 && !v.Operand(0).IsAGlobalVariable().IsNil() {
// looks like a constant getelementptr of a global.
// TODO: find a way to make sure it really is: v.Opcode() returns 0.
e.markDirty(v.Operand(0))
return
}
return // nothing to mark
} else if !v.IsAGetElementPtrInst().IsNil() {
panic("interp: todo: GEP")
} else {
// Not constant and not a global or GEP so doesn't have to be marked
// non-constant.
// getFunction returns the compiled version of the given LLVM function. It
// compiles the function if necessary and caches the result.
func (r *runner) getFunction(llvmFn llvm.Value) *function {
if fn, ok := r.functionCache[llvmFn]; ok {
return fn
}
fn := r.compileFunction(llvmFn)
r.functionCache[llvmFn] = fn
return fn
}
+20
View File
@@ -42,9 +42,29 @@ func runTest(t *testing.T, pathPrefix string) {
// Perform the transform.
err = Run(mod, false)
if err != nil {
if err, match := err.(*Error); match {
println(err.Error())
if !err.Inst.IsNil() {
err.Inst.Dump()
println()
}
if len(err.Traceback) > 0 {
println("\ntraceback:")
for _, line := range err.Traceback {
println(line.Pos.String() + ":")
line.Inst.Dump()
println()
}
}
}
t.Fatal(err)
}
// To be sure, verify that the module is still valid.
if llvm.VerifyModule(mod, llvm.PrintMessageAction) != nil {
t.FailNow()
}
// Run some cleanup passes to get easy-to-read outputs.
pm := llvm.NewPassManager()
defer pm.Dispose()
+932
View File
@@ -0,0 +1,932 @@
package interp
import (
"errors"
"fmt"
"math"
"os"
"strings"
"time"
"tinygo.org/x/go-llvm"
)
func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent string) (value, memoryView, *Error) {
mem := memoryView{r: r, parent: parentMem}
locals := make([]value, len(fn.locals))
r.callsExecuted++
if time.Since(r.start) > time.Minute {
// Running for more than a minute. This should never happen.
return nil, mem, r.errorAt(fn.blocks[0].instructions[0], fmt.Errorf("interp: running for more than a minute, timing out (executed calls: %d)", r.callsExecuted))
}
// Parameters are considered a kind of local values.
for i, param := range params {
locals[i] = param
}
// Start with the first basic block and the first instruction.
// Branch instructions may modify both bb and instIndex when branching.
bb := fn.blocks[0]
currentBB := 0
lastBB := -1 // last basic block is undefined, only defined after a branch
var operands []value
for instIndex := 0; instIndex < len(bb.instructions); instIndex++ {
inst := bb.instructions[instIndex]
operands = operands[:0]
isRuntimeInst := false
if inst.opcode != llvm.PHI {
for _, v := range inst.operands {
if v, ok := v.(localValue); ok {
if localVal := locals[fn.locals[v.value]]; localVal == nil {
return nil, mem, r.errorAt(inst, errors.New("interp: local not defined"))
} else {
operands = append(operands, localVal)
if _, ok := localVal.(localValue); ok {
isRuntimeInst = true
}
continue
}
}
operands = append(operands, v)
}
}
if isRuntimeInst {
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
}
continue
}
switch inst.opcode {
case llvm.Ret:
if len(operands) != 0 {
if r.debug {
fmt.Fprintln(os.Stderr, indent+"ret", operands[0])
}
// Return instruction has a value to return.
return operands[0], mem, nil
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+"ret")
}
// Return instruction doesn't return anything, it's just 'ret void'.
return nil, mem, nil
case llvm.Br:
switch len(operands) {
case 1:
// Unconditional branch: [nextBB]
lastBB = currentBB
currentBB = int(operands[0].(literalValue).value.(uint32))
bb = fn.blocks[currentBB]
instIndex = -1 // start at 0 the next cycle
if r.debug {
fmt.Fprintln(os.Stderr, indent+"br", operands, "->", currentBB)
}
case 3:
// Conditional branch: [cond, thenBB, elseBB]
lastBB = currentBB
switch operands[0].Uint() {
case 1: // true -> thenBB
currentBB = int(operands[1].(literalValue).value.(uint32))
case 0: // false -> elseBB
currentBB = int(operands[2].(literalValue).value.(uint32))
default:
panic("bool should be 0 or 1")
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+"br", operands, "->", currentBB)
}
bb = fn.blocks[currentBB]
instIndex = -1 // start at 0 the next cycle
default:
panic("unknown operands length")
}
break // continue with next block
case llvm.PHI:
var result value
for i := 0; i < len(inst.operands); i += 2 {
if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
incoming := inst.operands[i+1]
if local, ok := incoming.(localValue); ok {
result = locals[fn.locals[local.value]]
} else {
result = incoming
}
break
}
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
}
if result == nil {
panic("could not find PHI input")
}
locals[inst.localIndex] = result
case llvm.Select:
// Select is much like a ternary operator: it picks a result from
// the second and third operand based on the boolean first operand.
var result value
switch operands[0].Uint() {
case 1:
result = operands[1]
case 0:
result = operands[2]
default:
panic("boolean must be 0 or 1")
}
locals[inst.localIndex] = result
if r.debug {
fmt.Fprintln(os.Stderr, indent+"select", operands, "->", result)
}
case llvm.Call:
// A call instruction can either be a regular call or a runtime intrinsic.
fnPtr, err := operands[0].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
callFn := r.getFunction(fnPtr.llvmValue(&mem))
switch {
case callFn.name == "runtime.trackPointer":
// Allocas and such are created as globals, so don't need a
// runtime.trackPointer.
// Unless the object is allocated at runtime for example, in
// which case this call won't even get to this point but will
// already be emitted in initAll.
continue
case callFn.name == "(reflect.Type).Elem" || strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet":
// These functions should be run at runtime. Specifically:
// * (reflect.Type).Elem is a special function. It should
// eventually be interpreted, but fall back to a runtime call
// for now.
// * Print and panic functions are best emitted directly without
// interpreting them, otherwise we get a ton of putchar (etc.)
// calls.
// * runtime.hashmapGet tries to access the map value directly.
// This is not possible as the map value is treated as a special
// kind of object in this package.
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
}
case callFn.name == "runtime.nanotime" && r.pkgName == "time":
// The time package contains a call to runtime.nanotime.
// This appears to be to work around a limitation in Windows
// Server 2008:
// > Monotonic times are reported as offsets from startNano.
// > We initialize startNano to runtimeNano() - 1 so that on systems where
// > monotonic time resolution is fairly low (e.g. Windows 2008
// > which appears to have a default resolution of 15ms),
// > we avoid ever reporting a monotonic time of 0.
// > (Callers may want to use 0 as "time not set".)
// Simply let runtime.nanotime return 0 in this case, which
// should be fine and avoids a call to runtime.nanotime. It
// means that monotonic time in the time package is counted from
// time.Time{}.Sub(1), which should be fine.
locals[inst.localIndex] = literalValue{uint64(0)}
case callFn.name == "runtime.alloc":
// Allocate heap memory. At compile time, this is instead done
// by creating a global variable.
// Get the requested memory size to be allocated.
size := operands[1].Uint()
// Create the object.
alloc := object{
globalName: r.pkgName + "$alloc",
buffer: newRawValue(uint32(size)),
size: uint32(size),
}
index := len(r.objects)
r.objects = append(r.objects, alloc)
// And create a pointer to this object, for working with it (so
// that stores to it copy it, etc).
ptr := newPointerValue(r, index, 0)
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.alloc:", size, "->", ptr)
}
locals[inst.localIndex] = ptr
case callFn.name == "runtime.sliceCopy":
// sliceCopy implements the built-in copy function for slices.
// It is implemented here so that it can be used even if the
// runtime implementation is not available. Doing it this way
// may also be faster.
// Code:
// func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int {
// n := srcLen
// if n > dstLen {
// n = dstLen
// }
// memmove(dst, src, n*elemSize)
// return int(n)
// }
dstLen := operands[3].Uint()
srcLen := operands[4].Uint()
elemSize := operands[5].Uint()
n := srcLen
if n > dstLen {
n = dstLen
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+"copy:", operands[1], operands[2], n)
}
if n != 0 {
// Only try to copy bytes when there are any bytes to copy.
// This is not just an optimization. If one of the slices
// (or both) are nil, the asPointer method call will fail
// even though copying a nil slice is allowed.
dst, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
src, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
nBytes := uint32(n * elemSize)
dstObj := mem.getWritable(dst.index())
dstBuf := dstObj.buffer.asRawValue(r)
srcBuf := mem.get(src.index()).buffer.asRawValue(r)
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
dstObj.buffer = dstBuf
mem.put(dst.index(), dstObj)
}
switch inst.llvmInst.Type().IntTypeWidth() {
case 16:
locals[inst.localIndex] = literalValue{uint16(n)}
case 32:
locals[inst.localIndex] = literalValue{uint32(n)}
case 64:
locals[inst.localIndex] = literalValue{uint64(n)}
default:
panic("unknown integer type width")
}
case strings.HasPrefix(callFn.name, "llvm.memcpy.p0i8.p0i8.") || strings.HasPrefix(callFn.name, "llvm.memmove.p0i8.p0i8."):
// Copy a block of memory from one pointer to another.
dst, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
src, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
nBytes := uint32(operands[3].Uint())
dstObj := mem.getWritable(dst.index())
dstBuf := dstObj.buffer.asRawValue(r)
srcBuf := mem.get(src.index()).buffer.asRawValue(r)
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
dstObj.buffer = dstBuf
mem.put(dst.index(), dstObj)
case callFn.name == "runtime.typeAssert":
// This function must be implemented manually as it is normally
// implemented by the interface lowering pass.
if r.debug {
fmt.Fprintln(os.Stderr, indent+"typeassert:", operands[1:])
}
typeInInterfacePtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
actualType, err := mem.load(typeInInterfacePtr, r.pointerSize).asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
assertedType, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
result := assertedType.asRawValue(r).equal(actualType.asRawValue(r))
if result {
locals[inst.localIndex] = literalValue{uint8(1)}
} else {
locals[inst.localIndex] = literalValue{uint8(0)}
}
case callFn.name == "runtime.interfaceImplements":
if r.debug {
fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:])
}
// Load various values for the interface implements check below.
typeInInterfacePtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
methodSetPtr, err := mem.load(typeInInterfacePtr.addOffset(r.pointerSize), r.pointerSize).asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
interfaceMethodSetPtr, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
interfaceMethodSet := mem.get(interfaceMethodSetPtr.index()).llvmGlobal.Initializer()
// Make a set of all the methods on the concrete type, for
// easier checking in the next step.
concreteTypeMethods := 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()
concreteTypeMethods[name] = struct{}{}
}
// Check whether all interface methods are also in the list
// of defined methods calculated above. This is the interface
// assert itself.
assertOk := uint8(1) // i1 true
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
if _, ok := concreteTypeMethods[name]; !ok {
// There is a method on the interface that is not
// implemented by the type. The assertion will fail.
assertOk = 0 // i1 false
break
}
}
// If assertOk is still 1, the assertion succeeded.
locals[inst.localIndex] = literalValue{assertOk}
case callFn.name == "runtime.hashmapMake":
// Create a new map.
hashmapPointerType := inst.llvmInst.Type()
keySize := uint32(operands[1].Uint())
valueSize := uint32(operands[2].Uint())
m := newMapValue(r, hashmapPointerType, keySize, valueSize)
alloc := object{
llvmType: hashmapPointerType,
globalName: r.pkgName + "$map",
buffer: m,
size: m.len(r),
}
index := len(r.objects)
r.objects = append(r.objects, alloc)
// Create a pointer to this map. Maps are reference types, so
// are implemented as pointers.
ptr := newPointerValue(r, index, 0)
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapMake:", keySize, valueSize, "->", ptr)
}
locals[inst.localIndex] = ptr
case callFn.name == "runtime.hashmapBinarySet":
// Do a mapassign operation with a binary key (that is, without
// a string key).
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
}
mapPtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
keyPtr, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
valuePtr, err := operands[3].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
err = m.putBinary(&mem, keyPtr, valuePtr)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
case callFn.name == "runtime.hashmapStringSet":
// Do a mapassign operation with a string key.
if r.debug {
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
}
mapPtr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
stringPtr, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
stringLen := operands[3].Uint()
valuePtr, err := operands[4].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
err = m.putString(&mem, stringPtr, stringLen, valuePtr)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
default:
if len(callFn.blocks) == 0 {
// Call to a function declaration without a definition
// available.
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
}
continue
}
// Call a function with a definition available. Run it as usual,
// possibly trying to recover from it if it failed to execute.
if r.debug {
argStrings := make([]string, len(operands)-1)
for i := range argStrings {
argStrings[i] = operands[i+1].String()
}
fmt.Fprintln(os.Stderr, indent+"call:", callFn.name+"("+strings.Join(argStrings, ", ")+")")
}
retval, callMem, callErr := r.run(callFn, operands[1:], &mem, indent+" ")
if callErr != nil {
if isRecoverableError(callErr.Err) {
// This error can be recovered by doing the call at
// runtime instead of at compile time. But we need to
// revert any changes made by the call first.
if r.debug {
fmt.Fprintln(os.Stderr, indent+"!! revert because of error:", callErr.Err)
}
callMem.revert()
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
}
continue
}
// Add to the traceback, so that error handling code can see
// how this function got called.
callErr.Traceback = append(callErr.Traceback, ErrorLine{
Pos: getPosition(inst.llvmInst),
Inst: inst.llvmInst,
})
return nil, mem, callErr
}
locals[inst.localIndex] = retval
mem.extend(callMem)
}
case llvm.Load:
// Load instruction, loading some data from the topmost memory view.
ptr, err := operands[0].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
size := operands[1].(literalValue).value.(uint64)
if mem.hasExternalStore(ptr) {
// If there could be an external store (for example, because a
// pointer to the object was passed to a function that could not
// be interpreted at compile time) then the load must be done at
// runtime.
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
}
continue
}
result := mem.load(ptr, uint32(size))
if r.debug {
fmt.Fprintln(os.Stderr, indent+"load:", ptr, "->", result)
}
locals[inst.localIndex] = result
case llvm.Store:
// Store instruction. Create a new object in the memory view and
// store to that, to make it possible to roll back this store.
ptr, err := operands[1].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
if mem.hasExternalLoadOrStore(ptr) {
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
}
continue
}
val := operands[0]
if r.debug {
fmt.Fprintln(os.Stderr, indent+"store:", val, ptr)
}
mem.store(val, ptr)
case llvm.Alloca:
// Alloca normally allocates some stack memory. In the interpreter,
// it allocates a global instead.
// This can likely be optimized, as all it really needs is an alloca
// in the initAll function and creating a global is wasteful for
// this purpose.
// Create the new object.
size := operands[0].(literalValue).value.(uint64)
alloca := object{
llvmType: inst.llvmInst.Type(),
globalName: r.pkgName + "$alloca",
buffer: newRawValue(uint32(size)),
size: uint32(size),
}
index := len(r.objects)
r.objects = append(r.objects, alloca)
// Create a pointer to this object (an alloca produces a pointer).
ptr := newPointerValue(r, index, 0)
if r.debug {
fmt.Fprintln(os.Stderr, indent+"alloca:", operands, "->", ptr)
}
locals[inst.localIndex] = ptr
case llvm.GetElementPtr:
// GetElementPtr does pointer arithmetic, changing the offset of the
// pointer into the underlying object.
var offset uint64
var gepOperands []uint64
for i := 2; i < len(operands); i += 2 {
index := operands[i].Uint()
elementSize := operands[i+1].Uint()
if int64(elementSize) < 0 {
// This is a struct field.
// The field number is encoded by flipping all the bits.
gepOperands = append(gepOperands, ^elementSize)
offset += index
} else {
// This is a normal GEP, probably an array index.
gepOperands = append(gepOperands, index)
offset += elementSize * index
}
}
ptr, err := operands[0].asPointer(r)
if err != nil {
if err != errIntegerAsPointer {
return nil, mem, r.errorAt(inst, err)
}
// GEP on fixed pointer value (for example, memory-mapped I/O).
ptrValue := operands[0].Uint() + offset
switch operands[0].len(r) {
case 8:
locals[inst.localIndex] = literalValue{uint64(ptrValue)}
case 4:
locals[inst.localIndex] = literalValue{uint32(ptrValue)}
case 2:
locals[inst.localIndex] = literalValue{uint16(ptrValue)}
default:
panic("pointer operand is not of a known pointer size")
}
continue
}
ptr = ptr.addOffset(uint32(offset))
locals[inst.localIndex] = ptr
if r.debug {
fmt.Fprintln(os.Stderr, indent+"gep:", operands, "->", ptr)
}
case llvm.BitCast, llvm.IntToPtr, llvm.PtrToInt:
// Various bitcast-like instructions that all keep the same bits
// while changing the LLVM type.
// Because interp doesn't preserve the type, these operations are
// identity operations.
if r.debug {
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", operands[0])
}
locals[inst.localIndex] = operands[0]
case llvm.ExtractValue:
agg := operands[0].asRawValue(r)
offset := operands[1].(literalValue).value.(uint64)
size := operands[2].(literalValue).value.(uint64)
elt := rawValue{
buf: agg.buf[offset : offset+size],
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+"extractvalue:", operands, "->", elt)
}
locals[inst.localIndex] = elt
case llvm.InsertValue:
agg := operands[0].asRawValue(r)
elt := operands[1].asRawValue(r)
offset := int(operands[2].(literalValue).value.(uint64))
newagg := newRawValue(uint32(len(agg.buf)))
copy(newagg.buf, agg.buf)
copy(newagg.buf[offset:], elt.buf)
if r.debug {
fmt.Fprintln(os.Stderr, indent+"insertvalue:", operands, "->", newagg)
}
locals[inst.localIndex] = newagg
case llvm.ICmp:
predicate := llvm.IntPredicate(operands[2].(literalValue).value.(uint8))
var result bool
lhs := operands[0]
rhs := operands[1]
switch predicate {
case llvm.IntEQ, llvm.IntNE:
lhsPointer, lhsErr := lhs.asPointer(r)
rhsPointer, rhsErr := rhs.asPointer(r)
if (lhsErr == nil) != (rhsErr == nil) {
// Fast path: only one is a pointer, so they can't be equal.
result = false
} else if lhsErr == nil {
// Both must be nil, so both are pointers.
// Compare them directly.
result = lhsPointer.equal(rhsPointer)
} else {
// Fall back to generic comparison.
result = lhs.asRawValue(r).equal(rhs.asRawValue(r))
}
if predicate == llvm.IntNE {
result = !result
}
case llvm.IntUGT:
result = lhs.Uint() > rhs.Uint()
case llvm.IntUGE:
result = lhs.Uint() >= rhs.Uint()
case llvm.IntULT:
result = lhs.Uint() < rhs.Uint()
case llvm.IntULE:
result = lhs.Uint() <= rhs.Uint()
case llvm.IntSGT:
result = lhs.Int() > rhs.Int()
case llvm.IntSGE:
result = lhs.Int() >= rhs.Int()
case llvm.IntSLT:
result = lhs.Int() < rhs.Int()
case llvm.IntSLE:
result = lhs.Int() <= rhs.Int()
default:
return nil, mem, r.errorAt(inst, errors.New("interp: unsupported icmp"))
}
if result {
locals[inst.localIndex] = literalValue{uint8(1)}
} else {
locals[inst.localIndex] = literalValue{uint8(0)}
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+"icmp:", operands[0], intPredicateString(predicate), operands[1], "->", result)
}
case llvm.FCmp:
predicate := llvm.FloatPredicate(operands[2].(literalValue).value.(uint8))
var result bool
var lhs, rhs float64
switch operands[0].len(r) {
case 8:
lhs = math.Float64frombits(operands[0].Uint())
rhs = math.Float64frombits(operands[1].Uint())
case 4:
lhs = float64(math.Float32frombits(uint32(operands[0].Uint())))
rhs = float64(math.Float32frombits(uint32(operands[1].Uint())))
default:
panic("unknown float type")
}
switch predicate {
case llvm.FloatOEQ:
result = lhs == rhs
case llvm.FloatUNE:
result = lhs != rhs
case llvm.FloatOGT:
result = lhs > rhs
case llvm.FloatOGE:
result = lhs >= rhs
case llvm.FloatOLT:
result = lhs < rhs
case llvm.FloatOLE:
result = lhs <= rhs
default:
return nil, mem, r.errorAt(inst, errors.New("interp: unsupported fcmp"))
}
if result {
locals[inst.localIndex] = literalValue{uint8(1)}
} else {
locals[inst.localIndex] = literalValue{uint8(0)}
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+"fcmp:", operands[0], predicate, operands[1], "->", result)
}
case llvm.Add, llvm.Sub, llvm.Mul, llvm.UDiv, llvm.SDiv, llvm.URem, llvm.SRem, llvm.Shl, llvm.LShr, llvm.AShr, llvm.And, llvm.Or, llvm.Xor:
// Integer binary operations.
lhs := operands[0]
rhs := operands[1]
lhsPtr, err := lhs.asPointer(r)
if err == nil {
// The lhs is a pointer. This sometimes happens for particular
// pointer tricks.
switch inst.opcode {
case llvm.Add:
// This likely means this is part of a
// unsafe.Pointer(uintptr(ptr) + offset) pattern.
lhsPtr = lhsPtr.addOffset(uint32(rhs.Uint()))
locals[inst.localIndex] = lhsPtr
continue
case llvm.Xor:
if rhs.Uint() == 0 {
// Special workaround for strings.noescape, see
// src/strings/builder.go in the Go source tree. This is
// the identity operator, so we can return the input.
locals[inst.localIndex] = lhs
continue
}
default:
// Catch-all for weird operations that should just be done
// at runtime.
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
if err != nil {
return nil, mem, err
}
continue
}
}
var result uint64
switch inst.opcode {
case llvm.Add:
result = lhs.Uint() + rhs.Uint()
case llvm.Sub:
result = lhs.Uint() - rhs.Uint()
case llvm.Mul:
result = lhs.Uint() * rhs.Uint()
case llvm.UDiv:
result = lhs.Uint() / rhs.Uint()
case llvm.SDiv:
result = uint64(lhs.Int() / rhs.Int())
case llvm.URem:
result = lhs.Uint() % rhs.Uint()
case llvm.SRem:
result = uint64(lhs.Int() % rhs.Int())
case llvm.Shl:
result = lhs.Uint() << rhs.Uint()
case llvm.LShr:
result = lhs.Uint() >> rhs.Uint()
case llvm.AShr:
result = uint64(lhs.Int() >> rhs.Uint())
case llvm.And:
result = lhs.Uint() & rhs.Uint()
case llvm.Or:
result = lhs.Uint() | rhs.Uint()
case llvm.Xor:
result = lhs.Uint() ^ rhs.Uint()
default:
panic("unreachable")
}
switch lhs.len(r) {
case 8:
locals[inst.localIndex] = literalValue{result}
case 4:
locals[inst.localIndex] = literalValue{uint32(result)}
case 2:
locals[inst.localIndex] = literalValue{uint16(result)}
case 1:
locals[inst.localIndex] = literalValue{uint8(result)}
default:
panic("unknown integer size")
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", lhs, rhs, "->", result)
}
case llvm.SExt, llvm.ZExt, llvm.Trunc:
// Change the size of an integer to a larger or smaller bit width.
// We make use of the fact that the Uint() function already
// zero-extends the value and that Int() already sign-extends the
// value, so we only need to truncate it to the appropriate bit
// width. This means we can implement sext, zext and trunc in the
// same way, by first {zero,sign}extending all the way up to uint64
// and then truncating it as necessary.
var value uint64
if inst.opcode == llvm.SExt {
value = uint64(operands[0].Int())
} else {
value = operands[0].Uint()
}
bitwidth := operands[1].Uint()
if r.debug {
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth)
}
switch bitwidth {
case 64:
locals[inst.localIndex] = literalValue{value}
case 32:
locals[inst.localIndex] = literalValue{uint32(value)}
case 16:
locals[inst.localIndex] = literalValue{uint16(value)}
case 8:
locals[inst.localIndex] = literalValue{uint8(value)}
default:
panic("unknown integer size in sext/zext/trunc")
}
case llvm.SIToFP, llvm.UIToFP:
var value float64
switch inst.opcode {
case llvm.SIToFP:
value = float64(operands[0].Int())
case llvm.UIToFP:
value = float64(operands[0].Uint())
}
bitwidth := operands[1].Uint()
if r.debug {
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth)
}
switch bitwidth {
case 64:
locals[inst.localIndex] = literalValue{math.Float64bits(value)}
case 32:
locals[inst.localIndex] = literalValue{math.Float32bits(float32(value))}
default:
panic("unknown integer size in sitofp/uitofp")
}
default:
if r.debug {
fmt.Fprintln(os.Stderr, indent+inst.String())
}
return nil, mem, r.errorAt(inst, errUnsupportedInst)
}
}
return nil, mem, r.errorAt(bb.instructions[len(bb.instructions)-1], errors.New("interp: reached end of basic block without terminator"))
}
func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error {
numOperands := inst.llvmInst.OperandsCount()
operands := make([]llvm.Value, numOperands)
for i := 0; i < numOperands; i++ {
operand := inst.llvmInst.Operand(i)
if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() {
operand = locals[fn.locals[operand]].toLLVMValue(operand.Type(), mem)
}
operands[i] = operand
}
if r.debug {
fmt.Fprintln(os.Stderr, indent+inst.String())
}
var result llvm.Value
switch inst.opcode {
case llvm.Call:
llvmFn := operands[len(operands)-1]
args := operands[:len(operands)-1]
for _, arg := range args {
if arg.Type().TypeKind() == llvm.PointerTypeKind {
mem.markExternalStore(arg)
}
}
result = r.builder.CreateCall(llvmFn, args, inst.name)
case llvm.Load:
mem.markExternalLoad(operands[0])
result = r.builder.CreateLoad(operands[0], inst.name)
if inst.llvmInst.IsVolatile() {
result.SetVolatile(true)
}
case llvm.Store:
mem.markExternalStore(operands[1])
result = r.builder.CreateStore(operands[0], operands[1])
if inst.llvmInst.IsVolatile() {
result.SetVolatile(true)
}
case llvm.BitCast:
result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name)
case llvm.ExtractValue:
indices := inst.llvmInst.Indices()
if len(indices) != 1 {
panic("expected exactly one index")
}
result = r.builder.CreateExtractValue(operands[0], int(indices[0]), inst.name)
case llvm.InsertValue:
indices := inst.llvmInst.Indices()
if len(indices) != 1 {
panic("expected exactly one index")
}
result = r.builder.CreateInsertValue(operands[0], operands[1], int(indices[0]), inst.name)
case llvm.Add:
result = r.builder.CreateAdd(operands[0], operands[1], inst.name)
case llvm.Sub:
result = r.builder.CreateSub(operands[0], operands[1], inst.name)
case llvm.Mul:
result = r.builder.CreateMul(operands[0], operands[1], inst.name)
case llvm.UDiv:
result = r.builder.CreateUDiv(operands[0], operands[1], inst.name)
case llvm.SDiv:
result = r.builder.CreateSDiv(operands[0], operands[1], inst.name)
case llvm.URem:
result = r.builder.CreateURem(operands[0], operands[1], inst.name)
case llvm.SRem:
result = r.builder.CreateSRem(operands[0], operands[1], inst.name)
case llvm.ZExt:
result = r.builder.CreateZExt(operands[0], inst.llvmInst.Type(), inst.name)
default:
return r.errorAt(inst, errUnsupportedRuntimeInst)
}
locals[inst.localIndex] = localValue{result}
mem.instructions = append(mem.instructions, result)
return nil
}
func intPredicateString(predicate llvm.IntPredicate) string {
switch predicate {
case llvm.IntEQ:
return "eq"
case llvm.IntNE:
return "ne"
case llvm.IntUGT:
return "ugt"
case llvm.IntUGE:
return "uge"
case llvm.IntULT:
return "ult"
case llvm.IntULE:
return "ule"
case llvm.IntSGT:
return "sgt"
case llvm.IntSGE:
return "sge"
case llvm.IntSLT:
return "slt"
case llvm.IntSLE:
return "sle"
default:
return "cmp?"
}
}
+1430
View File
File diff suppressed because it is too large Load Diff
-259
View File
@@ -1,259 +0,0 @@
package interp
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
)
type sideEffectSeverity int
func (severity sideEffectSeverity) String() string {
switch severity {
case sideEffectInProgress:
return "in progress"
case sideEffectNone:
return "none"
case sideEffectLimited:
return "limited"
case sideEffectAll:
return "all"
default:
return "unknown"
}
}
const (
sideEffectInProgress sideEffectSeverity = iota // computing side effects is in progress (for recursive functions)
sideEffectNone // no side effects at all (pure)
sideEffectLimited // has side effects, but the effects are known
sideEffectAll // has unknown side effects
)
// sideEffectResult contains the scan results after scanning a function for side
// effects (recursively).
type sideEffectResult struct {
severity sideEffectSeverity
mentionsGlobals map[llvm.Value]struct{}
}
// hasSideEffects scans this function and all descendants, recursively. It
// returns whether this function has side effects and if it does, which globals
// it mentions anywhere in this function or any called functions.
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error) {
name := fn.Name()
switch {
case name == "runtime.alloc":
// Cannot be scanned but can be interpreted.
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.nanotime":
// Fixed value at compile time.
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime._panic":
return &sideEffectResult{severity: sideEffectLimited}, nil
case name == "runtime.typeAssert":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.sliceCopy":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.trackPointer":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "(*sync/atomic.Value).Load" || name == "(*sync/atomic.Value).Store":
// These functions do some unsafe pointer loading/storing but are
// otherwise safe.
return &sideEffectResult{severity: sideEffectLimited}, nil
case strings.HasPrefix(name, "llvm.lifetime."):
return &sideEffectResult{severity: sideEffectNone}, nil
}
if fn.IsDeclaration() {
return &sideEffectResult{severity: sideEffectLimited}, nil
}
if e.sideEffectFuncs == nil {
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
}
if se, ok := e.sideEffectFuncs[fn]; ok {
return se, nil
}
result := &sideEffectResult{
severity: sideEffectInProgress,
mentionsGlobals: map[llvm.Value]struct{}{},
}
e.sideEffectFuncs[fn] = result
dirtyLocals := map[llvm.Value]struct{}{}
for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst.IsAInstruction().IsNil() {
// Should not happen in valid IR.
panic("not an instruction")
}
// Check for any globals mentioned anywhere in the function. Assume
// any mentioned globals may be read from or written to when
// executed, thus must be marked dirty with a call.
for i := 0; i < inst.OperandsCount(); i++ {
operand := inst.Operand(i)
if !operand.IsAGlobalVariable().IsNil() {
result.mentionsGlobals[operand] = struct{}{}
}
}
switch inst.InstructionOpcode() {
case llvm.IndirectBr, llvm.Invoke:
// Not emitted by the compiler.
return nil, e.errorAt(inst, errors.New("unknown instructions"))
case llvm.Call:
child := inst.CalledValue()
if !child.IsAInlineAsm().IsNil() {
// Inline assembly. This most likely has side effects.
// Assume they're only limited side effects, similar to
// external function calls.
result.updateSeverity(sideEffectLimited)
continue
}
if child.IsAFunction().IsNil() {
// Indirect call?
// In any case, we can't know anything here about what it
// affects exactly so mark this function as invoking all
// possible side effects.
result.updateSeverity(sideEffectAll)
continue
}
if child.IsDeclaration() {
// External function call. Assume only limited side effects
// (no affected globals, etc.).
switch child.Name() {
case "runtime.alloc":
continue
case "runtime.typeAssert":
continue // implemented in interp
case "runtime.interfaceImplements":
continue // implemented in interp
}
if e.hasLocalSideEffects(dirtyLocals, inst) {
result.updateSeverity(sideEffectLimited)
}
continue
}
childSideEffects, err := e.hasSideEffects(child)
if err != nil {
return nil, err
}
switch childSideEffects.severity {
case sideEffectInProgress, sideEffectNone:
// no side effects or recursive function - continue scanning
case sideEffectLimited:
// The return value may be problematic.
if e.hasLocalSideEffects(dirtyLocals, inst) {
result.updateSeverity(sideEffectLimited)
}
case sideEffectAll:
result.updateSeverity(sideEffectAll)
default:
panic("unreachable")
}
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)
}
case llvm.IntToPtr:
// Pointer casts are not yet supported.
result.updateSeverity(sideEffectLimited)
default:
// Ignore most instructions.
// Check this list for completeness:
// https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode
}
}
}
if result.severity == sideEffectInProgress {
// No side effect was reported for this function.
result.severity = sideEffectNone
}
return result, nil
}
// hasLocalSideEffects checks whether the given instruction flows into a branch
// or return instruction, in which case the whole function must be marked as
// having side effects and be called at runtime.
func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
if _, ok := dirtyLocals[inst]; ok {
// It is already known that this local is dirty.
return true
}
for use := inst.FirstUse(); !use.IsNil(); use = use.NextUse() {
user := use.User()
if user.IsAInstruction().IsNil() {
// Should not happen in valid IR.
panic("user not an instruction")
}
switch user.InstructionOpcode() {
case llvm.Br, llvm.Switch:
// A branch on a dirty value makes this function dirty: it cannot be
// interpreted at compile time so has to be run at runtime. It is
// marked as having side effects for this reason.
return true
case llvm.Ret:
// This function returns a dirty value so it is itself marked as
// dirty to make sure it is called at runtime.
return true
case llvm.Store:
ptr := user.Operand(1)
if !ptr.IsAGlobalVariable().IsNil() {
// Store to a global variable.
// Already handled in (*Eval).hasSideEffects.
continue
}
// This store might affect all kinds of values. While it is
// certainly possible to traverse through all of them, the easiest
// option right now is to just assume the worst and say that this
// function has side effects.
// TODO: traverse through all stores and mark all relevant allocas /
// globals dirty.
return true
default:
// All instructions that take 0 or more operands (1 or more if it
// was a use) and produce a result.
// For a list:
// https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode
dirtyLocals[user] = struct{}{}
if e.hasLocalSideEffects(dirtyLocals, user) {
return true
}
}
}
// No side effects found.
return false
}
// updateSeverity sets r.severity to the max of r.severity and severity,
// conservatively assuming the worst severity.
func (r *sideEffectResult) updateSeverity(severity sideEffectSeverity) {
if severity > r.severity {
r.severity = severity
}
}
// updateSeverity updates the severity with the severity of the child severity,
// like in a function call. This means it also copies the mentioned globals.
func (r *sideEffectResult) update(child *sideEffectResult) {
r.updateSeverity(child.severity)
for global := range child.mentionsGlobals {
r.mentionsGlobals[global] = struct{}{}
}
}
-95
View File
@@ -1,95 +0,0 @@
package interp
import (
"os"
"sort"
"testing"
"tinygo.org/x/go-llvm"
)
var scanTestTable = []struct {
name string
severity sideEffectSeverity
mentionsGlobals []string
}{
{"returnsConst", sideEffectNone, nil},
{"returnsArg", sideEffectNone, nil},
{"externalCallOnly", sideEffectNone, nil},
{"externalCallAndReturn", sideEffectLimited, nil},
{"externalCallBranch", sideEffectLimited, nil},
{"readCleanGlobal", sideEffectNone, []string{"cleanGlobalInt"}},
{"readDirtyGlobal", sideEffectLimited, []string{"dirtyGlobalInt"}},
{"callFunctionPointer", sideEffectAll, []string{"functionPointer"}},
{"getDirtyPointer", sideEffectLimited, nil},
{"storeToPointer", sideEffectLimited, nil},
{"callTypeAssert", sideEffectNone, nil},
{"callInterfaceImplements", sideEffectNone, nil},
}
func TestScan(t *testing.T) {
t.Parallel()
// Read the input IR.
path := "testdata/scan.ll"
ctx := llvm.NewContext()
buf, err := llvm.NewMemoryBufferFromFile(path)
os.Stat(path) // make sure this file is tracked by `go test` caching
if err != nil {
t.Fatalf("could not read file %s: %v", path, err)
}
mod, err := ctx.ParseIR(buf)
if err != nil {
t.Fatalf("could not load module:\n%v", err)
}
// Check all to-be-tested functions.
for _, tc := range scanTestTable {
// Create an eval object, for testing.
e := &Eval{
Mod: mod,
TargetData: llvm.NewTargetData(mod.DataLayout()),
dirtyGlobals: map[llvm.Value]struct{}{},
}
// Mark some globals dirty, for testing.
e.markDirty(mod.NamedGlobal("dirtyGlobalInt"))
// Scan for side effects.
fn := mod.NamedFunction(tc.name)
if fn.IsNil() {
t.Errorf("scan test: could not find tested function %s in the IR", tc.name)
continue
}
evalPkg := &evalPackage{e, "testdata"}
result, err := evalPkg.hasSideEffects(fn)
if err != nil {
t.Errorf("scan test: failed to scan %s for side effects: %v", fn.Name(), err)
}
// Check whether the result is what we expect.
if result.severity != tc.severity {
t.Errorf("scan test: function %s should have severity %s but it has %s", tc.name, tc.severity, result.severity)
}
// Check whether the mentioned globals match with what we'd expect.
mentionsGlobalNames := make([]string, 0, len(result.mentionsGlobals))
for global := range result.mentionsGlobals {
mentionsGlobalNames = append(mentionsGlobalNames, global.Name())
}
sort.Strings(mentionsGlobalNames)
globalsMismatch := false
if len(result.mentionsGlobals) != len(tc.mentionsGlobals) {
globalsMismatch = true
} else {
for i, globalName := range mentionsGlobalNames {
if tc.mentionsGlobals[i] != globalName {
globalsMismatch = true
}
}
}
if globalsMismatch {
t.Errorf("scan test: expected %s to mention globals %v, but it mentions globals %v", tc.name, tc.mentionsGlobals, mentionsGlobalNames)
}
}
}
+29
View File
@@ -4,6 +4,10 @@ target triple = "x86_64--linux"
@main.v1 = internal global i64 0
@main.nonConst1 = global [4 x i64] zeroinitializer
@main.nonConst2 = global i64 0
@main.someArray = global [8 x {i16, i32}] zeroinitializer
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
@main.exposedValue1 = global i16 0
@main.exposedValue2 = global i16 0
declare void @runtime.printint64(i64) unnamed_addr
@@ -47,6 +51,20 @@ entry:
%value2 = load i64, i64* %gep2
store i64 %value2, i64* @main.nonConst2
; Test that the following GEP works:
; var someArray
; modifyExternal(&someArray[3].field1)
%gep3 = getelementptr [8 x {i16, i32}], [8 x {i16, i32}]* @main.someArray, i32 0, i32 3, i32 1
call void @modifyExternal(i32* %gep3)
; Test that marking a value as external also marks all referenced values.
call void @modifyExternal(i32* bitcast ([1 x i16*]* @main.exportedValue to i32*))
store i16 5, i16* @main.exposedValue1
; Test that this even propagates through functions.
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
store i16 7, i16* @main.exposedValue2
ret void
}
@@ -58,3 +76,14 @@ entry:
}
declare i64 @someValue()
declare void @modifyExternal(i32*)
; This function will modify an external value. By passing this function as a
; function pointer to an external function, @main.exposedValue2 should be
; marked as external.
define void @willModifyGlobal() {
entry:
store i16 8, i16* @main.exposedValue2
ret void
}
+17
View File
@@ -3,6 +3,10 @@ target triple = "x86_64--linux"
@main.nonConst1 = local_unnamed_addr global [4 x i64] zeroinitializer
@main.nonConst2 = local_unnamed_addr global i64 0
@main.someArray = global [8 x { i16, i32 }] zeroinitializer
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
@main.exposedValue1 = global i16 0
@main.exposedValue2 = local_unnamed_addr global i16 0
declare void @runtime.printint64(i64) unnamed_addr
@@ -16,6 +20,11 @@ entry:
store i64 %value1, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
%value2 = load i64, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
store i64 %value2, i64* @main.nonConst2
call void @modifyExternal(i32* getelementptr inbounds ([8 x { i16, i32 }], [8 x { i16, i32 }]* @main.someArray, i32 0, i32 3, i32 1))
call void @modifyExternal(i32* bitcast ([1 x i16*]* @main.exportedValue to i32*))
store i16 5, i16* @main.exposedValue1
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
store i16 7, i16* @main.exposedValue2
ret void
}
@@ -27,3 +36,11 @@ entry:
}
declare i64 @someValue() local_unnamed_addr
declare void @modifyExternal(i32*) local_unnamed_addr
define void @willModifyGlobal() {
entry:
store i16 8, i16* @main.exposedValue2
ret void
}
+2 -4
View File
@@ -48,8 +48,7 @@ entry:
define internal void @main.testNonConstantBinarySet() {
%hashmap.key = alloca i8
%hashmap.value = alloca i8
; Create hashmap from global. This breaks the normal hashmapBinarySet
; optimization, to test the fallback.
; Create hashmap from global.
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 1, i32 1, i8* undef, i8* null)
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.binaryMap
%map = load %runtime.hashmap*, %runtime.hashmap** @main.binaryMap
@@ -64,8 +63,7 @@ define internal void @main.testNonConstantBinarySet() {
; operations (with string keys).
define internal void @main.testNonConstantStringSet() {
%hashmap.value = alloca i8
; Create hashmap from global. This breaks the normal hashmapStringSet
; optimization, to test the fallback.
; Create hashmap from global.
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 8, i8 1, i32 1, i8* undef, i8* null)
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.stringMap
%map = load %runtime.hashmap*, %runtime.hashmap** @main.stringMap
+8 -16
View File
@@ -2,27 +2,19 @@ target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv6m-none-eabi"
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
%runtime._string = type { i8*, i32 }
@main.m = local_unnamed_addr global %runtime.hashmap* @"main$map"
@main.binaryMap = local_unnamed_addr global %runtime.hashmap* @"main$map.4"
@main.stringMap = local_unnamed_addr global %runtime.hashmap* @"main$map.6"
@main.binaryMap = local_unnamed_addr global %runtime.hashmap* @"main$map.1"
@main.stringMap = local_unnamed_addr global %runtime.hashmap* @"main$map.3"
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
@"main$mapbucket" = internal unnamed_addr global { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, [8 x i8] c"\01\00\00\00\00\00\00\00", [8 x %runtime._string] [%runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer] }
@"main$map" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }, { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }* @"main$mapbucket", i32 0, i32 0, i32 0), i32 1, i8 1, i8 8, i8 0 }
@"main$alloca.2" = internal global i8 1
@"main$alloca.3" = internal global i8 2
@"main$map.4" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 1, i8 1, i8 0 }
@"main$alloca.5" = internal global i8 2
@"main$map.6" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 8, i8 1, i8 0 }
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8*, i8*) local_unnamed_addr
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8*, i8*) local_unnamed_addr
@"main$map" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } }, { [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } }* @"main$mapbucket", i32 0, i32 0, i32 0), i32 1, i8 1, i8 8, i8 0 }
@"main$mapbucket" = internal unnamed_addr global { [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, { i8, [7 x i8] } { i8 1, [7 x i8] zeroinitializer }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } { { [7 x i8]*, [4 x i8] } { [7 x i8]* @main.init.string, [4 x i8] c"\07\00\00\00" }, [56 x i8] zeroinitializer } }
@"main$map.1" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } }, { [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } }* @"main$mapbucket.2", i32 0, i32 0, i32 0), i32 1, i8 1, i8 1, i8 0 }
@"main$mapbucket.2" = internal unnamed_addr global { [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, { i8, [7 x i8] } { i8 1, [7 x i8] zeroinitializer }, { i8, [7 x i8] } { i8 2, [7 x i8] zeroinitializer } }
@"main$map.3" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } }, { [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } }* @"main$mapbucket.4", i32 0, i32 0, i32 0), i32 1, i8 8, i8 1, i8 0 }
@"main$mapbucket.4" = internal unnamed_addr global { [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } } { [8 x i8] c"x\00\00\00\00\00\00\00", i8* null, { { [7 x i8]*, [4 x i8] }, [56 x i8] } { { [7 x i8]*, [4 x i8] } { [7 x i8]* @main.init.string, [4 x i8] c"\07\00\00\00" }, [56 x i8] zeroinitializer }, { i8, [7 x i8] } { i8 2, [7 x i8] zeroinitializer } }
define void @runtime.initAll() unnamed_addr {
entry:
call void @runtime.hashmapBinarySet(%runtime.hashmap* @"main$map.4", i8* @"main$alloca.2", i8* @"main$alloca.3", i8* undef, i8* null)
call void @runtime.hashmapStringSet(%runtime.hashmap* @"main$map.6", i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* @"main$alloca.5", i8* undef, i8* null)
ret void
}
-78
View File
@@ -1,78 +0,0 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i64 }
declare i1 @runtime.typeAssert(i64, %runtime.typecodeID*, i8*, i8*)
declare i1 @runtime.interfaceImplements(i64, i8**)
define i64 @returnsConst() {
ret i64 0
}
define i64 @returnsArg(i64 %arg) {
ret i64 %arg
}
declare i64 @externalCall()
define i64 @externalCallOnly() {
%result = call i64 @externalCall()
ret i64 0
}
define i64 @externalCallAndReturn() {
%result = call i64 @externalCall()
ret i64 %result
}
define i64 @externalCallBranch() {
%result = call i64 @externalCall()
%zero = icmp eq i64 %result, 0
br i1 %zero, label %if.then, label %if.done
if.then:
ret i64 2
if.done:
ret i64 4
}
@cleanGlobalInt = global i64 5
define i64 @readCleanGlobal() {
%global = load i64, i64* @cleanGlobalInt
ret i64 %global
}
@dirtyGlobalInt = global i64 5
define i64 @readDirtyGlobal() {
%global = load i64, i64* @dirtyGlobalInt
ret i64 %global
}
declare i64* @getDirtyPointer()
define void @storeToPointer() {
%ptr = call i64* @getDirtyPointer()
store i64 3, i64* %ptr
ret void
}
@functionPointer = global i64()* null
define i64 @callFunctionPointer() {
%fp = load i64()*, i64()** @functionPointer
%result = call i64 %fp()
ret i64 %result
}
define i1 @callTypeAssert() {
; Note: parameters are not realistic.
%ok = call i1 @runtime.typeAssert(i64 0, %runtime.typecodeID* null, i8* undef, i8* null)
ret i1 %ok
}
define i1 @callInterfaceImplements() {
; Note: parameters are not realistic.
%ok = call i1 @runtime.interfaceImplements(i64 0, i8** null)
ret i1 %ok
}
+4 -1
View File
@@ -1,6 +1,8 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@"main$alloc.1" = internal unnamed_addr constant [6 x i8] c"\05\00{\00\00\04"
declare void @runtime.printuint8(i8) local_unnamed_addr
declare void @runtime.printint16(i16) local_unnamed_addr
@@ -15,6 +17,7 @@ entry:
call void @runtime.printuint8(i8 3)
call void @runtime.printuint8(i8 3)
call void @runtime.printint16(i16 5)
call void @runtime.printint16(i16 5)
%int16SliceDst.val = load i16, i16* bitcast ([6 x i8]* @"main$alloc.1" to i16*)
call void @runtime.printint16(i16 %int16SliceDst.val)
ret void
}
-126
View File
@@ -1,126 +0,0 @@
package interp
import (
"errors"
"tinygo.org/x/go-llvm"
)
// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
var uses []llvm.Value
use := value.FirstUse()
for !use.IsNil() {
uses = append(uses, use.User())
use = use.NextUse()
}
return uses
}
// getStringBytes loads the byte slice of a Go string represented as a
// {ptr, len} pair.
func getStringBytes(strPtr Value, strLen llvm.Value) ([]byte, error) {
if !strLen.IsConstant() {
return nil, errors.New("getStringBytes with a non-constant length")
}
buf := make([]byte, strLen.ZExtValue())
for i := range buf {
gep, err := strPtr.GetElementPtr([]uint32{uint32(i)})
if err != nil {
return nil, err
}
c, err := gep.Load()
if err != nil {
return nil, err
}
buf[i] = byte(c.ZExtValue())
}
return buf, nil
}
// getLLVMIndices converts an []uint32 into an []llvm.Value, for use in
// llvm.ConstGEP.
func getLLVMIndices(int32Type llvm.Type, indices []uint32) []llvm.Value {
llvmIndices := make([]llvm.Value, len(indices))
for i, index := range indices {
llvmIndices[i] = llvm.ConstInt(int32Type, uint64(index), false)
}
return llvmIndices
}
// Return true if this type is a scalar value (integer or floating point), false
// otherwise.
func isScalar(t llvm.Type) bool {
switch t.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return true
default:
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.
result, ok = isZero(v.Operand(0))
if ok {
return
}
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
}
if !v.IsAGlobalValue().IsNil() {
// A global value is never null.
return false, true
}
return false, false // not valid
}
// isZero returns whether the value in v is the integer zero, and whether that
// can be known right now.
func isZero(v llvm.Value) (result bool, ok bool) {
if !v.IsAConstantExpr().IsNil() {
switch v.Opcode() {
case llvm.PtrToInt:
return isPointerNil(v.Operand(0))
}
}
if !v.IsAConstantInt().IsNil() {
val := v.ZExtValue()
return val == 0, true
}
return false, false // not valid
}
// unwrap returns the underlying value, with GEPs removed. This can be useful to
// get the underlying global of a GEP pointer.
func unwrap(value llvm.Value) llvm.Value {
for {
if !value.IsAConstantExpr().IsNil() {
switch value.Opcode() {
case llvm.GetElementPtr:
value = value.Operand(0)
continue
}
} else if !value.IsAGetElementPtrInst().IsNil() {
value = value.Operand(0)
continue
}
break
}
return value
}
-443
View File
@@ -1,443 +0,0 @@
package interp
// This file provides a litte bit of abstraction around LLVM values.
import (
"errors"
"strconv"
"tinygo.org/x/go-llvm"
)
// A Value is a LLVM value with some extra methods attached for easier
// interpretation.
type Value interface {
Value() llvm.Value // returns a LLVM value
Type() llvm.Type // equal to Value().Type()
IsConstant() bool // returns true if this value is a constant value
Load() (llvm.Value, error) // dereference a pointer
Store(llvm.Value) error // store to a pointer
GetElementPtr([]uint32) (Value, error) // returns an interior pointer
String() string // string representation, for debugging
}
// A type that simply wraps a LLVM constant value.
type LocalValue struct {
Eval *Eval
Underlying llvm.Value
}
// Value implements Value by returning the constant value itself.
func (v *LocalValue) Value() llvm.Value {
return v.Underlying
}
func (v *LocalValue) Type() llvm.Type {
return v.Underlying.Type()
}
func (v *LocalValue) IsConstant() bool {
if _, ok := v.Eval.dirtyGlobals[unwrap(v.Underlying)]; ok {
return false
}
return v.Underlying.IsConstant()
}
// Load loads a constant value if this is a constant pointer.
func (v *LocalValue) Load() (llvm.Value, error) {
if !v.Underlying.IsAGlobalVariable().IsNil() {
return v.Underlying.Initializer(), nil
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr:
indices := v.getConstGEPIndices()
if indices[0] != 0 {
return llvm.Value{}, errors.New("invalid GEP")
}
global := v.Eval.getValue(v.Underlying.Operand(0))
agg, err := global.Load()
if err != nil {
return llvm.Value{}, err
}
return llvm.ConstExtractValue(agg, indices[1:]), nil
case llvm.BitCast:
return llvm.Value{}, errors.New("interp: load from a bitcast")
default:
return llvm.Value{}, errors.New("interp: load from a constant")
}
}
// Store stores to the underlying value if the value type is a pointer type,
// otherwise it returns an error.
func (v *LocalValue) Store(value llvm.Value) error {
if !v.Underlying.IsAGlobalVariable().IsNil() {
if !value.IsConstant() {
v.MarkDirty()
v.Eval.builder.CreateStore(value, v.Underlying)
} else {
v.Underlying.SetInitializer(value)
}
return nil
}
if !value.IsConstant() {
v.MarkDirty()
v.Eval.builder.CreateStore(value, v.Underlying)
return nil
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr:
indices := v.getConstGEPIndices()
if indices[0] != 0 {
return errors.New("invalid GEP")
}
global := &LocalValue{v.Eval, v.Underlying.Operand(0)}
agg, err := global.Load()
if err != nil {
return err
}
agg = llvm.ConstInsertValue(agg, value, indices[1:])
return global.Store(agg)
default:
return errors.New("interp: store on a constant")
}
}
// GetElementPtr returns a GEP when the underlying value is of pointer type.
func (v *LocalValue) GetElementPtr(indices []uint32) (Value, error) {
if !v.Underlying.IsAGlobalVariable().IsNil() {
int32Type := v.Underlying.Type().Context().Int32Type()
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
return &LocalValue{v.Eval, gep}, nil
}
if !v.Underlying.IsAConstantExpr().IsNil() {
switch v.Underlying.Opcode() {
case llvm.GetElementPtr, llvm.IntToPtr, llvm.BitCast:
int32Type := v.Underlying.Type().Context().Int32Type()
llvmIndices := getLLVMIndices(int32Type, indices)
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}, nil
}
}
return nil, errors.New("interp: unknown GEP")
}
// stripPointerCasts removes all const bitcasts from pointer values, if there
// are any.
func (v *LocalValue) stripPointerCasts() *LocalValue {
value := v.Underlying
for {
if !value.IsAConstantExpr().IsNil() {
switch value.Opcode() {
case llvm.BitCast:
value = value.Operand(0)
continue
}
}
return &LocalValue{
Eval: v.Eval,
Underlying: value,
}
}
}
func (v *LocalValue) String() string {
isConstant := "false"
if v.IsConstant() {
isConstant = "true"
}
return "&LocalValue{Type: " + v.Type().String() + ", IsConstant: " + isConstant + "}"
}
// getConstGEPIndices returns indices of this constant GEP, if this is a GEP
// instruction. If it is not, the behavior is undefined.
func (v *LocalValue) getConstGEPIndices() []uint32 {
indices := make([]uint32, v.Underlying.OperandsCount()-1)
for i := range indices {
operand := v.Underlying.Operand(i + 1)
indices[i] = uint32(operand.ZExtValue())
}
return indices
}
// 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 *LocalValue) MarkDirty() {
underlying := unwrap(v.Underlying)
if underlying.IsAGlobalVariable().IsNil() {
panic("trying to mark a non-global as dirty")
}
if !v.IsConstant() {
return // already dirty
}
v.Eval.dirtyGlobals[underlying] = struct{}{}
}
// MapValue implements a Go map which is created at compile time and stored as a
// global variable.
type MapValue struct {
Eval *Eval
PkgName string
Underlying llvm.Value
Keys []Value
Values []Value
KeySize int
ValueSize int
KeyType llvm.Type
ValueType llvm.Type
}
func (v *MapValue) newBucket() llvm.Value {
ctx := v.Eval.Mod.Context()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
bucketType := ctx.StructType([]llvm.Type{
llvm.ArrayType(ctx.Int8Type(), 8), // tophash
i8ptrType, // next bucket
llvm.ArrayType(v.KeyType, 8), // key type
llvm.ArrayType(v.ValueType, 8), // value type
}, false)
bucketValue := llvm.ConstNull(bucketType)
bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket")
bucket.SetInitializer(bucketValue)
bucket.SetLinkage(llvm.InternalLinkage)
bucket.SetUnnamedAddr(true)
return bucket
}
// Value returns a global variable which is a pointer to the actual hashmap.
func (v *MapValue) Value() llvm.Value {
if !v.Underlying.IsNil() {
return v.Underlying
}
ctx := v.Eval.Mod.Context()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
var firstBucketGlobal llvm.Value
if len(v.Keys) == 0 {
// there are no buckets
firstBucketGlobal = llvm.ConstPointerNull(i8ptrType)
} else {
// create initial bucket
firstBucketGlobal = v.newBucket()
}
// Insert each key/value pair in the hashmap.
bucketGlobal := firstBucketGlobal
for i, key := range v.Keys {
var keyBuf []byte
llvmKey := key.Value()
llvmValue := v.Values[i].Value()
if key.Type().TypeKind() == llvm.StructTypeKind && key.Type().StructName() == "runtime._string" {
keyPtr := llvm.ConstExtractValue(llvmKey, []uint32{0})
keyLen := llvm.ConstExtractValue(llvmKey, []uint32{1})
keyPtrVal := v.Eval.getValue(keyPtr)
var err error
keyBuf, err = getStringBytes(keyPtrVal, keyLen)
if err != nil {
panic(err) // TODO
}
} else if key.Type().TypeKind() == llvm.IntegerTypeKind {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
n := key.Value().ZExtValue()
for i := range keyBuf {
keyBuf[i] = byte(n)
n >>= 8
}
} else if key.Type().TypeKind() == llvm.ArrayTypeKind &&
key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind &&
key.Type().ElementType().IntTypeWidth() == 8 {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
for i := range keyBuf {
keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue())
}
} else {
panic("interp: map key type not implemented: " + key.Type().String())
}
hash := v.hash(keyBuf)
if i%8 == 0 && i != 0 {
// Bucket is full, create a new one.
newBucketGlobal := v.newBucket()
zero := llvm.ConstInt(ctx.Int32Type(), 0, false)
newBucketPtr := llvm.ConstInBoundsGEP(newBucketGlobal, []llvm.Value{zero})
newBucketPtrCast := llvm.ConstBitCast(newBucketPtr, i8ptrType)
// insert pointer into old bucket
bucket := bucketGlobal.Initializer()
bucket = llvm.ConstInsertValue(bucket, newBucketPtrCast, []uint32{1})
bucketGlobal.SetInitializer(bucket)
// switch to next bucket
bucketGlobal = newBucketGlobal
}
tophashValue := llvm.ConstInt(ctx.Int8Type(), uint64(v.topHash(hash)), false)
bucket := bucketGlobal.Initializer()
bucket = llvm.ConstInsertValue(bucket, tophashValue, []uint32{0, uint32(i % 8)})
bucket = llvm.ConstInsertValue(bucket, llvmKey, []uint32{2, uint32(i % 8)})
bucket = llvm.ConstInsertValue(bucket, llvmValue, []uint32{3, uint32(i % 8)})
bucketGlobal.SetInitializer(bucket)
}
// Create the hashmap itself.
zero := llvm.ConstInt(ctx.Int32Type(), 0, false)
bucketPtr := llvm.ConstInBoundsGEP(firstBucketGlobal, []llvm.Value{zero})
hashmapType := v.Type()
hashmap := llvm.ConstNamedStruct(hashmapType, []llvm.Value{
llvm.ConstPointerNull(llvm.PointerType(hashmapType, 0)), // next
llvm.ConstBitCast(bucketPtr, i8ptrType), // buckets
llvm.ConstInt(hashmapType.StructElementTypes()[2], uint64(len(v.Keys)), false), // count
llvm.ConstInt(ctx.Int8Type(), uint64(v.KeySize), false), // keySize
llvm.ConstInt(ctx.Int8Type(), uint64(v.ValueSize), false), // valueSize
llvm.ConstInt(ctx.Int8Type(), 0, false), // bucketBits
})
// Create a pointer to this hashmap.
hashmapPtr := llvm.AddGlobal(v.Eval.Mod, hashmap.Type(), v.PkgName+"$map")
hashmapPtr.SetInitializer(hashmap)
hashmapPtr.SetLinkage(llvm.InternalLinkage)
hashmapPtr.SetUnnamedAddr(true)
v.Underlying = llvm.ConstInBoundsGEP(hashmapPtr, []llvm.Value{zero})
return v.Underlying
}
// Type returns type runtime.hashmap, which is the actual hashmap type.
func (v *MapValue) Type() llvm.Type {
return v.Eval.Mod.GetTypeByName("runtime.hashmap")
}
func (v *MapValue) IsConstant() bool {
return true // TODO: dirty maps
}
// Load panics: maps are of reference type so cannot be dereferenced.
func (v *MapValue) Load() (llvm.Value, error) {
panic("interp: load from a map")
}
// Store returns an error: maps are of reference type so cannot be stored to.
func (v *MapValue) Store(value llvm.Value) error {
// This must be a bug, but it might be helpful to indicate the location
// anyway.
return errors.New("interp: store on a map")
}
// GetElementPtr panics: maps are of reference type so their (interior)
// addresses cannot be calculated.
func (v *MapValue) GetElementPtr(indices []uint32) (Value, error) {
return nil, errors.New("interp: GEP on a map")
}
// PutString does a map assign operation, assuming that the map is of type
// map[string]T.
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) error {
if !v.Underlying.IsNil() {
return errors.New("map already created")
}
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
}
value, err := valPtr.Load()
if err != nil {
return err
}
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
return errors.New("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
return errors.New("interp: map store value type is inconsistent")
}
}
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
v.KeyType = keyType
key := llvm.ConstNull(keyType)
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
// TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value})
return nil
}
// PutBinary does a map assign operation.
func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) error {
if !v.Underlying.IsNil() {
return errors.New("map already created")
}
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
}
value, err := valPtr.Load()
if err != nil {
return err
}
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
return errors.New("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
return errors.New("interp: map store value type is inconsistent")
}
}
if !keyPtr.Underlying.IsAConstantExpr().IsNil() {
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, err := keyPtr.Load()
if err != nil {
return err
}
if v.KeyType.IsNil() {
v.KeyType = key.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize {
return errors.New("interp: map store key type has the wrong size")
}
} else {
if key.Type() != v.KeyType {
return errors.New("interp: map store key type is inconsistent")
}
}
// TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value})
return nil
}
// Get FNV-1a hash of this string.
//
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
func (v *MapValue) hash(data []byte) uint32 {
var result uint32 = 2166136261 // FNV offset basis
for _, c := range data {
result ^= uint32(c)
result *= 16777619 // FNV prime
}
return result
}
// Get the topmost 8 bits of the hash, without using a special value (like 0).
func (v *MapValue) topHash(hash uint32) uint8 {
tophash := uint8(hash >> 24)
if tophash < 1 {
// 0 means empty slot, so make it bigger.
tophash += 1
}
return tophash
}
func (v *MapValue) String() string {
return "&MapValue{KeySize: " + strconv.Itoa(v.KeySize) + ", ValueSize: " + strconv.Itoa(v.ValueSize) + "}"
}
-271
View File
@@ -1,271 +0,0 @@
package ir
import (
"go/ast"
"go/types"
"sort"
"strings"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// This file provides a wrapper around go/ssa values and adds extra
// functionality to them.
// View on all functions, types, and globals in a program, with analysis
// results.
type Program struct {
Program *ssa.Program
LoaderProgram *loader.Program
mainPkg *ssa.Package
Functions []*Function
functionMap map[*ssa.Function]*Function
}
// Function or method.
type Function struct {
*ssa.Function
LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
inline InlineType // go:inline
}
// Interface type that is at some point used in a type assert (to check whether
// it implements another interface).
type Interface struct {
Num int
Type *types.Interface
}
type InlineType int
// How much to inline.
const (
// Default behavior. The compiler decides for itself whether any given
// function will be inlined. Whether any function is inlined depends on the
// optimization level.
InlineDefault InlineType = iota
// Inline hint, just like the C inline keyword (signalled using
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
InlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
InlineNone
)
// Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program) *Program {
program := lprogram.LoadSSA()
program.Build()
mainPkg := program.ImportedPackage(lprogram.MainPkg().ImportPath)
if mainPkg == nil {
panic("could not find main package")
}
p := &Program{
Program: program,
LoaderProgram: lprogram,
mainPkg: mainPkg,
functionMap: make(map[*ssa.Function]*Function),
}
for _, pkg := range lprogram.Sorted() {
p.AddPackage(program.ImportedPackage(pkg.ImportPath))
}
return p
}
// Add a package to this Program. All packages need to be added first before any
// analysis is done for correct results.
func (p *Program) AddPackage(pkg *ssa.Package) {
memberNames := make([]string, 0)
for name := range pkg.Members {
memberNames = append(memberNames, name)
}
sort.Strings(memberNames)
for _, name := range memberNames {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
p.addFunction(member)
case *ssa.Type:
methods := getAllMethods(pkg.Prog, member.Type())
if !types.IsInterface(member.Type()) {
// named type
for _, method := range methods {
p.addFunction(pkg.Prog.MethodValue(method))
}
}
case *ssa.Global:
// Ignore. Globals are not handled here.
case *ssa.NamedConst:
// Ignore: these are already resolved.
default:
panic("unknown member type: " + member.String())
}
}
}
func (p *Program) addFunction(ssaFn *ssa.Function) {
if _, ok := p.functionMap[ssaFn]; ok {
return
}
f := &Function{Function: ssaFn}
f.parsePragmas()
p.Functions = append(p.Functions, f)
p.functionMap[ssaFn] = f
for _, anon := range ssaFn.AnonFuncs {
p.addFunction(anon)
}
}
// Return true if this package imports "unsafe", false otherwise.
func hasUnsafeImport(pkg *types.Package) bool {
for _, imp := range pkg.Imports() {
if imp == types.Unsafe {
return true
}
}
return false
}
func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
return p.functionMap[ssaFn]
}
func (p *Program) MainPkg() *ssa.Package {
return p.mainPkg
}
// Parse compiler directives in the preceding comments.
func (f *Function) parsePragmas() {
if f.Syntax() == nil {
return
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
for _, comment := range decl.Doc.List {
text := comment.Text
if strings.HasPrefix(text, "//export ") {
// Rewrite '//export' to '//go:export' for compatibility with
// gc.
text = "//go:" + text[2:]
}
if !strings.HasPrefix(text, "//go:") {
continue
}
parts := strings.Fields(text)
switch parts[0] {
case "//go:export":
if len(parts) != 2 {
continue
}
f.linkName = parts[1]
f.exported = true
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
f.module = parts[1]
case "//go:inline":
f.inline = InlineHint
case "//go:noinline":
f.inline = InlineNone
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
f.linkName = parts[2]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
f.nobounds = true
}
}
}
}
}
func (f *Function) IsNoBounds() bool {
return f.nobounds
}
// Return true iff this function is externally visible.
func (f *Function) IsExported() bool {
return f.exported || f.CName() != ""
}
// Return the inline directive of this function.
func (f *Function) Inline() InlineType {
return f.inline
}
// Return the module name if not the default.
func (f *Function) Module() string {
return f.module
}
// Return the link name for this function.
func (f *Function) LinkName() string {
if f.linkName != "" {
return f.linkName
}
if f.Signature.Recv() != nil {
// Method on a defined type (which may be a pointer).
return f.RelString(nil)
} else {
// Bare function.
if name := f.CName(); name != "" {
// Name CGo functions directly.
return name
} else {
return f.RelString(nil)
}
}
}
// Return the name of the C function if this is a CGo wrapper. Otherwise, return
// a zero-length string.
func (f *Function) CName() string {
name := f.Name()
if strings.HasPrefix(name, "_Cfunc_") {
// emitted by `go tool cgo`
return name[len("_Cfunc_"):]
}
if strings.HasPrefix(name, "C.") {
// created by ../loader/cgo.go
return name[2:]
}
return ""
}
// Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ)
methods := make([]*types.Selection, ms.Len())
for i := 0; i < ms.Len(); i++ {
methods[i] = ms.At(i)
}
return methods
}
-149
View File
@@ -1,149 +0,0 @@
package ir
import (
"errors"
"go/types"
"golang.org/x/tools/go/ssa"
)
// This file implements several optimization passes (analysis + transform) to
// optimize code in SSA form before it is compiled to LLVM IR. It is based on
// the IR defined in ir.go.
// Make a readable version of a method signature (including the function name,
// excluding the receiver name). This string is used internally to match
// interfaces and to call the correct method on an interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func MethodSignature(method *types.Func) string {
return method.Name() + signature(method.Type().(*types.Signature))
}
// Make a readable version of a function (pointer) signature.
// Examples:
//
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
s := ""
if sig.Params().Len() == 0 {
s += "()"
} else {
s += "("
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Params().At(i).Type().String()
}
s += ")"
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + sig.Results().At(0).Type().String()
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
s += ", "
}
s += sig.Results().At(i).Type().String()
}
s += ")"
}
return s
}
// Simple pass that removes dead code. This pass makes later analysis passes
// more useful.
func (p *Program) SimpleDCE() error {
// Unmark all functions.
for _, f := range p.Functions {
f.flag = false
}
// Initial set of live functions. Include main.main, *.init and runtime.*
// functions.
main, ok := p.mainPkg.Members["main"].(*ssa.Function)
if !ok {
if p.mainPkg.Members["main"] == nil {
return errors.New("function main is undeclared in the main package")
} else {
return errors.New("cannot declare main - must be func")
}
}
runtimePkg := p.Program.ImportedPackage("runtime")
mathPkg := p.Program.ImportedPackage("math")
taskPkg := p.Program.ImportedPackage("internal/task")
p.GetFunction(main).flag = true
worklist := []*ssa.Function{main}
for _, f := range p.Functions {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
if f.flag {
continue
}
f.flag = true
worklist = append(worklist, f.Function)
}
}
// Mark all called functions recursively.
for len(worklist) != 0 {
f := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, block := range f.Blocks {
for _, instr := range block.Instrs {
if instr, ok := instr.(*ssa.MakeInterface); ok {
for _, sel := range getAllMethods(p.Program, instr.X.Type()) {
fn := p.Program.MethodValue(sel)
callee := p.GetFunction(fn)
if callee == nil {
// TODO: why is this necessary?
p.addFunction(fn)
callee = p.GetFunction(fn)
}
if !callee.flag {
callee.flag = true
worklist = append(worklist, callee.Function)
}
}
}
for _, operand := range instr.Operands(nil) {
if operand == nil || *operand == nil {
continue
}
switch operand := (*operand).(type) {
case *ssa.Function:
f := p.GetFunction(operand)
if f == nil {
// FIXME HACK: this function should have been
// discovered already. It is not for bound methods.
p.addFunction(operand)
f = p.GetFunction(operand)
}
if !f.flag {
f.flag = true
worklist = append(worklist, operand)
}
}
}
}
}
}
// Remove unmarked functions.
livefunctions := []*Function{}
for _, f := range p.Functions {
if f.flag {
livefunctions = append(livefunctions, f)
} else {
delete(p.functionMap, f.Function)
}
}
p.Functions = livefunctions
return nil
}
Submodule
+1
Submodule lib/stm32-svd added at c6b5be976f
+17 -7
View File
@@ -16,7 +16,6 @@ import (
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/tinygo-org/tinygo/cgo"
"github.com/tinygo-org/tinygo/compileopts"
@@ -43,6 +42,7 @@ type Program struct {
type PackageJSON struct {
Dir string
ImportPath string
Name string
ForTest string
// Source files
@@ -51,7 +51,8 @@ type PackageJSON struct {
CFiles []string
// Dependency information
Imports []string
Imports []string
ImportMap map[string]string
// Error information
Error *struct {
@@ -108,10 +109,7 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t
err = cmd.Run()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
}
os.Exit(1)
os.Exit(exitErr.ExitCode())
}
return nil, fmt.Errorf("failed to run `go list`: %s", err)
}
@@ -335,7 +333,16 @@ func (p *Package) Check() error {
// Do typechecking of the package.
checker.Importer = p
typesPkg, err := checker.Check(p.ImportPath, p.program.fset, p.Files, &p.info)
packageName := p.ImportPath
if p.Name == "main" {
// The main package normally has a different import path, such as
// "command-line-arguments" or "./testdata/cgo". Therefore, use the name
// "main" in such a case: this package isn't imported from anywhere.
// This is safe as it isn't possible to import a package with the name
// "main".
packageName = "main"
}
typesPkg, err := checker.Check(packageName, p.program.fset, p.Files, &p.info)
if err != nil {
if err, ok := err.(Errors); ok {
return err
@@ -401,6 +408,9 @@ func (p *Package) Import(to string) (*types.Package, error) {
if to == "unsafe" {
return types.Unsafe, nil
}
if newTo, ok := p.ImportMap[to]; ok && !strings.HasSuffix(newTo, ".test]") {
to = newTo
}
if imported, ok := p.program.Packages[to]; ok {
return imported.Pkg, nil
} else {
+8
View File
@@ -16,3 +16,11 @@ func (p *Program) LoadSSA() *ssa.Program {
return prog
}
// LoadSSA constructs the SSA form of this package.
//
// The program must already be parsed and type-checked with the .Parse() method.
func (p *Package) LoadSSA() *ssa.Package {
prog := ssa.NewProgram(p.program.fset, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug)
return prog.CreatePackage(p.Pkg, p.Files, &p.info, true)
}
+87 -78
View File
@@ -14,16 +14,17 @@ import (
"os/signal"
"path/filepath"
"runtime"
"strconv"
"strings"
"syscall"
"sync/atomic"
"time"
"github.com/mattn/go-colorable"
"github.com/tinygo-org/tinygo/builder"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
"go.bug.st/serial"
@@ -89,6 +90,14 @@ func copyFile(src, dst string) error {
return err
}
// executeCommand is a simple wrapper to exec.Cmd
func executeCommand(options *compileopts.Options, name string, arg ...string) *exec.Cmd {
if options.PrintCommands {
fmt.Printf("%s %s\n ", name, strings.Join(arg, " "))
}
return exec.Command(name, arg...)
}
// Build compiles and links the given package and writes it to outpath.
func Build(pkgName, outpath string, options *compileopts.Options) error {
config, err := builder.NewConfig(options)
@@ -96,7 +105,7 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
return err
}
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
return builder.Build(pkgName, outpath, config, nil, func(result builder.BuildResult) error {
if err := os.Rename(result.Binary, outpath); err != nil {
// Moving failed. Do a file copy.
inf, err := os.Open(result.Binary)
@@ -132,7 +141,7 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
return err
}
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
return builder.Build(pkgName, outpath, config, nil, func(result builder.BuildResult) error {
if testCompileOnly || outpath != "" {
// Write test binary to the specified file name.
if outpath == "" {
@@ -148,7 +157,7 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
}
if len(config.Target.Emulator) == 0 {
// Run directly.
cmd := exec.Command(result.Binary)
cmd := executeCommand(config.Options, result.Binary)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = result.MainDir
@@ -156,10 +165,7 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
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)
os.Exit(err.ExitCode())
}
return &commandError{"failed to run compiled binary", result.Binary, err}
}
@@ -167,7 +173,7 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
} else {
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary)
cmd := exec.Command(config.Target.Emulator[0], args...)
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
buf := &bytes.Buffer{}
w := io.MultiWriter(os.Stdout, buf)
cmd.Stdout = w
@@ -231,7 +237,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return errors.New("unknown flash method: " + flashMethod)
}
return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error {
return builder.Build(pkgName, fileExt, config, func() error {
// do we need port reset to put MCU into bootloader mode?
if config.Target.PortReset == "true" && flashMethod != "openocd" {
if port == "" {
@@ -244,19 +250,21 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
err := touchSerialPortAt1200bps(port)
if err != nil {
return &commandError{"failed to reset port", result.Binary, err}
return &commandError{"failed to reset port", "", err}
}
// give the target MCU a chance to restart into bootloader
time.Sleep(3 * time.Second)
}
return nil
}, func(result builder.BuildResult) error {
// this flashing method copies the binary data to a Mass Storage Device (msd)
switch flashMethod {
case "", "command":
// Create the command.
flashCmd := config.Target.FlashCommand
fileToken := "{" + fileExt[1:] + "}"
flashCmd = strings.Replace(flashCmd, fileToken, result.Binary, -1)
flashCmd = strings.ReplaceAll(flashCmd, fileToken, result.Binary)
if port == "" && strings.Contains(flashCmd, "{port}") {
var err error
@@ -266,7 +274,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
}
}
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
flashCmd = strings.ReplaceAll(flashCmd, "{port}", port)
// Execute the command.
var cmd *exec.Cmd
@@ -276,9 +284,9 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
if len(command) < 2 {
return errors.New("invalid flash command")
}
cmd = exec.Command(command[0], command[1:]...)
cmd = executeCommand(config.Options, command[0], command[1:]...)
default:
cmd = exec.Command("/bin/sh", "-c", flashCmd)
cmd = executeCommand(config.Options, "/bin/sh", "-c", flashCmd)
}
cmd.Stdout = os.Stdout
@@ -292,13 +300,13 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
case "msd":
switch fileExt {
case ".uf2":
err := flashUF2UsingMSD(config.Target.FlashVolume, result.Binary)
err := flashUF2UsingMSD(config.Target.FlashVolume, result.Binary, config.Options)
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
}
return nil
case ".hex":
err := flashHexUsingMSD(config.Target.FlashVolume, result.Binary)
err := flashHexUsingMSD(config.Target.FlashVolume, result.Binary, config.Options)
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
}
@@ -312,7 +320,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return err
}
args = append(args, "-c", "program "+filepath.ToSlash(result.Binary)+" reset exit")
cmd := exec.Command("openocd", args...)
cmd := executeCommand(config.Options, "openocd", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
@@ -342,18 +350,20 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
return errors.New("gdb not configured in the target specification")
}
return builder.Build(pkgName, "", config, func(result builder.BuildResult) error {
return builder.Build(pkgName, "", config, nil, func(result builder.BuildResult) error {
// Find a good way to run GDB.
gdbInterface, openocdInterface := config.Programmer()
switch gdbInterface {
case "msd", "command", "":
if len(config.Target.Emulator) != 0 {
// Assume QEMU as an emulator.
if config.Target.Emulator[0] == "mgba" {
gdbInterface = "mgba"
} else if config.Target.Emulator[0] == "simavr" {
gdbInterface = "simavr"
} else if strings.HasPrefix(config.Target.Emulator[0], "qemu-system-") {
gdbInterface = "qemu"
} else {
// Assume QEMU as an emulator.
gdbInterface = "qemu-user"
}
} else if openocdInterface != "" && config.Target.OpenOCDTarget != "" {
@@ -379,11 +389,11 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
if err != nil {
return err
}
daemon = exec.Command("openocd", args...)
daemon = executeCommand(config.Options, "openocd", args...)
if ocdOutput {
// Make it clear which output is from the daemon.
w := &ColorWriter{
Out: os.Stderr,
Out: colorable.NewColorableStderr(),
Prefix: "openocd: ",
Color: TermColorYellow,
}
@@ -394,11 +404,11 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
gdbCommands = append(gdbCommands, "target remote :2331", "load", "monitor reset halt")
// We need a separate debugging daemon for on-chip debugging.
daemon = exec.Command("JLinkGDBServer", "-device", config.Target.JLinkDevice)
daemon = executeCommand(config.Options, "JLinkGDBServer", "-device", config.Target.JLinkDevice)
if ocdOutput {
// Make it clear which output is from the daemon.
w := &ColorWriter{
Out: os.Stderr,
Out: colorable.NewColorableStderr(),
Prefix: "jlink: ",
Color: TermColorYellow,
}
@@ -410,7 +420,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary, "-s", "-S")
daemon = exec.Command(config.Target.Emulator[0], args...)
daemon = executeCommand(config.Options, config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
case "qemu-user":
@@ -418,7 +428,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
// Run in an emulator.
args := append(config.Target.Emulator[1:], "-g", "1234", result.Binary)
daemon = exec.Command(config.Target.Emulator[0], args...)
daemon = executeCommand(config.Options, config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
case "mgba":
@@ -426,7 +436,15 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary, "-g")
daemon = exec.Command(config.Target.Emulator[0], args...)
daemon = executeCommand(config.Options, config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
case "simavr":
gdbCommands = append(gdbCommands, "target remote :1234")
// Run in an emulator.
args := append(config.Target.Emulator[1:], "-g", result.Binary)
daemon = executeCommand(config.Options, config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
case "msd":
@@ -447,8 +465,15 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
}
defer func() {
daemon.Process.Signal(os.Interrupt)
// Maybe we should send a .Kill() after x seconds?
var stopped uint32
go func() {
time.Sleep(time.Millisecond * 100)
if atomic.LoadUint32(&stopped) == 0 {
daemon.Process.Kill()
}
}()
daemon.Wait()
atomic.StoreUint32(&stopped, 1)
}()
}
@@ -467,7 +492,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
for _, cmd := range gdbCommands {
params = append(params, "-ex", cmd)
}
cmd := exec.Command(config.Target.GDB, params...)
cmd := executeCommand(config.Options, config.Target.GDB, params...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -489,10 +514,10 @@ func Run(pkgName string, options *compileopts.Options) error {
return err
}
return builder.Build(pkgName, ".elf", config, func(result builder.BuildResult) error {
return builder.Build(pkgName, ".elf", config, nil, func(result builder.BuildResult) error {
if len(config.Target.Emulator) == 0 {
// Run directly.
cmd := exec.Command(result.Binary)
cmd := executeCommand(config.Options, result.Binary)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
@@ -507,7 +532,7 @@ func Run(pkgName string, options *compileopts.Options) error {
} else {
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary)
cmd := exec.Command(config.Target.Emulator[0], args...)
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
@@ -550,7 +575,7 @@ func touchSerialPortAt1200bps(port string) (err error) {
const maxMSDRetries = 10
func flashUF2UsingMSD(volume, tmppath string) error {
func flashUF2UsingMSD(volume, tmppath string, options *compileopts.Options) error {
// find standard UF2 info path
var infoPath string
switch runtime.GOOS {
@@ -559,7 +584,7 @@ func flashUF2UsingMSD(volume, tmppath string) error {
case "darwin":
infoPath = "/Volumes/" + volume + "/INFO_UF2.TXT"
case "windows":
path, err := windowsFindUSBDrive(volume)
path, err := windowsFindUSBDrive(volume, options)
if err != nil {
return err
}
@@ -574,7 +599,7 @@ func flashUF2UsingMSD(volume, tmppath string) error {
return moveFile(tmppath, filepath.Dir(d)+"/flash.uf2")
}
func flashHexUsingMSD(volume, tmppath string) error {
func flashHexUsingMSD(volume, tmppath string, options *compileopts.Options) error {
// find expected volume path
var destPath string
switch runtime.GOOS {
@@ -583,7 +608,7 @@ func flashHexUsingMSD(volume, tmppath string) error {
case "darwin":
destPath = "/Volumes/" + volume
case "windows":
path, err := windowsFindUSBDrive(volume)
path, err := windowsFindUSBDrive(volume, options)
if err != nil {
return err
}
@@ -617,8 +642,8 @@ func locateDevice(volume, path string) (string, error) {
return d[0], nil
}
func windowsFindUSBDrive(volume string) (string, error) {
cmd := exec.Command("wmic",
func windowsFindUSBDrive(volume string, options *compileopts.Options) (string, error) {
cmd := executeCommand(options, "wmic",
"PATH", "Win32_LogicalDisk", "WHERE", "VolumeName = '"+volume+"'",
"get", "DeviceID,VolumeName,FileSystem,DriveType")
@@ -640,30 +665,6 @@ func windowsFindUSBDrive(volume string) (string, error) {
return "", errors.New("unable to locate a USB device to be flashed")
}
// 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
}
// getDefaultPort returns the default serial port depending on the operating system.
func getDefaultPort() (port string, err error) {
var portPath string
@@ -774,6 +775,15 @@ func printCompilerError(logln func(...interface{}), err error) {
logln()
}
}
case transform.CoroutinesError:
logln(err.Pos.String() + ": " + err.Msg)
logln("\ntraceback:")
for _, line := range err.Traceback {
logln(line.Name)
if line.Position.IsValid() {
logln("\t" + line.Position.String())
}
}
case loader.Errors:
logln("#", err.Pkg.ImportPath)
for _, err := range err.Errs {
@@ -822,6 +832,7 @@ func main() {
target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
printSize := flag.String("size", "", "print sizes (none, short, full)")
printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines")
printCommands := flag.Bool("x", false, "Print commands")
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
port := flag.String("port", "", "flash port")
@@ -829,7 +840,6 @@ func main() {
cFlags := flag.String("cflags", "", "additional cflags for compiler")
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
wasmAbi := flag.String("wasm-abi", "", "WebAssembly ABI conventions: js (no i64 params) or generic")
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
var flagJSON, flagDeps *bool
if command == "help" || command == "list" {
@@ -870,6 +880,7 @@ func main() {
Debug: !*nodebug,
PrintSizes: *printSize,
PrintStacks: *printStacks,
PrintCommands: *printCommands,
Tags: *tags,
WasmAbi: *wasmAbi,
Programmer: *programmer,
@@ -883,16 +894,9 @@ func main() {
options.LDFlags = strings.Split(*ldFlags, " ")
}
var err error
if options.HeapSize, err = parseSize(*heapSize); err != nil {
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
usage()
os.Exit(1)
}
os.Setenv("CC", "clang -target="+*target)
err = options.Verify()
err := options.Verify()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
usage()
@@ -945,9 +949,17 @@ func main() {
fmt.Fprintf(os.Stderr, "Unknown library: %s\n", name)
os.Exit(1)
}
path, err := lib.Load(*target)
tmpdir, err := ioutil.TempDir("", "tinygo*")
if err != nil {
handleCompilerError(err)
}
defer os.RemoveAll(tmpdir)
path, err := lib.Load(*target, tmpdir)
handleCompilerError(err)
copyFile(path, outpath)
err = copyFile(path, outpath)
if err != nil {
handleCompilerError(err)
}
case "flash", "gdb":
pkgName := filepath.ToSlash(flag.Arg(0))
if command == "flash" {
@@ -1066,10 +1078,7 @@ func main() {
err = cmd.Run()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
}
os.Exit(1)
os.Exit(exitErr.ExitCode())
}
fmt.Fprintln(os.Stderr, "failed to run `go list`:", err)
os.Exit(1)
+14 -2
View File
@@ -58,6 +58,9 @@ func TestCompiler(t *testing.T) {
if runtime.GOOS != "windows" {
t.Run("Host", func(t *testing.T) {
runPlatTests("", matches, t)
if runtime.GOOS == "darwin" {
runTest("testdata/libc/env.go", "", t, []string{"ENV1=VALUE1", "ENV2=VALUE2"}...)
}
})
}
@@ -104,6 +107,7 @@ func TestCompiler(t *testing.T) {
t.Run("WASI", func(t *testing.T) {
runPlatTests("wasi", matches, t)
runTest("testdata/libc/env.go", "wasi", t, []string{"ENV1=VALUE1", "ENV2=VALUE2"}...)
})
}
}
@@ -113,7 +117,6 @@ func runPlatTests(target string, matches []string, t *testing.T) {
for _, path := range matches {
path := path // redefine to avoid race condition
t.Run(filepath.Base(path), func(t *testing.T) {
t.Parallel()
runTest(path, target, t)
@@ -133,7 +136,7 @@ func runBuild(src, out string, opts *compileopts.Options) error {
return Build(src, out, opts)
}
func runTest(path, target string, t *testing.T) {
func runTest(path, target string, t *testing.T, environmentVars ...string) {
// Get the expected output for this test.
txtpath := path[:len(path)-3] + ".txt"
if path[len(path)-1] == os.PathSeparator {
@@ -182,6 +185,7 @@ func runTest(path, target string, t *testing.T) {
ranTooLong := false
if target == "" {
cmd = exec.Command(binary)
cmd.Env = append(cmd.Env, environmentVars...)
} else {
spec, err := compileopts.LoadTarget(target)
if err != nil {
@@ -193,6 +197,14 @@ func runTest(path, target string, t *testing.T) {
args := append(spec.Emulator[1:], binary)
cmd = exec.Command(spec.Emulator[0], args...)
}
if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" {
for _, v := range environmentVars {
cmd.Args = append(cmd.Args, "--env", v)
}
} else {
cmd.Env = append(cmd.Env, environmentVars...)
}
}
stdout := &bytes.Buffer{}
cmd.Stdout = stdout
+7 -2
View File
@@ -24,7 +24,7 @@ call_start_cpu0:
wsr.ps a2
rsync
// Set WINDOWBASE to 1 << WINDOWSTART.
// Set WINDOWSTART to 1 << WINDOWBASE.
rsr.windowbase a2
ssl a2
movi a2, 1
@@ -42,8 +42,13 @@ call_start_cpu0:
wsr.ps a2
rsync
// Enable the FPU (coprocessor 0 so the lowest bit).
movi a2, 1
wsr.cpenable a2
rsync
// Jump to the runtime start function written in Go.
j main
call4 main
.section .text.tinygo_scanCurrentStack
.global tinygo_scanCurrentStack
-57
View File
@@ -1,57 +0,0 @@
// Hand created file. DO NOT DELETE.
// STM32FXXX (except stm32f1xx) bitfield definitions that are not
// auto-generated by gen-device-svd.go
// These apply to the stm32 families that use the MODER, OTYPE amd PUPDR
// registers for managing GPIO functionality.
// Add in other families that use the same settings, e.g. stm32f0xx, etc
// +build stm32,!stm32f103xx
package stm32
// AltFunc represents the alternate function peripherals that can be mapped to
// the GPIO ports. Since these differ by what is supported on the stm32 family
// they are defined in the more specific files
type AltFunc uint8
// Family-wide common post-reset AltFunc. This represents
// normal GPIO operation of the pins
const AF0_SYSTEM AltFunc = 0
const (
// Register values for the chip
// GPIOx_MODER
GPIOModeInput = 0
GPIOModeOutputGeneral = 1
GPIOModeOutputAltFunc = 2
GPIOModeAnalog = 3
// GPIOx_OTYPER
GPIOOutputTypePushPull = 0
GPIOOutputTypeOpenDrain = 1
// GPIOx_OSPEEDR
GPIOSpeedLow = 0
GPIOSpeedMid = 1
GPIOSpeedHigh = 2 // Note: this is also low speed on stm32f0, see RM0091
GPIOSpeedVeryHigh = 3
// GPIOx_PUPDR
GPIOPUPDRFloating = 0
GPIOPUPDRPullUp = 1
GPIOPUPDRPullDown = 2
)
// SPI prescaler values fPCLK / X
const (
SPI_PCLK_2 = 0
SPI_PCLK_4 = 1
SPI_PCLK_8 = 2
SPI_PCLK_16 = 3
SPI_PCLK_32 = 4
SPI_PCLK_64 = 5
SPI_PCLK_128 = 6
SPI_PCLK_256 = 7
)
-88
View File
@@ -1,88 +0,0 @@
// Hand created file. DO NOT DELETE.
// STM32F103XX bitfield definitions that are not auto-generated by gen-device-svd.go
// +build stm32,stm32f103xx
package stm32
const (
// Flash Access Control Register flag values.
FLASH_ACR_LATENCY_0 = 0x00000001
FLASH_ACR_LATENCY_1 = 0x00000002
FLASH_ACR_LATENCY_2 = 0x00000004
// Reset and Clock Control Control Register flag values.
// System Clock source
RCC_CFGR_SW_HSI = 0
RCC_CFGR_SW_HSE = 1
RCC_CFGR_SW_PLL = 2
// Flags for when System Clock source is set.
RCC_CFGR_SWS_HSI = 0x00000000
RCC_CFGR_SWS_HSE = 0x00000004
RCC_CFGR_SWS_PLL = 0x00000008
// Sets PCLK1
RCC_CFGR_PPRE1_DIV_NONE = 0x00000000
RCC_CFGR_PPRE1_DIV_2 = 0x00000400
RCC_CFGR_PPRE1_DIV_4 = 0x00000500
RCC_CFGR_PPRE1_DIV_8 = 0x00000600
RCC_CFGR_PPRE1_DIV_16 = 0x00000700
// Sets PCLK2
RCC_CFGR_PPRE2_DIV_NONE = 0x00000000
RCC_CFGR_PPRE2_DIV_2 = 0x00002000
RCC_CFGR_PPRE2_DIV_4 = 0x00002800
RCC_CFGR_PPRE2_DIV_8 = 0x00003000
RCC_CFGR_PPRE2_DIV_16 = 0x00003800
// Sets PLL multiplier
RCC_CFGR_PLLMUL_2 = 0x00000000
RCC_CFGR_PLLMUL_3 = 0x00040000
RCC_CFGR_PLLMUL_4 = 0x00080000
RCC_CFGR_PLLMUL_5 = 0x000C0000
RCC_CFGR_PLLMUL_6 = 0x00100000
RCC_CFGR_PLLMUL_7 = 0x00140000
RCC_CFGR_PLLMUL_8 = 0x00180000
RCC_CFGR_PLLMUL_9 = 0x001C0000
RCC_CFGR_PLLMUL_10 = 0x00200000
RCC_CFGR_PLLMUL_11 = 0x00240000
RCC_CFGR_PLLMUL_12 = 0x00280000
RCC_CFGR_PLLMUL_13 = 0x002C0000
RCC_CFGR_PLLMUL_14 = 0x00300000
RCC_CFGR_PLLMUL_15 = 0x00340000
RCC_CFGR_PLLMUL_16 = 0x00380000
// RTC clock source
RCC_RTCCLKSource_LSE = 0x00000100
RCC_RTCCLKSource_LSI = 0x00000200
RCC_RTCCLKSource_HSE_Div128 = 0x00000300
// SPI settings
SPI_FirstBit_MSB = 0x0000
SPI_FirstBit_LSB = 0x0080
SPI_BaudRatePrescaler_2 = 0x0000
SPI_BaudRatePrescaler_4 = 0x0008
SPI_BaudRatePrescaler_8 = 0x0010
SPI_BaudRatePrescaler_16 = 0x0018
SPI_BaudRatePrescaler_32 = 0x0020
SPI_BaudRatePrescaler_64 = 0x0028
SPI_BaudRatePrescaler_128 = 0x0030
SPI_BaudRatePrescaler_256 = 0x0038
SPI_Direction_2Lines_FullDuplex = 0x0000
SPI_Direction_2Lines_RxOnly = 0x0400
SPI_Direction_1Line_Rx = 0x8000
SPI_Direction_1Line_Tx = 0xC000
SPI_Mode_Master = 0x0104
SPI_Mode_Slave = 0x0000
SPI_NSS_Soft = 0x0200
SPI_NSS_Hard = 0x0000
SPI_NSSInternalSoft_Set = 0x0100
SPI_NSSInternalSoft_Reset = 0xFEFF
)
@@ -1,28 +0,0 @@
// Hand created file. DO NOT DELETE.
// STM32FXXX (except stm32f1xx) bitfield definitions that are not
// auto-generated by gen-device-svd.go
// +build stm32f4
// Alternate function settings on the stm32f4 series
package stm32
const (
// Alternative peripheral pin functions
// AF0_SYSTEM is defined im the common bitfields package
AF1_TIM1_2 AltFunc = 1
AF2_TIM3_4_5 = 2
AF3_TIM8_9_10_11 = 3
AF4_I2C1_2_3 = 4
AF5_SPI1_SPI2 = 5
AF6_SPI3 = 6
AF7_USART1_2_3 = 7
AF8_USART4_5_6 = 8
AF9_CAN1_CAN2_TIM12_13_14 = 9
AF10_OTG_FS_OTG_HS = 10
AF11_ETH = 11
AF12_FSMC_SDIO_OTG_HS_1 = 12
AF13_DCMI = 13
AF14 = 14
AF15_EVENTOUT = 15
)
+1 -1
View File
@@ -15,7 +15,7 @@ func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
sensor := machine.ADC{machine.ADC2}
sensor.Configure()
sensor.Configure(machine.ADCConfig{})
for {
val := sensor.Get()
+28 -1
View File
@@ -45,6 +45,11 @@ func Pause() {
currentTask.state.pause()
}
//export tinygo_pause
func pause() {
Pause()
}
// Resume the task until it pauses or completes.
// This may only be called from the scheduler.
func (t *Task) Resume() {
@@ -58,10 +63,32 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
// Create a stack.
stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0)))
// Set up the stack canary, a random number that should be checked when
// switching from the task back to the scheduler. The stack canary pointer
// points to the first word of the stack. If it has changed between now and
// the next stack switch, there was a stack overflow.
s.canaryPtr = &stack[0]
*s.canaryPtr = stackCanary
// Get a pointer to the top of the stack, where the initial register values
// are stored. They will be popped off the stack on the first stack switch
// to the goroutine, and will start running tinygo_startTask (this setup
// happens in archInit).
r := (*calleeSavedRegs)(unsafe.Pointer(&stack[uintptr(len(stack))-(unsafe.Sizeof(calleeSavedRegs{})/unsafe.Sizeof(uintptr(0)))]))
// Invoke architecture-specific initialization.
s.archInit(stack, fn, args)
s.archInit(r, fn, args)
}
//export tinygo_swapTask
func swapTask(oldStack uintptr, newStack *uintptr)
// startTask is a small wrapper function that sets up the first (and only)
// argument to the new goroutine and makes sure it is exited when the goroutine
// finishes.
//go:extern tinygo_startTask
var startTask [0]uint8
//go:linkname runqueuePushBack runtime.runqueuePushBack
func runqueuePushBack(*Task)
+99
View File
@@ -0,0 +1,99 @@
.section .bss.tinygo_systemStack
.global tinygo_systemStack
.type tinygo_systemStack, %object
tinygo_systemStack:
.short 0
.section .text.tinygo_startTask
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, r2r3 contain the pc of the to-be-started function and
// r4r5 contain the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
movw r24, r4
// Branch to the "goroutine start" function. Note that the Z register is
// call-clobbered, so does not need to be restored after use.
movw Z, r2
icall
// After return, exit this goroutine. This is a tail call.
#if __AVR_ARCH__ == 2 || __AVR_ARCH__ == 25
// Small memory devices (8kB flash) that do not have the long call
// instruction availble will need to use rcall instead.
// Note that they will probably not be able to run more than the main
// goroutine anyway, but this file is compiled for all AVRs so it needs to
// compile at least.
rcall tinygo_pause
#else
// Other devices can (and must) use the regular call instruction.
call tinygo_pause
#endif
.global tinygo_swapTask
.type tinygo_swapTask, %function
tinygo_swapTask:
// This function gets the following parameters:
// r24:r25 = newStack uintptr
// r22:r23 = oldStack *uintptr
// Save all call-saved registers:
// https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers
push r29 // Y
push r28 // Y
push r17
push r16
push r15
push r14
push r13
push r12
push r11
push r10
push r9
push r8
push r7
push r6
push r5
push r4
push r3
push r2
// Save the current stack pointer in oldStack.
in r2, 0x3d; SPL
in r3, 0x3e; SPH
movw Y, r22
std Y+0, r2
std Y+1, r3
// Switch to the new stack pointer.
out 0x3d, r24; SPL
out 0x3e, r25; SPH
// Load saved register from the new stack.
pop r2
pop r3
pop r4
pop r5
pop r6
pop r7
pop r8
pop r9
pop r10
pop r11
pop r12
pop r13
pop r14
pop r15
pop r16
pop r17
pop r28 // Y
pop r29 // Y
// Return into the new task, as if tinygo_swapTask was a regular call.
ret
+15 -34
View File
@@ -4,9 +4,12 @@ package task
import "unsafe"
//go:extern tinygo_systemStack
var systemStack uintptr
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see scheduler_avr.S that relies on the
// exact layout of this struct.
// switching between tasks. Also see task_stack_avr.S that relies on the exact
// layout of this struct.
//
// https://gcc.gnu.org/wiki/avr-gcc#Call-Saved_Registers
type calleeSavedRegs struct {
@@ -23,34 +26,15 @@ type calleeSavedRegs struct {
pc uintptr
}
// registers gets a pointer to the registers stored at the top of the stack.
func (s *state) registers() *calleeSavedRegs {
return (*calleeSavedRegs)(unsafe.Pointer(s.sp + 1))
}
// startTask is a small wrapper function that sets up the first (and only)
// argument to the new goroutine and makes sure it is exited when the goroutine
// finishes.
//go:extern tinygo_startTask
var startTask [0]uint8
// archInit runs architecture-specific setup for the goroutine startup.
// Note: adding //go:noinline to work around an AVR backend bug.
//go:noinline
func (s *state) archInit(stack []uintptr, fn uintptr, args unsafe.Pointer) {
// Set up the stack canary, a random number that should be checked when
// switching from the task back to the scheduler. The stack canary pointer
// points to the first word of the stack. If it has changed between now and
// the next stack switch, there was a stack overflow.
s.canaryPtr = &stack[0]
*s.canaryPtr = stackCanary
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
// Store the initial sp for the startTask function (implemented in assembly).
s.sp = uintptr(unsafe.Pointer(&stack[uintptr(len(stack))-(unsafe.Sizeof(calleeSavedRegs{})/unsafe.Sizeof(uintptr(0)))])) - 1
s.sp = uintptr(unsafe.Pointer(r)) - 1
// Initialize the registers.
// These will be popped off of the stack on the first resume of the goroutine.
r := s.registers()
// Start the function at tinygo_startTask.
startTask := uintptr(unsafe.Pointer(&startTask))
@@ -67,20 +51,17 @@ func (s *state) archInit(stack []uintptr, fn uintptr, args unsafe.Pointer) {
}
func (s *state) resume() {
switchToTask(s.sp)
swapTask(s.sp, &systemStack)
}
//export tinygo_switchToTask
func switchToTask(uintptr)
//export tinygo_switchToScheduler
func switchToScheduler(*uintptr)
func (s *state) pause() {
switchToScheduler(&s.sp)
newStack := systemStack
systemStack = 0
swapTask(newStack, &s.sp)
}
//export tinygo_pause
func pause() {
Pause()
// SystemStack returns the system stack pointer when called from a task stack.
// When called from the system stack, it returns 0.
func SystemStack() uintptr {
return systemStack
}
@@ -30,17 +30,6 @@ tinygo_startTask:
.cfi_endproc
.size tinygo_startTask, .-tinygo_startTask
.section .text.tinygo_getSystemStackPointer
.global tinygo_getSystemStackPointer
.type tinygo_getSystemStackPointer, %function
tinygo_getSystemStackPointer:
.cfi_startproc
// The system stack pointer is always stored in the MSP register.
mrs r0, MSP
bx lr
.cfi_endproc
.size tinygo_getSystemStackPointer, .-tinygo_getSystemStackPointer
.section .text.tinygo_switchToScheduler
.global tinygo_switchToScheduler
.type tinygo_switchToScheduler, %function
+12 -27
View File
@@ -2,10 +2,13 @@
package task
import "unsafe"
import (
"device/arm"
"unsafe"
)
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see scheduler_cortexm.S that relies on the
// switching between tasks. Also see task_stack_cortexm.S that relies on the
// exact layout of this struct.
type calleeSavedRegs struct {
r4 uintptr
@@ -20,34 +23,15 @@ type calleeSavedRegs struct {
pc uintptr
}
// registers gets a pointer to the registers stored at the top of the stack.
func (s *state) registers() *calleeSavedRegs {
return (*calleeSavedRegs)(unsafe.Pointer(s.sp))
}
// startTask is a small wrapper function that sets up the first (and only)
// argument to the new goroutine and makes sure it is exited when the goroutine
// finishes.
//go:extern tinygo_startTask
var startTask [0]uint8
// archInit runs architecture-specific setup for the goroutine startup.
func (s *state) archInit(stack []uintptr, fn uintptr, args unsafe.Pointer) {
// Set up the stack canary, a random number that should be checked when
// switching from the task back to the scheduler. The stack canary pointer
// points to the first word of the stack. If it has changed between now and
// the next stack switch, there was a stack overflow.
s.canaryPtr = &stack[0]
*s.canaryPtr = stackCanary
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
// Store the initial sp for the startTask function (implemented in assembly).
s.sp = uintptr(unsafe.Pointer(&stack[uintptr(len(stack))-(unsafe.Sizeof(calleeSavedRegs{})/unsafe.Sizeof(uintptr(0)))]))
s.sp = uintptr(unsafe.Pointer(r))
// Initialize the registers.
// These will be popped off of the stack on the first resume of the goroutine.
r := s.registers()
// Start the function at tinygo_startTask (defined in src/runtime/scheduler_cortexm.S).
// Start the function at tinygo_startTask (defined in src/internal/task/task_stack_cortexm.S).
// This assembly code calls a function (passed in r4) with a single argument (passed in r5).
// After the function returns, it calls Pause().
r.pc = uintptr(unsafe.Pointer(&startTask))
@@ -75,7 +59,8 @@ func (s *state) pause() {
switchToScheduler(&s.sp)
}
//export tinygo_pause
func pause() {
Pause()
// SystemStack returns the system stack pointer. On Cortex-M, it is always
// available.
func SystemStack() uintptr {
return arm.AsmFull("mrs {}, MSP", nil)
}
+86
View File
@@ -0,0 +1,86 @@
.section .text.tinygo_startTask,"ax",@progbits
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
// Small assembly stub for starting a goroutine. This already runs on the
// new stack, control reaches this function after returning from the initial
// tinygo_swapTask below (the retw.n instruction).
//
// The stack was set up in such a way that it looks as if this function was
// paused using tinygo_swapTask by setting up the parent register window and
// return pointer as a call4 instruction - except such a call never took
// place. Instead, the stack pointer is switched to the new stack after all
// live-but-invisible registers have been flushed to the stack. This means
// that all registers as present in tinygo_swapTask are moved four up (a2 in
// tinygo_swapTask is a6 in this function). We don't use any of those
// registers however. Instead, the retw.n instruction will load them through
// an underflow exception from the stack which means we get a0-a3 as defined
// in task_stack_esp32.go.
// Branch to the "goroutine start" function. The first (and only) parameter
// is stored in a2, but has to be moved to a6 to make it appear as a2 in the
// goroutine start function (due to changing the register window by four
// with callx4).
mov.n a6, a2
callx4 a3
// After return, exit this goroutine. This call never returns.
call4 tinygo_pause
.section .text.tinygo_swapTask,"ax",@progbits
.global tinygo_swapTask
.type tinygo_swapTask, %function
tinygo_swapTask:
// This function gets the following parameters:
// a2 = newStack uintptr
// a3 = oldStack *uintptr
// Reserve 32 bytes on the stack. It really needs to be 32 bytes, with 16
// extra at the bottom to adhere to the ABI.
entry sp, 32
// Disable interrupts while flushing registers. This is necessary because
// interrupts might want to use the stack pointer (at a2) which will be some
// arbitrary register while registers are flushed.
rsil a4, 3 // XCHAL_EXCM_LEVEL
// Flush all unsaved registers to the stack.
// This trick has been borrowed from the Zephyr project:
// https://github.com/zephyrproject-rtos/zephyr/blob/d79b003758/arch/xtensa/include/xtensa-asm2-s.h#L17
and a12, a12, a12
rotw 3
and a12, a12, a12
rotw 3
and a12, a12, a12
rotw 3
and a12, a12, a12
rotw 3
and a12, a12, a12
rotw 4
// Restore interrupts.
wsr.ps a4
// At this point, the following is true:
// WindowStart == 1 << WindowBase
// Therefore, we don't need to do this manually.
// It also means that the stack pointer can now be safely modified.
// Save a0, which stores the return address and the parent register window
// in the upper two bits.
s32i.n a0, sp, 0
// Save the current stack pointer in oldStack.
s32i.n sp, a3, 0
// Switch to the new stack pointer (newStack).
mov.n sp, a2
// Load a0, which is the previous return addres from before the previous
// switch or the constructed return address to tinygo_startTask. This
// register also stores the parent register window.
l32i.n a0, sp, 0
// Return into the new stack. This instruction will trigger a window
// underflow, reloading the saved registers from the stack.
retw.n
+76
View File
@@ -0,0 +1,76 @@
// +build scheduler.tasks,esp32
package task
// The windowed ABI (used on the ESP32) is as follows:
// a0: return address (link register)
// a1: stack pointer (must be 16-byte aligned)
// a2-a7: incoming arguments
// a7: stack frame pointer (optional, normally unused in TinyGo)
// Sources:
// http://cholla.mmto.org/esp8266/xtensa.html
// https://0x04.net/~mwk/doc/xtensa.pdf
import (
"unsafe"
)
var systemStack uintptr
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see task_stack_esp8266.S that relies on the
// exact layout of this struct.
type calleeSavedRegs struct {
// Registers in the register window of tinygo_startTask.
a0 uintptr
a1 uintptr
a2 uintptr
a3 uintptr
// Locals that can be used by tinygo_swapTask.
// The first field is the a0 loaded in tinygo_swapTask, the rest is unused.
locals [4]uintptr
}
// archInit runs architecture-specific setup for the goroutine startup.
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
// Store the stack pointer for the tinygo_swapTask function (implemented in
// assembly). It needs to point to the locals field instead of a0 so that
// the retw.n at the end of tinygo_swapTask will return into
// tinygo_startTask with a0-a3 loaded (using the register window mechanism).
s.sp = uintptr(unsafe.Pointer(&r.locals[0]))
// Start the goroutine at tinygo_startTask (defined in
// src/internal/task/task_stack_esp32.S). The topmost two bits are not part
// of the address but instead store the register window of the caller.
// In this case there is no caller, instead we set up the return address as
// if tinygo_startTask called tinygo_swapTask with a call4 instruction.
r.locals[0] = uintptr(unsafe.Pointer(&startTask))&^(3<<30) | (1 << 30)
// Set up the stack pointer inside tinygo_startTask.
// Unlike most calling conventions, the windowed ABI actually saves the
// stack pointer on the stack to make register windowing work.
r.a1 = uintptr(unsafe.Pointer(r)) + 32
// Store the function pointer and the (only) parameter on the stack in a
// location that will be reloaded into registers when doing the
// pseudo-return to tinygo_startTask using the register window mechanism.
r.a3 = fn
r.a2 = uintptr(args)
}
func (s *state) resume() {
swapTask(s.sp, &systemStack)
}
func (s *state) pause() {
newStack := systemStack
systemStack = 0
swapTask(newStack, &s.sp)
}
// SystemStack returns the system stack pointer when called from a task stack.
// When called from the system stack, it returns 0.
func SystemStack() uintptr {
return systemStack
}
+54
View File
@@ -0,0 +1,54 @@
.section .text.tinygo_startTask,"ax",@progbits
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, r4 contains the pc of the to-be-started function and r5
// contains the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
mov.n a2, a13
// Branch to the "goroutine start" function.
callx0 a12
// After return, exit this goroutine. This is a tail call.
call0 tinygo_pause
.size tinygo_startTask, .-tinygo_startTask
.global tinygo_swapTask
.type tinygo_swapTask, %function
tinygo_swapTask:
// This function gets the following parameters:
// a2 = newStack uintptr
// a3 = oldStack *uintptr
// Note:
// a0 is the return address
// a1 is the stack pointer (sp)
// Save all callee-saved registers:
addi sp, sp, -20
s32i.n a12, sp, 0
s32i.n a13, sp, 4
s32i.n a14, sp, 8
s32i.n a15, sp, 12
s32i.n a0, sp, 16
// Save the current stack pointer in oldStack.
s32i.n sp, a3, 0
// Switch to the new stack pointer.
mov.n sp, a2
// Load state from new task and branch to the previous position in the
// program.
l32i.n a12, sp, 0
l32i.n a13, sp, 4
l32i.n a14, sp, 8
l32i.n a15, sp, 12
l32i.n a0, sp, 16
addi sp, sp, 20
ret.n
+70
View File
@@ -0,0 +1,70 @@
// +build scheduler.tasks,esp8266
package task
// Stack switch implementation for the ESP8266, which does not use the windowed
// ABI of Xtensa. Registers are assigned as follows:
// a0: return address (link register)
// a1: stack pointer (must be 16-byte aligned)
// a2-a7: incoming arguments
// a8: static chain (unused)
// a12-a15: callee-saved
// a15: stack frame pointer (optional, unused)
// Sources:
// http://cholla.mmto.org/esp8266/xtensa.html
// https://0x04.net/~mwk/doc/xtensa.pdf
import "unsafe"
var systemStack uintptr
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see task_stack_esp8266.S that relies on the
// exact layout of this struct.
type calleeSavedRegs struct {
a12 uintptr
a13 uintptr
a14 uintptr
a15 uintptr
pc uintptr // also link register or r0
}
// archInit runs architecture-specific setup for the goroutine startup.
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
// Store the initial sp for the startTask function (implemented in assembly).
s.sp = uintptr(unsafe.Pointer(r))
// Initialize the registers.
// These will be popped off of the stack on the first resume of the goroutine.
// Start the function at tinygo_startTask (defined in
// src/internal/task/task_stack_esp8266.S).
// This assembly code calls a function (passed in a12) with a single argument
// (passed in a13). After the function returns, it calls Pause().
r.pc = uintptr(unsafe.Pointer(&startTask))
// Pass the function to call in a12.
// This function is a compiler-generated wrapper which loads arguments out of a struct pointer.
// See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information.
r.a12 = fn
// Pass the pointer to the arguments struct in a13.
r.a13 = uintptr(args)
}
func (s *state) resume() {
swapTask(s.sp, &systemStack)
}
func (s *state) pause() {
newStack := systemStack
systemStack = 0
swapTask(newStack, &s.sp)
}
// SystemStack returns the system stack pointer when called from a task stack.
// When called from the system stack, it returns 0.
func SystemStack() uintptr {
return systemStack
}
+12
View File
@@ -0,0 +1,12 @@
package machine
// Hardware abstraction layer for the analog-to-digital conversion (ADC)
// peripheral.
// ADCConfig holds ADC configuration parameters. If left unspecified, the zero
// value of each parameter will use the peripheral's default settings.
type ADCConfig struct {
Reference uint32 // analog reference voltage (AREF) in millivolts
Resolution uint32 // number of bits for a single conversion (e.g., 8, 10, 12)
Samples uint32 // number of samples for a single conversion (e.g., 4, 8, 16, 32)
}
+91
View File
@@ -0,0 +1,91 @@
// +build arduino_mkr1000
// This contains the pin mappings for the Arduino MKR1000 board.
//
// For more information, see: https://store.arduino.cc/usa/arduino-mkr1000-with-headers-mounted
//
package machine
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0x07738135
// GPIO Pins
const (
RX0 Pin = PB23 // UART2 RX
TX1 Pin = PB22 // UART2 TX
D0 Pin = PA22 // PWM available
D1 Pin = PA23 // PWM available
D2 Pin = PA10 // PWM available
D3 Pin = PA11 // PWM available
D4 Pin = PB10 // PWM available
D5 Pin = PB11 // PWM available
D6 Pin = PA20 // PWM available
D7 Pin = PA21 // PWM available
D8 Pin = PA16 // PWM available
D9 Pin = PA17
D10 Pin = PA19 // PWM available
D11 Pin = PA08 // SDA
D12 Pin = PA09 // PWM available, SCL
D13 Pin = PB23 // RX
D14 Pin = PB22 // TX
)
// Analog pins
const (
A0 Pin = PA02 // ADC0/AIN[0]
A1 Pin = PB02 // AIN[10]
A2 Pin = PB03 // AIN[11]
A3 Pin = PA04 // AIN[04]
A4 Pin = PA05 // AIN[05]
A5 Pin = PA06 // AIN[06]
A6 Pin = PA07 // AIN[07]
)
const (
LED = D6
)
// UART0 aka USBCDC pins
const (
USBCDC_DM_PIN Pin = PA24
USBCDC_DP_PIN Pin = PA25
)
// UART1 pins
const (
UART_TX_PIN Pin = PB22
UART_RX_PIN Pin = PB23
)
// I2C pins
const (
SDA_PIN Pin = D11 // SDA
SCL_PIN Pin = D12 // SCL
)
// SPI pins
const (
SPI0_SCK_PIN Pin = D9 // SCK: S1
SPI0_SDO_PIN Pin = D8 // SDO: S1
SPI0_SDI_PIN Pin = D10 // SDI: S1
)
// I2S pins
const (
I2S_SCK_PIN Pin = PA10
I2S_SD_PIN Pin = PA07
I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33.
)
// USB CDC identifiers
const (
usb_STRING_PRODUCT = "Arduino MKR1000"
usb_STRING_MANUFACTURER = "Arduino"
)
var (
usb_VID uint16 = 0x2341
usb_PID uint16 = 0x804e
)

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