Compare commits

...

430 Commits

Author SHA1 Message Date
BCG 6b6b2bfc59 Adding extra files for testing multiline, globbing, etc. 2021-07-23 17:07:25 -04:00
BCG 778c79cc85 Started parsing of //go:embed pragmas 2021-07-19 20:12:44 -04:00
Ayke van Laethem b40703e986 wasm: override dlmalloc heap implementation from wasi-libc
These two heaps conflict with each other, so that if any function uses
the dlmalloc heap implementation it will eventually result in memory
corruption.

This commit fixes this by implementing all heap-related functions. This
overrides the functions that are implemented in wasi-libc. That's why
all of them are implemented (even if they just panic): to make sure no
program accidentally uses the wrong one.
2021-07-15 00:13:17 +02:00
Ayke van Laethem 00ea0b1d57 build: list libraries at the end of the linker command
Static libraries should be added at the end of the linker command, after
all object files. If that isn't done, that's _usually_ not a problem,
unless there are duplicate symbols. In that case, weird dependency
issues can arise. To solve that, object files (that may include symbols
to override symbols in the library) should be listed first on the
command line and then the static libraries should be listed.

This fixes an issue with overriding some symbols in wasi-libc.
2021-07-15 00:13:17 +02:00
Ayke van Laethem efa0410075 interp: fix bug in compiler-time/run-time package initializers
Make sure that if a package initializer cannot be run, later package
initializers won't try to access any global variables touched by the
uninterpretable package initializer.
2021-07-14 22:33:32 +02:00
Ayke van Laethem 607d824211 interp: keep reverted package initializers in order
Previously, a package initializer that could not be reverted correctly
would be called at runtime. But the initializer would be called in the
wrong order: after later packages are initialized.

This commit fixes this oversight and adds a test to verify the new
behavior.
2021-07-14 22:33:32 +02:00
Ayke van Laethem 8cc7c6d572 interp: populate Inst field in interp.Error
It is used in the main package but wasn't actually set anywhere.
2021-07-14 22:33:32 +02:00
Ayke van Laethem cdba4fa8cc interp: don't ignore array indices for untyped objects
This fixes https://github.com/tinygo-org/tinygo/issues/1884.
My original plan to fix this was much more complicated, but then I
realized that the output type doesn't matter anyway and I can simply
cast the type to an *i8 and perform a GEP on that pointer.
2021-07-14 07:55:05 +02:00
Ayke van Laethem 0565b7c0e0 cortexm: fix stack overflow because of unaligned stacks
On ARM, the stack has to be aligned to 8 bytes on function calls, but
not necessarily within a function. Leaf functions can take advantage of
this by not keeping the stack aligned so they can avoid pushing one
register. However, because regular functions might expect an aligned
stack, the interrupt controller will forcibly re-align the stack when an
interrupt happens in such a leaf function (controlled by the STKALIGN
flag, defaults to on). This means that stack size calculation (as used
in TinyGo) needs to make sure this extra space for stack re-alignment is
available.

This commit fixes this by aligning the stack size that will be used for
new goroutines.

Additionally, it increases the stack canary size from 4 to 8 bytes, to
keep the stack aligned. This is not strictly necessary but is required
by the AAPCS so let's do it anyway just to be sure.
2021-07-07 09:25:47 +02:00
soypat 444dded92c move xtoi2 to parse.go 2021-07-02 18:49:14 +02:00
Patricio Whittingslow 42785e08e8 add MAC address implementation to net 2021-07-02 18:49:14 +02:00
sago35 2d633e3a28 version: update TinyGo version to 0.20.0-dev 2021-07-02 18:47:30 +02:00
Ayke van Laethem 64d048c47c main: release version 0.19.0 2021-06-30 20:05:10 +02:00
sago35 c8e231bc0b targets: add serial and serial-port key to JSON files for seeed boards 2021-06-29 09:07:02 +02:00
sago35 b00cfc001e targets: add serial and serial-port key to JSON files for adafruit boards 2021-06-29 09:07:02 +02:00
sago35 e127ceac67 machine/feather-nrf52840-sense: fix msd-volume-name 2021-06-28 11:38:10 +02:00
sago35 e5453ebe27 machine/feather-nrf52840-sense: add board definition for Adafruit Feather nRF52840 Sense 2021-06-26 15:37:17 +02:00
deadprogram 0e267dd230 targets: add serial key to JSON files for newly added rp2040 boards, and also nano-33-ble board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-06-25 22:06:16 +02:00
Ayke van Laethem 96e863f0f3 all: add a flag to the command line to select the serial implementation
This can be very useful for some purposes:

  * It makes it possible to disable the UART in cases where it is not
    needed or needs to be disabled to conserve power.
  * It makes it possible to disable the serial output to reduce code
    size, which may be important for some chips. Sometimes, a few kB can
    be saved this way.
  * It makes it possible to override the default, for example you might
    want to use an actual UART to debug the USB-CDC implementation.

It also lowers the dependency on having machine.Serial defined, which is
often not defined when targeting a chip. Eventually, we might want to
make it possible to write `-target=nrf52` or `-target=atmega328p` for
example to target the chip itself with no board specific assumptions.

The defaults don't change. I checked this by running `make smoketest`
before and after and comparing the results.
2021-06-25 17:58:39 +02:00
Ayke van Laethem 75298bb84b os: implement process related functions
This commit implements various process related functions like
os.Getuid() and os.Getpid(). It also implements or improves this support
in the syscall package if it isn't available yet.
2021-06-25 16:14:47 +02:00
Ayke van Laethem e65592599c compiler: implement syscall.rawSyscallNoError in inline assembly
This makes it possible to call syscall.Getpid() on Linux, for example.
These syscalls never return an error so don't need any error checking.
2021-06-25 16:14:47 +02:00
Yurii Soldak e02f308d43 rp2040: fix for nano-rp2040 board 2021-06-24 17:34:14 +02:00
Yurii Soldak bfe3f68647 smoke&readme: add missing boards 2021-06-24 17:34:14 +02:00
Ayke van Laethem 2bb70812a8 compiler: add function and global section pragmas
This patch adds a new pragma for functions and globals to set the
section name. This can be useful to place a function or global in a
special device specific section, for example:

  * Functions may be placed in RAM to make them run faster, or in flash
    (if RAM is the default) to not let them take up RAM.
  * DMA memory may only be placed in a special memory area.
  * Some RAM may be faster than other RAM, and some globals may be
    performance critical thus placing them in this special RAM area can
    help.
  * Some (large) global variables may need to be placed in external RAM,
    which can be done by placing them in a special section.

To use it, you have to place a function or global in a special section,
for example:

    //go:section .externalram
    var externalRAMBuffer [1024]byte

This can then be placed in a special section of the linker script, for
example something like this:

    .bss.extram (NOLOAD) : {
        *(.externalram)
    } > ERAM
2021-06-24 15:00:30 +02:00
Ayke van Laethem 293f4ea7bc compiler: add tests for pragmas
These pragmas weren't really tested anywhere, except that some code
might break if they are not properly applied.

These tests make it easy to see they work correctly and also provide a
logical place to add new pragma tests.

I've also made a slight change to how functions and globals are created:
with the change they're also created in the IR even if they're not
referenced. This makes testing easier.
2021-06-24 15:00:30 +02:00
Ayke van Laethem c3032660c9 wasi: remove wasm build tag
The wasm build tag together with GOARCH=arm was causing problems in the
internal/cpu package. In general, I think having two architecture build
tag will only cause problems (in this case, wasm and arm) so I've
removed the wasm build tag and replaced it with tinygo.wasm.

This is similar to the tinygo.riscv build tag, which is used for older
Go versions that don't yet have RISC-V support in the standard library
(and therefore pretend to be GOARCH=arm instead).
2021-06-22 09:03:23 +02:00
Ayke van Laethem d94f42f6e2 crypto/rand: replace this package with a TinyGo version
This package provides access to an operating system resource
(cryptographic numbers) and so needs to be replaced with a TinyGo
version that does this in a different way.

I've made the following choices while adding this feature:

  - I'm using the getentropy call whenever possible (most POSIX like
    systems), because it is easier to use and more reliable. Linux is
    the exception: it only added getentropy relatively recently.
  - I've left bare-metal implementations to a future patch. This because
    it's hard to reliably get cryptographically secure random numbers on
    embedded devices: most devices do not have a hardware PRNG for this
    purpose.
2021-06-21 18:22:31 +02:00
Ayke van Laethem d8ac7ccaae interp: fix a bug in pointer cast workaround
This was triggered by the following code:

    var smallPrimesProduct = new(big.Int).SetUint64(16294579238595022365)

It is part of the new TinyGo version of the crypto/rand package.
2021-06-21 18:22:31 +02:00
Federico G. Schwindt 64058c3efb net: os: add more stubs for 1.15
Fix importing net/http.
2021-06-21 16:21:44 +02:00
Ayke van Laethem e107efa63f main: detect specific serial port IDs based on USB vid/pid
This makes it possible to flash a board even when there are multiple
different kinds of boards attached, e.g. an Arduino Uno and a Circuit
Playground Express. You can find the VID/PID pair in several ways:

 1. By running `lsusb` before and after attaching the board and looking
    at the new USB device.
 2. By grepping for `usb_PID` and `usb_VID` in the TinyGo source code.
 3. By checking the Arduino IDE boards.txt from the vendor.

Note that one board may have multiple VID/PID pairs:

  * The bootloader and main program may have a different PID, so far
    I've seen that the main program generally has the bootloader PID
    with 0x8000 added.
  * The software running on the board may have an erroneous PID, for
    example from a different board. I've seen this happen a few times.
  * A single board may have had some revisions which changed the PID.
    This is particularly true for the Arduino Uno.

As a fallback, if the given VID/PID pair isn't found, the whole set of
serial ports will be used.

There are many boards which I haven't included yet simply because I
couldn't test them.
2021-06-19 16:45:56 +02:00
Kenneth Bell 8e33f1c9eb rp2040: support Adafruit Feather RP2040 2021-06-19 12:34:40 +02:00
Ayke van Laethem 1913cb76a5 cortexm: bump default stack size to 2048 bytes
Previously it was 1024 bytes, which occasionally ran into a stack
overflow. I hope that 2048 bytes will be enough for most purposes.

I've also removed some 2048-byte stack size settings in JSON files,
which are unnecessary now that the parent (cortex-m.json) sets them.
2021-06-18 13:23:50 +02:00
Ayke van Laethem cd628bcde6 nrf52840: add support for flashing with the BOSSA tool
This only works with a custom bossac build from Arduino, not with the
upstream version. It avoids needing the manual "double tap" to enter
bootloader mode before flashing firmware.
2021-06-18 13:00:00 +02:00
Ayke van Laethem d1f445735c syscall: fix int type in libc version
Int in Go and C are two different types (hence why CGo has C.int). The
code in syscall assumed they were of the same type, which led to a bug:
https://github.com/tinygo-org/tinygo/issues/1957

While the C standard makes no guarantees on the size of int, in most
modern operating systems it is 32-bits so Go int32 would be the correct
choice.
2021-06-18 08:07:01 +02:00
Yurii Soldak 28a2ad4757 gdb: use "extended-remote" instead of "remote", allows connect from another client
My gdb complains bare "remote" command is deprecated
On top of that "extended-remote" allows "disconnect" command that enables attaching from another debug client, like an IDE
See https://sourceware.org/gdb/current/onlinedocs/gdb/Connecting.html
2021-06-17 23:52:44 +02:00
Ayke van Laethem f2e8d7112c compiler: refactor method names
This commit includes two changes:

  * It makes unexported interface methods package-private, so that it's
    not possible to type-assert on an unexported method in a different
    package.
  * It makes the globals used to identify interface methods defined
    globals, so that they can (eventually) be left in the program for an
    eventual non-LTO build mode.
2021-06-17 12:17:32 +02:00
Kenneth Bell 52d640967b rp2040: patch elf to checksum 2nd stage boot 2021-06-17 12:10:04 +02:00
deadprogram 87e48c1057 machine/rp2040: implement UART0/UART1, can be used on all rp2040 boards
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-06-16 19:13:01 +02:00
sago35 b406b81416 machine: add definition for ws2812 2021-06-16 07:32:39 +02:00
Ayke van Laethem 3a458ec75c main: make flash-command portable and safer to use
Previously, flash-command would assume it could execute a command
straight via /bin/sh, at least on non-Windows systems. Otherwise it
would just split the command using `strings.Split`. This is all a bit
hacky, so I've replaced it with a proper solution: splitting the command
_before_ substituting various paths using a real shell splitter
(shlex.Split, from Google). This solves a few things:

  * It guards against special characters in path names. This can be an
    issue on Windows where the temporary path may contain spaces (this
    is uncommon on POSIX systems).
  * It is more portable, by disallowing the use of a shell. That way, it
    doesn't differentiate between Windows and non-Windows anymore.
2021-06-15 14:36:54 +02:00
Yurii Soldak 7764797061 board/nano-33-ble: pins, blinking leds and serial 2021-06-14 00:06:59 +02:00
Kenneth Bell 5f9e339cf3 stm32: support pin input interrupts 2021-06-11 09:07:32 +02:00
Kenneth Bell c017ed2242 bluepill: GPIO PinInputPullup / PinInputPulldown
Other chips support explicit control of pull-up vs pull-down for GPIO input.  Support that with bluepill also.  PinInputPullUpDown is maintained for back-compat.  It is implicit pull-down.
2021-06-11 09:07:32 +02:00
Yurii Soldak d62a9e24e5 runtime: expose memory stats 2021-06-10 22:03:00 +02:00
Federico G. Schwindt b092856238 Add more net compatibility
Required for net/http.
2021-06-10 15:33:46 +02:00
Yurii Soldak 15d77119c9 board/nano-rp2040: pins and blinking led 2021-06-09 19:01:02 +02:00
Yurii Soldak 95af444896 machine/rp2040: gpio and adc pin definitions 2021-06-09 12:27:05 +02:00
Olaf Flebbe 1f5e4e79aa support flashing pca10059 from windows 2021-06-08 14:17:04 +02:00
deadprogram 9912dd6db1 machine/rp2040: add basic support for ADC
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-06-08 09:22:51 +02:00
deadprogram 42ec3e2469 docker: use github actions to build/publish tinygo-dev dockerfile
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-06-08 07:37:31 +02:00
Ayke van Laethem 4e610a0ee7 main: escape commands while printing them with the -x flag
This should make issues such as the one in
https://github.com/tinygo-org/tinygo/issues/1910 more obvious.
2021-06-02 16:29:30 +02:00
Ayke van Laethem af65c006e6 loader: fix testing a main package
This was broken because multiple packages in the program were named
'main', even one that was imported (by the generated main package).

This fixes tests for main packages.
2021-06-02 15:50:28 +02:00
Ayke van Laethem 98f117fca4 main: add -test flag for tinygo list
This matches `go list` and is convenient for debugging.
2021-06-02 15:50:28 +02:00
Ayke van Laethem 4c95febeee main: don't consider compile-only tests as failing
Previously a command like the following would incorrectly print FAIL:

    tinygo test -c math

This commit fixes this issue by defaulting to a passing test (the test
is marked as passed if it isn't run).
2021-06-02 14:27:55 +02:00
Federico G. Schwindt 793a3175d3 reflect: add stubs required for net/http 2021-06-02 13:28:38 +02:00
Federico G. Schwindt 78d030aa7a Add os stubs required for net/http 2021-06-01 15:20:58 +02:00
Federico G. Schwindt 62f9f61664 Add runtime stubs required for net/http
Continued from #1911.
2021-06-01 15:15:12 +02:00
Federico G. Schwindt 5b82125765 Add sync.NewCond
Required by net/http.
2021-06-01 15:02:11 +02:00
Kenneth Bell 2f248bbf8b scheduler: task.Data made 64bit to avoid overflow 2021-06-01 15:00:07 +02:00
sago35 c5ea1fde61 increase stack size for access to sdcard 2021-06-01 09:16:34 +02:00
deadprogram 36dffb5554 machine/rp2040: add support for GPIO input 2021-05-31 11:43:00 +02:00
Ayke van Laethem e8c4c4a865 nrf: don't trigger a heap allocation in SPI.Transfer
By using a 1-byte buffer, two heap allocations each `SPI.Transfer` call
can be avoided.
2021-05-30 20:56:01 +02:00
Ayke van Laethem 8b79e82686 nrf: avoid heap allocation in waitForEvent
Because arm.SVCall1 lets pointers escape, the return value of
sd_softdevice_is_enabled (passed as a pointer in a parameter) will
escape and thus this value will be heap allocated.

Use a global variable for this purpose instead to avoid the heap
allocation. This is safe as waitForEvent may only be called outside of
interrupts.
2021-05-30 20:56:01 +02:00
sago35 22eeed2da1 qtpy: add pin for neopixels 2021-05-30 11:01:19 +02:00
Kenneth Bell cfbc9be9bf rp2040: git ignore generated device files 2021-05-29 19:56:50 +02:00
Rajiv Kanchan 722a3a5c94 add rp2040, pico
adds preliminary support (just enough to run blinky1) for the Raspberry Pi Pico board along with the rp2040 mcu.
2021-05-28 18:29:04 +02:00
deadprogram ed2db8a26d Revert "scheduler: task.Data made 64bit to avoid overflow"
This reverts commit fd8938c634.
2021-05-28 07:38:59 +02:00
Kenneth Bell fd8938c634 scheduler: task.Data made 64bit to avoid overflow 2021-05-28 00:02:46 +02:00
Kenneth Bell fa3dd41a4f stm32: Use TIM for runtime clock 2021-05-28 00:02:46 +02:00
Kenneth Bell 003c96edc0 stm32f103 (bluepill): add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell ac54302301 stm32f7: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell c43a41165a stm32l4: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell 3145c2747e stm32l0: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell 2c4b507d34 stm32l5: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell ee167f15de stm32: add pwm for f4 series 2021-05-28 00:02:46 +02:00
Yurii Soldak 422ebeeec7 Do not disable interrupts on abort
Allows panic messages to be printed fully into serial console
2021-05-27 09:28:08 +02:00
Ayke van Laethem c93ddb630b compiler: skip context parameter when starting regular goroutine
Do not store the context parameter (which is used for closures and
function pointers) in the goroutine start parameter bundle for direct
functions that don't need a context parameter. This avoids storing the
(undef) context parameter and thus makes the IR to start a new goroutine
simpler in most cases.

This reduces code size in the channel.go and goroutines.go tests.
Surprisingly, all test cases (when compiled with -target=microbit) have
a changed binary, I haven't investigated why but I suppose the codegen
is slightly different for the runtime.run function (which starts the
main goroutine).
2021-05-26 20:21:08 +02:00
Ayke van Laethem 3edcdb5f0d compiler: do not emit nil checks for loading closure variables
Closure variables are allocated in a parent function and are thus never
nil. Don't do a nil check before reading or modifying the value.

This commit results in a slight reduction in code size in some test
cases: calls.go, channel.go, goroutines.go, json.go, sort.go -
presumably wherever closures are used.
2021-05-26 20:21:08 +02:00
Ayke van Laethem ec325c0643 compiler: add support for running a builtin in a goroutine
Not sure why you would ever do this, but it appears to be allowed by the
Go specification and previously TinyGo would crash with an unhelpful
error message when you would do this. I don't see any practical use of
it.

The implementation simply runs the builtin directly.
2021-05-26 20:21:08 +02:00
Ayke van Laethem 87c2ccb0b9 compiler: add tests for starting a goroutine
This commit adds a test for both WebAssembly and Cortex-M targets (which
use a different way of goroutine lowering) to show how they lower
goroutines. It makes it easier to show how the output changes in future
commits.
2021-05-26 20:21:08 +02:00
Ayke van Laethem 6e1eb28ed0 main: rename goroutine tests
While LLVM coroutines are one implementation of goroutines, it is not
the only one. Therefore, rename the tests to 'goroutines' to better
describe what they're for.
2021-05-26 20:21:08 +02:00
Ayke van Laethem 45cf2a5a1a compiler: refactor goroutine code
Move the code from the compiler.go file to the goroutine.go file, which
is a more appropriate place. This keeps all the goroutine related code
in one file, to make it easier to find.
2021-05-26 20:21:08 +02:00
deadprogram 423cae86df docker: update dev dockerfile to Go 1.16
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-05-26 17:33:50 +02:00
deadprogram ea6b65e360 cmsis-svd: update with latest to sync with upstream repo
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-05-25 22:50:08 +02:00
Kenneth Bell e3b98dabfd Support chained interrupt handlers
Multiple calls to interrupt.New are permitted with handlers called sequentially in undefined order.
2021-05-25 20:44:49 +02:00
sago35 e312cb0fe7 os: add FileMode constants from Go 1.16 2021-05-23 21:44:27 +02:00
Ayke van Laethem 541d8dcd2e reflect: implement AppendSlice
This implementation of AppendSlice simply calls through to the version
used in the runtime: runtime.sliceAppend.
2021-05-22 21:41:06 +02:00
Ayke van Laethem c8742e2b96 reflect: use SliceHeader and StringHeader variant for internal use
These variants uses an unsafe.Pointer instead of uintptr so that the
pointer/non-pointer fields match those of real slices and strings. This
may be necessary in the future once we switch to a precise garbage
collector.
2021-05-22 21:41:06 +02:00
Ayke van Laethem 711889bc3f cgo: implement prefix parsing
This implements expressions such as "-5" and "-5 - 2", in other words,
negative numbers.
2021-05-21 17:54:13 +02:00
Ayke van Laethem 70f8eeaca0 cgo: parse binary operators
This follows the Pratt parser design.
2021-05-21 17:54:13 +02:00
Ayke van Laethem 3339d0f47e cgo: create skeleton of a Pratt parser
This converts the existing const parser to the basics of a Pratt parser,
following the book "Writing An Interpreter In Go" by Thorsten Ball. It
doesn't really do anything interesting yet, it simply converts the
existing code (with existing tests) to the new structure.
2021-05-21 17:54:13 +02:00
Federico G. Schwindt a38b5c4e01 Add a stub for os.ReadDir()
This is needed by crypto/x509 in some configuration.
Related to #1888.
2021-05-20 14:14:04 +02:00
Federico G. Schwindt b20905da13 Add support for net.IP 2021-05-20 13:46:19 +02:00
Ayke van Laethem 95e4dcfb53 interp: ignore inline assembly in markExternal
The markExternal function is used when a global (function or global
variable) is somehow run at runtime. All the other globals it refers to
are from then on no longer known at compile time, so can't be used by
the interp package anymore.

This can also include inline assembly. While it is possible to modify
globals that way, it is only possible to modify exported globals:
similar to calling an undefined function (in C for example).
2021-05-20 07:42:19 +02:00
Ayke van Laethem 841f19f49e ci: disable building some optional Clang components
This commit disables the Clang static analyzer and ARCMigrate components
of Clang. These aren't used at the moment in TinyGo so don't need to be
enabled. This reduces the build by 200 files (2909 -> 2709).

The idea comes from here (via LLVM weekly):
https://www.cambus.net/speedbuilding-llvm-clang-in-5-minutes/
2021-05-19 19:12:26 +02:00
sago35 2085ffb334 version: update TinyGo version to 0.19.0-dev 2021-05-14 11:47:07 +02:00
Ayke van Laethem b67351babe machine: define Serial as the default output
Previously, the machine.UART0 object had two meanings:

  - it was the first UART on the chip
  - it was the default output for println

These two meanings conflict, and resulted in workarounds like:

  - Defining UART0 to refer to the USB-CDC interface (atsamd21,
    atsamd51, nrf52840), even though that clearly isn't an UART.
  - Defining NRF_UART0 to avoid a conflict with UART0 (which was
    redefined as a USB-CDC interface).
  - Defining aliases like UART0 = UART1, which refer to the same
    hardware peripheral (stm32).

This commit changes this to use a new machine.Serial object for the
default serial port. It might refer to the first or second UART
depending on the board, or even to the USB-CDC interface. Also, UART0
now really refers to the first UART on the chip, no longer to a USB-CDC
interface.

The changes in the runtime package are all just search+replace. The
changes in the machine package are a mixture of search+replace and
manual modifications.

This commit does not affect binary size, in fact it doesn't affect the
resulting binary at all.
2021-05-13 16:43:37 +02:00
Ayke van Laethem aa5b8d0df7 machine: make UART objects pointer receivers
This means that machine.UART0, machine.UART1, etc are of type
*machine.UART, not machine.UART. This makes them easier to pass around
and avoids surprises when they are passed around by value while they
should be passed around by reference.

There is a small code size impact in some cases, but it is relatively
minor.
2021-05-13 16:43:37 +02:00
Ayke van Laethem 7c949ad386 machine: make USBCDC global a pointer
Make the USBCDC use a pointer receiver everywhere. This makes it easier
to pass around the object in the future.

This commit sometimes changes code size, but not significantly (a few
bytes) and usually in a positive way.

My eventual goal is the following:

  - Declare `machine.USB` (or similar, name TBD) as a pointer receiver
    for the USB-CDC interface.
  - Let `machine.UART0` always point to an UART, never actually to a
    USBCDC object.
  - Define `machine.Serial`, which is either a real UART or an USB-CDC,
    depending on the board.

This way, if you want a real UART you can use machine.UARTx and if you
just want to print to the default serial port, you can use
machine.Serial.

This change does have an effect on code size and memory consumption.
There is often a small reduction (-8 bytes) in RAM consumption and an
increase in flash consumption.
2021-05-13 16:43:37 +02:00
sago35 1646326164 Update README.md 2021-05-12 12:22:08 +02:00
Ayke van Laethem e44cb7e519 main: version 0.18.0 2021-05-12 07:23:49 +02:00
Ayke van Laethem d1e96fd0a8 wasm: scan globals conservatively
Make the GC globals scan phase conservative instead of precise on
WebAssembly. This reduces code size at the risk of introducing some
false positives.

This is a stopgap measure to mitigate an issue with the precise scanning
of globals that doesn't track all pointers. It works for regular globals
but globals created in the interp package don't always have a type and
therefore may be missed by the AddGlobalsBitmap pass.
The same issue is present on Linux and macOS, but is not as noticeable
there.
2021-05-11 18:15:36 +02:00
Ayke van Laethem ff5d0c9886 compiler: only check for default stack size with tasks scheduler
With -scheduler=none, there are no goroutines so there is no stack size
anywhere. Therefore, don't check whether the default stack size is set.
2021-05-11 00:17:03 +02:00
Yurii Soldak d7ec9ddcd2 Discover USB ports only
This will ignore f.ex. bluetooth
2021-05-10 13:27:59 +02:00
sago35 e7c6bd3730 atsame5x: add support for CAN 2021-05-10 12:27:10 +02:00
Ayke van Laethem 9f5066aa6f runtime: use the tasks scheduler instead of coroutines
This results in smaller and likely more efficient code. It does require
some architecture specific code for each architecture, but I've kept the
amount of code as small as possible.
2021-05-09 17:40:13 +02:00
Ayke van Laethem 3b24fedf92 compiler: use wasm for tests
The next commit will change the implementation of func values on Linux
as a result of switching to a task-based scheduler. To keep the
compiler/testdata/func.go test working as expected, switch to
WebAssembly tests.
2021-05-09 17:40:13 +02:00
Ayke van Laethem 8cd2a462b9 runtime: remove the asyncScheduler constant
There is no reason to specialize this per chip as it is only ever used
for JavaScript. Not only that, it is causing confusion and is yet
another quirk to learn when porting the runtime to a new
microcontroller.
2021-05-08 23:08:12 +02:00
Ayke van Laethem 25b045d0a7 runtime: improve timers on nrf, and samd chips
This commit improves the timers on various microcontrollers to better
deal with counter wraparound. The result is a reduction in RAM size of
around 12 bytes and a small effect (sometimes positive, sometimes
negative) on flash consumption. But perhaps more importantly: getting
the current time is now interrupt-safe (it previously could result in a
race condition) and the timer will now be correct when the timer isn't
retrieved for a long duration. Before this commit, a call to `time.Now`
more than 8 minutes after the previous call could result in an incorrect
time.

For more details, see:
https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
2021-05-08 20:59:40 +02:00
Ayke van Laethem 78acbdf0d9 main: match go test output
This commit makes the output of `tinygo test` similar to that of `go
test`. It changes the following things in the process:

  * Running multiple tests in a single command is now possible. They
    aren't paralellized yet.
  * Packages with no test files won't crash TinyGo, instead it logs it
    in the same way the Go toolchain does.
2021-05-06 20:04:16 +02:00
Federico G. Schwindt 617e2791ef Add -llvm-features parameter
With this is possible to enable e.g., SIMD in WASM using -llvm-features
+simd128.  Multiple features can be specified separated by comma,
e.g., -llvm-features +simd128,+tail-call

With help from @deadprogram and @aykevl.
2021-05-06 18:07:14 +02:00
Kenneth Bell a0908ff55b compiler: openocd commands in tinygo command line 2021-05-06 15:09:41 +02:00
Ayke van Laethem 2f1f8fb075 machine: move PinMode to central location
It is always implemented exactly the same way (as an uint8) so there is
no reason to implement it in each target separately.

This also makes it easier to add some documentation to it.
2021-05-06 13:59:12 +02:00
sago35 6f61b83ad5 atsamd21: remove special handling for SPI-24mhz 2021-05-06 10:32:24 +02:00
Dan Kegel 8dfefb46d1 wasi: do not crash if argc is 0
Instead, leave args at its default value (which provides a fake argv[0] as it has for a long time).

linux and mac do not seem affected.

Fixes #1862 (tinygo apps after v0.17.0-113-g7b761fa crash if run without argv[0])
2021-05-05 19:16:28 +02:00
Ayke van Laethem 959442dc82 unix: use conservative GC by default
This commit does two things:

 1. It makes it possible to grow the heap on Linux and MacOS by
    allocating 1GB of virtual memory on startup and then slowly using it
    as necessary, when running out of available heap space.
 2. It switches the default GC to be the conservative GC (previously
    extalloc). This is good for consistency with other platforms that
    all use this same GC.

This makes the extalloc GC unused by default.
2021-05-05 17:20:15 +02:00
Ayke van Laethem c1aa152a63 unix: avoid possible heap allocation with -opt=0
This heap allocation would normally be optimized away, but with -opt=0
perhaps not. This is a problem if the conservative GC is used, because
the conservative GC needs to be initialized before use.
2021-05-05 17:20:15 +02:00
sago35 dd3d8a363a qtpy: fix i2c setting 2021-05-05 10:12:08 +02:00
sago35 bb509ec91d atsamd51, atsamd21: fix ADC.Get() value at 8bit and 10bit 2021-05-05 06:51:09 +02:00
Ayke van Laethem 944f022060 interp: support extractvalue/insertvalue with multiple operands
TinyGo doesn't emit these instructions, but they can occur as a result
of optimizations.
2021-05-04 21:21:56 +02:00
Ayke van Laethem cd517a30af transform: split interface and reflect lowering
These two passes are related, but can definitely work independently.
Which is what this change does: it splits the two passes. This should
make it easier to change these two new passes in the future.

This change now also enables slightly better testing by testing these
two passes independently. In particular, the reflect lowering pass got
some actual tests: it was barely unit-tested before.

I have verified that this doesn't really change code size, at least not
on the microbit target. Two tests do change, but in a very minor way
(and in opposite direction).
2021-05-03 20:10:49 +02:00
Olivier Fauchon 52d8655eec Patch Cleanup 2021-05-03 18:16:46 +02:00
Olivier Fauchon f5786941e5 Fix bad I2C0/I2C1 declaration 2021-05-03 18:16:46 +02:00
Ayke van Laethem 1f73941c43 ci: bump Xcode version to use macOS 10.14
The CircleCI macOS builds are failing, probably due to the old macOS
version that's used. This version (10.13 High Sierra) isn't supported
anymore on Homebrew so it seems best to me to simply bump the version.

I picked Xcode 11.1.0 because 10.3.0 is somehow triggering an error
while trying to install QEMU (the Python install fails).

Because of this newer Xcode version, I had to add an extra flag
(-isysroot) to the default command line for MacOS. The reason is that
this newer Xcode version no longer stores header files in /usr/local, an
SDK must be specified manually. With this change, the default SDK is
used.
2021-05-02 23:55:10 +02:00
Raqbit abeab51d00 Add Arduino Nano w/ New Bootloader target
Since 2018, Arduino Nanos and clones are sold with a new bootloader, which
requires programming at 115200 baud instead of the 57600 baud required
by the old one.
2021-05-01 17:09:46 +02:00
sago35 9ef75f17bf atsamd51, atsame5x: unify samd51 and same5x 2021-04-29 09:20:44 +02:00
Ayke van Laethem c3992bd77b compiler: improve position information
In many cases, position information is not stored in Go SSA instructions
because they don't exit directly in the source code. This includes
implicit type conversions, implicit returns at the end of a function,
the creation of a (hidden) slice when calling a variadic function, and
many other cases. I'm not sure where this information is supposed to
come from, but this patch takes the value (usually) from the value the
instruction refers to. This seems to work well for these implicit
conversions.

I've also added a few extra tests to the heap-to-stack transform pass,
of which one requires this improved position information.
2021-04-26 16:15:57 +02:00
Ayke van Laethem f79e66ac2e cortexm: disable FPU on Cortex-M4
On some boards the FPU is already enabled on startup, probably as part
of the bootloader. On other chips it was enabled as part of the runtime
startup code. In all these cases, enabling the FPU is currently
unsupported: the automatic stack sizing of goroutines assumes that the
processor won't need to reserve space for FPU registers. Enabling the
FPU therefore can lead to a stack overflow.

This commit either removes the code that enables the FPU, or simply
disables it in startup code. A future change should fully enable the FPU
so that operations on float32 can be performed by the FPU instead of in
software, greatly speeding up such code.
2021-04-24 18:41:40 +02:00
Ayke van Laethem 4eac212695 gen-device: add extra constants and rename them to be Go style
- Add some extra fields: FPUPresent, CPU and NVICPrioBits which may
    come in handy at a later time (and are easy to add).
  - Rename DEVICE to Device, to match Go style.

This is in preparation to the next commit, which requires the FPUPresent
flag.
2021-04-24 18:41:40 +02:00
deadprogram b661882391 machine/usbcdc: remove remaining heap allocations for USB CDC implementations
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-04-23 23:37:24 +02:00
Ayke van Laethem 321488dcfe machine: avoid heap allocations in USB code
This commit replaces most heap allocations in USB related code with
stack allocations. This is important for several reasons:

  - It avoids running the GC unnecessarily.
  - It reduces code size by 400-464 bytes.
  - USB code might be called from interrupt handlers. The heap may be in
    an inconsistent state when that happens if main thread code also
    performs heap allocations.

The last one is by far the most important one: not doing heap
allocations in interrupts is critical for correctness. But the code size
reduction alone should be worth it.

There are two heap allocations in USB related code left: in the function
receiveUSBControlPacket (SAMD21 and SAMD51). This heap allocation must
also be removed because it runs in an interrupt, but I've left that for
a future change.
2021-04-23 23:37:24 +02:00
Ayke van Laethem 80caf2dab2 copiler: add function attributes to some runtime calls
This allows better escape analysis even without being able to see the
entire program. This makes the stack allocation test case more complete
but probably won't have much of an effect outside of that (as the
compiler is able to infer these attributes in the whole-program
functionattrs pass).
2021-04-22 19:53:42 +02:00
Ayke van Laethem c466465c32 main: add -print-allocs flag that lets you print all heap allocations
This flag, if set, is a regexp for function names. If there are heap
allocations in the matching function names, these heap allocations will
be printed with an explanation why the heap allocation exists (and why
the object can't be stack allocated).
2021-04-22 19:53:42 +02:00
Ayke van Laethem 404b65941a transform: move tests to transform_test package
This allows for adding more advanced tests, for example tests that use
the compiler package so that test sources can be written in Go instead
of LLVM IR.
2021-04-22 19:53:42 +02:00
Kenneth Bell 25f3adb47e stm32: support SPI on L4 series 2021-04-21 21:09:41 +02:00
sago35 b9043b649d atsamd51: fix PWM support in atsamd51p20
This change is related to the following commit
72acda22b0
2021-04-21 15:02:47 +02:00
Ayke van Laethem 7b761fac78 runtime: implement command line arguments in hosted environments
Implement command line arguments for Linux, MacOS and WASI.
2021-04-21 10:32:09 +02:00
Ayke van Laethem c47cdfa66f runtime: implement environment variables for Linux 2021-04-21 10:32:09 +02:00
Ayke van Laethem 768a15c1dd interp: remove map support
The interp package is in many cases able to execute map functions in the
runtime directly. This is probably slower than adding special support
for them in the interp package and also doesn't cover all cases (most
importantly, map keys that contain pointers) but removing this code also
removes a large amount of code that needs to be maintained and is
susceptible to hard-to-find bugs.

