Compare commits

...

467 Commits

Author SHA1 Message Date
deadprogram e8615d1007 Prepare for 0.15.0 release
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-09-17 11:48:55 +02:00
sago35 9245337ecf atsamd2x: fix BAUD value 2020-09-17 09:46:15 +02:00
sago35 8170f59440 atsamd5x: fix BAUD value 2020-09-17 09:46:15 +02:00
Lucas Teske 622de53854 nintendoswitch: simplified assembly code 2020-09-17 07:46:29 +02:00
Lucas Teske 6cd9e3c348 arm64: make dynamic loader structs and constants private 2020-09-17 07:46:29 +02:00
Lucas Teske 33e2411b6a nintendoswitch: fix import cycle on dynamic_arm64.go 2020-09-17 07:46:29 +02:00
Lucas Teske fb1fc267ab nintendoswitch: Add dynamic loader for runtime loading PIE sections 2020-09-17 07:46:29 +02:00
Olivier Fauchon 490e377bba bluepill: Enable stm32's USART2 for the board and map it to UART1 tinygo's device 2020-09-17 06:26:57 +02:00
Ayke van Laethem fcedf0beaa stacksize: deal with DW_CFA_advance_loc1
In some cases this operation is emitted. It appears to be emitted when a
switch is lowered to a jump table in the ARM backend.
2020-09-16 05:06:34 +02:00
Ayke van Laethem 4dadb31e18 microbit: reelboard: flash using OpenOCD when needed
When using a SoftDevice, the MSD flash method is not appropriate as it
will erase the entire flash area before writing the new firmware. This
also wipes the SoftDevice. Instead, use OpenOCD to only rewrite the
parts of flash that need to be rewritten and leave the SoftDevice alone.
2020-09-15 17:07:40 +02:00
Ayke van Laethem 5b81b835ba esp32: add SPI support 2020-09-14 12:24:46 +02:00
ardnew a9a6d0ee63 add basic UART handler 2020-09-14 08:48:01 +02:00
Ayke van Laethem 19d5e05e37 esp32: configure the I/O matrix for GPIO pins
Only some pins (notably including GPIO2 aka machine.LED) have GPIO for
the default function 1. Other pins (such as GPIO 15) had a different
function by default. Function 3 means GPIO for all the pins, so always
use that when configuring a pin to use as a GPIO pin.

In the future, the mux configuration will need to be updated for other
functions such as SPI, I2C, etc.
2020-09-13 11:24:33 +02:00
Ayke van Laethem 9e599bac49 nintendoswitch: support outputting .nro files directly
By modifying the linker script a bit and adding the NRO0 header directly
in the assembly, it's possible to craft an ELF file that can be
converted straight to a binary (using objcopy or similar) that is a NRO
file. This avoids custom code for NRO files or an extra build step.

With another change, .nro files are recognized by TinyGo so that this
will create a ready-to-run NRO file:

    tinygo build -o test.nro -target=nintendoswitch examples/serial
2020-09-12 18:37:58 +02:00
Nia Weiss 81e325205f update my name in the contributors list 2020-09-12 16:51:47 +02:00
deadprogram 71cbb1495e docs: add ESP32, ESP8266, and Adafruit Feather STM32F405 to list of supported boards
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-09-11 09:36:19 +02:00
ardnew efc824a103 change default flash method to DFU (using dfu-util over USB) 2020-09-11 09:09:02 +02:00
ardnew cb4f3f12e6 replace flash method with openocd, disable automatic stack sizing 2020-09-11 09:09:02 +02:00
ardnew d1b470f04e remove UART/SPI vars until peripherals implemented 2020-09-11 09:09:02 +02:00
ardnew 7aaa9e45a6 add pin/bus doc comments, I2C pins 2020-09-11 09:09:02 +02:00
ardnew 5c9930df3f move clock settings docs comment to respective const definitions 2020-09-11 09:09:02 +02:00
ardnew cf6b544cd9 remove (or stub) UART/SPI/I2C peripheral code from initial feather-stm32f405 board support 2020-09-11 09:09:02 +02:00
ardnew 097f628955 add feather-stm32f405 smoketest target 2020-09-11 09:09:02 +02:00
ardnew 20a1c730a1 add STM32F405 machine/runtime, and new board/target feather-stm32f405 2020-09-11 09:09:02 +02:00
jreamy 04f65f1189 Adding support for the Arduino Zero (#1365)
* machine/arduino-zero: adding support for Arduino Zero

Co-authored-by: Ayke
Co-authored-by: Jack Reamy
2020-09-10 10:32:12 +02:00
Ayke van Laethem 2ce17a1892 esp8266: add support for this chip
Many thanks to cnlohr for the nosdk8266 project:
    https://github.com/cnlohr/nosdk8266
2020-09-09 19:17:11 +02:00
Lucas Teske 0b9b293651 nintendoswitch: Fix invalid memory read / write in print calls 2020-09-09 18:51:00 +02:00
Ayke van Laethem f20c932bb9 nrf52840: use higher priority for USB-CDC code
This ensures that stdout (println etc) keeps working in interrupts.

Generally you shouldn't print anything in an interrupt. However,
printing things for debugging is very useful and printing panic messages
can be critical when the code doesn't work for some reason.
2020-09-09 17:15:50 +02:00
Ayke van Laethem e7227df80d main: implement tinygo targets to list usable targets
This patch adds the `tinygo targets` command, which lists usable
targets (targets that can be used in the `-target` flag).

The assumption here is that usable targets can either be flashed or
emulated by TinyGo. There is one exception where it doesn't work yet:
the nintendoswitch target. Right now it requires some manual steps to
build a .nro file which can then be run by yuzu, hence why it doesn't
show up in the list.
2020-09-09 13:05:14 +02:00
Ayke van Laethem b99175a436 avr: configure emulator in board files
Instead of specifying the emulator command in atmega328p.json, specify
it in the two boards based on it (arduino and arduino-nano). This makes
the configuration consistent with the machine package, which only
defines the CPUFrequency function in the board files (and not in
machine_atmega328p.json).
2020-09-09 13:05:14 +02:00
Ayke van Laethem de3ffe0af7 main: add cached GOROOT to info subcommand
This is necessary for an upcoming VS Code extension to support TinyGo,
and may be useful for other people wanting to use proper autocompletion
etc in their IDE.
2020-09-07 13:07:17 +02:00
sago35 1e47d9efac docker: fix the problem with the wasm build (#1357)
* docker: fix the problem with the wasm build
2020-09-06 10:22:47 +02:00
Ayke van Laethem 8a2e7bac04 esp32: add libgcc ROM functions to linker script
They are copied from ESP-IDF. They are always available (burned in the
mask ROM) so best to use them.
2020-09-05 10:41:35 +02:00
sago35 ae13db917f ci: set git-fetch-depth to 1 2020-09-05 10:41:04 +02:00
Ayke van Laethem 98dbbbfda6 esp32: export machine.PortMask* for bitbanging implementations
This is useful for some drivers, in particular for the WS2812 driver.
2020-09-05 09:23:31 +02:00
Ayke van Laethem 475135f546 ci: run tinygo test for known-working packages
These packages are known to pass tests with `tinygo test`. It's still a
very short list, but hopefully this list can be expanded to eventually
cover most or all of the standard library.
2020-09-04 14:10:48 +02:00
Ayke van Laethem 88fd2823df all: run test binaries in the correct directory
Test binaries must be run in the source directory of the package to be
tested. This wasn't done, leading to a few "file not found" errors.

This commit implements this. Unfortunately, it does not allow more
packages to be tested as both affected packages (debug/macho and
debug/plan9obj) will still fail with this patch even though the "file
not found" errors are gone.
2020-09-04 12:21:19 +02:00
Ayke van Laethem 57a5b833b2 Makefile: check whether submodules have been downloaded in some common cases
This should help new people making this very common mistake.
2020-09-04 12:07:00 +02:00
Ayke van Laethem c810628a20 loader: rewrite/refactor much of the code to use go list directly
There were a few problems with the go/packages package. While it is more
or less designed for our purpose, it didn't work quite well as it didn't
provide access to indirectly imported packages (most importantly the
runtime package). This led to a workaround that sometimes broke
`tinygo test`.

This PR contains a number of related changes:

  * It uses `go list` directly to retrieve the list of packages/files to
    compile, instead of relying on the go/packages package.
  * It replaces our custom TestMain replace code with the standard code
    for running tests (generated by `go list`).
  * It adds a dummy runtime/pprof package and modifies the testing
    package, to get tests to run again with the code generated by
    `go list`.
2020-09-03 22:10:14 +02:00
Ayke van Laethem 51238fba50 arduino-mega2560: fix flashing on Windows
Without the extra `:i` at the end, avrdude will misinterpret the colon
in Windows paths.
2020-09-03 06:24:18 +02:00
ardnew 7f829fe153 machine/stm32f4: refactor common code and add new build tag stm32f4 (#1332)
* machine/STM32F4: break out STM32F4 machine with new build tag
2020-09-01 11:31:41 +02:00
sago35 946184b8ba flash: call PortReset only on other than openocd 2020-08-31 20:01:22 +02:00
Ayke van Laethem 753162f4e0 esp32: add support for basic GPIO
GPIO is much more advanced on the ESP32, but this is a starting point.
It gets examples/blinky1 to work.
2020-08-31 16:43:31 +02:00
Johan Brandhorst 0e6d2af028 Fix arch release job
Instead of using su, which is blocking, set the
user explicitly for each command.
2020-08-31 14:15:05 +02:00
Ayke van Laethem 0df4a7a35f esp32: support flashing directly from tinygo
Right now this requires setting the -port parameter, but other than that
it totally works (if esptool.py is installed). It works by converting
the ELF file to the custom ESP32 image format and flashing that using
esptool.py.
2020-08-31 13:59:32 +02:00
Ayke van Laethem 9a17698d6a compileopts: add support for custom binary formats
Some chips (like the ESP family) have a particular image format that is
more complex than simply dumping everything in a raw image.
2020-08-31 13:59:32 +02:00
Ayke van Laethem 3ee47a9c1b esp: add support for the Espressif ESP32 chip
This is only very minimal support. More support (such as tinygo flash,
or peripheral access) should be added in later commits, to keep this one
focused.

Importantly, this commit changes the LLVM repo from llvm/llvm-project to
tinygo-org/llvm-project. This provides a little bit of versioning in
case something changes in the Espressif fork. If we want to upgrade to
LLVM 11 it's easy to switch back to llvm/llvm-project until Espressif
has updated their fork.
2020-08-31 09:02:23 +02:00
Ayke van Laethem da7db81087 cortexm: fix stack size calculation with interrupts
Interrupts store 32 bytes on the current stack, which may be a goroutine
stack. After that the interrupt switches to the main stack pointer so
nothing more is pushed to the current stack. However, these 32 bytes
were not included in the stack size calculation.

This commit adds those 32 bytes. The code is rather verbose, but that is
intentional to make sure it is readable. This is tricky code that's hard
to get right, so I'd rather keep it well documented.
2020-08-30 18:23:20 +02:00
Ayke van Laethem 098fb5f39c nrf52840: add build tags for SoftDevice support
The SoftDevice should already be installed on these chips. Adding the
right build tags makes them work with the bluetooth package.

I did not change the HasLowFrequencyCrystal property: all these boards
use the MDBT50Q which appears to include a low-frequency oscillator.
That is, I tested the ItsyBitsy nRF52840 with the property set to true
and advertisement worked just fine.
2020-08-30 16:16:31 +02:00
Ayke van Laethem 47a975a44f nrf: add SoftDevice support for the Circuit Playground Bluefruit
This also fixes a bug: the Bluefruit doesn't have a low frequency
crystal. Somehow non-SoftDevice code still worked. However, the
SoftDevice won't initialize when this flag is set incorrectly.
2020-08-30 16:16:31 +02:00
deadprogram 83252448b0 device/atsamd51x: add all remaining bitfield values for PCHCTRLm Mapping
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-30 09:27:17 +02:00
deadprogram 222977a642 runtime/atsamd51x: use PCHCTRL_GCLK_SERCOMX_SLOW for setting clocks on all SERCOM ports
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-30 09:27:17 +02:00
deadprogram 58565fa46d machine/atsamd51x,runtime/atsamd51x: fixes needed for full support for all PWM pins. Also adds some useful constants to clarify peripheral clock usage
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-30 09:27:17 +02:00
Ayke van Laethem ecaf9461ce loader: be more robust when creating the cached GOROOT
This commit fixes two issues:

  * Do not try to create the cached GOROOT multiple times in parallel.
    This may happen in tests and is a waste of resources (and thus
    speed).
  * Check for an "access denied" error when trying to rename a directory
    over an existing directory. On *nix systems, this results in the
    expected "file exists" error. Unfortunately, Windows gives an access
    denied. This commit fixes the Windows behavior.
2020-08-29 00:02:55 +02:00
sago35 ae01904ab0 flash: add openocd settings to nrf5 2020-08-28 14:54:53 +02:00
Ayke van Laethem 2194d447e9 loader: use ioutil.TempDir to create a temporary directory
... instead of generating one with math/rand. The problem was that
math/rand is deterministic across runs, resulting in a possible race
when trying to create the same directory between two processes.
Additionally, because I used `os.MkdirAll`, no error was reported when
the directory already existed. The solution to this is to use the stdlib
function designed for this: ioutil.TempDir.
2020-08-28 14:48:37 +02:00
Ayke van Laethem a21a039ac7 arm: automatically determine stack sizes
This is a big change that will determine the stack size for many
goroutines automatically. Functions that aren't recursive and don't call
function pointers can in many cases have an automatically determined
worst case stack size. This is useful, as the stack size is usually much
lower than the previous hardcoded default of 1024 bytes: somewhere
around 200-500 bytes is common.

A side effect of this change is that the default stack sizes (including
the stack size for other architectures such as AVR) can now be changed
in the config JSON file, making it tunable per application.
2020-08-27 19:23:22 +02:00
Ayke van Laethem a761f556ff compiler: improve display of goroutine wrappers
This commit stores the original name of functions in metadata so it can
be recovered when printing goroutine names. This removes the .L prefix.
2020-08-27 19:23:22 +02:00
Ron Evans 29d65cb637 machine/itsybitsy-nrf52840: add support for Adafruit Itsybitsy nrf52840 (#1243)
* machine/itsybitsy-nrf52840: add support for Adafruit Itsybitsy nrf52840 Express board
2020-08-25 19:16:42 +02:00
Ayke van Laethem 63005622ae wasm: update wasi-libc dependency
Several updates are necessary for LLVM 11 support, so simply update to
the latest commit.
2020-08-25 18:42:42 +02:00
Ayke van Laethem 510f145a3a interp: show error line in first line of the traceback 2020-08-25 17:34:32 +02:00
sago35 e5e324f93e main: embed git-hash in tinygo-dev executable 2020-08-25 16:23:16 +02:00
Ayke van Laethem 4fa1fc6e72 interp: don't panic in the Store method
Instead return an error, which indicates where it goes wrong. That's
less user unfriendly than panicking.
2020-08-25 16:16:35 +02:00
Ayke van Laethem ccb803e35d interp: replace some panics with error messages
A number of functions now return errors instead of panicking, which
should help greatly when investigating interp errors. It at least shows
the package responsible for it.
2020-08-25 16:16:35 +02:00
sago35 d1ac0138e6 main: use ToSlash() to specify program path 2020-08-25 14:25:49 +02:00
sago35 b132b5bc60 flash: add openocd settings to atsamd21 / atsamd51 2020-08-25 14:18:01 +02:00
deadprogram 743254d5bc main: use simpler file copy instead of file renaming to avoid issues on nrf52840 UF2 bootloaders
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-25 13:51:15 +02:00
Ayke van Laethem 9d625a1ccb nrf: call sd_app_evt_wait when the SoftDevice is enabled
This reduces current consumption from 500-1000µA to very low (<10µA)
current consumption. This change is important for battery powered
devices, especially devices that may be running for long periods of
time.
2020-08-24 22:46:21 +02:00
Ayke van Laethem b75f042c23 runtime: use waitForEvents when appropriate
This is better than using the wfe or wfi instruction directly as it can
be replaced with build tags.
2020-08-24 22:46:21 +02:00
Elliott Sales de Andrade 32c7f3baf9 Remove --no-threads from wasm-ld calls.
The bugs linked from the original addition of this flag are fixed:
https://bugs.llvm.org/show_bug.cgi?id=41508
or 'possibly fixed':
https://bugs.llvm.org/show_bug.cgi?id=37064#c8
since LLVM 8.0.1. TinyGo only support LLVM 9+, and as noted in the
original PR (#377), this can be removed when switching to 9.

Additionally, this flag was dropped from LLVM 11.
2020-08-24 12:04:47 +02:00
deadprogram 1dc85ded47 version: update TinyGo version to 0.15.0-dev
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-24 12:04:47 +02:00
deadprogram 26a0819119 main: release 0.14.1
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-19 08:37:16 +02:00
Ayke van Laethem b59a46eef0 loader: work around Windows symlink limitation
Currently there will be a problem if the TinyGo installation directory
is not the same filesystem as the cache directory (usually the C drive)
and Developer Mode is disabled. Therefore, let's add another fallback
for when both conditions are true, falling back to copying the file
instead of symlinking/hardlinking it.
2020-08-19 08:37:16 +02:00
deadprogram 8a410b993b make,builder: incorporate feedback from code review on Go 1.15 update
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-19 08:37:16 +02:00
deadprogram e412a63a1c make: use buildmode flag to set exe for windows to use standard linker
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-19 08:37:16 +02:00
deadprogram a81face618 builder: simplify Go version check message
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-19 08:37:16 +02:00
deadprogram 0ac216b093 build: use Golang 1.15 for MS Azure builds
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-19 08:37:16 +02:00
deadprogram 9575fe628f internal/bytealg: naive attempt to copy the main Go 1.15 implementatation
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-19 08:37:16 +02:00
deadprogram f4e8ea0d23 builder: allow Go 1.15 to pass config check
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-19 08:37:16 +02:00
deadprogram 2d03f65d67 build: add Go 1.15 to CircleCI build 2020-08-19 08:37:16 +02:00
Ayke van Laethem 154d4a781f main: release 0.14.0 2020-08-03 12:46:32 +02:00
deadprogram b5ab114514 dockerhub: use post checkout hook for git submodule init
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-08-03 08:30:31 +02:00
BCG 0a7e74045a feather-nrf52840: corrected USB identifier constants 2020-08-01 10:55:33 +02:00
Ayke van Laethem f05b378b89 compiler: add proper parameter names to runtime.initAll
This is required by the coroutines pass, otherwise it will panic. It
checks for the proper parameter names to make sure the function is not
exported. In this case, the runtime.initAll function wasn't exported but
simply didn't have the correct parameter names so the check triggered
even though it shouldn't.
2020-07-31 17:34:44 +02:00
Ayke van Laethem 888ca4ab0c interp: fix sync/atomic.Value load/store methods
These methods do some unsafe pointer casting but can be assumed to not
have significant side effects. Simply call these functions at runtime
instead of compile time.

This is a partial fix for importing image/png.
2020-07-31 17:34:44 +02:00
deadprogram 903bebd071 docs: add Nintendo Switch to list of supported boards/devices
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-07-31 09:12:44 +02:00
waj334 848c3e55a9 compiler: implement func value and builtin defers
Co-authored-by: Justin A. Wilson <maker.pro.game@gmail.com>
2020-07-31 01:48:57 +02:00
Lucas Teske 3650c2c739 nintendoswitch: Add experimental Nintendo Switch support without CRT
Bare minimal nintendo switch support using LLD
2020-07-31 00:58:09 +02:00
Ayke van Laethem d4e04e4e49 compiler: fix named string to []byte slice conversion
This was missing a `.Underlying()` call to avoid testing the named type
(but instead test for the underlying type).
2020-07-29 12:13:37 +02:00
Ayke van Laethem e41e5106cc main: add -target flag to tests
This makes it easy to test one particular architecture, for example:

    go test -v -target=hifive1-qemu

This speeds up testing and allows testing targets that are not included
in the test by default (such as RISC-V tests on Linux).
2020-07-24 16:59:37 +02:00
Ayke van Laethem ca03b8d442 ci: fix Windows QEMU version
It appears that version 2020.07.22 or 2020.07.23 introduced a breaking
change in RISC-V. We will have to fix this eventually, but for now it's
easiest to just pin the QEMU version. Once this new QEMU version
(version 5?) is more widely available, it becomes easier to debug and
fix the underlying cause.
2020-07-24 08:24:20 +02:00
deadprogram d1c4ed664e all: changeover to eliminate all direct use of master/slave terminology
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-07-23 22:45:23 +02:00
Ayke van Laethem fdaddf6917 ci: do not cache TinyGo cache dir
It doesn't seem to speed up the build and it causes issues with a stale
cache.
2020-07-23 21:49:20 +02:00
Ayke van Laethem 50a677e9b7 arm: use CFI directives for stack usage
Call Frame Information is stored in the .debug_frame section and is used
by debuggers for unwinding. For assembly, this information is not known.
Debuggers will normally use heuristics to figure out the parent function
in the absence of call frame information.

This usually works fine, but is not enough for determining stack sizes.
Instead, I hardcoded the stack size information in
stacksize/stacksize.go, which is somewhat fragile. This change uses CFI
assembly directives to store this information instead of hardcoding it.

This change also fixes the following error message that would appear in
GDB:

    Backtrace stopped: previous frame identical to this frame (corrupt stack?)

More information on CFI:
  * https://sourceware.org/binutils/docs/as/CFI-directives.html
  * https://www.imperialviolet.org/2017/01/18/cfi.html
2020-07-20 17:36:50 +02:00
Ethan Reesor 6ad6f14a04 Use a jump table instead of if-then-else 2020-07-18 08:39:26 -04:00
Jaden Weiss 19e0f4709e transform: track 0-index GEPs
It appears that LLVM is turning bitcasts into 0-index GEPs.
This caused stuff to not be tracked, resulting in use-after-free issues.
This solution is sub-optimal, but is the most reasonable solution I could come up with without redesigning the stack slots pass.
2020-07-16 20:50:23 +02:00
Jaden Weiss ae5b297d59 builder: remove optimization level 0
Currently, turning optimizations off causes compile failures.
We rely on the optimizer removing some dead symbols.
Avoid providing an option that does not work right now.
In the future once everything has been fixed we can re-enable this.
2020-07-16 16:41:52 +02:00
Ethan Reesor ca1a282495 Use runtime/volatile.T.ReplaceBits 2020-07-14 06:08:08 +02:00
deadprogram 01f5c51b77 machine/feather-nrf52840: add smoketest for Adafruit Feather nrf52840 board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-07-14 00:13:10 +02:00
BCG ad6adfd8ff Added board definition for Feather nRF52840 Express 2020-07-13 16:50:06 +02:00
Ethan Reesor 9815628930 Cleanup Teensy 3.6 linker script 2020-07-13 12:35:53 +02:00
Ayke van Laethem 05495c4282 all: fix -gc=none
This option was broken for a long time, in part because we didn't test
for it. This commit fixes that and adds a test to make sure it won't
break again unnoticed.
2020-07-13 12:20:09 +02:00
Ayke van Laethem d606315515 builder: try to determine stack size information at compile time
For now, this is just an extra flag that can be used to print stack
frame information, but this is intended to provide a way to determine
stack sizes for goroutines at compile time in many cases.

Stack sizes are often somewhere around 350 bytes so are in fact not all
that big usually. Once this can be determined at compile time in many
cases, it is possible to use this information when available and as a
result increase the fallback stack size if the size cannot be determined
at compile time. This should reduce stack overflows while at the same
time reducing RAM consumption in many cases.

Interesting output for testdata/channel.go:

    function                                 stack usage (in bytes)
    Reset_Handler                            332
    .Lcommand-line-arguments.fastreceiver    220
    .Lcommand-line-arguments.fastsender      192
    .Lcommand-line-arguments.iterator        192
    .Lcommand-line-arguments.main$1          184
    .Lcommand-line-arguments.main$2          200
    .Lcommand-line-arguments.main$3          200
    .Lcommand-line-arguments.main$4          328
    .Lcommand-line-arguments.receive         176
    .Lcommand-line-arguments.selectDeadlock  72
    .Lcommand-line-arguments.selectNoOp      72
    .Lcommand-line-arguments.send            184
    .Lcommand-line-arguments.sendComplex     192
    .Lcommand-line-arguments.sender          192
    .Lruntime.run$1                          548

This shows that the stack size (if these numbers are correct) can in
fact be determined automatically in many cases, especially for small
goroutines. One of the great things about Go is lightweight goroutines,
and reducing stack sizes is very important to make goroutines
lightweight on microcontrollers.
2020-07-11 14:47:43 +02:00
sago35 60fdf81209 docs: add MAix BiT and Teensy 3.6 to list of supported boards (#1230)
* docs: add MAix BiT and Teensy 3.6 to list of supported boards
2020-07-11 09:21:16 +02:00
Ayke van Laethem 39433a3553 compileopts: automatically add -g flag when including debug symbols
Debug information is often useful and there is no reason to include it
for Go code but not for C code. Also, disabling debug information should
disable it entirely, not just for Go code.
2020-07-10 16:56:13 +02:00
Ethan Reesor 04d097f4ea Implement custom abort and fault handler for debugging 2020-07-08 21:58:15 +02:00
Ethan Reesor 4750635a20 Viable NXP/Teensy support
- Fix UART & putChar
- Timer-based sleep
- Enable systick in abort
- Buffered, interrupt-based UART TX
- Use the new interrupt API and fix sleepTicks
- Make pins behave more like other boards
- Use the MCU's UART numbering
- Allow interrupts to wake the scheduler (#1214)
2020-07-08 21:58:15 +02:00
Ethan Reesor 59218cd784 Working on NXP/Teensy support 2020-07-08 21:58:15 +02:00
Ethan Reesor 079a789d49 Minimal NXP/Teensy support 2020-07-08 21:58:15 +02:00
deadprogram ca8e1b075a targets/maixbit: cleanup output from kflash command by removing ansi colors
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-07-08 20:16:03 +02:00
Branden Timm 33024d4aa2 Fix portL mappings for atmega2560 (#1222)
* machine/atmega2560: fix portL mapping for atmega2560
2020-07-08 19:02:10 +02:00
Branden Timm 49df129ccd docs: add submodule update step prior to 'make release' (#1213)
* docs: add submodule update step prior to 'make release'
2020-07-08 17:37:43 +02:00
Ayke van Laethem 3088dcadec building: add links to tinygo.org
The BUILDING.md file is not intended for developer builds but for
release builds. The website contains more specific and more complete
information on how to build TinyGo, so provide links to these pages.

The Windows link doesn't work yet, but should work with the next
release when we update the website.
2020-07-08 17:35:31 +02:00
Yannis Huber 7ed7e6cb11 risc-v: disable linker relaxations during gp init 2020-07-08 01:58:12 +02:00
Yannis Huber 0b94e486c1 maixbit: changes according to feedback 2020-07-08 00:21:59 +02:00
Yannis Huber 5ff76aacab runtime: reuse common code between 32 and 64-bit RISC-V 2020-07-08 00:21:59 +02:00
Yannis Huber 3ee7599a09 maixbit: use custom linker script
This linker script does not offer stack overflow protection
because the stack cannot be placed at the bottom of the RAM.
2020-07-08 00:21:59 +02:00
Yannis Huber 43a66b39cc riscv: refactor assembly files to support RV64 and F extension 2020-07-08 00:21:59 +02:00
Yannis Huber 66b21b4c86 maixbit (interruptions): fix fpioa function test 2020-07-08 00:21:59 +02:00
Yannis Huber a9568932be maixbit: workaround to avoid medium code model 2020-07-08 00:21:59 +02:00
Yannis Huber f2fbd1dd7e maixbit (gpio): fix pin configuration 2020-07-08 00:21:59 +02:00
Yannis Huber 21a9aa8102 maixbit (i2c): fix rx fifo buffer length 2020-07-08 00:21:59 +02:00
Yannis Huber e1757e0347 builder: add support for 64-bit RISC-V 2020-07-08 00:21:59 +02:00
Yannis Huber a05fc10699 maixbit: add smoke test 2020-07-08 00:21:59 +02:00
Yannis Huber a685217743 maixbit: add I2C support 2020-07-08 00:21:59 +02:00
Yannis Huber ad0c15080a maixbit: add SPI support 2020-07-08 00:21:59 +02:00
Yannis Huber 5446c6927e maixbit: add GPIOHS pin interrupt support 2020-07-08 00:21:59 +02:00
Yannis Huber 53c83fa445 maixbit: support both GPIO and GPIOHS controllers 2020-07-08 00:21:59 +02:00
Yannis Huber 804dc8b1f9 maixbit: init fpioa clock at reset 2020-07-08 00:21:59 +02:00
Yannis Huber e1ceca1931 maixbit: remove atomic operations 2020-07-08 00:21:59 +02:00
Yannis Huber 6620c4d2aa maixbit: add chip datasheet link and reformat code 2020-07-08 00:21:59 +02:00
Yannis Huber ccc604d2e0 riscv: fix offset in 64bit scheduler
Also keep common start.S file for 64 and 32 bit architectures.
2020-07-08 00:21:59 +02:00
Yannis Huber dfab1aa717 maixbit (uart): serial is working with echo example 2020-07-08 00:21:59 +02:00
Yannis Huber 75bcbbe6d8 riscv: align stack and data sections to 8 bytes
Alignment on 4 bytes can cause load/store address misalignment
exceptions when loading/storing 64bit values on the stack.
2020-07-08 00:21:59 +02:00
Yannis Huber d599959711 maixbit (uart): working on data tx
When the data to send is too long the program gives an exception.
2020-07-08 00:21:59 +02:00
Yannis Huber 7814964693 maixbit: add board definition and dummy runtime 2020-07-08 00:21:59 +02:00
Yannis Huber 2fe4a9be71 maix-bit: add code model in target definition
This is needed to avoid linking errors because the globals are placed
in memory at address 0x80000000 which is out of bounds for the default
code model.
2020-07-08 00:21:59 +02:00
Yannis Huber 163631df9e cmsis-svd: change submodule url to the TinyGo fork 2020-07-08 00:21:59 +02:00
Yannis Huber 9ad96fd809 Changes according to @aykevl's feedback 2020-07-08 00:21:59 +02:00
Yannis Huber 4a658b9082 Add llvm code model option in target definition 2020-07-08 00:21:59 +02:00
Yannis Huber 34e0961a79 Split RISC-V targets into 32/64-bit 2020-07-08 00:21:59 +02:00
Yannis Huber 875d36cba0 Add new kendryte k210 target definition 2020-07-08 00:21:59 +02:00
sago35 1a6bed3305 machine/samd51: add DAC support (#1198)
* machine/samd51: add DAC support
2020-07-06 14:02:51 +02:00
Branden Timm e0b9b1ecd1 machine: fix atmega2560 mapping for pins D2 and D5 2020-07-05 21:18:20 +02:00
Johan Brandhorst 149c9533e2 ci: add archlinux release job
Adds the arch-release job, which automatically updates
the tinygo-bin AUR package on every new git tag with
a semver version.
2020-07-05 09:51:44 +02:00
Ron Evans a85df334e6 machine/samd21: basic implementation for DAC (#1183)
* machine/samd21: basic DAC implementation

Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-07-04 18:16:25 +02:00
Jaden Weiss 89a9c09af5 runtime (atsamd51): allow interrupts to wake the scheduler 2020-07-04 15:00:44 +02:00
Ayke van Laethem 1451eeaf41 machine: change machine.Pin type to uint8
The `machine.Pin` type was a int8, which works fine but limits the
number of pin numbers to 127. This patch changes the type to uint8 and
changes NoPin to 0xff, which allows more pins to be used.

Some boards might not have that many pins but their internal
organization requires more pin numbers to be used (because it is
organized in pin ports and not all pins in a port have a physical
connection). Therefore the range of a int8 is too low to address these
higher pins.

This patch also has the surprising side effect of reducing binary size
in a number of cases. If there is a reduction it's usually just a few
bytes, with one outlier: the driver example amg88xx when compiled for
the pybadge board. I have not seen any increases in binary size.
2020-07-04 09:46:12 +02:00
Jaden Weiss e8c84d24a0 runtime (gc): do not scan the runqueue when the platform is not baremetal with a scheduler 2020-07-04 08:34:39 +02:00
Jaden Weiss a4f3457747 runtime: make channels work in interrupts 2020-07-04 08:34:39 +02:00
Ayke van Laethem aa3481e06a avr: fix target triple
It was `avr-atmel-none`, which is incorrect. It must be
`avr-unknown-unknown`.

Additionally, there is no reason to specify the target triple per chip,
it can be done for all AVR chips at once as it doesn't vary like
Cortex-M chips.
2020-06-30 20:48:42 +02:00
Hiroki Noda 2136cb2f59 Building self-built LLVM faster
Disable LIBEDIT/Z3/OCAMLDOC in LLVM build and use shallow-clone.
2020-06-30 19:09:23 +02:00
Ayke van Laethem acb3cfba6d avr: work around codegen bug in LLVM 10
Commit fc4857e98c (runtime: avoid recursion in printuint64 function)
caused a regression for AVR. I have tried locally with LLVM 11 (which
contains a number of codegen bugs) and the issue is no longer present,
so I'm assuming it's a codegen bug that is now fixed. However, LLVM 11
is not yet released so it seems best to me to work around this
temporarily (for the next few months).

This commit can easily be reverted when we start using LLVM 11.
2020-06-30 17:56:10 +02:00
deadprogram 8cfc4005d3 machine/stm32f4disco: add smoketests for newer version of board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-06-30 14:08:20 +02:00
deadprogram 2dbe29327a machine/stm32f4disco: add updated target file for newer version of board that have updated st-link
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-06-30 14:08:20 +02:00
sago35 de0fbb5e2f docs: add PyGamer to list of supported boards 2020-06-27 14:16:36 -04:00
Yannis Huber 9b1a19f184 gen-device-svd: fix lowercase in register spaced array 2020-06-26 13:16:34 +02:00
deadprogram 1ad6953858 build: clean tinygo cache before running tests
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-06-23 20:10:30 +02:00
sago35 3e8cdb62c9 main: use ToSlash() to specify pkgName
This change allows windows users to specify the package with backslash
as a path separator
2020-06-23 19:20:18 +02:00
Cornel 720a54a0fe extend stdlib to allow import of more packages (#1099)
* stdlib: extend stdlib to allow import of more packages
2020-06-23 11:56:28 +02:00
deadprogram de45da5df4 docker: try installing lld in initial stage to avoid cache problem with deb package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-06-22 19:50:56 +02:00
Hiroki Noda c9fc95f6f3 wasm32: Add --no-demangle option
LLD supports only C++ demangle, so disable it.
2020-06-22 11:42:17 +02:00
sago35 2721ab146f Seeed XIAO support (#1170)
* machine/xiao: add support for Seeedstudio XIAO board
2020-06-22 09:01:13 +02:00
Jaden Weiss 81c723db1a device/arm: do not mask fault handlers in critical sections 2020-06-22 00:08:14 +02:00
sago35 9a20af5b59 Add adc settings 2020-06-19 15:07:57 +02:00
APDevice 4e8e3f348f Added Adafruit PyGamer Target (#1173)
* machine/PyGamer: add board support
2020-06-19 14:35:42 +02:00
deadprogram 496452b676 machine/hifive1b: add definitions for UART0 pins
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-06-17 16:28:37 +02:00
Ayke van Laethem e86ca2080d runtime/interrupt: add cross-chip disable/restore interrupt support
This should cover all supported baremetal targets.
2020-06-14 14:44:22 -04:00
Ayke van Laethem e2bf7bbb49 device: add new cross-arch Asm and AsmFull functions
This is necessary to avoid a circular dependency between the device/avr
and runtime/interrupts package in the next commit.

It may be worth replacing existing calls like device/arm.Asm to
device.Asm, to have a single place where these are defined.
2020-06-14 14:44:22 -04:00
Ayke van Laethem 4d1d0c2d3b main: go mod tidy
Clean up the go.mod and go.sum which have gotten a bit messy, and add an
extra line to go.sum that keeps reappearing locally for some reason (so
it seems important).
2020-06-11 08:14:48 +02:00
deadprogram 2281b6a3f5 machine/hifive1b: remove extra println left in by mistake
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-06-09 13:30:22 +02:00
Jaden Weiss 4321923641 compiler/runtime: move the channel blocked list onto the stack
Previously, chansend and chanrecv allocated a heap object before blocking on a channel.
This object was used to implement a linked list of goroutines blocked on the channel.
The chansend and chanrecv now instead accept a buffer to store this object in as an argument.
The compiler now creates a stack allocation for this object and passes it in.
2020-06-08 19:59:57 +02:00
Ayke van Laethem 4e4b5955db nrf: support debugging the PCA10056
This change adds support for `tinygo gdb` on the PCA10056.

The board is normally flashed with the MSD programmer. Debugging needs a
real debugger interface however, which is relatively simple by just
adding the right OpenOCD configuration.
2020-06-08 19:54:41 +02:00
Ayke van Laethem 169d5f17b8 nrf: fix bug in SPI.Tx
There was what appears to be a race condition in the Tx function. While
it would work fine in many cases, when there were interrupts (such as
when using BLE), the function would just hang waiting for `EVENTS_READY`
to arrive.

I think what was happening was that the `spi.Bus.RXD.Get()` would start
the next transfer, which would complete (and generate an event) before
`EVENTS_READY` was reset to 0. The fix is easy: clear `EVENTS_READY`
before doing something that can trigger an event.

I believe I've seen this bug before on the PineTime but I couldn't find
the issue back then.
2020-06-08 19:54:41 +02:00
Ayke van Laethem 9ed5eae6a9 cgo: use scanner.Error in libclang
Previously it would return a `*scanner.Error`, which is not supported in
the error printer of the main package. This can easily be fixed by
making it a regular object (instead of a pointer).
2020-06-08 19:54:41 +02:00
Yannis Huber e2c55e3d26 gen-device-svd: fix lowercase cluster name 2020-06-08 16:59:13 +02:00
Yannis Huber d3f5b51cd8 compiler: add support for custom code model 2020-06-08 16:50:39 +02:00
Yannis Huber 2396c22658 risc-v: add support for 64-bit RISC-V CPUs 2020-06-08 16:47:39 +02:00
Yannis Huber d61d5d7ab1 Zero PLIC threshold value at startup
The PLIC threshold value must be zeroed at startup to permit
all interrupt priorities. Fixes #1128.
2020-06-07 11:45:26 +02:00
sago35 c5a896771d Seeed WioTerminal support (#1124)
* machine/wioterminal: add support for wioterminal board
2020-06-06 12:00:26 +02:00
sago35 0c880ec44c Standardize SAMD51 UART settings (#1155)
* machine/samd51: standardize samd51 uart settings
2020-06-05 08:14:31 +02:00
sago35 64d51b215f Extend SAMD51 pinPadMapping 2020-06-03 19:37:16 +02:00
sago35 f103e910d7 Add SAMD51 pin change interrupt settings 2020-06-03 19:37:16 +02:00
sago35 40afeea569 Add SAMD51 ADC settings 2020-06-03 19:37:16 +02:00
sago35 97122972fb Add SAMD51 pins 2020-06-03 19:37:16 +02:00
sago35 72064e12db WIP flash: fix touchSerialPortAt1200bps on windows 2020-06-03 15:26:03 +02:00
Ayke van Laethem 3c31a3110f builder: use newer version of gohex
This version adds error handling for the DumpIntelHex method, and thus
fixes a TODO comment.
2020-06-01 19:03:15 +02:00
Ayke van Laethem 0e73790d67 Dockerfile: avoid duplicate LLVM apt line
This line causes problems when installing software: apt-get complains
that there are duplicate lines.
2020-06-01 11:47:29 +02:00
deadprogram bcbc241d81 main: improve/simplify auto-retry to locate MSD for UF2 and HEX flashing
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-05-31 13:23:19 +02:00
deadprogram 5e2a8024a3 main: use auto-retry (up to 10 seconds) to locate MSD for UF2 and HEX flashing
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-05-31 13:23:19 +02:00
sago35 b92fad8da6 sam: fix register access for interrupts pins in samd51 (#1141)
* machine/samd51: fix register access for interrupts pins in samd51
2020-05-30 18:03:46 +02:00
sago35 5c8d4e54d6 sam: add support for pin change interrupts (samd5x) 2020-05-29 11:36:22 +02:00
Ayke van Laethem fed433c046 compiler: add support for atomic operations
This also implements DisableInterrupts/EnableInterrupts for RISC-V, as
those operations were needed to implement a few libcalls.
2020-05-28 15:11:46 +02:00
Ayke van Laethem 734613c20e transform: introduce check for method calls on nil interfaces
I ran into an issue where I did a method call on a nil interface and it
resulted in a HardFault. Luckily I quickly realized what was going on so
I could fix it, but I think undefined behavior is definitely the wrong
behavior in this case. This commit therefore changes such calls to cause
a nil panic instead of introducing undefined behavior.

This does have a code size impact. It's relatively minor, much lower
than I expected. When comparing the before and after of the drivers
smoke tests (probably the most representative sample available), I found
that most did not change at all and those that did change, normally not
more than 100 bytes (16 or 32 byte changes are typical).

Right now the pattern is the following:

    switch typecode {
    case 1:
        call method 1
    case 2:
        call method 2
    default:
        nil panic
    }

I also tried the following (in the hope that it would be easier to
optimize), but it didn't really result in a code size reduction:

    switch typecode {
    case 1:
        call method 1
    case 2:
        call method 2
    case 0:
        nil panic
    default:
        unreachable
    }

Some code got smaller, while other code (the majority) got bigger. Maybe
this can be improved once range[1] is finally allowed[2] on function
parameters, but it'll probably take a while before that is implemented.

[1]: https://llvm.org/docs/LangRef.html#range-metadata
[2]: https://github.com/rust-lang/rust/issues/50156
2020-05-28 13:42:36 +02:00
Ayke van Laethem 1570adac1c transform: do not special-case zero or one implementations of a method call
This is a common case, but it also complicates the code. Removing this
special case does have a negative effect on code size in rare cases, but
I don't think it's worth keeping around (and possibly causing bugs) for
such uncommon cases.

This should not result in functional changes, although the output (as
stated above) sometimes changes a little bit.
2020-05-28 13:42:36 +02:00
Ayke van Laethem c72f9eb08c sam: add support for pin change interrupts 2020-05-28 11:20:33 +02:00
Ayke van Laethem 19c7965fc5 nrf: add support for pin change interrupts 2020-05-28 11:20:33 +02:00
Ayke van Laethem c248418dbe compiler: fix a few crashes due to named types
There were a few cases left where a named type would cause a crash in
the compiler. While going through enough code would have found them
eventually, I specifically looked for the `Type().(` pattern: a Type()
call that is then used in a type assert. Most of those were indeed bugs,
although for some I couldn't come up with a reproducer so I left them
as-is.
2020-05-27 16:14:41 +02:00
Ayke van Laethem 4ca2d3f0cf loader: load packages using Go modules
This commit replaces the existing ad-hoc package loader with a package
loader that uses the x/tools/go/packages package to find all
to-be-loaded packages.
2020-05-27 13:08:17 +02:00
Ayke van Laethem 35015a7918 loader: merge roots from both Go and TinyGo in a cached directory
This commit changes the way that packages are looked up. Instead of
working around the loader package by modifying the GOROOT variable for
specific packages, create a new GOROOT using symlinks. This GOROOT is
cached for the specified configuration (Go version, underlying GOROOT
path, TinyGo path, whether to override the syscall package).

This will also enable go module support in the future.

Windows is a bit harder to support, because it only allows the creation
of symlinks when developer mode is enabled. This is worked around by
using symlinks and if that fails, using directory junctions or hardlinks
instead. This should work in the vast majority of cases. The only case
it doesn't work, is if developer mode is disabled and TinyGo, the Go
toolchain, and the cache directory are not all on the same filesystem.
If this is a problem, it is still possible to improve the code by using
file copies instead.

As a side effect, this also makes diagnostics use a relative file path
only when the file is not in GOROOT or in TINYGOROOT.
2020-05-27 13:08:17 +02:00
Ayke van Laethem bde73fc214 main: fix test subcommand
It was broken for quite some time without anybody noticing...
2020-05-27 13:08:17 +02:00
Ayke van Laethem ab2a81cc52 main: move TinyGo version to goenv
This is needed to make it available to more packages, for caching
purposes.

For caching, the version itself may not be enough during development.
But for regular releases, the version provides some protection against
accidentally using a cache entry that is invalid in a newer version.
2020-05-27 13:08:17 +02:00
Ayke van Laethem 2a98433c8e builder: move Go version code to goenv package
This is necessary to avoid a circular dependency in the loader (which
soon will need to read the Go version) and because it seems like a
better place anyway.
2020-05-27 13:08:17 +02:00
Brad Peabody 4918395f88 added test for wasm log output
callback case for log test
2020-05-27 08:43:29 +02:00
Ayke van Laethem 67ac4fdd8e nrf: add microbit-s110v8 target
This makes it possible to use Bluetooth on the BBC micro:bit.

Note that you need to use -programmer=cmsis-dap otherwise the SoftDevice
will be erased while flashing something that uses Bluetooth.
2020-05-26 18:50:33 +02:00
Ayke van Laethem e69131c0d3 nrf: expose the RAM base address
The RAM base address is needed during SoftDevice initialization. So far,
the same magic value has been used in aykevl/go-bluetooth and in TinyGo,
but this should be configured in only one place.

This will have additional benefits in the future:

  * It is currently set to 0x39c0, which is around 14.5kB. Most nrf51822
    chips have only 16kB of RAM, so this is way too much for those
    chips.
  * LLD in LLVM 11 allows expressions in the MEMORY part of linker
    scripts, which will allow overriding the SoftDevice RAM area with a
    linker flag, which might come in handy.
2020-05-26 18:49:44 +02:00
Ayke van Laethem 9f4459cee1 arm: make FPU configuraton consistent
Eventually we might want to start using the FPU, but the easy option
right now is to simply disable it everywhere. Previously, it depended on
whether Clang was built as part of TinyGo or it was an external binary.
By setting the floating point mode explicitly, such inconsistencies are
avoided.

This commit creates a new cortex-m4 target which can be the central
place for setting FPU-related settings across all Cortex-M4 chips.
2020-05-26 16:39:14 +02:00
Ayke van Laethem 3c55689566 runtime: refactor time handling
This commit refactors both determining the current time and sleeping for
a given time. It also improves precision for many chips.

  * The nrf chips had a long-standing TODO comment about a slightly
    inaccurate clock. This should now be fixed.
  * The SAM D2x/D5x chips may have a slightly more accurate clock,
    although probably within the error margin of the RTC. Also, by
    working with RTC ticks and converting in the least number of places,
    code size is often slightly reduced (usually just a few bytes, up to
    around 1kB in some cases).
  * I believe the HiFive1 rev B timer was slightly wrong (32768Hz vs
    30517.6Hz). Because the datasheet says the clock runs at 32768Hz,
    I've used the same conversion code here as in the nrf and sam cases.
  * I couldn't test both stm32 timers, so I kept them as they currently
    are. It may be possible to make them more efficient by using the
    native tick frequency instead of using microseconds everywhere.
2020-05-25 22:08:28 +02:00
Brad Peabody 95f509b109 wasm test suite (#1116)
* wasm: add test suite using headlless chrome
2020-05-23 14:12:01 +02:00
Ayke van Laethem dda576e80b avr: add support for PinInputPullup 2020-05-22 13:17:04 +02:00
Ayke van Laethem da505a6b17 avr: unify GPIO pin/port code
All the AVRs that I've looked at had the same pin/port structure, with
the possible states being input/floating, input/pullup, low, and high
(with the same PORT/DDR registers). The main difference is the number of
available ports and pins. To reduce the amount of code and avoid
duplication (and thus errors) I decided to centralize this, following
the design used by the atmega2560 but while using a trick to save
tracking a few registers.

In the process, I noticed that the Pin.Get() function was incorrect on
the atmega2560 implementation. It is now fixed in the unified code.
2020-05-22 13:17:04 +02:00
deadprogram 424d775bf4 build: remove CircleCI orb, now using different integration
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-05-22 00:07:03 +02:00
Lucas Teske 726d735ad3 cgo: Add LDFlags support 2020-05-21 00:57:19 +02:00
deadprogram b9fd6cee6f build: add webhook notifier orb for circleci
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-05-20 00:15:23 +02:00
cebernardi 76fb3bd177 compileopts: improve error reporting of unsupported flags 2020-05-16 23:29:47 +02:00
Brad Peabody 29ca1147f5 call scheduler from resume 2020-05-16 23:16:53 +02:00
Jaden Weiss 473644d918 internal/bytealg: reimplement bytealg in pure Go
Previously, we implemented individual bytealg functions via linknaming, and had to update them every once in a while when we hit linker errors.
Instead, this change reimplements the bytealg package in pure Go.
If something is missing, it will cause a compiler error rather than a linker error.
This is easier to test and maintain.
2020-05-16 14:56:05 +02:00
Ayke van Laethem 38fc340802 sam: return an error when an incorrect PWM pin is used
Previously it would trigger a nil pointer panic.
2020-05-13 19:19:45 +02:00
Ayke van Laethem b5f028e1f7 ci: build .deb files along with .tar.gz files for Debian 2020-05-13 08:39:12 +02:00
Ayke van Laethem aa40ddc48b ci: do not install the SiFive toolchain
We don't need it anymore since we use lld to link RISC-V binaries.
2020-05-13 08:29:40 +02:00
Ayke van Laethem 6bcb40fe01 os: implement virtual filesystem support
This allows applications to mount filesystems in the os package. This is
useful for mounting external flash filesystems, for example.
2020-05-13 08:08:57 +02:00
cornelk e907db1481 os: add Args and stub it with mock data 2020-05-12 16:53:07 +02:00
Ayke van Laethem 6b8940421e avr: use standard pin numbering
This commit changes pin numbering for atmega328 based boards (Uno, Nano)
to use the standard format, where pin number is determined by the
pin/port. Previously, pin numbers were based on what the Uno uses, which
does not seem to have a clear pattern.

One difference is that counting starts at port B, as there is no port A.
So PB0 is 0, PB1 is 1… PC0 is 8.

This commit also moves PWM code to the atmega328 file, as it may not be
generic to all ATmega chips.
2020-05-12 08:16:34 +02:00
cornelk 2c71f08922 reflect: add Cap and Len support for map and chan 2020-05-12 01:17:27 +02:00
cornelk 7e64bc8f77 runtime: add cap and len support for chans 2020-05-12 01:17:27 +02:00
cornelk 1461563e3f testdata: fix formatting 2020-05-12 01:17:27 +02:00
cornelk acdaa72365 runtime: fix compilation errors when using gc.extalloc 2020-05-12 01:11:46 +02:00
deadprogram 01f5c1d455 machine/arduino-nano33: remove (d)ebug flag to reduce console noise when flashing
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-05-09 13:49:40 +02:00
deadprogram 00f3a65903 machine/arduino-nano33: use (U)SB flag to ensure that device can be found when not on default port
Signed-off-by: deadprogram <ron@hybridgroup.com>
2020-05-09 13:49:40 +02:00
Jaden Weiss b4815192a6 testdata, sync: add sync.Mutex test to testdata/coroutines.go 2020-05-09 01:08:56 +02:00
Jaden Weiss c54e1cc955 sync: modify sync.Cond 2020-05-09 01:08:56 +02:00
Jaden Weiss 7801921cc0 testdata: replace fake waitgroup in channel.go with sync.WaitGroup 2020-05-09 01:08:56 +02:00
Jaden Weiss afc6bd5cdd sync: add WaitGroup 2020-05-09 01:08:56 +02:00
Jaden Weiss ae2cbbf851 internal/task: fix nil panic in (*internal/task.Stack).Pop
While adding some code to clear the Next field when popping from a task stack for safety reasons, the clear was placed outside of a nil pointer check.
As a result, (*internal/task.Stack).Pop panicked when the Stack is empty.
2020-05-09 01:08:56 +02:00
Jaden Weiss ccd79ee289 add sync.Cond 2020-05-09 01:08:56 +02:00
Jaden Weiss 171d0fe123 implement mutex blocking 2020-05-09 01:08:56 +02:00
sago35 5ed0f67e1c sam: fix ROM / RAM size on atsamd51j20 2020-05-07 23:04:10 +02:00
sago35 8ce3cfad40 flash: fix getDefaultPort() fails on Windows locales such as Japan 2020-05-07 22:47:45 +02:00
Ayke van Laethem 904fa852f6 transform: fix debug information in func lowering pass
This commit fixes errors like the following:

    inlinable function call in a function with debug info must have a !dbg location
      call void @runtime.nilPanic(i8* undef, i8* null)
    inlinable function call in a function with debug info must have a !dbg location
      %24 = call fastcc %runtime._interface @"(*github.com/vugu/vugu/domrender.JSRenderer).render$1"(%"github.com/vugu/vugu.VGNode"** %19, i32 %22, i32 %23, i8* %15, i8* undef)
    error: optimizations caused a verification failure

Not all instructions had a debug location, which apparently caused
issues for the inliner.
2020-05-07 08:24:14 +02:00
Ayke van Laethem d0b2585e82 main: update go-llvm to fix macOS build problem 2020-05-05 08:11:20 +02:00
Ayke van Laethem 23e88bfb15 arm: allow nesting in DisableInterrupts and EnableInterrupts
This finally fixes a TODO left in the code.
2020-04-29 18:25:16 +02:00
Ayke van Laethem 6389e45d99 all: replace ReadRegister with AsmFull inline assembly
This makes AsmFull more powerful (by supporting return values) and
avoids a compiler builtin.
2020-04-29 18:25:16 +02:00
Ayke van Laethem 9342e73ae1 builder: fix picolibc include path
Previously we used --sysroot to set the sysroot explicitly.
Unfortunately, this flag is not used directly by Clang to set the
include path (<sysroot>/include) but is instead interpreted by the
toolchain code. This means that even when the toolchain is explicitly
set (using the --sysroot parameter), it may still decide to use a
different include path such as <sysroot>/usr/include (such as on
baremetal aarch64).

This commit uses the Clang-internal -internal-isystem flag which sets
the include directory directly (as a system include path). This should
be more robust.

The reason the --sysroot parameter has so far worked is that all
existing targets happened to add <sysroot>/include as an include path.

The relevant Clang code is here:
https://github.com/llvm/llvm-project/blob/release/9.x/clang/lib/Driver/Driver.cpp#L4693-L4739
So far, RISC-V is handled by RISCVToolchain, Cortex-M targets by
BareMetal (which seems to be specific to ARM unlike what the name says)
and aarch64 fell back to Generic_ELF.
2020-04-29 15:41:08 +02:00
Ayke van Laethem fc4857e98c runtime: avoid recursion in printuint64 function
This function is called from runtime.printitf, which is called from
runtime._panic, and is therefore the leaf function of many call paths.
This makes analyzing stack usage very difficult.

Also forwarding printuint32 to printuint64 as it reduces code size in
the few examples I've tested. Printing numbers is not often done so it
doesn't matter if it's a bit slow (the serial connection is probably
slower anyway).
2020-04-26 17:19:07 +02:00
Yannis Huber f66492a338 Fix return address in scheduler 2020-04-26 16:58:02 +02:00
Jaden Weiss 445fd37bef main: update version for beginning of v0.14 development cycle 2020-04-26 16:57:24 +02:00
Ayke van Laethem a9ba6ebad9 main: version 0.13.1
This release fixes a few bugs introduced in the previous 0.13.0 release.
2020-04-21 17:02:43 +02:00
Ayke van Laethem 565ff99c31 gba: always use ARM mode instead of Thumb mode
This results in bigger code size, but it works around a bug in the
linker.

The issue starts with the problem that libraries (picolibc, compiler-rt)
were compiled as ARM and the rest as Thumb. This causes some blx
instructions to be inserted by the linker to call into these libraries.

Ideally we should fix the libraries to use Thumb mode instead, but that
requires some more extensive changes (including fixes to compiler-rt)
and it's just way easier to use ARM mode everywhere.
2020-04-21 15:40:52 +02:00
Jaden Weiss ceeba528e7 runtime: copy stack scan assembly for GBA
The GC stack scanning code was implemented in the Cortex-M assembly, which meant that it was not available on the GBA which is pre-cortex.
This change adds a copy of the relevant code into a new asembly file which is used on the GBA.
2020-04-21 10:28:42 +02:00
Ayke van Laethem 16c2d84c49 compiler: add parameter names to IR
This makes viewing the IR easier because parameters have readable names.

This also makes it easier to write compiler tests (still a work in
progress), that work in LLVM 9 and LLVM 10, as LLVM 10 started printing
value names for unnamed parameters.
2020-04-21 08:54:39 +02:00
Ayke van Laethem f00bb63330 runtime: do not put scheduler and GC code in the same section
This allows dead code elimination and avoids linker errors with
-scheduler=leaking.
2020-04-20 21:32:29 +02:00
Ayke van Laethem efdb2e852e main: version 0.13.0 2020-04-13 17:48:30 +02:00
Ron Evans bb540df2d1 flash: retry 3 times when attempting to reset the serial port
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-04-13 16:18:48 +02:00
suzuki-koya e181199305 compiler/llvm.go: fix typo 2020-04-13 16:07:35 +02:00
Ayke van Laethem 7b2377586f compiler: unexport some exported symbols
Some symbols (constants/types/methods) were exported while they are an
implementation detail. To keep the public API clean, unexport them.
2020-04-13 09:24:57 +02:00
Elliott Sales de Andrade 471cb4cfd7 wasm_exec: Implement syscall/js.valueDelete.
This is only useful on Go 1.14, and is a port of:
https://github.com/golang/go/commit/60f271358f07647be0de9ee8225b50a391ea5def
2020-04-13 08:47:34 +02:00
Elliott Sales de Andrade 5706b062e9 wasm_exec: Add copyBytesToGo.
This is basically just a copy of copyBytesToJS, but with arguments
reversed.
2020-04-13 08:47:34 +02:00
Elliott Sales de Andrade 4347496623 wasm_exec: Cross-port exit with code 1 change.
This cross-ports the following commit:
https://github.com/golang/go/commit/9eef9648005c17681800fdb55ed2404ab769761e
2020-04-13 08:47:34 +02:00
Jaden Weiss 9c78f7039d runtime (chan): fix blocking select on a nil channel
Previously, a blocking select on a nil channel would result in a nil panic inside the channel runtime code.
This change fixes the nil checks so that the select works as intended.
2020-04-13 08:28:24 +02:00
Jaden Weiss 9890c760cf transform (func-lowering): remove specializations from function value lowering and fix lowering of a function value of an unimplemented type
Previously, the function value lowering pass had special cases for when there were 0 or 1 function implementations.
However, the results of the pass were incorrect in both of these cases.
This change removes the specializations and fixes the transformation.

In the case that there was a single function implementation, the compiler emitted a select instruction to obtain the function pointer.
This selected between null and the implementing function pointer.
While this was technically correct, it failed to eliminate indirect function calls.
This prevented discovery of these calls by the coroutine lowering pass, and caused async function calls to be passed through unlowered.
As a result, the generated code had undefined behavior (usually resulting in a segfault).

In the case of no function implementations, the lowering code was correct.
However, the lowering code was not run.
The discovery of function signatures was accomplished by scanning implementations, and when there were no implementations nothing was discovered or lowered.

For maintainability reasons, I have removed both specializations rather than fixing them.
This substantially simplifies the code, and reduces the amount of variation that we need to worry about for testing purposes.
The IR now generated in the cases of 0 or 1 function implementations can be efficiently simplified by LLVM's optimization passes.
Therefore, there should not be a substantial regression in terms of performance or machine code size.
2020-04-12 22:43:43 +02:00
Elliott Sales de Andrade 0a8bfc57ef all: support Go 1.14 2020-04-12 18:41:34 +02:00
Ayke van Laethem 5674c35e14 wasm: backport "garbage collect references to JavaScript values"
See commit:
https://github.com/golang/go/commit/54e6ba6724dfde355070238f9abc16362cac2e3d

Warning: this will drop support for Go 1.13 for WebAssembly targets!
I have modified the integration tests to specifically blacklist Go 1.13
instead of whitelisting any other version, to avoid accidentally not
testing WebAssembly.
2020-04-12 18:41:34 +02:00
Ayke van Laethem 0f9038ad2e wasm: remove _callbackShutdown
It doesn't seem to be used.

This commit is somewhat related to this commit:
https://github.com/golang/go/commit/6dd70fc5e391eb7a47be5eb6353107f38b73f161
Most of the things don't port over nicely.
2020-04-12 18:41:34 +02:00
Elliott Sales de Andrade 343bb42644 cgo: normalize test results
This makes the result consistent across Go versions, by running a regex
on the CGo output that wraps all single-line functions in a consistent
way.

Originally written by Elliott Sales de Andrade and modified by Ayke van
Laethem.
2020-04-12 18:41:34 +02:00
Jaden Weiss bb5f7534e5 transform (coroutines): remove map iteration from coroutine lowering pass
The coroutine lowering pass had issues where it iterated over maps, sometimes resulting in non-deterministic output.
This change removes many of the maps and ensures that the transformations are deterministic.
2020-04-12 16:54:40 +02:00
Ron Evans 3862d6e8a2 docs: update README to have all currently supported boards
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-04-12 09:00:06 +02:00
Ayke van Laethem 5ef8c89937 reelboard: add SoftDevice target reelboard-s140v7
Unfortunately, `tinygo flash` doesn't work here. You have to merge the
SoftDevice and the application hex with a mergehex tool and flash that
to the board.
2020-04-11 10:56:58 +02:00
Ayke van Laethem 2078b042a4 nrf: add lib/nrfx/mdk to include dirs
This directory is needed for nrf.h and other headers that are used by
the SoftDevice. It is definitely needed for nrf52840 for example, but
I've also added it to the nrf51 as it will likely also require adding
this directory.
2020-04-11 10:56:58 +02:00
Ayke van Laethem 9e453a5a29 builder: work around a bug in ld.lld in LLVM 10
See comment in the commit for details. It works around a bug that's been
reported here: https://bugs.llvm.org/show_bug.cgi?id=45336

This is a separate commit so it can easily be reverted if/when this
patch is backported to the LLVM 10 stable branch.
2020-04-09 20:23:51 +02:00
Ayke van Laethem a08d3aa1dd ci: run tests in LLVM9 + LLVM10 2020-04-09 20:23:51 +02:00
Ayke van Laethem 0afd42c439 main: switch to LLVM 10
This commit also adds a bit of version independence, in particular for
external commands. It also adds the LLVM version to the `tinygo version`
command, which might help while debugging.
2020-04-09 20:23:51 +02:00
Ayke van Laethem 584e94ce2f transform: allow updating tests with -update flag
This should make it much easier to update existing tests.
2020-04-09 20:23:51 +02:00
Jaden Weiss 5308e8903e compiler: pass interface typecode through defer frames
Previously, the typecode was passed via a direct reference, which results in invalid IR when the defer is not reached in all return paths.
It also results in incorrect behavior if the defer is in a loop, causing all defers to use the typecode of the last iteration.
2020-04-09 15:02:09 +02:00
Jaden Weiss e077e35386 runtime (gc): split marking into two phases 2020-04-09 14:13:10 +02:00
Jaden Weiss 4a6fba7e3a runtime (gc): remove recursion from "conservative" GC 2020-04-09 14:13:10 +02:00
Ayke van Laethem 012c4a02c9 machine: switch to modern interrupt registration method
This saves about 112 bytes in flash and 288 bytes in RAM when the UART
is not used.
2020-04-07 17:53:52 +02:00
Ayke van Laethem dd0fb1dd9a arm: use -fomit-frame-pointer
The frame pointer was already omitted in the object files that TinyGo
emits, but wasn't yet omitted in the C files it compiles. Omitting the
frame pointer is good for code size (and perhaps performance).

The frame pointer was originally used for printing stack traces in a
debugger. However, advances in DWARF debug info have made it largely
unnecessary (debug info contains enough information now to recover the
frame pointer even without an explicit frame pointer register). In fact,
GDB has been able to produce backtraces in TinyGo compiled code for a
while now while it didn't include a frame pointer.
2020-04-07 16:17:10 +02:00
Ayke van Laethem 639ec1e6ee builder: make sure -fshort-enums is used consistently
The main change is in building the libraries, where -fshort-enums was
passed on RISC-V while other C files weren't compiled with this setting.

Note: the test already passed before this change, but it seems like a
good idea to explicitly test for enum size consistency.
There is also not a particular reason not to pass -fshort-enums on
RISC-V. Perhaps it's better to do it there too (on baremetal targets
that don't have to worry about binary compatibility).
2020-04-07 16:17:10 +02:00
Ayke van Laethem 8333c171f4 hifive1-qemu: fix compile error and add smoke test
This probably got broken with this PR:
https://github.com/tinygo-org/tinygo/pull/976
2020-04-07 16:17:10 +02:00
Ayke van Laethem b8f5627c9f machine: move errors.New calls to globals
Calling errors.New in an error path causes a heap allocation at an
already unfortunate moment. It is more efficient to create these error
values in globals and return these constant globals. If these errors are
not used (because the related code was optimized out), the globals will
also be optimized out.
2020-04-07 13:24:26 +02:00
Jaden Weiss 9f8715c143 runtime (gc): scan callee-saved registers while marking stack 2020-04-07 11:50:50 +02:00
Ayke van Laethem cbaa58a2d9 all: change //go:export to //export
This is the kind that is used in Go (actually CGo) for exporting
functions. I think it's best to use //export instead of our custom
//go:export pragma, for consistency (they are equivalent in TinyGo).
Therefore I've updated all instances to the standard format (except for
two that are updated in https://github.com/tinygo-org/tinygo/pull/1024).

No smoke tests changed (when comparing the output hash), except for some
wasm tests that include DWARF debug info and tend to be flaky anyway.
2020-04-05 16:16:57 +02:00
Ayke van Laethem 46345aade6 compiler: optimize comparing interface values against nil
This is a very common case. Avoiding a runtime.interfaceEqual call leads
to a very big reduction in code size in some cases (while it doesn't
affect many other examples). A number of driver smoke tests are reduced
by about 4kB just with this optimization.

I found this issue while looking into automatically calculating the
required amount of stack space for goroutines. The
runtime.interfaceEqual function is recursive, so it is best avoided.
2020-04-04 22:47:21 +02:00
Ayke van Laethem f06d7d1bd6 builder: run tools (clang, ...) as separate processes
This is necessary because LLVM defines many options in global variables
that are modified when invoking Clang. In particular, LLVM 10 seems to
have a bug in which it always sets the -pgo-warn-misexpect flag. Setting
it multiple times (over various cc1 invocations) results in an error:

    clang (LLVM option parsing): for the --pgo-warn-misexpect option: may only occur zero or one times!

This is fixed by running the Clang invocation in a new `tinygo`
invocation.

Because we've had issues with lld in the past, also run lld in a
separate process so similar issues won't happen with lld in the future.
2020-04-03 12:41:44 +02:00
Ayke van Laethem 407149e323 compiler: add debug info to goroutine start wrappers 2020-04-03 08:12:18 +02:00
Ron Evans 5d539df216 machine/atsamd21,atsamd51: clear the USB packet size before setting it again when sending
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-04-02 11:09:24 -04:00
Jaden Weiss ccf8b378b3 compiler: track PHI nodes 2020-04-02 15:06:58 +02:00
Jaden Weiss ae16b2c922 transform (gc): track phi nodes in stack slots 2020-04-02 15:06:58 +02:00
Jaden Weiss 6647c43a7b compiler: track the result of string concatenation
Before this commit, the garbage collector was able to collect string values while they were still in use.
2020-04-02 14:04:25 +02:00
Yannis Huber 6e86daa95e riscv: add I2C support for the SiFive HiFive1 Rev B board 2020-04-01 13:04:25 +02:00
Jaden Weiss 62e78c0a26 runtime (gc): add garbage collector that uses an external allocator 2020-03-30 14:35:29 +02:00
BCG 57320c0922 runtime: export implementations of malloc/free for use from C 2020-03-30 14:22:42 +02:00
Ron Evans 03fa9dd9b7 machine/atsamd21,atsamd51,nrf52840: refactor USB CDC device descriptor to reduce code duplication and heap allocations
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-03-29 17:54:58 +02:00
Ron Evans 06797b6d1a machine/atsamd21,atsamd51,nrf52840: correct USB CDC composite descriptors
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-03-29 17:54:58 +02:00
Ayke van Laethem 0596b3c003 compiler: add support for anonymous type asserts
This is used for example by the errors package, which contains:

    if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
        return true
    }

The interface here is not a named type.
2020-03-29 08:39:07 +02:00
Ayke van Laethem cc4a4c755f interp: show backtrace with error
This should make it much easier to figure out why and where an error
happens at package initialization time.
2020-03-28 21:55:12 +01:00
Jaden Weiss 2501602b4f ir: add descriptive error messages to SimpleDCE pass
This commit modifies the SimpleDCE pass to emit errors similar to those emitted by gc when the main function is missing.
2020-03-28 20:52:53 +01:00
Jaden Weiss 5cc130bb6e compiler: implement spec-compliant shifts
Previously, the compiler used LLVM's shift instructions directly, which have UB whenever the shifts are large or negative.
This commit adds runtime checks for negative shifts, and handles oversized shifts.
2020-03-28 17:35:19 +01:00
Ayke van Laethem 91d1a23b14 compiler,runtime: translate memzero calls to LLVM memset intrinsics
This gives the optimizer a bit more information about what the calls do.
This should result in slightly better generated code.

Code size sometimes goes up and sometimes goes down. I blame the code
size going up on the inliner which inlines more functions, because
compiling the smoke tests in the drivers repository with -opt=1 results
in a slight code size reduction in all cases.
2020-03-27 21:01:59 +01:00
Ayke van Laethem eaa54bc7e3 compiler,runtime: use LLVM intrinsics for memcpy/memmove
This replaces the custom runtime.memcpy and runtime.memmove functions
with calls to LLVM builtins that should hopefully allow LLVM to better
optimize such calls. They will be lowered to regular libc memcpy/memmove
when they can't be optimized away.

When testing this change with some smoke tests, I found that many smoke
tests resulted in slightly larger binary sizes with this commit applied.
I looked into it and it appears that machine.sendUSBPacket was not
inlined before while it is with this commit applied. Additionally, when
I compared all driver smoke tests with -opt=1 I saw that many were
reduced slightly in binary size and none increased in size.
2020-03-27 21:01:59 +01:00
Ayke van Laethem c01f81144e machine: avoid binary size regression after LLVM memory intrinsics
Somehow moving to LLVM memory intrinsics for calls like memcpy made the
machine.sendUSBPacket get inlined. This is a problem because it is
called in many different functions and it is just big enough to cause a
significant file size increase.

Adding //go:noinline solves this problem and gets the examples/blinky1
program below the file size it was before this change (tested:
itsybitsy-m0, itsybitsy-m4, circuitplay-bluefruit).
2020-03-27 21:00:54 +01:00
Ayke van Laethem 67c242173c sync: implement sync.Map
This is a very simple implementation, just enough to get packages to
compile.
2020-03-27 19:02:45 +01:00
Ayke van Laethem f8876ea245 compiler, transform: remove runtime.isnil hack
This hack was originally introduced in
https://github.com/tinygo-org/tinygo/pull/251 to fix an escape analysis
regression after https://github.com/tinygo-org/tinygo/pull/222
introduced nil checks. Since a new optimization in LLVM (see
https://reviews.llvm.org/D60047) this hack is not necessary anymore and
can be removed.

I've compared all regular tests and smoke tests before and after to
check the size. In most cases this change was an improvement although
there are a few regressions.
2020-03-27 07:38:16 +01:00
Ayke van Laethem bbfa601d27 compiler: avoid nil pointer checks with unsafe.Pointer
The unsafe.Pointer type is used for many low-level operations,
especially in the runtime. It can for example be used to copy the
contents of a slice (in the copy builtin) independent of the slice
element type.
2020-03-27 07:38:16 +01:00
Ayke van Laethem 19f8874764 compiler: do not perform nil checking when indexing slices
The x/tools/go/ssa package splits slice loads/stores into two
operations. So for code like this:

    x = p[3]

It has two instructions:

    x_ptr = &p[3]
    x = *x_ptr

This makes the IR simpler, but also means we're accidentally inserting
more nil checks than necessary: the slice index operation has
effectively already checked for nil by performing a bounds check.
Therefore, omit nil pointer checks for pointers created by
*ssa.IndexAddr.

This change is necessary to make sure a future removal of runtime.isnil
will not cause the escape analysis pass to regress. Apart from that, it
reduces code size slightly in many smoke tests (with no increases in
code size).
2020-03-27 07:38:16 +01:00
Ayke van Laethem 85854cd58b compiler: add dereferenceable_or_null attribute where possible
This gives a hint to the compiler that such parameters are either NULL
or point to a valid object that can be dereferenced. This is not
directly very useful, but is very useful when combined with
https://reviews.llvm.org/D60047 to remove the runtime.isnil hack without
regressing escape analysis.
2020-03-27 07:38:16 +01:00
Ayke van Laethem 980068543a riscv: implement VirtIO target
This allows running RISC-V tests in CI using QEMU, which should help
catch bugs.
2020-03-26 23:03:55 +01:00
Ayke van Laethem c4fd19be99 compiler: refactor public interface
This commit merges NewCompiler and Compile into one simplifying the
external interface. More importantly, it does away with the entire
Compiler object so the public API becomes a lot smaller.

The refactor is not complete: eventually, the compiler should just
compile a single package without trying to load it first (that should be
done by the builder package).
2020-03-25 20:17:46 +01:00
Ayke van Laethem 8ef921e028 compiler: remove leftover code after refactor
A few functions were duplicated during the refactor. They can now be
deleted.
2020-03-25 20:17:46 +01:00
Ayke van Laethem 315b028317 compiler: remove *Frame type 2020-03-25 20:17:46 +01:00
Ayke van Laethem c8b5042870 compiler: refactor creation of functions 2020-03-25 20:17:46 +01:00
Ayke van Laethem ad992e2456 compiler: refactor top-level createInstruction function 2020-03-25 20:17:46 +01:00
Ayke van Laethem c1521fe12e compiler: refactor starting new goroutines 2020-03-25 20:17:46 +01:00
Ayke van Laethem 405ec2a563 compiler: refactor defer operations 2020-03-25 20:17:46 +01:00
Ayke van Laethem 19bf8acde0 compiler: refactor parseExpr
parseExpr (now createExpr) and all callers (recursively) are switched
over to the new builder object!
2020-03-25 20:17:46 +01:00
Ayke van Laethem d752e66be5 compiler: refactor function calling 2020-03-25 20:17:46 +01:00
Ayke van Laethem d46934d1f1 compiler: refactor builtins 2020-03-25 20:17:46 +01:00
Ayke van Laethem 2d9f3605b9 compiler: refactor map operations to use the builder object 2020-03-25 20:17:46 +01:00
Ayke van Laethem ce84f77c8d compiler: refactor interface creation and calling 2020-03-25 20:17:46 +01:00
Ayke van Laethem fc0ac9af8e compiler: refactor interface invoke wrapper creation
Now that most of the utility compiler methods are ported over to the
builder or compilerContext, it is possible to avoid having to do the
wrapper creation in two steps. A new builder is created just to create
the wrapper.

This is a small reduction in line count (and a significant reduction in
complexity!), even though more documentation was added.
2020-03-25 20:17:46 +01:00
Ayke van Laethem bee5a67097 compiler: refactor parseConvert 2020-03-25 20:17:46 +01:00
Ayke van Laethem a1ba71ce99 compiler: refactor parseUnOp 2020-03-25 20:17:46 +01:00
Ayke van Laethem 6dafb6c65e compiler: refactor creating of channel operations 2020-03-25 20:17:46 +01:00
Ayke van Laethem b8d20535ba compiler: refactor asserts 2020-03-25 20:17:46 +01:00
Ayke van Laethem 7733666fa8 compiler: refactor parseTypeAssert
Move to the builder object, and rename to createTypeAssert.
2020-03-25 20:17:46 +01:00
Ayke van Laethem 349ecf1736 compiler: rename Compiler.getValue -> builder.getValue
This is a fairly big commit, but it actually changes very little.
getValue should really be a property of the builder (or frame), where
the previously created instructions are kept.
2020-03-25 20:17:46 +01:00
Ayke van Laethem 840acdd316 compiler: refactor createBinOp
This commit unfortunately introduces a significant amount of code
duplication. However, all that duplicate code should be removed once
this refactor is done.
2020-03-25 20:17:46 +01:00
Ayke van Laethem b5e29bf0c1 compiler: refactor IR generation
This is the first commit in a series to refactor the compiler. The
intention is to make sure every function to be compiled eventually has
its own IR builder. This will make it much easier to do other
refactorings in the future:

  * Most code won't depend (directly) on the central Compiler object,
    perhaps making it possible to eliminate it in the future. Right now
    it's embedded in the `builder` struct but individual fields from the
    `Compiler` can easily be moved into the `builder` object.
  * Some functions are not directly exposed in Go SSA, they are wrapper
    functions for something. At the moment they are included in the list
    of functions to be compiled with the reachability analysis
    (SimpleDCE) in the ir package, but eventually this reachability
    analys will be removed. At that point, it would be very convenient
    to be able to simply build a function with a new IR builder.

The `compilerContext` struct makes sure that it is not possible for
`builder` methods to accidentally use global state such as the global IR
builder. It is a transitional mechanism and may be removed when
finished.
2020-03-25 20:17:46 +01:00
Ayke van Laethem 8c86eae924 avr: clean up ATtiny definitions
Add definitions for constants like PB0.
2020-03-25 19:47:36 +01:00
Ayke van Laethem 3b1759f463 transform: fix error in interface lowering pass
It appears that LLVM can sometimes recognize that multiple calls to
runtime.interfaceMethod can be merged into one. When that happens, the
interface lowering pass shows an error as it didn't expect that
situation.

Luckily the fix is very easy.
2020-03-25 16:28:38 +01:00
GeoffThomas b4dddfe439 stm32 add SPI for stm32f4 2020-03-25 16:24:19 +01:00
gwtnz ac73430502 Dockerfile: copy picolibc header into container 2020-03-25 14:51:18 +01:00
Elliott Sales de Andrade 7446413dc9 wasm_exec: Sync polyfills with Go 1.14.1.
This ports over the following three commits:
https://github.com/golang/go/commit/9627180f0f1f016307f4987cec6594baf90d64ae
https://github.com/golang/go/commit/aff2f6ece896e0fe76a2c8853abf868f689006f0
https://github.com/golang/go/commit/42b79f08239216eeea3cc1b0febc992f91bd88de
https://github.com/golang/go/commit/ecba83520d4c34870e0f5f0997d59d4496957240
2020-03-25 14:37:49 +01:00
Travis McLane 83426edcdc avr: add Arduino Mega 2560 2020-03-24 22:24:47 +01:00
Ayke van Laethem 26aba72729 transform: replace panics with source locations
Panics are bad for usability: whenever something breaks, the user is
shown a (not very informative) backtrace. Replace it with real error
messages instead, that even try to display the Go source location.
2020-03-24 15:07:55 +01:00
Ayke van Laethem 25fcf3e18e interp: better support interface operations
This commit teaches the interp scanner that supported interface
operations (type assertions, interface assertions) are supported.

This fixes a problem with math/rand in Go 1.14.
2020-03-24 14:49:24 +01:00
Wojtek Siudzinski 5133604b51 machine: set USB CDC identifiers to Particle ones 2020-03-24 14:42:10 +01:00
Ayke van Laethem 04cec56141 main: extend test timeout from 1s to 10s
This should avoid the rather frequent "test ran too long,
terminating..." error message that often occurs in CI and when running
`go test` manually. Apparently I was too optimistic: some tests take
longer than 1 second to run.
2020-03-22 21:57:40 +01:00
Ayke van Laethem f316ebc23b all: include picolibc for bare metal targets
This is necessary for better CGo support on bare metal. Existing
libraries expect to be able to include parts of libc and expect to be
able to link to those symbols.

Because with this all targets have a working libc, it is now possible to
add tests to check that a libc in fact works basically.

Not all parts of picolibc are included, such as the math or stdio parts.
These should be added later, when needed.

This commit also avoids the need for the custom memcpy/memset/memcmp
symbols that are sometimes emitted by LLVM. The C library will take care
of that.
2020-03-22 17:14:59 +01:00
Ayke van Laethem 9ec426e25e builder: refactor compiler-rt library
This refactor makes adding a new library (such as a libc) much easier in
the future as it avoids a lot of duplicate code. Additionally, CI should
become a little bit faster (~15s) as build-builtins now uses the build
cache.
2020-03-22 17:14:59 +01:00
Ayke van Laethem 854092c7bc avr: add emulator to atmega1284p
Somehow I forgot to add this emulator. With this, you can easily emulate
programs:

    $ tinygo run -target=atmega1284p examples/serial
    Loaded 698 .text at address 0x0
    Loaded 12 .data
    hello world!..
    hello world!..
    hello world!..
2020-03-22 11:45:03 +01:00
GeoffThomas c61c5e5799 refactor stm32 UART code 2020-03-22 07:02:17 +01:00
Ron Evans 046efdd93a machine/stm32f103xx: enable clock on configure of GPIO pin
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-03-21 20:47:46 +01:00
Ayke van Laethem 9d3de55229 compiler: move Optimizer function to transform package
This refactor is a prerequisite to much larger refactors in the
compiler.
2020-03-21 15:45:25 +01:00
Ayke van Laethem 78bd7e094f compiler: move funcImplementation to compileopts
This allows packages other than the compiler to know (from a single
source of truth) which implemenation is used for Go func values.

This refactor is necessary to be able to move the Optimize function to
the transform package.
2020-03-21 15:45:25 +01:00
Ayke van Laethem 2f88c7aab4 compiler: move IR checker to separate package
This is a preparation for moving the Optimize function to the transform
package.
2020-03-21 15:45:25 +01:00
Ron Evans 599670cef6 license: update license year for 2020. No other changes.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-03-21 14:37:25 +01:00
Ayke van Laethem 9222bda9c6 interp: add support for constant type asserts
Non-constant type asserts are not yet implemented, but should be
relatively easy to add at a later time. They should result in a clear
error message for now.
2020-03-20 22:22:24 +01:00
Ayke van Laethem 213a6240a1 avr: move data address space to 0x800000
This convention is followed by most of the avr-gcc toolchain but older
versions of binutils don't mind overlapping program/data spaces.
However, newer versions start complaining about an overlap in address
space:

    avr-ld: section .stack VMA [0000000000000100,00000000000002ff] overlaps section .text VMA [0000000000000000,0000000000000225]

This commit moves the data space in the linker script to 0x800000, like
the rest of the toolchain does.
2020-03-20 15:12:38 +01:00
Ayke van Laethem add014f21b avr: add support for tasks scheduler
This adds support for the `-scheduler=tasks` flag for AVR. On most AVR
chips you wouldn't want to run a real scheduler but it may be useful in
some cases, especially on devices with more RAM. It is disabled by
default.
2020-03-20 15:12:05 +01:00
Ayke van Laethem c5cb2cec9b compiler: move NonConstGlobals pass to transform package 2020-03-19 19:56:08 +01:00
Ayke van Laethem b6314fa6ab compiler: move ApplyFunctionSections to transform package 2020-03-19 19:56:08 +01:00
Ron Evans 945ff4d160 build: use go1.13.8 instead of image default which is now go1.14
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-03-19 12:55:26 +01:00
Ron Evans fa861085c0 machine/adafruit: make all USB vendor and product names string descriptors consistent
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-03-18 12:08:15 +01:00
Ron Evans a02d4e0b77 machine/nrf52840: use correct USB device descriptor and string descriptor for nrf52840 based boards
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-03-18 12:08:15 +01:00
Ron Evans 9f597bbbc3 machine/nrf52840: add correct USB VID and PID for all nrf52840 based boards
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-03-18 12:08:15 +01:00
Ron Evans c03e3616a2 machine/atsamd21 and atsamd51: corrections needed to return valid UCB CDC device and string descriptors
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-03-18 12:08:15 +01:00
Ron Evans 0312f12696 machine/usb: set the vid and pid to valid values supplied by Adafruit and Arduino for boards that support USB CDC
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-03-18 12:08:15 +01:00
Ayke van Laethem fbb8efd24c Makefile: allow overriding the llvm-project directory
This allows setting a different directory for experimental changes. For
example, I also have a llvm-project.master directory that tracks the
master branch. It is very useful to be able to temporarily use that
directory:

    make llvm-build.master LLVM_BUILDDIR=llvm-build.master LLVM_PROJECT=llvm-project.master
2020-03-17 21:33:39 +01:00
Ayke van Laethem a8da601672 cgo: make -I and -L paths absolute
This is very useful for (conditionally) adding extra include paths
relative to the package path.
2020-03-17 20:43:28 +01:00
Jaden Weiss 9cef23c318 internal/task: fix build tags on src/internal/task/task_stack_cortexm.go 2020-03-17 19:25:30 +01:00
Jaden Weiss 0759b70c50 run init in a goroutine 2020-03-17 19:25:30 +01:00
Jaden Weiss 5d869f6042 generalize -scheduler=none to support most platforms 2020-03-17 19:25:30 +01:00
Ayke van Laethem 5089d1a5a7 avr: add atmega1284 chip support
Not tested on actual hardware, only on simavr. The main motivation for
adding this chip is to be able to run simulated tests using a much
larger memory space (16kB RAM, 128kB flash) without jumping to the XMega
devices that may not be as well supported by LLVM.
2020-03-17 14:46:56 +01:00
Ayke van Laethem 66afcb3b39 avr: pass the correct -mmcu flag to the linker
It does not appear to be necessary for these devices but might result in
more appropriate libraries to be linked in.

It is best to _not_ specify the exact MCU because otherwise a few other
settings (such as startfiles and some linker script configs) also get
set, which we do manually anyway and should not be interfered with.

I discovered this while working on support for the atmega1284.
2020-03-17 14:46:56 +01:00
Ayke van Laethem 5bace979ea avr: use the correct RAM start address
Previously, the RAM was set to start at address 0. This is incorrect: on
AVR, the first few addresses are taken up by memory-mapped I/O. The
reason this didn't lead to problems (yet) was because the stack was
usually big enough to avoid real problems.
2020-03-17 14:46:56 +01:00
Ayke van Laethem 63cfb09e9e main: move some duplicate code to a single place
This makes fixes like https://github.com/tinygo-org/tinygo/pull/915
easier to apply. And in general, avoiding duplication is a good thing.
2020-03-17 13:24:41 +01:00
Jaden Weiss 6a50f25a48 refactor coroutine lowering and tasks 2020-03-17 12:16:10 +01:00
Ron Evans 2521cacb51 docker: use git submodule sync to handle case where repo url changes
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-03-17 09:05:30 +01:00
Ayke van Laethem 7ac531906b main: update go-llvm dependency 2020-03-16 15:30:40 -07:00
Ayke van Laethem 982b2d06ab main: improve error reporting while running go test
This commit switches integration tests to use the same error reporting
mechanism as the tinygo compiler normally uses. It replaces errors like
this:

    main_test.go:139: failed to build: interp: branch on a non-constant

With this:

    main.go:693: # math/rand
    main.go:695: interp: branch on a non-constant

In this particular case the error isn't much better (it gives the
relevant package, though) but other errors should also include the
source location where they happen.
2020-03-16 07:40:23 -07:00
Ayke van Laethem ad8996c4ee arm: fix linker script
There were a few instances like `.text` and `.text*`. The first was
redundant with the second, but the intention was to write `.text.*`.

This doesn't change anything (tested with `make smoketest`) but should
avoid propagating this error in the future.
2020-03-16 07:18:39 -07:00
Ayke van Laethem 571a412266 compiler: merge some redundant assertion code 2020-03-13 16:15:36 -07:00
Ayke van Laethem 79dae62c78 compiler,runtime: check for channel size limits
This patch is a combination of two related changes:

 1. The compiler now allows other types than `int` when specifying the
    size of a channel in a make(chan ..., size) call.
 2. The compiler now checks for maximum allowed channel sizes. Such
    checks are trivially optimized out in the vast majority of cases as
    channel sizes are usually constant.

I discovered this issue when trying out channels on AVR.
2020-03-13 16:15:36 -07:00
Ayke van Laethem 1a7369af6e runtime: return the correct type from the copy builtin
The copy builtin is defined as follows by the Go language spec:

    copy(dst, src []T) int
    copy(dst []byte, src string) int

In other words, it returns an int. The runtime.sliceCopy compiler
intrinsic returned a uintptr instead, which led to a problem while
compiling the strings package for AVR.

No other architecture should be affected by this change as the
conversion from an uintptr to an int is a no-op on most architectures.
2020-03-11 15:44:31 -07:00
Ayke van Laethem 928a970782 ci: move Go version tests to buster containers
This fixes an error like the following:

    E: The method driver /usr/lib/apt/methods/https could not be found.
    N: Is the package apt-transport-https installed?

Apparently apt.llvm.org has been switched over to HTTPS. One solution
could be to install apt-transport-https, but another (easier) solution
is to switch to a newer container.

Note: I did not switch the other containers, to make sure TinyGo is
still built with an older Debian release. That ensures the resulting
binaries are relatively portable across distros, even relatively old
distros.
2020-03-11 15:08:17 -07:00
Ayke van Laethem 0c73d56861 compiler: add debug info for local variables
Local variables aren't scoped yet and not all are included, but this is
a start. To use it, run `info locals` in GDB.
2020-03-11 14:55:04 -07:00
gwtnz c8a4994feb stm32f4: refactor GPIO, in prep for adding SPI, I2C, PWM etc 2020-03-07 15:34:42 +01:00
Daniel Esteban 3aaa029c70 added support for ADC1 on atsamd51 (PB04 / PB05 / PB06 / PB07) 2020-03-01 21:27:59 +01:00
Justin Clift aeb7539f67 wasm: add js.copyBytesToJS() 2020-03-01 13:51:57 +01:00
Ayke van Laethem 978a0cd9b0 Makefile: fix LLVM tools detection for wasi-libc
This makes the `make wasi-libc` command much more reliable and makes the
CI configuration simpler. Also, it avoids warnings when they are not
relevant.
2020-02-28 14:23:31 +01:00
Jaden Weiss 6896b0014b compiler: fix deferred calls to exported functions
Previously using defer with an exported function generated an invalid function call due to differences between TinyGo's calling convention and the C calling convention.
2020-02-27 16:35:31 +01:00
Ayke van Laethem 4dfc289ae5 compiler,runtime: support operations on nil map
The index expression and delete keyword are valid on nil maps, so the
runtime must be modified to support this.
2020-02-26 20:42:01 +01:00
Ayke van Laethem 53688c86c8 gameboy-advance: enable debugging with GDB 2020-02-26 20:39:43 +01:00
Ron Evans 100cbad65e main: increment version to 0.13.0-dev
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-02-26 20:14:04 +01:00
Ayke van Laethem b3d3debd4c main: check for error after starting the OCD daemon
This replaces a panic with an informative error message if the right
tool (such as OpenOCD) is not installed.
2020-02-26 20:04:32 +01:00
Ayke van Laethem e83d1a02b6 avr: support tinygo run with simavr
This commit adds support for running both the Arduino Uno and the
DigiSpark under a simulator.
2020-02-26 19:50:54 +01:00
Ayke van Laethem 1a2f051bee main: use gdb-multiarch for debugging Cortex-M chips
Previously this was set to arm-none-eabi-gdb, but that has been replaced
by gdb-multiarch in recent Debian/Ubuntu versions.
2020-02-26 19:03:04 +01:00
Jaden Weiss 2ca0f0d0c6 compiler/llvmutil: move constant pointer packs into constant globals
This adds an optimization to EmitPointerPack when all values are constants.
The values are stored in a constant global instead of on the heap.
This eliminates a heap allocation, and can reduce binary size.
2020-02-24 23:27:04 +01:00
Ayke van Laethem 4da137d7c8 compiler: check for non-static allocas
Non-static allocas are disallowed for a number of reasons, so check for
their presence. See the commit diff for details.
2020-02-24 23:11:07 +01:00
Jaden Weiss c622cbac39 compiler: mark abort as noreturn
This marks the libc function abort as non-returning. This allows LLVM to optimize away code after panics. Also, this allows deadlocks to be properly propogated with the coroutines scheduler.
2020-02-24 21:43:27 +01:00
Jaden Weiss 67229af879 transform: do not track const globals 2020-02-24 21:04:50 +01:00
Johann Freymuth c0b8716d7f targets/wasm: update wasm_exec.js for go 1.13
This fixes the wasm examples for go 1.13, but breaks them for older go versions.
2020-02-23 23:55:04 +01:00
BCG 0655086048 machine/nrf: implement auto-reset over USB for nrf52840 2020-02-22 15:46:33 +01:00
Ayke van Laethem bf57ae01fa machine: remove old comment on volatile behavior
The volatile pragma has long since been replaced by builtins, so this
commentis now outdated.
2020-02-20 21:36:56 +01:00
Ayke van Laethem 3f74e3c41f main: update cmsis-svd module
This reverts back to the upstream repository which has merged all
necessary updates.
2020-02-19 09:39:26 +01:00
Wojtek Siudzinski 0c0af6d3fe Apply review suggestions 2020-02-18 23:34:56 +01:00
Wojtek Siudzinski 765440b8ec Add smoke tests 2020-02-18 23:34:56 +01:00
Wojtek Siudzinski b07b13ca1b Add the boards to README 2020-02-18 23:34:56 +01:00
Wojtek Siudzinski 19c6ac01df Fix UART variables 2020-02-18 23:34:56 +01:00
Wojtek Siudzinski 9e79b41bb4 Add GPIOs and fix errors 2020-02-18 23:34:56 +01:00
Wojtek Siudzinski b4ee57db26 Add the missing Pin type 2020-02-18 23:34:56 +01:00
Wojtek Siudzinski 6f0c49ad7a Fix copypasta 2020-02-18 23:34:56 +01:00
Wojtek Siudzinski e29379ce3a Fix the formatting 2020-02-18 23:34:56 +01:00
Wojtek Siudzinski f0fd24e7ba Add Argon and Boron 2020-02-18 23:34:56 +01:00
Wojtek Siudzinski bdef52ad62 Allow changing the UART pins 2020-02-18 23:34:56 +01:00
Wojtek Siudzinski fd1785ed2d Add Particle Xenon 2020-02-18 23:34:56 +01:00
Jaden Weiss 94ec082f6c Makefile: autodetect llvm-ar and llvm-nm commands 2020-02-18 19:17:28 +01:00
Scott Yoder 630c498efa nrf52840: implement USB-CDC (#883)
* machine/nrf52840: usb-cdc implementation
2020-02-17 15:14:24 +01:00
Elliott Sales de Andrade d11abb33fe main: use go mod expected path for go.bug.st/serial 2020-02-11 20:09:22 +01:00
gwtnz e3ae57090f runtime/volatile: include ReplaceBits method 2020-02-11 15:55:14 +01:00
Scott Yoder a0cdd6b4ed fix typo in pin configuration options for NRF 2020-02-09 22:32:27 +01:00
BCG c721cae48b machine: add Adafruit CLUE Alpha 2020-02-07 23:49:59 +01:00
BCG 2138fd7854 machine/samd51: Moving QSPI definitions to common file
The QSPI peripheral is only available on a fixed set of pins, so these can be added as constants for all atsamd51 boards.
2020-02-05 17:45:33 +01:00
Ayke van Laethem 1d913a62bc main: version 0.12.0 2020-01-31 00:01:47 +01:00
Ayke van Laethem 24a0f237d8 wasm: use wasi ABI for basic startup/stdout
This allows TinyGo-built binaries to run under wasmtime, for example:

    tinygo build -o test.wasm -no-debug -target=wasm examples/test
    wasmtime run test.wasm 0
2020-01-28 20:31:09 +01:00
BCG eb9c2c276e Added indexBytePortal from standard library to link as implementation for internal/bytealg.IndexByte 2020-01-28 20:17:11 +01:00
Ayke van Laethem 519adf3aef transform: wasm-abi: create temporary allocas in the entry block
This avoids problems with goroutines in WebAssembly, and is generally a
good thing. It fixes some cases of the following problem:

    LLVM ERROR: Coroutines cannot handle non static allocas yet
2020-01-28 19:29:09 +01:00
Ayke van Laethem 4d79d473c4 compiler: move wasm ABI workaround to transform package
By considering this as a regular transformation, it can be easily
tested.
2020-01-28 19:29:09 +01:00
Ron Evans 91299b6466 docs: correct count for list of supported boards
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-01-27 23:10:58 +01:00
Ayke van Laethem 8687f3f8f4 targets/gba: implement interrupt handler
Thanks to Kyle Lemons for the inspiration and original design. The
implementation in this commit is very different however, building on top
of the software vectoring needed in RISC-V. The result is a flexible
interrupt handler that does not take up any RAM for configuration.
2020-01-27 21:56:17 +01:00
Ayke van Laethem f14127be76 targets/gba: make linker script cleaner
Make it clearer where the stack is located. Additionally, get the heap
to work (the GC needs to have _stack_top defined to work correctly).
2020-01-27 21:56:17 +01:00
Ron Evans a9b2d8c294 docs: add Arduino Nano to list of supported boards
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-01-27 21:02:29 +01:00
Ayke van Laethem 960ab3fca4 runtime: fix external address declarations
This is the same problem as in
https://github.com/tinygo-org/tinygo/pull/605, but other targets also
suffer from it.

Discovered with the GBA target, but as pointed out in
https://bugs.llvm.org/show_bug.cgi?id=42881#c1 this appears to be a bug
in the way external globals are declared, not in LLVM. Therefore I
decided that fixing it everywhere would be the best thing to do.
2020-01-27 20:34:10 +01:00
Ayke van Laethem 94fec09b31 machine/fe310: implement UART receive interrupts
This makes the echo example work on the HiFive1 rev B board.
2020-01-27 19:58:39 +01:00
Ayke van Laethem 415c60551e runtime/fe310: add support for PLIC interrupts
This commit adds support for software vectoring in the PLIC interrupt.
The interrupt table is created by the compiler, which leads to very
compact code while retaining the flexibility that the interrupt API
provides.
2020-01-27 19:58:39 +01:00
Ayke van Laethem a9174d9184 runtime/fe310: do peripheral initialization in an explicit call
This avoids some issues with interp in the next commit.
2020-01-27 19:58:39 +01:00
Ayke van Laethem 46ccb6793e main: differentiate between various serial/USB error messages
This way it is possible to determine the source of an error when it
happens.
2020-01-27 19:46:53 +01:00
Ayke van Laethem 15c7d93ea9 avr: use a garbage collector
This might sound crazy, but I think it's better to enable the GC by
default to avoid surprises. It costs 1130 bytes of flash and 16 bytes of
RAM (plus heap overhead) so it's not exactly free, but if needed it can
easily be disabled with `-gc=leaking`. On the Uno (32kB flash, 2kB RAM)
that's not massive, on the DigiSpark (8kB flash, 0.5kB RAM) that may be
too much depending on the application.
2020-01-27 19:01:55 +01:00
Ayke van Laethem 6e26728391 compiler: remove some dead code 2020-01-27 08:27:14 +01:00
Ayke van Laethem 0d34f933eb compiler,runtime: implement maps for arbitrary keys
This implementation simply casts types without special support to an
interface, to make the implementation simpler and possibly reducing the
code size too. It will likely be slower than the canonical Go
implementation though (which builds special compare and hash functions
at compile time).
2020-01-27 08:27:14 +01:00
Ayke van Laethem 440dc8ed4e compiler: move making maps to the map.go file
This is a useful refactor for when I add support for any type in map
keys.
2020-01-27 08:27:14 +01:00
Ayke van Laethem aeb2e6f70a all: add Arduino Nano support 2020-01-26 23:24:45 +01:00
Ayke van Laethem e830acadf3 ci: fix Windows build failure for wasi-libc
The wasi-libc Makefile uses the `find` command line tool. Unfortunately,
it was using the Windows find version instead of the MinGW version,
leading to lots of errors at a later stage.

This commit prepends /usr/bin to `$PATH` to make sure the MinGW version
is found first.
2020-01-25 17:11:45 +01:00
Ron Evans 3f9609560e flash: remove default port check for Digispark as micronucleus communicates directly using HID
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-01-25 13:49:27 +01:00
Ayke van Laethem e2aa3789c3 wasm: include wasi-libc
This allows CGo code to call some libc functions. Additionally, by
putting memset/memmove/memcpy in an archive they're not included anymore
when not necessary, reducing code size for small programs.
2020-01-23 16:40:09 +01:00
Ayke van Laethem c61657d22d machine: refactor pin handling to auto-detect pin mode
With this change, it's no longer necessary to set a specific pin mode:
it will get autodetected in the Configure() call.

Tested on an ItsyBitsy M4 with the mpu6050 example in the drivers repo.
2020-01-23 00:08:34 +01:00
Ayke van Laethem 4c0ebb5b41 arduino: make avrdude command line compatible with Windows
On Windows, it is common that there is a colon in the path. avrdude will
treat that as a separator and everything behind it as the file format
specifier instead of defaulting to Intel hex format.

By explicitly specifying the Intel hex format (with `:i`), this issue
should be fixed.
2020-01-21 07:49:17 +01:00
Ayke van Laethem e17a2e6776 machine/atsamd51: use only the necessary UART interrupts
A small footnote in the datasheet says that interrupt source numbers
correspond to the bit position in INTFLAG. We only need the RXC
interrupt for UART. In other words, ony the _2 interrupts (RXC is in the
2nd bit position) needs to be used for UART to work correctly.

In the future, more interrupts may be needed. They can then be added as
necessary.
2020-01-20 21:33:53 +01:00
Ayke van Laethem bdfa4d28cf machine/atsamd51: fix obvious bug in I2C code
I2C uses a hardcoded peripheral instead of referring to a specific
peripheral. In addition to that, it refers to the wrong SERCOM
(SERCOM3), which isn't used on any of the atsamd51 boards for I2C.
2020-01-20 21:33:53 +01:00
Ayke van Laethem 3745fb1c40 machine/atsamd51: switch UART to use new pin configuration
This makes UART configuration much more flexible.

I confirmed that UART1 and UART2 still work with this change on the
ItsyBitsy M4.
2020-01-20 21:33:53 +01:00
Ayke van Laethem a5ed993f8d all: add compiler support for interrupts
This commit lets the compiler know about interrupts and allows
optimizations to be performed based on that: interrupts are eliminated
when they appear to be unused in a program. This is done with a new
pseudo-call (runtime/interrupt.New) that is treated specially by the
compiler.
2020-01-20 21:19:12 +01:00
Ayke van Laethem 3729fcfa9e wasm: don't skip the GC test
Finally, all tests run on all targets!
2020-01-20 20:30:42 +01:00
Ayke van Laethem 8f8232aada compileopts: fix CGo when cross compiling
Use the cross compiling toolchains for compiling/linking. This fixes CGo
support, and therefore allows CGo to be used when cross compiling to
Linux on a different architecture.
This commit also removes some redundant testing code.
2020-01-20 20:30:42 +01:00
Ayke van Laethem d5e11fa19b compiler: do not emit debug info for extern globals
This results in a link error in the following commit (undefined
reference to runtime.trackedGlobalsBitmap from .debug_info). Solution:
don't emit debug info for declared but not defined symbols.
2020-01-20 20:30:42 +01:00
Ayke van Laethem 4ee7bf00e1 machine: avoid bytes package in USB logic
This greatly cuts down on compile time (by about 5x for small programs)
and also makes the program a whole lot smaller. Overall it cuts down
`make smoke-test` in the drivers repository by half (from 160s to 80s).

This will probably also fix the timeout issue in the Playground:
https://github.com/tinygo-org/playground/issues/7
2020-01-18 14:23:49 +01:00
Ayke van Laethem c698e99880 gameboy-advance: include compiler-rt in build
This avoids errors like the following:

    ld.lld-9: error: undefined symbol: __umodsi3
    ld.lld-9: error: undefined symbol: __aeabi_uidivmod
2020-01-16 08:11:04 +01:00
Ayke van Laethem 4d5dafd360 main: fix race condition in tests
This caused most tests to run the zeroalloc.go test instead of what they
should have been tested, and in turn explains most of the performance
gains of parallel testing.

This commit fixes it by avoiding race conditions. Luckily, no tests
started failing since then due to this.
2020-01-14 16:40:13 +01:00
Ayke van Laethem 1a32a68674 compiler: add support for CGO_ENABLED environment variable 2020-01-14 07:13:12 +01:00
Ayke van Laethem 9a69c6bcca hifive1b: add support for gdb subcommand
This makes debugging on the HiFive1 rev B much easier:

    tinygo gdb -target=hifive1b examples/echo

Using JLinkGDBServer as I couldn't figure out how to do it with OpenOCD.
2020-01-12 17:02:53 +01:00
Ayke van Laethem 6841f9e245 compiler: add support for debugging through JLinkGDBServer
Some J-Link targets aren't supported in OpenOCD (or would need some
special configuration), so also give the option to use JLinkGDBServer
instead.
2020-01-12 17:02:53 +01:00
Ron Evans ef4ede43d1 machine/pyportal: remove manual SPI pin mapping as now handled by default
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2020-01-11 21:31:33 +01:00
Ayke van Laethem 31d3ac725f machine/atsamd51: refactor SPI pin configuration to only look at pin numbers
This commit does the same thing as
https://github.com/tinygo-org/tinygo/pull/597 but for samd51 series
chips. Pin mode and pad numbers are automatically calculated from pin
numbers, returning an error if no valid pinout is possible.
2020-01-11 20:57:10 +01:00
Ron Evans 7ace991a2b machine/digispark: add clock speed and pin mappings 2020-01-11 12:58:37 +01:00
Ayke van Laethem b258f3424b riscv: print exception PC and code
This can be useful for debugging critical bugs in code. I haven't added
human-readable exceptions (such as "illegal instruction" or "stack
overflow") yet, they can be added when they happen in practice (to avoid
increasing code size too much).
2020-01-11 12:27:58 +01:00
Ayke van Laethem b9cdfd9e9a riscv: add bare-bones interrupt support
This commit adds support for timer interrupts, replacing the busy loop
that was used before. It is perhaps the most simple interrupt to
implement and should serve as the basis for further interrupt support in
RISC-V.
2020-01-10 08:04:13 +01:00
Ayke van Laethem 360923abbf compiler,riscv: implement CSR operations as intrinsics
CSR operations must be implemented in assembly. The easiest way to
implement them is with some custom intrinsics in the compiler.
2020-01-10 08:04:13 +01:00
Ayke van Laethem ed9b2dbc03 runtime/hifive1: use CLINT peripheral for timekeeping
The CLINT is implemented both on the fe310-g002 chip and in the sifive_e
QEMU machine type. Therefore, use that peripheral for consistency.

The only difference is the clock speed, which runs at 10MHz in QEMU for
some reason instead of 32.768kHz as on the physical HiFive1 boards.
2020-01-10 08:04:13 +01:00
Ayke van Laethem 3e521f710a compiler: add support for debugging globals
This makes most globals visible from GDB, using `info variables`.
2020-01-07 20:20:19 +01:00
BCG ecff6f8e0c Adafruit PyPortal Support (#800)
* machine/PyPortal: implementation, also includes refinements for other members of samd51x familty.
2020-01-06 11:53:43 +01:00
Jaden Weiss 7363bdc298 Add Jaden Weiss to CONTRIBUTORS 2020-01-06 09:24:20 +01:00
Michael Matloob 1cb9b948bc targets: add target circuitplay-bluefruit
Add a target for the Adafruit Circuit Playground Bluefruit, which is
based on the nRF52840. Adds the necessary code for the machine
package and the json and linker script files in the targets directory.
The machine package code is based on board_circuitplay_express.go,
with modifications made by consulting the wiring diagram on the
adafruit website here:
https://learn.adafruit.com/adafruit-circuit-playground-bluefruit/downloads

Also adds support to the uf2 conversion packacge to set the familyID
field. The Circuit Playground Bluefruit firmware rejects uf2 files
without the family id set to 0xADA52840 (and without the flag specifying
that the family id is present).
2020-01-06 09:23:39 +01:00
Ayke van Laethem 25cff20117 interp: error location for "unknown GEP" error
This commit removes a panic and replaces it with a proper source
location. The message still isn't very helpful, but at least it points
to a location in the source code.

I'm not very happy with all the `err.Error()` calls, but that's the way
to fit this in a `scanner.Error`. Eventually we should make a
replacement for `scanner.Error` that does proper wrapping of the
original error message.
2020-01-05 13:45:23 +01:00
Ayke van Laethem ec467da83c ci: produce zip as artifact in Azure Pipelines
Zip files are far more often used on Windows systems, so we provide zip
files in releases. Unfortunately, previously the provided artifact in CI
was really just a compressed .tar.gz file, which defeats the purpose.

This commit zips the release tarball itself so it can be downloaded as
an artifact.
2020-01-05 12:43:21 +01:00
BCG 5f77447e1a Adding code use 12-bit resolution for ADC and to clear previous ADC INPUTCTRL before subsequent read 2020-01-05 12:34:47 +01:00
Ayke van Laethem 27fafb7ab5 runtime: fix atsamd51 volatile usage
It was still using the (long removed) //go:volatile pragma for volatile
variables, thus it was only accidentally working.
2020-01-05 12:08:33 +01:00
Ayke van Laethem d1cc3c109d compiler: add globaldce pass to start of optimization pipeline
This reduces code size in a few cases when tested against the drivers
smoketests (although there was one minor increase) without significantly
increasing compile time. In fact, in my testing compile time appears to
be going down a little bit (around 1%, within the noise).
2020-01-05 11:37:26 +01:00
521 changed files with 29001 additions and 7939 deletions
+190 -46
View File
@@ -14,18 +14,16 @@ commands:
- run:
name: "Install apt dependencies"
command: |
echo 'deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
sudo apt-get update
sudo apt-get install \
llvm<<parameters.llvm>>-dev \
clang<<parameters.llvm>> \
libclang<<parameters.llvm>>-dev \
lld<<parameters.llvm>> \
llvm-<<parameters.llvm>>-dev \
clang-<<parameters.llvm>> \
libclang-<<parameters.llvm>>-dev \
lld-<<parameters.llvm>> \
gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \
qemu-user \
gcc-avr \
@@ -39,39 +37,83 @@ commands:
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
rm node-v10.15.1-linux-x64.tar.xz
install-chrome:
steps:
- run:
name: "Install Chrome"
command: |
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install ./google-chrome-stable_current_amd64.deb
install-xtensa-toolchain:
parameters:
variant:
type: string
steps:
- run:
name: "Install Xtensa toolchain"
command: |
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
sudo ln -s /usr/local/xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/xtensa-esp32-elf-ld
rm xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
llvm-source-linux:
steps:
- restore_cache:
keys:
- llvm-source-9-v0
- llvm-source-10-v1
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-9-v0
key: llvm-source-10-v1
paths:
- llvm-project
build-wasi-libc:
steps:
- restore_cache:
keys:
- wasi-libc-sysroot-v3
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-v3
paths:
- lib/wasi-libc/sysroot
test-linux:
parameters:
llvm:
type: string
steps:
- checkout
- submodules
- apt-dependencies:
llvm: "-9"
llvm: "<<parameters.llvm>>"
- install-node
- install-chrome
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- llvm-source-linux
- run: go install .
- run: go test -v ./cgo ./compileopts ./interp ./transform .
- run: go install -tags=llvm<<parameters.llvm>> .
- restore_cache:
keys:
- wasi-libc-sysroot-systemclang-v2
- run: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-systemclang-v2
paths:
- lib/wasi-libc/sysroot
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./interp ./transform .
- run: make gen-device -j4
- run: make smoketest
- run: make smoketest XTENSA=0
- run: make tinygo-test
- run: make wasmtest
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run: make fmt-check
assert-test-linux:
@@ -92,6 +134,8 @@ commands:
gcc-avr \
avr-libc
- install-node
- install-xtensa-toolchain:
variant: "linux-amd64"
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
@@ -99,7 +143,7 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-9-linux-v0-assert
- llvm-build-10-linux-v1-assert
- run:
name: "Build LLVM"
command: |
@@ -117,10 +161,11 @@ commands:
make ASSERT=1 llvm-build
fi
- save_cache:
key: llvm-build-9-linux-v0-assert
key: llvm-build-10-linux-v1-assert
paths:
llvm-build
- run: make ASSERT=1
- build-wasi-libc
- run:
name: "Test TinyGo"
command: make ASSERT=1 test
@@ -128,7 +173,6 @@ commands:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run: make gen-device -j4
- run: make smoketest TINYGO=build/tinygo
@@ -150,6 +194,8 @@ commands:
gcc-avr \
avr-libc
- install-node
- install-xtensa-toolchain:
variant: "linux-amd64"
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
@@ -157,7 +203,7 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-9-linux-v0
- llvm-build-10-linux-v1
- run:
name: "Build LLVM"
command: |
@@ -175,24 +221,32 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-9-linux-v0
key: llvm-build-10-linux-v1
paths:
llvm-build
- build-wasi-libc
- run:
name: "Test TinyGo"
command: make test
- run:
name: "Install fpm"
command: |
sudo apt-get install ruby ruby-dev
sudo gem install --no-document fpm
- run:
name: "Build TinyGo release"
command: |
make release -j3
make release deb -j3
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_amd64.deb
- store_artifacts:
path: /tmp/tinygo.linux-amd64.tar.gz
- store_artifacts:
path: /tmp/tinygo_amd64.deb
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run:
name: "Extract release tarball"
@@ -209,27 +263,29 @@ commands:
- run:
name: "Install dependencies"
command: |
curl https://dl.google.com/go/go1.13.darwin-amd64.tar.gz -o go1.13.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.13.darwin-amd64.tar.gz
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
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" }}
- restore_cache:
keys:
- llvm-source-9-macos-v0
- llvm-source-10-macos-v1
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-9-macos-v0
key: llvm-source-10-macos-v1
paths:
- llvm-project
- restore_cache:
keys:
- llvm-build-9-macos-v0
- llvm-build-10-macos-v1
- run:
name: "Build LLVM"
command: |
@@ -241,9 +297,19 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-9-macos-v0
key: llvm-build-10-macos-v1
paths:
llvm-build
- restore_cache:
keys:
- wasi-libc-sysroot-macos-v2
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-macos-v2
paths:
- lib/wasi-libc/sysroot
- run:
name: "Test TinyGo"
command: make test
@@ -261,44 +327,106 @@ commands:
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
tinygo version
- run:
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /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-stretch
- image: circleci/golang:1.11-buster
steps:
- test-linux
test-llvm9-go112:
- test-linux:
llvm: "9"
test-llvm10-go112:
docker:
- image: circleci/golang:1.12-stretch
- image: circleci/golang:1.12-buster
steps:
- test-linux
test-llvm9-go113:
- test-linux:
llvm: "10"
test-llvm10-go113:
docker:
- image: circleci/golang:1.13-stretch
- image: circleci/golang:1.13-buster
steps:
- test-linux
- test-linux:
llvm: "10"
test-llvm10-go114:
docker:
- image: circleci/golang:1.14-buster
steps:
- test-linux:
llvm: "10"
test-llvm10-go115:
docker:
- image: circleci/golang:1.15-buster
steps:
- test-linux:
llvm: "10"
assert-test-linux:
docker:
- image: circleci/golang:1.13-stretch
- image: circleci/golang:1.14-stretch
steps:
- assert-test-linux
build-linux:
docker:
- image: circleci/golang:1.13-stretch
- image: circleci/golang:1.14-stretch
steps:
- build-linux
build-macos:
@@ -306,6 +434,11 @@ jobs:
xcode: "10.1.0"
steps:
- build-macos
arch-release:
docker:
- image: archlinux:latest
steps:
- arch-release
@@ -314,8 +447,19 @@ workflows:
test-all:
jobs:
- test-llvm9-go111
- test-llvm9-go112
- test-llvm9-go113
- test-llvm10-go112
- test-llvm10-go113
- test-llvm10-go114
- test-llvm10-go115
- 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]+)*(-.*)*/
+5
View File
@@ -3,14 +3,19 @@ docs/_build
src/device/avr/*.go
src/device/avr/*.ld
src/device/avr/*.s
src/device/esp/*.go
src/device/nrf/*.go
src/device/nrf/*.s
src/device/nxp/*.go
src/device/nxp/*.s
src/device/sam/*.go
src/device/sam/*.s
src/device/sifive/*.go
src/device/sifive/*.s
src/device/stm32/*.go
src/device/stm32/*.s
src/device/kendryte/*.go
src/device/kendryte/*.s
vendor
llvm-build
llvm-project
+6
View File
@@ -14,3 +14,9 @@
path = lib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt.git
branch = release_80
[submodule "lib/wasi-libc"]
path = lib/wasi-libc
url = https://github.com/CraneStation/wasi-libc
[submodule "lib/picolibc"]
path = lib/picolibc
url = https://github.com/keith-packard/picolibc.git
+12
View File
@@ -10,6 +10,14 @@ This guide describes how to statically link TinyGo against LLVM, libclang and
lld so that the binary can be easily moved between systems. It also shows how to
build a release tarball that includes this binary and all necessary extra files.
**Note**: this documentation describes how to build a statically linked release
tarball. If you want to develop TinyGo, you will probably want to follow a
different guide:
* [Linux](https://tinygo.org/getting-started/linux/#source-install)
* [macOS](https://tinygo.org/getting-started/macos/#source-install)
* [Windows](https://tinygo.org/getting-started/windows/#source-install)
## Dependencies
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
@@ -84,6 +92,10 @@ Now that we have a working static build, it's time to make a release tarball:
make release
If you did not clone the repository with the `--recursive` option, you will get errors until you initialize the project submodules:
git submodule update --init
The release tarball is stored in build/release.tar.gz, and can be extracted with
the following command (for example in ~/lib):
+327
View File
@@ -1,3 +1,330 @@
0.15.0
---
* **command-line**
- add cached GOROOT to info subcommand
- embed git-hash in tinygo-dev executable
- implement tinygo targets to list usable targets
- use simpler file copy instead of file renaming to avoid issues on nrf52840 UF2 bootloaders
- use ToSlash() to specify program path
- support flashing esp32/esp8266 directly from tinygo
- when flashing call PortReset only on other than openocd
* **compiler**
- `compileopts`: add support for custom binary formats
- `compiler`: improve display of goroutine wrappers
- `interp`: don't panic in the Store method
- `interp`: replace some panics with error messages
- `interp`: show error line in first line of the traceback
- `loader`: be more robust when creating the cached GOROOT
- `loader`: rewrite/refactor much of the code to use go list directly
- `loader`: use ioutil.TempDir to create a temporary directory
- `stacksize`: deal with DW_CFA_advance_loc1
* **standard library**
- `runtime`: use waitForEvents when appropriate
* **wasm**
- `wasm`: Remove --no-threads from wasm-ld calls.
- `wasm`: update wasi-libc dependency
* **targets**
- `arduino-mega2560`: fix flashing on Windows
- `arm`: automatically determine stack sizes
- `arm64`: make dynamic loader structs and constants private
- `avr`: configure emulator in board files
- `cortexm`: fix stack size calculation with interrupts
- `flash`: add openocd settings to atsamd21 / atsamd51
- `flash`: add openocd settings to nrf5
- `microbit`: reelboard: flash using OpenOCD when needed
- `nintendoswitch`: Add dynamic loader for runtime loading PIE sections
- `nintendoswitch`: fix import cycle on dynamic_arm64.go
- `nintendoswitch`: Fix invalid memory read / write in print calls
- `nintendoswitch`: simplified assembly code
- `nintendoswitch`: support outputting .nro files directly
* **boards**
- `arduino-zero`: Adding support for the Arduino Zero (#1365)
- `atsamd2x`: fix BAUD value
- `atsamd5x`: fix BAUD value
- `bluepill`: Enable stm32's USART2 for the board and map it to UART1 tinygo's device
- `device/atsamd51x`: add all remaining bitfield values for PCHCTRLm Mapping
- `esp32`: add libgcc ROM functions to linker script
- `esp32`: add SPI support
- `esp32`: add support for basic GPIO
- `esp32`: add support for the Espressif ESP32 chip
- `esp32`: configure the I/O matrix for GPIO pins
- `esp32`: export machine.PortMask* for bitbanging implementations
- `esp8266`: add support for this chip
- `machine/atsamd51x,runtime/atsamd51x`: fixes needed for full support for all PWM pins. Also adds some useful constants to clarify peripheral clock usage
- `machine/itsybitsy-nrf52840`: add support for Adafruit Itsybitsy nrf52840 (#1243)
- `machine/stm32f4`: refactor common code and add new build tag stm32f4 (#1332)
- `nrf`: add SoftDevice support for the Circuit Playground Bluefruit
- `nrf`: call sd_app_evt_wait when the SoftDevice is enabled
- `nrf52840`: add build tags for SoftDevice support
- `nrf52840`: use higher priority for USB-CDC code
- `runtime/atsamd51x`: use PCHCTRL_GCLK_SERCOMX_SLOW for setting clocks on all SERCOM ports
- `stm32f405`: add basic UART handler
- `stm32f405`: add STM32F405 machine/runtime, and new board/target feather-stm32f405
* **build**
- `all`: run test binaries in the correct directory
- `build`: Fix arch release job
- `ci`: run `tinygo test` for known-working packages
- `ci`: set git-fetch-depth to 1
- `docker`: fix the problem with the wasm build (#1357)
- `Makefile`: check whether submodules have been downloaded in some common cases
* **docs**
- add ESP32, ESP8266, and Adafruit Feather STM32F405 to list of supported boards
0.14.1
---
* **command-line**
- support for Go 1.15
* **compiler**
- loader: work around Windows symlink limitation
0.14.0
---
* **command-line**
- fix `getDefaultPort()` on non-English Windows locales
- compileopts: improve error reporting of unsupported flags
- fix test subcommand
- use auto-retry to locate MSD for UF2 and HEX flashing
- fix touchSerialPortAt1200bps on Windows
- support package names with backslashes on Windows
* **compiler**
- fix a few crashes due to named types
- add support for atomic operations
- move the channel blocked list onto the stack
- fix -gc=none
- fix named string to `[]byte` slice conversion
- implement func value and builtin defers
- add proper parameter names to runtime.initAll, to fix a panic
- builder: fix picolibc include path
- builder: use newer version of gohex
- builder: try to determine stack size information at compile time
- builder: remove -opt=0
- interp: fix sync/atomic.Value load/store methods
- loader: add Go module support
- transform: fix debug information in func lowering pass
- transform: do not special-case zero or one implementations of a method call
- transform: introduce check for method calls on nil interfaces
- transform: gc: track 0-index GEPs to fix miscompilation
* **cgo**
- Add LDFlags support
* **standard library**
- extend stdlib to allow import of more packages
- replace master/slave terminology with appropriate alternatives (MOSI->SDO
etc)
- `internal/bytealg`: reimplement bytealg in pure Go
- `internal/task`: fix nil panic in (*internal/task.Stack).Pop
- `os`: add Args and stub it with mock data
- `os`: implement virtual filesystem support
- `reflect`: add Cap and Len support for map and chan
- `runtime`: fix return address in scheduler on RISC-V
- `runtime`: avoid recursion in printuint64 function
- `runtime`: replace ReadRegister with AsmFull inline assembly
- `runtime`: fix compilation errors when using gc.extalloc
- `runtime`: add cap and len support for chans
- `runtime`: refactor time handling (improving accuracy)
- `runtime`: make channels work in interrupts
- `runtime/interrupt`: add cross-chip disable/restore interrupt support
- `sync`: implement `sync.Cond`
- `sync`: add WaitGroup
* **targets**
- `arm`: allow nesting in DisableInterrupts and EnableInterrupts
- `arm`: make FPU configuraton consistent
- `arm`: do not mask fault handlers in critical sections
- `atmega2560`: fix pin mapping for pins D2, D5 and the L port
- `atsamd`: return an error when an incorrect PWM pin is used
- `atsamd`: add support for pin change interrupts
- `atsamd`: add DAC support
- `atsamd21`: add more ADC pins
- `atsamd51`: fix ROM / RAM size on atsamd51j20
- `atsamd51`: add more pins
- `atsamd51`: add more ADC pins
- `atsamd51`: add pin change interrupt settings
- `atsamd51`: extend pinPadMapping
- `arduino-nano33`: use (U)SB flag to ensure that device can be found when
not on default port
- `arduino-nano33`: remove (d)ebug flag to reduce console noise when flashing
- `avr`: use standard pin numbering
- `avr`: unify GPIO pin/port code
- `avr`: add support for PinInputPullup
- `avr`: work around codegen bug in LLVM 10
- `avr`: fix target triple
- `fe310`: remove extra println left in by mistake
- `feather-nrf52840`: add support for the Feather nRF52840
- `maixbit`: add board definition and dummy runtime
- `nintendoswitch`: Add experimental Nintendo Switch support without CRT
- `nrf`: expose the RAM base address
- `nrf`: add support for pin change interrupts
- `nrf`: add microbit-s110v8 target
- `nrf`: fix bug in SPI.Tx
- `nrf`: support debugging the PCA10056
- `pygamer`: add Adafruit PyGamer suport
- `riscv`: fix interrupt configuration bug
- `riscv`: disable linker relaxations during gp init
- `stm32f4disco`: add new target with ST-Link v2.1 debugger
- `teensy36`: add Teensy 3.6 support
- `wasm`: fix event handling
- `wasm`: add --no-demangle linker option
- `wioterminal`: add support for the Seeed Wio Terminal
- `xiao`: add support for the Seeed XIAO
0.13.1
---
* **standard library**
- `runtime`: do not put scheduler and GC code in the same section
- `runtime`: copy stack scan assembly for GBA
* **boards**
- `gameboy-advance`: always use ARM mode instead of Thumb mode
0.13.0
---
* **command line**
- use `gdb-multiarch` for debugging Cortex-M chips
- support `tinygo run` with simavr
- support LLVM 10
- support Go 1.14
- retry 3 times when attempting to do a 1200-baud reset
* **compiler**
- mark the `abort` function as noreturn
- fix deferred calls to exported functions
- add debug info for local variables
- check for channel size limit
- refactor coroutine lowering
- add `dereferenceable_or_null` attribute to pointer parameters
- do not perform nil checking when indexing slices and on `unsafe.Pointer`
- remove `runtime.isnil` hack
- use LLVM builtins for runtime `memcpy`/`memmove`/`memzero` functions
- implement spec-compliant shifts on negative/overflow
- support anonymous type asserts
- track pointer result of string concatenation for GC
- track PHI nodes for GC
- add debug info to goroutine start wrappers
- optimize comparing interface values against nil
- fix miscompilation when deferring an interface call
- builder: include picolibc for most baremetal targets
- builder: run tools (clang, lld) as separate processes
- builder: use `-fshort-enums` consistently
- interp: add support for constant type asserts
- interp: better support for interface operations
- interp: include backtrace with error
- transform: do not track const globals for GC
- transform: replace panics with source locations
- transform: fix error in interface lowering pass
- transform: make coroutine lowering deterministic
- transform: fix miscompilation in func lowering
* **cgo**
- make `-I` and `-L` paths absolute
* **standard library**
- `machine`: set the USB VID and PID to the manufacturer values
- `machine`: correct USB CDC composite descriptors
- `machine`: move `errors.New` calls to globals
- `runtime`: support operations on nil maps
- `runtime`: fix copy builtin return value on AVR
- `runtime`: refactor goroutines
- `runtime`: support `-scheduler=none` on most platforms
- `runtime`: run package initialization in the main goroutine
- `runtime`: export `malloc` / `free` for use from C
- `runtime`: add garbage collector that uses an external allocator
- `runtime`: scan callee-saved registers while marking the stack
- `runtime`: remove recursion from conservative GC
- `runtime`: fix blocking select on nil channel
- `runtime/volatile`: include `ReplaceBits` method
- `sync`: implement trivial `sync.Map`
* **targets**
- `arm`: use `-fomit-frame-pointer`
- `atmega1284`: support this chip for testing purposes
- `atsamd51`: make QSPI available on all boards
- `atsamd51`: add support for ADC1
- `atsamd51`: use new interrupt registration in UART code
- `attiny`: clean up pin definitions
- `avr`: use the correct RAM start address
- `avr`: pass the correct `-mmcu` flag to the linker
- `avr`: add support for tasks scheduler (disabled by default)
- `avr`: fix linker problem with overlapping program/data areas
- `nrf`: fix typo in pin configuration options
- `nrf`: add lib/nrfx/mdk to include dirs
- `nrf52840`: implement USB-CDC
- `riscv`: implement VirtIO target and add RISC-V integration test
- `riscv`: add I2C support for the HiFive1 rev B board
- `stm32`: refactor GPIO pin handling
- `stm32`: refactor UART code
- `stm32f4`: add SPI
- `wasm`: support Go 1.14 (breaking previous versions)
- `wasm`: support `syscall/js.CopyBytesToJS`
- `wasm`: sync polyfills from Go 1.14.
* **boards**
- `arduino-mega2560`: add the Arduino Mega 2560
- `clue-alpha`: add the Adafruit CLUE Alpha
- `gameboy-advance`: enable debugging with GDB
- `particle-argon`: add the Particle Argon board
- `particle-boron`: add the Particle Boron board
- `particle-xenon`: add the Particle Xenon board
- `reelboard`: add `reelboard-s140v7` SoftDevice target
0.12.0
---
* **command line**
- add initial FreeBSD support
- remove getting a serial port in gdb subcommand
- add support for debugging through JLinkGDBServer
- fix CGo when cross compiling
- remove default port check for Digispark as micronucleus communicates directly using HID
- differentiate between various serial/USB error messages
* **builder**
- improve detection of Clang headers
* **compiler**
- fix assertion on empty interface
- don't crash when encountering `types.Invalid`
- revise defer to use heap allocations when running a variable number of times
- improve error messages for failed imports
- improve "function redeclared" error
- add globaldce pass to start of optimization pipeline
- add support for debugging globals
- implement RISC-V CSR operations as intrinsics
- add support for CGO_ENABLED environment variable
- do not emit debug info for extern globals (bugfix)
- add support for interrupts
- implement maps for arbitrary keys
- interp: error location for "unknown GEP" error
- wasm-abi: create temporary allocas in the entry block
* **cgo**
- add support for symbols in `#define`
- fix a bug in number tokenization
* **standard library**
- `machine`: avoid bytes package in USB logic
- `runtime`: fix external address declarations
- `runtime`: provide implementation for `internal/bytealg.IndexByte`
* **targets**
- `atsamd51`: fix volatile usage
- `atsamd51`: fix ADC, updating to 12-bits precision
- `atsamd51`: refactor SPI pin configuration to only look at pin numbers
- `atsamd51`: switch UART to use new pin configuration
- `atsamd51`: fix obvious bug in I2C code
- `atsamd51`: use only the necessary UART interrupts
- `atsamd51`: refactor I2C pin handling to auto-detect pin mode
- `avr`: use a garbage collector
- `fe310`: use CLINT peripheral for timekeeping
- `fe310`: add support for PLIC interrupts
- `fe310`: implement UART receive interrupts
- `riscv`: support sleeping in QEMU
- `riscv`: add bare-bones interrupt support
- `riscv`: print exception PC and code
- `wasm`: implement memcpy and memset
- `wasm`: include wasi-libc
- `wasm`: use wasi ABI for basic startup/stdout
* **boards**
- `arduino`: make avrdude command line compatible with Windows
- `arduino-nano`: add this board
- `arduino-nano33`: fix UART1 and UART2
- `circuitplay-bluefruit`: add this board
- `digispark`: add clock speed and pin mappings
- `gameboy-advance`: include compiler-rt in build
- `gameboy-advance`: implement interrupt handler
- `hifive1b`: add support for gdb subcommand
- `pyportal`: add this board
- `pyportal`: remove manual SPI pin mapping as now handled by default
0.11.0
---
* **command line**
+1 -1
View File
@@ -32,7 +32,7 @@ Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't h
## How to use our Github repository
The `master` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
The `release` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
Here is how to contribute back some code or documentation:
+1
View File
@@ -15,3 +15,4 @@ Ayke van Laethem <aykevanlaethem@gmail.com>
Daniel Esteban <conejo@conejo.me>
Loon, LLC.
Ron Evans <ron@hybridgroup.com>
Nia Weiss <niaow1234@gmail.com>
+12 -9
View File
@@ -1,10 +1,10 @@
# TinyGo base stage installs Go 1.13, LLVM 9 and the TinyGo compiler itself.
FROM golang:1.13 AS tinygo-base
# TinyGo base stage installs Go 1.14, LLVM 10 and the TinyGo compiler itself.
FROM golang:1.14 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-9 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-9-dev libclang-9-dev git
apt-get install -y llvm-10-dev libclang-10-dev lld-10 git
COPY . /tinygo
@@ -12,8 +12,11 @@ COPY . /tinygo
# after copying the tinygo directory in the previous step.
RUN cd /tinygo/ && \
rm -rf ./lib/* && \
git submodule sync && \
git submodule update --init --recursive --force
COPY ./lib/picolibc-include/* /tinygo/lib/picolibc-include/
RUN cd /tinygo/ && \
go install /tinygo/
@@ -24,10 +27,10 @@ COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y libllvm9 lld-9
apt-get install -y make clang-10 libllvm10 lld-10 && \
make wasi-libc
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
FROM tinygo-base AS tinygo-avr
@@ -58,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-9 && \
apt-get install -y apt-utils make clang-10 && \
make gen-device-nrf && make gen-device-stm32
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
@@ -70,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-9 binutils-avr gcc-avr avr-libc && \
apt-get install -y apt-utils make clang-10 binutils-avr gcc-avr avr-libc && \
make gen-device
CMD ["tinygo"]
+2 -2
View File
@@ -1,7 +1,7 @@
Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
Copyright (c) 2018-2020 TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library.
Copyright (c) 2009-2019 The Go Authors. All rights reserved.
Copyright (c) 2009-2020 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.
+166 -23
View File
@@ -4,8 +4,30 @@ all: tinygo
# Default build and source directories, as created by `make llvm-build`.
LLVM_BUILDDIR ?= llvm-build
CLANG_SRC ?= llvm-project/clang
LLD_SRC ?= llvm-project/lld
LLVM_PROJECTDIR ?= llvm-project
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
# Go binary and GOROOT to select
GO ?= go
@@ -18,7 +40,7 @@ MD5SUM = md5sum
TINYGO ?= tinygo
# Use CCACHE for LLVM if possible
ifneq (, $(shell which ccache))
ifneq (, $(shell command -v ccache 2> /dev/null))
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=ON'
endif
@@ -29,9 +51,9 @@ 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-avr
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
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
ifeq ($(OS),Windows_NT)
EXE = .exe
@@ -81,8 +103,8 @@ LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -
# 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_CXXFLAGS=-std=c++11
CGO_LDFLAGS+=$(LIBCLANG_PATH) -std=c++11 -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_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)
endif
@@ -96,9 +118,10 @@ fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
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-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
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
@@ -107,53 +130,79 @@ gen-device-avr:
build/gen-device-svd: ./tools/gen-device-svd/*.go
$(GO) build -o $@ ./tools/gen-device-svd/
gen-device-esp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
GO111MODULE=off $(GO) fmt ./src/device/esp
gen-device-nrf: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
GO111MODULE=off $(GO) fmt ./src/device/nrf
gen-device-nxp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/NXP lib/cmsis-svd/data/NXP/ src/device/nxp/
GO111MODULE=off $(GO) fmt ./src/device/nxp
gen-device-sam: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/
GO111MODULE=off $(GO) fmt ./src/device/sam
gen-device-sifive: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
GO111MODULE=off $(GO) fmt ./src/device/sifive
gen-device-kendryte: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Kendryte-Community -interrupts=software lib/cmsis-svd/data/Kendryte-Community/ src/device/kendryte/
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/
GO111MODULE=off $(GO) fmt ./src/device/stm32
# Get LLVM sources.
llvm-project/README.md:
git clone -b release/9.x https://github.com/llvm/llvm-project
llvm-source: llvm-project/README.md
$(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
# Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=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_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
# Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
cd $(LLVM_BUILDDIR); ninja
# Build wasi-libc sysroot
.PHONY: wasi-libc
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
cd lib/wasi-libc && make -j4 WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
# Build the Go compiler.
tinygo:
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
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:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform .
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 .
# Test known-working standard library packages.
# TODO: do this in one command, parallelize, and only show failing tests (no
# implied -v flag).
.PHONY: tinygo-test
tinygo-test:
cd tests/tinygotest && tinygo test
$(TINYGO) test container/list
$(TINYGO) test container/ring
$(TINYGO) test text/scanner
.PHONY: smoketest
smoketest:
$(TINYGO) version
# test all examples
# test all examples (except pwm)
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
@@ -174,7 +223,7 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pwm
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@$(MD5SUM) test.hex
@@ -185,6 +234,8 @@ 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
@@ -200,6 +251,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
@$(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=nrf52840-mdk examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
@@ -226,8 +279,16 @@ smoketest:
@$(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
@$(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=itsybitsy-m4 examples/blinky1
@@ -238,6 +299,14 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=metro-m4-airlift examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pyportal examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-argon examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-boron examples/blinky1
@$(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
@@ -246,23 +315,78 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pygamer examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pyportal examples/dac
@$(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=itsybitsy-nrf52840 examples/blinky1
@$(MD5SUM) test.hex
# test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/pwm
@$(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
@$(MD5SUM) test.hex
ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/pwm
@$(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-nano examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
@$(MD5SUM) test.hex
endif
ifneq ($(XTENSA), 0)
$(TINYGO) build -size short -o test.bin -target=esp32-wroom-32 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=maixbit examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
# test various compiler flags
$(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
@$(MD5SUM) test.nro
release: tinygo gen-device
wasmtest:
$(GO) test ./tests/wasm
build/release: tinygo gen-device wasi-libc
@mkdir -p build/release/tinygo/bin
@mkdir -p build/release/tinygo/lib/clang/include
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
@mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
@mkdir -p build/release/tinygo/lib/wasi-libc
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
@@ -275,9 +399,28 @@ release: tinygo gen-device
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
@cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc-include build/release/tinygo/lib
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
@cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets
./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc
release: build/release
tar -czf build/release.tar.gz -C build/release tinygo
deb: build/release
@mkdir -p build/release-deb/usr/local/bin
@mkdir -p build/release-deb/usr/local/lib
cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo
ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo
fpm -f -s dir -t deb -n tinygo -v $(shell grep "const Version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
+24 -2
View File
@@ -43,27 +43,47 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 22 microcontroller boards are currently supported:
The following 44 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 Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [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 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 Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
* [BBC micro:bit](https://microbit.org/)
* [Digispark](http://digistump.com/products/1)
* [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/)
* [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)
* [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)
* [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)
@@ -128,4 +148,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/9.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/10.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.
+32 -18
View File
@@ -1,7 +1,7 @@
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
# be built anyway.
trigger:
- master
- release
- dev
jobs:
@@ -10,21 +10,29 @@ jobs:
pool:
vmImage: 'VS2017-Win2016'
steps:
- task: GoTool@0
inputs:
version: '1.15'
- checkout: self
- task: CacheBeta@0
fetchDepth: 1
- task: Cache@2
displayName: Cache LLVM source
inputs:
key: llvm-source-9-windows-v0
key: llvm-source-10-windows-v1
path: llvm-project
- task: Bash@3
displayName: Download LLVM source
inputs:
targetType: inline
script: make llvm-source
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
- task: CacheBeta@0
displayName: Cache LLVM build
inputs:
key: llvm-build-9-windows-v0
key: llvm-build-10-windows-v1
path: llvm-build
- task: Bash@3
displayName: Build LLVM
@@ -34,23 +42,29 @@ jobs:
if [ ! -f llvm-build/lib/liblldELF.a ]
then
choco install ninja
# LLVM 9 cannot be built with MinGW 8.
# For details: https://reviews.llvm.org/D70266
choco uninstall mingw
choco install mingw --version=7.3.0
make llvm-build
fi
- task: Bash@3
displayName: Install QEMU
inputs:
targetType: inline
script: choco install qemu
script: choco install qemu --version=2020.06.12
- task: CacheBeta@0
displayName: Cache wasi-libc sysroot
inputs:
key: wasi-libc-sysroot-v3
path: lib/wasi-libc/sysroot
- task: Bash@3
displayName: Build wasi-libc
inputs:
targetType: inline
script: PATH=/usr/bin:$PATH make wasi-libc
- task: Bash@3
displayName: Test TinyGo
inputs:
targetType: inline
script: |
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make test
- task: Bash@3
@@ -58,17 +72,17 @@ jobs:
inputs:
targetType: inline
script: |
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make release -j4
- publish: $(System.DefaultWorkingDirectory)/build/release.tar.gz
displayName: Publish tarball as artifact
artifact: tinygo.windows-amd64.tar.gz
make build/release -j4
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
displayName: Publish zip as artifact
artifact: tinygo
- task: Bash@3
displayName: Smoke tests
inputs:
targetType: inline
script: |
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make smoketest TINYGO=build/tinygo AVR=0
make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
+375 -48
View File
@@ -4,11 +4,14 @@
package builder
import (
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -16,43 +19,59 @@ import (
"github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/stacksize"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
// BuildResult is the output of a build. This includes the binary itself and
// some other metadata that is obtained while building the binary.
type BuildResult struct {
// A path to the output binary. It will be removed after Build returns, so
// if it should be kept it must be copied or moved away.
Binary string
// The directory of the main package. This is useful for testing as the test
// binary must be run in the directory of the tested package.
MainDir string
}
// Build performs a single package to executable Go build. It takes in a package
// name, an output path, and set of compile options and from that it manages the
// whole compilation process.
//
// The error value may be of type *MultiError. Callers will likely want to check
// for this case and print such errors individually.
func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error {
c, err := compiler.NewCompiler(pkgName, config)
func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error {
// Compile Go code to IR.
machine, err := compiler.NewTargetMachine(config)
if err != nil {
return err
}
// Compile Go code to IR.
errs := c.Compile(pkgName)
if len(errs) != 0 {
buildOutput, errs := compiler.Compile(pkgName, machine, config)
if errs != nil {
return newMultiError(errs)
}
mod := buildOutput.Mod
if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:")
fmt.Println(c.IR())
fmt.Println(mod.String())
}
if err := c.Verify(); err != nil {
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after IR construction")
}
err = interp.Run(c.Module(), config.DumpSSA())
err = interp.Run(mod, config.DumpSSA())
if err != nil {
return err
}
if err := c.Verify(); err != nil {
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after interpreting runtime.initAll")
}
if config.GOOS() != "darwin" {
c.ApplyFunctionSections() // -ffunction-sections
transform.ApplyFunctionSections(mod) // -ffunction-sections
}
// Browsers cannot handle external functions that have type i64 because it
@@ -61,7 +80,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// stack-allocated values.
// Use -wasm-abi=generic to disable this behaviour.
if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") {
err := c.ExternalInt64AsPtr()
err := transform.ExternalInt64AsPtr(mod)
if err != nil {
return err
}
@@ -71,23 +90,30 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// exactly.
errs = nil
switch config.Options.Opt {
case "none:", "0":
errs = c.Optimize(0, 0, 0) // -O0
/*
Currently, turning optimizations off causes compile failures.
We rely on the optimizer removing some dead symbols.
Avoid providing an option that does not work right now.
In the future once everything has been fixed we can re-enable this.
case "none", "0":
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
*/
case "1":
errs = c.Optimize(1, 0, 0) // -O1
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
case "2":
errs = c.Optimize(2, 0, 225) // -O2
errs = transform.Optimize(mod, config, 2, 0, 225) // -O2
case "s":
errs = c.Optimize(2, 1, 225) // -Os
errs = transform.Optimize(mod, config, 2, 1, 225) // -Os
case "z":
errs = c.Optimize(2, 2, 5) // -Oz, default
errs = transform.Optimize(mod, config, 2, 2, 5) // -Oz, default
default:
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
}
if len(errs) > 0 {
return newMultiError(errs)
}
if err := c.Verify(); err != nil {
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification failure after LLVM optimization passes")
}
@@ -97,21 +123,34 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// pointers are flash and which are in RAM so that pointers can have a
// correct address space parameter (address space 1 is for flash).
if strings.HasPrefix(config.Triple(), "avr") {
c.NonConstGlobals()
if err := c.Verify(); err != nil {
transform.NonConstGlobals(mod)
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after making all globals non-constant on AVR")
}
}
// Make sure stack sizes are loaded from a separate section so they can be
// modified after linking.
var stackSizeLoads []string
if config.AutomaticStackSize() {
stackSizeLoads = transform.CreateStackSizeLoads(mod, config)
}
// Generate output.
outext := filepath.Ext(outpath)
switch outext {
case ".o":
return c.EmitObject(outpath)
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666)
case ".bc":
return c.EmitBitcode(outpath)
data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes()
return ioutil.WriteFile(outpath, data, 0666)
case ".ll":
return c.EmitText(outpath)
data := []byte(mod.String())
return ioutil.WriteFile(outpath, data, 0666)
default:
// Act as a compiler driver.
@@ -124,29 +163,39 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// Write the object file.
objfile := filepath.Join(dir, "main.o")
err = c.EmitObject(objfile)
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
// Load builtins library from the cache, possibly compiling it on the
// fly.
var librt string
if config.Target.RTLib == "compiler-rt" {
librt, err = loadBuiltins(config.Triple())
if err != nil {
return err
}
err = ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
if err != nil {
return err
}
// Prepare link command.
executable := filepath.Join(dir, "main")
tmppath := executable // final file
ldflags := append(config.LDFlags(), "-o", executable, objfile)
// Load builtins library from the cache, possibly compiling it on the
// fly.
if config.Target.RTLib == "compiler-rt" {
librt, err := CompilerRT.Load(config.Triple())
if err != nil {
return err
}
ldflags = append(ldflags, librt)
}
// Add libc.
if config.Target.Libc == "picolibc" {
libc, err := Picolibc.Load(config.Triple())
if err != nil {
return err
}
ldflags = append(ldflags, libc)
}
// Compile extra files.
root := goenv.Get("TINYGOROOT")
for i, path := range config.ExtraFiles() {
@@ -160,16 +209,17 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
}
// Compile C files in packages.
for i, pkg := range c.Packages() {
for _, file := range pkg.CFiles {
path := filepath.Join(pkg.Package.Dir, file)
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, path)...)
if err != nil {
return &commandError{"failed to build", path, err}
}
ldflags = append(ldflags, outpath)
for i, file := range buildOutput.ExtraFiles {
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+filepath.Base(file)+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
if err != nil {
return &commandError{"failed to build", file, err}
}
ldflags = append(ldflags, outpath)
}
if len(buildOutput.ExtraLDFlags) > 0 {
ldflags = append(ldflags, buildOutput.ExtraLDFlags...)
}
// Link the object files together.
@@ -178,6 +228,26 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
return &commandError{"failed to link", executable, err}
}
var calculatedStacks []string
var stackSizes map[string]functionStackSize
if config.Options.PrintStacks || config.AutomaticStackSize() {
// Try to determine stack sizes at compile time.
// Don't do this by default as it usually doesn't work on
// unsupported architectures.
calculatedStacks, stackSizes, err = determineStackSizes(mod, executable)
if err != nil {
return err
}
}
if config.AutomaticStackSize() {
// Modify the .tinygo_stacksizes section that contains a stack size
// for each goroutine.
err = modifyStackSizes(executable, stackSizeLoads, stackSizes)
if err != nil {
return fmt.Errorf("could not modify stack sizes: %w", err)
}
}
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable)
if err != nil {
@@ -197,21 +267,278 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
}
}
// Print goroutine stack sizes, as far as possible.
if config.Options.PrintStacks {
printStacks(calculatedStacks, stackSizes)
}
// Get an Intel .hex file or .bin file from the .elf file.
if outext == ".hex" || outext == ".bin" || outext == ".gba" {
outputBinaryFormat := config.BinaryFormat(outext)
switch outputBinaryFormat {
case "elf":
// do nothing, file is already in ELF format
case "hex", "bin":
// Extract raw binary, either encoding it as a hex file or as a raw
// firmware file.
tmppath = filepath.Join(dir, "main"+outext)
err := objcopy(executable, tmppath)
err := objcopy(executable, tmppath, outputBinaryFormat)
if err != nil {
return err
}
} else if outext == ".uf2" {
case "uf2":
// Get UF2 from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
err := convertELFFileToUF2File(executable, tmppath)
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
if err != nil {
return err
}
case "esp32", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
tmppath = filepath.Join(dir, "main"+outext)
err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
}
return action(BuildResult{
Binary: tmppath,
MainDir: buildOutput.MainDir,
})
}
}
// functionStackSizes keeps stack size information about a single function
// (usually a goroutine).
type functionStackSize struct {
humanName string
stackSize uint64
stackSizeType stacksize.SizeType
missingStackSize *stacksize.CallNode
}
// determineStackSizes tries to determine the stack sizes of all started
// goroutines and of the reset vector. The LLVM module is necessary to find
// functions that call a function pointer.
func determineStackSizes(mod llvm.Module, executable string) ([]string, map[string]functionStackSize, error) {
var callsIndirectFunction []string
gowrappers := []string{}
gowrapperNames := make(map[string]string)
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
// Determine which functions call a function pointer.
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst.IsACallInst().IsNil() {
continue
}
if callee := inst.CalledValue(); callee.IsAFunction().IsNil() && callee.IsAInlineAsm().IsNil() {
callsIndirectFunction = append(callsIndirectFunction, fn.Name())
}
}
}
// Get a list of "go wrappers", small wrapper functions that decode
// parameters when starting a new goroutine.
attr := fn.GetStringAttributeAtIndex(-1, "tinygo-gowrapper")
if !attr.IsNil() {
gowrappers = append(gowrappers, fn.Name())
gowrapperNames[fn.Name()] = attr.GetStringValue()
}
}
sort.Strings(gowrappers)
// Load the ELF binary.
f, err := elf.Open(executable)
if err != nil {
return nil, nil, fmt.Errorf("could not load executable for stack size analysis: %w", err)
}
defer f.Close()
// Determine the frame size of each function (if available) and the callgraph.
functions, err := stacksize.CallGraph(f, callsIndirectFunction)
if err != nil {
return nil, nil, fmt.Errorf("could not parse executable for stack size analysis: %w", err)
}
// Goroutines need to be started and finished and take up some stack space
// that way. This can be measured by measuing the stack size of
// tinygo_startTask.
if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 {
return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs)
}
baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize()
sizes := make(map[string]functionStackSize)
// Add the reset handler function, for convenience. The reset handler runs
// startup code and the scheduler. The listed stack size is not the full
// stack size: interrupts are not counted.
var resetFunction string
switch f.Machine {
case elf.EM_ARM:
// Note: all interrupts happen on this stack so the real size is bigger.
resetFunction = "Reset_Handler"
}
if resetFunction != "" {
funcs := functions[resetFunction]
if len(funcs) != 1 {
return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", resetFunction, len(funcs))
}
stackSize, stackSizeType, missingStackSize := funcs[0].StackSize()
sizes[resetFunction] = functionStackSize{
stackSize: stackSize,
stackSizeType: stackSizeType,
missingStackSize: missingStackSize,
humanName: resetFunction,
}
}
// Add all goroutine wrapper functions.
for _, name := range gowrappers {
funcs := functions[name]
if len(funcs) != 1 {
return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", name, len(funcs))
}
humanName := gowrapperNames[name]
if humanName == "" {
humanName = name // fallback
}
stackSize, stackSizeType, missingStackSize := funcs[0].StackSize()
if baseStackSizeType != stacksize.Bounded {
// It was not possible to determine the stack size at compile time
// because tinygo_startTask does not have a fixed stack size. This
// can happen when using -opt=1.
stackSizeType = baseStackSizeType
missingStackSize = baseStackSizeFailedAt
} else if stackSize < baseStackSize {
// This goroutine has a very small stack, but still needs to fit all
// registers to start and suspend the goroutine. Otherwise a stack
// overflow will occur even before the goroutine is started.
stackSize = baseStackSize
}
sizes[name] = functionStackSize{
stackSize: stackSize,
stackSizeType: stackSizeType,
missingStackSize: missingStackSize,
humanName: humanName,
}
}
if resetFunction != "" {
return append([]string{resetFunction}, gowrappers...), sizes, nil
}
return gowrappers, sizes, nil
}
// modifyStackSizes modifies the .tinygo_stacksizes section with the updated
// stack size information. Before this modification, all stack sizes in the
// section assume the default stack size (which is relatively big).
func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error {
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
if err != nil {
return err
}
defer fp.Close()
elfFile, err := elf.NewFile(fp)
if err != nil {
return err
}
section := elfFile.Section(".tinygo_stacksizes")
if section == nil {
return errors.New("could not find .tinygo_stacksizes section")
}
if section.Size != section.FileSize {
// Sanity check.
return fmt.Errorf("expected .tinygo_stacksizes to have identical size and file size, got %d and %d", section.Size, section.FileSize)
}
// Read all goroutine stack sizes.
data := make([]byte, section.Size)
_, err = fp.ReadAt(data, int64(section.Offset))
if err != nil {
return err
}
if len(stackSizeLoads)*4 != len(data) {
// Note: while AVR should use 2 byte stack sizes, even 64-bit platforms
// should probably stick to 4 byte stack sizes as a larger than 4GB
// stack doesn't make much sense.
return errors.New("expected 4 byte stack sizes")
}
// Modify goroutine stack sizes with a compile-time known worst case stack
// size.
for i, name := range stackSizeLoads {
fn, ok := stackSizes[name]
if !ok {
return fmt.Errorf("could not find symbol %s in ELF file", name)
}
if fn.stackSizeType == stacksize.Bounded {
stackSize := uint32(fn.stackSize)
// Adding 4 for the stack canary. Even though the size may be
// automatically determined, stack overflow checking is still
// important as the stack size cannot be determined for all
// goroutines.
stackSize += 4
// Add stack size used by interrupts.
switch elfFile.Machine {
case elf.EM_ARM:
// On Cortex-M (assumed here), this stack size is 8 words or 32
// bytes. This is only to store the registers that the interrupt
// may modify, the interrupt will switch to the interrupt stack
// (MSP).
// Some background:
// https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching
stackSize += 32
}
// Finally write the stack size to the binary.
binary.LittleEndian.PutUint32(data[i*4:], stackSize)
}
}
// Write back the modified stack sizes.
_, err = fp.WriteAt(data, int64(section.Offset))
if err != nil {
return err
}
return nil
}
// printStacks prints the maximum stack depth for functions that are started as
// goroutines. Stack sizes cannot always be determined statically, in particular
// recursive functions and functions that call interface methods or function
// pointers may have an unknown stack depth (depending on what the optimizer
// manages to optimize away).
//
// It might print something like the following:
//
// function stack usage (in bytes)
// Reset_Handler 316
// examples/blinky2.led1 92
// runtime.run$1 300
func printStacks(calculatedStacks []string, stackSizes map[string]functionStackSize) {
// Print the sizes of all stacks.
fmt.Printf("%-32s %s\n", "function", "stack usage (in bytes)")
for _, name := range calculatedStacks {
fn := stackSizes[name]
switch fn.stackSizeType {
case stacksize.Bounded:
fmt.Printf("%-32s %d\n", fn.humanName, fn.stackSize)
case stacksize.Unknown:
fmt.Printf("%-32s unknown, %s does not have stack frame information\n", fn.humanName, fn.missingStackSize)
case stacksize.Recursive:
fmt.Printf("%-32s recursive, %s may call itself\n", fn.humanName, fn.missingStackSize)
case stacksize.IndirectCall:
fmt.Printf("%-32s unknown, %s calls a function pointer\n", fn.humanName, fn.missingStackSize)
}
return action(tmppath)
}
}
+15 -105
View File
@@ -1,12 +1,7 @@
package builder
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
// These are the GENERIC_SOURCES according to CMakeList.txt.
@@ -156,105 +151,20 @@ var aeabiBuiltins = []string{
"arm/aeabi_uldivmod.S",
}
func builtinFiles(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if strings.HasPrefix(target, "arm") {
builtins = append(builtins, aeabiBuiltins...)
}
return builtins
}
// builtinsDir returns the directory where the sources for compiler-rt are kept.
func builtinsDir() string {
return filepath.Join(goenv.Get("TINYGOROOT"), "lib", "compiler-rt", "lib", "builtins")
}
// Get the builtins archive, possibly generating it as needed.
func loadBuiltins(target string) (path string, err error) {
// Try to load a precompiled compiler-rt library.
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, "compiler-rt.a")
if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled compiler-rt for this OS/architecture. Return the
// path directly.
return precompiledPath, nil
}
outfile := "librt-" + target + ".a"
builtinsDir := builtinsDir()
builtins := builtinFiles(target)
srcs := make([]string, len(builtins))
for i, name := range builtins {
srcs[i] = filepath.Join(builtinsDir, name)
}
if path, err := cacheLoad(outfile, commands["clang"][0], srcs); path != "" || err != nil {
return path, err
}
var cachepath string
err = CompileBuiltins(target, func(path string) error {
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
cachepath = path
return err
})
return cachepath, err
}
// CompileBuiltins compiles builtins from compiler-rt into a static library.
// When it succeeds, it will call the callback with the resulting path. The path
// will be removed after callback returns. If callback returns an error, this is
// passed through to the return value of this function.
func CompileBuiltins(target string, callback func(path string) error) error {
builtinsDir := builtinsDir()
builtins := builtinFiles(target)
srcs := make([]string, len(builtins))
for i, name := range builtins {
srcs[i] = filepath.Join(builtinsDir, name)
}
dirPrefix := "tinygo-builtins"
remapDir := filepath.Join(os.TempDir(), dirPrefix)
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
if err != nil {
return err
}
defer os.RemoveAll(dir)
// Compile all builtins.
// TODO: use builtins optimized for a given target if available.
objs := make([]string, 0, len(builtins))
for _, name := range builtins {
objname := name
if strings.LastIndexByte(objname, '/') >= 0 {
objname = objname[strings.LastIndexByte(objname, '/'):]
// CompilerRT is a library with symbols required by programs compiled with LLVM.
// These symbols are for operations that cannot be emitted with a single
// instruction or a short sequence of instructions for that target.
//
// For more information, see: https://compiler-rt.llvm.org/
var CompilerRT = Library{
name: "compiler-rt",
cflags: func() []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} },
sourceDir: "lib/compiler-rt/lib/builtins",
sources: func(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
builtins = append(builtins, aeabiBuiltins...)
}
objpath := filepath.Join(dir, objname+".o")
objs = append(objs, objpath)
srcpath := filepath.Join(builtinsDir, name)
// 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 := []string{"-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target=" + target, "-fdebug-prefix-map=" + dir + "=" + remapDir}
if strings.HasPrefix(target, "riscv32-") {
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
}
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
}
// Put all the object files in a single archive. This archive file will be
// used to statically link compiler-rt.
arpath := filepath.Join(dir, "librt.a")
err = makeArchive(arpath, objs)
if err != nil {
return err
}
// Give the caller the resulting file. The callback must copy the file,
// because after it returns the temporary directory will be removed.
return callback(arpath)
return builtins
},
}
+24 -18
View File
@@ -48,6 +48,7 @@
#include "llvm/Support/Host.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/TargetRegistry.h"
@@ -70,12 +71,12 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
bool Success = true;
// Parse the arguments.
std::unique_ptr<OptTable> OptTbl(createDriverOptTable());
const OptTable &OptTbl = getDriverOptTable();
const unsigned IncludedFlagsBitmask = options::CC1AsOption;
unsigned MissingArgIndex, MissingArgCount;
InputArgList Args = OptTbl->ParseArgs(Argv, MissingArgIndex, MissingArgCount,
IncludedFlagsBitmask);
InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount,
IncludedFlagsBitmask);
// Check for missing argument error.
if (MissingArgCount) {
@@ -88,7 +89,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
auto ArgString = A->getAsString(Args);
std::string Nearest;
if (OptTbl->findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
if (OptTbl.findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
Diags.Report(diag::err_drv_unknown_argument) << ArgString;
else
Diags.Report(diag::err_drv_unknown_argument_with_suggestion)
@@ -181,6 +182,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.RelaxAll = Args.hasArg(OPT_mrelax_all);
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.IncrementalLinkerCompatible =
@@ -208,8 +210,8 @@ getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
sys::RemoveFileOnSignal(Path);
std::error_code EC;
auto Out = llvm::make_unique<raw_fd_ostream>(
Path, EC, (Binary ? sys::fs::F_None : sys::fs::F_Text));
auto Out = std::make_unique<raw_fd_ostream>(
Path, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_Text));
if (EC) {
Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message();
return nullptr;
@@ -245,7 +247,9 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(Opts.Triple));
assert(MRI && "Unable to create target register info!");
std::unique_ptr<MCAsmInfo> MAI(TheTarget->createMCAsmInfo(*MRI, Opts.Triple));
MCTargetOptions MCOptions;
std::unique_ptr<MCAsmInfo> MAI(
TheTarget->createMCAsmInfo(*MRI, Opts.Triple, MCOptions));
assert(MAI && "Unable to create target asm info!");
// Ensure MCAsmInfo initialization occurs before any use, otherwise sections
@@ -269,7 +273,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
std::unique_ptr<MCObjectFileInfo> MOFI(new MCObjectFileInfo());
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr);
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr, &MCOptions);
bool PIC = false;
if (Opts.RelocationModel == "static") {
@@ -326,7 +330,8 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
raw_pwrite_stream *Out = FDOS.get();
std::unique_ptr<buffer_ostream> BOS;
MCTargetOptions MCOptions;
MCOptions.MCNoWarn = Opts.NoWarn;
MCOptions.MCFatalWarnings = Opts.FatalWarnings;
MCOptions.ABIName = Opts.TargetABI;
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
@@ -340,7 +345,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
std::unique_ptr<MCAsmBackend> MAB(
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
auto FOut = llvm::make_unique<formatted_raw_ostream>(*Out);
auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
Str.reset(TheTarget->createAsmStreamer(
Ctx, std::move(FOut), /*asmverbose*/ true,
/*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB),
@@ -351,7 +356,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
assert(Opts.OutputType == AssemblerInvocation::FT_Obj &&
"Invalid file type!");
if (!FDOS->supportsSeeking()) {
BOS = make_unique<buffer_ostream>(*FDOS);
BOS = std::make_unique<buffer_ostream>(*FDOS);
Out = BOS.get();
}
@@ -436,7 +441,7 @@ static void LLVMErrorHandler(void *UserData, const std::string &Message,
Diags.Report(diag::err_fe_error_backend) << Message;
// We cannot recover from llvm errors.
exit(1);
sys::Process::Exit(1);
}
int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
@@ -464,11 +469,11 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
return 1;
if (Asm.ShowHelp) {
std::unique_ptr<OptTable> Opts(driver::createDriverOptTable());
Opts->PrintHelp(llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler",
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
/*ShowAllAliases=*/false);
getDriverOptTable().PrintHelp(
llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler",
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
/*ShowAllAliases=*/false);
return 0;
}
@@ -485,7 +490,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
// FIXME: Remove this, one day.
if (!Asm.LLVMArgs.empty()) {
unsigned NumArgs = Asm.LLVMArgs.size();
auto Args = llvm::make_unique<const char*[]>(NumArgs + 2);
auto Args = std::make_unique<const char*[]>(NumArgs + 2);
Args[0] = "clang (LLVM option parsing)";
for (unsigned i = 0; i != NumArgs; ++i)
Args[i + 1] = Asm.LLVMArgs[i].c_str();
@@ -499,6 +504,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
// If any timers were active but haven't been destroyed yet, print their
// results now.
TimerGroup::printAll(errs());
TimerGroup::clearAll();
return !!Failed;
}
+2 -2
View File
@@ -11,8 +11,6 @@
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/ArrayRef.h"
/// Helper class for representing a single invocation of the assembler.
struct AssemblerInvocation {
/// @name Target Options
@@ -82,6 +80,7 @@ struct AssemblerInvocation {
unsigned RelaxAll : 1;
unsigned NoExecStack : 1;
unsigned FatalWarnings : 1;
unsigned NoWarn : 1;
unsigned IncrementalLinkerCompatible : 1;
unsigned EmbedBitcode : 1;
@@ -107,6 +106,7 @@ public:
RelaxAll = 0;
NoExecStack = 0;
FatalWarnings = 0;
NoWarn = 0;
IncrementalLinkerCompatible = 0;
DwarfVersion = 0;
EmbedBitcode = 0;
+3 -3
View File
@@ -52,8 +52,7 @@ bool tinygo_clang_driver(int argc, char **argv) {
std::unique_ptr<clang::CompilerInstance> Clang(new clang::CompilerInstance());
bool success = clang::CompilerInvocation::CreateFromArgs(
Clang->getInvocation(),
const_cast<const char **>(CCArgs.data()),
const_cast<const char **>(CCArgs.data()) + CCArgs.size(),
CCArgs,
Diags);
if (!success) {
return false;
@@ -74,7 +73,8 @@ bool tinygo_clang_driver(int argc, char **argv) {
} else if (strcmp(*CCArgs.data(), "-cc1as") == 0) {
// This is the assembler frontend. Parse the arguments.
AssemblerInvocation Asm;
if (!AssemblerInvocation::CreateFromArgs(Asm, llvm::ArrayRef<const char*>(CCArgs).slice(1), Diags))
ArrayRef<const char *> Argv = llvm::ArrayRef<const char*>(CCArgs);
if (!AssemblerInvocation::CreateFromArgs(Asm, Argv.slice(1), Diags))
return false;
// Execute the invocation, unless there were parsing errors.
+17 -13
View File
@@ -6,25 +6,28 @@ import (
"os/exec"
"runtime"
"strings"
"tinygo.org/x/go-llvm"
)
// Commands lists command alternatives for various operating systems. These
// commands may have a slightly different name across operating systems and
// distributions or may not even exist in $PATH, in which case absolute paths
// may be used.
var commands = map[string][]string{
"clang": {"clang-9"},
"ld.lld": {"ld.lld-9", "ld.lld"},
"wasm-ld": {"wasm-ld-9", "wasm-ld"},
}
var commands = map[string][]string{}
func init() {
// Add the path to a Homebrew-installed LLVM 9 for ease of use (no need to
llvmMajor := strings.Split(llvm.Version, ".")[0]
commands["clang"] = []string{"clang-" + llvmMajor}
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
// Add the path to a Homebrew-installed LLVM for ease of use (no need to
// manually set $PATH).
if runtime.GOOS == "darwin" {
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm@9/bin/clang-9")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm@9/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm@9/bin/wasm-ld")
prefix := "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
}
// Add the path for when LLVM was installed with the installer from
// llvm.org, which by default doesn't add LLVM to the $PATH environment
@@ -34,11 +37,12 @@ func init() {
commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
}
// Add the path to the llvm90 installed from ports
// Add the path to LLVM installed from ports.
if runtime.GOOS == "freebsd" {
commands["clang"] = append(commands["clang"], "/usr/local/llvm90/bin/clang-9")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/llvm90/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/llvm90/bin/wasm-ld")
prefix := "/usr/local/llvm" + llvmMajor + "/bin/"
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
}
}
-60
View File
@@ -1,60 +0,0 @@
// +build byollvm
package builder
import (
"errors"
"os"
"os/exec"
"unsafe"
"github.com/tinygo-org/tinygo/goenv"
)
/*
#cgo CXXFLAGS: -fno-rtti
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_clang_driver(int argc, char **argv);
*/
import "C"
// runCCompiler invokes a C compiler with the given arguments.
//
// This version invokes the built-in Clang when trying to run the Clang compiler.
func runCCompiler(command string, flags ...string) error {
switch command {
case "clang":
// Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
return errors.New("could not locate Clang headers")
}
flags = append(flags, "-I"+headerPath)
flags = append([]string{"tinygo:" + command}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_clang_driver(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to compile using built-in clang")
}
return nil
default:
// 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()
}
}
-24
View File
@@ -1,24 +0,0 @@
// +build !byollvm
package builder
// This file provides a way to a C compiler as an external command. See also:
// clang-external.go
import (
"os"
"os/exec"
)
// runCCompiler invokes a C compiler with the given arguments.
//
// This version always runs the compiler as an external command.
func runCCompiler(command string, flags ...string) error {
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
+3 -3
View File
@@ -21,12 +21,12 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually")
}
major, minor, err := getGorootVersion(goroot)
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 != 12 && minor != 13) {
return nil, fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor)
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)
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{
-58
View File
@@ -1,71 +1,13 @@
package builder
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
)
// getGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
func getGorootVersion(goroot string) (major, minor int, err error) {
s, err := GorootVersionString(goroot)
if err != nil {
return 0, 0, err
}
if s == "" || s[:2] != "go" {
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
}
parts := strings.Split(s[2:], ".")
if len(parts) < 2 {
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
}
// Ignore the errors, we don't really handle errors here anyway.
var trailing string
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
if n == 2 && err == io.EOF {
// Means there were no trailing characters (i.e., not an alpha/beta)
err = nil
}
if err != nil {
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
}
return
}
// GorootVersionString returns the version string as reported by the Go
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
// can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`")
matches := r.FindSubmatch(data)
if len(matches) != 2 {
return "", errors.New("Invalid go version output:\n" + string(data))
}
return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else {
return "", err
}
}
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
// multiple locations, which should make it find the directory when installed in
// various ways.
+153
View File
@@ -0,0 +1,153 @@
package builder
// This file implements support for writing ESP image files. These image files
// are read by the ROM bootloader so have to be in a particular format.
//
// In the future, it may be necessary to implement support for other image
// formats, such as the ESP8266 image formats (again, used by the ROM bootloader
// to load the firmware).
import (
"bytes"
"crypto/sha256"
"debug/elf"
"encoding/binary"
"fmt"
"io/ioutil"
"sort"
)
type espImageSegment struct {
addr uint32
data []byte
}
// makeESPFirmare converts an input ELF file to an image file for an ESP32 or
// ESP8266 chip. This is a special purpose image format just for the ESP chip
// family, and is parsed by the on-chip mask ROM bootloader.
//
// The following documentation has been used:
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format
// https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L58
// https://github.com/espressif/esptool/blob/master/esptool.py
func makeESPFirmareImage(infile, outfile, format string) error {
inf, err := elf.Open(infile)
if err != nil {
return err
}
defer inf.Close()
// Load all segments to be written to the image. These are actually ELF
// sections, not true ELF segments (similar to how esptool does it).
var segments []*espImageSegment
for _, section := range inf.Sections {
if section.Type != elf.SHT_PROGBITS || section.Size == 0 || section.Flags&elf.SHF_ALLOC == 0 {
continue
}
data, err := section.Data()
if err != nil {
return fmt.Errorf("failed to read section data: %w", err)
}
for len(data)%4 != 0 {
// Align segment to 4 bytes.
data = append(data, 0)
}
if uint64(uint32(section.Addr)) != section.Addr {
return fmt.Errorf("section address too big: 0x%x", section.Addr)
}
segments = append(segments, &espImageSegment{
addr: uint32(section.Addr),
data: data,
})
}
// Sort the segments by address. This is what esptool does too.
sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr })
// Calculate checksum over the segment data. This is used in the image
// footer.
checksum := uint8(0xef)
for _, segment := range segments {
for _, b := range segment.data {
checksum ^= b
}
}
// Write first to an in-memory buffer, primarily so that we can easily
// calculate a hash over the entire image.
// An added benefit is that we don't need to check for errors all the time.
outf := &bytes.Buffer{}
// Image header.
switch format {
case "esp32":
// Header format:
// https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
binary.Write(outf, binary.LittleEndian, struct {
magic uint8
segment_count uint8
spi_mode uint8
spi_speed_size uint8
entry_addr uint32
wp_pin uint8
spi_pin_drv [3]uint8
reserved [11]uint8
hash_appended bool
}{
magic: 0xE9,
segment_count: byte(len(segments)),
spi_mode: 0, // irrelevant, replaced by esptool when flashing
spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
entry_addr: uint32(inf.Entry),
wp_pin: 0xEE, // disable WP pin
hash_appended: true, // add a SHA256 hash
})
case "esp8266":
// Header format:
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format
// Basically a truncated version of the ESP32 header.
binary.Write(outf, binary.LittleEndian, struct {
magic uint8
segment_count uint8
spi_mode uint8
spi_speed_size uint8
entry_addr uint32
}{
magic: 0xE9,
segment_count: byte(len(segments)),
spi_mode: 0, // irrelevant, replaced by esptool when flashing
spi_speed_size: 0x20, // spi_speed, spi_size: replaced by esptool when flashing
entry_addr: uint32(inf.Entry),
})
default:
return fmt.Errorf("builder: unknown binary format %#v, expected esp32 or esp8266", format)
}
// Write all segments to the image.
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format#segment
for _, segment := range segments {
binary.Write(outf, binary.LittleEndian, struct {
addr uint32
length uint32
}{
addr: segment.addr,
length: uint32(len(segment.data)),
})
outf.Write(segment.data)
}
// Footer, including checksum.
// The entire image size must be a multiple of 16, so pad the image to one
// byte less than that before writing the checksum.
outf.Write(make([]byte, 15-outf.Len()%16))
outf.WriteByte(checksum)
if format == "esp32" {
// SHA256 hash (to protect against image corruption, not for security).
hash := sha256.Sum256(outf.Bytes())
outf.Write(hash[:])
}
// Write the image to the output file.
return ioutil.WriteFile(outfile, outf.Bytes(), 0666)
}
+105
View File
@@ -0,0 +1,105 @@
package builder
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
// Library is a container for information about a single C library, such as a
// compiler runtime or libc.
type Library struct {
// The library name, such as compiler-rt or picolibc.
name string
cflags func() []string
// The source directory, relative to TINYGOROOT.
sourceDir string
// The source files, relative to sourceDir.
sources func(target string) []string
}
// fullPath returns the full path to the source directory.
func (l *Library) fullPath() string {
return filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir)
}
// sourcePaths returns a slice with the full paths to the source files.
func (l *Library) sourcePaths(target string) []string {
sources := l.sources(target)
paths := make([]string, len(sources))
for i, name := range sources {
paths[i] = filepath.Join(l.fullPath(), name)
}
return paths
}
// Load the library archive, possibly generating and caching it if needed.
func (l *Library) Load(target string) (path string, 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
}
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 {
// Cache hit.
return path, err
}
// Cache miss, build it now.
dirPrefix := "tinygo-" + l.name
remapDir := filepath.Join(os.TempDir(), dirPrefix)
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
if err != nil {
return "", err
}
defer os.RemoveAll(dir)
// Precalculate the flags to the compiler invocation.
args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
}
if strings.HasPrefix(target, "riscv32-") {
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
}
if strings.HasPrefix(target, "riscv64-") {
args = append(args, "-march=rv64gc", "-mabi=lp64")
}
// Compile all sources.
var objs []string
for _, srcpath := range l.sourcePaths(target) {
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}
}
}
// 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))
}
-71
View File
@@ -1,71 +0,0 @@
// +build byollvm
package builder
// This file provides a Link() function that uses the bundled lld if possible.
import (
"errors"
"os"
"os/exec"
"unsafe"
"github.com/tinygo-org/tinygo/goenv"
)
/*
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_link_elf(int argc, char **argv);
bool tinygo_link_wasm(int argc, char **argv);
*/
import "C"
// link invokes a linker with the given name and flags.
//
// This version uses the built-in linker when trying to use lld.
func link(linker string, flags ...string) error {
switch linker {
case "ld.lld":
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_link_elf(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to link using built-in ld.lld")
}
return nil
case "wasm-ld":
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
defer C.free(buf)
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_link_wasm(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to link using built-in wasm-ld")
}
return nil
default:
// Fall back to external command.
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
}
-27
View File
@@ -1,27 +0,0 @@
// +build !byollvm
package builder
// This file provides a Link() function that always runs an external command. It
// is provided for when tinygo is built without linking to liblld.
import (
"os"
"os/exec"
"github.com/tinygo-org/tinygo/goenv"
)
// link invokes a linker with the given name and arguments.
//
// This version always runs the linker as an external command.
func link(linker string, flags ...string) error {
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
+2 -2
View File
@@ -8,12 +8,12 @@ extern "C" {
bool tinygo_link_elf(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::elf::link(args, false);
return lld::elf::link(args, false, llvm::outs(), llvm::errs());
}
bool tinygo_link_wasm(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::wasm::link(args, false);
return lld::wasm::link(args, false, llvm::outs(), llvm::errs());
}
} // external "C"
+20 -16
View File
@@ -4,12 +4,15 @@ import (
"debug/elf"
"io/ioutil"
"os"
"path/filepath"
"sort"
"github.com/marcinbor85/gohex"
)
// maxPadBytes is the maximum allowed bytes to be padded in a rom extraction
// this value is currently defined by Nintendo Switch Page Alignment (4096 bytes)
const maxPadBytes = 4095
// objcopyError is an error returned by functions that act like objcopy.
type objcopyError struct {
Op string
@@ -71,7 +74,12 @@ func extractROM(path string) (uint64, []byte, error) {
var rom []byte
for _, prog := range progs {
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
if diff > maxPadBytes {
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
}
// Pad the difference
rom = append(rom, make([]byte, diff)...)
}
data, err := ioutil.ReadAll(prog.Open())
if err != nil {
@@ -93,7 +101,7 @@ func extractROM(path string) (uint64, []byte, error) {
// objcopy converts an ELF file to a different (simpler) output file format:
// .bin or .hex. It extracts only the .text section.
func objcopy(infile, outfile string) error {
func objcopy(infile, outfile, binaryFormat string) error {
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
@@ -107,24 +115,20 @@ func objcopy(infile, outfile string) error {
}
// Write to the file, in the correct format.
switch filepath.Ext(outfile) {
case ".gba":
// The address is not stored in a .gba file.
_, err := f.Write(data)
return err
case ".bin":
// The address is not stored in a .bin file (therefore you
// should use .hex files in most cases).
_, err := f.Write(data)
return err
case ".hex":
switch binaryFormat {
case "hex":
// Intel hex file, includes the firmware start address.
mem := gohex.NewMemory()
err := mem.AddBinary(uint32(addr), data)
if err != nil {
return objcopyError{"failed to create .hex file", err}
}
mem.DumpIntelHex(f, 16) // TODO: handle error
return nil
return mem.DumpIntelHex(f, 16)
case "bin":
// The start address is not stored in raw firmware files (therefore you
// should use .hex files in most cases).
_, err := f.Write(data)
return err
default:
panic("unreachable")
}
+127
View File
@@ -0,0 +1,127 @@
package builder
import (
"path/filepath"
"github.com/tinygo-org/tinygo/goenv"
)
// Picolibc is a C library for bare metal embedded devices. It was originally
// based on newlib.
var Picolibc = Library{
name: "picolibc",
cflags: func() []string {
picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc")
return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"}
},
sourceDir: "lib/picolibc/newlib/libc",
sources: func(target string) []string {
return picolibcSources
},
}
var picolibcSources = []string{
"string/bcmp.c",
"string/bcopy.c",
"string/bzero.c",
"string/explicit_bzero.c",
"string/ffsl.c",
"string/ffsll.c",
"string/fls.c",
"string/flsl.c",
"string/flsll.c",
"string/gnu_basename.c",
"string/index.c",
"string/memccpy.c",
"string/memchr.c",
"string/memcmp.c",
"string/memcpy.c",
"string/memmem.c",
"string/memmove.c",
"string/mempcpy.c",
"string/memrchr.c",
"string/memset.c",
"string/rawmemchr.c",
"string/rindex.c",
"string/stpcpy.c",
"string/stpncpy.c",
"string/strcasecmp.c",
"string/strcasecmp_l.c",
"string/strcasestr.c",
"string/strcat.c",
"string/strchr.c",
"string/strchrnul.c",
"string/strcmp.c",
"string/strcoll.c",
"string/strcoll_l.c",
"string/strcpy.c",
"string/strcspn.c",
"string/strdup.c",
"string/strerror.c",
"string/strerror_r.c",
"string/strlcat.c",
"string/strlcpy.c",
"string/strlen.c",
"string/strlwr.c",
"string/strncasecmp.c",
"string/strncasecmp_l.c",
"string/strncat.c",
"string/strncmp.c",
"string/strncpy.c",
"string/strndup.c",
"string/strnlen.c",
"string/strnstr.c",
"string/strpbrk.c",
"string/strrchr.c",
"string/strsep.c",
"string/strsignal.c",
"string/strspn.c",
"string/strstr.c",
"string/strtok.c",
"string/strtok_r.c",
"string/strupr.c",
"string/strverscmp.c",
"string/strxfrm.c",
"string/strxfrm_l.c",
"string/swab.c",
"string/timingsafe_bcmp.c",
"string/timingsafe_memcmp.c",
"string/u_strerr.c",
"string/wcpcpy.c",
"string/wcpncpy.c",
"string/wcscasecmp.c",
"string/wcscasecmp_l.c",
"string/wcscat.c",
"string/wcschr.c",
"string/wcscmp.c",
"string/wcscoll.c",
"string/wcscoll_l.c",
"string/wcscpy.c",
"string/wcscspn.c",
"string/wcsdup.c",
"string/wcslcat.c",
"string/wcslcpy.c",
"string/wcslen.c",
"string/wcsncasecmp.c",
"string/wcsncasecmp_l.c",
"string/wcsncat.c",
"string/wcsncmp.c",
"string/wcsncpy.c",
"string/wcsnlen.c",
"string/wcspbrk.c",
"string/wcsrchr.c",
"string/wcsspn.c",
"string/wcsstr.c",
"string/wcstok.c",
"string/wcswidth.c",
"string/wcsxfrm.c",
"string/wcsxfrm_l.c",
"string/wcwidth.c",
"string/wmemchr.c",
"string/wmemcmp.c",
"string/wmemcpy.c",
"string/wmemmove.c",
"string/wmempcpy.c",
"string/wmemset.c",
"string/xpg_strerror_r.c",
}
+12 -1
View File
@@ -84,7 +84,18 @@ func loadProgramSize(path string) (*programSize, error) {
if section.Type != elf.SHT_PROGBITS && section.Type != elf.SHT_NOBITS {
continue
}
if section.Type == elf.SHT_NOBITS {
if section.Name == ".stack" {
// HACK: this works around a bug in ld.lld from LLVM 10. The linker
// marks sections with no input symbols (such as is the case for the
// .stack section) as SHT_PROGBITS instead of SHT_NOBITS. While it
// doesn't affect the generated binaries (.hex and .bin), it does
// affect the reported size.
// https://bugs.llvm.org/show_bug.cgi?id=45336
// https://reviews.llvm.org/D76981
// It has been merged in master, but it has not (yet) been
// backported to the LLVM 10 release branch.
sumBSS += section.Size
} else if section.Type == elf.SHT_NOBITS {
sumBSS += section.Size
} else if section.Flags&elf.SHF_EXECINSTR != 0 {
sumCode += section.Size
+54
View File
@@ -0,0 +1,54 @@
// +build byollvm
package builder
import (
"errors"
"unsafe"
)
/*
#cgo CXXFLAGS: -fno-rtti
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_clang_driver(int argc, char **argv);
bool tinygo_link_elf(int argc, char **argv);
bool tinygo_link_wasm(int argc, char **argv);
*/
import "C"
const hasBuiltinTools = true
// RunTool runs the given tool (such as clang).
//
// This version actually runs the tools because TinyGo was compiled while
// linking statically with LLVM (with the byollvm build tag).
func RunTool(tool string, args ...string) error {
args = append([]string{"tinygo:" + tool}, args...)
var cflag *C.char
buf := C.calloc(C.size_t(len(args)), C.size_t(unsafe.Sizeof(cflag)))
defer C.free(buf)
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(args):len(args)]
for i, flag := range args {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
var ok C.bool
switch tool {
case "clang":
ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf))
case "ld.lld":
ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf))
case "wasm-ld":
ok = C.tinygo_link_wasm(C.int(len(args)), (**C.char)(buf))
default:
return errors.New("unknown tool: " + tool)
}
if !ok {
return errors.New("failed to run tool: " + tool)
}
return nil
}
+15
View File
@@ -0,0 +1,15 @@
// +build !byollvm
package builder
import "errors"
const hasBuiltinTools = false
// RunTool runs the given tool (such as clang).
//
// This version doesn't actually run the tool: TinyGo has not been compiled by
// statically linking to LLVM.
func RunTool(tool string, args ...string) error {
return errors.New("cannot run tool: " + tool)
}
+59
View File
@@ -0,0 +1,59 @@
package builder
import (
"errors"
"os"
"os/exec"
"github.com/tinygo-org/tinygo/goenv"
)
// runCCompiler invokes a C compiler with the given arguments.
func runCCompiler(command string, flags ...string) error {
if hasBuiltinTools && command == "clang" {
// Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
return errors.New("could not locate Clang headers")
}
flags = append(flags, "-I"+headerPath)
cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
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()
}
// link invokes a linker with the given name and flags.
func link(linker string, flags ...string) error {
if hasBuiltinTools && (linker == "ld.lld" || linker == "wasm-ld") {
// Run command with internal linker.
cmd := exec.Command(os.Args[0], append([]string{linker}, flags...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// Fall back to external command.
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
+30 -9
View File
@@ -11,26 +11,33 @@ import (
"bytes"
"encoding/binary"
"io/ioutil"
"strconv"
)
// convertELFFileToUF2File converts an ELF file to a UF2 file.
func convertELFFileToUF2File(infile, outfile string) error {
func convertELFFileToUF2File(infile, outfile string, uf2FamilyID string) error {
// Read the .text segment.
targetAddress, data, err := extractROM(infile)
if err != nil {
return err
}
output, _ := convertBinToUF2(data, uint32(targetAddress))
output, _, err := convertBinToUF2(data, uint32(targetAddress), uf2FamilyID)
if err != nil {
return err
}
return ioutil.WriteFile(outfile, output, 0644)
}
// convertBinToUF2 converts the binary bytes in input to UF2 formatted data.
func convertBinToUF2(input []byte, targetAddr uint32) ([]byte, int) {
func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byte, int, error) {
blocks := split(input, 256)
output := make([]byte, 0)
bl := newUF2Block(targetAddr)
bl, err := newUF2Block(targetAddr, uf2FamilyID)
if err != nil {
return nil, 0, err
}
bl.SetNumBlocks(len(blocks))
for i := 0; i < len(blocks); i++ {
@@ -41,7 +48,7 @@ func convertBinToUF2(input []byte, targetAddr uint32) ([]byte, int) {
bl.IncrementAddress(bl.payloadSize)
}
return output, len(blocks)
return output, len(blocks), nil
}
const (
@@ -65,18 +72,32 @@ type uf2Block struct {
}
// newUF2Block returns a new uf2Block struct that has been correctly populated
func newUF2Block(targetAddr uint32) *uf2Block {
func newUF2Block(targetAddr uint32, uf2FamilyID string) (*uf2Block, error) {
var flags uint32
var familyID uint32
if uf2FamilyID != "" {
flags |= flagFamilyIDPresent
v, err := strconv.ParseUint(uf2FamilyID, 0, 32)
if err != nil {
return nil, err
}
familyID = uint32(v)
}
return &uf2Block{magicStart0: uf2MagicStart0,
magicStart1: uf2MagicStart1,
magicEnd: uf2MagicEnd,
targetAddr: targetAddr,
flags: 0x0,
familyID: 0x0,
flags: flags,
familyID: familyID,
payloadSize: 256,
data: make([]byte, 476),
}
}, nil
}
const (
flagFamilyIDPresent = 0x00002000
)
// Bytes converts the uf2Block to a slice of bytes that can be written to file.
func (b *uf2Block) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, 512))
+60 -2
View File
@@ -14,7 +14,9 @@ package cgo
import (
"fmt"
"go/ast"
"go/scanner"
"go/token"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -39,6 +41,7 @@ type cgoPackage struct {
elaboratedTypes map[string]*elaboratedTypeInfo
enums map[string]enumInfo
anonStructNum int
ldflags []string
}
// constantInfo stores some information about a CGo constant found by libclang
@@ -154,7 +157,7 @@ typedef unsigned long long _Cgo_ulonglong;
// newly created *ast.File that should be added to the list of to-be-parsed
// files. If there is one or more error, it returns these in the []error slice
// but still modifies the AST.
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) {
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []error) {
p := &cgoPackage{
dir: dir,
fset: fset,
@@ -168,11 +171,28 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
enums: map[string]enumInfo{},
}
// 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})
p.generatedPos = generatedTokenPos.Pos(0)
// Find the absolute path for this package.
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
if err != nil {
return nil, nil, []error{
scanner.Error{
Pos: fset.Position(files[0].Pos()),
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
},
}
}
packagePath = filepath.Dir(packagePath)
// Construct a new in-memory AST for CGo declarations of this package.
unsafeImport := &ast.ImportSpec{
Path: &ast.BasicLit{
@@ -338,7 +358,21 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
continue
}
makePathsAbsolute(flags, packagePath)
cflags = append(cflags, flags...)
case "LDFLAGS":
flags, err := shlex.Split(value)
if err != nil {
// TODO: find the exact location where the error happened.
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
continue
}
if err := checkLinkerFlags(name, flags); err != nil {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
continue
}
makePathsAbsolute(flags, packagePath)
p.ldflags = append(p.ldflags, flags...)
default:
startPos := strings.LastIndex(line[4:colon], name) + 4
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
@@ -392,7 +426,31 @@ 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.errors
return p.generated, p.ldflags, p.errors
}
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
// relative flags into absolute flags, if they are relative. This is necessary
// because the C compiler is usually not invoked from the package path.
func makePathsAbsolute(args []string, packagePath string) {
nextIsPath := false
for i, arg := range args {
if nextIsPath {
if !filepath.IsAbs(arg) {
args[i] = filepath.Join(packagePath, arg)
}
}
if arg == "-I" || arg == "-L" {
nextIsPath = true
continue
}
if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
path := arg[2:]
if !filepath.IsAbs(path) {
args[i] = arg[:2] + filepath.Join(packagePath, path)
}
}
}
}
// addFuncDecls adds the C function declarations found by libclang in the
+18 -2
View File
@@ -11,6 +11,7 @@ import (
"go/types"
"io/ioutil"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
@@ -19,6 +20,21 @@ import (
// Pass -update to go test to update the output of the test files.
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)
// 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
// such functions are wrapped and it's important to have consistent test
// results.
re := regexp.MustCompile(`func \((.+)\)( .*?) +{ (.+) }`)
actual = re.ReplaceAllString(actual, "func ($1)$2 {\n\t$3\n}")
return actual
}
func TestCGo(t *testing.T) {
var cflags = []string{"--target=armv6m-none-eabi"}
@@ -34,7 +50,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
@@ -74,7 +90,7 @@ func TestCGo(t *testing.T) {
if err != nil {
t.Errorf("could not write out CGo AST: %v", err)
}
actual := strings.Replace(string(buf.Bytes()), "\r\n", "\n", -1)
actual := normalizeResult(string(buf.Bytes()))
// Read the file with the expected output, to compare against.
outfile := filepath.Join("testdata", name+".out.go")
+4 -4
View File
@@ -15,7 +15,7 @@ import (
)
/*
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
#include <stdlib.h>
#include <stdint.h>
@@ -246,9 +246,9 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
}
value := source[len(name):]
// Try to convert this #define into a Go constant expression.
expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value)
if err != nil {
p.errors = append(p.errors, err)
expr, scannerError := parseConst(pos+token.Pos(len(name)), p.fset, value)
if scannerError != nil {
p.errors = append(p.errors, *scannerError)
}
if expr != nil {
// Parsing was successful.
+7 -6
View File
@@ -1,13 +1,14 @@
// +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/llvm90/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/llvm90/lib -lclang
#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
@@ -0,0 +1,14 @@
// +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"
+1 -1
View File
@@ -3,7 +3,7 @@
// are slightly different from the ones defined in libclang.go, but they
// should be ABI compatible.
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
return clang_getTranslationUnitCursor(tu);
+11
View File
@@ -9,6 +9,9 @@ package main
#cgo CFLAGS: -DFOO
#cgo CFLAGS: -Iinclude
#include "foo.h"
#if defined(FOO)
#define BAR 3
#else
@@ -18,9 +21,17 @@ package main
#if defined(NOTDEFINED)
#warning flag must not be defined
#endif
// Check Compiler flags
#cgo LDFLAGS: -lc
// This flag is not valid ldflags
#cgo LDFLAGS: -does-not-exists
*/
import "C"
var (
_ = C.BAR
_ = C.FOO_H
)
+2
View File
@@ -1,6 +1,7 @@
// CGo errors:
// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
// testdata/flags.go:29:14: invalid flag: -does-not-exists
package main
@@ -9,6 +10,7 @@ import "unsafe"
var _ unsafe.Pointer
const C.BAR = 3
const C.FOO_H = 1
type C.int16_t = int16
type C.int32_t = int32
+1
View File
@@ -0,0 +1 @@
#define FOO_H 1
+39 -13
View File
@@ -62,12 +62,18 @@ type C.union3_t = C.union_1
type C.union_nested_t = C.union_3
type C.unionarray_t = struct{ arr [10]C.uchar }
func (s *C.struct_4) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
func (s *C.struct_4) set_bitfield_a(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 }
func (s *C.struct_4) bitfield_a() C.uchar {
return s.__bitfield_1 & 0x1f
}
func (s *C.struct_4) set_bitfield_a(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0
}
func (s *C.struct_4) bitfield_b() C.uchar {
return s.__bitfield_1 >> 5 & 0x1
}
func (s *C.struct_4) set_bitfield_b(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 }
func (s *C.struct_4) set_bitfield_b(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5
}
func (s *C.struct_4) bitfield_c() C.uchar {
return s.__bitfield_1 >> 6
}
@@ -94,25 +100,45 @@ type C.struct_type1 struct {
}
type C.struct_type2 struct{ _type C.int }
func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_s() *C.short {
return (*C.short)(unsafe.Pointer(&union.$union))
}
type C.union_1 struct{ $union uint64 }
func (union *C.union_2) unionfield_area() *C.point2d_t { return (*C.point2d_t)(unsafe.Pointer(&union.$union)) }
func (union *C.union_2) unionfield_solid() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) }
func (union *C.union_2) unionfield_area() *C.point2d_t {
return (*C.point2d_t)(unsafe.Pointer(&union.$union))
}
func (union *C.union_2) unionfield_solid() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
}
type C.union_2 struct{ $union [3]uint32 }
func (union *C.union_3) unionfield_point() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) }
func (union *C.union_3) unionfield_array() *C.unionarray_t { return (*C.unionarray_t)(unsafe.Pointer(&union.$union)) }
func (union *C.union_3) unionfield_thing() *C.union3_t { return (*C.union3_t)(unsafe.Pointer(&union.$union)) }
func (union *C.union_3) unionfield_point() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
}
func (union *C.union_3) unionfield_array() *C.unionarray_t {
return (*C.unionarray_t)(unsafe.Pointer(&union.$union))
}
func (union *C.union_3) unionfield_thing() *C.union3_t {
return (*C.union3_t)(unsafe.Pointer(&union.$union))
}
type C.union_3 struct{ $union [2]uint64 }
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
func (union *C.union_union2d) unionfield_d() *[2]float64 { return (*[2]float64)(unsafe.Pointer(&union.$union)) }
func (union *C.union_union2d) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C.union_union2d) unionfield_d() *[2]float64 {
return (*[2]float64)(unsafe.Pointer(&union.$union))
}
type C.union_union2d struct{ $union [2]uint64 }
type C.enum_option C.int
+126 -12
View File
@@ -5,6 +5,7 @@ package compileopts
import (
"errors"
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
@@ -21,6 +22,29 @@ 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
@@ -65,8 +89,14 @@ func (c *Config) BuildTags() []string {
return tags
}
// CgoEnabled returns true if (and only if) CGo is enabled. It is true by
// default and false if CGO_ENABLED is set to "0".
func (c *Config) CgoEnabled() bool {
return goenv.Get("CGO_ENABLED") == "1"
}
// GC returns the garbage collection strategy in use on this platform. Valid
// values are "none", "leaking", and "conservative".
// values are "none", "leaking", "extalloc", and "conservative".
func (c *Config) GC() string {
if c.Options.GC != "" {
return c.Options.GC
@@ -74,26 +104,33 @@ func (c *Config) GC() string {
if c.Target.GC != "" {
return c.Target.GC
}
return "conservative"
for _, tag := range c.Target.BuildTags {
if tag == "baremetal" || tag == "wasm" {
return "conservative"
}
}
return "extalloc"
}
// NeedsStackObjects returns true if the compiler should insert stack objects
// that can be traced by the garbage collector.
func (c *Config) NeedsStackObjects() bool {
if c.GC() != "conservative" {
switch c.GC() {
case "conservative", "extalloc":
for _, tag := range c.BuildTags() {
if tag == "baremetal" {
return false
}
}
return true
default:
return false
}
for _, tag := range c.BuildTags() {
if tag == "baremetal" {
return false
}
}
return true
}
// Scheduler returns the scheduler implementation. Valid values are "coroutines"
// and "tasks".
// Scheduler returns the scheduler implementation. Valid values are "none",
//"coroutines" and "tasks".
func (c *Config) Scheduler() string {
if c.Options.Scheduler != "" {
return c.Options.Scheduler
@@ -105,6 +142,21 @@ func (c *Config) Scheduler() string {
return "coroutines"
}
// 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.
switch c.Scheduler() {
case "none", "coroutines":
return FuncValueSwitch
case "tasks":
return FuncValueDoubleword
default:
panic("unknown scheduler type")
}
}
// PanicStrategy returns the panic strategy selected for this target. Valid
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
// instruction).
@@ -112,6 +164,16 @@ func (c *Config) PanicStrategy() string {
return c.Options.PanicStrategy
}
// AutomaticStackSize returns whether goroutine stack sizes should be determined
// automatically at compile time, if possible. If it is false, no attempt is
// made.
func (c *Config) AutomaticStackSize() bool {
if c.Target.AutoStackSize != nil && c.Scheduler() == "tasks" {
return *c.Target.AutoStackSize
}
return false
}
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing.
func (c *Config) CFlags() []string {
@@ -119,6 +181,14 @@ func (c *Config) CFlags() []string {
for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.Replace(flag, "{root}", goenv.Get("TINYGOROOT"), -1))
}
if c.Target.Libc == "picolibc" {
root := goenv.Get("TINYGOROOT")
cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
}
if c.Debug() {
cflags = append(cflags, "-g")
}
return cflags
}
@@ -169,6 +239,31 @@ func (c *Config) Debug() bool {
return c.Options.Debug
}
// BinaryFormat returns an appropriate binary format, based on the file
// extension and the configured binary format in the target JSON file.
func (c *Config) BinaryFormat(ext string) string {
switch ext {
case ".bin", ".gba", ".nro":
// The simplest format possible: dump everything in a raw binary file.
if c.Target.BinaryFormat != "" {
return c.Target.BinaryFormat
}
return "bin"
case ".hex":
// Similar to bin, but includes the start address and is thus usually a
// better format.
return "hex"
case ".uf2":
// Special purpose firmware format, mainly used on Adafruit boards.
// More information:
// https://github.com/Microsoft/uf2
return "uf2"
default:
// Use the ELF format for unrecognized file formats.
return "elf"
}
}
// Programmer returns the flash method and OpenOCD interface name given a
// particular configuration. It may either be all configured in the target JSON
// file or be modified using the -programmmer command-line option.
@@ -215,6 +310,25 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
return args, nil
}
// CodeModel returns the code model used on this platform.
func (c *Config) CodeModel() string {
if c.Target.CodeModel != "" {
return c.Target.CodeModel
}
return "default"
}
// RelocationModel returns the relocation model in use on this platform. Valid
// values are "static", "pic", "dynamicnopic".
func (c *Config) RelocationModel() string {
if c.Target.RelocationModel != "" {
return c.Target.RelocationModel
}
return "static"
}
type TestConfig struct {
CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc
+63
View File
@@ -1,5 +1,17 @@
package compileopts
import (
"fmt"
"strings"
)
var (
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"}
)
// Options contains extra options to give to the compiler. These options are
// usually passed from the command line.
type Options struct {
@@ -13,6 +25,7 @@ type Options struct {
VerifyIR bool
Debug bool
PrintSizes string
PrintStacks bool
CFlags []string
LDFlags []string
Tags string
@@ -21,3 +34,53 @@ type Options struct {
TestConfig TestConfig
Programmer string
}
// Verify performs a validation on the given options, raising an error if options are not valid.
func (o *Options) Verify() error {
if o.GC != "" {
valid := isInArray(validGCOptions, o.GC)
if !valid {
return fmt.Errorf(`invalid gc option '%s': valid values are %s`,
o.GC,
strings.Join(validGCOptions, ", "))
}
}
if o.Scheduler != "" {
valid := isInArray(validSchedulerOptions, o.Scheduler)
if !valid {
return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`,
o.Scheduler,
strings.Join(validSchedulerOptions, ", "))
}
}
if o.PrintSizes != "" {
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
if !valid {
return fmt.Errorf(`invalid size option '%s': valid values are %s`,
o.PrintSizes,
strings.Join(validPrintSizeOptions, ", "))
}
}
if o.PanicStrategy != "" {
valid := isInArray(validPanicStrategyOptions, o.PanicStrategy)
if !valid {
return fmt.Errorf(`invalid panic option '%s': valid values are %s`,
o.PanicStrategy,
strings.Join(validPanicStrategyOptions, ", "))
}
}
return nil
}
func isInArray(arr []string, item string) bool {
for _, i := range arr {
if i == item {
return true
}
}
return false
}
+138
View File
@@ -0,0 +1,138 @@
package compileopts_test
import (
"errors"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
)
func TestVerifyOptions(t *testing.T) {
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, extalloc, conservative`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, coroutines`)
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`)
expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
testCases := []struct {
name string
opts compileopts.Options
expectedError error
}{
{
name: "OptionsEmpty",
opts: compileopts.Options{},
},
{
name: "InvalidGCOption",
opts: compileopts.Options{
GC: "incorrect",
},
expectedError: expectedGCError,
},
{
name: "GCOptionNone",
opts: compileopts.Options{
GC: "none",
},
},
{
name: "GCOptionLeaking",
opts: compileopts.Options{
GC: "leaking",
},
},
{
name: "GCOptionExtalloc",
opts: compileopts.Options{
GC: "extalloc",
},
},
{
name: "GCOptionConservative",
opts: compileopts.Options{
GC: "conservative",
},
},
{
name: "InvalidSchedulerOption",
opts: compileopts.Options{
Scheduler: "incorrect",
},
expectedError: expectedSchedulerError,
},
{
name: "SchedulerOptionNone",
opts: compileopts.Options{
Scheduler: "none",
},
},
{
name: "SchedulerOptionTasks",
opts: compileopts.Options{
Scheduler: "tasks",
},
},
{
name: "SchedulerOptionCoroutines",
opts: compileopts.Options{
Scheduler: "coroutines",
},
},
{
name: "InvalidPrintSizeOption",
opts: compileopts.Options{
PrintSizes: "incorrect",
},
expectedError: expectedPrintSizeError,
},
{
name: "PrintSizeOptionNone",
opts: compileopts.Options{
PrintSizes: "none",
},
},
{
name: "PrintSizeOptionShort",
opts: compileopts.Options{
PrintSizes: "short",
},
},
{
name: "PrintSizeOptionFull",
opts: compileopts.Options{
PrintSizes: "full",
},
},
{
name: "InvalidPanicOption",
opts: compileopts.Options{
PanicStrategy: "incorrect",
},
expectedError: expectedPanicStrategyError,
},
{
name: "PanicOptionPrint",
opts: compileopts.Options{
PanicStrategy: "print",
},
},
{
name: "PanicOptionTrap",
opts: compileopts.Options{
PanicStrategy: "trap",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.opts.Verify()
if tc.expectedError != err {
if tc.expectedError.Error() != err.Error() {
t.Errorf("expected %v, got %v", tc.expectedError, err)
}
}
})
}
}
+46 -1
View File
@@ -32,6 +32,9 @@ type TargetSpec struct {
Compiler string `json:"compiler"`
Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"`
AutoStackSize *bool `json:"automatic-stack-size"` // Determine stack size automatically at compile time.
DefaultStackSize uint64 `json:"default-stack-size"` // Default stack size if the size couldn't be determined at compile time.
CFlags []string `json:"cflags"`
LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"`
@@ -43,9 +46,14 @@ type TargetSpec struct {
FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"`
UF2FamilyID string `json:"uf2-family-id"`
BinaryFormat string `json:"binary-format"`
OpenOCDInterface string `json:"openocd-interface"`
OpenOCDTarget string `json:"openocd-target"`
OpenOCDTransport string `json:"openocd-transport"`
JLinkDevice string `json:"jlink-device"`
CodeModel string `json:"code-model"`
RelocationModel string `json:"relocation-model"`
}
// copyProperties copies all properties that are set in spec2 into itself.
@@ -82,6 +90,15 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
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 != "" {
@@ -109,6 +126,12 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
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
}
@@ -118,6 +141,16 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
if spec2.OpenOCDTransport != "" {
spec.OpenOCDTransport = spec2.OpenOCDTransport
}
if spec2.JLinkDevice != "" {
spec.JLinkDevice = spec2.JLinkDevice
}
if spec2.CodeModel != "" {
spec.CodeModel = spec2.CodeModel
}
if spec2.RelocationModel != "" {
spec.RelocationModel = spec2.RelocationModel
}
}
// load reads a target specification from the JSON in the given io.Reader. It
@@ -221,6 +254,14 @@ func LoadTarget(target string) (*TargetSpec, error) {
if len(tripleSplit) < 3 {
return nil, errors.New("expected a full LLVM target or a custom target in -target flag")
}
if tripleSplit[0] == "arm" {
// LLVM and Clang have a different idea of what "arm" means, so
// upgrade to a slightly more modern ARM. In fact, when you pass
// --target=arm--linux-gnueabihf to Clang, it will convert that
// internally to armv7-unknown-linux-gnueabihf. Changing the
// architecture to armv7 will keep things consistent.
tripleSplit[0] = "armv7"
}
goos := tripleSplit[2]
if strings.HasPrefix(goos, "darwin") {
goos = "darwin"
@@ -229,11 +270,12 @@ func LoadTarget(target string) (*TargetSpec, error) {
"i386": "386",
"x86_64": "amd64",
"aarch64": "arm64",
"armv7": "arm",
}[tripleSplit[0]]
if goarch == "" {
goarch = tripleSplit[0]
}
return defaultTarget(goos, goarch, target)
return defaultTarget(goos, goarch, strings.Join(tripleSplit, "-"))
}
}
@@ -247,6 +289,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
BuildTags: []string{goos, goarch},
Compiler: "clang",
Linker: "cc",
CFlags: []string{"--target=" + triple},
GDB: "gdb",
PortReset: "false",
FlashMethod: "native",
@@ -259,11 +302,13 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs.
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"}
+143 -80
View File
@@ -4,16 +4,19 @@ package compiler
// required by the Go programming language.
import (
"fmt"
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// emitLookupBoundsCheck emits a bounds check before doing a lookup into a
// createLookupBoundsCheck emits a bounds check before doing a lookup into a
// slice. This is required by the Go language spec: an index out of bounds must
// cause a panic.
func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
if frame.fn.IsNoBounds() {
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
if b.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -23,41 +26,29 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
// correctly extend that type.
if indexType.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 {
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
index = b.CreateZExt(index, arrayLen.Type(), "")
} else {
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
index = b.CreateSExt(index, arrayLen.Type(), "")
}
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
// The index is bigger than the array length type, so extend it.
arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "")
arrayLen = b.CreateZExt(arrayLen, index.Type(), "")
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now do the bounds check: index >= arrayLen
outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("lookupPanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "")
b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic")
}
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
// createSliceBoundsCheck emits a bounds check before a slicing operation to make
// sure it is within bounds.
//
// This function is both used for slicing a slice (low and high have their
// normal meaning) and for creating a new slice, where 'capacity' means the
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases.
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if frame.fn.IsNoBounds() {
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if b.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -75,92 +66,164 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
capacityType = max.Type()
}
if capacityType != capacity.Type() {
capacity = c.builder.CreateZExt(capacity, capacityType, "")
capacity = b.CreateZExt(capacity, capacityType, "")
}
// Extend low and high to be the same size as capacity.
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if lowType.Info()&types.IsUnsigned != 0 {
low = c.builder.CreateZExt(low, capacityType, "")
low = b.CreateZExt(low, capacityType, "")
} else {
low = c.builder.CreateSExt(low, capacityType, "")
low = b.CreateSExt(low, capacityType, "")
}
}
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if highType.Info()&types.IsUnsigned != 0 {
high = c.builder.CreateZExt(high, capacityType, "")
high = b.CreateZExt(high, capacityType, "")
} else {
high = c.builder.CreateSExt(high, capacityType, "")
high = b.CreateSExt(high, capacityType, "")
}
}
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if maxType.Info()&types.IsUnsigned != 0 {
max = c.builder.CreateZExt(max, capacityType, "")
max = b.CreateZExt(max, capacityType, "")
} else {
max = c.builder.CreateSExt(max, capacityType, "")
max = b.CreateSExt(max, capacityType, "")
}
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now do the bounds check: low > high || high > capacity
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("slicePanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := b.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
}
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
// has no effect in well-behaved programs, but makes sure no uncaught nil
// 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() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
}
// Check whether the bufSize parameter must be cast to a wider integer for
// comparison.
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
if bufSizeType.Info()&types.IsUnsigned != 0 {
// Unsigned, so zero-extend to uint type.
bufSizeType = types.Typ[types.Uint]
bufSize = b.CreateZExt(bufSize, b.intType, "")
} else {
// Signed, so sign-extend to int type.
bufSizeType = types.Typ[types.Int]
bufSize = b.CreateSExt(bufSize, b.intType, "")
}
}
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in an
// uintptr if uintptrs were signed.
maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false))
if elementSize > maxBufSize.ZExtValue() {
b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize))
return
}
// Avoid divide-by-zero.
if elementSize == 0 {
elementSize = 1
}
// Make the maxBufSize actually the maximum allowed value (in number of
// elements in the channel buffer).
maxBufSize = llvm.ConstUDiv(maxBufSize, llvm.ConstInt(b.uintptrType, elementSize, false))
// Make sure maxBufSize has the same type as bufSize.
if maxBufSize.Type() != bufSize.Type() {
maxBufSize = llvm.ConstZExt(maxBufSize, bufSize.Type())
}
// Do the check for a too large (or negative) buffer size.
bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "")
b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic")
}
// createNilCheck checks whether the given pointer is nil, and panics if it is.
// It has no effect in well-behaved programs, but makes sure no uncaught nil
// pointer dereferences exist in valid Go code.
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix string) {
// Check whether we need to emit this check at all.
if !ptr.IsAGlobalValue().IsNil() {
return
}
// Check whether this is a nil pointer.
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
switch inst := inst.(type) {
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
// must be a valid (non-nil) pointer. No nil checking is necessary.
return
case *ssa.Convert:
// This is a pointer that comes from a conversion from unsafe.Pointer.
// Don't do nil checking because this is unsafe code and the code should
// know what it is doing.
// Note: all *ssa.Convert instructions that result in a pointer must
// come from unsafe.Pointer. Testing here for unsafe.Pointer to be sure.
if inst.X.Type() == types.Typ[types.UnsafePointer] {
return
}
}
// Compare against nil.
var isnil llvm.Value
if ptr.Type().PointerAddressSpace() == 0 {
// Do the nil check using the isnil builtin, which marks the parameter
// as nocapture.
// The reason it has to go through a builtin, is that a regular icmp
// instruction may capture the pointer in LLVM semantics, see
// https://reviews.llvm.org/D60047 for details. Pointer capturing
// unfortunately breaks escape analysis, so we use this trick to let the
// functionattr pass know that this pointer doesn't really escape.
ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "")
isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "")
} else {
// Do the nil check using a regular icmp. This can happen with function
// pointers on AVR, which don't benefit from escape analysis anyway.
nilptr := llvm.ConstPointerNull(ptr.Type())
isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
}
c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
// We previously used a hack to make sure this wouldn't break escape
// analysis, but this is not necessary anymore since
// https://reviews.llvm.org/D60047 has been merged.
nilptr := llvm.ConstPointerNull(ptr.Type())
isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("nilPanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
// Emit the nil check in IR.
b.createRuntimeAssert(isnil, blockPrefix, "nilPanic")
}
// 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() {
// Function disabled bounds checking - skip shift check.
return
}
// isNegative = shift < 0
isNegative := b.CreateICmp(llvm.IntSLT, shift, llvm.ConstInt(shift.Type(), 0, false), "")
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
}
// createRuntimeAssert is a common function to create a new branch on an assert
// bool, calling an assert func if the assert value is true (1).
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
// Check whether we can resolve this check at compile time.
if !assert.IsAConstantInt().IsNil() {
val := assert.ZExtValue()
if val == 0 {
// Everything is constant so the check does not have to be emitted
// in IR. This avoids emitting some redundant IR.
return
}
}
faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.fn.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.
b.CreateCondBr(assert, faultBlock, nextBlock)
// Fail: the assert triggered so panic.
b.SetInsertPointAtEnd(faultBlock)
b.createRuntimeCall(assertFunc, nil, "")
b.CreateUnreachable()
// Ok: assert didn't trigger so continue normally.
b.SetInsertPointAtEnd(nextBlock)
}
+57
View File
@@ -0,0 +1,57 @@
package compiler
import (
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createAtomicOp lowers an atomic library call by lowering it as an LLVM atomic
// operation. It returns the result of the operation and true if the call could
// be lowered inline, and false otherwise.
func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
name := call.Value.(*ssa.Function).Name()
switch name {
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
ptr := b.getValue(call.Args[0])
val := b.getValue(call.Args[1])
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
// Return the new value, not the original value returned by atomicrmw.
return b.CreateAdd(oldVal, val, ""), true
case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer":
ptr := b.getValue(call.Args[0])
val := b.getValue(call.Args[1])
isPointer := val.Type().TypeKind() == llvm.PointerTypeKind
if isPointer {
// atomicrmw only supports integers, so cast to an integer.
val = b.CreatePtrToInt(val, b.uintptrType, "")
ptr = b.CreateBitCast(ptr, llvm.PointerType(val.Type(), 0), "")
}
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
if isPointer {
oldVal = b.CreateIntToPtr(oldVal, b.i8ptrType, "")
}
return oldVal, true
case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer":
ptr := b.getValue(call.Args[0])
old := b.getValue(call.Args[1])
newVal := b.getValue(call.Args[2])
tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true)
swapped := b.CreateExtractValue(tuple, 1, "")
return swapped, true
case "LoadInt32", "LoadInt64", "LoadUint32", "LoadUint64", "LoadUintptr", "LoadPointer":
ptr := b.getValue(call.Args[0])
val := b.CreateLoad(ptr, "")
val.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
val.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
return val, true
case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer":
ptr := b.getValue(call.Args[0])
val := b.getValue(call.Args[1])
store := b.CreateStore(val, ptr)
store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
return store, true
default:
return llvm.Value{}, false
}
}
+167 -67
View File
@@ -1,8 +1,9 @@
package compiler
import (
"fmt"
"golang.org/x/tools/go/ssa"
"go/types"
"strconv"
"tinygo.org/x/go-llvm"
)
@@ -11,62 +12,80 @@ import (
// The maximum number of arguments that can be expanded from a single struct. If
// a struct contains more fields, it is passed as a struct without expanding.
const MaxFieldsPerParam = 3
const maxFieldsPerParam = 3
// Shortcut: create a call to runtime.<fnName> with the given arguments.
func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
runtimePkg := c.ir.Program.ImportedPackage("runtime")
member := runtimePkg.Members[fnName]
if member == nil {
panic("trying to call runtime." + fnName)
}
fn := c.ir.GetFunction(member.(*ssa.Function))
if fn.LLVMFn.IsNil() {
panic(fmt.Errorf("function %s does not appear in LLVM IR", fnName))
}
if !fn.IsExported() {
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
}
return c.createCall(fn.LLVMFn, args, name)
// paramInfo contains some information collected about a function parameter,
// useful while declaring or defining a function.
type paramInfo struct {
llvmType llvm.Type
name string // name, possibly with suffixes for e.g. struct fields
flags paramFlags
}
// Create a call to the given function with the arguments possibly expanded.
func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
// paramFlags identifies parameter attributes for flags. Most importantly, it
// determines which parameters are dereferenceable_or_null and which aren't.
type paramFlags uint8
const (
// Parameter may have the deferenceable_or_null attribute. This attribute
// cannot be applied to unsafe.Pointer and to the data pointer of slices.
paramIsDeferenceableOrNull = 1 << iota
)
// 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)
}
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)
}
// createCall creates a call to the given function with the arguments possibly
// expanded.
func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
expanded := make([]llvm.Value, 0, len(args))
for _, arg := range args {
fragments := c.expandFormalParam(arg)
fragments := b.expandFormalParam(arg)
expanded = append(expanded, fragments...)
}
return c.builder.CreateCall(fn, expanded, name)
return b.CreateCall(fn, expanded, name)
}
// Expand an argument type to a list that can be used in a function call
// parameter list.
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := c.flattenAggregateType(t)
if len(fields) <= MaxFieldsPerParam {
return fields
fieldInfos := flattenAggregateType(t, name, goType)
if len(fieldInfos) <= maxFieldsPerParam {
return fieldInfos
} else {
// failed to lower
return []llvm.Type{t}
}
default:
// TODO: split small arrays
return []llvm.Type{t}
}
// TODO: split small arrays
return []paramInfo{
{
llvmType: t,
name: name,
flags: getTypeFlags(goType),
},
}
}
// Expand an argument type to a list of offsets from the start of the object.
// Used together with expandFormalParam to get the offset of each value from the
// start of the non-expanded value.
func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
// expandFormalParamOffsets returns a list of offsets from the start of an
// object of type t after it would have been split up by expandFormalParam. This
// is useful for debug information, where it is necessary to know the offset
// from the start of the combined object.
func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := c.flattenAggregateTypeOffsets(t)
if len(fields) <= MaxFieldsPerParam {
fields := b.flattenAggregateTypeOffsets(t)
if len(fields) <= maxFieldsPerParam {
return fields
} else {
// failed to lower
@@ -78,14 +97,17 @@ func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
}
}
// Equivalent of expandFormalParamType for parameter values.
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
// expandFormalParam splits a formal param value into pieces, so it can be
// passed directly as part of a function call. For example, it splits up small
// structs into individual fields. It is the equivalent of expandFormalParamType
// for parameter values.
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() {
case llvm.StructTypeKind:
fieldTypes := c.flattenAggregateType(v.Type())
if len(fieldTypes) <= MaxFieldsPerParam {
fields := c.flattenAggregate(v)
if len(fields) != len(fieldTypes) {
fieldInfos := flattenAggregateType(v.Type(), "", nil)
if len(fieldInfos) <= maxFieldsPerParam {
fields := b.flattenAggregate(v)
if len(fields) != len(fieldInfos) {
panic("type and value param lowering don't match")
}
return fields
@@ -101,24 +123,98 @@ func (c *Compiler) 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 (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type {
func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
typeFlags := getTypeFlags(goType)
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := make([]llvm.Type, 0, t.StructElementTypesCount())
for _, subfield := range t.StructElementTypes() {
subfields := c.flattenAggregateType(subfield)
fields = append(fields, subfields...)
paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
for i, subfield := range t.StructElementTypes() {
suffix := strconv.Itoa(i)
if goType != nil {
// Try to come up with a good suffix for this struct field,
// depending on which Go type it's based on.
switch goType := goType.Underlying().(type) {
case *types.Interface:
suffix = []string{"typecode", "value"}[i]
case *types.Slice:
suffix = []string{"data", "len", "cap"}[i]
case *types.Struct:
suffix = goType.Field(i).Name()
case *types.Basic:
switch goType.Kind() {
case types.Complex64, types.Complex128:
suffix = []string{"r", "i"}[i]
case types.String:
suffix = []string{"data", "len"}[i]
}
case *types.Signature:
suffix = []string{"context", "funcptr"}[i]
}
}
subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
for i := range subInfos {
subInfos[i].flags |= typeFlags
}
paramInfos = append(paramInfos, subInfos...)
}
return fields
return paramInfos
default:
return []llvm.Type{t}
return []paramInfo{
{
llvmType: t,
name: name,
flags: typeFlags,
},
}
}
}
// Return the offsets from the start of the object if this object type were
// flattened like in flattenAggregate. Used together with flattenAggregate to
// know the start indices of each value in the non-flattened object.
func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
// getTypeFlags returns the type flags for a given type. It will not recurse
// into sub-types (such as in structs).
func getTypeFlags(t types.Type) paramFlags {
if t == nil {
return 0
}
switch t.Underlying().(type) {
case *types.Pointer:
// Pointers in Go must either point to an object or be nil.
return paramIsDeferenceableOrNull
case *types.Chan, *types.Map:
// Channels and maps are implemented as pointers pointing to some
// object, and follow the same rules as *types.Pointer.
return paramIsDeferenceableOrNull
default:
return 0
}
}
// extractSubfield extracts a field from a struct, or returns null if this is
// not a struct and thus no subfield can be obtained.
func extractSubfield(t types.Type, field int) types.Type {
if t == nil {
return nil
}
switch t := t.Underlying().(type) {
case *types.Struct:
return t.Field(field).Type()
case *types.Interface, *types.Slice, *types.Basic, *types.Signature:
// These Go types are (sometimes) implemented as LLVM structs but can't
// really be split further up in Go (with the possible exception of
// complex numbers).
return nil
default:
// This should be unreachable.
panic("cannot split subfield: " + t.String())
}
}
// flattenAggregateTypeOffset returns the offsets from the start of an object of
// type t if this object were flattened like in flattenAggregate. Used together
// with flattenAggregate to know the start indices of each value in the
// non-flattened object.
//
// Note: this is an implementation detail, use expandFormalParamOffsets instead.
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := make([]uint64, 0, t.StructElementTypesCount())
@@ -136,15 +232,15 @@ func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
}
}
// Break down a struct into its elementary types for argument passing. The value
// equivalent of flattenAggregateType
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
// flattenAggregate breaks down a struct into its elementary values for argument
// passing. It is the value equivalent of flattenAggregateType
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() {
subfield := c.builder.CreateExtractValue(v, i, "")
subfields := c.flattenAggregate(subfield)
subfield := b.CreateExtractValue(v, i, "")
subfields := b.flattenAggregate(subfield)
fields = append(fields, subfields...)
}
return fields
@@ -153,25 +249,29 @@ func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
}
}
// Collapse a list of fields into its original value.
func (c *Compiler) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
param, remaining := c.collapseFormalParamInternal(t, fields)
// collapseFormalParam combines an aggregate object back into the original
// value. This is used to join multiple LLVM parameters into a single Go value
// in the function entry block.
func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
param, remaining := b.collapseFormalParamInternal(t, fields)
if len(remaining) != 0 {
panic("failed to expand back all fields")
}
return param
}
// Returns (value, remainingFields). Used by collapseFormalParam.
func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
// collapseFormalParamInternal is an implementation detail of
// collapseFormalParam: it works by recursing until there are no fields left.
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
switch t.TypeKind() {
case llvm.StructTypeKind:
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
flattened := flattenAggregateType(t, "", nil)
if len(flattened) <= maxFieldsPerParam {
value := llvm.ConstNull(t)
for i, subtyp := range t.StructElementTypes() {
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
fields = remaining
value = c.builder.CreateInsertValue(value, structField, i, "")
value = b.CreateInsertValue(value, structField, i, "")
}
return value, fields
} else {
+102 -86
View File
@@ -11,73 +11,89 @@ import (
"tinygo.org/x/go-llvm"
)
func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value {
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false)
bufSize := c.getValue(frame, expr.Size)
return c.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()))
elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
bufSize := b.getValue(expr.Size)
b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
bufSize = b.CreateZExt(bufSize, b.uintptrType, "")
} else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
bufSize = b.CreateTrunc(bufSize, b.uintptrType, "")
}
return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
}
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
// channel send operation during goroutine lowering.
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
ch := c.getValue(frame, instr.Chan)
chanValue := c.getValue(frame, instr.X)
// createChanSend emits a pseudo chan send operation. It is lowered to the
// actual channel send operation during goroutine lowering.
func (b *builder) createChanSend(instr *ssa.Send) {
ch := b.getValue(instr.Chan)
chanValue := b.getValue(instr.X)
// store value-to-send
valueType := c.getLLVMType(instr.X.Type())
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
c.builder.CreateStore(chanValue, valueAlloca)
valueType := b.getLLVMType(instr.X.Type())
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the send.
c.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
// End the lifetime of the alloca.
// End the lifetime of the allocas.
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
// actual channel receive operation during goroutine lowering.
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
ch := c.getValue(frame, unop.X)
func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem())
ch := b.getValue(unop.X)
// Allocate memory to receive into.
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the receive.
commaOk := c.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received")
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
received := b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, received, 0, "")
tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
return tuple
} else {
return received
}
}
// emitChanClose closes the given channel.
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
ch := c.getValue(frame, param)
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
// createChanClose closes the given channel.
func (b *builder) createChanClose(param ssa.Value) {
ch := b.getValue(param)
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
}
// emitSelect emits all IR necessary for a select statements. That's a
// createSelect emits all IR necessary for a select statements. That's a
// non-trivial amount of code because select is very complex to implement.
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := c.getLLVMType(expr.Type())
llvmType := b.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
c.createRuntimeCall("deadlock", nil, "")
b.createRuntimeCall("deadlock", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:
@@ -85,7 +101,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
// default:
// }
retval := llvm.Undef(llvmType)
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "")
return retval // {-1, false}
}
}
@@ -103,30 +119,30 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := c.getValue(frame, state.Chan)
ch := b.getValue(state.Chan)
selectState := llvm.ConstNull(chanSelectStateType)
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
selectState = b.CreateInsertValue(selectState, ch, 0, "")
switch state.Dir {
case types.RecvOnly:
// Make sure the receive buffer is big enough and has the correct alignment.
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem())
if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
recvbufSize = size
}
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align
}
hasReceives = true
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
sendValue := c.getValue(frame, state.Send)
alloca := llvmutil.CreateEntryBlockAlloca(c.builder, sendValue.Type(), "select.send.value")
c.builder.CreateStore(sendValue, alloca)
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
sendValue := b.getValue(state.Send)
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
b.CreateStore(sendValue, alloca)
ptr := b.CreateBitCast(alloca, b.i8ptrType, "")
selectState = b.CreateInsertValue(selectState, ptr, 1, "")
default:
panic("unreachable")
}
@@ -134,74 +150,74 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
}
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(c.i8ptrType)
recvbuf := llvm.Undef(b.i8ptrType)
if hasReceives {
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := c.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
recvbuf = b.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.recvbuf")
}
// Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
statesAlloca, statesI8, statesSize := c.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
for i, state := range selectStates {
// Set each slice element to the appropriate channel.
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
gep := b.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
}, "")
c.builder.CreateStore(state, gep)
b.CreateStore(state, gep)
}
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
statesPtr := b.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.states")
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
// Do the select in the runtime.
var results llvm.Value
if expr.Blocking {
// Stack-allocate operation structures.
// If these were simply created as a slice, they would heap-allocate.
chBlockAllocaType := llvm.ArrayType(c.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockAllocaPtr, chBlockSize := c.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := c.builder.CreateGEP(chBlockAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockAllocaPtr, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := b.CreateGEP(chBlockAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.block")
results = c.createRuntimeCall("chanSelect", []llvm.Value{
results = b.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
}, "select.result")
// Terminate the lifetime of the operation structures.
c.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
} else {
results = c.createRuntimeCall("tryChanSelect", []llvm.Value{
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
}, "select.result")
}
// Terminate the lifetime of the states alloca.
c.emitLifetimeEnd(statesI8, statesSize)
b.emitLifetimeEnd(statesI8, statesSize)
// The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract
// instruction will treat a *ssa.Select specially and load it there inline.
// Store the receive alloca in a sidetable until we hit this extract
// instruction.
if frame.selectRecvBuf == nil {
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
if b.selectRecvBuf == nil {
b.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
}
frame.selectRecvBuf[expr] = recvbuf
b.selectRecvBuf[expr] = recvbuf
return results
}
@@ -210,28 +226,28 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
// when extracting a value from a select statement (*ssa.Select). Because
// *ssa.Select cannot load all values in advance, it does this later in the
// *ssa.Extract expression.
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
if expr.Index == 0 {
// index
value := c.getValue(frame, expr.Tuple)
index := c.builder.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
index = c.builder.CreateSExt(index, c.intType, "")
value := b.getValue(expr.Tuple)
index := b.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() {
index = b.CreateSExt(index, b.intType, "")
}
return index
} else if expr.Index == 1 {
// comma-ok
value := c.getValue(frame, expr.Tuple)
return c.builder.CreateExtractValue(value, expr.Index, "")
value := b.getValue(expr.Tuple)
return b.CreateExtractValue(value, expr.Index, "")
} else {
// Select statements are (index, ok, ...) where ... is a number of
// received values, depending on how many receive statements there
// are. They are all combined into one alloca (because only one
// receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it.
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
return c.builder.CreateLoad(ptr, "")
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0)
ptr := b.CreateBitCast(recvbuf, typ, "")
return b.CreateLoad(ptr, "")
}
}
+1056 -1166
View File
File diff suppressed because it is too large Load Diff
+224 -121
View File
@@ -16,6 +16,7 @@ package compiler
import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -23,16 +24,18 @@ import (
// deferInitFunc sets up this function for future deferred calls. It must be
// called from within the entry block when this function contains deferred
// calls.
func (c *Compiler) deferInitFunc(frame *Frame) {
func (b *builder) deferInitFunc() {
// Some setup.
frame.deferFuncs = make(map[*ir.Function]int)
frame.deferInvokeFuncs = make(map[string]int)
frame.deferClosureFuncs = make(map[*ir.Function]int)
b.deferFuncs = make(map[*ir.Function]int)
b.deferInvokeFuncs = make(map[string]int)
b.deferClosureFuncs = make(map[*ir.Function]int)
b.deferExprFuncs = make(map[ssa.Value]int)
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
// Create defer list pointer.
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
b.deferPtr = b.CreateAlloca(deferType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr)
}
// isInLoop checks if there is a path from a basic block to itself.
@@ -69,53 +72,54 @@ func isInLoop(start *ssa.BasicBlock) bool {
return false
}
// emitDefer emits a single defer instruction, to be run when this function
// createDefer emits a single defer instruction, to be run when this function
// returns.
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
func (b *builder) createDefer(instr *ssa.Defer) {
// The pointer to the previous defer struct, which we will replace to
// make a linked list.
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
next := b.CreateLoad(b.deferPtr, "defer.next")
var values []llvm.Value
valueTypes := []llvm.Type{c.uintptrType, next.Type()}
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
if instr.Call.IsInvoke() {
// Method call on an interface.
// Get callback type number.
methodName := instr.Call.Method.FullName()
if _, ok := frame.deferInvokeFuncs[methodName]; !ok {
frame.deferInvokeFuncs[methodName] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, &instr.Call)
if _, ok := b.deferInvokeFuncs[methodName]; !ok {
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
}
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferInvokeFuncs[methodName]), false)
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by the call parameters).
itf := c.getValue(frame, instr.Call.Value) // interface
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, receiverValue}
valueTypes = append(valueTypes, c.i8ptrType)
itf := b.getValue(instr.Call.Value) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, typecode, receiverValue}
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
for _, arg := range instr.Call.Args {
val := c.getValue(frame, arg)
val := b.getValue(arg)
values = append(values, val)
valueTypes = append(valueTypes, val.Type())
}
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call.
fn := c.ir.GetFunction(callee)
fn := b.ir.GetFunction(callee)
if _, ok := frame.deferFuncs[fn]; !ok {
frame.deferFuncs[fn] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, fn)
if _, ok := b.deferFuncs[fn]; !ok {
b.deferFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, fn)
}
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferFuncs[fn]), false)
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false)
// 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 := c.getValue(frame, param)
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -127,65 +131,110 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
// pointer.
// TODO: ignore this closure entirely and put pointers to the free
// variables directly in the defer struct, avoiding a memory allocation.
closure := c.getValue(frame, instr.Call.Value)
context := c.builder.CreateExtractValue(closure, 0, "")
closure := b.getValue(instr.Call.Value)
context := b.CreateExtractValue(closure, 0, "")
// Get the callback number.
fn := c.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
if _, ok := frame.deferClosureFuncs[fn]; !ok {
frame.deferClosureFuncs[fn] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, makeClosure)
fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
if _, ok := b.deferClosureFuncs[fn]; !ok {
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
}
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferClosureFuncs[fn]), false)
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferClosureFuncs[fn]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the
// context pointer).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := c.getValue(frame, param)
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
values = append(values, context)
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
}
if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{
funcName,
len(b.allDeferFuncs),
}
b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferBuiltinFuncs[instr.Call.Value].callback), false)
// 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())
}
} else {
c.addError(instr.Pos(), "todo: defer on uncommon function call type")
return
funcValue := b.getValue(instr.Call.Value)
if _, ok := b.deferExprFuncs[instr.Call.Value]; !ok {
b.deferExprFuncs[instr.Call.Value] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferExprFuncs[instr.Call.Value]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the
// context pointer).
values = []llvm.Value{callback, next, funcValue}
valueTypes = append(valueTypes, funcValue.Type())
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
}
// Make a struct out of the collected values to put in the defer frame.
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFrame := llvm.ConstNull(deferFrameType)
for i, value := range values {
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
deferFrame = b.CreateInsertValue(deferFrame, value, i, "")
}
// Put this struct in an allocation.
var alloca llvm.Value
if !isInLoop(instr.Block()) {
// This can safely use a stack allocation.
alloca = llvmutil.CreateEntryBlockAlloca(c.builder, deferFrameType, "defer.alloca")
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferFrameType, "defer.alloca")
} else {
// This may be hit a variable number of times, so use a heap allocation.
size := c.targetData.TypeAllocSize(deferFrameType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
allocCall := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
alloca = c.builder.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
size := b.targetData.TypeAllocSize(deferFrameType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
}
if c.NeedsStackObjects() {
c.trackPointer(alloca)
if b.NeedsStackObjects() {
b.trackPointer(alloca)
}
c.builder.CreateStore(deferFrame, alloca)
b.CreateStore(deferFrame, alloca)
// Push it on top of the linked list by replacing deferPtr.
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
c.builder.CreateStore(allocaCast, frame.deferPtr)
allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
b.CreateStore(allocaCast, b.deferPtr)
}
// emitRunDefers emits code to run all deferred functions.
func (c *Compiler) emitRunDefers(frame *Frame) {
// createRunDefers emits code to run all deferred functions.
func (b *builder) createRunDefers() {
// Add a loop like the following:
// for stack != nil {
// _stack := stack
@@ -202,151 +251,205 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// }
// Create loop.
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
c.builder.CreateBr(loophead)
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")
b.CreateBr(loophead)
// Create loop head:
// for stack != nil {
c.builder.SetInsertPointAtEnd(loophead)
deferData := c.builder.CreateLoad(frame.deferPtr, "")
stackIsNil := c.builder.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
c.builder.CreateCondBr(stackIsNil, end, loop)
b.SetInsertPointAtEnd(loophead)
deferData := b.CreateLoad(b.deferPtr, "")
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
b.CreateCondBr(stackIsNil, end, loop)
// Create loop body:
// _stack := stack
// stack = stack.next
// switch stack.callback {
c.builder.SetInsertPointAtEnd(loop)
nextStackGEP := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
b.SetInsertPointAtEnd(loop)
nextStackGEP := b.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
}, "stack.next.gep")
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
c.builder.CreateStore(nextStack, frame.deferPtr)
gep := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
nextStack := b.CreateLoad(nextStackGEP, "stack.next")
b.CreateStore(nextStack, b.deferPtr)
gep := b.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field
}, "callback.gep")
callback := c.builder.CreateLoad(gep, "callback")
sw := c.builder.CreateSwitch(callback, unreachable, len(frame.allDeferFuncs))
callback := b.CreateLoad(gep, "callback")
sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs))
for i, callback := range frame.allDeferFuncs {
for i, callback := range b.allDeferFuncs {
// Create switch case, for example:
// case 0:
// // run first deferred call
block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
c.builder.SetInsertPointAtEnd(block)
block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
case *ssa.CallCommon:
// Call on an interface value.
if !callback.IsInvoke() {
panic("expected an invoke call, not a direct call")
}
// Call on an value or interface value.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType}
for _, arg := range callback.Args {
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
if !callback.IsInvoke() {
//Expect funcValue to be passed through the defer frame.
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
} else {
//Expect typecode
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
}
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
}
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct (including receiver).
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
var fnPtr llvm.Value
if !callback.IsInvoke() {
// Isolate the func value.
funcValue := forwardParams[0]
forwardParams = forwardParams[1:]
//Get function pointer and context
fp, context := b.decodeFuncValue(funcValue, callback.Signature())
fnPtr = fp
//Pass context
forwardParams = append(forwardParams, context)
} else {
// Isolate the typecode.
typecode := forwardParams[0]
forwardParams = forwardParams[1:]
fnPtr = b.getInvokePtr(callback, typecode)
// Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
}
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
fnPtr, _ := c.getInvokeCall(frame, callback)
c.createCall(fnPtr, forwardParams, "")
b.createCall(fnPtr, forwardParams, "")
case *ir.Function:
// Direct call.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
for _, param := range callback.Params {
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
}
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range callback.Params {
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
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)
}
// 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(c.i8ptrType))
// 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() {
// 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))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
}
// Call real function.
c.createCall(callback.LLVMFn, forwardParams, "")
b.createCall(callback.LLVMFn, forwardParams, "")
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
fn := b.ir.GetFunction(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++ {
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
}
valueTypes = append(valueTypes, c.i8ptrType) // closure
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
valueTypes = append(valueTypes, b.i8ptrType) // closure
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := c.builder.CreateLoad(gep, "param")
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Call deferred function.
c.createCall(fn.LLVMFn, forwardParams, "")
b.createCall(fn.LLVMFn, forwardParams, "")
case *ssa.Builtin:
db := b.deferBuiltinFuncs[callback]
//Get parameter types
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
//Get signature from call results
params := callback.Type().Underlying().(*types.Signature).Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
}
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
var forwardParams []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)
}
b.createRuntimeCall(db.funcName, forwardParams, "")
default:
panic("unknown deferred function type")
}
// Branch back to the start of the loop.
c.builder.CreateBr(loophead)
b.CreateBr(loophead)
}
// Create default unreachable block:
// default:
// unreachable
// }
c.builder.SetInsertPointAtEnd(unreachable)
c.builder.CreateUnreachable()
b.SetInsertPointAtEnd(unreachable)
b.CreateUnreachable()
// End of loop.
c.builder.SetInsertPointAtEnd(end)
b.SetInsertPointAtEnd(end)
}
+5 -2
View File
@@ -1,5 +1,7 @@
package compiler
// This file contains some utility functions related to error handling.
import (
"go/scanner"
"go/token"
@@ -9,7 +11,8 @@ import (
"tinygo.org/x/go-llvm"
)
func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
// 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,
Pos: pos,
@@ -17,7 +20,7 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
}
}
func (c *Compiler) addError(pos token.Pos, msg string) {
func (c *compilerContext) addError(pos token.Pos, msg string) {
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
}
+41 -64
View File
@@ -6,53 +6,26 @@ package compiler
import (
"go/types"
"github.com/tinygo-org/tinygo/compileopts"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
type funcValueImplementation int
const (
funcValueNone funcValueImplementation = iota
// A func value is implemented as a pair of pointers:
// {context, function pointer}
// where the context may be a pointer to a heap-allocated struct containing
// the free variables, or it may be undef if the function being pointed to
// doesn't need a context. The function pointer is a regular function
// pointer.
funcValueDoubleword
// As funcValueDoubleword, but with the function pointer replaced by a
// unique ID per function signature. Function values are called by using a
// switch statement and choosing which function to call.
funcValueSwitch
)
// funcImplementation picks an appropriate func value implementation for the
// target.
func (c *Compiler) funcImplementation() funcValueImplementation {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
switch c.Scheduler() {
case "coroutines":
return funcValueSwitch
case "tasks":
return funcValueDoubleword
default:
panic("unknown scheduler type")
}
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
return b.compilerContext.createFuncValue(b.Builder, funcPtr, context, sig)
}
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
var funcValueScalar llvm.Value
switch c.funcImplementation() {
case funcValueDoubleword:
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
case funcValueSwitch:
case compileopts.FuncValueSwitch:
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
@@ -73,35 +46,35 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
}
funcValueType := c.getFuncType(sig)
funcValue := llvm.Undef(funcValueType)
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
funcValue = builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
return funcValue
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation.
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
return c.builder.CreateExtractValue(funcValue, 1, "")
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
return b.CreateExtractValue(funcValue, 1, "")
}
// extractFuncContext extracts the context pointer from this function value. It
// is a cheap operation.
func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
return c.builder.CreateExtractValue(funcValue, 0, "")
func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
return b.CreateExtractValue(funcValue, 0, "")
}
// decodeFuncValue extracts the context and the function pointer from this func
// value. This may be an expensive operation.
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = c.builder.CreateExtractValue(funcValue, 0, "")
switch c.funcImplementation() {
case funcValueDoubleword:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch:
llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getTypeCode(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
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:
funcPtr = b.CreateExtractValue(funcValue, 1, "")
case compileopts.FuncValueSwitch:
llvmSig := b.getRawFuncType(sig)
sigGlobal := b.getTypeCode(sig)
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
default:
panic("unimplemented func value variant")
}
@@ -109,12 +82,12 @@ func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (
}
// getFuncType returns the type of a func value given a signature.
func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
switch c.funcImplementation() {
case funcValueDoubleword:
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case funcValueSwitch:
case compileopts.FuncValueSwitch:
return c.getLLVMRuntimeType("funcValue")
default:
panic("unimplemented func value variant")
@@ -122,7 +95,7 @@ func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
}
// getRawFuncType returns a LLVM function pointer type for a given signature.
func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// Get the return type.
var returnType llvm.Type
switch typ.Results().Len() {
@@ -152,11 +125,15 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
// The receiver is not an interface, but a i8* type.
recv = c.i8ptrType
}
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
for _, info := range expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
}
for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(typ.Params().At(i).Type())
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
for _, info := range expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
}
// All functions take these parameters at the end.
paramTypes = append(paramTypes, c.i8ptrType) // context
@@ -168,24 +145,24 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
// parseMakeClosure makes a function value (with context) from the given
// closure expression.
func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.Value, error) {
func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables")
}
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
f := b.ir.GetFunction(expr.Fn.(*ssa.Function))
// Collect all bound variables.
boundVars := make([]llvm.Value, len(expr.Bindings))
for i, binding := range expr.Bindings {
// The context stores the bound variables.
llvmBoundVar := c.getValue(frame, binding)
llvmBoundVar := b.getValue(binding)
boundVars[i] = llvmBoundVar
}
// Store the bound variables in a single object, allocating it on the heap
// if necessary.
context := c.emitPointerPack(boundVars)
context := b.emitPointerPack(boundVars)
// Create the closure.
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
return b.createFuncValue(f.LLVMFn, context, f.Signature), nil
}
+23 -17
View File
@@ -12,53 +12,59 @@ import (
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
// one of the expressions that need this.
func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) {
// There are uses of this expression, Make sure the pointers
// are tracked during GC.
switch expr := expr.(type) {
case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap:
// These values are always of pointer type in IR.
c.trackPointer(value)
b.trackPointer(value)
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
if !value.IsNil() {
c.trackValue(value)
b.trackValue(value)
}
case *ssa.Select:
if alloca, ok := frame.selectRecvBuf[expr]; ok {
if alloca, ok := b.selectRecvBuf[expr]; ok {
if alloca.IsAUndefValue().IsNil() {
c.trackPointer(alloca)
b.trackPointer(alloca)
}
}
case *ssa.UnOp:
switch expr.Op {
case token.MUL:
// Pointer dereference.
c.trackValue(value)
b.trackValue(value)
case token.ARROW:
// Channel receive operator.
// It's not necessary to look at commaOk here, because in that
// case it's just an aggregate and trackValue will extract the
// pointer in there (if there is one).
c.trackValue(value)
b.trackValue(value)
}
case *ssa.BinOp:
switch expr.Op {
case token.ADD:
// String concatenation.
b.trackValue(value)
}
}
}
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
// individual pointers
func (c *Compiler) trackValue(value llvm.Value) {
func (b *builder) trackValue(value llvm.Value) {
typ := value.Type()
switch typ.TypeKind() {
case llvm.PointerTypeKind:
c.trackPointer(value)
b.trackPointer(value)
case llvm.StructTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.StructElementTypesCount()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
}
case llvm.ArrayTypeKind:
if !typeHasPointers(typ) {
@@ -66,19 +72,19 @@ func (c *Compiler) trackValue(value llvm.Value) {
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
}
}
}
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
// first if needed. The input value must be of LLVM pointer type.
func (c *Compiler) trackPointer(value llvm.Value) {
if value.Type() != c.i8ptrType {
value = c.builder.CreateBitCast(value, c.i8ptrType, "")
func (b *builder) trackPointer(value llvm.Value) {
if value.Type() != b.i8ptrType {
value = b.CreateBitCast(value, b.i8ptrType, "")
}
c.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
b.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
}
// typeHasPointers returns whether this type is a pointer or contains pointers.
File diff suppressed because it is too large Load Diff
+90 -37
View File
@@ -3,34 +3,46 @@ package compiler
// This file implements the 'go' keyword to start a new goroutine. See
// goroutine-lowering.go for more details.
import "tinygo.org/x/go-llvm"
import (
"go/token"
// emitStartGoroutine starts a new goroutine with the provided function pointer
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"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 (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
switch c.Scheduler() {
case "tasks":
paramBundle := c.emitPointerPack(params)
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
calleeValue := c.createGoroutineStartWrapper(funcPtr)
c.createRuntimeCall("startGoroutine", []llvm.Value{calleeValue, paramBundle}, "")
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
paramBundle := b.emitPointerPack(params)
var callee, stackSize llvm.Value
switch b.Scheduler() {
case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, 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")
} 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)
}
case "coroutines":
// We roundtrip through runtime.makeGoroutine as a signal (to find these
// calls) and to break any optimizations LLVM will try to do: they are
// invalid if we called this as a regular function to be updated later.
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
c.createCall(calleeValue, append(params, llvm.ConstPointerNull(c.i8ptrType)), "")
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
// There is no goroutine stack size: coroutines are used instead of
// stacks.
stackSize = llvm.Undef(b.uintptrType)
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())
}
@@ -52,36 +64,58 @@ func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) l
// 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 *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
var wrapper llvm.Value
builder := c.ctx.NewBuilder()
defer builder.Dispose()
if !fn.IsAFunction().IsNil() {
// See whether this wrapper has already been created. If so, return it.
name := fn.Name()
wrapper = c.mod.NamedFunction(name + "$gowrapper")
if !wrapper.IsNil() {
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
}
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// 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.PrivateLinkage)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
entry := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(entry)
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
Flags: 0, // ?
})
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
Name: "<goroutine wrapper>",
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: diFuncType,
LocalToUnit: true,
IsDefinition: true,
ScopeLine: 0,
Flags: llvm.FlagPrototyped,
Optimized: true,
})
wrapper.SetSubprogram(difunc)
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
// Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes()
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-1])
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
params = append(params, llvm.Undef(c.i8ptrType))
// Create the call.
c.builder.CreateCall(fn, params, "")
builder.CreateCall(fn, params, "")
} else {
// For a function pointer like this:
@@ -101,22 +135,41 @@ func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
// With a bit of luck, identical wrapper functions like these can be
// merged into one.
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, ".gowrapper", wrapperType)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
entry := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(entry)
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
Flags: 0, // ?
})
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
Name: "<goroutine wrapper>",
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: diFuncType,
LocalToUnit: true,
IsDefinition: true,
ScopeLine: 0,
Flags: llvm.FlagPrototyped,
Optimized: true,
})
wrapper.SetSubprogram(difunc)
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
// Get the list of parameters, with the extra parameters at the end.
paramTypes := fn.Type().ElementType().ParamTypes()
paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes)
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
// Get the function pointer.
fnPtr := params[len(params)-1]
@@ -126,13 +179,13 @@ func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call.
c.builder.CreateCall(fnPtr, params, "")
builder.CreateCall(fnPtr, params, "")
}
// Finish the function. Every basic block must end in a terminator, and
// because goroutines never return a value we can simply return void.
c.builder.CreateRetVoid()
builder.CreateRetVoid()
// Return a ptrtoint of the wrapper, not the function itself.
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
return builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
+96 -51
View File
@@ -13,27 +13,6 @@ import (
"tinygo.org/x/go-llvm"
)
// This is a compiler builtin, which reads the given register by name:
//
// func ReadRegister(name string) uintptr
//
// The register name must be a constant, for example "sp".
func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
var asm string
switch name {
case "device/arm.ReadRegister":
asm = "mov $0, " + regname
case "device/riscv.ReadRegister":
asm = "mv $0, " + regname
default:
panic("unknown architecture")
}
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
}
// This is a compiler builtin, which emits a piece of inline assembly with no
// operands or return values. It is useful for trivial instructions, like wfi in
// ARM or sleep in AVR.
@@ -41,18 +20,18 @@ func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value,
// func Asm(asm string)
//
// The provided assembly must be a constant.
func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
// Magic function: insert inline assembly instead of calling it.
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false)
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{}, false)
asm := constant.StringVal(args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
return b.CreateCall(target, nil, ""), nil
}
// This is a compiler builtin, which allows assembly to be called in a flexible
// way.
//
// func AsmFull(asm string, regs map[string]interface{})
// func AsmFull(asm string, regs map[string]interface{}) uintptr
//
// The asm parameter must be a constant string. The regs parameter must be
// provided immediately. For example:
@@ -63,27 +42,27 @@ func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
// "value": 1
// "result": &dest,
// })
func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) {
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
registers := map[string]llvm.Value{}
registerMap := instr.Args[1].(*ssa.MakeMap)
for _, r := range *registerMap.Referrers() {
switch r := r.(type) {
case *ssa.DebugRef:
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.Block() {
return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block")
if registerMap, ok := instr.Args[1].(*ssa.MakeMap); ok {
for _, r := range *registerMap.Referrers() {
switch r := r.(type) {
case *ssa.DebugRef:
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.Block() {
return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block")
}
key := constant.StringVal(r.Key.(*ssa.Const).Value)
registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X)
case *ssa.Call:
if r.Common() == instr {
break
}
default:
return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
}
key := constant.StringVal(r.Key.(*ssa.Const).Value)
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X)
case *ssa.Call:
if r.Common() == instr {
break
}
default:
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
}
}
// TODO: handle dollar signs in asm string
@@ -92,13 +71,22 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
argTypes := []llvm.Type{}
args := []llvm.Value{}
constraints := []string{}
hasOutput := false
asmString = regexp.MustCompile("\\{\\}").ReplaceAllStringFunc(asmString, func(s string) string {
hasOutput = true
return "$0"
})
if hasOutput {
constraints = append(constraints, "=&r")
registerNumbers[""] = 0
}
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
// TODO: skip strings like {r4} etc. that look like ARM push/pop
// instructions.
name := s[1 : len(s)-1]
if _, ok := registers[name]; !ok {
if err == nil {
err = c.makeError(instr.Pos(), "unknown register name: "+name)
err = b.makeError(instr.Pos(), "unknown register name: "+name)
}
return s
}
@@ -112,7 +100,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
case llvm.PointerTypeKind:
constraints = append(constraints, "*m")
default:
err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
err = b.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
return s
}
}
@@ -121,9 +109,21 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
if err != nil {
return llvm.Value{}, err
}
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
var outputType llvm.Type
if hasOutput {
outputType = b.uintptrType
} else {
outputType = b.ctx.VoidType()
}
fnType := llvm.FunctionType(outputType, argTypes, false)
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
return c.builder.CreateCall(target, args, ""), nil
result := b.CreateCall(target, args, "")
if hasOutput {
return result, nil
} else {
// Make sure we return something valid.
return llvm.ConstInt(b.uintptrType, 0, false), nil
}
}
// This is a compiler builtin which emits an inline SVCall instruction. It can
@@ -137,7 +137,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
//
// The num parameter must be a constant. All other parameters may be any scalar
// value supported by LLVM inline assembly.
func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error) {
func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -150,7 +150,7 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
}
llvmValue := c.getValue(frame, arg)
llvmValue := b.getValue(arg)
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
@@ -158,7 +158,52 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
// clobbered. r0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{r1},~{r2},~{r3}"
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
return c.builder.CreateCall(target, llvmArgs, ""), nil
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
// func (csr CSR) Set(uintptr)
// func (csr CSR) SetBits(uintptr) uintptr
// func (csr CSR) ClearBits(uintptr) uintptr
//
// The csr parameter (method receiver) must be a constant. Other parameter can
// be any value.
func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) {
csrConst, ok := call.Args[0].(*ssa.Const)
if !ok {
return llvm.Value{}, b.makeError(call.Pos(), "CSR must be constant")
}
csr := csrConst.Uint64()
switch name := call.StaticCallee().Name(); name {
case "Get":
// Note that this instruction may have side effects, and thus must be
// marked as such.
fnType := llvm.FunctionType(b.uintptrType, nil, false)
asm := fmt.Sprintf("csrr $0, %d", csr)
target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0)
return b.CreateCall(target, nil, ""), nil
case "Set":
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrw %d, $0", csr)
target := llvm.InlineAsm(fnType, asm, "r", true, false, 0)
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
case "SetBits":
// Note: it may be possible to optimize this to csrrsi in many cases.
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrrs $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
case "ClearBits":
// Note: it may be possible to optimize this to csrrci in many cases.
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrrc $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name)
}
}
+101 -101
View File
@@ -16,35 +16,35 @@ import (
"tinygo.org/x/go-llvm"
)
// parseMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
// createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
// It tries to put the type in the interface value, but if that's not possible,
// it will do an allocation of the right size and put that in the interface
// value field.
//
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := c.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := c.getTypeCode(typ)
itfMethodSetGlobal := c.getTypeMethodSet(typ)
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
// An interface value is a {typecode, value} tuple named runtime._interface.
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 := c.getLLVMRuntimeType("typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
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 := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
itfTypeCode := b.CreatePtrToInt(itfConcreteTypeGlobal, b.uintptrType, "")
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = b.CreateInsertValue(itf, itfValue, 1, "")
return itf
}
// getTypeCode returns a reference to a type code.
// It returns a pointer to an external global which should be replaced with the
// real type in the interface lowering pass.
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
globalName := "reflect/types.type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
@@ -91,7 +91,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
// makeStructTypeFields creates a new global that stores all type information
// related to this struct type, and returns the resulting global. This global is
// actually an array of all the fields in the structs.
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
// The global is an array of runtime.structField structs.
runtimeStructField := c.getLLVMRuntimeType("structField")
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
@@ -228,7 +228,7 @@ func getTypeCodeName(t types.Type) string {
// getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass.
func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$methodset")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
@@ -271,8 +271,13 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
// getInterfaceMethodSet returns a global variable with the method set of the
// given named interface type. This method set is used by the interface lowering
// pass.
func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$interface")
func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
name := typ.String()
if _, ok := typ.(*types.Named); !ok {
// Anonymous interface.
name = "reflect/types.interface:" + name
}
global := c.mod.NamedGlobal(name + "$interface")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
// method set already exist, return it
@@ -287,7 +292,7 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
}
value := llvm.ConstArray(c.i8ptrType, methods)
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.PrivateLinkage)
@@ -297,7 +302,7 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
// getMethodSignature returns a global variable which is a reference to an
// external *i8 indicating the indicating the signature of this method. It is
// used during the interface lowering pass.
func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
signature := ir.MethodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() {
@@ -307,18 +312,18 @@ func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
return signatureGlobal
}
// parseTypeAssert will emit the code for a typeassert, used in if statements
// createTypeAssert will emit the code for a typeassert, used in if statements
// and in type switches (Go SSA does not have type switches, only if/else
// chains). Note that even though the Go SSA does not contain type switches,
// LLVM will recognize the pattern and make it a real switch in many cases.
//
// Type asserts on concrete types are trivial: just compare type numbers. Type
// asserts on interfaces are more difficult, see the comments in the function.
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
itf := c.getValue(frame, expr.X)
assertedType := c.getLLVMType(expr.AssertedType)
func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
itf := b.getValue(expr.X)
assertedType := b.getLLVMType(expr.AssertedType)
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type")
commaOk := llvm.Value{}
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type.
@@ -329,15 +334,15 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
// the main Go compiler, where the runtime checks whether the type
// implements each method of the interface. See:
// https://research.swtch.com/interfaces
methodSet := c.getInterfaceMethodSet(expr.AssertedType.(*types.Named))
commaOk = c.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
methodSet := b.getInterfaceMethodSet(expr.AssertedType)
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
} else {
// 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 := c.getTypeCode(expr.AssertedType)
commaOk = c.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType)
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
}
// Add 2 new basic blocks (that should get optimized away): one for the
@@ -351,15 +356,15 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
// typeassert should return a zero value, not an incorrectly casted
// value.
prevBlock := c.builder.GetInsertBlock()
okBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.ok")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
c.builder.CreateCondBr(commaOk, okBlock, nextBlock)
prevBlock := b.GetInsertBlock()
okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was
// successful.
c.builder.SetInsertPointAtEnd(okBlock)
b.SetInsertPointAtEnd(okBlock)
var valueOk llvm.Value
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type. Easy: just return the same
@@ -368,74 +373,70 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
} else {
// Type assert on concrete type. Extract the underlying type from
// the interface (but only after checking it matches).
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
valueOk = b.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
}
c.builder.CreateBr(nextBlock)
b.CreateBr(nextBlock)
// Continue after the if statement.
c.builder.SetInsertPointAtEnd(nextBlock)
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
b.SetInsertPointAtEnd(nextBlock)
phi := b.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
if expr.CommaOk {
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple
tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
return tuple
} else {
// This is kind of dirty as the branch above becomes mostly useless,
// but hopefully this gets optimized away.
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
return phi
}
}
// getInvokeCall creates and returns the function pointer and parameters of an
// interface call. It can be used in a call or defer instruction.
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf := c.getValue(frame, instr.Value) // interface
llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature))
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
// getInvokePtr creates an interface function pointer lookup for the specified invoke instruction, using a specified typecode.
func (b *builder) getInvokePtr(instr *ssa.CallCommon, typecode llvm.Value) llvm.Value {
llvmFnType := b.getRawFuncType(instr.Method.Type().(*types.Signature))
values := []llvm.Value{
typecode,
c.getInterfaceMethodSet(instr.Value.Type().(*types.Named)),
c.getMethodSignature(instr.Method),
b.getInterfaceMethodSet(instr.Value.Type()),
b.getMethodSignature(instr.Method),
}
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
fnCast := c.builder.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func")
return b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
}
// getInvokeCall creates and returns the function pointer and parameters of an
// interface call.
func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf := b.getValue(instr.Value) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.typecode")
fnCast := b.getInvokePtr(instr, typecode)
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
args := []llvm.Value{receiverValue}
for _, arg := range instr.Args {
args = append(args, c.getValue(frame, arg))
args = append(args, b.getValue(arg))
}
// Add the context parameter. An interface call never takes a context but we
// have to supply the parameter anyway.
args = append(args, llvm.Undef(c.i8ptrType))
args = append(args, llvm.Undef(b.i8ptrType))
// Add the parent goroutine handle.
args = append(args, llvm.Undef(c.i8ptrType))
args = append(args, llvm.Undef(b.i8ptrType))
return fnCast, args
}
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
// createInterfaceInvokeWrapper. The former is called during IR construction
// itself and the latter is called when finishing up the IR.
type interfaceInvokeWrapper struct {
fn *ir.Function
wrapper llvm.Value
receiverType llvm.Type
}
// Wrap an interface method function pointer. The wrapper takes in a pointer to
// the underlying value, dereferences it, and calls the real method. This
// wrapper is only needed when the interface value actually doesn't fit in a
// pointer and a pointer to the value must be created.
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be
// invoked from an interface. The wrapper takes in a pointer to the underlying
// 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"
wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() {
@@ -445,7 +446,10 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// Get the expanded receiver type.
receiverType := c.getLLVMType(f.Params[0].Type())
expandedReceiverType := c.expandFormalParamType(receiverType)
var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
}
// Does this method even need any wrapping?
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {
@@ -464,41 +468,37 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
if f.LLVMFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle")
}
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
fn: f,
wrapper: wrapper,
receiverType: receiverType,
})
return wrapper
}
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
// see that function for details.
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
wrapper := state.wrapper
fn := state.fn
receiverType := state.receiverType
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
// Create a new builder just to create this wrapper.
b := builder{
compilerContext: c,
Builder: c.ctx.NewBuilder(),
}
defer b.Builder.Dispose()
// add debug info if needed
if c.Debug() {
pos := c.ir.Program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
pos := c.ir.Program.Fset.Position(f.Pos())
difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
// set up IR builder
block := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(block)
block := b.ctx.AddBasicBlock(wrapper, "entry")
b.SetInsertPointAtEnd(block)
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
c.builder.CreateCall(fn.LLVMFn, params, "")
c.builder.CreateRetVoid()
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, "")
b.CreateRetVoid()
} else {
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
c.builder.CreateRet(ret)
ret := b.CreateCall(f.LLVMFn, params, "ret")
b.CreateRet(ret)
}
return wrapper
}
+92
View File
@@ -0,0 +1,92 @@
package compiler
import (
"strconv"
"strings"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
// will be lowered to a real interrupt during interrupt lowering.
//
// This two-stage approach allows unused interrupts to be optimized away if
// necessary.
func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, error) {
// Get the interrupt number, which must be a compile-time constant.
id, ok := instr.Args[0].(*ssa.Const)
if !ok {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt ID is not a constant")
}
// Get the func value, which also must be a compile time constant.
// Note that bound functions are allowed if the function has a pointer
// receiver and is a global. This is rather strict but still allows for
// idiomatic Go code.
funcValue := b.getValue(instr.Args[1])
if funcValue.IsAConstant().IsNil() {
// Try to determine the cause of the non-constantness for a nice error
// message.
switch instr.Args[1].(type) {
case *ssa.MakeClosure:
// This may also be a bound method.
return llvm.Value{}, b.makeError(instr.Pos(), "closures are not supported in interrupt.New")
}
// Fall back to a generic error.
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
}
// 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()
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")
}
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
global.SetLinkage(llvm.PrivateLinkage)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
initializer := llvm.ConstNull(globalLLVMType)
initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{1, 0})
global.SetInitializer(initializer)
// Add debug info to the interrupt global.
if b.Debug() {
pos := b.ir.Program.Fset.Position(instr.Pos())
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
LinkageName: globalName,
File: b.getDIFile(pos.Filename),
Line: pos.Line,
Type: b.getDIType(globalType),
Expr: b.dibuilder.CreateExpression(nil),
LocalToUnit: false,
})
global.AddMetadata(0, diglobal)
}
// Create the runtime/interrupt.Interrupt type. It is a struct with a single
// member of type int.
num := llvm.ConstPtrToInt(global, b.intType)
interrupt := llvm.ConstNamedStruct(b.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
// Add dummy "use" call for AVR, because interrupts may be used even though
// they are never referenced again. This is unlike Cortex-M or the RISC-V
// 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") {
useFn := b.mod.NamedFunction("runtime/interrupt.use")
if useFn.IsNil() {
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType)
}
b.CreateCall(useFn, []llvm.Value{interrupt}, "")
}
return interrupt, nil
}
+50
View File
@@ -0,0 +1,50 @@
package compiler
// This file contains helper functions to create calls to LLVM intrinsics.
import (
"strconv"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createMemoryCopyCall creates a call to a builtin LLVM memcpy or memmove
// function, declaring this function if needed. These calls are treated
// specially by optimization passes possibly resulting in better generated code,
// and will otherwise be lowered to regular libc memcpy/memmove calls.
func (b *builder) createMemoryCopyCall(fn *ssa.Function, args []ssa.Value) (llvm.Value, error) {
fnName := "llvm." + fn.Name() + ".p0i8.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.i8ptrType, b.uintptrType, b.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
}
var params []llvm.Value
for _, param := range args {
params = append(params, b.getValue(param))
}
params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false))
b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil
}
// createMemoryZeroCall creates calls to llvm.memset.* to zero a block of
// memory, declaring the function if needed. These calls will be lowered to
// regular libc memset calls if they aren't optimized out in a different way.
func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
fnName := "llvm.memset.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.ctx.Int8Type(), b.uintptrType, b.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
}
params := []llvm.Value{
b.getValue(args[0]),
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
b.getValue(args[1]),
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
}
b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil
}
+41 -19
View File
@@ -1,7 +1,8 @@
package compiler
// This file implements a set of sanity checks for the IR that is generated.
// It can catch some mistakes that LLVM's verifier cannot.
// Package ircheck implements a checker for LLVM IR, that goes a bit further
// than the regular LLVM IR verifier. Note that it checks different things, so
// this is not a replacement for the LLVM verifier but does catch things that
// the LLVM verifier doesn't catch.
package ircheck
import (
"errors"
@@ -10,7 +11,11 @@ import (
"tinygo.org/x/go-llvm"
)
func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
type checker struct {
ctx llvm.Context
}
func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// prevent infinite recursion for self-referential types
if _, ok := checked[t]; ok {
return nil
@@ -81,7 +86,7 @@ func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specia
return nil
}
func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// check type
if err := c.checkType(v.Type(), types, specials); err != nil {
return fmt.Errorf("failed to verify type of value: %s", err.Error())
@@ -95,12 +100,30 @@ func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specia
return nil
}
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// check value properties
if err := c.checkValue(inst, types, specials); err != nil {
return errorAt(inst, err.Error())
}
// The alloca instruction can be present in every basic block. However,
// allocas in basic blocks other than the entry basic block have a number of
// problems:
// * They are hard to optimize, leading to potential missed optimizations.
// * They may cause stack overflows in loops that would otherwise be
// innocent.
// * They cause extra code to be generated, because it requires the use of
// a frame pointer.
// * Perhaps most importantly, the coroutine lowering pass of LLVM (as of
// LLVM 9) cannot deal with these allocas:
// https://llvm.org/docs/Coroutines.html
// Therefore, alloca instructions should be limited to the entry block.
if !inst.IsAAllocaInst().IsNil() {
if inst.InstructionParent() != inst.InstructionParent().Parent().EntryBasicBlock() {
return errorAt(inst, "internal error: non-static alloca")
}
}
// check operands
for i := 0; i < inst.OperandsCount(); i++ {
if err := c.checkValue(inst.Operand(i), types, specials); err != nil {
@@ -111,7 +134,7 @@ func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{
return nil
}
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
// check basic block value and type
var errs []error
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
@@ -128,7 +151,7 @@ func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struc
return errs
}
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
// check function value and type
var errs []error
if err := c.checkValue(fn, types, specials); err != nil {
@@ -143,26 +166,25 @@ func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, sp
return errs
}
func (c *Compiler) checkModule() []error {
// Module checks the given module and returns a slice of error, if there are
// any.
func Module(mod llvm.Module) []error {
// check for any context mismatches
var errs []error
switch {
case c.mod.Context() == c.ctx:
// this is correct
case c.mod.Context() == llvm.GlobalContext():
c := checker{
ctx: mod.Context(),
}
if c.ctx == llvm.GlobalContext() {
// somewhere we accidentally used the global context instead of a real context
errs = append(errs, errors.New("module uses global context"))
default:
// we used some other context by accident
errs = append(errs, fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
}
types := map[llvm.Type]struct{}{}
specials := map[llvm.TypeKind]llvm.Type{}
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
errs = append(errs, c.checkFunction(fn, types, specials)...)
}
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
for g := mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
if err := c.checkValue(g, types, specials); err != nil {
errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error()))
}
+48
View File
@@ -0,0 +1,48 @@
package ircheck
import (
"go/scanner"
"go/token"
"path/filepath"
"tinygo.org/x/go-llvm"
)
// errorAt returns an error value at the location of the instruction.
// The location information may not be complete as it depends on debug
// information in the IR.
func errorAt(inst llvm.Value, msg string) scanner.Error {
return scanner.Error{
Pos: getPosition(inst),
Msg: msg,
}
}
// getPosition returns the position information for the given value, as far as
// it is available.
func getPosition(val llvm.Value) token.Position {
if !val.IsAInstruction().IsNil() {
loc := val.InstructionDebugLoc()
if loc.IsNil() {
return token.Position{}
}
file := loc.LocationScope().ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.LocationLine()),
Column: int(loc.LocationColumn()),
}
} else if !val.IsAFunction().IsNil() {
loc := val.Subprogram()
if loc.IsNil() {
return token.Position{}
}
file := loc.ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.SubprogramLine()),
}
} else {
return token.Position{}
}
}
+10 -10
View File
@@ -24,39 +24,39 @@ func getUses(value llvm.Value) []llvm.Value {
}
// createTemporaryAlloca creates a new alloca in the entry block and adds
// lifetime start infromation in the IR signalling that the alloca won't be used
// lifetime start information in the IR signalling that the alloca won't be used
// before this point.
//
// This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it.
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
return llvmutil.CreateTemporaryAlloca(c.builder, c.mod, t, name)
func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
}
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
// llvm.lifetime.end intrinsic. It is commonly used together with
// createTemporaryAlloca.
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
llvmutil.EmitLifetimeEnd(c.builder, c.mod, ptr, size)
func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
llvmutil.EmitLifetimeEnd(b.Builder, b.mod, ptr, size)
}
// emitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(c.builder, c.mod, c.Config, values)
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.Config, values)
}
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes)
func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
return llvmutil.EmitPointerUnpack(b.Builder, b.mod, ptr, valueTypes)
}
// makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global.
// Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself.
func (c *Compiler) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
globalType := llvm.ArrayType(elementType, len(buf))
global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType)
+22
View File
@@ -12,6 +12,7 @@ import (
// EmitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
// 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 {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
@@ -42,6 +43,27 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
// it in an alloca first for bitcasting (store+bitcast+load).
packedAlloc, _, _ = CreateTemporaryAlloca(builder, mod, packedType, "")
} else {
// Check if the values are all constants.
constant := true
for _, v := range values {
if !v.IsConstant() {
constant = false
break
}
}
if constant {
// The data is known at compile time, so store it in a constant global.
// The global address is marked as unnamed, which allows LLVM to merge duplicates.
funcName := builder.GetInsertBlock().Parent().Name()
global := llvm.AddGlobal(mod, packedType, funcName+"$pack")
global.SetInitializer(ctx.ConstStruct(values, false))
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstBitCast(global, i8ptrType)
}
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(uintptrType, size, false)
alloc := mod.NamedFunction("runtime.alloc")
+108 -56
View File
@@ -6,115 +6,167 @@ import (
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType := c.getLLVMType(valueType)
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
// initializing an appropriately sized object.
func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
mapType := expr.Type().Underlying().(*types.Map)
keyType := mapType.Key().Underlying()
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
var llvmKeyType llvm.Type
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// String keys.
llvmKeyType = b.getLLVMType(keyType)
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = b.getLLVMType(keyType)
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = b.getLLVMRuntimeType("_interface")
}
keySize := b.targetData.TypeAllocSize(llvmKeyType)
valueSize := b.targetData.TypeAllocSize(llvmValueType)
llvmKeySize := llvm.ConstInt(b.ctx.Int8Type(), keySize, false)
llvmValueSize := llvm.ConstInt(b.ctx.Int8Type(), valueSize, false)
sizeHint := llvm.ConstInt(b.uintptrType, 8, false)
if expr.Reserve != nil {
sizeHint = b.getValue(expr.Reserve)
var err error
sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos())
if err != nil {
return llvm.Value{}, err
}
}
hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "")
return hashmap, nil
}
// createMapLookup returns the value in a map. It calls a runtime function
// depending on the map key type to load the map value and its comma-ok value.
func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType := b.getLLVMType(valueType)
// Allocate the memory for the resulting type. Do not zero this memory: it
// will be zeroed by the hashmap get implementation if the key is not
// present in the map.
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value")
mapValueAlloca, mapValuePtr, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
// We need the map size (with type uintptr) to pass to the hashmap*Get
// functions. This is necessary because those *Get functions are valid on
// nil maps, and they'll need to zero the value pointer by that number of
// bytes.
mapValueSize := mapValueAllocaSize
if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType)
}
// Do the lookup. How it is done depends on the key type.
var commaOkValue llvm.Value
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
params := []llvm.Value{m, key, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
// Store the key in an alloca, in the entry block to avoid dynamic stack
// growth.
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, mapKeyAlloca)
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca)
// Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} else {
// Not trivially comparable using memcmp.
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// Not trivially comparable using memcmp. Make it an interface instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface now.
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
}
// Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap.
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
mapValue := b.CreateLoad(mapValueAlloca, "")
b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize)
if commaOk {
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOkValue, 1, "")
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, mapValue, 0, "")
tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "")
return tuple, nil
} else {
return mapValue, nil
}
}
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value")
c.builder.CreateStore(value, valueAlloca)
// createMapUpdate updates a map key to a given value, by creating an
// appropriate runtime call.
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
b.CreateStore(value, valueAlloca)
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, valuePtr}
c.createRuntimeCall("hashmapStringSet", params, "")
b.createRuntimeCall("hashmapStringSet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr, valuePtr}
c.createRuntimeCall("hashmapBinarySet", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
} else {
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// Key is not trivially comparable, so compare it as an interface instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first.
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey, valuePtr}
b.createRuntimeCall("hashmapInterfaceSet", params, "")
}
c.emitLifetimeEnd(valuePtr, valueSize)
b.emitLifetimeEnd(valuePtr, valueSize)
}
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
// createMapDelete deletes a key from a map by calling the appropriate runtime
// function. It is the implementation of the Go delete() builtin.
func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key}
c.createRuntimeCall("hashmapStringDelete", params, "")
b.createRuntimeCall("hashmapStringDelete", params, "")
return nil
} else if hashmapIsBinaryKey(keyType) {
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr}
c.createRuntimeCall("hashmapBinaryDelete", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
return nil
} else {
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// Key is not trivially comparable, so compare it as an interface
// instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first.
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey}
b.createRuntimeCall("hashmapInterfaceDelete", params, "")
return nil
}
}
// Get FNV-1a hash of this string.
//
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
func hashmapHash(data []byte) uint32 {
var result uint32 = 2166136261 // FNV offset basis
for _, c := range data {
result ^= uint32(c)
result *= 16777619 // FNV prime
}
return result
}
// Get the topmost 8 bits of the hash, without using a special value (like 0).
func hashmapTopHash(hash uint32) uint8 {
tophash := uint8(hash >> 24)
if tophash < 1 {
// 0 means empty slot, so make it bigger.
tophash += 1
}
return tophash
}
// Returns true if this key type does not contain strings, interfaces etc., so
// can be compared with runtime.memequal.
func hashmapIsBinaryKey(keyType types.Type) bool {
-153
View File
@@ -1,153 +0,0 @@
package compiler
import (
"errors"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
// Run the LLVM optimizer over the module.
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []error {
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.SetSizeLevel(sizeLevel)
if inlinerThreshold != 0 {
builder.UseInlinerWithThreshold(inlinerThreshold)
}
builder.AddCoroutinePassesToExtensionPoints()
if c.PanicStrategy() == "trap" {
transform.ReplacePanicsWithTrap(c.mod) // -panic=trap
}
// run a check of all of our code
if c.VerifyIR() {
errs := c.checkModule()
if errs != nil {
return errs
}
}
// Run function passes for each function.
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
defer funcPasses.Dispose()
builder.PopulateFunc(funcPasses)
funcPasses.InitializeFunc()
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
if optLevel > 0 {
// Run some preparatory passes for the Go optimizer.
goPasses := llvm.NewPassManager()
defer goPasses.Dispose()
goPasses.AddGlobalOptimizerPass()
goPasses.AddConstantPropagationPass()
goPasses.AddAggressiveDCEPass()
goPasses.AddFunctionAttrsPass()
goPasses.Run(c.mod)
// Run Go-specific optimization passes.
transform.OptimizeMaps(c.mod)
transform.OptimizeStringToBytes(c.mod)
transform.OptimizeAllocs(c.mod)
transform.LowerInterfaces(c.mod)
if c.funcImplementation() == funcValueSwitch {
transform.LowerFuncValues(c.mod)
}
// After interfaces are lowered, there are many more opportunities for
// interprocedural optimizations. To get them to work, function
// attributes have to be updated first.
goPasses.Run(c.mod)
// Run TinyGo-specific interprocedural optimizations.
transform.OptimizeAllocs(c.mod)
transform.OptimizeStringToBytes(c.mod)
// Lower runtime.isnil calls to regular nil comparisons.
isnil := c.mod.NamedFunction("runtime.isnil")
if !isnil.IsNil() {
for _, use := range getUses(isnil) {
c.builder.SetInsertPointBefore(use)
ptr := use.Operand(0)
if !ptr.IsABitCastInst().IsNil() {
ptr = ptr.Operand(0)
}
nilptr := llvm.ConstPointerNull(ptr.Type())
icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
use.ReplaceAllUsesWith(icmp)
use.EraseFromParentAsInstruction()
}
}
err := c.LowerGoroutines()
if err != nil {
return []error{err}
}
} else {
// Must be run at any optimization level.
transform.LowerInterfaces(c.mod)
if c.funcImplementation() == funcValueSwitch {
transform.LowerFuncValues(c.mod)
}
err := c.LowerGoroutines()
if err != nil {
return []error{err}
}
}
if c.VerifyIR() {
if errs := c.checkModule(); errs != nil {
return errs
}
}
if err := c.Verify(); err != nil {
return []error{errors.New("optimizations caused a verification failure")}
}
if sizeLevel >= 2 {
// Set the "optsize" attribute to make slightly smaller binaries at the
// cost of some performance.
kind := llvm.AttributeKindID("optsize")
attr := c.ctx.CreateEnumAttribute(kind, 0)
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
fn.AddFunctionAttr(attr)
}
}
// After TinyGo-specific transforms have finished, undo exporting these functions.
for _, name := range c.getFunctionsUsedInTransforms() {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
fn.SetLinkage(llvm.InternalLinkage)
}
// Run function passes again, because without it, llvm.coro.size.i32()
// doesn't get lowered.
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
// Run module passes.
modPasses := llvm.NewPassManager()
defer modPasses.Dispose()
builder.Populate(modPasses)
modPasses.Run(c.mod)
hasGCPass := transform.AddGlobalsBitmap(c.mod)
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
if hasGCPass {
if err := c.Verify(); err != nil {
return []error{errors.New("GC pass caused a verification failure")}
}
}
return nil
}
+4 -4
View File
@@ -10,13 +10,13 @@ import (
// The original license can be found here:
// https://golang.org/LICENSE
type StdSizes struct {
type stdSizes struct {
IntSize int64
PtrSize int64
MaxAlign int64
}
func (s *StdSizes) Alignof(T types.Type) int64 {
func (s *stdSizes) Alignof(T types.Type) int64 {
// For arrays and structs, alignment is defined in terms
// of alignment of the elements and fields, respectively.
switch t := T.Underlying().(type) {
@@ -61,7 +61,7 @@ func (s *StdSizes) Alignof(T types.Type) int64 {
return a
}
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
func (s *stdSizes) Offsetsof(fields []*types.Var) []int64 {
offsets := make([]int64, len(fields))
var o int64
for i, f := range fields {
@@ -89,7 +89,7 @@ var basicSizes = [...]byte{
types.Complex128: 16,
}
func (s *StdSizes) Sizeof(T types.Type) int64 {
func (s *stdSizes) Sizeof(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Basic:
k := t.Kind()
+36 -6
View File
@@ -26,7 +26,7 @@ type globalInfo struct {
// loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals.
func (c *Compiler) loadASTComments(lprogram *loader.Program) {
func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
@@ -56,25 +56,55 @@ func (c *Compiler) loadASTComments(lprogram *loader.Program) {
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
// the LLVM IR if it has not been added already.
func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
info := c.getGlobalInfo(g)
llvmGlobal := c.mod.NamedGlobal(info.linkName)
if llvmGlobal.IsNil() {
llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem())
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)
}
if info.align > c.targetData.ABITypeAlignment(llvmType) {
llvmGlobal.SetAlignment(info.align)
// Set alignment from the //go:align comment.
var alignInBits uint32
if info.align < 0 || info.align&(info.align-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)
}
}
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())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: g.RelString(nil),
LinkageName: info.linkName,
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: c.getDIType(typ),
LocalToUnit: false,
Expr: c.dibuilder.CreateExpression(nil),
AlignInBits: alignInBits,
})
llvmGlobal.AddMetadata(0, diglobal)
}
}
return llvmGlobal
}
// getGlobalInfo returns some information about a specific global.
func (c *Compiler) getGlobalInfo(g *ssa.Global) globalInfo {
func (c *compilerContext) getGlobalInfo(g *ssa.Global) globalInfo {
info := globalInfo{}
if strings.HasPrefix(g.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
+45 -45
View File
@@ -10,14 +10,14 @@ import (
"tinygo.org/x/go-llvm"
)
// emitSyscall emits an inline system call instruction, depending on the target
// OS/arch.
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
num := c.getValue(frame, call.Args[0])
// createSyscall emits an inline system call instruction, depending on the
// target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
num := b.getValue(call.Args[0])
var syscallResult llvm.Value
switch {
case c.GOARCH() == "amd64":
if c.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.
@@ -28,13 +28,13 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
// > All system classes enter the kernel via the syscall instruction.
//
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
num = b.CreateOr(num, llvm.ConstInt(b.uintptrType, 0x2000000, false), "")
}
// Sources:
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
args := []llvm.Value{num}
argTypes := []llvm.Type{c.uintptrType}
argTypes := []llvm.Type{b.uintptrType}
// Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
constraints := "={rax},0"
@@ -50,21 +50,21 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{r12}",
"{r13}",
}[i]
llvmValue := c.getValue(frame, arg)
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH() == "386" && c.GOOS() == "linux":
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "386" && b.GOOS() == "linux":
// Sources:
// syscall(2) man page
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
args := []llvm.Value{num}
argTypes := []llvm.Type{c.uintptrType}
argTypes := []llvm.Type{b.uintptrType}
// Constraints will look something like:
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
constraints := "={eax},0"
@@ -77,14 +77,14 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{edi}",
"{ebp}",
}[i]
llvmValue := c.getValue(frame, arg)
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH() == "arm" && c.GOOS() == "linux":
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "arm" && b.GOOS() == "linux":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
args := []llvm.Value{}
@@ -102,21 +102,21 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{r5}",
"{r6}",
}[i]
llvmValue := c.getValue(frame, arg)
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
argTypes = append(argTypes, c.uintptrType)
argTypes = append(argTypes, b.uintptrType)
constraints += ",{r7}" // syscall number
for i := len(call.Args) - 1; i < 4; i++ {
// r0-r3 get clobbered after the syscall returns
constraints += ",~{r" + strconv.Itoa(i) + "}"
}
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH() == "arm64" && c.GOOS() == "linux":
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "arm64" && b.GOOS() == "linux":
// Source: syscall(2) man page.
args := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -132,12 +132,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{x4}",
"{x5}",
}[i]
llvmValue := c.getValue(frame, arg)
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
argTypes = append(argTypes, c.uintptrType)
argTypes = append(argTypes, b.uintptrType)
constraints += ",{x8}" // syscall number
for i := len(call.Args) - 1; i < 8; i++ {
// x0-x7 may get clobbered during the syscall following the aarch64
@@ -145,13 +145,13 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
constraints += ",~{x" + strconv.Itoa(i) + "}"
}
constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = c.builder.CreateCall(target, args, "")
syscallResult = b.CreateCall(target, args, "")
default:
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
}
switch c.GOOS() {
switch b.GOOS() {
case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno
// Pseudocode:
@@ -160,15 +160,15 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
// err = -syscallResult
// }
// return syscallResult, 0, err
zero := llvm.ConstInt(c.uintptrType, 0, false)
inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
zero := llvm.ConstInt(b.uintptrType, 0, false)
inrange1 := b.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "")
inrange2 := b.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(b.uintptrType, 0xfffffffffffff000, true), "") // -4096
hasError := b.CreateAnd(inrange1, inrange2, "")
errResult := b.CreateSelect(hasError, b.CreateSub(zero, syscallResult, ""), zero, "syscallError")
retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, zero, 1, "")
retval = b.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
case "darwin":
// Return values: r0, r1 uintptr, err Errno
@@ -178,15 +178,15 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
// err = syscallResult
// }
// return syscallResult, 0, err
zero := llvm.ConstInt(c.uintptrType, 0, false)
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
zero := llvm.ConstInt(b.uintptrType, 0, false)
hasError := b.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "")
errResult := b.CreateSelect(hasError, syscallResult, zero, "syscallError")
retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, zero, 1, "")
retval = b.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
default:
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
}
}
+13 -9
View File
@@ -8,19 +8,23 @@ import (
"tinygo.org/x/go-llvm"
)
func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
c.emitNilCheck(frame, addr, "deref")
val := c.builder.CreateLoad(addr, "")
// createVolatileLoad is the implementation of the intrinsic function
// runtime/volatile.LoadT().
func (b *builder) createVolatileLoad(instr *ssa.CallCommon) (llvm.Value, error) {
addr := b.getValue(instr.Args[0])
b.createNilCheck(instr.Args[0], addr, "deref")
val := b.CreateLoad(addr, "")
val.SetVolatile(true)
return val, nil
}
func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
val := c.getValue(frame, instr.Args[1])
c.emitNilCheck(frame, addr, "deref")
store := c.builder.CreateStore(val, addr)
// createVolatileStore is the implementation of the intrinsic function
// runtime/volatile.StoreT().
func (b *builder) createVolatileStore(instr *ssa.CallCommon) (llvm.Value, error) {
addr := b.getValue(instr.Args[0])
val := b.getValue(instr.Args[1])
b.createNilCheck(instr.Args[0], addr, "deref")
store := b.CreateStore(val, addr)
store.SetVolatile(true)
return llvm.Value{}, nil
}
+6 -6
View File
@@ -4,11 +4,11 @@ go 1.11
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/creack/goselect v0.1.0 // indirect
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac
github.com/chromedp/chromedp v0.5.3
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
go.bug.st/serial v1.0.0
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d
)
+45 -29
View File
@@ -1,34 +1,50 @@
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/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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/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/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/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 h1:mACY1anK6HNCZtm/DK2Rf2ZPHggVqeB0+7rY9Gl6wyI=
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/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/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=
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=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9 h1:d6rAX39a3C0pKrY5HcojEGyN8w9ocU0v7X28lC/TRKU=
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2 h1:Q5Hv3e5cLMGkiYwYgZL1Zrv6nb/EY+DJpRWrdO6ws6o=
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8 h1:9Bfvso+tTVQg16UzOA614NaYA4x8vsRBNtd3eBrXwp0=
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc=
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae h1:s8J5EyxCkHxXB08UI3gk9W9IS/ekizRvSX+PfZxnAB0=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
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=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
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=
+8
View File
@@ -18,6 +18,7 @@ var Keys = []string{
"GOROOT",
"GOPATH",
"GOCACHE",
"CGO_ENABLED",
"TINYGOROOT",
}
@@ -57,6 +58,13 @@ func Get(name string) string {
panic("could not find cache dir: " + err.Error())
}
return filepath.Join(dir, "tinygo")
case "CGO_ENABLED":
val := os.Getenv("CGO_ENABLED")
if val == "1" || val == "0" {
return val
}
// Default to enabling CGo.
return "1"
case "TINYGOROOT":
return sourceDir()
default:
+68
View File
@@ -0,0 +1,68 @@
package goenv
import (
"errors"
"fmt"
"io"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
)
// Version of TinyGo.
// Update this value before release of new version of software.
const Version = "0.15.0"
// GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
func GetGorootVersion(goroot string) (major, minor int, err error) {
s, err := GorootVersionString(goroot)
if err != nil {
return 0, 0, err
}
if s == "" || s[:2] != "go" {
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
}
parts := strings.Split(s[2:], ".")
if len(parts) < 2 {
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
}
// Ignore the errors, we don't really handle errors here anyway.
var trailing string
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
if n == 2 && err == io.EOF {
// Means there were no trailing characters (i.e., not an alpha/beta)
err = nil
}
if err != nil {
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
}
return
}
// GorootVersionString returns the version string as reported by the Go
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
// can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`")
matches := r.FindSubmatch(data)
if len(matches) != 2 {
return "", errors.New("Invalid go version output:\n" + string(data))
}
return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else {
return "", err
}
}
+5
View File
@@ -0,0 +1,5 @@
# Hooks for Docker Hub
Files in this directory are custom commands to be run during the different Docker Hub build phases.
See https://docs.docker.com/docker-hub/builds/advanced/#custom-build-phase-hooks
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
# Docker hub does a recursive clone, then checks the branch out,
# so when a PR adds a submodule (or updates it), it fails.
git submodule update --init
+21 -30
View File
@@ -13,55 +13,46 @@ import (
// errUnreachable is returned when an unreachable instruction is executed. This
// error should not be visible outside of the interp package.
var errUnreachable = errors.New("interp: unreachable executed")
// Unsupported is the specific error that is returned when an unsupported
// instruction is hit while trying to interpret all initializers.
type Unsupported struct {
ImportPath string
Inst llvm.Value
Pos token.Position
}
func (e Unsupported) Error() string {
// TODO: how to return the actual instruction string?
// It looks like LLVM provides no function for that...
return scanner.Error{
Pos: e.Pos,
Msg: "interp: unsupported instruction",
}.Error()
}
var errUnreachable = &Error{Err: errors.New("interp: unreachable executed")}
// 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) *Unsupported {
return &Unsupported{
ImportPath: e.packagePath,
Inst: inst,
Pos: getPosition(inst),
}
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Error {
return e.errorAt(inst, errors.New("interp: unsupported instruction"))
}
// ErrorLine is one line in a traceback. The position may be missing.
type ErrorLine struct {
Pos token.Position
Inst llvm.Value
}
// Error encapsulates compile-time interpretation errors with an associated
// import path. The errors may not have a precise location attached.
type Error struct {
ImportPath string
Errs []scanner.Error
Inst llvm.Value
Pos token.Position
Err error
Traceback []ErrorLine
}
// Error returns the string of the first error in the list of errors.
func (e Error) Error() string {
return e.Errs[0].Error()
func (e *Error) Error() string {
return e.Pos.String() + ": " + e.Err.Error()
}
// 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, msg string) Error {
return Error{
func (e *evalPackage) errorAt(inst llvm.Value, err error) *Error {
pos := getPosition(inst)
return &Error{
ImportPath: e.packagePath,
Errs: []scanner.Error{errorAt(inst, msg)},
Pos: pos,
Err: err,
Traceback: []ErrorLine{{pos, inst}},
}
}
+117 -32
View File
@@ -4,6 +4,7 @@ package interp
// functions.
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
@@ -21,7 +22,7 @@ type frame struct {
// 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) {
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)
@@ -94,10 +95,14 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
} else {
value = operand.Load()
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, "interp: load: type does not match")
return nil, nil, fr.errorAt(inst, errors.New("interp: load: type does not match"))
}
fr.locals[inst] = fr.getValue(value)
case !inst.IsAStoreInst().IsNil():
@@ -106,7 +111,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
if inst.IsVolatile() {
fr.builder.CreateStore(value.Value(), ptr.Value())
} else {
ptr.Store(value.Value())
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))
@@ -121,13 +129,16 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// Not a constant operation.
// This should be detected by the scanner, but isn't at the
// moment.
return nil, nil, fr.errorAt(inst, "todo: non-const gep")
return nil, nil, fr.errorAt(inst, errors.New("todo: non-const gep"))
}
indices[i] = uint32(operand.Value().ZExtValue())
}
result := value.GetElementPtr(indices)
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, "interp: gep: type does not match")
return nil, nil, fr.errorAt(inst, errors.New("interp: gep: type does not match"))
}
fr.locals[inst] = result
@@ -182,7 +193,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
}
// It is not possible in Go to bitcast a map value to a pointer.
return nil, nil, fr.errorAt(inst, "unimplemented: bitcast of map")
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(), "")}
@@ -264,7 +275,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
if elementCount == 1 {
fr.locals[resultInst] = result
} else {
fr.locals[resultInst] = result.GetElementPtr([]uint32{0, 0})
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
@@ -300,7 +315,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
// "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters.
m.PutString(keyBuf, keyLen, valPtr)
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)
@@ -321,15 +339,24 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.builder.CreateCall(callee, llvmParams, "")
continue
}
m.PutBinary(keyBuf, valPtr)
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 := getStringBytes(buf1Ptr, buf1Len.Value())
buf2 := getStringBytes(buf2Ptr, buf2Len.Value())
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 {
@@ -365,38 +392,64 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// 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...
dstArray = dstArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
srcArray = srcArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
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, "interp: slice dst element size does not match pointer type")
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, "interp: slice src element size does not match pointer type")
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, "interp: slice element types don't match")
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, "interp: trying to copy a slice with negative length?")
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
dstArray.Store(srcArray.Load())
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++
dstArray = dstArray.GetElementPtr([]uint32{1}).(*LocalValue)
dstArrayValue, err := dstArray.GetElementPtr([]uint32{1})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
dstArray = dstArrayValue.(*LocalValue)
// src++
srcArray = srcArray.GetElementPtr([]uint32{1}).(*LocalValue)
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 := getStringBytes(bufPtr, bufLen.Value())
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)
@@ -416,20 +469,35 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
fr.locals[inst] = &LocalValue{fr.Eval, ret}
case callee.Name() == "runtime.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, "interp: expected typecode to be a 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, "interp: expected method set in runtime.interfaceImplements to be a constant gep")
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, "interp: expected method set to be a constant gep")
return nil, nil, fr.errorAt(inst, errors.New("interp: expected method set to be a constant gep"))
}
methodSet = methodSet.Operand(0).Initializer()
@@ -529,6 +597,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// 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
}
}
@@ -547,7 +620,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = fr.getValue(newValue)
} else {
if len(indices) != 1 {
return nil, nil, fr.errorAt(inst, "interp: cannot handle extractvalue with not exactly 1 index")
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())}
}
@@ -560,10 +633,22 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = &LocalValue{fr.Eval, newValue}
} else {
if len(indices) != 1 {
return nil, nil, fr.errorAt(inst, "interp: cannot handle insertvalue with not exactly 1 index")
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
@@ -573,17 +658,17 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// 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, "expected an i1 in a branch instruction")
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, "interp: branch on a non-constant")
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, "interp: branch on a non-const-propagated constant expression")
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
@@ -591,7 +676,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
return nil, []llvm.Value{elseBB}, nil // else
default:
return nil, nil, fr.errorAt(inst, "branch was not true or false")
return nil, nil, fr.errorAt(inst, errors.New("branch was not true or false"))
}
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
// unconditional branch (goto)
+1 -1
View File
@@ -97,7 +97,7 @@ func Run(mod llvm.Module, debug bool) error {
// 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) {
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, *Error) {
fr := frame{
evalPackage: e,
fn: fn,
+1
View File
@@ -15,6 +15,7 @@ func TestInterp(t *testing.T) {
"slice-copy",
"consteval",
"map",
"interface",
} {
name := name // make tc local to this closure
t.Run(name, func(t *testing.T) {
+17 -2
View File
@@ -1,6 +1,7 @@
package interp
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
@@ -40,7 +41,7 @@ type sideEffectResult 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) {
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error) {
name := fn.Name()
switch {
case name == "runtime.alloc":
@@ -51,6 +52,8 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
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":
@@ -59,6 +62,10 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
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
}
@@ -97,7 +104,7 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
switch inst.InstructionOpcode() {
case llvm.IndirectBr, llvm.Invoke:
// Not emitted by the compiler.
return nil, e.errorAt(inst, "unknown instructions")
return nil, e.errorAt(inst, errors.New("unknown instructions"))
case llvm.Call:
child := inst.CalledValue()
if !child.IsAInlineAsm().IsNil() {
@@ -118,6 +125,14 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
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)
}
+2
View File
@@ -23,6 +23,8 @@ var scanTestTable = []struct {
{"callFunctionPointer", sideEffectAll, []string{"functionPointer"}},
{"getDirtyPointer", sideEffectLimited, nil},
{"storeToPointer", sideEffectLimited, nil},
{"callTypeAssert", sideEffectNone, nil},
{"callInterfaceImplements", sideEffectNone, nil},
}
func TestScan(t *testing.T) {
+28
View File
@@ -0,0 +1,28 @@
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.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: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*)
define void @runtime.initAll() unnamed_addr {
entry:
call void @main.init()
ret void
}
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)
store i1 %typecode, i1* @main.v1
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.v1 = local_unnamed_addr global i1 true
define void @runtime.initAll() unnamed_addr {
entry:
ret void
}
+17
View File
@@ -1,6 +1,11 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i64 }
declare i1 @runtime.typeAssert(i64, %runtime.typecodeID*, i8*, i8*)
declare i1 @runtime.interfaceImplements(i64, i8**)
define i64 @returnsConst() {
ret i64 0
}
@@ -59,3 +64,15 @@ define i64 @callFunctionPointer() {
%result = call i64 %fp()
ret i64 %result
}
define i1 @callTypeAssert() {
; Note: parameters are not realistic.
%ok = call i1 @runtime.typeAssert(i64 0, %runtime.typecodeID* null, i8* undef, i8* null)
ret i1 %ok
}
define i1 @callInterfaceImplements() {
; Note: parameters are not realistic.
%ok = call i1 @runtime.interfaceImplements(i64 0, i8** null)
ret i1 %ok
}
+13 -4
View File
@@ -1,6 +1,8 @@
package interp
import (
"errors"
"tinygo.org/x/go-llvm"
)
@@ -18,16 +20,23 @@ func getUses(value llvm.Value) []llvm.Value {
// getStringBytes loads the byte slice of a Go string represented as a
// {ptr, len} pair.
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
func getStringBytes(strPtr Value, strLen llvm.Value) ([]byte, error) {
if !strLen.IsConstant() {
panic("getStringBytes with a non-constant length")
return nil, errors.New("getStringBytes with a non-constant length")
}
buf := make([]byte, strLen.ZExtValue())
for i := range buf {
c := strPtr.GetElementPtr([]uint32{uint32(i)}).Load()
gep, err := strPtr.GetElementPtr([]uint32{uint32(i)})
if err != nil {
return nil, err
}
c, err := gep.Load()
if err != nil {
return nil, err
}
buf[i] = byte(c.ZExtValue())
}
return buf
return buf, nil
}
// getLLVMIndices converts an []uint32 into an []llvm.Value, for use in
+72 -47
View File
@@ -3,6 +3,7 @@ package interp
// This file provides a litte bit of abstraction around LLVM values.
import (
"errors"
"strconv"
"tinygo.org/x/go-llvm"
@@ -11,13 +12,13 @@ import (
// A Value is a LLVM value with some extra methods attached for easier
// interpretation.
type Value interface {
Value() llvm.Value // returns a LLVM value
Type() llvm.Type // equal to Value().Type()
IsConstant() bool // returns true if this value is a constant value
Load() llvm.Value // dereference a pointer
Store(llvm.Value) // store to a pointer
GetElementPtr([]uint32) Value // returns an interior pointer
String() string // string representation, for debugging
Value() llvm.Value // returns a LLVM value
Type() llvm.Type // equal to Value().Type()
IsConstant() bool // returns true if this value is a constant value
Load() (llvm.Value, error) // dereference a pointer
Store(llvm.Value) error // store to a pointer
GetElementPtr([]uint32) (Value, error) // returns an interior pointer
String() string // string representation, for debugging
}
// A type that simply wraps a LLVM constant value.
@@ -43,29 +44,32 @@ func (v *LocalValue) IsConstant() bool {
}
// Load loads a constant value if this is a constant pointer.
func (v *LocalValue) Load() llvm.Value {
func (v *LocalValue) Load() (llvm.Value, error) {
if !v.Underlying.IsAGlobalVariable().IsNil() {
return v.Underlying.Initializer()
return v.Underlying.Initializer(), nil
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr:
indices := v.getConstGEPIndices()
if indices[0] != 0 {
panic("invalid GEP")
return llvm.Value{}, errors.New("invalid GEP")
}
global := v.Eval.getValue(v.Underlying.Operand(0))
agg := global.Load()
return llvm.ConstExtractValue(agg, indices[1:])
agg, err := global.Load()
if err != nil {
return llvm.Value{}, err
}
return llvm.ConstExtractValue(agg, indices[1:]), nil
case llvm.BitCast:
panic("interp: load from a bitcast")
return llvm.Value{}, errors.New("interp: load from a bitcast")
default:
panic("interp: load from a constant")
return llvm.Value{}, errors.New("interp: load from a constant")
}
}
// Store stores to the underlying value if the value type is a pointer type,
// otherwise it panics.
func (v *LocalValue) Store(value llvm.Value) {
// otherwise it returns an error.
func (v *LocalValue) Store(value llvm.Value) error {
if !v.Underlying.IsAGlobalVariable().IsNil() {
if !value.IsConstant() {
v.MarkDirty()
@@ -73,45 +77,47 @@ func (v *LocalValue) Store(value llvm.Value) {
} else {
v.Underlying.SetInitializer(value)
}
return
return nil
}
if !value.IsConstant() {
v.MarkDirty()
v.Eval.builder.CreateStore(value, v.Underlying)
return
return nil
}
switch v.Underlying.Opcode() {
case llvm.GetElementPtr:
indices := v.getConstGEPIndices()
if indices[0] != 0 {
panic("invalid GEP")
return errors.New("invalid GEP")
}
global := &LocalValue{v.Eval, v.Underlying.Operand(0)}
agg := global.Load()
agg, err := global.Load()
if err != nil {
return err
}
agg = llvm.ConstInsertValue(agg, value, indices[1:])
global.Store(agg)
return
return global.Store(agg)
default:
panic("interp: store on a constant")
return errors.New("interp: store on a constant")
}
}
// GetElementPtr returns a GEP when the underlying value is of pointer type.
func (v *LocalValue) GetElementPtr(indices []uint32) Value {
func (v *LocalValue) GetElementPtr(indices []uint32) (Value, error) {
if !v.Underlying.IsAGlobalVariable().IsNil() {
int32Type := v.Underlying.Type().Context().Int32Type()
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
return &LocalValue{v.Eval, gep}
return &LocalValue{v.Eval, gep}, nil
}
if !v.Underlying.IsAConstantExpr().IsNil() {
switch v.Underlying.Opcode() {
case llvm.GetElementPtr, llvm.IntToPtr, llvm.BitCast:
int32Type := v.Underlying.Type().Context().Int32Type()
llvmIndices := getLLVMIndices(int32Type, indices)
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}, nil
}
}
panic("interp: unknown GEP")
return nil, errors.New("interp: unknown GEP")
}
// stripPointerCasts removes all const bitcasts from pointer values, if there
@@ -224,7 +230,11 @@ func (v *MapValue) Value() llvm.Value {
keyPtr := llvm.ConstExtractValue(llvmKey, []uint32{0})
keyLen := llvm.ConstExtractValue(llvmKey, []uint32{1})
keyPtrVal := v.Eval.getValue(keyPtr)
keyBuf = getStringBytes(keyPtrVal, keyLen)
var err error
keyBuf, err = getStringBytes(keyPtrVal, keyLen)
if err != nil {
panic(err) // TODO
}
} else if key.Type().TypeKind() == llvm.IntegerTypeKind {
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
n := key.Value().ZExtValue()
@@ -298,40 +308,45 @@ func (v *MapValue) IsConstant() bool {
}
// Load panics: maps are of reference type so cannot be dereferenced.
func (v *MapValue) Load() llvm.Value {
func (v *MapValue) Load() (llvm.Value, error) {
panic("interp: load from a map")
}
// Store panics: maps are of reference type so cannot be stored to.
func (v *MapValue) Store(value llvm.Value) {
panic("interp: store on a map")
// Store returns an error: maps are of reference type so cannot be stored to.
func (v *MapValue) Store(value llvm.Value) error {
// This must be a bug, but it might be helpful to indicate the location
// anyway.
return errors.New("interp: store on a map")
}
// GetElementPtr panics: maps are of reference type so their (interior)
// addresses cannot be calculated.
func (v *MapValue) GetElementPtr(indices []uint32) Value {
panic("interp: GEP on a map")
func (v *MapValue) GetElementPtr(indices []uint32) (Value, error) {
return nil, errors.New("interp: GEP on a map")
}
// PutString does a map assign operation, assuming that the map is of type
// map[string]T.
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) error {
if !v.Underlying.IsNil() {
panic("map already created")
return errors.New("map already created")
}
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
}
value := valPtr.Load()
value, err := valPtr.Load()
if err != nil {
return err
}
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
panic("interp: map store value type has the wrong size")
return errors.New("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
panic("interp: map store value type is inconsistent")
return errors.New("interp: map store value type is inconsistent")
}
}
@@ -344,26 +359,31 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
// TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value})
return nil
}
// PutBinary does a map assign operation.
func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) {
func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) error {
if !v.Underlying.IsNil() {
panic("map already created")
return errors.New("map already created")
}
if valPtr.Underlying.Opcode() == llvm.BitCast {
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
}
value := valPtr.Load()
value, err := valPtr.Load()
if err != nil {
return err
}
if v.ValueType.IsNil() {
v.ValueType = value.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
panic("interp: map store value type has the wrong size")
return errors.New("interp: map store value type has the wrong size")
}
} else {
if value.Type() != v.ValueType {
panic("interp: map store value type is inconsistent")
return errors.New("interp: map store value type is inconsistent")
}
}
@@ -374,21 +394,26 @@ func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) {
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
}
}
key := keyPtr.Load()
key, err := keyPtr.Load()
if err != nil {
return err
}
if v.KeyType.IsNil() {
v.KeyType = key.Type()
if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize {
panic("interp: map store key type has the wrong size")
return errors.New("interp: map store key type has the wrong size")
}
} else {
if key.Type() != v.KeyType {
panic("interp: map store key type is inconsistent")
return errors.New("interp: map store key type is inconsistent")
}
}
// TODO: avoid duplicate keys
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
v.Values = append(v.Values, &LocalValue{v.Eval, value})
return nil
}
// Get FNV-1a hash of this string.
+11 -91
View File
@@ -27,14 +27,13 @@ type Program struct {
// Function or method.
type Function struct {
*ssa.Function
LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export, go:interrupt
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
interrupt bool // go:interrupt
inline InlineType // go:inline
LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
inline InlineType // go:inline
}
// Interface type that is at some point used in a type assert (to check whether
@@ -64,73 +63,14 @@ const (
)
// Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
func NewProgram(lprogram *loader.Program) *Program {
program := lprogram.LoadSSA()
program.Build()
// Find the main package, which is a bit difficult when running a .go file
// directly.
mainPkg := program.ImportedPackage(mainPath)
if mainPkg == nil {
for _, pkgInfo := range program.AllPackages() {
if pkgInfo.Pkg.Name() == "main" {
if mainPkg != nil {
panic("more than one main package found")
}
mainPkg = pkgInfo
}
}
}
mainPkg := program.ImportedPackage(lprogram.MainPkg().ImportPath)
if mainPkg == nil {
panic("could not find main package")
}
// Make a list of packages in import order.
packageList := []*ssa.Package{}
packageSet := map[string]struct{}{}
worklist := []string{"runtime", mainPath}
for len(worklist) != 0 {
pkgPath := worklist[0]
var pkg *ssa.Package
if pkgPath == mainPath {
pkg = mainPkg // necessary for compiling individual .go files
} else {
pkg = program.ImportedPackage(pkgPath)
}
if pkg == nil {
// Non-SSA package (e.g. cgo).
packageSet[pkgPath] = struct{}{}
worklist = worklist[1:]
continue
}
if _, ok := packageSet[pkgPath]; ok {
// Package already in the final package list.
worklist = worklist[1:]
continue
}
unsatisfiedImports := make([]string, 0)
imports := pkg.Pkg.Imports()
for _, pkg := range imports {
if _, ok := packageSet[pkg.Path()]; ok {
continue
}
unsatisfiedImports = append(unsatisfiedImports, pkg.Path())
}
if len(unsatisfiedImports) == 0 {
// All dependencies of this package are satisfied, so add this
// package to the list.
packageList = append(packageList, pkg)
packageSet[pkgPath] = struct{}{}
worklist = worklist[1:]
} else {
// Prepend all dependencies to the worklist and reconsider this
// package (by not removing it from the worklist). At that point, it
// must be possible to add it to packageList.
worklist = append(unsatisfiedImports, worklist...)
}
}
p := &Program{
Program: program,
LoaderProgram: lprogram,
@@ -138,8 +78,8 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
functionMap: make(map[*ssa.Function]*Function),
}
for _, pkg := range packageList {
p.AddPackage(pkg)
for _, pkg := range lprogram.Sorted() {
p.AddPackage(program.ImportedPackage(pkg.ImportPath))
}
return p
@@ -243,18 +183,6 @@ func (f *Function) parsePragmas() {
f.inline = InlineHint
case "//go:noinline":
f.inline = InlineNone
case "//go:interrupt":
if len(parts) != 2 {
continue
}
name := parts[1]
if strings.HasSuffix(name, "_vect") {
// AVR vector naming
name = "__vector_" + name[:len(name)-5]
}
f.linkName = name
f.exported = true
f.interrupt = true
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
@@ -288,14 +216,6 @@ func (f *Function) IsExported() bool {
return f.exported || f.CName() != ""
}
// Return true for functions annotated with //go:interrupt. The function name is
// already customized in LinkName() to hook up in the interrupt vector.
//
// On some platforms (like AVR), interrupts need a special compiler flag.
func (f *Function) IsInterrupt() bool {
return f.interrupt
}
// Return the inline directive of this function.
func (f *Function) Inline() InlineType {
return f.inline
+14 -3
View File
@@ -1,6 +1,7 @@
package ir
import (
"errors"
"go/types"
"golang.org/x/tools/go/ssa"
@@ -58,7 +59,7 @@ func signature(sig *types.Signature) string {
// Simple pass that removes dead code. This pass makes later analysis passes
// more useful.
func (p *Program) SimpleDCE() {
func (p *Program) SimpleDCE() error {
// Unmark all functions.
for _, f := range p.Functions {
f.flag = false
@@ -66,13 +67,21 @@ func (p *Program) SimpleDCE() {
// Initial set of live functions. Include main.main, *.init and runtime.*
// functions.
main := p.mainPkg.Members["main"].(*ssa.Function)
main, ok := p.mainPkg.Members["main"].(*ssa.Function)
if !ok {
if p.mainPkg.Members["main"] == nil {
return errors.New("function main is undeclared in the main package")
} else {
return errors.New("cannot declare main - must be func")
}
}
runtimePkg := p.Program.ImportedPackage("runtime")
mathPkg := p.Program.ImportedPackage("math")
taskPkg := p.Program.ImportedPackage("internal/task")
p.GetFunction(main).flag = true
worklist := []*ssa.Function{main}
for _, f := range p.Functions {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || (f.Pkg == mathPkg && f.Pkg != nil) {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
if f.flag {
continue
}
@@ -135,4 +144,6 @@ func (p *Program) SimpleDCE() {
}
}
p.Functions = livefunctions
return nil
}
Submodule
+1
Submodule lib/picolibc added at 80528c684b
View File
Submodule
+1
Submodule lib/wasi-libc added at 215adc8ac9
+8 -22
View File
@@ -1,9 +1,6 @@
package loader
import (
"go/token"
"strings"
)
import "go/scanner"
// Errors contains a list of parser errors or a list of typechecker errors for
// the given package.
@@ -16,24 +13,13 @@ func (e Errors) Error() string {
return "could not compile: " + e.Errs[0].Error()
}
// ImportCycleErrors is returned when encountering an import cycle. The list of
// packages is a list from the root package to the leaf package that imports one
// of the packages in the list.
type ImportCycleError struct {
Packages []string
ImportPositions []token.Position
// Error is a regular error but with an added import stack. This is especially
// useful for debugging import cycle errors.
type Error struct {
ImportStack []string
Err scanner.Error
}
func (e *ImportCycleError) Error() string {
var msg strings.Builder
msg.WriteString("import cycle:\n\t")
msg.WriteString(strings.Join(e.Packages, "\n\t"))
msg.WriteString("\n at ")
for i, pos := range e.ImportPositions {
if i > 0 {
msg.WriteString(", ")
}
msg.WriteString(pos.String())
}
return msg.String()
func (e Error) Error() string {
return e.Err.Error()
}
+281
View File
@@ -0,0 +1,281 @@
package loader
// This file constructs a new temporary GOROOT directory by merging both the
// standard Go GOROOT and the GOROOT from TinyGo using symlinks.
import (
"crypto/sha512"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"sync"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
var gorootCreateMutex sync.Mutex
// GetCachedGoroot creates a new GOROOT by merging both the standard GOROOT and
// the GOROOT from TinyGo using lots of symbolic links.
func GetCachedGoroot(config *compileopts.Config) (string, error) {
goroot := goenv.Get("GOROOT")
if goroot == "" {
return "", errors.New("could not determine GOROOT")
}
tinygoroot := goenv.Get("TINYGOROOT")
if tinygoroot == "" {
return "", errors.New("could not determine TINYGOROOT")
}
// Determine the location of the cached GOROOT.
version, err := goenv.GorootVersionString(goroot)
if err != nil {
return "", err
}
// This hash is really a cache key, that contains (hopefully) enough
// information to make collisions unlikely during development.
// By including the Go version and TinyGo version, cache collisions should
// not happen outside of development.
hash := sha512.New512_256()
fmt.Fprintln(hash, goroot)
fmt.Fprintln(hash, version)
fmt.Fprintln(hash, goenv.Version)
fmt.Fprintln(hash, tinygoroot)
gorootsHash := hash.Sum(nil)
gorootsHashHex := hex.EncodeToString(gorootsHash[:])
cachedgorootName := "goroot-" + version + "-" + gorootsHashHex
cachedgoroot := filepath.Join(goenv.Get("GOCACHE"), cachedgorootName)
if needsSyscallPackage(config.BuildTags()) {
cachedgoroot += "-syscall"
}
// Do not try to create the cached GOROOT in parallel, that's only a waste
// of I/O bandwidth and thus speed. Instead, use a mutex to make sure only
// one goroutine does it at a time.
// This is not a way to ensure atomicity (a different TinyGo invocation
// could be creating the same directory), but instead a way to avoid
// creating it many times in parallel when running tests in parallel.
gorootCreateMutex.Lock()
defer gorootCreateMutex.Unlock()
if _, err := os.Stat(cachedgoroot); err == nil {
return cachedgoroot, nil
}
err = os.MkdirAll(goenv.Get("GOCACHE"), 0777)
if err != nil {
return "", err
}
tmpgoroot, err := ioutil.TempDir(goenv.Get("GOCACHE"), cachedgorootName+".tmp")
if err != nil {
return "", err
}
// Remove the temporary directory if it wasn't moved to the right place
// (for example, when there was an error).
defer os.RemoveAll(tmpgoroot)
for _, name := range []string{"bin", "lib", "pkg"} {
err = symlink(filepath.Join(goroot, name), filepath.Join(tmpgoroot, name))
if err != nil {
return "", err
}
}
err = mergeDirectory(goroot, tinygoroot, tmpgoroot, "", pathsToOverride(needsSyscallPackage(config.BuildTags())))
if err != nil {
return "", err
}
err = os.Rename(tmpgoroot, cachedgoroot)
if err != nil {
if os.IsExist(err) {
// Another invocation of TinyGo also seems to have created a GOROOT.
// Use that one instead. Our new GOROOT will be automatically
// deleted by the defer above.
return cachedgoroot, nil
}
if runtime.GOOS == "windows" && os.IsPermission(err) {
// On Windows, a rename with a destination directory that already
// exists does not result in an IsExist error, but rather in an
// access denied error. To be sure, check for this case by checking
// whether the target directory exists.
if _, err := os.Stat(cachedgoroot); err == nil {
return cachedgoroot, nil
}
}
return "", err
}
return cachedgoroot, nil
}
// mergeDirectory merges two roots recursively. The tmpgoroot is the directory
// that will be created by this call by either symlinking the directory from
// goroot or tinygoroot, or by creating the directory and merging the contents.
func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides map[string]bool) error {
if mergeSubdirs, ok := overrides[importPath+"/"]; ok {
if !mergeSubdirs {
// This directory and all subdirectories should come from the TinyGo
// root, so simply make a symlink.
newname := filepath.Join(tmpgoroot, "src", importPath)
oldname := filepath.Join(tinygoroot, "src", importPath)
return symlink(oldname, newname)
}
// Merge subdirectories. Start by making the directory to merge.
err := os.Mkdir(filepath.Join(tmpgoroot, "src", importPath), 0777)
if err != nil {
return err
}
// Symlink all files from TinyGo, and symlink directories from TinyGo
// that need to be overridden.
tinygoEntries, err := ioutil.ReadDir(filepath.Join(tinygoroot, "src", importPath))
if err != nil {
return err
}
for _, e := range tinygoEntries {
if e.IsDir() {
// A directory, so merge this thing.
err := mergeDirectory(goroot, tinygoroot, tmpgoroot, path.Join(importPath, e.Name()), overrides)
if err != nil {
return err
}
} else {
// A file, so symlink this.
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
oldname := filepath.Join(tinygoroot, "src", importPath, e.Name())
err := symlink(oldname, newname)
if err != nil {
return err
}
}
}
// Symlink all directories from $GOROOT that are not part of the TinyGo
// overrides.
gorootEntries, err := ioutil.ReadDir(filepath.Join(goroot, "src", importPath))
if err != nil {
return err
}
for _, e := range gorootEntries {
if !e.IsDir() {
// Don't merge in files from Go. Otherwise we'd end up with a
// weird syscall package with files from both roots.
continue
}
if _, ok := overrides[path.Join(importPath, e.Name())+"/"]; ok {
// Already included above, so don't bother trying to create this
// symlink.
continue
}
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
oldname := filepath.Join(goroot, "src", importPath, e.Name())
err := symlink(oldname, newname)
if err != nil {
return err
}
}
}
return nil
}
// needsSyscallPackage returns whether the syscall package should be overriden
// with the TinyGo version. This is the case on some targets.
func needsSyscallPackage(buildTags []string) bool {
for _, tag := range buildTags {
if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" {
return true
}
}
return false
}
// The boolean indicates whether to merge the subdirs. True means merge, false
// means use the TinyGo version.
func pathsToOverride(needsSyscallPackage bool) map[string]bool {
paths := map[string]bool{
"/": true,
"device/": false,
"examples/": false,
"internal/": true,
"internal/bytealg/": false,
"internal/reflectlite/": false,
"internal/task/": false,
"machine/": false,
"os/": true,
"reflect/": false,
"runtime/": false,
"sync/": true,
"testing/": true,
}
if needsSyscallPackage {
paths["syscall/"] = true // include syscall/js
}
return paths
}
// symlink creates a symlink or something similar. On Unix-like systems, it
// always creates a symlink. On Windows, it tries to create a symlink and if
// that fails, creates a hardlink or directory junction instead.
//
// Note that while Windows 10 does support symlinks and allows them to be
// created using os.Symlink, it requires developer mode to be enabled.
// Therefore provide a fallback for when symlinking is not possible.
// Unfortunately this fallback only works when TinyGo is installed on the same
// filesystem as the TinyGo cache and the Go installation (which is usually the
// C drive).
func symlink(oldname, newname string) error {
symlinkErr := os.Symlink(oldname, newname)
if runtime.GOOS == "windows" && symlinkErr != nil {
// Fallback for when developer mode is disabled.
// Note that we return the symlink error even if something else fails
// later on. This is because symlinks are the easiest to support
// (they're also used on Linux and MacOS) and enabling them is easy:
// just enable developer mode.
st, err := os.Stat(oldname)
if err != nil {
return symlinkErr
}
if st.IsDir() {
// Make a directory junction. There may be a way to do this
// programmatically, but it involves a lot of magic. Use the mklink
// command built into cmd instead (mklink is a builtin, not an
// external command).
err := exec.Command("cmd", "/k", "mklink", "/J", newname, oldname).Run()
if err != nil {
return symlinkErr
}
} else {
// Try making a hard link.
err := os.Link(oldname, newname)
if err != nil {
// Making a hardlink failed. Try copying the file as a last
// fallback.
inf, err := os.Open(oldname)
if err != nil {
return err
}
defer inf.Close()
outf, err := os.Create(newname)
if err != nil {
return err
}
defer outf.Close()
_, err = io.Copy(outf, inf)
if err != nil {
os.Remove(newname)
return err
}
// File was copied.
}
}
return nil // success
}
return symlinkErr
}
+30
View File
@@ -0,0 +1,30 @@
package loader
import (
"os"
"os/exec"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
)
// List returns a ready-to-run *exec.Cmd for running the `go list` command with
// the configuration used for TinyGo.
func List(config *compileopts.Config, extraArgs, pkgs []string) (*exec.Cmd, error) {
goroot, err := GetCachedGoroot(config)
if err != nil {
return nil, err
}
args := append([]string{"list"}, extraArgs...)
if len(config.BuildTags()) != 0 {
args = append(args, "-tags", strings.Join(config.BuildTags(), " "))
}
args = append(args, pkgs...)
cgoEnabled := "0"
if config.CgoEnabled() {
cgoEnabled = "1"
}
cmd := exec.Command("go", args...)
cmd.Env = append(os.Environ(), "GOROOT="+goroot, "GOOS="+config.GOOS(), "GOARCH="+config.GOARCH(), "CGO_ENABLED="+cgoEnabled)
return cmd, nil
}
+221 -343
View File
@@ -2,226 +2,239 @@ package loader
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/scanner"
"go/token"
"go/types"
"io"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
"strings"
"text/template"
"syscall"
"github.com/tinygo-org/tinygo/cgo"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
// Program holds all packages and some metadata about the program as a whole.
type Program struct {
mainPkg string
Build *build.Context
OverlayBuild *build.Context
OverlayPath func(path string) string
Packages map[string]*Package
sorted []*Package
fset *token.FileSet
TypeChecker types.Config
Dir string // current working directory (for error reporting)
TINYGOROOT string // root of the TinyGo installation or root of the source code
CFlags []string
ClangHeaders string
config *compileopts.Config
clangHeaders string
typeChecker types.Config
goroot string // synthetic GOROOT
workingDir string
Packages map[string]*Package
sorted []*Package
fset *token.FileSet
// Information obtained during parsing.
LDFlags []string
}
// PackageJSON is a subset of the JSON struct returned from `go list`.
type PackageJSON struct {
Dir string
ImportPath string
ForTest string
// Source files
GoFiles []string
CgoFiles []string
CFiles []string
// Dependency information
Imports []string
ImportMap map[string]string
// Error information
Error *struct {
ImportStack []string
Pos string
Err string
}
}
// Package holds a loaded package, its imports, and its parsed files.
type Package struct {
*Program
*build.Package
Imports map[string]*Package
Importing bool
Files []*ast.File
Pkg *types.Package
types.Info
PackageJSON
program *Program
Files []*ast.File
Pkg *types.Package
info types.Info
}
// Import loads the given package relative to srcDir (for the vendor directory).
// It only loads the current package without recursion.
func (p *Program) Import(path, srcDir string, pos token.Position) (*Package, error) {
if p.Packages == nil {
p.Packages = make(map[string]*Package)
}
// Load this package.
ctx := p.Build
if newPath := p.OverlayPath(path); newPath != "" {
ctx = p.OverlayBuild
path = newPath
}
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
if err != nil {
return nil, scanner.Error{
Pos: pos,
Msg: err.Error(), // TODO: define a new error type that will wrap the inner error
}
}
if existingPkg, ok := p.Packages[buildPkg.ImportPath]; ok {
// Already imported, or at least started the import.
return existingPkg, nil
}
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
return pkg, nil
}
// ImportFile loads and parses the import statements in the given path and
// creates a pseudo-package out of it.
func (p *Program) ImportFile(path string) (*Package, error) {
if p.Packages == nil {
p.Packages = make(map[string]*Package)
}
if _, ok := p.Packages[path]; ok {
// unlikely
return nil, errors.New("loader: cannot import file that is already imported as package: " + path)
}
file, err := p.parseFile(path, parser.ImportsOnly)
// Load loads the given package with all dependencies (including the runtime
// package). Call .Parse() afterwards to parse all Go files (including CGo
// processing, if necessary).
func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, typeChecker types.Config) (*Program, error) {
goroot, err := GetCachedGoroot(config)
if err != nil {
return nil, err
}
buildPkg := &build.Package{
Dir: filepath.Dir(path),
ImportPath: path,
GoFiles: []string{filepath.Base(path)},
wd, err := os.Getwd()
if err != nil {
return nil, err
}
for _, importSpec := range file.Imports {
buildPkg.Imports = append(buildPkg.Imports, importSpec.Path.Value[1:len(importSpec.Path.Value)-1])
}
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
p := &Program{
config: config,
clangHeaders: clangHeaders,
typeChecker: typeChecker,
goroot: goroot,
workingDir: wd,
Packages: make(map[string]*Package),
fset: token.NewFileSet(),
}
return pkg, nil
// List the dependencies of this package, in raw JSON format.
extraArgs := []string{"-json", "-deps"}
if config.TestConfig.CompileTestBinary {
extraArgs = append(extraArgs, "-test")
}
cmd, err := List(config, extraArgs, inputPkgs)
if err != nil {
return nil, err
}
buf := &bytes.Buffer{}
cmd.Stdout = buf
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
}
os.Exit(1)
}
return nil, fmt.Errorf("failed to run `go list`: %s", err)
}
// Parse the returned json from `go list`.
decoder := json.NewDecoder(buf)
for {
pkg := &Package{
program: p,
info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
},
}
err := decoder.Decode(&pkg.PackageJSON)
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
if pkg.Error != nil {
// There was an error while importing (for example, a circular
// dependency).
pos := token.Position{}
fields := strings.Split(pkg.Error.Pos, ":")
if len(fields) >= 2 {
// There is some file/line/column information.
if n, err := strconv.Atoi(fields[len(fields)-2]); err == nil {
// Format: filename.go:line:colum
pos.Filename = strings.Join(fields[:len(fields)-2], ":")
pos.Line = n
pos.Column, _ = strconv.Atoi(fields[len(fields)-1])
} else {
// Format: filename.go:line
pos.Filename = strings.Join(fields[:len(fields)-1], ":")
pos.Line, _ = strconv.Atoi(fields[len(fields)-1])
}
pos.Filename = p.getOriginalPath(pos.Filename)
}
err := scanner.Error{
Pos: pos,
Msg: pkg.Error.Err,
}
if len(pkg.Error.ImportStack) != 0 {
return nil, Error{
ImportStack: pkg.Error.ImportStack,
Err: err,
}
}
return nil, err
}
p.sorted = append(p.sorted, pkg)
p.Packages[pkg.ImportPath] = pkg
}
return p, nil
}
// newPackage instantiates a new *Package object with initialized members.
func (p *Program) newPackage(pkg *build.Package) *Package {
return &Package{
Program: p,
Package: pkg,
Imports: make(map[string]*Package, len(pkg.Imports)),
Info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
},
// getOriginalPath looks whether this path is in the generated GOROOT and if so,
// replaces the path with the original path (in GOROOT or TINYGOROOT). Otherwise
// the input path is returned.
func (p *Program) getOriginalPath(path string) string {
originalPath := path
if strings.HasPrefix(path, p.goroot+string(filepath.Separator)) {
// If this file is part of the synthetic GOROOT, try to infer the
// original path.
relpath := path[len(filepath.Join(p.goroot, "src"))+1:]
realgorootPath := filepath.Join(goenv.Get("GOROOT"), "src", relpath)
if _, err := os.Stat(realgorootPath); err == nil {
originalPath = realgorootPath
}
maybeInTinyGoRoot := false
for prefix := range pathsToOverride(needsSyscallPackage(p.config.BuildTags())) {
if !strings.HasPrefix(relpath, prefix) {
continue
}
maybeInTinyGoRoot = true
}
if maybeInTinyGoRoot {
tinygoPath := filepath.Join(goenv.Get("TINYGOROOT"), "src", relpath)
if _, err := os.Stat(tinygoPath); err == nil {
originalPath = tinygoPath
}
}
}
return originalPath
}
// Sorted returns a list of all packages, sorted in a way that no packages come
// before the packages they depend upon.
func (p *Program) Sorted() []*Package {
if p.sorted == nil {
p.sort()
}
return p.sorted
}
func (p *Program) sort() {
p.sorted = nil
packageList := make([]*Package, 0, len(p.Packages))
packageSet := make(map[string]struct{}, len(p.Packages))
worklist := make([]string, 0, len(p.Packages))
for path := range p.Packages {
worklist = append(worklist, path)
}
sort.Strings(worklist)
for len(worklist) != 0 {
pkgPath := worklist[0]
pkg := p.Packages[pkgPath]
if _, ok := packageSet[pkgPath]; ok {
// Package already in the final package list.
worklist = worklist[1:]
continue
}
unsatisfiedImports := make([]string, 0)
for _, pkg := range pkg.Imports {
if _, ok := packageSet[pkg.ImportPath]; ok {
continue
}
unsatisfiedImports = append(unsatisfiedImports, pkg.ImportPath)
}
sort.Strings(unsatisfiedImports)
if len(unsatisfiedImports) == 0 {
// All dependencies of this package are satisfied, so add this
// package to the list.
packageList = append(packageList, pkg)
packageSet[pkgPath] = struct{}{}
worklist = worklist[1:]
} else {
// Prepend all dependencies to the worklist and reconsider this
// package (by not removing it from the worklist). At that point, it
// must be possible to add it to packageList.
worklist = append(unsatisfiedImports, worklist...)
}
}
p.sorted = packageList
// MainPkg returns the last package in the Sorted() slice. This is the main
// package of the program.
func (p *Program) MainPkg() *Package {
return p.sorted[len(p.sorted)-1]
}
// Parse recursively imports all packages, parses them, and typechecks them.
// Parse parses all packages and typechecks them.
//
// The returned error may be an Errors error, which contains a list of errors.
//
// Idempotent.
func (p *Program) Parse(compileTestBinary bool) error {
includeTests := compileTestBinary
// Load all imports
for _, pkg := range p.Sorted() {
err := pkg.importRecursively(includeTests)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
if pkg.ImportPath != err.Packages[0] {
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
}
}
return err
}
}
func (p *Program) Parse() error {
// Parse all packages.
for _, pkg := range p.Sorted() {
err := pkg.Parse(includeTests)
if err != nil {
return err
}
}
if compileTestBinary {
err := p.SwapTestMain()
// TODO: do this in parallel.
for _, pkg := range p.sorted {
err := pkg.Parse()
if err != nil {
return err
}
}
// Typecheck all packages.
for _, pkg := range p.Sorted() {
for _, pkg := range p.sorted {
err := pkg.Check()
if err != nil {
return err
@@ -231,83 +244,6 @@ func (p *Program) Parse(compileTestBinary bool) error {
return nil
}
func (p *Program) SwapTestMain() error {
var tests []string
isTestFunc := func(f *ast.FuncDecl) bool {
// TODO: improve signature check
if strings.HasPrefix(f.Name.Name, "Test") && f.Name.Name != "TestMain" {
return true
}
return false
}
mainPkg := p.Packages[p.mainPkg]
for _, f := range mainPkg.Files {
for i, d := range f.Decls {
switch v := d.(type) {
case *ast.FuncDecl:
if isTestFunc(v) {
tests = append(tests, v.Name.Name)
}
if v.Name.Name == "main" {
// Remove main
if len(f.Decls) == 1 {
f.Decls = make([]ast.Decl, 0)
} else {
f.Decls[i] = f.Decls[len(f.Decls)-1]
f.Decls = f.Decls[:len(f.Decls)-1]
}
}
}
}
}
// TODO: Check if they defined a TestMain and call it instead of testing.TestMain
const mainBody = `package main
import (
"testing"
)
func main () {
m := &testing.M{
Tests: []testing.TestToCall{
{{range .TestFunctions}}
{Name: "{{.}}", Func: {{.}}},
{{end}}
},
}
testing.TestMain(m)
}
`
tmpl := template.Must(template.New("testmain").Parse(mainBody))
b := bytes.Buffer{}
tmplData := struct {
TestFunctions []string
}{
TestFunctions: tests,
}
err := tmpl.Execute(&b, tmplData)
if err != nil {
return err
}
path := filepath.Join(p.mainPkg, "$testmain.go")
if p.fset == nil {
p.fset = token.NewFileSet()
}
newMain, err := parser.ParseFile(p.fset, path, b.Bytes(), parser.AllErrors)
if err != nil {
return err
}
mainPkg.Files = append(mainPkg.Files, newMain)
return nil
}
// parseFile is a wrapper around parser.ParseFile.
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
if p.fset == nil {
@@ -319,34 +255,26 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
return nil, err
}
defer rd.Close()
relpath := path
if filepath.IsAbs(path) {
rp, err := filepath.Rel(p.Dir, path)
if err == nil {
relpath = rp
}
}
return parser.ParseFile(p.fset, relpath, rd, mode)
return parser.ParseFile(p.fset, p.getOriginalPath(path), rd, mode)
}
// Parse parses and typechecks this package.
//
// Idempotent.
func (p *Package) Parse(includeTests bool) error {
func (p *Package) Parse() error {
if len(p.Files) != 0 {
return nil
return nil // nothing to do (?)
}
// Load the AST.
// TODO: do this in parallel.
if p.ImportPath == "unsafe" {
// Special case for the unsafe package. Don't even bother loading
// the files.
// Special case for the unsafe package, which is defined internally by
// the types package.
p.Pkg = types.Unsafe
return nil
}
files, err := p.parseFiles(includeTests)
files, err := p.parseFiles()
if err != nil {
return err
}
@@ -361,11 +289,11 @@ func (p *Package) Parse(includeTests bool) error {
// Idempotent.
func (p *Package) Check() error {
if p.Pkg != nil {
return nil
return nil // already typechecked
}
var typeErrors []error
checker := p.TypeChecker
checker := p.program.typeChecker // make a copy, because it will be modified
checker.Error = func(err error) {
typeErrors = append(typeErrors, err)
}
@@ -373,7 +301,7 @@ func (p *Package) Check() error {
// Do typechecking of the package.
checker.Importer = p
typesPkg, err := checker.Check(p.ImportPath, p.fset, p.Files, &p.Info)
typesPkg, err := checker.Check(p.ImportPath, p.program.fset, p.Files, &p.info)
if err != nil {
if err, ok := err.(Errors); ok {
return err
@@ -385,52 +313,47 @@ func (p *Package) Check() error {
}
// parseFiles parses the loaded list of files and returns this list.
func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
// TODO: do this concurrently.
func (p *Package) parseFiles() ([]*ast.File, error) {
var files []*ast.File
var fileErrs []error
var gofiles []string
if includeTests {
gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
gofiles = append(gofiles, p.GoFiles...)
gofiles = append(gofiles, p.TestGoFiles...)
} else {
gofiles = p.GoFiles
}
for _, file := range gofiles {
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
continue
// Parse all files (incuding CgoFiles).
parseFile := func(file string) {
if !filepath.IsAbs(file) {
file = filepath.Join(p.Dir, file)
}
f, err := p.program.parseFile(file, parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
continue
return
}
files = append(files, f)
}
for _, file := range p.GoFiles {
parseFile(file)
}
for _, file := range p.CgoFiles {
path := filepath.Join(p.Package.Dir, file)
f, err := p.parseFile(path, parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
continue
}
files = append(files, f)
parseFile(file)
}
// Do CGo processing.
if len(p.CgoFiles) != 0 {
cflags := append(p.CFlags, "-I"+p.Package.Dir)
if p.ClangHeaders != "" {
cflags = append(cflags, "-I"+p.ClangHeaders)
var cflags []string
cflags = append(cflags, p.program.config.CFlags()...)
cflags = append(cflags, "-I"+p.Dir)
if p.program.clangHeaders != "" {
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.program.clangHeaders)
}
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags)
generated, ldflags, errs := cgo.Process(files, p.program.workingDir, p.program.fset, cflags)
if errs != nil {
fileErrs = append(fileErrs, errs...)
}
files = append(files, generated)
p.program.LDFlags = append(p.program.LDFlags, ldflags...)
}
// Only return an error after CGo processing, so that errors in parsing and
// CGo can be reported together.
if len(fileErrs) != 0 {
return nil, Errors{p, fileErrs}
}
@@ -444,59 +367,14 @@ func (p *Package) Import(to string) (*types.Package, error) {
if to == "unsafe" {
return types.Unsafe, nil
}
if _, ok := p.Imports[to]; ok {
return p.Imports[to].Pkg, nil
if replace, ok := p.ImportMap[to]; ok {
// This import path should be replaced by another import path, according
// to `go list`.
to = replace
}
if imported, ok := p.program.Packages[to]; ok {
return imported.Pkg, nil
} else {
return nil, errors.New("package not imported: " + to)
}
}
// importRecursively calls Program.Import() on all imported packages, and calls
// importRecursively() on the imported packages as well.
//
// Idempotent.
func (p *Package) importRecursively(includeTests bool) error {
p.Importing = true
imports := p.Package.Imports
if includeTests {
imports = append(imports, p.Package.TestImports...)
}
for _, to := range imports {
if to == "C" {
// Do CGo processing in a later stage.
continue
}
if _, ok := p.Imports[to]; ok {
continue
}
// Find error location.
var pos token.Position
if len(p.Package.ImportPos[to]) > 0 {
pos = p.Package.ImportPos[to][0]
} else {
pos = token.Position{Filename: p.Package.ImportPath}
}
importedPkg, err := p.Program.Import(to, p.Package.Dir, pos)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
}
return err
}
if importedPkg.Importing {
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
}
err = importedPkg.importRecursively(false)
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
}
return err
}
p.Imports[to] = importedPkg
}
p.Importing = false
return nil
}

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