As a side effect, this resulted in different output of the
testdata/map.go test because the test relied on the existing iteration
order of TinyGo maps. I've updated the test to not rely on this test,
making the output compatible with what the Go toolchain would output.
2021-04-21 07:37:22 +02:00
Ayke van Laethem c1c3be1aa7 interp: fix phi instruction
I've discovered a bug in the implementation of the PHI instruction in
the interp package. This commit fixes the bug.

I've found this issue while investigating an issue with maps after
running interp per package.
2021-04-21 07:37:22 +02:00
sago35 b2e72c96f4 ci: improve llvm-build cache 2021-04-19 17:24:52 +02:00
Ayke van Laethem f706219996 builder: hard code Clang compiler
At the moment, all targets use the Clang compiler to compile C and
assembly files. There is no good reason to make this configurable
anymore and in fact it will make future changes more complicated (and
thus more likely to have bugs). Therefore, I've removed support for
setting the compiler.

Note that the same is not true for the linker. While it makes sense to
standardize on the Clang compiler (because if Clang doesn't support a
target, TinyGo is unlikely to support it either), linkers will remain
configurable for the foreseeable future. One example is Xtensa, which is
supported by the Xtensa LLVM fork but doesn't have support in ld.lld
yet.

I've also fixed a bug in compileAndCacheCFile: it wasn't using the right
CFlags for caching purposes. This could lead to using stale caches. This
commit fixes that too.
2021-04-19 13:14:33 +02:00
sago35 6152a661e8 ci: improve llvm-source cache 2021-04-19 00:55:42 +02:00
Dan Kegel f1a5743f77 Make fmt-check happy again 2021-04-17 00:01:37 +02:00
sago35 9f52fe4e4a atsame51: add initial support for feather-m4-can 2021-04-16 17:49:46 +02:00
developer cb886a35c9 PWM Support for atmega1280
Add arduino mega 1280 PWM test
2021-04-16 17:47:31 +02:00
Tobias Theel 5707022951 add goroot for snap installs 2021-04-15 17:34:21 +02:00
sago35 bd212cc000 atsame54: add initial support for atsame54-xpro 2021-04-15 15:43:37 +02:00
Ayke van Laethem 6dd5666ed1 wasm: use WASI ABI for exit function
This improves compatibility between the regular browser target
(-target=wasm) and the WASI target (-target=wasi). Specifically, it
allows running WASI tests like this:

    tinygo test -target=wasi encoding/base32
2021-04-15 08:45:08 +02:00
Ayke van Laethem f145663464 cortexm: add __isr_vector symbol
This doesn't change the firmware, but it does make the disassembly of
the ELF files. Before:

    Disassembly of section .text:

    00000000 <(machine.UART).Write-0x100>:
           0:       20001000        .word   0x20001000
           4:       000009db        .word   0x000009db
           8:       00000f05        .word   0x00000f05
           c:       00000f0b        .word   0x00000f0b
          10:       00000f05        .word   0x00000f05

After:

    Disassembly of section .text:

    00000000 <__isr_vector>:
           0:       20001000        .word   0x20001000
           4:       000009db        .word   0x000009db
           8:       00000f05        .word   0x00000f05
           c:       00000f0b        .word   0x00000f0b
          10:       00000f05        .word   0x00000f05

The difference is that the disassembler will now use a proper symbol name
instead of using the closest by symbol (in this case, (machine.UART).Write).
This makes the disassembly easier to read.
2021-04-15 07:38:52 +02:00
Ayke van Laethem 0d66475e10 nrf52833: add PWM support
This chip wasn't included in the PR for PWM support. Adding this support
is very easy, luckily.
2021-04-14 23:42:02 +02:00
Ayke van Laethem 22b143eba8 microbit-v2: add support for S113 SoftDevice
This currently doesn't work with `tinygo flash` yet (even with
`-programmer=openocd`), you can use pyocd instead. For example, from the
Bluetooth package:

    tinygo build -o test.hex -target=microbit-v2-s113v7 ./examples/advertisement/
    pyocd flash --target=nrf52 test.hex

I intend to add support for pyocd to work around this issue, so that a simple
`tinygo flash` suffices.
2021-04-14 22:55:52 +02:00
Ayke van Laethem 3fe13a72bd microbit: remove LED constant
There doesn't appear to be a user-controllable LED outside of the LED
matrix. In fact, the pin assigned for this was P13, which was connected
to the SPI SCK pin.
2021-04-14 17:40:32 +02:00
Ayke van Laethem d919905c96 all: clean up Cortex-M target files
In this commit I've moved all core-specific flags to files for that
specific core. This is a bit of a cleanup (less duplicated JSON) but
should also help in the future when core-specific changes are made, such
as core specific build tags or when the FPU finally gets supported in
TinyGo.

Some notable specific changes:

  - I've removed floating point flags from the Teensy 3.6 target. The
    reason is that the FPU is not yet supported in TinyGo (in goroutine
    stack switching for example) and floating point numbers would only
    be supported by C files, not Go files (because the LLVM FPU feature
    flags aren't used). This would create an ABI mismatch across CGo.
  - I've added the "cpu":"cortex-m7" to the cortex-m7.json file to match
    the configuration for the Teensy 4.0. This implies a change to the
    nucleo-f722ze (because now it has its CPU field set). Somehow that
    reduces the code size, so it looks like a good change.

I don't believe any of these changes should have any practical
consequences.

One issue I've found is in the Cortex-M33 target: it uses armv7m, which
is incorrect: it should be armv8m. But the chip is backwards compatible
so this should mostly work. Switching to armv8m led to a compilation
failure because PRIMASK isn't defined, this may be an actual bug.
2021-04-14 09:17:54 +02:00
Ayke van Laethem 96b1b76483 all: use -Qunused-arguments only for assembly files
The -Qunused-arguments flag disables the warning where some flags are
not relevant to a compilation. This commonly happens when compiling
assembly files (.s or .S files) because some flags are specific to C and
not relevant to assembly.
Because practically all baremetal targets need some form of assembly,
this flag is added to most CFlags. This creates a lot of noise. And it
is also added for compiling C code where it might hide bugs (by hiding
the fact a flag is actually unused).

This commit adds the flag to all assembly compilations and removes them
from all target JSON files.
2021-04-14 09:17:54 +02:00
sago35 f234df7a50 cmsis-svd: add svd file for the atsame5x 2021-04-14 06:39:36 +02:00
Agurato e6d5c26df5 Fix RGBA color interpretation for GameBoyAdvance 2021-04-13 18:51:14 +02:00
Kenneth Bell ae59e7703e stm32: make SPI CLK fast to fix data issue
See "STM32F40x and STM32F41x Errata sheet" - SPI CLK port must be 'fast' or 'very fast' to avoid data corruption on last bit (at the APB clocks we configure).
2021-04-13 07:38:30 +02:00
Ayke van Laethem e587b1d1b4 reflect: implement New function
This is very important for some use cases, for example for Vecty.
2021-04-12 14:49:26 +02:00
Ayke van Laethem 57271d7eaa compiler: decouple func lowering from interface type codes
There is no good reason for func values to refer to interface type
codes. The only thing they need is a stable identifier for function
signatures, which is easily created as a new kind of globals. Decoupling
makes it easier to change interface related code.
2021-04-12 12:07:42 +02:00
Ayke van Laethem 8383552552 compiler: add func tests
This is basically just a golden test for the "switch" style of func
lowering. The next commit will make changes to this lowering, which will
be visible in the test output.
2021-04-12 12:07:42 +02:00
developer aa8e12c464 Arduino Mega 1280 support
Fix ldflags

Update targets/arduino-mega1280.json

Co-authored-by: Ayke <aykevanlaethem@gmail.com>

Update atmega1280.json

Update Makefile
2021-04-12 11:03:13 +02:00
Ayke van Laethem e7a05b6e74 transform: do not lower zero-sized alloc to alloca
The LLVM CoroFrame pass appears to be tripping over this zero-sized
alloca. Therefore, do what the runtime would do: return a pointer to
runtime.zeroSizedAlloc. Or just don't deal with this case. But don't
emit a zero sized alloca to avoid this LLVM bug.

More information: https://bugs.llvm.org/show_bug.cgi?id=49916
2021-04-12 08:11:28 +02:00
Ayke van Laethem 2fd8f103ab transform: fix func lowering assertion failure
The func-lowering pass has started to fail in the dev branch, probably
as a result of replacing the ConstPropagation pass with the IPSCCP pass.
This commit makes the code a bit more robust and should be able to
handle all possible cases (not just ptrtoint).
2021-04-12 08:11:28 +02:00
Ayke van Laethem 33f76d1c2e main: implement -ldflags="-X ..."
This commit implements replacing some global variables with a different
value, if the global variable has no initializer. For example, if you
have:

    package main

    var version string

you can replace the value with -ldflags="-X main.version=0.2".

Right now it only works for uninitialized globals. The Go tooling also
supports initialized globals (var version = "<undefined>") but that is a
bit hard to combine with how initialized globals are currently
implemented.

The current implementation still allows caching package IR files while
making sure the values don't end up in the build cache. This means
compiling a program multiple times with different values will use the
cached package each time, inserting the string value only late in the
build process.

Fixes #1045
2021-04-09 18:33:48 +02:00
Ayke van Laethem ea8f7ba1f9 main: add tests for less common build configurations
Don't run the entire test suite for these options, as that would quickly
explode the testing time (making it less likely people actually run it).
Instead, run just one test for each configuration that should check for
most issues.
2021-04-09 18:33:48 +02:00
Ayke van Laethem 0ffe5ac2fa main: clean up tests
- Explicitly list all test cases. This makes it possible to store
    tests in testdata/ that aren't tested on all platforms.
  - Clean up filesystem and env test, by running them in a subtest and
    deduplicating some code and removing the additionalArgs parameter.
2021-04-09 18:33:48 +02:00
Ayke van Laethem 7bac93aab3 main: remove -cflags and -ldflags flags
These are unnecessary now that they are supported in CGo.
2021-04-09 18:33:48 +02:00
Ayke van Laethem b61751e429 compiler: check for errors
Some errors were generated but never returned or never checked in the
test function. That's a problem. Therefore this commit fixes this
oversight (by me).
2021-04-09 14:05:44 +02:00
Ayke van Laethem 25dac32a88 transform: use IPSCCP pass instead of the constant propagation pass
The constant propagation pass is removed in LLVM 12, so this pass needs
to be replaced anyway. The direct replacement would be the SCCP (sparse
conditional constant propagation) pass, but perhaps a better replacement
is the IPSCCP pass, which is an interprocedural version of the SCCP
pass and propagates constants across function calls if possible.

This is not always a code size reduction, but it appears to reduce code
size in a majority of cases. It certainly reduces code size in almost
all WebAssembly tests I did.
2021-04-08 12:31:26 +02:00
Ayke van Laethem 56cf69a66b builder: run function passes per package
This should result in a small compile time reduction for incremental
builds, somewhere around 5-9%.

This commit, while small, required many previous commits to not regress
binary size. Right now binary size is basically identical with very few
changes in size (the only baremetal program that changed in size did so
with a 4 byte increase).

This commit is one extra step towards doing as much work as possible in
the parallel and cached package build step, out of the serial LTO phase.
Later improvements in this area have this change as a prerequisite.
2021-04-08 11:40:59 +02:00
Ayke van Laethem 04d12bf2ba interp: add support for switch statement
A switch statement is not normally emitted by the compiler package, but
LLVM function passes may convert a series of if/else pairs to a switch
statement. A future change will run function passes in the package
compile phase, so the interp package (which is also run after all
modules are merged together) will need to deal with these new switch
statements.
2021-04-08 11:40:59 +02:00
Ayke van Laethem 0b7957d612 compiler: optimize string literals and globals
This commit optimizes string literals and globals by setting the
appropriate alignment and using a nil pointer in zero-length strings.

  - Setting the alignment for string values has a surprisingly large
    effect, up to around 2% in binary size. I suspect that LLVM will
    pick some default alignment for larger byte arrays if no alignment
    has been specified and forcing an alignment of 1 will pack all
    strings closer together.
  - Using nil for zero-length strings also has a positive effect, but
    I'm not sure why. Perhaps it makes some optimizations more trivial.
  - Always setting the alignment on globals improves code size slightly,
    probably for the same reasons setting the alignment of string
    literals improves code size. The effect is much smaller, however.

This commit might have an effect on performance, but if it does this
should be tested separately and such a large win in binary size should
definitely not be ignored for small embedded systems.
2021-04-08 11:40:59 +02:00
Ayke van Laethem 61243f6c57 transform: don't rely on struct name of runtime.typecodeID
Sometimes, LLVM may rename named structs when merging modules.
Therefore, we can't rely on typecodeID structs to retain their struct
names.

This commit changes the interface lowering pass to not rely on these
names. The interp package does however still rely on this name, but I
hope to fix that in the future.
2021-04-08 11:40:59 +02:00
Ayke van Laethem 49ec3eb58e builder: add optsize attribute while building the package
This simplifies future changes. While the move itself is very simple, it
required some other changes to a few transforms that create new
functions to add the optsize attribute manually. It also required
abstracting away the optimization level flags (based on the -opt flag)
so that it can easily be retrieved from the config object.

This commit does not impact binary size on baremetal and WebAssembly.
I've seen a few tests on linux/amd64 grow slightly in size, but I'm not
too worried about those.
2021-04-08 11:40:59 +02:00
sago35 fa6c1b69ce build: improve error messages in getDefaultPort(), support for multiple ports 2021-04-08 10:32:20 +02:00
deadprogram 1e9a41dc94 modules: add latest go-llvm because seems like older SHA is missing?
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-04-07 18:10:43 +02:00
Kenneth Bell a30671751f stm32: add nucleo-l031k6 support
Adds i2c for all L0 series

UART, Blinky (LED) and i2c tested
2021-04-07 17:20:19 +02:00
Ayke van Laethem c246978dd7 ci: limit test runs of assert-test-linux to two jobs
Instead of the regular build, it's the `make test` line that fails due
to OOM. This is because testing means that a lot of test binaries need
to be built while the regular build only needs to link one binary.

This improves https://github.com/tinygo-org/tinygo/pull/1774 and should
hopefully actually fix the OOM errors.
2021-04-07 08:08:40 +02:00
Ayke van Laethem 72acda22b0 machine: refactor PWM support
This commit refactors PWM support in the machine package to be more
flexible. The new API can be used to produce tones at a specific
frequency and control servos in a portable way, by abstracting over
counter widths and prescalers.
2021-04-06 20:36:10 +02:00
Ayke van Laethem f880950c3e ci: limit number of jobs for assert-test-linux
This job is causing OOM errors on CircleCI so limit it to just two jobs
(which should be fine on a 2CPU executor). Hopefully this fixes the
errors in CI that have occured recently.
2021-04-06 12:57:43 +02:00
Ayke van Laethem 1bed192de0 cgo: add support for CFLAGS in .c files
This patch adds support for passing CFLAGS added in #cgo lines of the
CGo preprocessing phase to the compiler when compiling C files inside
packages. This is expected and convenient but didn't work before.
2021-04-06 10:57:50 +02:00
Ayke van Laethem 896a848001 builder: add support for -x flag
Print commands as they are executed with the -x flag. This is not yet
complete: library builds don't print commands yet. But it's a step
closer.
2021-04-05 20:52:04 +02:00
Ayke van Laethem fb03787b73 builder: cache C and assembly file outputs
This probably won't speed up the build on multicore systems (the build
is still dominated by the whole-program optimization step) but should be
useful at a later date for other optimizations. For example, I intend to
eventually optimize each package individually including C files, which
should enable cross-language optimizations (inlining C functions into Go
functions, for example). For that to work, accurate dependency tracking
is important.
2021-04-05 20:52:04 +02:00
Ayke van Laethem 83a949647f builder: refactor link file inputs
Add a 'result' member to the compileJob struct which is used by the link
job to get all the paths that should be linked together. This is not yet
necessary (the paths are fixed), but soon the paths are only known after
a linker dependency has run.
2021-04-05 20:52:04 +02:00
Ayke van Laethem 99a41bec4e transform: fix bug in interface lowering when signatures are renamed
In rare cases the signature might change as a result of LLVM renaming
some named struct types when multiple LLVM modules are merged. The
easiest workaround is to detect such mismatched signatures and adding a
bitcast: this should be safe as the underlying data is effectively of
the same type.
2021-04-05 11:44:00 +02:00
Ayke van Laethem 312f5d3833 builder: run interp per package
This results in a significant speedup in some cases. For example, this
runs over twice as fast with a warm cache:

    tinygo build -o test.elf ./testdata/stdlib.go

This should help a lot with edit-compile-test cycles, that typically
only modify a single package.

This required some changes to the interp package to deal with globals
created in a previous run of the interp package and to deal with
external globals (that can't be loaded from or stored to).
2021-04-02 13:43:57 +02:00
Ayke van Laethem 35bf0746a1 interp: make toLLVMValue return an error instead of panicking
This commit replaces a number of panics with returning an error value as
a result of changing the toLLVMValue method signature. This should make
it easier to diagnose issues.
2021-04-02 13:43:57 +02:00
sago35 8d93b9e545 atsamd21, atsamd51, nrf52840: unify usbcdc code 2021-03-29 10:31:58 +02:00
Ayke van Laethem b44d41d9ec compiler: fix "fragment covers entire variable" bug
This bug could sometimes be triggered by syscall/js code it seems. But
it's a generic bug, not specific to WebAssembly.
2021-03-29 10:16:59 +02:00
Tobias Theel 4be9802d26 throw an error on windows builds with no target specified 2021-03-29 09:35:19 +02:00
Ayke van Laethem 90b42799a2 machine: make machine.I2C0 and similar objects pointers
This makes it possible to assign I2C objects (machine.I2C0,
machine.I2C1, etc.) without needing to take a pointer.

This is important especially in the future when I2C may be driven using
DMA and the machine.I2C type needs to store some state.
2021-03-29 02:02:04 +02:00
Ayke van Laethem 71bbe93ab2 avr: remove I2C stubs from attiny support
These stubs don't really belong there: attiny currently doesn't directly
support I2C at all (although it has hardware to support a software
implementation).
2021-03-29 02:02:04 +02:00
Elliott Sales de Andrade 0535c1bbad Group together STM32 smoke tests.
And then allow them to be disabled with one option.
2021-03-29 01:46:54 +02:00
Ayke van Laethem 4f6d598ea8 reflect: implement Sizeof and Alignof for func values
This is a small change that appears to be necessary for encoding/json
support. It's simple enough to implement.
2021-03-29 01:04:11 +02:00
sago35 16e7dd83a3 gdb: enable to specify multiple candidates for gdb 2021-03-29 00:21:38 +02:00
Olaf Flebbe f23ba3b023 initial support for pca10059
Using the official USB Vendor name even for other boards.
2021-03-28 21:38:05 +02:00
Ayke van Laethem 2aa2e750b9 reflect: implement Value.CanAddr
It is used in the crypto/sha512 test, for example. And it is very simple
to implement.
2021-03-28 19:28:55 +02:00
Ayke van Laethem bcce296ca3 transform: optimize reflect.Type Implements() method
This commit adds a new transform that converts reflect Implements()
calls to runtime.interfaceImplements. At the moment, the Implements()
method is not yet implemented (how ironic) but if the value passed to
Implements is known at compile time the method call can be optimized to
runtime.interfaceImplements to make it a regular interface assert.

This commit is the last change necessary to add basic support for the
encoding/json package. The json package is certainly not yet fully
supported, but some trivial objects can be converted to JSON.
2021-03-28 14:00:37 +02:00
Ayke van Laethem c5ec955081 compiler: fix lack of method name in interface matching
This is required for correctly differentiating between interface types.
2021-03-28 14:00:37 +02:00
Ayke van Laethem 5d334922d7 compiler: add interface IR test
This is important as golden test output and to verify that the output is
correct. Later improvements and bug fixes are clearly visible in the IR,
and unintentional changes will also be immediately spotted.
2021-03-28 14:00:37 +02:00
Ayke van Laethem e9f9a4b750 reflect: fix AssignableTo and Implements methods
They both reversed the direction of the check, in a way that mostly
cancelled each other out. Of course they're still mostly unimplemented,
but it's better if they're not wrong.
2021-03-28 14:00:37 +02:00
Takeshi Yoneda 1406453350 WASI & darwin: support basic file io based on libc
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
2021-03-28 12:37:15 +02:00
Ayke van Laethem 6d3c11627c compiler: fix use of global context: llvm.Int32Type()
This patch fixes a use of the global context. I've seen a few instances
of crashes in the llvm.ConstInt function when called from
makeStructTypeFields, which I believe are caused by this bug.
2021-03-25 13:05:09 +01:00
Ayke van Laethem f800f7507c reflect: check for access in the Interface method call
This fixes a type system loophole. The following program would
incorrectly run in TinyGo, while it would trigger a panic in Go:

    package main

    import "reflect"

    func main() {
        v := reflect.ValueOf(struct {
            x int
        }{})
        x := v.Field(0).Interface()
        println("x:", x.(int))
    }

Playground link: https://play.golang.org/p/nvvA18XFqFC

The panic in Go is the following:

    panic: reflect.Value.Interface: cannot return value obtained from unexported field or method

I've shortened it in TinyGo to save a little bit of space.
2021-03-24 12:19:16 +01:00
Kenneth Bell 46a7993fb8 stm32: i2c implementation for F7, L5 and L4 MCUs 2021-03-24 08:35:34 +01:00
Ayke van Laethem 9f3dcf3733 reflect: implement a number of stub functions
These stub functions are necessary for the encoding/json package. They
don't seem to be called in trivial cases, so leave them as simple stubs
for now.
2021-03-23 15:48:33 +01:00
Ayke van Laethem c849bccb83 reflect: let reflect.Type be of interface type
This matches the main Go implementation and (among others) fixes a
compatibility issue with the encoding/json package. The encoding/json
package compares reflect.Type variables against nil, which does not work
as long as reflect.Type is of integer type.

This also adds a reflect.RawType() function (like reflect.Type()) that
makes it easier to avoid working with interfaces in the runtime package.
It is internal only, but exported to let the runtime package use it.

This change introduces a small code size increase when working with the
reflect package, but I've tried to keep it to a minimum. Most programs
that don't make extensive use of the reflect package (and don't use
package like fmt) should not be impacted by this.
2021-03-23 14:32:33 +01:00
Ayke van Laethem cffe424849 interp: add support for runtime.interfaceMethod
This is necessary so that when reflect.Type is converted from a concrete
type to an interface type, the errors package can still be interpreted.
Without this change, basically every program would grow in size by a few
bytes.
2021-03-23 14:32:33 +01:00
Ayke van Laethem 51938e9d1c interp: handle (reflect.Type).Elem()
The errors package has a call like this in the package initializer. This
commit adds support for running it at compile time, avoiding the call at
runtime.

This doesn't always help (the call is already optimized away in many
small programs) but it does help to shave off some binary size in larger
programs. Perhaps more importantly, it will avoid a penalty in code size
when the reflect package will convert reflect.Type from a regular type
to an interface type.
2021-03-23 14:32:33 +01:00
Ayke van Laethem 19dec048b0 compiler: do not check for impossible type asserts
Previously there was code to avoid impossible type asserts but it wasn't
great and in fact was too aggressive when combined with reflection.

This commit improves this by checking all types that exist in the
program that may appear in an interface (even struct fields and the
like) but without creating runtime.typecodeID objects with the type
assert. This has two advantages:

  * As mentioned, it optimizes impossible type asserts away.
  * It allows methods on types that were only asserted on (in
    runtime.typeAssert) but never used in an interface to be optimized
    away using GlobalDCE. This may have a cascading effect so that other
    parts of the code can be further optimized.

This sometimes massively improves code size and mostly negates the code
size regression of the previous commit.
2021-03-23 14:32:33 +01:00
Ayke van Laethem bbb2909283 compiler: merge runtime.typecodeID and runtime.typeInInterface
This distinction was useful before when reflect wasn't properly
supported. Back then it made sense to only include method sets that were
actually used in an interface. But now that it is possible to get to
other values (for example, by extracting fields from structs) and it is
possible to turn them back into interfaces, it is necessary to preserve
all method sets that can possibly be used in the program in a type
assert, interface assert or interface method call.

In the future, this logic will need to be revisited again when
reflect.New or reflect.Zero gets implemented.

Code size increases a bit in some cases, but usually in a very limited
way (except for one outlier in the drivers smoke tests). The next commit
will improve the situation significantly.
2021-03-23 14:32:33 +01:00
Kenneth Bell aa7c7b7bd9 lgt92: update to new UART structure 2021-03-23 08:33:59 +01:00
Kenneth Bell c7bd5405c3 Add support for nucleol432 board
LED and UART are working
2021-03-23 08:33:59 +01:00
Kenneth Bell dc981ce509 stm32: separate altfunc selection for UART Tx/Rx
This is needed for stm32l432 nucleo with different altfun for tx and rx
2021-03-23 08:33:59 +01:00
Ayke van Laethem c522569378 wasm: only export explicitly exported functions
Previously we used the --export-all linker flag to export most
functions. However, this is not needed and possibly increases binary
size. Instead, we should be exporting the specific functions to be
exported.
2021-03-22 13:48:12 +01:00
Ayke van Laethem 0db4b13e37 compiler: do not emit nil checks for *ssa.Alloc instructions
An allocated object is never nil, so there is no need for a nil check.
This probably does not result in any better optimization (the nil check
is easily optimized away by LLVM because the result of runtime.alloc is
marked nonnull) but it makes the slice tests a bit cleaner.
2021-03-22 11:35:06 +01:00
Ayke van Laethem 2709d38d63 compiler: add some more slice tests 2021-03-22 11:35:06 +01:00
Ayke van Laethem 71d1b70ab7 compiler: only run tests on LLVM 11 or above
It's difficult to create clean test cases while remaining compatible
with multiple LLVM versions. Most test outputs are much more readable
after an instcombine pass but instcombine rules change between LLVM
versions, leading to different (but semantically equivalent) test
outputs.

This reduces the test coverage a little bit (because old LLVM versions
aren't tested as well), but it als makes it easier to add more complex
tests.

In the future it might be a good idea to make the compiler output a bit
less messy so these workarounds are not needed.
2021-03-22 11:35:06 +01:00
Ayke van Laethem 24676d4366 compiler: add tests for strings
This tests a few common operations on strings.
2021-03-22 11:35:06 +01:00
Ayke van Laethem e2f532709f builder, compiler: compile and cache packages in parallel
This commit switches from the previous behavior of compiling the whole
program at once, to compiling every package in parallel and linking the
LLVM bitcode files together for further whole-program optimization.
This is a small performance win, but it has several advantages in the
future:

  - There are many more things that can be done per package in parallel,
    avoiding the bottleneck at the end of the compiler phase. This
    should speed up the compiler futher.
  - This change is a necessary step towards a non-LTO build mode for
    fast incremental builds that only rebuild the changed package, when
    compiler speed is more important than binary size.
  - This change refactors the compiler in such a way that it will be
    easier to inspect the IR for one package only. Inspecting this IR
    will be very helpful for compiler developers.
2021-03-21 11:51:35 +01:00
Ayke van Laethem dc1ff80e10 compiler: remove SimpleDCE pass
The SimpleDCE pass was previously used to only compile the parts of the
program that were in use. However, lately the only real purpose has been
to speed up the compiler a bit by only compiling the necessary
functions.

This pass however is a problem for compiling (and caching) packages in
parallel. Therefore, this commit removes it as a preparatory step
towards that goal.
2021-03-21 11:51:35 +01:00
Kenneth Bell b5205cc3ca stm32: move f103 (bluepill) to common i2c code 2021-03-21 11:25:10 +01:00
akif999 a075cbedf5 fix msd-volume-name (qtpy)
at issue #1713
2021-03-21 10:00:43 +01:00
Kenneth Bell ef613a5db7 stm32: housekeeping - remove empty file 2021-03-21 00:45:29 +01:00
sago35 1571b8fd34 Add special handling when SPI Freq is 24Mhz 2021-03-19 17:34:49 +01:00
sago35 a41b72578b atsamd21: improve SPI 2021-03-19 17:34:49 +01:00
Kenneth Bell 9f3f9d05b8 housekeeping: ignore files generated by smoketests 2021-03-19 12:42:09 +01:00
Ayke van Laethem f9865a08bc transform: optimize string comparisons against ""
This optimizes a common pattern like:

    if s != "" {
        ...
    }

to:

    if len(s) != 0 {
        ...
    }

This avoids a runtime call and thus produces slightly better code.
2021-03-18 17:22:00 +01:00
Ayke van Laethem 13db2c13e5 compiler: do not use llvm.GlobalContext()
This is a leftover from a long time ago, when everything was still in
the global context. The fact that this uses the global context is most
certainly a bug.

I have seen occasional crashes in the build-packages-indepedently branch
(and PRs based on it) which I suspect are caused by this bug. I think
this is a long-dormant bug that only surfaced when doing the compilation
steps in parallel.
2021-03-18 16:49:33 +01:00
Kenneth Bell ce8ad3650a stm32l0: use unified UART logic 2021-03-18 12:10:36 +01:00
kenbell b0b84c48ec Harmonize stm32 ticks and sleep (#1673)
machine/stm32f*: move to harmonized tick / sleep logic code
2021-03-18 11:54:15 +01:00
Ayke van Laethem 5a4dcfb367 builder: add support for -opt=0
This optimization level wasn't working before because some passes expect
some globals to be cleaned up afterwards. Cleaning these globals is
easy, just add the pass necessary for it. This shouldn't reduce the
usefulness of the -opt=0 build flag as most optimizations are still
skipped.
2021-03-15 19:36:21 +01: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
Ayke van Laethem 15361c829e main: release 0.16.0 2020-11-17 11:15:15 +01:00
Ayke van Laethem 3f680b75f3 sam: remove redundant build tags
Some build tags were duplicated. This commit removes them.
2020-11-15 16:19:51 +01:00
Ayke van Laethem 9a7e633997 teensy36: add to smoketest
This required some changes to the UART code to get it to compile on Go
1.11.
2020-11-15 13:08:36 +01:00
ardnew 39b1f8b6f5 teensy40: UART: add missing godocs, rename Flush to Sync 2020-11-15 12:34:15 +01:00
ardnew 9aa50853b8 teensy40: add UART support 2020-11-15 12:34:15 +01:00
Ayke van Laethem 9ca0e3f2d1 Makefile: fix issue with Go 1.15.5
For details, see https://github.com/golang/go/issues/42606
2020-11-14 15:04:11 +01:00
ardnew 3cdc110462 teensy40: use implicit const defs (PinMode/PinChange) 2020-11-13 07:53:16 +01:00
ardnew 7cc687d416 teensy40: Add GPIO external interrupt support 2020-11-13 07:53:16 +01:00
Ron Evans ce57a034c3 ci: update CircleCI, Azure, and Docker builds to Go 1.15
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-11-13 03:07:35 +01:00
ardnew 30bee3afef add better fault identification for Cortex-M3/M33/M4/M7 hardfault handlers, add fault description registers to SCB_Type 2020-11-11 18:34:47 +01:00
ardnew b1d24a72c1 teensy40: fix typo in target JSON 2020-11-11 18:34:47 +01:00
ardnew 6e24c86320 teensy40: remove FPU spec in target JSON list of cflags 2020-11-11 18:34:47 +01:00
ardnew 19a0270303 teensy40: refactor to remove unnecessary code and constants 2020-11-11 18:34:47 +01:00
ardnew 47410a4b54 teensy40: init RTC and use ARM cycle counter for improved SysTick accuracy 2020-11-11 18:34:47 +01:00
ardnew 0d9c46b59e teensy40: fix PIT clock, which actually uses 24 MHz OSC
see: https://forum.pjrc.com/threads/63979-What-peripherals-are-affected-by-the-undocumented-24-MHz-OSC-circuit-on-Teensy-4-0
2020-11-11 18:34:47 +01:00
ardnew f93b28057a mimxrt1062: move device-specific files to "device/nxp" package 2020-11-11 18:34:47 +01:00
ardnew 691185f5f4 teensy40: initial implementation 2020-11-11 18:34:47 +01:00
Ayke van Laethem 163df7670a main: update go-llvm to fix LLVM build tags for Linux
This should make TinyGo buildable again on Darwin with a Homebrew
installed LLVM.
2020-11-09 18:45:37 +01:00
deadprogram 77c70d2758 machine/qtpy: add board definition for Adafruit QTPy
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-11-08 22:56:01 +01:00
Ayke van Laethem 7c4e83f5c0 machine: clarify caller's responsibility in SetInterrupt 2020-11-08 15:11:50 +01:00
jypelle db27541b1a Fix #1483 2020-11-08 09:32:13 +01:00
tom-horn 3bb994da9f Allow interrupts in stm32f103xx (#1466)
machine/stm32f103xx: allow interrupts in stm32f103xx
2020-11-07 12:21:38 +01:00
Elliott Sales de Andrade b3bd891ee0 Make lib64 clang include path check more robust.
On Fedora 33+, there is a buggy package that installs to
`/usr/lib64/clang/{version}/lib`, even on 32-bit systems. The original
code sees the `/usr/lib64/clang/{version}` directory, checks for an
`include` subdirectory, and then gives up because it doesn't exist.

To be more robust, check both `/usr/lib64/clang/{version}/include` and
`/usr/lib/clang/{version}/include`, and only allow versions that match
the LLVM major version used to build tinygo.
2020-11-04 00:04:33 +01:00
Lucas Teske 387bca8e32 nintendoswitch: Add env parser and removed unused stuff
*	Heap allocation based on available ram
*	Added homebrew launcher parser (for overriden heap)
*	Removed unused stuff (moved to gonx)
*	Kept require code at minimum to work in a real device
*	Moved everything to a single file
2020-11-03 23:28:55 +01:00
Nia Weiss d424b3d7ea add missing return pointer restore for regular coroutine tail calls
This fixes an issue where a normal suspending call followed by a plain tail call would result in the tail return value being written to the return pointer of the normal suspending call.
This is fixed by saving the return pointer at the start of the function and restoring it before initiating a plain tail call.
2020-11-01 08:32:55 +01:00
deadprogram c20328472b make: fixes error detecting llvm-nm tool for wasi-libc build
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-11-01 01:12:39 +01:00
Ayke van Laethem 171f793c1e avr: properly support the .rodata section
Unfortunately, the .rodata section can't be stored in flash. Instead, an
explicit .progmem section should be used, which is supported in LLVM as
address space 1 but not exposed to normal programs.

Eventually a pass should be written that converts trivial const globals
of which all loads are visible to be in addrspace 1, to get the benefits
of storing those globals directly in ROM.
2020-10-31 21:06:26 +01:00
Nia Weiss 3364da6f25 improve autodetection of LLVM tooling
Previously, our autodetection would fail on LLVM 11.
This now checks all common names for LLVM 10 and 11.
2020-10-31 18:17:38 +01:00
Ayke van Laethem e99b8a24fe runtime: allow ranging over a nil map
This appears to be allowed by the specification, at least it is allowed
by the main Go implementation: https://play.golang.org/p/S8jxAMytKDB

Allow it in TinyGo too, for consistency.

Found because it is triggered with `tinygo test flags`. This doesn't
make the flags package pass all tests, but is a step closer.
2020-10-29 21:53:41 +01:00
Ayke van Laethem 69e1aa4878 testing: add Run method
This patch adds subtests via the Run function. This gets two more
packages to pass tests: encoding/base32 and hash/fnv.
2020-10-28 18:25:56 +01:00
Ayke van Laethem 7a78b2dc0e all: replace underscores with dashes in target names
This is the convention, so use it everywhere.
2020-10-28 12:40:54 +01:00
Ayke van Laethem 2b1d4ce96e main: update go.sum 2020-10-28 08:48:11 +01:00
Ayke van Laethem 3e40b08ba0 compiler: implement negate for complex numbers 2020-10-28 07:38:51 +01:00
Takeshi Yoneda ffeff55706 wasm: use the fixed length buffer for putchar
Signed-off-by: mathetake <takeshi@tetrate.io>
2020-10-23 22:04:32 +02:00
Takeshi Yoneda 1dec9dcbc4 implement reflect.Swapper
Signed-off-by: mathetake <takeshi@tetrate.io>
2020-10-23 21:37:35 +02:00
Martin Tournoij ff833ef998 Add os.LookupEnv() stub
os.Getenv() was already stubbed out, but os.LookupEnv() wasn't. This
will allow me to compile my program unmodified without using separate
files and build tags.
2020-10-23 14:39:15 +02:00
Connor 6eeebfeb5c WIP: Esp8266 Get Function (#1438)
machine/esp8266: add Pin Get() support
2020-10-22 20:58:44 +02:00
Ayke van Laethem 6ab0106af3 nrf: fix nrf52832 flash size
I've accidentally specified just half of the available flash in the
linker script. This change fixes that.

There is in fact a 256kB version of the nrf52832, but it also has 32kB
of RAM so if you had used that it wouldn't actually work right now.
Also, extending the available flash should not affect existing programs
(as I haven't seen any run into size limitations yet).
2020-10-20 19:13:43 +02:00
蒼時弦也 e690ff0d8c Add instanceof support for WebAssembly 2020-10-18 22:15:47 +02:00
Ayke van Laethem 06564cbdb2 Switch default frequency to 4MHz
Let's use the same default frequency everywhere, for consistency.
It could be any frequency, but 4MHz is already used for other chips and
it seems like a reasonable frequency to me (not too fast for most chips
but still reasonably fast). Oh, and 4MHz is slow enough that it can be
inspected by a Saleae Logic 4 (that sadly has been discontinued).
2020-10-18 22:14:21 +02:00
Ayke van Laethem 47dc76fc34 nrf: give more flexibility in picking SPI speeds
Instead of only allowing a limited number of speeds, use the provided
speed as an upper bound on the allowed speed. The reasoning is that
picking a higher speed than requrested will likely result in malfunction
while picking a lower speed will usually only result in slower
operation.
This behavior matches the ESP32 at least.
2020-10-18 22:11:03 +02:00
deadprogram d382f3a259 esp8266: add target for d1mini board and add pin mappings for SPI/I2C to help out implementers
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-10-17 19:57:53 +02:00
Ayke van Laethem 47dc1087e8 ci: work around bug in Go 1.15.3
This works around a bug in Go 1.15.3 by pinning an older version.
See: https://github.com/golang/go/issues/42032
2020-10-17 12:23:03 +02:00
deadprogram c7d8223ab7 machine/esp32, targets/esp32: correct board definitions for actual boards not processor variants, also define all labeled pins
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-10-14 21:48:48 +02:00
ardnew 3eb33dff5d feather-stm32f405: add I2C support (#1378)
* machine/stm32f405: add initial I2C support
2020-10-14 18:00:24 +02:00
deadprogram bb146edb47 wasi: remove --no-threads flag as no longer in LLVM 11 linker
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-10-14 13:58:01 +02:00
Nia Weiss ed9b97cc0d runtime: add cheap atomic condition variable 2020-10-14 13:35:00 +02:00
Ayke van Laethem b40f250530 main: add initial support for (in-development) LLVM 11
This can be useful to test improvements in LLVM master and to make it
possible to support LLVM 11 for the most part already before the next
release. That also allows catching LLVM bugs early to fix them upstream.

Note that tests do not yet pass for this LLVM version, but the TinyGo
compiler can be built with the binaries from apt.llvm.org (at the time
of making this commit).
2020-10-13 20:23:50 +02:00
ardnew 184175378f gen-device-svd: ensure enum bitfields are unique 2020-10-10 12:52:11 +02:00
Ayke van Laethem d8dbe5748a testing: implement some benchmark stubs
This allows the following packages to pass tests:

  * crypto/des
  * encoding/hex

I have not included crypto/rc4 as it doesn't pass tests on Go 1.11 (but
it works on later versions).
2020-10-09 15:22:19 +02:00
Lucas Teske c2bfe6bc8d arm64: Add support for system calls (SVC) 2020-10-03 20:07:51 +02:00
Takeshi Yoneda 9a015f4f64 add wasm-abi field in TargetSpec && set generic for WASI by default (#1421)
Signed-off-by: mathetake <takeshi@tetrate.io>
2020-10-03 19:52:01 +02:00
ardnew 9ad2315079 feather-stm32f405: add SPI support (#1377)
* machine/stm32f405: add SPI support
2020-10-02 20:05:58 +02:00
Ayke van Laethem 431e51b8a0 runtime: use dedicated printfloat32
It can be unexpected that printing a float32 involves 64-bit floating
point routines, see for example:
https://github.com/tinygo-org/tinygo/issues/1415

This commit adds a dedicated printfloat32 instead just for printing
float32 values. It comes with a possible code size increase, but only if
both float32 and float64 values are printed. Therefore, this should be
an improvement in almost all cases.

I also tried using printfloat32 for everything (and casting a float64 to
float32 to print) but the printed values are slightly different,
breaking the testdata/math.go test for example.
2020-10-02 11:26:22 +02:00
Ayke van Laethem 67de8b490d gc: use raw stack access whenever possible
The only architecture that actually needs special support for scanning
the stack is WebAssembly. All others allow raw access to the stack with
a small bit of assembly. Therefore, don't manually keep track of all
these objects on the stack manually and instead just use conservative
stack scanning.

This results in a massive code size decrease in the affected targets
(only tested linux/amd64 for code size) - sometimes around 33%. It also
allows for future improvements such as using proper stackful goroutines.
2020-10-02 08:54:43 +02:00
Ayke van Laethem bfa29f17da runtime: move/refactor some GC-related code
Instead of putting tinygo_scanCurrentStack in scheduler_*.S files, put
them in dedicated files. The function tinygo_scanCurrentStack has
nothing to do with scheduling and so doesn't belong there. Additionally,
while scheduling code is made specific for the Cortex-M, the
tinygo_scanCurrentStack is generic to all ARM targets so this move
removes some duplication there.

Specifically:

  * tinygo_scanCurrentStack is moved out of scheduler_cortexm.S as it
    isn't really part of the scheduler. It is now gc_arm.S.
  * Same for the AVR target.
  * Same for the RISCV target.
  * scheduler_gba.S is removed, using gc_arm.S instead as it only
    contains tinygo_scanCurrentStack.
2020-10-02 08:54:43 +02:00
Ayke van Laethem 7123941df0 main: add support for debugging qemu-user targets
This commit allows debugging like the following:

    GOARCH=arm tinygo gdb ./testdata/alias.go

This can be very useful to debug issues on a different instruction set
architecture but still on a host system.

I tested the following 7 configurations to make sure it works and I
didn't break anything:

    GOOS=amd64
    GOOS=386
    GOOS=arm
    GOOS=arm64
    tinygo gdb -target=hifive1-qemu
    tinygo gdb -target=cortex-m-qemu
    tinygo gdb -target=microbit
2020-10-02 08:54:43 +02:00
Ayke van Laethem 05d2f2c412 main: improve support for x86-32 and add tests
To avoid breaking this, make sure we actually test x86-32 (aka i386 aka
GOARCH=386) support in CI.

Also remove the now-unnecessary binutils-arm-none-eabi package to speed
up CI a bit.
2020-10-02 08:54:43 +02:00
Ayke van Laethem 9a12d129ab testing: implement dummy Helper method
This lets a few more packages pass tests: container/heap and
encoding/ascii85.
2020-10-01 02:14:59 +02:00
Ayke van Laethem 0ecfe9eade ci: fix make wasmtest
This fixes issue https://github.com/tinygo-org/tinygo/issues/1418. In
short, it appears there was a race condition that was only visible on
GOARCH=386 but not on GOARCH=amd64. Updating to a more recent chromedp
version fixes the issue.
2020-10-01 02:07:00 +02:00
Takeshi Yoneda f50ad3585d support WASI target (#1373)
* initial commit for WASI support

* merge "time" package with wasi build tag
* override syscall package with wasi build tag
* create runtime_wasm_{js,wasi}.go files
* create syscall_wasi.go file
* create time/zoneinfo_wasi.go file as the replacement of zoneinfo_js.go
* add targets/wasi.json target

* set visbility hidden for runtime extern variables

Accodring to the WASI docs (https://github.com/WebAssembly/WASI/blob/master/design/application-abi.md#current-unstable-abi),
none of exports of WASI executable(Command) should no be accessed.

v0.19.0 of bytecodealliance/wasmetime, which is often refered to as the reference implementation of WASI,
does not accept any exports except functions and the only limited variables like "table", "memory".

* merge syscall_{baremetal,wasi}.go

* fix js target build

* mv wasi functions to syscall/wasi && implement sleepTicks

* WASI: set visibility hidden for globals variables

* mv back syscall/wasi/* to runtime package

* WASI: add test

* unexport wasi types

* WASI test: fix wasmtime path

* stop changing visibility of runtime.alloc

* use GOOS=linux, GOARCH=arm for wasi target

Signed-off-by: mathetake <takeshi@tetrate.io>

* WASI: fix build tags for os/runtime packages

Signed-off-by: mathetake <takeshi@tetrate.io>

* run WASI test only on Linux

Signed-off-by: mathetake <takeshi@tetrate.io>

* set InternalLinkage instead of changing visibility

Signed-off-by: mathetake <takeshi@tetrate.io>
2020-09-29 21:58:03 +02:00
Ayke van Laethem d39c7abb4d nrf: fix double stop signal in I2C 2020-09-27 15:21:54 +02:00
Daniel M. Lambea 9e61e6fe4d nrf: add I2C error checking (#1392)
* machine/nrf: add I2C error checking
2020-09-26 09:20:43 +02:00
Lucas Teske d1f90ef59c nintendoswitch: fix crash when printing long lines (> 120) 2020-09-26 02:52:10 +02:00
Ayke van Laethem 54602fe0c3 main: add support for -c and -o flags to tinygo test 2020-09-25 16:18:10 +02:00
Ayke van Laethem c10dcd429c main: refactor -o flag 2020-09-25 16:18:10 +02:00
Ayke van Laethem b713001313 test: support non-host tests
For example, for running tests with -target=wasm or
-target=cortex-m-qemu. It looks at the output to determine whether tests
were successful in the absence of a status code.
2020-09-24 21:17:26 +02:00
Ayke van Laethem 1096596b69 compiler: fix floating point bugs
There were a few bugs related to floating point. After fixing these, the
math package started passing all tests.
2020-09-21 10:43:46 +02:00
Ayke van Laethem 7b601b3e3c loader: fix linkname in test binaries
This is an issue in particular in the math package, of which most
functions are defined in the runtime package.
2020-09-21 10:43:46 +02:00
Ayke van Laethem ec54e7763d runtime: fix UTF-8 decoding
The algorithm now checks for invalid UTF-8 sequences, which is required
by the Go spec.

This gets the tests of the unicode/utf8 package to pass.

Also add bytes.Equal for Go 1.11, which again is necessary for the
unicode/utf8 package.
2020-09-21 08:49:13 +02:00
Elliott Sales de Andrade 41afb77080 builder: also check lib64 for clang include path.
On 64-bit Fedora, `lib64` is where the clang headers are, not `lib`. For
multiarch systems, both will exist, but it's likely you want 64-bit, so
check that first.
2020-09-20 14:00:02 +02:00
Jacques Supcik 13fe668929 compileopts: simplify copyProperties using reflection 2020-09-20 13:49:16 +02:00
sago35 2a72262c33 version: update TinyGo version to 0.16.0-dev 2020-09-18 01:32:31 +02:00
686 changed files with 38867 additions and 9658 deletions
+75 -121
View File
@@ -28,6 +28,7 @@ commands:
qemu-user \
gcc-avr \
avr-libc
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-8-dev
install-node:
steps:
- run:
@@ -44,6 +45,13 @@ commands:
command: |
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install ./google-chrome-stable_current_amd64.deb
install-wasmtime:
steps:
- run:
name: "Install wasmtime"
command: |
curl https://wasmtime.dev/install.sh -sSf | bash
sudo ln -s ~/.wasmtime/bin/wasmtime /usr/local/bin/wasmtime
install-xtensa-toolchain:
parameters:
variant:
@@ -60,24 +68,27 @@ commands:
steps:
- restore_cache:
keys:
- llvm-source-10-v1
- llvm-source-11-v2
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-v1
key: llvm-source-11-v2
paths:
- llvm-project
- llvm-project/clang/lib/Headers
- llvm-project/clang/include
- llvm-project/lld/include
- llvm-project/llvm/include
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:
@@ -91,6 +102,7 @@ commands:
llvm: "<<parameters.llvm>>"
- install-node
- install-chrome
- install-wasmtime
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
@@ -99,13 +111,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
@@ -123,9 +135,9 @@ commands:
- run:
name: "Install apt dependencies"
command: |
sudo apt-get update
sudo apt-get install \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
@@ -133,7 +145,9 @@ commands:
qemu-user \
gcc-avr \
avr-libc
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
- install-node
- install-wasmtime
- install-xtensa-toolchain:
variant: "linux-amd64"
- restore_cache:
@@ -143,25 +157,26 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-10-linux-v1-assert
- llvm-build-11-linux-v4-assert
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# 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
# build!
make ASSERT=1 llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-10-linux-v1-assert
key: llvm-build-11-linux-v4-assert
paths:
llvm-build
- run: make ASSERT=1
@@ -169,6 +184,11 @@ commands:
- run:
name: "Test TinyGo"
command: make ASSERT=1 test
environment:
# Note: -p=2 limits parallelism to two jobs at a time, which is
# necessary to keep memory consumption down and avoid OOM (for a
# 2CPU/4GB executor).
GOFLAGS: -p=2
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
@@ -183,9 +203,9 @@ commands:
- run:
name: "Install apt dependencies"
command: |
sudo apt-get update
sudo apt-get install \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
@@ -193,7 +213,9 @@ commands:
qemu-user \
gcc-avr \
avr-libc
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
- install-node
- install-wasmtime
- install-xtensa-toolchain:
variant: "linux-amd64"
- restore_cache:
@@ -203,25 +225,26 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-10-linux-v1
- llvm-build-11-linux-v4-noassert
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# 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
# build!
make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-10-linux-v1
key: llvm-build-11-linux-v4-noassert
paths:
llvm-build
- build-wasi-libc
@@ -263,51 +286,58 @@ 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:
variant: "macos"
- restore_cache:
keys:
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v2-{{ checksum "go.mod" }}
- go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v3-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-10-macos-v1
- llvm-source-11-macos-v3
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-macos-v1
key: llvm-source-11-macos-v3
paths:
- llvm-project
- llvm-project/clang/lib/Headers
- llvm-project/clang/include
- llvm-project/lld/include
- llvm-project/llvm/include
- restore_cache:
keys:
- llvm-build-10-macos-v1
- llvm-build-11-macos-v5
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# build!
make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-10-macos-v1
key: llvm-build-11-macos-v5
paths:
llvm-build
- restore_cache:
keys:
- wasi-libc-sysroot-macos-v2
- wasi-libc-sysroot-macos-v4
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-macos-v2
key: wasi-libc-sysroot-macos-v4
paths:
- lib/wasi-libc/sysroot
- run:
@@ -329,78 +359,12 @@ commands:
tinygo version
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
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
@@ -413,12 +377,18 @@ jobs:
steps:
- test-linux:
llvm: "10"
test-llvm10-go115:
test-llvm11-go115:
docker:
- image: circleci/golang:1.15-buster
steps:
- test-linux:
llvm: "10"
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
@@ -431,35 +401,19 @@ jobs:
- build-linux
build-macos:
macos:
xcode: "10.1.0"
xcode: "11.1.0" # macOS 10.14
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-llvm10-go115
- 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]+)*(-.*)*/
@@ -0,0 +1,45 @@
name: CI for tinygo-dev docker container
on:
push:
branches: [ dev ]
jobs:
push_to_registry:
name: Push Docker image to GHCR/Docker Hub
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v2
with:
submodules: recursive
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: |
tinygo/tinygo-dev
ghcr.io/${{ github.repository }}/tinygo-dev
tags: |
type=sha,format=long
type=raw,value=latest
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Log in to Github Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+9
View File
@@ -16,6 +16,15 @@ src/device/stm32/*.go
src/device/stm32/*.s
src/device/kendryte/*.go
src/device/kendryte/*.s
src/device/rp/*.go
src/device/rp/*.s
vendor
llvm-build
llvm-project
# Ignore files generated by smoketest
test.gba
test.hex
test.nro
test.wasm
wasm.wasm
+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
+332
View File
@@ -1,3 +1,335 @@
0.19.0
---
* **command line**
- don't consider compile-only tests as failing
- add -test flag for `tinygo list`
- escape commands while printing them with the -x flag
- make flash-command portable and safer to use
- use `extended-remote` instead of `remote` in GDB
- detect specific serial port IDs based on USB vid/pid
- add a flag to the command line to select the serial implementation
* **compiler**
- `cgo`: improve constant parser
- `compiler`: support chained interrupt handlers
- `compiler`: add support for running a builtin in a goroutine
- `compiler`: do not emit nil checks for loading closure variables
- `compiler`: skip context parameter when starting regular goroutine
- `compiler`: refactor method names
- `compiler`: add function and global section pragmas
- `compiler`: implement `syscall.rawSyscallNoError` in inline assembly
- `interp`: ignore inline assembly in markExternal
- `interp`: fix a bug in pointer cast workaround
- `loader`: fix testing a main package
* **standard library**
- `crypto/rand`: replace this package with a TinyGo version
- `machine`: make USBCDC global a pointer
- `machine`: make UART objects pointer receivers
- `machine`: define Serial as the default output
- `net`: add initial support for net.IP
- `net`: add more net compatibility
- `os`: add stub for os.ReadDir
- `os`: add FileMode constants from Go 1.16
- `os`: add stubs required for net/http
- `os`: implement process related functions
- `reflect`: implement AppendSlice
- `reflect`: add stubs required for net/http
- `runtime`: make task.Data a 64-bit integer to avoid overflow
- `runtime`: expose memory stats
- `sync`: implement NewCond
- `syscall`: fix int type in libc version
* **targets**
- `cortexm`: do not disable interrupts on abort
- `cortexm`: bump default stack size to 2048 bytes
- `nrf`: avoid heap allocation in waitForEvent
- `nrf`: don't trigger a heap allocation in SPI.Transfer
- `nrf52840`: add support for flashing with the BOSSA tool
- `rp2040`: add support for GPIO input
- `rp2040`: add basic support for ADC
- `rp2040`: gpio and adc pin definitions
- `rp2040`: implement UART
- `rp2040`: patch elf to checksum 2nd stage boot
- `stm32`: add PWM for most chips
- `stm32`: add support for pin interrupts
- `stm32f103`: add support for PinInputPullup / PinInputPulldown
- `wasi`: remove wasm build tag
* **boards**
- `feather-rp2040`: add support for this board
- `feather-nrf52840-sense`: add board definition for this board
- `pca10059`: support flashing from Windows
- `nano-rp2040`: add this board
- `nano-33-ble`: add support for this board
- `pico`: add the Raspberry Pi Pico board with the new RP2040 chip
- `qtpy`: add pin for neopixels
- all: add definition for ws2812 for supported boards
0.18.0
---
* **command line**
- drop support for Go 1.11 and 1.12
- throw an error when no target is specified on Windows
- improve error messages in `getDefaultPort()`, support for multiple ports
- remove `-cflags` and `-ldflags` flags
- implement `-ldflags="-X ..."`
- add `-print-allocs` flag that lets you print all heap allocations
- openocd commands in tinygo command line
- add `-llvm-features` parameter
- match `go test` output
- discover USB ports only, this will ignore f.ex. bluetooth
- use physicmal path instead of cached GOROOT in function getGoroot
- add goroot for snap installs
* **compiler**
- `builder`: add support for `-opt=0`
- `builder`, `compiler`: compile and cache packages in parallel
- `builder`: run interp per package
- `builder`: cache C and assembly file outputs
- `builder`: add support for `-x` flag to print commands
- `builder`: add optsize attribute while building the package
- `builder`: run function passes per package
- `builder`: hard code Clang compiler
- `compiler`: do not use `llvm.GlobalContext()`
- `compiler`: remove SimpleDCE pass
- `compiler`: do not emit nil checks for `*ssa.Alloc` instructions
- `compiler`: merge `runtime.typecodeID` and runtime.typeInInterface
- `compiler`: do not check for impossible type asserts
- `compiler`: fix use of global context: `llvm.Int32Type()`
- `compiler`: add interface IR test
- `compiler`: fix lack of method name in interface matching
- `compiler`: fix "fragment covers entire variable" bug
- `compiler`: optimize string literals and globals
- `compiler`: decouple func lowering from interface type codes
- `compiler`: add function attributes to some runtime calls
- `compiler`: improve position information in error messages
- `cgo`: add support for CFLAGS in .c files
- `interp`: support GEP on fixed (MMIO) addresses
- `interp`: handle `(reflect.Type).Elem()`
- `interp`: add support for runtime.interfaceMethod
- `interp`: make toLLVMValue return an error instead of panicking
- `interp`: add support for switch statement
- `interp`: fix phi instruction
- `interp`: remove map support
- `interp`: support extractvalue/insertvalue with multiple operands
- `transform`: optimize string comparisons against ""
- `transform`: optimize `reflect.Type` `Implements()` method
- `transform`: fix bug in interface lowering when signatures are renamed
- `transform`: don't rely on struct name of `runtime.typecodeID`
- `transform`: use IPSCCP pass instead of the constant propagation pass
- `transform`: fix func lowering assertion failure
- `transform`: do not lower zero-sized alloc to alloca
- `transform`: split interface and reflect lowering
* **standard library**
- `runtime`: add dummy debug package
- `machine`: fix data shift/mask in newUSBSetup
- `machine`: make `machine.I2C0` and similar objects pointers
- `machine`: unify usbcdc code
- `machine`: refactor PWM support
- `machine`: avoid heap allocations in USB code
- `reflect`: let `reflect.Type` be of interface type
- `reflect`: implement a number of stub functions
- `reflect`: check for access in the `Interface` method call
- `reflect`: fix `AssignableTo` and `Implements` methods
- `reflect`: implement `Value.CanAddr`
- `reflect`: implement `Sizeof` and `Alignof` for func values
- `reflect`: implement `New` function
- `runtime`: implement command line arguments in hosted environments
- `runtime`: implement environment variables for Linux
- `runtime`: improve timers on nrf, and samd chips
* **targets**
- all: use -Qunused-arguments only for assembly files
- `atmega1280`: add PWM support
- `attiny`: remove dummy UART
- `atsamd21`: improve SPI
- `atsamd51`: fix PWM support in atsamd51p20
- `atsamd5x`: improve SPI
- `atsamd51`, `atsame5x`: unify samd51 and same5x
- `atsamd51`, `atsamd21`: fix `ADC.Get()` value at 8bit and 10bit
- `atsame5x`: add support for CAN
- `avr`: remove I2C stubs from attiny support
- `cortexm`: check for `arm-none-eabi-gdb` and `gdb-multiarch` commands
- `cortexm`: add `__isr_vector` symbol
- `cortexm`: disable FPU on Cortex-M4
- `cortexm`: clean up Cortex-M target files
- `fe310`: fix SPI read
- `gameboy-advance`: Fix RGBA color interpretation
- `nrf52833`: add PWM support
- `stm32l0`: use unified UART logic
- `stm32`: move f103 (bluepill) to common i2c code
- `stm32`: separate altfunc selection for UART Tx/Rx
- `stm32`: i2c implementation for F7, L5 and L4 MCUs
- `stm32`: make SPI CLK fast to fix data issue
- `stm32`: support SPI on L4 series
- `unix`: avoid possible heap allocation with `-opt=0`
- `unix`: use conservative GC by default
- `unix`: use the tasks scheduler instead of coroutines
- `wasi`: upgrade WASI version to wasi_snapshot_preview1
- `wasi`: darwin: support basic file io based on libc
- `wasm`: only export explicitly exported functions
- `wasm`: use WASI ABI for exit function
- `wasm`: scan globals conservatively
* **boards**
- `arduino-mega1280`: add support for the Arduino Mega 1280
- `arduino-nano-new`: Add Arduino Nano w/ New Bootloader target
- `atsame54-xpro`: add initial support this board
- `feather-m4-can`: add initial support for this board
- `grandcentral-m4`: add board support for Adafruit Grand Central M4 (SAMD51)
- `lgt92`: update to new UART structure
- `microbit`: remove LED constant
- `microbit-v2`: add support for S113 SoftDevice
- `nucleol432`: add support for this board
- `nucleo-l031k6`: add this board
- `pca10059`: initial support for this board
- `qtpy`: fix msd-volume-name
- `qtpy`: fix i2c setting
- `teensy40`: move txBuffer allocation to UART declaration
- `teensy40`: add UART0 as alias for UART1
0.17.0
---
* **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
---
* **command-line**
- add initial support for LLVM 11
- make lib64 clang include path check more robust
- `build`: improve support for GOARCH=386 and add tests
- `gdb`: add support for qemu-user targets
- `test`: support non-host tests
- `test`: add support for -c and -o flags
- `test`: implement some benchmark stubs
* **compiler**
- `builder`: improve detection of clang on Fedora
- `compiler`: fix floating point comparison bugs
- `compiler`: implement negate for complex numbers
- `loader`: fix linkname in test binaries
- `transform`: add missing return pointer restore for regular coroutine tail
calls
* **standard library**
- `machine`: switch default frequency to 4MHz
- `machine`: clarify caller's responsibility in `SetInterrupt`
- `os`: add `LookupEnv()` stub
- `reflect`: implement `Swapper`
- `runtime`: fix UTF-8 decoding
- `runtime`: gc: use raw stack access whenever possible
- `runtime`: use dedicated printfloat32
- `runtime`: allow ranging over a nil map
- `runtime`: avoid device/nxp dependency in HardFault handler
- `testing`: implement dummy Helper method
- `testing`: add Run method
* **targets**
- `arm64`: add support for SVCall intrinsic
- `atsamd51`: avoid panic when configuring SPI with SDI=NoPin
- `avr`: properly support the `.rodata` section
- `esp8266`: implement `Pin.Get` function
- `nintendoswitch`: fix crash when printing long lines (> 120)
- `nintendoswitch`: add env parser and removed unused stuff
- `nrf`: add I2C error checking
- `nrf`: give more flexibility in picking SPI speeds
- `nrf`: fix nrf52832 flash size
- `stm32f103`: support wakeups from interrupts
- `stm32f405`: add SPI support
- `stm32f405`: add I2C support
- `wasi`: add support for this target
- `wasi`: use 'generic' ABI by default
- `wasi`: remove --no-threads flag from wasm-ld
- `wasm`: add instanceof support for WebAssembly
- `wasm`: use fixed length buffer for putchar
* **boards**
- `d1mini`: add this ESP8266 based board
- `esp32`: use board definitions instead of chip names
- `qtpy`: add board definition for Adafruit QTPy
- `teensy40`: add this board
0.15.0
---
+7 -7
View File
@@ -1,10 +1,10 @@
# TinyGo base stage installs Go 1.14, LLVM 10 and the TinyGo compiler itself.
FROM golang:1.14 AS tinygo-base
# TinyGo base stage installs the most recent Go 1.16.x, LLVM 11 and the TinyGo compiler itself.
FROM golang:1.16 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.
+142 -72
View File
@@ -9,25 +9,10 @@ CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
# Try to autodetect LLVM build tools.
ifneq (, $(shell command -v llvm-build/bin/clang 2> /dev/null))
CLANG ?= $(abspath llvm-build/bin/clang)
else
CLANG ?= clang-10
endif
ifneq (, $(shell command -v llvm-build/bin/llvm-ar 2> /dev/null))
LLVM_AR ?= $(abspath llvm-build/bin/llvm-ar)
else ifneq (, $(shell command -v llvm-ar-10 2> /dev/null))
LLVM_AR ?= llvm-ar-10
else
LLVM_AR ?= llvm-ar
endif
ifneq (, $(shell command -v llvm-build/bin/llvm-nm 2> /dev/null))
LLVM_NM ?= $(abspath llvm-build/bin/llvm-nm)
else ifneq (, $(shell command -v llvm-nm-10 2> /dev/null))
LLVM_NM ?= llvm-nm-10
else
LLVM_NM ?= llvm-nm
endif
detect = $(shell command -v $(1) 2> /dev/null && echo $(1))
CLANG ?= $(word 1,$(abspath $(call detect,llvm-build/bin/clang))$(call detect,clang-11)$(call detect,clang-10)$(call detect,clang))
LLVM_AR ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-ar))$(call detect,llvm-ar-11)$(call detect,llvm-ar-10)$(call detect,llvm-ar))
LLVM_NM ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-nm))$(call detect,llvm-nm-11)$(call detect,llvm-nm-10)$(call detect,llvm-nm))
# Go binary and GOROOT to select
GO ?= go
@@ -37,7 +22,7 @@ export GOROOT = $(shell $(GO) env GOROOT)
MD5SUM = md5sum
# tinygo binary for tests
TINYGO ?= tinygo
TINYGO ?= $(word 1,$(call detect,tinygo)$(call detect,build/tinygo))
# Use CCACHE for LLVM if possible
ifneq (, $(shell command -v ccache 2> /dev/null))
@@ -51,7 +36,7 @@ else
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
@@ -63,62 +48,69 @@ 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 clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_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) -std=c++14 -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:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32 gen-device-kendryte gen-device-nxp
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp
ifneq ($(STM32), 0)
gen-device: gen-device-stm32
endif
gen-device-avr:
@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
@@ -155,23 +147,26 @@ 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
gen-device-rp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/
GO111MODULE=off $(GO) fmt ./src/device/rp
# Get LLVM sources.
$(LLVM_PROJECTDIR)/README.md:
git clone -b xtensa_release_10.0.1 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/README.md
$(LLVM_PROJECTDIR)/llvm:
git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/llvm
# 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 -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=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
@@ -188,16 +183,30 @@ 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 ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
TEST_PACKAGES = \
container/heap \
container/list \
container/ring \
crypto/des \
encoding \
encoding/ascii85 \
encoding/base32 \
encoding/hex \
hash/adler32 \
hash/fnv \
hash/crc64 \
math \
math/cmplx \
text/scanner \
unicode/utf8 \
# Test known-working standard library packages.
# TODO: do this in one command, parallelize, and only show failing tests (no
# implied -v flag).
# TODO: parallelize, and only show failing tests (no implied -v flag).
.PHONY: tinygo-test
tinygo-test:
$(TINYGO) test container/list
$(TINYGO) test container/ring
$(TINYGO) test text/scanner
$(TINYGO) test $(TEST_PACKAGES)
.PHONY: smoketest
smoketest:
@@ -221,6 +230,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/memstats
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@@ -234,8 +245,6 @@ smoketest:
# test simulated boards on play.tinygo.org
$(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=hifive1-qemu examples/serial
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
@@ -253,12 +262,14 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit-v2-s113v7 examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2
@@ -267,6 +278,10 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
@@ -275,22 +290,16 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=clue_alpha examples/blinky1
$(TINYGO) build -size short -o test.hex -target=clue-alpha examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
@$(MD5SUM) test.gba
$(TINYGO) build -size short -o test.hex -target=grandcentral-m4 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
@@ -307,8 +316,6 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
@$(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=x9pro examples/blinky1
@@ -329,8 +336,36 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840-sense examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=qtpy examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1
@$(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=p1am-100 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/can
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/caninterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/blinky1
@$(MD5SUM) test.hex
# test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex
@@ -338,8 +373,32 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pyportal examples/pwm
ifneq ($(STM32), 0)
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 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=nucleo-f103rb 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-l031k6 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc 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=stm32f4disco examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/pwm
@$(MD5SUM) test.hex
endif
ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
@$(MD5SUM) test.hex
@@ -349,6 +408,10 @@ ifneq ($(AVR), 0)
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
@@ -357,13 +420,15 @@ ifneq ($(AVR), 0)
@$(MD5SUM) test.hex
endif
ifneq ($(XTENSA), 0)
$(TINYGO) build -size short -o test.bin -target=esp32-wroom-32 examples/blinky1
$(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
@$(MD5SUM) test.bin
endif
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
@@ -373,8 +438,13 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
@$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex
wasmtest:
$(GO) test ./tests/wasm
+30 -6
View File
@@ -43,50 +43,74 @@ 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 68 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 M4 CAN](https://www.adafruit.com/product/4759)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
* [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 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
* [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 Nano 33 BLE](https://store.arduino.cc/nano-33-ble)
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [Arduino 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/)
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
* [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)
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
* [Nordic Semiconductor pca10059](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
* [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/)
* [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/)
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
* [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)
* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/)
* [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" L031K6](https://www.st.com/ja/evaluation-tools/nucleo-l031k6.html)
* [ST Micro "Nucleo" L432KC](https://www.st.com/ja/evaluation-tools/nucleo-l432kc.html)
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
* [ST Micro 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 +172,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-v5
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
+774 -256
View File
File diff suppressed because it is too large Load Diff
+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
+309
View File
@@ -0,0 +1,309 @@
package builder
// This file implements a wrapper around the C compiler (Clang) which uses a
// build cache.
import (
"crypto/sha512"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"unicode"
"github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm"
)
// compileAndCacheCFile compiles a C or assembly file using a build cache.
// Compiling the same file again (if nothing changed, including included header
// files) the output is loaded from the build cache instead.
//
// Its operation is a bit complex (more complex than Go package build caching)
// because the list of file dependencies is only known after the file is
// compiled. However, luckily compilers have a flag to write a list of file
// dependencies in Makefile syntax which can be used for caching.
//
// Because of this complexity, every file has in fact two cached build outputs:
// the file itself, and the list of dependencies. Its operation is as follows:
//
// depfile = hash(path, compiler, cflags, ...)
// if depfile exists:
// outfile = hash of all files and depfile name
// if outfile exists:
// # cache hit
// return outfile
// # cache miss
// tmpfile = compile file
// read dependencies (side effect of compile)
// write depfile
// outfile = hash of all files and depfile name
// rename tmpfile to outfile
//
// There are a few edge cases that are not handled:
// - If a file is added to an include path, that file may be included instead of
// some other file. This would be fixed by also including lookup failures in the
// dependencies file, but I'm not aware of a compiler which does that.
// - The Makefile syntax that compilers output has issues, see readDepFile for
// details.
// - A header file may be changed to add/remove an include. This invalidates the
// depfile but without invalidating its name. For this reason, the depfile is
// written on each new compilation (even when it seems unnecessary). However, it
// could in rare cases lead to a stale file fetched from the cache.
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) {
// Hash input file.
fileHash, err := hashFile(abspath)
if err != nil {
return "", err
}
// Create cache key for the dependencies file.
buf, err := json.Marshal(struct {
Path string
Hash string
Flags []string
LLVMVersion string
}{
Path: abspath,
Hash: fileHash,
Flags: cflags,
LLVMVersion: llvm.Version,
})
if err != nil {
panic(err) // shouldn't happen
}
depfileNameHashBuf := sha512.Sum512_224(buf)
depfileNameHash := hex.EncodeToString(depfileNameHashBuf[:])
// Load dependencies file, if possible.
depfileName := "dep-" + depfileNameHash + ".json"
depfileCachePath := filepath.Join(goenv.Get("GOCACHE"), depfileName)
depfileBuf, err := ioutil.ReadFile(depfileCachePath)
var dependencies []string // sorted list of dependency paths
if err == nil {
// There is a dependency file, that's great!
// Parse it first.
err := json.Unmarshal(depfileBuf, &dependencies)
if err != nil {
return "", fmt.Errorf("could not parse dependencies JSON: %w", err)
}
// Obtain hashes of all the files listed as a dependency.
outpath, err := makeCFileCachePath(dependencies, depfileNameHash)
if err == nil {
if _, err := os.Stat(outpath); err == nil {
return outpath, nil
} else if !os.IsNotExist(err) {
return "", err
}
}
} else if !os.IsNotExist(err) {
// expected either nil or IsNotExist
return "", err
}
objTmpFile, err := ioutil.TempFile(goenv.Get("GOCACHE"), "tmp-*.o")
if err != nil {
return "", err
}
objTmpFile.Close()
depTmpFile, err := ioutil.TempFile(tmpdir, "dep-*.d")
if err != nil {
return "", err
}
depTmpFile.Close()
flags := append([]string{}, cflags...) // copy cflags
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
if strings.ToLower(filepath.Ext(abspath)) == ".s" {
// If this is an assembly file (.s or .S, lowercase or uppercase), then
// we'll need to add -Qunused-arguments because many parameters are
// relevant to C, not assembly. And with -Werror, having meaningless
// flags (for the assembler) is a compiler error.
flags = append(flags, "-Qunused-arguments")
}
if printCommands != nil {
printCommands("clang", flags...)
}
err = runCCompiler(flags...)
if err != nil {
return "", &commandError{"failed to build", abspath, err}
}
// Create sorted and uniqued slice of dependencies.
dependencyPaths, err := readDepFile(depTmpFile.Name())
if err != nil {
return "", err
}
dependencyPaths = append(dependencyPaths, abspath) // necessary for .s files
dependencySet := make(map[string]struct{}, len(dependencyPaths))
var dependencySlice []string
for _, path := range dependencyPaths {
if _, ok := dependencySet[path]; ok {
continue
}
dependencySet[path] = struct{}{}
dependencySlice = append(dependencySlice, path)
}
sort.Strings(dependencySlice)
// Write dependencies file.
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
buf, err = json.MarshalIndent(dependencySlice, "", "\t")
if err != nil {
panic(err) // shouldn't happen
}
_, err = f.Write(buf)
if err != nil {
return "", err
}
err = f.Close()
if err != nil {
return "", err
}
err = os.Rename(f.Name(), depfileCachePath)
if err != nil {
return "", err
}
// Move temporary object file to final location.
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash)
if err != nil {
return "", err
}
err = os.Rename(objTmpFile.Name(), outpath)
if err != nil {
return "", err
}
return outpath, nil
}
// Create a cache path (a path in GOCACHE) to store the output of a compiler
// job. This path is based on the dep file name (which is a hash of metadata
// including compiler flags) and the hash of all input files in the paths slice.
func makeCFileCachePath(paths []string, depfileNameHash string) (string, error) {
// Hash all input files.
fileHashes := make(map[string]string, len(paths))
for _, path := range paths {
hash, err := hashFile(path)
if err != nil {
return "", err
}
fileHashes[path] = hash
}
// Calculate a cache key based on the above hashes.
buf, err := json.Marshal(struct {
DepfileHash string
FileHashes map[string]string
}{
DepfileHash: depfileNameHash,
FileHashes: fileHashes,
})
if err != nil {
panic(err) // shouldn't happen
}
outFileNameBuf := sha512.Sum512_224(buf)
cacheKey := hex.EncodeToString(outFileNameBuf[:])
outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+".o")
return outpath, nil
}
// hashFile hashes the given file path and returns the hash as a hex string.
func hashFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", fmt.Errorf("failed to hash file: %w", err)
}
defer f.Close()
fileHasher := sha512.New512_224()
_, err = io.Copy(fileHasher, f)
if err != nil {
return "", fmt.Errorf("failed to hash file: %w", err)
}
return hex.EncodeToString(fileHasher.Sum(nil)), nil
}
// readDepFile reads a dependency file in NMake (Visual Studio make) format. The
// file is assumed to have a single target named deps.
//
// There are roughly three make syntax variants:
// - BSD make, which doesn't support any escaping. This means that many special
// characters are not supported in file names.
// - GNU make, which supports escaping using a backslash but when it fails to
// find a file it tries to fall back with the literal path name (to match BSD
// make).
// - NMake (Visual Studio) and Jom, which simply quote the string if there are
// any weird characters.
// Clang supports two variants: a format that's a compromise between BSD and GNU
// make (and is buggy to match GCC which is equally buggy), and NMake/Jom, which
// is at least somewhat sane. This last format isn't perfect either: it does not
// correctly handle filenames with quote marks in them. Those are generally not
// allowed on Windows, but of course can be used on POSIX like systems. Still,
// it's the most sane of any of the formats so readDepFile will use that format.
func readDepFile(filename string) ([]string, error) {
buf, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
if len(buf) == 0 {
return nil, nil
}
return parseDepFile(string(buf))
}
func parseDepFile(s string) ([]string, error) {
// This function makes no attempt at parsing anything other than Clang -MD
// -MV output.
// For Windows: replace CRLF with LF to make the logic below simpler.
s = strings.ReplaceAll(s, "\r\n", "\n")
// Collapse all lines ending in a backslash. These backslashes are really
// just a way to continue a line without making very long lines.
s = strings.ReplaceAll(s, "\\\n", " ")
// Only use the first line, which is expected to begin with "deps:".
line := strings.SplitN(s, "\n", 2)[0]
if !strings.HasPrefix(line, "deps:") {
return nil, errors.New("readDepFile: expected 'deps:' prefix")
}
line = strings.TrimSpace(line[len("deps:"):])
var deps []string
for line != "" {
if line[0] == '"' {
// File path is quoted. Path ends with double quote.
// This does not handle double quotes in path names, which is a
// problem on non-Windows systems.
line = line[1:]
end := strings.IndexByte(line, '"')
if end < 0 {
return nil, errors.New("readDepFile: path is incorrectly quoted")
}
dep := line[:end]
line = strings.TrimSpace(line[end+1:])
deps = append(deps, dep)
} else {
// File path is not quoted. Path ends in space or EOL.
end := strings.IndexFunc(line, unicode.IsSpace)
if end < 0 {
// last dependency
deps = append(deps, line)
break
}
dep := line[:end]
line = strings.TrimSpace(line[end:])
deps = append(deps, dep)
}
}
return deps, nil
}
+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.
+33
View File
@@ -0,0 +1,33 @@
package builder
import (
"reflect"
"testing"
)
func TestSplitDepFile(t *testing.T) {
for i, tc := range []struct {
in string
out []string
}{
{`deps: foo bar`, []string{"foo", "bar"}},
{`deps: foo "bar"`, []string{"foo", "bar"}},
{`deps: "foo" bar`, []string{"foo", "bar"}},
{`deps: "foo bar"`, []string{"foo bar"}},
{`deps: "foo bar" `, []string{"foo bar"}},
{"deps: foo\nbar", []string{"foo"}},
{"deps: foo \\\nbar", []string{"foo", "bar"}},
{"deps: foo\\bar \\\nbaz", []string{"foo\\bar", "baz"}},
{"deps: foo\\bar \\\r\n baz", []string{"foo\\bar", "baz"}}, // Windows uses CRLF line endings
} {
out, err := parseDepFile(tc.in)
if err != nil {
t.Errorf("test #%d failed: %v", i, err)
continue
}
if !reflect.DeepEqual(out, tc.out) {
t.Errorf("test #%d failed: expected %#v but got %#v", i, tc.out, out)
continue
}
}
}
+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;
+12 -2
View File
@@ -17,18 +17,28 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if err != nil {
return nil, err
}
if options.OpenOCDCommands != nil {
// Override the OpenOCDCommands from the target spec if specified on
// the command-line
spec.OpenOCDCommands = options.OpenOCDCommands
}
goroot := goenv.Get("GOROOT")
if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually")
}
major, minor, err := goenv.GetGorootVersion(goroot)
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{
Options: options,
Target: spec,
+57
View File
@@ -0,0 +1,57 @@
package builder
import (
"debug/elf"
"fmt"
"os"
)
func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileHeader, error) {
elfFile, err := elf.Open(executable)
if err != nil {
return nil, elf.FileHeader{}, err
}
defer elfFile.Close()
section := elfFile.Section(sectionName)
if section == nil {
return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName)
}
data, err := section.Data()
return data, elfFile.FileHeader, err
}
func replaceElfSection(executable string, sectionName string, data []byte) error {
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
if err != nil {
return err
}
defer fp.Close()
elfFile, err := elf.Open(executable)
if err != nil {
return err
}
defer elfFile.Close()
section := elfFile.Section(sectionName)
if section == nil {
return fmt.Errorf("could not find %s section", sectionName)
}
// Implicitly check for compressed sections
if section.Size != section.FileSize {
return fmt.Errorf("expected section %s to have identical size and file size, got %d and %d", sectionName, section.Size, section.FileSize)
}
// Only permit complete replacement of section
if section.Size != uint64(len(data)) {
return fmt.Errorf("expected section %s to have size %d, was actually %d", sectionName, len(data), section.Size)
}
// Write the replacement section data
_, err = fp.WriteAt(data, int64(section.Offset))
return err
}
+29 -9
View File
@@ -6,6 +6,9 @@ import (
"os/exec"
"path/filepath"
"sort"
"strings"
"tinygo.org/x/go-llvm"
)
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
@@ -26,6 +29,7 @@ func getClangHeaderPath(TINYGOROOT string) string {
// It looks like we are built with a system-installed LLVM. Do a last
// attempt: try to use Clang headers relative to the clang binary.
llvmMajor := strings.Split(llvm.Version, ".")[0]
for _, cmdName := range commands["clang"] {
binpath, err := exec.LookPath(cmdName)
if err == nil {
@@ -40,22 +44,38 @@ func getClangHeaderPath(TINYGOROOT string) string {
// Example executable:
// /usr/lib/llvm-9/bin/clang
// Example include path:
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
// /usr/lib/llvm-9/lib64/clang/9.0.1/include/
llvmRoot := filepath.Dir(filepath.Dir(binpath))
clangVersionRoot := filepath.Join(llvmRoot, "lib", "clang")
dirs, err := ioutil.ReadDir(clangVersionRoot)
if err != nil {
clangVersionRoot := filepath.Join(llvmRoot, "lib64", "clang")
dirs64, err64 := ioutil.ReadDir(clangVersionRoot)
// Example include path:
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
clangVersionRoot = filepath.Join(llvmRoot, "lib", "clang")
dirs32, err32 := ioutil.ReadDir(clangVersionRoot)
if err64 != nil && err32 != nil {
// Unexpected.
continue
}
dirnames := make([]string, len(dirs))
for i, d := range dirs {
dirnames[i] = d.Name()
dirnames := make([]string, len(dirs64)+len(dirs32))
dirCount := 0
for _, d := range dirs32 {
name := d.Name()
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
dirnames[dirCount] = filepath.Join(llvmRoot, "lib", "clang", name)
dirCount++
}
}
for _, d := range dirs64 {
name := d.Name()
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
dirnames[dirCount] = filepath.Join(llvmRoot, "lib64", "clang", name)
dirCount++
}
}
sort.Strings(dirnames)
// Check for the highest version first.
for i := len(dirnames) - 1; i >= 0; i-- {
path := filepath.Join(clangVersionRoot, dirnames[i], "include")
for i := dirCount - 1; i >= 0; i-- {
path := filepath.Join(dirnames[i], "include")
_, err := os.Stat(filepath.Join(path, "stdint.h"))
if err == nil {
return path
+173
View File
@@ -0,0 +1,173 @@
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
result string // result (path)
run func(*compileJob) (err error)
state jobState
err error // error if finished
duration time.Duration // how long it took to run this job (only set after finishing)
}
// dummyCompileJob returns a new *compileJob that produces an output without
// doing anything. This can be useful where a *compileJob producing an output is
// expected but nothing needs to be done, for example for a load from a cache.
func dummyCompileJob(result string) *compileJob {
return &compileJob{
description: "<dummy>",
result: result,
}
}
// 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()
if job.run != nil {
err := job.run(job)
if err != nil {
job.err = err
}
}
job.duration = time.Since(start)
doneChan <- job
}
}
+73 -29
View File
@@ -1,7 +1,6 @@
package builder
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -40,34 +39,62 @@ 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) {
job, err := l.load(target, "", tmpdir)
if err != nil {
return "", err
}
jobs := append([]*compileJob{job}, job.dependencies...)
err = runJobs(jobs)
return job.result, err
}
// load returns a compile job to build this library file for the given target
// and CPU. It may return a dummy compileJob if the library build is already
// cached. The path is stored as job.result but is only valid if the job and
// job.dependencies have been run.
// 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) (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 dummyCompileJob(precompiledPath), 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 dummyCompileJob(path), nil
}
// 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 +105,45 @@ 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",
result: arpath,
run: func(*compileJob) 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(*compileJob) error {
var compileArgs []string
compileArgs = append(compileArgs, args...)
compileArgs = append(compileArgs, "-o", objpath, srcpath)
err := runCCompiler(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 job, nil
}
+27
View File
@@ -0,0 +1,27 @@
package builder
import (
"fmt"
"io/ioutil"
"os/exec"
"github.com/tinygo-org/tinygo/compileopts"
)
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
if options.PrintCommands != nil {
options.PrintCommands(cmdLine[0], cmdLine[1:]...)
}
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
cmd.Stdout = ioutil.Discard
err := cmd.Run()
if err != nil {
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
}
return nil
}
+7 -1
View File
@@ -61,7 +61,7 @@ func extractROM(path string) (uint64, []byte, error) {
progs := make(progSlice, 0, 2)
for _, prog := range f.Progs {
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 0 {
continue
}
progs = append(progs, prog)
@@ -73,6 +73,12 @@ func extractROM(path string) (uint64, []byte, error) {
var rom []byte
for _, prog := range progs {
romEnd := progs[0].Paddr + uint64(len(rom))
if prog.Paddr > romEnd && prog.Paddr < romEnd+16 {
// Sometimes, the linker seems to insert a bit of padding between
// segments. Simply zero-fill these parts.
rom = append(rom, make([]byte, prog.Paddr-romEnd)...)
}
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
if diff > maxPadBytes {
+4 -13
View File
@@ -9,8 +9,8 @@ import (
)
// runCCompiler invokes a C compiler with the given arguments.
func runCCompiler(command string, flags ...string) error {
if hasBuiltinTools && command == "clang" {
func runCCompiler(flags ...string) error {
if hasBuiltinTools {
// Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
@@ -23,17 +23,8 @@ func runCCompiler(command string, flags ...string) error {
return cmd.Run()
}
// Running some other compiler. Maybe it has been defined in the
// commands map (unlikely).
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
// Alternatively, run the compiler directly.
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
// Compile this with an external invocation of the Clang compiler.
return execCommand(commands["clang"], flags...)
}
// link invokes a linker with the given name and flags.
+33 -16
View File
@@ -41,7 +41,9 @@ type cgoPackage struct {
elaboratedTypes map[string]*elaboratedTypeInfo
enums map[string]enumInfo
anonStructNum int
ldflags []string
cflags []string // CFlags from #cgo lines
ldflags []string // LDFlags from #cgo lines
visitedFiles map[string][]byte
}
// constantInfo stores some information about a CGo constant found by libclang
@@ -54,9 +56,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).
@@ -155,9 +158,10 @@ typedef unsigned long long _Cgo_ulonglong;
// Process extracts `import "C"` statements from the AST, parses the comment
// with libclang, and modifies the AST to use this information. It returns a
// newly created *ast.File that should be added to the list of to-be-parsed
// files. If there is one or more error, it returns these in the []error slice
// but still modifies the AST.
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []error) {
// files, the CFLAGS and LDFLAGS found in #cgo lines, and a map of file hashes
// of the accessed C header files. If there is one or more error, it returns
// these in the []error slice but still modifies the AST.
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{
dir: dir,
fset: fset,
@@ -169,13 +173,9 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
typedefs: map[string]*typedefInfo{},
elaboratedTypes: map[string]*elaboratedTypeInfo{},
enums: map[string]enumInfo{},
visitedFiles: map[string][]byte{},
}
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
// Note that it is only disabled for memcpy (etc) calls made from Go, which
// have better alternatives anyway.
cflags = append(cflags, "-D_FORTIFY_SOURCE=0")
// Add a new location for the following file.
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
generatedTokenPos.SetLines([]int{0})
@@ -184,7 +184,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
// Find the absolute path for this package.
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
if err != nil {
return nil, nil, []error{
return nil, nil, nil, nil, []error{
scanner.Error{
Pos: fset.Position(files[0].Pos()),
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
@@ -359,7 +359,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
continue
}
makePathsAbsolute(flags, packagePath)
cflags = append(cflags, flags...)
p.cflags = append(p.cflags, flags...)
case "LDFLAGS":
flags, err := shlex.Split(value)
if err != nil {
@@ -382,6 +382,13 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
}
}
// Define CFlags that will be used while parsing the package.
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
// Note that it is only disabled for memcpy (etc) calls made from Go, which
// have better alternatives anyway.
cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...)
cflagsForCGo = append(cflagsForCGo, p.cflags...)
// Process all CGo imports.
for _, genDecl := range statements {
cgoComment := genDecl.Doc.Text()
@@ -391,7 +398,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
pos = genDecl.Doc.Pos()
}
position := fset.PositionFor(pos, true)
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
p.parseFragment(cgoComment+cgoTypes, cflagsForCGo, position.Filename, position.Line)
}
// Declare functions found by libclang.
@@ -426,7 +433,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
// Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated)
return p.generated, p.ldflags, p.errors
return p.generated, p.cflags, p.ldflags, p.visitedFiles, p.errors
}
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
@@ -484,6 +491,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{
+19 -4
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()
@@ -50,7 +65,7 @@ func TestCGo(t *testing.T) {
}
// Process the AST with CGo.
cgoAST, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
cgoAST, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
// Check the AST for type errors.
var typecheckErrors []error
@@ -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"
}
+164 -74
View File
@@ -11,105 +11,184 @@ import (
"strings"
)
var (
prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error)
precedences = map[token.Token]int{
token.ADD: precedenceAdd,
token.SUB: precedenceAdd,
token.MUL: precedenceMul,
token.QUO: precedenceMul,
token.REM: precedenceMul,
}
)
const (
precedenceLowest = iota + 1
precedenceAdd
precedenceMul
precedencePrefix
)
func init() {
// This must be done in an init function to avoid an initialization order
// failure.
prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){
token.IDENT: parseIdent,
token.INT: parseBasicLit,
token.FLOAT: parseBasicLit,
token.STRING: parseBasicLit,
token.CHAR: parseBasicLit,
token.LPAREN: parseParenExpr,
token.SUB: parseUnaryExpr,
}
}
// parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value)
expr, err := parseConstExpr(t)
if t.token != token.EOF {
expr, err := parseConstExpr(t, precedenceLowest)
t.Next()
if t.curToken != token.EOF {
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: "unexpected token " + t.token.String(),
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + ", expected end of expression",
}
}
return expr, err
}
// parseConstExpr parses a stream of C tokens to a Go expression.
func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
switch t.token {
case token.LPAREN:
lparen := t.pos
t.Next()
x, err := parseConstExpr(t)
if err != nil {
return nil, err
}
if t.token != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.pos,
}
t.Next()
return expr, nil
case token.INT, token.FLOAT, token.STRING, token.CHAR:
expr := &ast.BasicLit{
ValuePos: t.pos,
Kind: t.token,
Value: t.value,
}
t.Next()
return expr, nil
case token.IDENT:
expr := &ast.Ident{
NamePos: t.pos,
Name: "C." + t.value,
}
t.Next()
return expr, nil
case token.EOF:
func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
if t.curToken == token.EOF {
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Pos: t.fset.Position(t.curPos),
Msg: "empty constant",
}
default:
}
prefix := prefixParseFns[t.curToken]
if prefix == nil {
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s", t.token),
Pos: t.fset.Position(t.curPos),
Msg: fmt.Sprintf("unexpected token %s", t.curToken),
}
}
leftExpr, err := prefix(t)
for t.peekToken != token.EOF && precedence < precedences[t.peekToken] {
switch t.peekToken {
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
t.Next()
leftExpr, err = parseBinaryExpr(t, leftExpr)
}
}
return leftExpr, err
}
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
return &ast.Ident{
NamePos: t.curPos,
Name: "C." + t.curValue,
}, nil
}
func parseBasicLit(t *tokenizer) (ast.Expr, *scanner.Error) {
return &ast.BasicLit{
ValuePos: t.curPos,
Kind: t.curToken,
Value: t.curValue,
}, nil
}
func parseParenExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
lparen := t.curPos
t.Next()
x, err := parseConstExpr(t, precedenceLowest)
if err != nil {
return nil, err
}
t.Next()
if t.curToken != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.curPos,
}
return expr, nil
}
func parseBinaryExpr(t *tokenizer, left ast.Expr) (ast.Expr, *scanner.Error) {
expression := &ast.BinaryExpr{
X: left,
Op: t.curToken,
OpPos: t.curPos,
}
precedence := precedences[t.curToken]
t.Next()
right, err := parseConstExpr(t, precedence)
expression.Y = right
return expression, err
}
func parseUnaryExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
expression := &ast.UnaryExpr{
OpPos: t.curPos,
Op: t.curToken,
}
t.Next()
x, err := parseConstExpr(t, precedencePrefix)
expression.X = x
return expression, err
}
// unexpectedToken returns an error of the form "unexpected token FOO, expected
// BAR".
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
return &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
Pos: t.fset.Position(t.curPos),
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.curToken, expected),
}
}
// tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct {
pos token.Pos
fset *token.FileSet
token token.Token
value string
buf string
curPos, peekPos token.Pos
fset *token.FileSet
curToken, peekToken token.Token
curValue, peekValue string
buf string
}
// newTokenizer initializes a new tokenizer, positioned at the first token in
// the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
t := &tokenizer{
pos: start,
fset: fset,
buf: buf,
token: token.ILLEGAL,
peekPos: start,
fset: fset,
buf: buf,
peekToken: token.ILLEGAL,
}
t.Next() // Parse the first token.
// Parse the first two tokens (cur and peek).
t.Next()
t.Next()
return t
}
// Next consumes the next token in the stream. There is no return value, read
// the next token from the pos, token and value properties.
func (t *tokenizer) Next() {
t.pos += token.Pos(len(t.value))
// The previous peek is now the current token.
t.curPos = t.peekPos
t.curToken = t.peekToken
t.curValue = t.peekValue
// Parse the next peek token.
t.peekPos += token.Pos(len(t.curValue))
for {
if len(t.buf) == 0 {
t.token = token.EOF
t.peekToken = token.EOF
return
}
c := t.buf[0]
@@ -118,17 +197,28 @@ func (t *tokenizer) Next() {
// Skip whitespace.
// Based on this source, not sure whether it represents C whitespace:
// https://en.cppreference.com/w/cpp/string/byte/isspace
t.pos++
t.peekPos++
t.buf = t.buf[1:]
case c == '(' || c == ')':
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%':
// Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators.
switch c {
case '(':
t.token = token.LPAREN
t.peekToken = token.LPAREN
case ')':
t.token = token.RPAREN
t.peekToken = token.RPAREN
case '+':
t.peekToken = token.ADD
case '-':
t.peekToken = token.SUB
case '*':
t.peekToken = token.MUL
case '/':
t.peekToken = token.QUO
case '%':
t.peekToken = token.REM
}
t.value = t.buf[:1]
t.peekValue = t.buf[:1]
t.buf = t.buf[1:]
return
case c >= '0' && c <= '9':
@@ -146,17 +236,17 @@ func (t *tokenizer) Next() {
break
}
}
t.value = t.buf[:tokenLen]
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
if hasDot {
// Integer constants are more complicated than this but this is
// a close approximation.
// https://en.cppreference.com/w/cpp/language/integer_literal
t.token = token.FLOAT
t.value = strings.TrimRight(t.value, "f")
t.peekToken = token.FLOAT
t.peekValue = strings.TrimRight(t.peekValue, "f")
} else {
t.token = token.INT
t.value = strings.TrimRight(t.value, "uUlL")
t.peekToken = token.INT
t.peekValue = strings.TrimRight(t.peekValue, "uUlL")
}
return
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
@@ -170,9 +260,9 @@ func (t *tokenizer) Next() {
break
}
}
t.value = t.buf[:tokenLen]
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
t.token = token.IDENT
t.peekToken = token.IDENT
return
case c == '"':
// String constant. Find the first '"' character that is not
@@ -188,8 +278,8 @@ func (t *tokenizer) Next() {
escape = c == '\\'
}
}
t.token = token.STRING
t.value = t.buf[:tokenLen]
t.peekToken = token.STRING
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
case c == '\'':
@@ -206,12 +296,12 @@ func (t *tokenizer) Next() {
escape = c == '\\'
}
}
t.token = token.CHAR
t.value = t.buf[:tokenLen]
t.peekToken = token.CHAR
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
default:
t.token = token.ILLEGAL
t.peekToken = token.ILLEGAL
return
}
}
+19 -2
View File
@@ -18,7 +18,7 @@ func TestParseConst(t *testing.T) {
{`(5)`, `(5)`},
{`(((5)))`, `(5)`},
{`)`, `error: 1:1: unexpected token )`},
{`5)`, `error: 1:2: unexpected token )`},
{`5)`, `error: 1:2: unexpected token ), expected end of expression`},
{" \t)", `error: 1:4: unexpected token )`},
{`5.8f`, `5.8`},
{`foo`, `C.foo`},
@@ -30,7 +30,24 @@ func TestParseConst(t *testing.T) {
{`'a'`, `'a'`},
{`0b10`, `0b10`},
{`0x1234_5678`, `0x1234_5678`},
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
{`5 5`, `error: 1:3: unexpected token INT, expected end of expression`}, // test for a bugfix
// Binary operators.
{`5+5`, `5 + 5`},
{`5-5`, `5 - 5`},
{`5*5`, `5 * 5`},
{`5/5`, `5 / 5`},
{`5%5`, `5 % 5`},
{`(5/5)`, `(5 / 5)`},
{`1 - 2`, `1 - 2`},
{`1 - 2 + 3`, `1 - 2 + 3`},
{`1 - 2 * 3`, `1 - 2*3`},
{`(1 - 2) * 3`, `(1 - 2) * 3`},
{`1 * 2 - 3`, `1*2 - 3`},
{`1 * (2 - 3)`, `1 * (2 - 3)`},
// Unary operators.
{`-5`, `-5`},
{`-5-2`, `-5 - 2`},
{`5 - - 2`, `5 - -2`},
} {
fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0)
+37 -4
View File
@@ -4,6 +4,7 @@ package cgo
// modification. It does not touch the AST itself.
import (
"crypto/sha512"
"fmt"
"go/ast"
"go/scanner"
@@ -56,6 +57,7 @@ unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data);
*/
import "C"
@@ -114,6 +116,7 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
}
defer C.clang_disposeTranslationUnit(unit)
// Report parser and type errors.
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
addDiagnostic := func(diagnostic C.CXDiagnostic) {
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
@@ -134,10 +137,36 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
}
}
// Extract information required by CGo.
ref := storedRefs.Put(p)
defer storedRefs.Remove(ref)
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
// Determine files read during CGo processing, for caching.
inclusionCallback := func(includedFile C.CXFile) {
// Get full file path.
path := getString(C.clang_getFileName(includedFile))
// Get contents of file (that should be in-memory).
size := C.size_t(0)
rawData := C.clang_getFileContents(unit, includedFile, &size)
if rawData == nil {
// Sanity check. This should (hopefully) never trigger.
panic("libclang: file contents was not loaded")
}
data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size]
// Hash the contents if it isn't hashed yet.
if _, ok := p.visitedFiles[path]; !ok {
// already stored
sum := sha512.Sum512_224(data)
p.visitedFiles[path] = sum[:]
}
}
inclusionCallbackRef := storedRefs.Put(inclusionCallback)
defer storedRefs.Remove(inclusionCallbackRef)
C.clang_getInclusions(unit, C.CXInclusionVisitor(C.tinygo_clang_inclusion_visitor), C.CXClientData(inclusionCallbackRef))
}
//export tinygo_clang_globals_visitor
@@ -152,12 +181,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++ {
@@ -774,3 +801,9 @@ func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientDat
}
return C.CXChildVisit_Continue
}
//export tinygo_clang_inclusion_visitor
func tinygo_clang_inclusion_visitor(includedFile C.CXFile, inclusionStack *C.CXSourceLocation, includeLen C.unsigned, clientData C.CXClientData) {
callback := storedRefs.Get(unsafe.Pointer(clientData)).(func(C.CXFile))
callback(includedFile)
}
+7 -7
View File
@@ -1,14 +1,14 @@
// +build !byollvm
// +build !llvm9
// +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 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"
+3 -3
View File
@@ -1,13 +1,13 @@
// CGo errors:
// testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:13:23: unexpected token )
// testdata/errors.go:13:23: unexpected token ), expected end of expression
// 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
+88 -50
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
@@ -79,7 +55,7 @@ func (c *Config) GOARCH() string {
// BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string {
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
@@ -104,12 +80,7 @@ func (c *Config) GC() string {
if c.Target.GC != "" {
return c.Target.GC
}
for _, tag := range c.Target.BuildTags {
if tag == "baremetal" || tag == "wasm" {
return "conservative"
}
}
return "extalloc"
return "conservative"
}
// NeedsStackObjects returns true if the compiler should insert stack objects
@@ -118,12 +89,12 @@ func (c *Config) NeedsStackObjects() bool {
switch c.GC() {
case "conservative", "extalloc":
for _, tag := range c.BuildTags() {
if tag == "baremetal" {
return false
if tag == "tinygo.wasm" {
return true
}
}
return true
return false
default:
return false
}
@@ -142,16 +113,59 @@ func (c *Config) Scheduler() string {
return "coroutines"
}
// Serial returns the serial implementation for this build configuration: uart,
// usb (meaning USB-CDC), or none.
func (c *Config) Serial() string {
if c.Options.Serial != "" {
return c.Options.Serial
}
if c.Target.Serial != "" {
return c.Target.Serial
}
return "none"
}
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline.
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
switch c.Options.Opt {
case "none", "0":
return 0, 0, 0 // -O0
case "1":
return 1, 0, 0 // -O1
case "2":
return 2, 0, 225 // -O2
case "s":
return 2, 1, 225 // -Os
case "z":
return 2, 2, 5 // -Oz, default
default:
// This is not shown to the user: valid choices are already checked as
// part of Options.Verify(). It is here as a sanity check.
panic("unknown optimization level: -opt=" + c.Options.Opt)
}
}
// FuncImplementation picks an appropriate func value implementation for the
// 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")
}
@@ -174,12 +188,21 @@ func (c *Config) AutomaticStackSize() bool {
return false
}
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
// calculates and patches in the checksum for the 2nd stage bootloader.
func (c *Config) RP2040BootPatch() bool {
if c.Target.RP2040BootPatch != nil {
return *c.Target.RP2040BootPatch
}
return false
}
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing.
func (c *Config) CFlags() []string {
cflags := append([]string{}, c.Options.CFlags...)
var cflags []string
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")
@@ -198,17 +221,11 @@ func (c *Config) CFlags() []string {
func (c *Config) LDFlags() []string {
root := goenv.Get("TINYGOROOT")
// Merge and adjust LDFlags.
ldflags := append([]string{}, c.Options.LDFlags...)
var ldflags []string
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)
}
@@ -258,6 +275,11 @@ func (c *Config) BinaryFormat(ext string) string {
// More information:
// https://github.com/Microsoft/uf2
return "uf2"
case ".zip":
if c.Target.BinaryFormat != "" {
return c.Target.BinaryFormat
}
return "zip"
default:
// Use the ELF format for unrecognized file formats.
return "elf"
@@ -303,6 +325,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)
}
@@ -329,6 +354,19 @@ func (c *Config) RelocationModel() string {
return "static"
}
// WasmAbi returns the WASM ABI which is specified in the target JSON file, and
// the value is overridden by `-wasm-abi` flag if it is provided
func (c *Config) WasmAbi() string {
if c.Options.WasmAbi != "" {
return c.Options.WasmAbi
}
return c.Target.WasmAbi
}
func (c *Config) LLVMFeatures() string {
return c.Options.LLVMFeatures
}
type TestConfig struct {
CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc
+39 -18
View File
@@ -2,37 +2,43 @@ package compileopts
import (
"fmt"
"regexp"
"strings"
)
var (
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
validSerialOptions = []string{"none", "uart", "usb"}
validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
)
// Options contains extra options to give to the compiler. These options are
// usually passed from the command line.
type Options struct {
Target string
Opt string
GC string
PanicStrategy string
Scheduler string
PrintIR bool
DumpSSA bool
VerifyIR bool
Debug bool
PrintSizes string
PrintStacks bool
CFlags []string
LDFlags []string
Tags string
WasmAbi string
HeapSize int64
TestConfig TestConfig
Programmer string
Target string
Opt string
GC string
PanicStrategy string
Scheduler string
Serial string
PrintIR bool
DumpSSA bool
VerifyIR bool
PrintCommands func(cmd string, args ...string)
Debug bool
PrintSizes string
PrintAllocs *regexp.Regexp // regexp string
PrintStacks bool
Tags string
WasmAbi string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
TestConfig TestConfig
Programmer string
OpenOCDCommands []string
LLVMFeatures string
}
// Verify performs a validation on the given options, raising an error if options are not valid.
@@ -55,6 +61,15 @@ func (o *Options) Verify() error {
}
}
if o.Serial != "" {
valid := isInArray(validSerialOptions, o.Serial)
if !valid {
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
o.Serial,
strings.Join(validSerialOptions, ", "))
}
}
if o.PrintSizes != "" {
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
if !valid {
@@ -73,6 +88,12 @@ func (o *Options) Verify() error {
}
}
if o.Opt != "" {
if !isInArray(validOptOptions, o.Opt) {
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
}
}
return nil
}
+90 -113
View File
@@ -7,7 +7,9 @@ import (
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"strings"
@@ -29,7 +31,7 @@ type TargetSpec struct {
BuildTags []string `json:"build-tags"`
GC string `json:"gc"`
Scheduler string `json:"scheduler"`
Compiler string `json:"compiler"`
Serial string `json:"serial"` // which serial output to use (uart, usb, none)
Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"`
@@ -39,10 +41,12 @@ type TargetSpec struct {
LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"`
ExtraFiles []string `json:"extra-files"`
Emulator []string `json:"emulator"`
RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
FlashCommand string `json:"flash-command"`
GDB string `json:"gdb"`
GDB []string `json:"gdb"`
PortReset string `json:"flash-1200-bps-reset"`
SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"`
@@ -51,105 +55,53 @@ 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"`
WasmAbi string `json:"wasm-abi"`
}
// copyProperties copies all properties that are set in spec2 into itself.
func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
// TODO: simplify this using reflection? Inherits and BuildTags are special
// cases, but the rest can simply be copied if set.
spec.Inherits = append(spec.Inherits, spec2.Inherits...)
if spec2.Triple != "" {
spec.Triple = spec2.Triple
}
if spec2.CPU != "" {
spec.CPU = spec2.CPU
}
spec.Features = append(spec.Features, spec2.Features...)
if spec2.GOOS != "" {
spec.GOOS = spec2.GOOS
}
if spec2.GOARCH != "" {
spec.GOARCH = spec2.GOARCH
}
spec.BuildTags = append(spec.BuildTags, spec2.BuildTags...)
if spec2.GC != "" {
spec.GC = spec2.GC
}
if spec2.Scheduler != "" {
spec.Scheduler = spec2.Scheduler
}
if spec2.Compiler != "" {
spec.Compiler = spec2.Compiler
}
if spec2.Linker != "" {
spec.Linker = spec2.Linker
}
if spec2.RTLib != "" {
spec.RTLib = spec2.RTLib
}
if spec2.Libc != "" {
spec.Libc = spec2.Libc
}
if spec2.AutoStackSize != nil {
spec.AutoStackSize = spec2.AutoStackSize
}
if spec2.DefaultStackSize != 0 {
spec.DefaultStackSize = spec2.DefaultStackSize
}
spec.CFlags = append(spec.CFlags, spec2.CFlags...)
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
if spec2.LinkerScript != "" {
spec.LinkerScript = spec2.LinkerScript
}
spec.ExtraFiles = append(spec.ExtraFiles, spec2.ExtraFiles...)
if len(spec2.Emulator) != 0 {
spec.Emulator = spec2.Emulator
}
if spec2.FlashCommand != "" {
spec.FlashCommand = spec2.FlashCommand
}
if spec2.GDB != "" {
spec.GDB = spec2.GDB
}
if spec2.PortReset != "" {
spec.PortReset = spec2.PortReset
}
if spec2.FlashMethod != "" {
spec.FlashMethod = spec2.FlashMethod
}
if spec2.FlashVolume != "" {
spec.FlashVolume = spec2.FlashVolume
}
if spec2.FlashFilename != "" {
spec.FlashFilename = spec2.FlashFilename
}
if spec2.UF2FamilyID != "" {
spec.UF2FamilyID = spec2.UF2FamilyID
}
if spec2.BinaryFormat != "" {
spec.BinaryFormat = spec2.BinaryFormat
}
if spec2.OpenOCDInterface != "" {
spec.OpenOCDInterface = spec2.OpenOCDInterface
}
if spec2.OpenOCDTarget != "" {
spec.OpenOCDTarget = spec2.OpenOCDTarget
}
if spec2.OpenOCDTransport != "" {
spec.OpenOCDTransport = spec2.OpenOCDTransport
}
if spec2.JLinkDevice != "" {
spec.JLinkDevice = spec2.JLinkDevice
}
if spec2.CodeModel != "" {
spec.CodeModel = spec2.CodeModel
}
// overrideProperties overrides all properties that are set in child into itself using reflection.
func (spec *TargetSpec) overrideProperties(child *TargetSpec) {
specType := reflect.TypeOf(spec).Elem()
specValue := reflect.ValueOf(spec).Elem()
childValue := reflect.ValueOf(child).Elem()
if spec2.RelocationModel != "" {
spec.RelocationModel = spec2.RelocationModel
for i := 0; i < specType.NumField(); i++ {
field := specType.Field(i)
src := childValue.Field(i)
dst := specValue.Field(i)
switch kind := field.Type.Kind(); kind {
case reflect.String: // for strings, just copy the field of child to spec if not empty
if src.Len() > 0 {
dst.Set(src)
}
case reflect.Uint, reflect.Uint32, reflect.Uint64: // for Uint, copy if not zero
if src.Uint() != 0 {
dst.Set(src)
}
case reflect.Ptr: // for pointers, copy if not nil
if !src.IsNil() {
dst.Set(src)
}
case reflect.Slice: // for slices...
if src.Len() > 0 { // ... if not empty ...
switch tag := field.Tag.Get("override"); tag {
case "copy":
// copy the field of child to spec
dst.Set(src)
case "append", "":
// or append the field of child to spec
dst.Set(reflect.AppendSlice(src, dst))
default:
panic("override mode must be 'copy' or 'append' (default). I don't know how to '" + tag + "'.")
}
}
default:
panic("unknown field type : " + kind.String())
}
}
}
@@ -198,11 +150,11 @@ func (spec *TargetSpec) resolveInherits() error {
if err != nil {
return err
}
newSpec.copyProperties(subtarget)
newSpec.overrideProperties(subtarget)
}
// When all properties are loaded, make sure they are properly inherited.
newSpec.copyProperties(spec)
newSpec.overrideProperties(spec)
*spec = *newSpec
return nil
@@ -268,6 +220,7 @@ func LoadTarget(target string) (*TargetSpec, error) {
}
goarch := map[string]string{ // map from LLVM arch to Go arch
"i386": "386",
"i686": "386",
"x86_64": "amd64",
"aarch64": "arm64",
"armv7": "arm",
@@ -279,44 +232,68 @@ func LoadTarget(target string) (*TargetSpec, error) {
}
}
// WindowsBuildNotSupportedErr is being thrown, when goos is windows and no target has been specified.
var WindowsBuildNotSupportedErr = errors.New("Building Windows binaries is currently not supported. Try specifying a different target")
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
if goos == "windows" {
return nil, WindowsBuildNotSupportedErr
}
// No target spec available. Use the default one, useful on most systems
// with a regular OS.
spec := TargetSpec{
Triple: triple,
GOOS: goos,
GOARCH: goarch,
BuildTags: []string{goos, goarch},
Compiler: "clang",
Linker: "cc",
CFlags: []string{"--target=" + triple},
GDB: "gdb",
PortReset: "false",
FlashMethod: "native",
Triple: triple,
GOOS: goos,
GOARCH: goarch,
BuildTags: []string{goos, goarch},
Scheduler: "tasks",
Linker: "cc",
DefaultStackSize: 1024 * 64, // 64kB
CFlags: []string{"--target=" + triple},
GDB: []string{"gdb"},
PortReset: "false",
}
if goos == "darwin" {
spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
} else {
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
}
if goarch != "wasm" {
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+".S")
}
if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs.
spec.GDB = []string{"gdb-multiarch"}
if goarch == "arm" && goos == "linux" {
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
spec.Linker = "arm-linux-gnueabihf-gcc"
spec.GDB = "arm-linux-gnueabihf-gdb"
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
}
if goarch == "arm64" && goos == "linux" {
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu")
spec.Linker = "aarch64-linux-gnu-gcc"
spec.GDB = "aarch64-linux-gnu-gdb"
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
}
if goarch == "386" {
spec.CFlags = []string{"-m32"}
spec.LDFlags = []string{"-m32"}
if goarch == "386" && runtime.GOARCH == "amd64" {
spec.CFlags = append(spec.CFlags, "-m32")
spec.LDFlags = append(spec.LDFlags, "-m32")
}
}
return &spec, nil
}
// LookupGDB looks up a gdb executable.
func (spec *TargetSpec) LookupGDB() (string, error) {
if len(spec.GDB) == 0 {
return "", errors.New("gdb not configured in the target specification")
}
for _, d := range spec.GDB {
_, err := exec.LookPath(d)
if err == nil {
return d, nil
}
}
return "", errors.New("no gdb found configured in the target specification (" + strings.Join(spec.GDB, ", ") + ")")
}
+68 -1
View File
@@ -1,6 +1,9 @@
package compileopts
import "testing"
import (
"reflect"
"testing"
)
func TestLoadTarget(t *testing.T) {
_, err := LoadTarget("arduino")
@@ -17,3 +20,67 @@ func TestLoadTarget(t *testing.T) {
t.Error("LoadTarget failed for wrong reason:", err)
}
}
func TestOverrideProperties(t *testing.T) {
baseAutoStackSize := true
base := &TargetSpec{
GOOS: "baseGoos",
CPU: "baseCpu",
Features: []string{"bf1", "bf2"},
BuildTags: []string{"bt1", "bt2"},
Emulator: []string{"be1", "be2"},
DefaultStackSize: 42,
AutoStackSize: &baseAutoStackSize,
}
childAutoStackSize := false
child := &TargetSpec{
GOOS: "",
CPU: "chlidCpu",
Features: []string{"cf1", "cf2"},
Emulator: []string{"ce1", "ce2"},
AutoStackSize: &childAutoStackSize,
DefaultStackSize: 64,
}
base.overrideProperties(child)
if base.GOOS != "baseGoos" {
t.Errorf("Overriding failed : got %v", base.GOOS)
}
if base.CPU != "chlidCpu" {
t.Errorf("Overriding failed : got %v", base.CPU)
}
if !reflect.DeepEqual(base.Features, []string{"cf1", "cf2", "bf1", "bf2"}) {
t.Errorf("Overriding failed : got %v", base.Features)
}
if !reflect.DeepEqual(base.BuildTags, []string{"bt1", "bt2"}) {
t.Errorf("Overriding failed : got %v", base.BuildTags)
}
if !reflect.DeepEqual(base.Emulator, []string{"ce1", "ce2"}) {
t.Errorf("Overriding failed : got %v", base.Emulator)
}
if *base.AutoStackSize != false {
t.Errorf("Overriding failed : got %v", base.AutoStackSize)
}
if base.DefaultStackSize != 64 {
t.Errorf("Overriding failed : got %v", base.DefaultStackSize)
}
baseAutoStackSize = true
base = &TargetSpec{
AutoStackSize: &baseAutoStackSize,
DefaultStackSize: 42,
}
child = &TargetSpec{
AutoStackSize: nil,
DefaultStackSize: 0,
}
base.overrideProperties(child)
if *base.AutoStackSize != true {
t.Errorf("Overriding failed : got %v", base.AutoStackSize)
}
if base.DefaultStackSize != 42 {
t.Errorf("Overriding failed : got %v", base.DefaultStackSize)
}
}
+13 -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
@@ -159,6 +159,13 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
}
switch inst := inst.(type) {
case *ssa.Alloc:
// An alloc is never nil.
return
case *ssa.FreeVar:
// A free variable is allocated in a parent function and is thus never
// nil.
return
case *ssa.IndexAddr:
// This pointer is the result of an index operation into a slice or
// array. Such slices/arrays are already bounds checked so the pointer
@@ -189,7 +196,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 +219,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
+28 -15
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
@@ -57,10 +58,10 @@ func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm
// Expand an argument type to a list that can be used in a function call
// parameter list.
func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
switch t.TypeKind() {
case llvm.StructTypeKind:
fieldInfos := flattenAggregateType(t, name, goType)
fieldInfos := c.flattenAggregateType(t, name, goType)
if len(fieldInfos) <= maxFieldsPerParam {
return fieldInfos
} else {
@@ -104,7 +105,7 @@ func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() {
case llvm.StructTypeKind:
fieldInfos := flattenAggregateType(v.Type(), "", nil)
fieldInfos := b.flattenAggregateType(v.Type(), "", nil)
if len(fieldInfos) <= maxFieldsPerParam {
fields := b.flattenAggregate(v)
if len(fields) != len(fieldInfos) {
@@ -123,12 +124,15 @@ func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
// Try to flatten a struct type to a list of types. Returns a 1-element slice
// with the passed in type if this is not possible.
func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
typeFlags := getTypeFlags(goType)
switch t.TypeKind() {
case llvm.StructTypeKind:
paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
var paramInfos []paramInfo
for i, subfield := range t.StructElementTypes() {
if c.targetData.TypeAllocSize(subfield) == 0 {
continue
}
suffix := strconv.Itoa(i)
if goType != nil {
// Try to come up with a good suffix for this struct field,
@@ -151,7 +155,7 @@ func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramIn
suffix = []string{"context", "funcptr"}[i]
}
}
subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
for i := range subInfos {
subInfos[i].flags |= typeFlags
}
@@ -217,8 +221,11 @@ func extractSubfield(t types.Type, field int) types.Type {
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := make([]uint64, 0, t.StructElementTypesCount())
var fields []uint64
for fieldIndex, field := range t.StructElementTypes() {
if c.targetData.TypeAllocSize(field) == 0 {
continue
}
suboffsets := c.flattenAggregateTypeOffsets(field)
offset := c.targetData.ElementOffset(t, fieldIndex)
for i := range suboffsets {
@@ -237,8 +244,11 @@ func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() {
case llvm.StructTypeKind:
fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
for i := range v.Type().StructElementTypes() {
var fields []llvm.Value
for i, field := range v.Type().StructElementTypes() {
if b.targetData.TypeAllocSize(field) == 0 {
continue
}
subfield := b.CreateExtractValue(v, i, "")
subfields := b.flattenAggregate(subfield)
fields = append(fields, subfields...)
@@ -265,10 +275,13 @@ func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Val
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
switch t.TypeKind() {
case llvm.StructTypeKind:
flattened := flattenAggregateType(t, "", nil)
flattened := b.flattenAggregateType(t, "", nil)
if len(flattened) <= maxFieldsPerParam {
value := llvm.ConstNull(t)
for i, subtyp := range t.StructElementTypes() {
if b.targetData.TypeAllocSize(subtyp) == 0 {
continue
}
structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
fields = remaining
value = b.CreateInsertValue(value, structField, i, "")
+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}, "")
}
+565 -479
View File
File diff suppressed because it is too large Load Diff
+186
View File
@@ -0,0 +1,186 @@
package compiler
import (
"flag"
"go/types"
"io/ioutil"
"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) {
t.Parallel()
// Check LLVM version.
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if err != nil {
t.Fatal("could not parse LLVM version:", llvm.Version)
}
if llvmMajor < 11 {
// It is likely this version needs to be bumped in the future.
// The goal is to at least test the LLVM version that's used by default
// in TinyGo and (if possible without too many workarounds) also some
// previous versions.
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
}
tests := []struct {
file string
target string
}{
{"basic.go", ""},
{"pointer.go", ""},
{"slice.go", ""},
{"string.go", ""},
{"float.go", ""},
{"interface.go", ""},
{"func.go", ""},
{"pragma.go", ""},
{"goroutine.go", "wasm"},
{"goroutine.go", "cortex-m-qemu"},
}
for _, tc := range tests {
name := tc.file
targetString := "wasm"
if tc.target != "" {
targetString = tc.target
name = tc.file + "-" + tc.target
}
t.Run(name, func(t *testing.T) {
target, err := compileopts.LoadTarget(targetString)
if err != nil {
t.Fatal("failed to load target:", err)
}
config := &compileopts.Config{
Options: &compileopts.Options{},
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
}
// Load entire program AST into memory.
lprogram, err := loader.Load(config, []string{"./testdata/" + tc.file}, 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", tc.file, err)
}
// Compile AST to IR.
program := lprogram.LoadSSA()
pkg := lprogram.MainPkg()
mod, errs := CompilePackage(tc.file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
if errs != nil {
for _, err := range errs {
t.Error(err)
}
return
}
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
if err != nil {
t.Error(err)
}
// 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()
outFilePrefix := tc.file[:len(tc.file)-3]
if tc.target != "" {
outFilePrefix += "-" + tc.target
}
outPath := "./testdata/" + outFilePrefix + ".ll"
// Update test if needed. Do not check the result.
if *flagUpdate {
err := ioutil.WriteFile(outPath, []byte(mod.String()), 0666)
if err != nil {
t.Error("failed to write updated output file:", err)
}
return
}
expected, err := ioutil.ReadFile(outPath)
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())
}
})
}
}
// 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]
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
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
}
out = append(out, line)
}
return out
}
+40 -40
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,12 +347,12 @@ 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.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
for _, param := range callback.Params {
for _, param := range getParams(callback.Signature) {
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
}
deferFrameType := b.ctx.StructType(valueTypes, false)
@@ -364,7 +361,7 @@ func (b *builder) createRunDefers() {
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range callback.Params {
for i := range getParams(callback.Signature) {
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)
@@ -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")
}
+70
View File
@@ -0,0 +1,70 @@
package compiler
import (
"fmt"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns.
// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings.
// Copied from https://github.com/golang/go/blob/219fe9d547d09d3de1b024c6c8b8314dd0bf12e4/src/cmd/compile/internal/noder/noder.go#L1717-L1776
func parseGoEmbed(args string) ([]string, error) {
var list []string
for args = strings.TrimSpace(args); args != ""; args = strings.TrimSpace(args) {
var path string
Switch:
switch args[0] {
default:
i := len(args)
for j, c := range args {
if unicode.IsSpace(c) {
i = j
break
}
}
path = args[:i]
args = args[i:]
case '`':
i := strings.Index(args[1:], "`")
if i < 0 {
return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
}
path = args[1 : 1+i]
args = args[1+i+1:]
case '"':
i := 1
for ; i < len(args); i++ {
if args[i] == '\\' {
i++
continue
}
if args[i] == '"' {
q, err := strconv.Unquote(args[:i+1])
if err != nil {
return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
}
path = q
args = args[i+1:]
break Switch
}
}
if i >= len(args) {
return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
}
}
if args != "" {
r, _ := utf8.DecodeRuneInString(args)
if !unicode.IsSpace(r) {
return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
}
}
list = append(list, path)
}
return list, nil
}
+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,
}
+29 -18
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,24 +20,23 @@ 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:
sigGlobal := c.getTypeCode(sig)
case "switch":
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
sigGlobal,
c.getFuncSignatureID(sig),
})
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
funcValueWithSignatureGlobal.SetGlobalConstant(true)
funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage)
funcValueWithSignatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
}
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
default:
@@ -51,6 +49,19 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
return funcValue
}
// getFuncSignatureID returns a new external global for a given signature. This
// global reference is not real, it is only used during func lowering to assign
// signature types to functions and will then be removed.
func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value {
sigGlobalName := "reflect/types.funcid:" + getTypeCodeName(sig)
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetGlobalConstant(true)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation.
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@@ -67,12 +78,12 @@ 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)
sigGlobal := b.getFuncSignatureID(sig)
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
default:
@@ -83,11 +94,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")
@@ -125,13 +136,13 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// The receiver is not an interface, but a i8* type.
recv = c.i8ptrType
}
for _, info := range expandFormalParamType(recv, "", nil) {
for _, info := range c.expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
}
for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(typ.Params().At(i).Type())
for _, info := range expandFormalParamType(subType, "", nil) {
for _, info := range c.expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
}
@@ -149,7 +160,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 +175,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
}
+131 -26
View File
@@ -5,34 +5,124 @@ package compiler
import (
"go/token"
"go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createGoInstruction starts a new goroutine with the provided function pointer
// and parameters.
// In general, you should pass all regular parameters plus the context parameter.
// There is one exception: the task-based scheduler needs to have the function
// pointer passed in as a parameter too in addition to the context.
//
// Because a go statement doesn't return anything, return undef.
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
// createGo emits code to start a new goroutine.
func (b *builder) createGo(instr *ssa.Go) {
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.getValue(param))
}
var prefix string
var funcPtr llvm.Value
hasContext := false
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
// Goroutine call is regular function call. No context is necessary.
if b.Scheduler == "coroutines" {
// The context parameter is assumed to be always present in the
// coroutines scheduler.
context = llvm.Undef(b.i8ptrType)
}
case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue := b.getValue(value)
context = b.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
if !context.IsNil() {
params = append(params, context) // context parameter
hasContext = true
}
funcPtr = b.getFunction(callee)
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
// We cheat. None of the builtins do any long or blocking operation, so
// we might as well run these builtins right away without the program
// noticing the difference.
// Possible exceptions:
// - copy: this is a possibly long operation, but not a blocking
// operation. Semantically it makes no difference to run it right
// away (not in a goroutine). However, in practice it makes no sense
// to run copy in a goroutine as there is no way to (safely) know
// when it is finished.
// - panic: the error message would appear in the parent goroutine.
// But because `go panic("err")` would halt the program anyway
// (there is no recover), panicking right away would give the same
// behavior as creating a goroutine, switching the scheduler to that
// goroutine, and panicking there. So this optimization seems
// correct.
// - recover: because it runs in a new goroutine, it is never a
// deferred function. Thus this is a no-op.
if builtin.Name() == "recover" {
// This is a no-op, even in a deferred function:
// go recover()
return
}
var argTypes []types.Type
var argValues []llvm.Value
for _, arg := range instr.Call.Args {
argTypes = append(argTypes, arg.Type())
argValues = append(argValues, b.getValue(arg))
}
b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
return
} else if !instr.Call.IsInvoke() {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context) // context parameter
hasContext = true
switch b.Scheduler {
case "none", "coroutines":
// There are no additional parameters needed for the goroutine start operation.
case "tasks":
// Add the function pointer as a parameter to start the goroutine.
params = append(params, funcPtr)
default:
panic("unknown scheduler type")
}
prefix = b.fn.RelString(nil)
} else {
b.addError(instr.Pos(), "todo: go on interface call")
return
}
paramBundle := b.emitPointerPack(params)
var callee, stackSize llvm.Value
switch b.Scheduler() {
switch b.Scheduler {
case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
if b.AutomaticStackSize() {
callee = b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos())
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)
if b.Scheduler == "tasks" && b.DefaultStackSize == 0 {
b.addError(instr.Pos(), "default stack size for goroutines is not set")
}
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
}
case "coroutines":
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
@@ -42,8 +132,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)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
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)}, "")
}
// createGoroutineStartWrapper creates a wrapper for the task-based
@@ -64,7 +154,12 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
// allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a
// return value.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
//
// The hasContext parameter indicates whether the context parameter (the second
// to last parameter of the function) is used for this wrapper. If hasContext is
// false, the parameter bundle is assumed to have no context parameter and undef
// is passed instead.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
var wrapper llvm.Value
builder := c.ctx.NewBuilder()
@@ -81,14 +176,14 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
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
@@ -111,8 +206,15 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes()
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
params = append(params, llvm.Undef(c.i8ptrType))
paramTypes = paramTypes[:len(paramTypes)-1] // strip parentHandle parameter
if !hasContext {
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
}
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
if !hasContext {
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
}
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy parentHandle parameter
// Create the call.
builder.CreateCall(fn, params, "")
@@ -138,14 +240,14 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
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
@@ -174,8 +276,11 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Get the function pointer.
fnPtr := params[len(params)-1]
// Ignore the last param, which isn't used anymore.
// TODO: avoid this extra "parent handle" parameter in most functions.
// The last parameter in the packed object has somewhat of a dual role.
// Inside the parameter bundle it's the function pointer, stored right
// after the context pointer. But in the IR call instruction, it's the
// parentHandle function that's always undef outside of the coroutines
// scheduler. Thus, make the parameter undef here.
params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call.
+38
View File
@@ -163,6 +163,44 @@ func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
return b.CreateCall(target, llvmArgs, ""), nil
}
// This is a compiler builtin which emits an inline SVCall instruction. It can
// be one of:
//
// func SVCall0(num uintptr) uintptr
// func SVCall1(num uintptr, a1 interface{}) uintptr
// func SVCall2(num uintptr, a1, a2 interface{}) uintptr
// func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
// func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
//
// The num parameter must be a constant. All other parameters may be any scalar
// value supported by LLVM inline assembly.
// Same as emitSVCall but for AArch64
func (b *builder) emitSV64Call(args []ssa.Value) (llvm.Value, error) {
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
asm := "svc #" + strconv.FormatUint(num, 10)
constraints := "={x0}"
for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X
if i == 0 {
constraints += ",0"
} else {
constraints += ",{x" + strconv.Itoa(i) + "}"
}
llvmValue := b.getValue(arg)
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
// Implement the ARM64 calling convention by marking x1-x7 as
// clobbered. x0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
return b.CreateCall(target, llvmArgs, ""), nil
}
// This is a compiler builtin which emits CSR instructions. It can be one of:
//
// func (csr CSR) Get() uintptr
+123 -54
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"
)
@@ -25,16 +24,7 @@ import (
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := b.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := b.getTypeCode(typ)
itfMethodSetGlobal := b.getTypeMethodSet(typ)
itfConcreteTypeGlobal := b.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := b.getLLVMRuntimeType("typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(b.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
itfConcreteTypeGlobal.SetGlobalConstant(true)
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
}
itfTypeCode := b.CreatePtrToInt(itfConcreteTypeGlobal, b.uintptrType, "")
itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "")
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = b.CreateInsertValue(itf, itfValue, 1, "")
@@ -55,6 +45,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// reflect lowering simpler.
var references llvm.Value
var length int64
var methodSet llvm.Value
var ptrTo llvm.Value
switch typ := typ.(type) {
case *types.Named:
references = c.getTypeCode(typ.Underlying())
@@ -71,18 +63,32 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// Take a pointer to the typecodeID of the first field (if it exists).
structGlobal := c.makeStructTypeFields(typ)
references = llvm.ConstBitCast(structGlobal, global.Type())
case *types.Interface:
methodSetGlobal := c.getInterfaceMethodSet(typ)
references = llvm.ConstBitCast(methodSetGlobal, global.Type())
}
if _, ok := typ.Underlying().(*types.Interface); !ok {
methodSet = c.getTypeMethodSet(typ)
}
if _, ok := typ.Underlying().(*types.Pointer); !ok {
ptrTo = c.getTypeCode(types.NewPointer(typ))
}
globalValue := llvm.ConstNull(global.Type().ElementType())
if !references.IsNil() {
// Set the 'references' field of the runtime.typecodeID struct.
globalValue := llvm.ConstNull(global.Type().ElementType())
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.PrivateLinkage)
}
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
if !methodSet.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
}
if !ptrTo.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetGlobalConstant(true)
}
return global
@@ -104,8 +110,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
fieldName.SetLinkage(llvm.PrivateLinkage)
fieldName.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
if typ.Tag(i) != "" {
@@ -113,8 +119,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
fieldTag.SetLinkage(llvm.PrivateLinkage)
fieldTag.SetUnnamedAddr(true)
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
}
@@ -187,7 +193,7 @@ func getTypeCodeName(t types.Type) string {
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ {
methods[i] = getTypeCodeName(t.Method(i).Type())
methods[i] = t.Method(i).Name() + ":" + getTypeCodeName(t.Method(i).Type())
}
return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map:
@@ -236,7 +242,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 +253,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
}
@@ -264,7 +271,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.PrivateLinkage)
global.SetLinkage(llvm.LinkOnceODRLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
@@ -295,7 +302,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.PrivateLinkage)
global.SetLinkage(llvm.LinkOnceODRLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
@@ -303,11 +310,22 @@ 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)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
signature := methodSignature(method)
var globalName string
if token.IsExported(method.Name()) {
globalName = "reflect/methods." + signature
} else {
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
}
signatureGlobal := c.mod.NamedGlobal(globalName)
if signatureGlobal.IsNil() {
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
// TODO: put something useful in these globals, such as the method
// signature. Useful to one day implement reflect.Value.Method(n).
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false))
signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
signatureGlobal.SetGlobalConstant(true)
signatureGlobal.SetAlignment(1)
}
return signatureGlobal
}
@@ -338,10 +356,16 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
} else {
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() {
// Create a new typecode global.
assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
assertedTypeCodeGlobal.SetGlobalConstant(true)
}
// Type assert on concrete type.
// Call runtime.typeAssert, which will be lowered to a simple icmp or
// const false in the interface lowering pass.
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType)
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
}
@@ -357,8 +381,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 +460,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,9 +469,9 @@ 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.Signature.Recv().Type())
var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) {
for _, info := range c.expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
}
@@ -457,19 +481,17 @@ 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.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetUnnamedAddr(true)
// Create a new builder just to create this wrapper.
@@ -480,9 +502,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 +514,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
}
+6 -9
View File
@@ -39,14 +39,11 @@ 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() {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
}
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
global.SetLinkage(llvm.PrivateLinkage)
global.SetVisibility(llvm.HiddenVisibility)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
initializer := llvm.ConstNull(globalLLVMType)
@@ -55,8 +52,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 +76,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
}
}
+3
View File
@@ -150,6 +150,9 @@ func (s *stdSizes) Sizeof(T types.Type) int64 {
return s.PtrSize * 2
case *types.Pointer:
return s.PtrSize
case *types.Signature:
// Func values in TinyGo are two words in size.
return s.PtrSize * 2
default:
panic("unknown type: " + t.String())
}
+362 -32
View File
@@ -15,6 +15,314 @@ 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
section string // go:section - object file section name
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 getParams(fn.Signature) {
paramType := c.getLLVMType(param.Type())
paramFragmentInfos := c.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.sliceAppend":
// Appending a slice will only read the to-be-appended slice, it won't
// be modified.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.sliceCopy":
// Copying a slice won't capture any of the parameters.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.trackPointer":
// 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)
}
}
}
// Synthetic functions are functions that do not appear in the source code,
// they are artificially constructed. Usually they are wrapper functions
// that are not referenced anywhere except in a SSA call instruction so
// should be created right away.
// The exception is the package initializer, which does appear in the
// *ssa.Package members and so shouldn't be created here.
if fn.Synthetic != "" && fn.Synthetic != "package initializer" {
irbuilder := c.ctx.NewBuilder()
b := newBuilder(c, irbuilder, fn)
b.createFunction()
irbuilder.Dispose()
llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
llvmFn.SetUnnamedAddr(true)
}
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:section":
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
info.section = parts[1]
}
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
}
}
}
}
// getParams returns the function parameters, including the receiver at the
// start. This is an alternative to the Params member of *ssa.Function, which is
// not yet populated when the package has not yet been built.
func getParams(sig *types.Signature) []*types.Var {
params := []*types.Var{}
if sig.Recv() != nil {
params = append(params, sig.Recv())
}
for i := 0; i < sig.Params().Len(); i++ {
params = append(params, sig.Params().At(i))
}
return params
}
// 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).
@@ -22,29 +330,28 @@ type globalInfo struct {
linkName string // go:extern
extern bool // go:extern
align int // go:align
section string // go:section
embeds string // go:embed
}
// loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals.
func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
c.astComments[id] = decl.Doc
}
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
for _, file := range pkg.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkg.Pkg.Path() + "." + name.Name
c.astComments[id] = decl.Doc
}
}
}
@@ -63,30 +370,26 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
typ := g.Type().(*types.Pointer).Elem()
llvmType := c.getLLVMType(typ)
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
if !info.extern {
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
llvmGlobal.SetLinkage(llvm.InternalLinkage)
}
// Set alignment from the //go:align comment.
var alignInBits uint32
if info.align < 0 || info.align&(info.align-1) != 0 {
alignment := c.targetData.ABITypeAlignment(llvmType)
if info.align > alignment {
alignment = info.align
}
if alignment <= 0 || alignment&(alignment-1) != 0 {
// Check for power-of-two (or 0).
// See: https://stackoverflow.com/a/108360
c.addError(g.Pos(), "global variable alignment must be a positive power of two")
} else {
// Set the alignment only when it is a power of two.
alignInBits = uint32(info.align) ^ uint32(info.align-1)
if info.align > c.targetData.ABITypeAlignment(llvmType) {
llvmGlobal.SetAlignment(info.align)
}
alignInBits = uint32(alignment) ^ uint32(alignment-1)
llvmGlobal.SetAlignment(alignment)
}
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,
@@ -142,6 +445,33 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
if err == nil {
info.align = align
}
case "//go:section":
if len(parts) == 2 {
info.section = parts[1]
}
case "//go:embed":
raw := strings.TrimPrefix(comment.Text, "//go:embed")
info.embeds += " " + raw
}
}
}
// 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
}
+38 -16
View File
@@ -10,14 +10,14 @@ import (
"tinygo.org/x/go-llvm"
)
// createSyscall emits an inline system call instruction, depending on the
// target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// createRawSyscall creates a system call with the provided system call number
// and returns the result as a single integer (the system call result). The
// result is not further interpreted.
func (b *builder) createRawSyscall(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.
@@ -57,8 +57,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{rcx},~{r11}"
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":
return b.CreateCall(target, args, ""), nil
case b.GOARCH == "386" && b.GOOS == "linux":
// Sources:
// syscall(2) man page
// https://stackoverflow.com/a/2538212
@@ -83,8 +83,8 @@ 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":
return b.CreateCall(target, args, ""), nil
case b.GOARCH == "arm" && b.GOOS == "linux":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
args := []llvm.Value{}
@@ -115,8 +115,8 @@ 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":
return b.CreateCall(target, args, ""), nil
case b.GOARCH == "arm64" && b.GOOS == "linux":
// Source: syscall(2) man page.
args := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -147,11 +147,20 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "")
return b.CreateCall(target, args, ""), 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)
}
switch b.GOOS() {
}
// createSyscall emits instructions for the syscall.Syscall* family of
// functions, depending on the target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
switch b.GOOS {
case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno
// Pseudocode:
@@ -187,6 +196,19 @@ 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)
}
}
// createRawSyscallNoError emits instructions for the Linux-specific
// syscall.rawSyscallNoError function.
func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) {
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "")
return retval, nil
}
+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)
+100
View File
@@ -0,0 +1,100 @@
; ModuleID = 'basic.go'
source_filename = "basic.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = add i32 %x, %y
ret i32 %0
}
define hidden i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp eq i32 %x, %y
ret i1 %0
}
define hidden i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp oeq float %x, %y
ret i1 %0
}
define hidden i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp une float %x, %y
ret i1 %0
}
define hidden i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp olt float %x, %y
ret i1 %0
}
define hidden i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp ole float %x, %y
ret i1 %0
}
define hidden i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp ogt float %x, %y
ret i1 %0
}
define hidden i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = fcmp oge float %x, %y
ret i1 %0
}
define hidden float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float %x.r
}
define hidden float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float %x.i
}
define hidden { 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 hidden { 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 hidden { 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)
}
+82
View File
@@ -0,0 +1,82 @@
; ModuleID = 'float.go'
source_filename = "float.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden 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 hidden float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret float 0x41F0000000000000
}
define hidden i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 -1
}
define hidden { 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 hidden 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 hidden 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 hidden 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 hidden 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
}
+12
View File
@@ -0,0 +1,12 @@
package main
func foo(callback func(int)) {
callback(3)
}
func bar() {
foo(someFunc)
}
func someFunc(int) {
}
+47
View File
@@ -0,0 +1,47 @@
; ModuleID = 'func.go'
source_filename = "func.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.someFunc$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @main.someFunc to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.foo(i8* %callback.context, i32 %callback.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i32 @runtime.getFuncPtr(i8* %callback.context, i32 %callback.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null)
%1 = icmp eq i32 %0, 0
br i1 %1, label %fpcall.throw, label %fpcall.next
fpcall.throw: ; preds = %entry
call void @runtime.nilPanic(i8* undef, i8* null)
unreachable
fpcall.next: ; preds = %entry
%2 = inttoptr i32 %0 to void (i32, i8*, i8*)*
call void %2(i32 3, i8* %callback.context, i8* undef)
ret void
}
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
declare void @runtime.nilPanic(i8*, i8*)
define hidden void @main.bar(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @main.foo(i8* undef, i32 ptrtoint (%runtime.funcValueWithSignature* @"main.someFunc$withSignature" to i32), i8* undef, i8* undef)
ret void
}
define hidden void @main.someFunc(i32 %arg0, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
+150
View File
@@ -0,0 +1,150 @@
; ModuleID = 'goroutine.go'
source_filename = "goroutine.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i32, i32* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null)
ret void
}
declare void @main.regularFunction(i32, i8*, i8*)
define linkonce_odr void @"main.regularFunction$gowrapper"(i8* %0) unnamed_addr #0 {
entry:
%unpack.int = ptrtoint i8* %0 to i32
call void @main.regularFunction(i32 %unpack.int, i8* undef, i8* undef)
ret void
}
declare i32 @"internal/task.getGoroutineStackSize"(i32, i8*, i8*)
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null)
ret void
}
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #1 {
entry:
%unpack.int = ptrtoint i8* %0 to i32
call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, i8* undef, i8* undef)
ret void
}
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%n = call i8* @runtime.alloc(i32 4, i8* undef, i8* null)
%0 = bitcast i8* %n to i32*
store i32 3, i32* %0, align 4
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %n, i8** %4, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* nonnull %1, i32 %stacksize, i8* undef, i8* null)
%5 = load i32, i32* %0, align 4
call void @runtime.printint32(i32 %5, i8* undef, i8* null)
ret void
}
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%unpack.ptr = bitcast i8* %context to i32*
store i32 7, i32* %unpack.ptr, align 4
ret void
}
define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #2 {
entry:
%1 = bitcast i8* %0 to i32*
%2 = load i32, i32* %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
call void @"main.closureFunctionGoroutine$1"(i32 %2, i8* %5, i8* undef)
ret void
}
declare void @runtime.printint32(i32, i8*, i8*)
define hidden void @main.funcGoroutine(i8* %fn.context, void (i32, i8*, i8*)* %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i8* @runtime.alloc(i32 12, i8* undef, i8* null)
%1 = bitcast i8* %0 to i32*
store i32 5, i32* %1, align 4
%2 = getelementptr inbounds i8, i8* %0, i32 4
%3 = bitcast i8* %2 to i8**
store i8* %fn.context, i8** %3, align 4
%4 = getelementptr inbounds i8, i8* %0, i32 8
%5 = bitcast i8* %4 to void (i32, i8*, i8*)**
store void (i32, i8*, i8*)* %fn.funcptr, void (i32, i8*, i8*)** %5, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* nonnull %0, i32 %stacksize, i8* undef, i8* null)
ret void
}
define linkonce_odr void @main.funcGoroutine.gowrapper(i8* %0) unnamed_addr #3 {
entry:
%1 = bitcast i8* %0 to i32*
%2 = load i32, i32* %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
%6 = getelementptr inbounds i8, i8* %0, i32 8
%7 = bitcast i8* %6 to void (i32, i8*, i8*)**
%8 = load void (i32, i8*, i8*)*, void (i32, i8*, i8*)** %7, align 4
call void %8(i32 %2, i8* %5, i8* undef)
ret void
}
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null)
ret void
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
attributes #0 = { "tinygo-gowrapper"="main.regularFunction" }
attributes #1 = { "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" }
attributes #2 = { "tinygo-gowrapper"="main.closureFunctionGoroutine$1" }
attributes #3 = { "tinygo-gowrapper" }
+106
View File
@@ -0,0 +1,106 @@
; ModuleID = 'goroutine.go'
source_filename = "goroutine.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i8* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
@"main.regularFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"main.inlineFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.closureFunctionGoroutine$1$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @main.regularFunction to i32), i8* bitcast ({ i32, i8* }* @"main.regularFunctionGoroutine$pack" to i8*), i32 undef, i8* undef, i8* null)
ret void
}
declare void @main.regularFunction(i32, i8*, i8*)
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.inlineFunctionGoroutine$1" to i32), i8* bitcast ({ i32, i8* }* @"main.inlineFunctionGoroutine$pack" to i8*), i32 undef, i8* undef, i8* null)
ret void
}
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%n = call i8* @runtime.alloc(i32 4, i8* undef, i8* null)
%0 = bitcast i8* %n to i32*
store i32 3, i32* %0, align 4
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %n, i8** %4, align 4
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* nonnull %1, i32 undef, i8* undef, i8* null)
%5 = load i32, i32* %0, align 4
call void @runtime.printint32(i32 %5, i8* undef, i8* null)
ret void
}
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%unpack.ptr = bitcast i8* %context to i32*
store i32 7, i32* %unpack.ptr, align 4
ret void
}
declare void @runtime.printint32(i32, i8*, i8*)
define hidden void @main.funcGoroutine(i8* %fn.context, i32 %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i32 @runtime.getFuncPtr(i8* %fn.context, i32 %fn.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null)
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %fn.context, i8** %4, align 4
call void @"internal/task.start"(i32 %0, i8* nonnull %1, i32 undef, i8* undef, i8* null)
ret void
}
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null)
ret void
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
+43
View File
@@ -0,0 +1,43 @@
package main
func regularFunctionGoroutine() {
go regularFunction(5)
}
func inlineFunctionGoroutine() {
go func(x int) {
}(5)
}
func closureFunctionGoroutine() {
n := 3
go func(x int) {
n = 7
}(5)
print(n) // note: this is racy (but good enough for this test)
}
func funcGoroutine(fn func(x int)) {
go fn(5)
}
func recoverBuiltinGoroutine() {
// This is a no-op.
go recover()
}
func copyBuiltinGoroutine(dst, src []byte) {
// This is not run in a goroutine. While this copy operation can indeed take
// some time (if there is a lot of data to copy), there is no race-free way
// to make use of the result so it's unlikely applications will make use of
// it. And doing it this way should be just within the Go specification.
go copy(dst, src)
}
func closeBuiltinGoroutine(ch chan int) {
// This builtin is executed directly, not in a goroutine.
// The observed behavior is the same.
go close(ch)
}
func regularFunction(x int)
+54
View File
@@ -0,0 +1,54 @@
// This file tests interface types and interface builtins.
package main
// Test interface construction.
func simpleType() interface{} {
return 0
}
func pointerType() interface{} {
// Pointers have an element type, in this case int.
var v *int
return v
}
func interfaceType() interface{} {
// Interfaces can exist in interfaces, but only indirectly (through
// pointers).
var v *error
return v
}
func anonymousInterfaceType() interface{} {
var v *interface {
String() string
}
return v
}
// Test interface builtins.
func isInt(itf interface{}) bool {
_, ok := itf.(int)
return ok
}
func isError(itf interface{}) bool {
// Interface assert on (builtin) named interface type.
_, ok := itf.(error)
return ok
}
func isStringer(itf interface{}) bool {
// Interface assert on anonymous interface type.
_, ok := itf.(interface {
String() string
})
return ok
}
func callErrorMethod(itf error) string {
return itf.Error()
}
+101
View File
@@ -0,0 +1,101 @@
; ModuleID = 'interface.go'
source_filename = "interface.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID* }
%runtime.interfaceMethodInfo = type { i8*, i32 }
%runtime._interface = type { i32, i8* }
%runtime._string = type { i8*, i32 }
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int" }
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error" }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }
@"reflect/methods.Error() string" = linkonce_odr constant i8 0, align 1
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }
@"reflect/methods.String() string" = linkonce_odr constant i8 0, align 1
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.String() string"]
@"reflect/types.typeid:basic:int" = external constant i8
@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden %runtime._interface @main.simpleType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* null }
}
define hidden %runtime._interface @main.pointerType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:basic:int" to i32), i8* null }
}
define hidden %runtime._interface @main.interfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:named:error" to i32), i8* null }
}
define hidden %runtime._interface @main.anonymousInterfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), i8* null }
}
define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.typeid:basic:int", i8* undef, i8* null)
br i1 %typecode, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
br label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
ret i1 %typecode
}
declare i1 @runtime.typeAssert(i32, i8* dereferenceable_or_null(1), i8*, i8*)
define hidden i1 @main.isError(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* undef, i8* null)
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
br label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
ret i1 %0
}
declare i1 @runtime.interfaceImplements(i32, i8** dereferenceable_or_null(4), i8*, i8*)
define hidden i1 @main.isStringer(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"reflect/types.interface:interface{String() string}$interface", i32 0, i32 0), i8* undef, i8* null)
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
br label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
ret i1 %0
}
define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Error() string", i8* undef, i8* null)
%invoke.func.cast = inttoptr i32 %invoke.func to %runtime._string (i8*, i8*, i8*)*
%0 = call %runtime._string %invoke.func.cast(i8* %itf.value, i8* undef, i8* undef)
ret %runtime._string %0
}
declare i32 @runtime.interfaceMethod(i32, i8** dereferenceable_or_null(4), i8* dereferenceable_or_null(1), i8*, i8*)
+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))
}
+51
View File
@@ -0,0 +1,51 @@
; ModuleID = 'pointer.go'
source_filename = "pointer.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret [0 x i32] zeroinitializer
}
define hidden i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = bitcast i8* %x to i32*
ret i32* %0
}
define hidden 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 hidden i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i8* %x
}
define hidden 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 hidden 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 hidden 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
}
+66
View File
@@ -0,0 +1,66 @@
package main
import _ "unsafe"
// Creates an external global with name extern_global.
//go:extern extern_global
var externGlobal [0]byte
// Creates a
//go:align 32
var alignedGlobal [4]uint32
// Test conflicting pragmas (the last one counts).
//go:align 64
//go:align 16
var alignedGlobal16 [4]uint32
// Test exported functions.
//export extern_func
func externFunc() {
}
// Define a function in a different package using go:linkname.
//go:linkname withLinkageName1 somepkg.someFunction1
func withLinkageName1() {
}
// Import a function from a different package using go:linkname.
//go:linkname withLinkageName2 somepkg.someFunction2
func withLinkageName2()
// Function has an 'inline hint', similar to the inline keyword in C.
//go:inline
func inlineFunc() {
}
// Function should never be inlined, equivalent to GCC
// __attribute__((noinline)).
//go:noinline
func noinlineFunc() {
}
// This function should have the specified section.
//go:section .special_function_section
func functionInSection() {
}
//export exportedFunctionInSection
//go:section .special_function_section
func exportedFunctionInSection() {
}
// This function should not: it's only a declaration and not a definition.
//go:section .special_function_section
func undefinedFunctionNotInSection()
//go:section .special_global_section
var globalInSection uint32
//go:section .special_global_section
//go:extern undefinedGlobalNotInSection
var undefinedGlobalNotInSection uint32
//go:align 1024
//go:section .global_section
var multipleGlobalPragmas uint32
+59
View File
@@ -0,0 +1,59 @@
; ModuleID = 'pragma.go'
source_filename = "pragma.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
@extern_global = external global [0 x i8], align 1
@main.alignedGlobal = hidden global [4 x i32] zeroinitializer, align 32
@main.alignedGlobal16 = hidden global [4 x i32] zeroinitializer, align 16
@main.globalInSection = hidden global i32 0, section ".special_global_section", align 4
@undefinedGlobalNotInSection = external global i32, align 4
@main.multipleGlobalPragmas = hidden global i32 0, section ".global_section", align 1024
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define void @extern_func() #0 {
entry:
ret void
}
define hidden void @somepkg.someFunction1(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
declare void @somepkg.someFunction2(i8*, i8*)
; Function Attrs: inlinehint
define hidden void @main.inlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #1 {
entry:
ret void
}
; Function Attrs: noinline
define hidden void @main.noinlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #2 {
entry:
ret void
}
define hidden void @main.functionInSection(i8* %context, i8* %parentHandle) unnamed_addr section ".special_function_section" {
entry:
ret void
}
define void @exportedFunctionInSection() #3 section ".special_function_section" {
entry:
ret void
}
declare void @main.undefinedFunctionNotInSection(i8*, i8*)
attributes #0 = { "wasm-export-name"="extern_func" }
attributes #1 = { inlinehint }
attributes #2 = { noinline }
attributes #3 = { "wasm-export-name"="exportedFunctionInSection" }
+25
View File
@@ -0,0 +1,25 @@
package main
func sliceLen(ints []int) int {
return len(ints)
}
func sliceCap(ints []int) int {
return cap(ints)
}
func sliceElement(ints []int, index int) int {
return ints[index]
}
func sliceAppendValues(ints []int) []int {
return append(ints, 1, 2, 3)
}
func sliceAppendSlice(ints, added []int) []int {
return append(ints, added...)
}
func sliceCopy(dst, src []int) int {
return copy(dst, src)
}
+88
View File
@@ -0,0 +1,88 @@
; ModuleID = 'slice.go'
source_filename = "slice.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %ints.len
}
define hidden i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %ints.cap
}
define hidden i32 @main.sliceElement(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%.not = icmp ult i32 %index, %ints.len
br i1 %.not, label %lookup.next, label %lookup.throw
lookup.throw: ; preds = %entry
call void @runtime.lookupPanic(i8* undef, i8* null)
unreachable
lookup.next: ; preds = %entry
%0 = getelementptr inbounds i32, i32* %ints.data, i32 %index
%1 = load i32, i32* %0, align 4
ret i32 %1
}
declare void @runtime.lookupPanic(i8*, i8*)
define hidden { i32*, i32, i32 } @main.sliceAppendValues(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%varargs = call i8* @runtime.alloc(i32 12, i8* undef, i8* null)
%0 = bitcast i8* %varargs to i32*
store i32 1, i32* %0, align 4
%1 = getelementptr inbounds i8, i8* %varargs, i32 4
%2 = bitcast i8* %1 to i32*
store i32 2, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %varargs, i32 8
%4 = bitcast i8* %3 to i32*
store i32 3, i32* %4, align 4
%append.srcPtr = bitcast i32* %ints.data to i8*
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, i8* undef, i8* null)
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
%append.newBuf = bitcast i8* %append.newPtr to i32*
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
%append.newCap = extractvalue { i8*, i32, i32 } %append.new, 2
%5 = insertvalue { i32*, i32, i32 } undef, i32* %append.newBuf, 0
%6 = insertvalue { i32*, i32, i32 } %5, i32 %append.newLen, 1
%7 = insertvalue { i32*, i32, i32 } %6, i32 %append.newCap, 2
ret { i32*, i32, i32 } %7
}
declare { i8*, i32, i32 } @runtime.sliceAppend(i8*, i8* nocapture readonly, i32, i32, i32, i32, i8*, i8*)
define hidden { i32*, i32, i32 } @main.sliceAppendSlice(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32* %added.data, i32 %added.len, i32 %added.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%append.srcPtr = bitcast i32* %ints.data to i8*
%append.srcPtr1 = bitcast i32* %added.data to i8*
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* %append.srcPtr1, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, i8* undef, i8* null)
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
%append.newBuf = bitcast i8* %append.newPtr to i32*
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
%append.newCap = extractvalue { i8*, i32, i32 } %append.new, 2
%0 = insertvalue { i32*, i32, i32 } undef, i32* %append.newBuf, 0
%1 = insertvalue { i32*, i32, i32 } %0, i32 %append.newLen, 1
%2 = insertvalue { i32*, i32, i32 } %1, i32 %append.newCap, 2
ret { i32*, i32, i32 } %2
}
define hidden i32 @main.sliceCopy(i32* %dst.data, i32 %dst.len, i32 %dst.cap, i32* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%copy.dstPtr = bitcast i32* %dst.data to i8*
%copy.srcPtr = bitcast i32* %src.data to i8*
%copy.n = call i32 @runtime.sliceCopy(i8* %copy.dstPtr, i8* %copy.srcPtr, i32 %dst.len, i32 %src.len, i32 4, i8* undef, i8* null)
ret i32 %copy.n
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
+29
View File
@@ -0,0 +1,29 @@
package main
func someString() string {
return "foo"
}
func zeroLengthString() string {
return ""
}
func stringLen(s string) int {
return len(s)
}
func stringIndex(s string, index int) byte {
return s[index]
}
func stringCompareEqual(s1, s2 string) bool {
return s1 == s2
}
func stringCompareUnequal(s1, s2 string) bool {
return s1 != s2
}
func stringCompareLarger(s1, s2 string) bool {
return s1 > s2
}
+71
View File
@@ -0,0 +1,71 @@
; ModuleID = 'string.go'
source_filename = "string.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime._string = type { i8*, i32 }
@"main.someString$string" = internal unnamed_addr constant [3 x i8] c"foo", align 1
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden %runtime._string @main.someString(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._string { i8* getelementptr inbounds ([3 x i8], [3 x i8]* @"main.someString$string", i32 0, i32 0), i32 3 }
}
define hidden %runtime._string @main.zeroLengthString(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret %runtime._string zeroinitializer
}
define hidden i32 @main.stringLen(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret i32 %s.len
}
define hidden i8 @main.stringIndex(i8* %s.data, i32 %s.len, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%.not = icmp ult i32 %index, %s.len
br i1 %.not, label %lookup.next, label %lookup.throw
lookup.throw: ; preds = %entry
call void @runtime.lookupPanic(i8* undef, i8* null)
unreachable
lookup.next: ; preds = %entry
%0 = getelementptr inbounds i8, i8* %s.data, i32 %index
%1 = load i8, i8* %0, align 1
ret i8 %1
}
declare void @runtime.lookupPanic(i8*, i8*)
define hidden i1 @main.stringCompareEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
ret i1 %0
}
declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*)
define hidden i1 @main.stringCompareUnequal(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
%1 = xor i1 %0, true
ret i1 %1
}
define hidden i1 @main.stringCompareLarger(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i1 @runtime.stringLess(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
%1 = xor i1 %0, true
ret i1 %1
}
declare i1 @runtime.stringLess(i8*, i32, i8*, i32, i8*, i8*)
+7 -5
View File
@@ -1,14 +1,16 @@
module github.com/tinygo-org/tinygo
go 1.11
go 1.16
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac
github.com/chromedp/chromedp v0.5.3
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-20200503225853-345b2947b59d
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c
)
+30 -17
View File
@@ -1,31 +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 h1:T7V5BXqnYd55Hj/g5uhDYumg9Fp3rMTS6bykYtTIFX4=
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/chromedp/chromedp v0.5.3 h1:F9LafxmYpsQhWQBdCs+6Sret1zzeeFyHS5LkRF//Ffg=
github.com/chromedp/chromedp v0.5.3/go.mod h1:YLdPtndaHQ4rCpSpBG+IPpy9JvX0VD+7aaLxYgYj28w=
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.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/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 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/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=
@@ -37,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=
@@ -46,5 +57,7 @@ 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-20200503225853-345b2947b59d h1:hcX7vpB067GWM/EH4sGGOti0PMgIx+0bbZwUXctOIvE=
tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c h1:vn9IPshzYmzZis10UEVrsIBRv9FpykADw6M3/tHHROg=
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
+24 -4
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.
@@ -170,8 +171,9 @@ func getGoroot() string {
switch runtime.GOOS {
case "linux":
candidates = []string{
"/usr/local/go", // manually installed
"/usr/lib/go", // from the distribution
"/usr/local/go", // manually installed
"/usr/lib/go", // from the distribution
"/snap/go/current/", // installed using snap
}
case "darwin":
candidates = []string{
@@ -195,3 +197,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.15.0"
const Version = "0.20.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/).
+426
View File
@@ -0,0 +1,426 @@
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 {
phiNodes []instruction
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.Switch:
// A switch is an array of (value, label) pairs, of which the
// first one indicates the to-switch value and the default
// label.
numOperands := llvmInst.OperandsCount()
for i := 0; i < numOperands; i += 2 {
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i)))
inst.operands = append(inst.operands, literalValue{uint32(blockIndices[llvmInst.Operand(i+1)])})
}
case llvm.PHI:
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).
}
if inst.opcode == llvm.PHI {
// PHI nodes need to be treated specially, see the comment in
// interpreter.go for an explanation.
bb.phiNodes = append(bb.phiNodes, inst)
} else {
bb.instructions = append(bb.instructions, inst)
}
}
}
return fn
}
// 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",
}
+20 -12
View File
@@ -11,15 +11,22 @@ 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"))
// This is one of the errors that can be returned from toLLVMValue when the
// passed type does not fit the data to serialize. It is recoverable by
// serializing without a type (using rawValue.rawLLVMValue).
var errInvalidPtrToIntSize = errors.New("interp: ptrtoint integer size does not equal pointer size")
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 +53,14 @@ 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,
Inst: inst.llvmInst,
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")
}
}
+239 -103
View File
@@ -1,59 +1,88 @@
// 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
// Version of the interp package. It must be incremented whenever the interp
// package is changed in a way that affects the output so that cached package
// builds will be invalidated.
// This version is independent of the TinyGo version number.
const Version = 2 // last change: fix GEP on untyped pointers
// 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
func newRunner(mod llvm.Module, debug bool) *runner {
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)
return &r
}
// 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 := newRunner(mod, debug)
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 +92,206 @@ 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()
call.EraseFromParentAsInstruction()
evalPkg := evalPackage{
Eval: e,
packagePath: pkgName,
if r.debug {
fmt.Fprintln(os.Stderr, "call:", fn.Name())
}
_, err := evalPkg.function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, "")
if err == errUnreachable {
break
_, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
call.EraseFromParentAsInstruction()
if callErr != nil {
if isRecoverableError(callErr.Err) {
if r.debug {
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
}
// Remove instructions that were created as part of interpreting
// the package.
mem.revert()
// Create a call to the package initializer (which was
// previously deleted).
i8undef := llvm.Undef(r.i8ptrType)
r.builder.CreateCall(fn, []llvm.Value{i8undef, i8undef}, "")
// Make sure that any globals touched by the package
// initializer, won't be accessed by later package initializers.
r.markExternalLoad(fn)
continue
}
return callErr
}
for index, obj := range mem.objects {
r.objects[index] = obj
}
}
r.pkgName = ""
// Update all global variables in the LLVM module.
mem := memoryView{r: r}
for i, obj := range r.objects {
if obj.llvmGlobal.IsNil() {
continue
}
if obj.buffer == nil {
continue
}
initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
if err == errInvalidPtrToIntSize {
// This can happen when a previous interp run did not have the
// correct LLVM type for a global and made something up. In that
// case, some fields could be written out as a series of (null)
// bytes even though they actually contain a pointer value.
// As a fallback, use asRawValue to get something of the correct
// memory layout.
initializer, err := obj.buffer.asRawValue(r).rawLLVMValue(&mem)
if err != nil {
return err
}
initializerType := initializer.Type()
newGlobal := llvm.AddGlobal(mod, initializerType, obj.llvmGlobal.Name()+".tmp")
newGlobal.SetInitializer(initializer)
newGlobal.SetLinkage(obj.llvmGlobal.Linkage())
newGlobal.SetAlignment(obj.llvmGlobal.Alignment())
// TODO: copy debug info, unnamed_addr, ...
bitcast := llvm.ConstBitCast(newGlobal, obj.llvmGlobal.Type())
obj.llvmGlobal.ReplaceAllUsesWith(bitcast)
name := obj.llvmGlobal.Name()
obj.llvmGlobal.EraseFromParentAsGlobal()
newGlobal.SetName(name)
// Update interp-internal references.
delete(r.globals, obj.llvmGlobal)
obj.llvmGlobal = newGlobal
r.globals[newGlobal] = i
r.objects[i] = obj
continue
}
if err != nil {
return err
}
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),
// RunFunc evaluates a single package initializer at compile time.
// Set debug to true if it should print output while running.
func RunFunc(fn llvm.Value, debug bool) error {
// Create and initialize *runner object.
mod := fn.GlobalParent()
r := newRunner(mod, debug)
initName := fn.Name()
if !strings.HasSuffix(initName, ".init") {
return errorAt(fn, "interp: unexpected function name (expected *.init)")
}
for i, param := range fn.Params() {
fr.locals[param] = params[i]
r.pkgName = initName[:len(initName)-len(".init")]
// Create new function with the interp result.
newFn := llvm.AddFunction(mod, fn.Name()+".tmp", fn.Type().ElementType())
newFn.SetLinkage(fn.Linkage())
newFn.SetVisibility(fn.Visibility())
entry := mod.Context().AddBasicBlock(newFn, "entry")
// Create a builder, to insert instructions that could not be evaluated at
// compile time.
r.builder = mod.Context().NewBuilder()
defer r.builder.Dispose()
r.builder.SetInsertPointAtEnd(entry)
// Copy debug information.
subprogram := fn.Subprogram()
if !subprogram.IsNil() {
newFn.SetSubprogram(subprogram)
r.builder.SetCurrentDebugLocation(subprogram.SubprogramLine(), 0, subprogram, llvm.Metadata{})
}
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()
// Run the initializer, filling the .init.tmp function.
if r.debug {
fmt.Fprintln(os.Stderr, "interp:", fn.Name())
}
_, pkgMem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
if callErr != nil {
if isRecoverableError(callErr.Err) {
// Could not finish, but could recover from it.
if r.debug {
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
}
newFn.EraseFromParentAsFunction()
return nil
}
return callErr
}
for index, obj := range pkgMem.objects {
r.objects[index] = obj
}
// Update globals with values determined while running the initializer above.
mem := memoryView{r: r}
for _, obj := range r.objects {
if obj.llvmGlobal.IsNil() {
continue
}
if obj.buffer == nil {
continue
}
initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
if err != nil {
return err
}
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
panic("initializer type mismatch")
}
obj.llvmGlobal.SetInitializer(initializer)
}
// Finalize: remove the old init function and replace it with the new
// (.init.tmp) function.
r.builder.CreateRetVoid()
fnName := fn.Name()
fn.ReplaceAllUsesWith(newFn)
fn.EraseFromParentAsFunction()
newFn.SetName(fnName)
return nil
}
// 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}
// 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
}
// 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
// markExternalLoad marks the given llvmValue as being loaded externally. This
// is primarily used to mark package initializers that could not be run at
// compile time. As an example, a package initialize might store to a global
// variable. Another package initializer might read from the same global
// variable. By marking this function as being run at runtime, that load
// instruction will need to be run at runtime instead of at compile time.
func (r *runner) markExternalLoad(llvmValue llvm.Value) {
mem := memoryView{r: r}
mem.markExternalLoad(llvmValue)
for index, obj := range mem.objects {
if obj.marked > r.objects[index].marked {
r.objects[index].marked = obj.marked
}
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.
}
}
+37 -3
View File
@@ -3,6 +3,7 @@ package interp
import (
"io/ioutil"
"os"
"regexp"
"strings"
"testing"
@@ -12,10 +13,11 @@ import (
func TestInterp(t *testing.T) {
for _, name := range []string{
"basic",
"phi",
"slice-copy",
"consteval",
"map",
"interface",
"revert",
} {
name := name // make tc local to this closure
t.Run(name, func(t *testing.T) {
@@ -41,9 +43,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()
@@ -66,6 +88,8 @@ func runTest(t *testing.T, pathPrefix 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.).
@@ -75,8 +99,18 @@ func fuzzyEqualIR(s1, s2 string) bool {
if len(lines1) != len(lines2) {
return false
}
for i, line := range lines1 {
if line != lines2[i] {
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
}
}
File diff suppressed because it is too large Load Diff
+1211
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)
}
}
}
+76
View File
@@ -4,6 +4,11 @@ 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
@main.insertedValue = global {i8, i32, {float, {i64, i16}}} zeroinitializer
declare void @runtime.printint64(i64) unnamed_addr
@@ -47,6 +52,36 @@ 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
; Test that inline assembly is ignored.
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
; Test switch statement.
%switch1 = call i64 @testSwitch(i64 1) ; 1 returns 6
%switch2 = call i64 @testSwitch(i64 9) ; 9 returns the default value -1
call void @runtime.printint64(i64 %switch1)
call void @runtime.printint64(i64 %switch2)
; Test extractvalue/insertvalue with multiple operands.
%agg = call {i8, i32, {float, {i64, i16}}} @nestedStruct()
%elt = extractvalue {i8, i32, {float, {i64, i16}}} %agg, 2, 1, 0
call void @runtime.printint64(i64 %elt)
%agg2 = insertvalue {i8, i32, {float, {i64, i16}}} %agg, i64 5, 2, 1, 0
store {i8, i32, {float, {i64, i16}}} %agg2, {i8, i32, {float, {i64, i16}}}* @main.insertedValue
ret void
}
@@ -58,3 +93,44 @@ 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
}
; Inline assembly should be ignored in the interp package. While it is possible
; to modify other globals that way, usually that's not the case and there is no
; real way to check.
define void @hasInlineAsm() {
entry:
call void asm sideeffect "", ""()
ret void
}
define i64 @testSwitch(i64 %val) {
entry:
; Test switch statement.
switch i64 %val, label %otherwise [ i64 0, label %zero
i64 1, label %one
i64 2, label %two ]
zero:
ret i64 5
one:
ret i64 6
two:
ret i64 7
otherwise:
ret i64 -1
}
declare {i8, i32, {float, {i64, i16}}} @nestedStruct()
+61
View File
@@ -3,6 +3,11 @@ 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
@main.insertedValue = local_unnamed_addr global { i8, i32, { float, { i64, i16 } } } zeroinitializer
declare void @runtime.printint64(i64) unnamed_addr
@@ -16,6 +21,25 @@ 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
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
call void @runtime.printint64(i64 6)
call void @runtime.printint64(i64 -1)
%agg = call { i8, i32, { float, { i64, i16 } } } @nestedStruct()
%elt.agg = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
%elt.agg1 = extractvalue { float, { i64, i16 } } %elt.agg, 1
%elt = extractvalue { i64, i16 } %elt.agg1, 0
call void @runtime.printint64(i64 %elt)
%agg2.agg0 = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
%agg2.agg1 = extractvalue { float, { i64, i16 } } %agg2.agg0, 1
%agg2.insertvalue2 = insertvalue { i64, i16 } %agg2.agg1, i64 5, 0
%agg2.insertvalue1 = insertvalue { float, { i64, i16 } } %agg2.agg0, { i64, i16 } %agg2.insertvalue2, 1
%agg2.insertvalue0 = insertvalue { i8, i32, { float, { i64, i16 } } } %agg, { float, { i64, i16 } } %agg2.insertvalue1, 2
store { i8, i32, { float, { i64, i16 } } } %agg2.insertvalue0, { i8, i32, { float, { i64, i16 } } }* @main.insertedValue
ret void
}
@@ -27,3 +51,40 @@ 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
}
define void @hasInlineAsm() {
entry:
call void asm sideeffect "", ""()
ret void
}
define i64 @testSwitch(i64 %val) local_unnamed_addr {
entry:
switch i64 %val, label %otherwise [
i64 0, label %zero
i64 1, label %one
i64 2, label %two
]
zero: ; preds = %entry
ret i64 5
one: ; preds = %entry
ret i64 6
two: ; preds = %entry
ret i64 7
otherwise: ; preds = %entry
ret i64 -1
}
declare { i8, i32, { float, { i64, i16 } } } @nestedStruct() local_unnamed_addr
+5 -6
View File
@@ -1,17 +1,16 @@
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 }
%runtime.typecodeID = type { %runtime.typecodeID*, i64, %runtime.interfaceMethodInfo* }
%runtime.interfaceMethodInfo = type { i8*, i64 }
%runtime.typeInInterface = type { %runtime.typecodeID*, %runtime.interfaceMethodInfo* }
@main.v1 = global i1 0
@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0 }
@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0, %runtime.interfaceMethodInfo* null }
@"reflect/types.typeid:named:main.foo" = external constant i8
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
@"typeInInterface:reflect/types.type:named:main.foo" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:named:main.foo", %runtime.interfaceMethodInfo* null }
declare i1 @runtime.typeAssert(i64, %runtime.typecodeID*, i8*, i8*)
declare i1 @runtime.typeAssert(i64, i8*, i8*, i8*)
define void @runtime.initAll() unnamed_addr {
entry:
@@ -22,7 +21,7 @@ entry:
define internal void @main.init() unnamed_addr {
entry:
; Test type asserts.
%typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:named:main.foo" to i64), %runtime.typecodeID* @"reflect/types.type:named:main.foo", i8* undef, i8* null)
%typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:main.foo" to i64), i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null)
store i1 %typecode, i1* @main.v1
ret void
}
-76
View File
@@ -1,76 +0,0 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv6m-none-eabi"
%runtime._string = type { i8*, i32 }
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
@main.m = global %runtime.hashmap* null
@main.binaryMap = global %runtime.hashmap* null
@main.stringMap = global %runtime.hashmap* null
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
declare %runtime.hashmap* @runtime.hashmapMake(i8, i8, i32, i8* %context, i8* %parentHandle)
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8* %context, i8* %parentHandle)
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8* %context, i8* %parentHandle)
declare void @llvm.lifetime.end.p0i8(i64, i8*)
declare void @llvm.lifetime.start.p0i8(i64, i8*)
define void @runtime.initAll() unnamed_addr {
entry:
call void @main.init(i8* undef, i8* null)
ret void
}
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
; Test that hashmap optimizations generally work (even with lifetimes).
%hashmap.key = alloca i8
%hashmap.value = alloca %runtime._string
%0 = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 8, i32 1, i8* undef, i8* null)
%hashmap.value.bitcast = bitcast %runtime._string* %hashmap.value to i8*
call void @llvm.lifetime.start.p0i8(i64 8, i8* %hashmap.value.bitcast)
store %runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string* %hashmap.value
call void @llvm.lifetime.start.p0i8(i64 1, i8* %hashmap.key)
store i8 1, i8* %hashmap.key
call void @runtime.hashmapBinarySet(%runtime.hashmap* %0, i8* %hashmap.key, i8* %hashmap.value.bitcast, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 1, i8* %hashmap.key)
call void @llvm.lifetime.end.p0i8(i64 8, i8* %hashmap.value.bitcast)
store %runtime.hashmap* %0, %runtime.hashmap** @main.m
; Other tests, that can be done in a separate function.
call void @main.testNonConstantBinarySet()
call void @main.testNonConstantStringSet()
ret void
}
; Test that a map loaded from a global can still be used for mapassign
; operations (with binary keys).
define internal void @main.testNonConstantBinarySet() {
%hashmap.key = alloca i8
%hashmap.value = alloca i8
; Create hashmap from global. This breaks the normal hashmapBinarySet
; optimization, to test the fallback.
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 1, i32 1, i8* undef, i8* null)
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.binaryMap
%map = load %runtime.hashmap*, %runtime.hashmap** @main.binaryMap
; Do the binary set to the newly loaded map.
store i8 1, i8* %hashmap.key
store i8 2, i8* %hashmap.value
call void @runtime.hashmapBinarySet(%runtime.hashmap* %map, i8* %hashmap.key, i8* %hashmap.value, i8* undef, i8* null)
ret void
}
; Test that a map loaded from a global can still be used for mapassign
; operations (with string keys).
define internal void @main.testNonConstantStringSet() {
%hashmap.value = alloca i8
; Create hashmap from global. This breaks the normal hashmapStringSet
; optimization, to test the fallback.
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 8, i8 1, i32 1, i8* undef, i8* null)
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.stringMap
%map = load %runtime.hashmap*, %runtime.hashmap** @main.stringMap
; Do the string set to the newly loaded map.
store i8 2, i8* %hashmap.value
call void @runtime.hashmapStringSet(%runtime.hashmap* %map, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* %hashmap.value, i8* undef, i8* null)
ret void
}
-28
View File
@@ -1,28 +0,0 @@
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.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
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
}
+31
View File
@@ -0,0 +1,31 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.phiNodesResultA = global i8 0
@main.phiNodesResultB = global i8 0
define void @runtime.initAll() {
call void @main.init()
ret void
}
; PHI nodes always use the value from the previous block, even in a loop. This
; means that the loop below should swap the values %a and %b on each iteration.
; Previously there was a bug which resulted in %b getting the value 3 on the
; second iteration while it should have gotten 1 (from the first iteration of
; %for.loop).
define internal void @main.init() {
entry:
br label %for.loop
for.loop:
%a = phi i8 [ 1, %entry ], [ %b, %for.loop ]
%b = phi i8 [ 3, %entry ], [ %a, %for.loop ]
%icmp = icmp eq i8 %a, 3
br i1 %icmp, label %for.done, label %for.loop
for.done:
store i8 %a, i8* @main.phiNodesResultA
store i8 %b, i8* @main.phiNodesResultB
ret void
}
+9
View File
@@ -0,0 +1,9 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.phiNodesResultA = local_unnamed_addr global i8 3
@main.phiNodesResultB = local_unnamed_addr global i8 1
define void @runtime.initAll() local_unnamed_addr {
ret void
}
+32
View File
@@ -0,0 +1,32 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
declare void @externalCall(i64)
@foo.knownAtRuntime = global i64 0
@bar.knownAtRuntime = global i64 0
define void @runtime.initAll() unnamed_addr {
entry:
call void @foo.init(i8* undef, i8* undef)
call void @bar.init(i8* undef, i8* undef)
call void @main.init(i8* undef, i8* undef)
ret void
}
define internal void @foo.init(i8* %context, i8* %parentHandle) unnamed_addr {
store i64 5, i64* @foo.knownAtRuntime
unreachable ; this triggers a revert of @foo.init.
}
define internal void @bar.init(i8* %context, i8* %parentHandle) unnamed_addr {
%val = load i64, i64* @foo.knownAtRuntime
store i64 %val, i64* @bar.knownAtRuntime
ret void
}
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @externalCall(i64 3)
ret void
}

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