Compare commits

...

1008 Commits

Author SHA1 Message Date
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
Ayke van Laethem 0933577e60 compiler: improve "function redeclared" error
This error can normally only happen with //go:linkname and such, but
it's nice to get an informative error message in that case.
2020-01-04 10:24:14 +01:00
Ayke van Laethem 69c1d802e1 loader: improve error messages for failed imports
Add location information (whenever possible) to failed imports. This
helps in debugging where an incorrect import came from.

For example, show the following error message:

    /home/ayke/src/github.com/tinygo-org/tinygo/src/machine/machine.go:5:8: cannot find package "foobar" in any of:
        /usr/local/go/src/foobar (from $GOROOT)
        /home/ayke/src/foobar (from $GOPATH)

Instead of the following:

    error: cannot find package "foobar" in any of:
        /usr/local/go/src/foobar (from $GOROOT)
        /home/ayke/src/foobar (from $GOPATH)
2020-01-04 00:01:07 +01:00
Ayke van Laethem b424056721 cgo: fix a bug in number tokenization 2020-01-03 23:44:58 +01:00
Ayke van Laethem d735df6e16 cgo: add support for symbols 2020-01-03 23:44:58 +01:00
Ayke van Laethem d37bbadb54 machine/arduino-nano33: fix UART1 and UART2
UART2 was configured with the wrong SERCOM for the used pins (PB22 and
PB23). However, after changing the SERCOM from 3 to 5 that led to a
conflict with UART1 (used for the on-board WiFi). But the used pins are
also usable from SERCOM 3, so in the end I switched SERCOM5 and SERCOM3
around.

With this change, I was able to get examples/echo working.
2020-01-03 23:25:37 +01:00
Ayke van Laethem ab7dc45288 wasm: implement memcpy and memset
This was reported in issue #805.
2019-12-30 20:51:46 +01:00
Jaden Weiss eee1b995f6 revise defer to use heap allocations when running a variable number of times 2019-12-30 18:12:40 +01:00
Ayke van Laethem a4fa41b49d compiler: don't crash when encountering types.Invalid
This commit fixes a crash when trying to compile the following (invalid)
code:

    package main

    import "unsafe"

    func main() {
    }

    type Foo struct {
       x DoesNotExist
    }

    const foo = unsafe.Sizeof(Foo{})

This commit fixes this situation. The result is a regular error message,
indicating that DoesNotExist is not defined.
2019-12-30 13:40:37 +01:00
Ayke van Laethem a5a90a57b9 main: remove getting a serial port in gdb subcommand
Remove this code for two reasons:

 1. It is not needed.
 2. It breaks `tinygo gdb` for debugging QEMU targets (such as
    cortex-m-qemu).
2019-12-29 19:41:24 +01:00
Dmitri Goutnik 71a380ce8c Add initial FreeBSD support 2019-12-29 10:48:28 +01:00
Ayke van Laethem 3b2a4b64c5 main: kill tests if they run too long
Sometimes, tests suddenly hang somewhere (in particular in emulators
where crashes often lead to hangs). Setting a limit has two advantages:

  1. Quickly killing test processes that are frozen (as opposed to
     waiting for the default 10min go test timeout).
  2. The output becomes visible, hopefully giving a clue what went
     wrong.
2019-12-28 21:10:13 +01:00
Ayke van Laethem 184827e4d8 riscv: support sleeping in QEMU
QEMU doesn't support the RTC peripheral yet so work around it for now.

This makes the following command work:

    tinygo run -target=hifive1-qemu ./testdata/coroutines.go
2019-12-26 09:57:58 +01:00
Ayke van Laethem 14474e7099 compiler: fix assertion on empty interface
This fixes issue #453.
2019-12-26 09:20:22 +01:00
Ron Evans 3656ac2fc9 main: increment version to 0.12-dev
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-12-24 19:48:38 +01:00
Ayke van Laethem 08f01ba3ff riscv: improve startup assembly
Now that we've switched to LLVM 9, we don't need a workaround anymore
for the 'la' pseudo-instruction.
2019-12-24 19:00:22 +01:00
Ayke van Laethem 699312f477 ci: remove symlink creation
These symlinks are now unnecessary because Clang (and previously lld)
have been integrated into TinyGo.
2019-12-24 08:59:33 +01:00
Ayke van Laethem 9644edcd5a builder: update Clang header location
We have long since moved towards a different location for these headers
in the git checkout, so update where getClangHeaderPath looks for these
headers.

Also add an extra check to make sure a path has been detected.
2019-12-24 08:59:33 +01:00
Ayke van Laethem 46325910c5 ci: increase Azure Pipelines timeout to 4 hours
3 hours is too close to the edge. Extend to 4 hours to make sure a build
including LLVM build will finish before the deadline.
2019-12-24 08:49:04 +01:00
Ayke van Laethem 923c2e7ada main: version 0.11.0 2019-12-23 16:37:59 +01:00
Ron Evans 18e446561d flash: use more precise searches for correct volume/port with default Windows matching
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-12-23 16:09:58 +01:00
Ron Evans 447537aebe flash: use win32 wmi to try to find UF2 and COM ports
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-12-23 16:09:58 +01:00
Ayke van Laethem 74e32acf33 compiler: improve error locations in goroutine lowering 2019-12-23 08:33:04 +01:00
Ayke van Laethem 072f8c354e interp: add runtime fallback for mapassign operations
Some mapassign operations cannot (yet) be done by the interp package.
Implement a fallback mechanism so that these operations can still be
performed at runtime.
2019-12-23 08:15:09 +01:00
Daniel Esteban 0587934a44 added missing pinetime devkit to list of suppoerted boards/targets 2019-12-22 21:23:07 +01:00
Daniel Esteban c2481b10f4 Added Adafruit's pybadge target (#795)
* machine/pybadge: add support for Adafruit PyBadge board
2019-12-22 19:37:16 +01:00
Ayke van Laethem d41f01f003 main: avoid leaving files open
Eventually, open files should be closed when the GC runs and the
finalizer is called. However we shouldn't rely on that.

Using `ioutil.ReadFile` as it's a simpler pattern anyway.
2019-12-21 23:12:13 +01:00
Ayke van Laethem 5a70c88483 transform: make reflection sidetables constant globals
These globals are (and must be!) never modified by the reflect package.
By marking them as constant, they will be put in read-only memory. This
reduces RAM consumption on microcontrollers.
2019-12-21 22:59:23 +01:00
Ayke van Laethem 5510dec846 compiler: add location information to the IR checker 2019-12-21 20:49:51 +01:00
Ayke van Laethem dffb9fbfa7 tools: use byte padding to skip unused register ranges
This simplifies the code. The fields are blank anyway so there is no way
to access them anyway (volatile or not).
Also do some other related simplifications of the code that result from
this change.
2019-12-21 19:59:41 +01:00
Jaden Weiss 525ded3d90 run tests partially in parallel 2019-12-21 12:22:59 +01:00
Ayke van Laethem ec2658ca79 interp: remove accidental debug print
Accidentally left in the source in
https://github.com/tinygo-org/tinygo/pull/787.
2019-12-20 14:47:20 +01:00
Ayke van Laethem 2004555fe2 interp: check whether the map update key/value are constant
This is a limitation in the PutString/PutBinary calls, make sure that
they won't get non-constant values as a safety measure.
2019-12-20 01:43:12 +01:00
Ayke van Laethem 9aeb8d9e06 interp: support llvm.lifetime.* calls
This fixes a bug found when updating a map with string keys.
Originally reported here:
https://github.com/hybridgroup/gdg-2019/issues/6
2019-12-20 01:43:12 +01:00
Ron Evans 34ee3883d6 flash: search for default serial port on both macOS and Linux
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-12-17 02:15:06 +01:00
Ayke van Laethem 768c652468 machine: rename CPU_FREQUENCY -> CPUFrequency()
These all-caps constants aren't in the Go style, so rename it to
CPUFrequency (which is more aligned with Go style). Additionally, make
it a function so that it is possible to add support for changing the
frequency in the future.

Tested by running `make smoketest`. None of the outputs did change.
2019-12-16 20:34:39 +01:00
Daniel Esteban 2778377ac9 Nano33 IoT: default SPI should be D13/D11/D12 instead of A2/A3/A6 (#781)
* Nano33 IoT: default SPI should be D13/D11/D12 instead of A2/A3/A6
2019-12-15 15:21:33 +01:00
Ayke van Laethem cf32607306 tools: rewrite gen-device-svd in Go
This should make it more maintainable. Another big advantage that
generation time (including gofmt) is now 3 times faster. No real attempt
at refactoring has been made, that will need to be done at a later time.
2019-12-14 22:27:45 +01:00
Ayke van Laethem ad022ef23d riscv: add support for compiler-rt
This gets all the tests to compile and many of them to pass. There are
some issues left, but those are probably unrelated to compiler-rt.
2019-12-14 12:48:21 +01:00
Ayke van Laethem c97b7221bd machine: support arduino-nano33 on play.tinygo.org 2019-12-11 21:49:19 +01:00
Ayke van Laethem 8d32a7c3a3 builder: use builtin Clang when building statically
This will be a huge help for people installing TinyGo that don't have
LLVM/Clang 9 already installed and in the $PATH variable.
2019-12-11 20:17:35 +01:00
Ayke van Laethem 49eb414530 machine: add Tx method to simulated SPI bus
This allows display drivers like st7789 to be used on the TinyGo
Playground.
2019-12-08 19:08:37 +01:00
Ayke van Laethem 39d21e21f1 targets: simulate Circuit Playground Express in play.tinygo.org
This requires a number of changes to atsamd21 code. It's not a very
clean separation, but I couldn't come up with a better one.
2019-12-08 13:11:39 +01:00
Ayke van Laethem fa8a93b4e7 cgo: don't run tests in parallel
Since LLVM 9, CGo sometimes randomly breaks with weird error messages on
Windows. I'm not sure why this is the case, but it might be related to
concurrency.

Disable concurrency for now, and hope that will make the errors go away.
2019-12-08 12:35:43 +01:00
Ayke van Laethem 8f9419a35d targets: add hifive1-qemu for testing RISC-V bare metal in QEMU
Most tests don't pass yet, so can't add this test to the standard tests,
yet.
2019-12-07 16:47:40 +01:00
Ayke van Laethem 7bdd4a1186 main: add support for QEMU in the gdb subcommand
This allows debugging QEMU-only targets with GDB, which can be super
useful.
2019-12-07 16:47:40 +01:00
Ayke van Laethem 0105f815c6 targets: rename qemu target to cortex-m-qemu
The target is intended to emulate the Cortex-M, not to be a generic QEMU
target.
2019-12-07 16:47:40 +01:00
Ayke van Laethem d441f0152f riscv: use LLVM tools instead of GNU toolchain
Now that we use LLVM 9, RISC-V support in LLVM has far fewer bugs and we
can avoid the GNU toolchain.

  * replace GNU linker with lld
  * replace GCC with clang

Additionally, RISC-V was promoted to stable so it can be enabled by
default in CI.
2019-12-07 16:27:10 +01:00
Ayke van Laethem 06647aab24 tools/gen-device-avr: process files in parallel
This significantly speeds up processing of the files.
2019-12-07 16:04:47 +01:00
Ayke van Laethem 24259cbb5f tools: rewrite gen-device-avr in Go
This brings a big speedup. Not counting gofmt time,
`make gen-device-avr` became about 3x faster. In the future, it might be
an idea to generate the AST in-memory and write it out already
formatted.
2019-12-07 16:04:47 +01:00
Ayke van Laethem 2f932a9eee riscv: fix heap corruption
The .sdata and .sbss sections are created by the compiler, but were not
present in the linker script. That means that the linker put them after
all other data/bss section, which happens to be where the heap also
resides.

This commit adds the .sdata and .sbss sections to the linker script,
which gets the blinky examples to work again on RISC-V.
2019-12-05 20:27:28 +01:00
Ayke van Laethem 93a06d1157 tools: avoid _paddingX in generated struct fields
This makes the generation script slightly simpler.
2019-12-04 23:11:42 +01:00
Ayke van Laethem 374349cfa5 compiler: refactor func lowering to the transform package
This commit makes a number of changes:

  * It avoids a dependency on Compiler.emitStartGoroutine.
  * It moves the func-lowering pass to the transform package.
  * It adds testing to func lowering.

No functionality should have changed with this commit.
2019-12-04 22:19:49 +01:00
Ron Evans 024a0827ea docs: add official code of conduct using 'Contributor Covenant'
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-12-04 21:53:46 +01:00
Ayke van Laethem fadddc54a3 main: increment version to 0.11-dev 2019-12-03 21:02:37 +00:00
Ayke van Laethem a44d74d4c0 main: version 0.10.0 2019-11-26 22:45:30 +01:00
Ayke van Laethem 24ff2d1ee2 interp: replace many panics with error messages
This commit replaces most panics in interp/frame.go and interp/scan.go
with real error messages. The remaining ones are panics that should not
happen when working with valid IR.
2019-11-26 15:45:39 +01:00
Ayke van Laethem 0db26b0662 interp: support integer icmp of ptrtoint
This kind of code might be generated by the switch implementation of
func values. The func value is represented as a ptrtoint, and before
calling it, it is compared against 0.
2019-11-26 14:43:05 +01:00
Ayke van Laethem 4f7a650614 interp: add test for icmp inttoptr workaround 2019-11-26 14:43:05 +01:00
Ayke van Laethem e74db01f82 interp: improve error reporting
This commit improves error reporting in several ways:

  * Location information is read from the intruction that causes the
    error, as far as that's available.
  * The package that is being interpreted is included in the error
    message. This may be the most useful part of the improvements.
  * The hashmap update intrinsics now doesn't panic, instead it logs a
    clear error (with location information, as in the above two bullet
    points).

This is possible thanks to improvements in LLVM 9. This means that after
this change, TinyGo will depend on LLVM 9.
2019-11-26 07:18:42 +01:00
Ayke van Laethem 86f48da594 ci: build with Go 1.13 on Windows
The default Go version is 1.12. Because Go 1.13 introduced language
changes (improved numeric constants), TinyGo compiled with Go 1.12
cannot handle Go 1.13 code such as the Go 1.13 standard library.

Use Go 1.13 to build TinyGo on Azure Pipelines to fix this.
2019-11-25 17:47:47 +01:00
Ayke van Laethem 4be80e0372 Revert "all: use compiler-rt from the llvm-project directory"
This reverts commit acdaaa17d8.
See https://github.com/tinygo-org/tinygo/issues/734 for details.
2019-11-25 14:37:52 +01:00
Ayke van Laethem 10e1420237 cgo: implement #cgo CFLAGS
This implementation is still very limited but provides a base to build
upon. Limitations:

  * CGO_CFLAGS etc is not taken into account.
  * These CFLAGS are not used in C files compiled with the package.
  * Other flags (CPPFLAGS, LDFAGS, ...) are not yet implemented.
2019-11-25 09:32:03 +01:00
Ayke van Laethem 6a1bb134f9 cgo: add tests for errors
This commit adds tests for CGo preprocessing. There are various errors
that can be reported while preprocessing, and they should integrate well
with the compiler (including accurate source location tracking).

Also allow CGo preprocessing to continue after Clang encountered an
error, for a better view of what happened.
2019-11-25 09:32:03 +01:00
Ayke van Laethem f0bb3c092d compiler: move GC passes to the transform package 2019-11-25 09:14:31 +01:00
Ayke van Laethem 3d3e48179e runtime: use MSP/PSP registers for scheduling on Cortex-M
The Cortex-M architecture contains two stack pointers, designed to be
used by RTOSes: MSP and PSP (where MSP is the default at reset). In
fact, the ARM documentation recommends using the PSP for tasks in a
RTOS.

This commit switches to using the PSP for goroutine stacks. Aside from
being the recommended operation, this has the big advantage that the
NVIC automatically switches to the MSP when handling interrupts. This
avoids having to make every goroutine stack big enough that interrupts
can be handled on it.

Additionally, I've optimized the assembly code to save/restore registers
(made possible by this change). For Cortex-M3 and up, saving all
registers is just a single push instruction and restoring+branching is a
single pop instruction. For Cortex-M0 it's a bit more work because the
push/pop instructions there don't support most high registers.

Sidenote: the fact that you can pop a number of registers and branch at
the same time makes ARM not exactly a true RISC system. However, it's
very useful in this case.
2019-11-23 13:55:19 +01:00
BCG ea5df0f214 Fixes for UART2 on Metro M4 Airlift Lite (#739)
* machine/samd51: Fixes for UART2
2019-11-21 21:45:13 +01:00
Nikolas Sepos c09724bfc5 nrf: support for SoftDevice s140 PCA10056 board 2019-11-20 16:43:55 +01:00
Mark Glines 00f745e351 runtime/atsamd21: i2s initialization fixes 2019-11-19 21:48:34 +01:00
BCG 5171618284 Added SPI1 connected to NINA-W102 chip on Arduino Nano 33 IOT 2019-11-19 08:40:32 +01:00
Ayke van Laethem 4605cbbc6e interp: fix inserting non-const values in a const aggregate
This bug was triggered by the following code:

    package main

    func foo() byte

    var array = [1]byte{foo()}

    func main() {
    }
2019-11-18 18:31:56 +01:00
Jaden Weiss 98eee7c22a compiler: add support for async interface calls 2019-11-17 23:46:10 +01:00
Jaden Weiss 81199da3f1 add code to handle programs which use heap allocations but never hit the GC 2019-11-17 15:14:51 +01:00
Ayke van Laethem 3cedebd299 nrf: fix nrf52-s132v6 config
In my excitement to get the SoftDevice PR ready, I made two mistakes.
They're fixed in this commit.

  * Add the `s132v6` build tag.
  * Remove the (old) `ldscript` property.

This fixes the following issue:
https://github.com/aykevl/go-bluetooth/issues/1
2019-11-16 21:57:18 +01:00
Ayke van Laethem 118af9df69 all: switch to LLVM 9 2019-11-16 18:44:27 +01:00
Ayke van Laethem 172efc26a7 compiler: move ReplacePanicsWithTrap pass to transforms
This moves the transformation pass to the right location, and adds tests
to see that it actually works correctly.
2019-11-16 18:41:28 +01:00
Jaden Weiss f49e69b02a eliminate extraneous getFakeCoroutine calls 2019-11-15 23:49:27 +01:00
Ayke van Laethem e20af665fa compiler,transform: move interface lowering to transform package 2019-11-15 23:37:17 +01:00
Ayke van Laethem 36d1198115 compiler: refactor alloca/lifetime/wordpack code into separate package
This code is required by transformation passes which are being moved
into a separate package, but is too complicated to simply copy.
Therefore, I decided to move them into a new package.
2019-11-15 23:37:17 +01:00
BCG 009b27350e Adding Support for Adafruit Metro M4 Express Airlift (#694)
* machine/metro-m4: add support for Adafruit Metro M4 Express Airlift board
2019-11-15 09:52:54 +01:00
Jaden Weiss 93961f9d41 fix incorrect starting value for optimized allocations in a loop 2019-11-13 16:45:09 +01:00
Ayke van Laethem acdaaa17d8 all: use compiler-rt from the llvm-project directory
We don't need the separate submodule: compiler-rt is already included in
the llvm-project repository.

This should hopefully make CI slightly faster too.
2019-11-13 16:00:22 +01:00
Ayke van Laethem b7b548a8d0 builder: make Clang header detection more robust
The header detection code failed way too easily, bailing out when there
was more than one Clang version directory.

This fixes the following problem in LLVM 9 on Debian:

    testdata/cgo/main.h:1:10: fatal: 'stdbool.h' file not found
    testdata/cgo/main.go:5:10: note: in file included from testdata/cgo/main.go!cgo.c:3:
2019-11-13 15:35:45 +01:00
Ayke van Laethem 8266d2ff58 builder: write a symbol table when writing out the compiler-rt lib
This should fix an issue with LLVM 9 (specifically, ld.lld-9).
See: https://github.com/tinygo-org/tinygo/issues/595
2019-11-13 13:52:20 +01:00
Ayke van Laethem d2d78d3d0a main: add -programmer flag
This flag is overloaded. It can be used in two ways:

  * Choosing the flash method to use (openocd, msd, command).
  * Choosing the OpenOCD programmer name.

For example, you can use one of these to use OpenOCD instead of the
mass-storage device programmer:

    tinygo flash -target=microbit -programmer=openocd
    tinygo flash -target=microbit -programmer=cmsis-dap
2019-11-12 17:45:26 +01:00
Ayke van Laethem c6255e4d0a targets: explicitly mark the stack as NOLOAD
This prevents it from being of type PROGBITS in lld 9, it should always
be NOBITS. It should fix the following error in lld 9:

    ROM segments are non-contiguous
2019-11-12 17:10:13 +01:00
Ayke van Laethem 8e6cb89ceb main: refactor compile/link part to a builder package
This is a large commit that moves all code directly related to
compiling/linking into a new builder package. This has a number of
advantages:

  * It cleanly separates the API between the command line and the full
    compilation (with a very small API surface).
  * When the compiler finally compiles one package at a time (instead of
    everything at once as it does now), something will have to invoke it
    once per package. This builder package will be the natural place to
    do that, and also be the place where the whole process can be
    parallelized.
  * It allows the TinyGo compiler to be used as a package. A client can
    simply import the builder package and compile code using it.

As part of this refactor, the following additional things changed:

  * Exported symbols have been made unexported when they weren't needed.
  * The compilation target has been moved into the compileopts.Options
    struct. This is done because the target really is just another
    compiler option, and the API is simplified by moving it in there.
  * The moveFile function has been duplicated. It does not really belong
    in the builder API but is used both by the builder and the command
    line. Moving it into a separate package didn't seem useful either
    for what is essentially an utility function.
  * Some doc strings have been improved.

Some future changes/refactors I'd like to make after this commit:

  * Clean up the API between the builder and the compiler package.
  * Perhaps move the test files (in testdata/) into the builder package.
  * Perhaps move the loader package into the builder package.
2019-11-11 20:53:50 +01:00
Ayke van Laethem 946e2dd405 runtime/unix: simplify time functions
There is no need for separate datatypes for 32-bit and 64-bit *nix
systems, because the int type already provides this.
2019-11-11 12:55:47 +01:00
Jaden Weiss ad73a727a3 fix time on 32-bit arm on linux 2019-11-11 09:20:40 +01:00
Ayke van Laethem efafda1d32 runtime: only implement CountString for required platforms 2019-11-10 21:33:11 +01:00
Ayke van Laethem 405c0263b0 runtime: add AdjustTimeOffset to update current time
This function adjusts the time returned by time.Now() and similar
functions. This is necessary on bare metal systems, where there would
not be a way to adjust the time otherwise.
2019-11-10 08:54:06 +01:00
Ayke van Laethem 45b5decb4e runtime: implement comparing uintptr values in interfaces
This was an oversight in https://github.com/tinygo-org/tinygo/pull/686.
With this PR, it's possible to compare interface values that contain
values/fields of type uintptr.
2019-11-09 13:41:27 -05:00
Ayke van Laethem fb39e0917b cgo: add -update flag to tests
When this flag is set, the testdata/*.out.go files will be updated when
they have changed. This is very convenient for updating these files
after the expected output changes.

Of course, the updated output must still be checked for validity.
2019-11-08 16:36:00 +01:00
Ayke van Laethem 16fbf53ae2 nrf: add support for SoftDevices to PCA10040 board 2019-11-08 13:08:59 +01:00
Ayke van Laethem b153bd63f2 cgo: add support for nested structs and unions 2019-11-08 08:38:50 +01:00
Ayke van Laethem 6108ee6859 cgo: refactor union support
Instead of putting the magic in the AST, generate regular accessor
methods. This avoids a number of special cases in the compiler, and
avoids missing any of them.

The resulting union accesses are somewhat clunkier to use, but the
compiler implementation has far less coupling between the CGo
implementation and the IR generator.
2019-11-07 21:39:29 +01:00
Ayke van Laethem 76c9f13e13 cgo: include all enums in the CGo Go AST
Not all enums may be used as a type anywhere, which was previously the
only way to include an enum in the AST. This commit makes sure all enums
are included.
2019-11-06 17:46:20 +01:00
Ayke van Laethem d31deda1b5 main: add 'info' subcommand
It lists some information about the device that is useful for tools, in
a way that can be parsed easily.
2019-11-06 16:48:27 +01:00
Ayke van Laethem 473576e756 cgo: do type checking in CGo testing
Such type checking should hopefully catch more bugs.

This commit also fixes some existing type errors.
2019-11-06 16:30:07 +01:00
Ayke van Laethem 913131bf62 cgo: avoid '"unsafe" imported but not used' error
This can happen when not all CGo features are used.
2019-11-06 16:30:07 +01:00
Ayke van Laethem b41e58bcb4 cgo: rename reserved field names like type
This commit renames reserved field names like `type` to `_type`, and in
turn renames those fields as well (recursively). This avoids name
clashes when a C struct contains a field named `type`, which is a
reserved keyword in Go.

For some details, see:
https://golang.org/cmd/cgo/#hdr-Go_references_to_C
2019-11-06 15:54:18 +01:00
BCG 0db403dc0c Adding Board: Feather M4 Express (#688)
* machine/feather-m4: Adding Feather M4
2019-11-05 15:34:46 +01:00
Ayke van Laethem cadb75a4aa cgo: implement the constant parser as a real parser
Previously it was just a combination of heuristics to try to fit a
constant in an *ast.BasicLit. For more complex expressions, this is not
enough.

This change also introduces proper syntax error with locations, if
parsing a constant failed. For example, this will print a real error
message with source location:

    #define FOO 5)
2019-11-05 14:18:38 +01:00
Ayke van Laethem 5987233b99 cgo: refactor constant expressions
Put them in a separate file for separation of concerns (making them
testable) and add some tests.
2019-11-05 14:18:38 +01:00
Jaden Weiss 992f1fa248 make compiler test names clearer 2019-11-04 16:52:40 +01:00
Ayke van Laethem 1cbe09ee89 compileopts: add linkerscript key
Setting the linker script as one property (instead of as part of the
generic ldflags property) allows it to be overriden.

This is important for the SoftDevice on Nordic chips, because the
SoftDevice takes up a fixed part of the flash/RAM and the application
must be flashed at a different position. With this linkerscript option,
it is possible to create (for example) a pca10040-s132v6 that overrides
the default linker script.
2019-11-04 16:21:59 +01:00
Ayke van Laethem 18cce571a2 main: move ldflags to compileopts 2019-11-04 16:21:59 +01:00
Konstantin Itskov ac330f4a70 runtime: implement interface equality
Code copied from Konstantin Itskov and modified by Ayke van Laethem.
For details: https://github.com/tinygo-org/tinygo/pull/569
2019-11-04 15:19:41 +01:00
Ayke van Laethem e977276044 interp: fix "todo: store" panic
This is really just a simple workaround. When such an instruction is
encountered, it will just fall back to marking the entire function as
having side effects. Ideally it should trace all affected instructions
and check if they would have any side effects, but this at least fixes a
number of compile errors.

This commit gets the following packages to compile:

  * context
  * database/sql/driver
  * image/jpeg
  * image/png
2019-11-04 13:32:22 +01:00
Ayke van Laethem feb2b4715b interp: fix scanning declarations
Declarations would enter an infinite loop when trying to loop over basic
blocks. That was probably an undefined operation, but still somehow
didn't crash the compiler.

Make sure that scanning declarations works as expected.
2019-11-04 13:32:22 +01:00
Ayke van Laethem 3b0ed63c29 all: refactor compile options
Move most of the logic of determining which compiler configuration to
use (such as GOOS/GOARCH, build tags, whether to include debug symbols,
panic strategy, etc.) into the compileopts package. This makes it a
single source of truth for anything related to compiler configuration.

It has a few advantages:

  * The compile configuration is independent of the compiler package.
    This makes it possible to move optimization passes out of the
    compiler, as they don't rely on compiler.Config anymore.
  * There is only one place to look if an incorrect compile option is
    used.
  * The compileopts provides some resistance against unintentionally
    picking the wrong option, such as with c.selectGC() vs c.GC() in the
    compiler.
  * It is now a lot easier to change compile options, as most options
    are getters now.
2019-11-04 11:45:35 +01:00
Ayke van Laethem 59cc901340 main: move compile options to compileopts package 2019-11-04 11:45:35 +01:00
Ayke van Laethem ef600965aa compiler: move Config struct to compileopts 2019-11-04 11:45:35 +01:00
Ayke van Laethem e7cf75030c main: move target specification into a separate package 2019-11-04 11:45:35 +01:00
Ron Evans dff6e6566d go-llvm: update modules to use latest version
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-11-04 11:01:11 +01:00
Jaden Weiss cdff0bd3ee add blocking select 2019-11-04 09:15:21 +01:00
Ayke van Laethem fa25fa1b0c macos: use llvm@8 instead of just llvm in paths
This should hopefully avoid needing to use `brew switch`.
2019-11-03 21:02:39 +01:00
Ayke van Laethem be7529b261 cgo: add tests for most C types 2019-11-03 19:15:38 +01:00
Ayke van Laethem 26bdfa9c84 cgo: add bare-bones test 2019-11-03 19:15:38 +01:00
Ayke van Laethem b72f677310 cgo: create new GenDecl for every symbol
Previously, a GenDecl was shared between many different
consts/vars/types. However, it actually makes much more sense not to
bundle them as that is also the case in C.

This makes the printed output of the CGo AST much nicer, and works
around a bug in Go 1.11.
2019-11-03 19:15:38 +01:00
Ayke van Laethem 2a5ab2500d machine: add support for the X9 Pro smartwatch
This is a Chinese smart watch sold on AliExpress etc. It is based on a
nrf52832. More information:
https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker
2019-11-03 17:28:32 +01:00
Ayke van Laethem c138a50457 cgo: improve diagnostics
This commit improves diagnostics in a few ways:

  * All panics apart from panics with no (easy) recovery are converted to
    regular errors with source location.
  * Some errors were improved slightly to give more information. For
    example, show the libclang type kind as a string instead of a
    number.
  * Improve source location by respecting line directives in the C
    preprocessor.
  * Refactor to unify error handling between libclang diagnostics and
    failures to parse the libclang AST.
2019-11-03 16:58:23 +01:00
Jaden Weiss 86ab03c999 fix miscompile of static goroutine calls to closures 2019-11-02 12:50:32 +01:00
Ayke van Laethem 923a6f5873 interp: add testing for scanning for side effects 2019-11-01 17:00:32 +01:00
Ayke van Laethem 071f863e5d qemu: rename assembly file
The name was cortex-m.s which looks like it is a generic assembly file
for all cortex-m targets. However, it really is only for qemu
simulation, because every chip has a slightly different interrupt vector
table.
2019-11-01 16:25:41 +01:00
Ayke van Laethem b884db81ea cortexm: move SemihostingCall impl to the right asm file
This is the more correct location. The targets/cortex-m.s file is really
just for qemu, not for Cortex-M in general.
2019-11-01 16:25:41 +01:00
Ron Evans 41df9648a8 machine/samd51: correct channel init and pin map for ADC based on ItsyBitsy-M4
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-11-01 10:07:07 +01:00
Ron Evans 3ec94a06ed gen-device: correct source for SiFive SVD files
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-31 13:07:51 +01:00
Ron Evans 3777791aa3 machine/samd51: allow setting pinmode for each of the SPI pins
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-31 12:47:48 +01:00
Ayke van Laethem 96d4987345 main: remove ocd-daemon property
This fixes a crash crash with the -ocd-output flag.
2019-10-31 12:37:06 +01:00
Ayke van Laethem 66ed03faa2 machine/hifive1b: fix compiling in simulation (wasm) 2019-10-31 12:03:35 +01:00
Ron Evans 46d468b79d machine/hifive1b: add support for SPI1
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-29 13:25:13 +01:00
Ayke van Laethem 373fa6d69b main: halt GDB on start
Most programmers support the "reset halt" command, which resets the
target but keeps it halted at the first instruction. This is a much more
natural way of working with GDB, and allows setting breakpoints before
the program is started.

This commit switches to `reset halt` by default and also stops running
the program directly when debugging natively on the host.
2019-10-29 13:13:08 +01:00
Ayke van Laethem a4642ddf59 Makefile: add simulated boards to smoke tests
One board (pca10040) was already tested. To make sure stuff doesn't
break in the future, test all the other boards too.
2019-10-29 13:10:50 +01:00
Ayke van Laethem ceece08959 nrf: add support for the PineTime64 devkit
This smartwatch doesn't have an on-board debugger, so I picked the one I
was using while getting this smartwatch to run Go programs (the J-Link
EDU Mini).
2019-10-28 07:17:39 +01:00
Ron Evans 7014f90120 machine/samd21: correct handling of pins > 32 based on bugfix for samd51 submitted by @Infinoid
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-27 23:32:56 +01:00
Infinoid f7dde33842 machine/atsamd51: pin method cleanup (#659)
* machine/samd51: pin method cleanups.
- Use bit-math to select the group in Pin methods.
- Move Pin methods to atsamd51. They are not chip-specific, they apply to the whole atsamd51 family.
- Move the group/pin-id calculation into a helper method.
- Add a Pin.Toggle() method.
2019-10-27 21:37:52 +01:00
Jun Takeda 2f059ac91e machine/atmega: add PORT C GPIO support 2019-10-26 11:35:52 +02:00
Mark Glines e139a9dd71 machine/atsamd51: pin function selection bugs 2019-10-25 17:54:54 +02:00
Ayke van Laethem 8dc0deaf46 Makefile: add systick example to smoke tests
This makes sure future changes will at least keep the example code
compiling.
2019-10-25 15:58:18 +02:00
Jaden Weiss 388d11eecf use error returns to generate detailed error messages in the IR checker 2019-10-25 15:50:03 +02:00
Ron Evans adff391bd2 runtime/samd51: correct initialization for RTC
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-25 15:47:57 +02:00
Ayke van Laethem a7794de99d compiler: fix interface lowering miscompilation with reflect
When using reflect, arbitrary types can be synthesized. This invalidates
a few assumptions in the interface-lowering pass, that think they can
see all types that are in use in a program and optimize accordingly.

The file size impact depends on the specific program. Sometimes it's
nonexistent, sometimes it's rather hefty (up to 30% bigger). Especially
the samd21 targets seem to be affected, with a 2000-6000 bytes increase
in code size. A moderately large case (the stdlib test) increases by
4%/6%/15% depending on the target.

I hope that this increase could be mitigated, but I don't see an obvious
way to do that.
2019-10-25 09:35:05 -04:00
Jaden Weiss 4339cbd56f add implementaion of array alignment in reflect 2019-10-25 15:18:03 +02:00
Infinoid 6b1faeb882 device/arm: add system timer registers (#654)
* device/arm: add system timer registers
Add SYST registers and bit definitions to device/arm.
Add a setup function.
Add an example that uses it to blink an LED.
2019-10-24 21:17:06 +02:00
Infinoid 2c15f36702 runtime/atsamd51: fix clock init code (#650)
* runtime/atsamd51: fix clock init code
The DPLL0 initialization should set LDRFRAC and LDR, not LDRFRAC twice.
Also explain what the magic numbers are doing.
2019-10-24 20:48:22 +02:00
Ayke van Laethem 9d21bfce8c ci: avoid rebuilding LLVM on Azure (Windows) 2019-10-18 22:41:03 +02:00
Jaden Weiss c4dff990d9 skip calling ticks when the sleep queue is empty 2019-10-18 11:29:27 +02:00
Jaden Weiss b66b15d02c main: switch to version 0.10.0-dev 2019-10-18 11:28:37 +02:00
Ayke van Laethem 8a771e3f89 main: version 0.9.0 2019-10-17 13:07:57 +02:00
Ayke van Laethem 5ad251b2bd main: add go version to tinygo version
This appears to be necessary for Visual Studio Code.
2019-10-17 12:38:49 +02:00
Ron Evans 5324fb7a40 build: add Azure build pipeline badge
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-17 07:30:12 +02:00
Ayke van Laethem 7369a0e7f2 all: add support for Windows 2019-10-17 00:14:59 +02:00
Jaden Weiss 8906192690 fix goroutine lowering type errors 2019-10-16 16:04:37 +02:00
Jaden Weiss 20a55e7944 fix miscompile of function nil panics 2019-10-16 15:57:25 +02:00
Jaden Weiss a2c7112b1f properly handle nil New func in sync.Pool 2019-10-16 15:12:27 +02:00
Ron Evans 3eec878a11 docs: correct the count of supported boards, and put into strict alpha order
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-15 18:04:46 +02:00
Ayke van Laethem e6cab6c327 main: add go env subcommand
This is required for integration in VS Code.
2019-10-14 17:58:06 +02:00
Ayke van Laethem 2463153a8c main: refactor environment variables into a separate package
This makes it possible to query these environment variables from
anywhere, which might be useful. More importantly, it puts them in a
central location from where they can be queried, useful for a `go env`
subcommand.
2019-10-14 17:58:06 +02:00
Ayke van Laethem 2a71aa90bc targets: refactor flash/gdb target configuration
Instead of specifying explicit commands, most of these commands have
been replaced by more specific properties.

This is work that will be necessary for an eventual -programmer flag to
the compiler, with which it is possible to select which programmer to
use to flash or debug a chip. That's not very useful for boards that
already include a programmer or bootloader for that purpose, but is very
useful for novel boards or single-purpose boards that are not already
included in TinyGo.
2019-10-14 16:44:33 +02:00
Ayke van Laethem 52bac4d75b compiler: support recursive types
Previously, the cycle was broken by inserting an unsafe.Pointer type in
some places. This is of course incorrect, and makes debugging harder.
However, LLVM provides a way to make temporary nodes that are later
replaced, exactly for this purpose.

This commit uses those temporary metadata nodes to allow such recursive
types.
2019-10-13 23:07:47 +02:00
Ron Evans be9e4f439c machine/spi: do not compare slices against nil, same as #612 but for all platforms that use shared SPI implementation for Tx
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-13 17:21:51 +02:00
Ron Evans ba49148644 flash: support flashing boards using Mass Storage Device (MSD) operation for uf2 and daplink bootloaders
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-13 15:11:21 +02:00
Ron Evans 02facb8568 flash: add ability to perform 1200baud port reset for MCUs that can detect this in order to go into bootloader mode for flashing without pressing any buttons. Also add support for this to the Arduino Nano33 IoT board target
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-13 00:43:00 +02:00
Ayke van Laethem 4164c39a4a machine/nrf: do not compare slices against nil
Comparing slices against nil currently causes the slice to escape, due
to a limitation in LLVM 8. This leads to lots of unnecessary heap
allocations. With LLVM 9 and some modifications to TinyGo, this should
be fixed. However, this commit is an easy win right now.

Returning an error when both slices are nil is not necessary, when the
check is left out it should just do nothing.

For updating an SPI screen using the st7735 driver, this results in a
~7% performance win.
2019-10-11 08:45:40 +02:00
Ron Evans 832f301a74 docs: add SiFive HiFive1 to list of supported boards
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-10 07:31:06 +02:00
Ron Evans 2168b1b516 machine/hifive1: add GPIO Get() implementation and update other implementation to match latest SVD wrappers
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-09 18:54:10 +02:00
Ron Evans 92e07ec8af machine/samd51: update to accomodate differences in updated SVD files for from main CMSIS-SVD repo
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-09 18:53:32 +02:00
Ron Evans be40c383c8 cmsis: update to latest version of SVD files
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-09 18:53:32 +02:00
Ron Evans b730590059 runtime/all: add implementation of bytealg.CountString to complete #424
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-09 18:37:29 +02:00
Ron Evans fb1a476033 generator: handle fields that use bitRange element and ensure all caps for attributes that are not already capitalized or start with number.
Also handle subclusters.

Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-10-09 18:36:03 +02:00
Ayke van Laethem 4397152108 machine/samd21: switch UART to use new pin configuration
This allows all possible UART pin configurations to be used and avoids
some tricky configuration.
2019-10-07 17:49:52 +02:00
Ayke van Laethem d266e44bc5 machine/samd21: use pins specified in I2CConfig
Instead of configuring machine.I2C0, machine.I2C1, etc. statically,
allow the pins to be set using machine.I2CConfig. This will also
automatically configure the correct pin mode for each pin instead of
having to specify that manually.
2019-10-07 17:25:49 +02:00
Jaden Weiss 8fe126b1f6 raise timeouts on USB on atsamd and fix slice sizing 2019-10-06 13:25:55 +02:00
parasquid ab50f823dd arduino: fix avr hex not working when flashed
An optimization introduced in https://github.com/tinygo-org/tinygo/commit/a04db67ea9d882eed9164321bcb503f76a65d2f1
seems to have broken arduino uno compiled hex. Setting optimzation
flags to 1, 2, or s builds proper hex binaries though.

These patches have been the result of troubleshooting over slack:

> @aykevl
> that preinit also doesn't look right. Can you try this variant,
> with 8-bit stores instead of 32-bit stores?
> There might be some alignment issue: the _ebss might not be
> aligned resulting in ptr != unsafe.Pointer(&_ebss) never being true.

Co-authored-by: Ayke van Laethem <aykevanlaethem@gmail.com>
Co-authored-by: Jaden Weiss <jadr2ddude@gmail.com>
2019-10-06 13:07:52 +02:00
Ayke van Laethem 3fa926c512 machine/atsamd21: refactor SPI pin handling to only look at pin numbers
Pin mode and pad numbers are automatically calculated from the pin
numbers, returning an error if no pinout could be found.
2019-10-01 21:31:00 +02:00
Ayke van Laethem 01e58691a1 compiler: support constant indices with a named type 2019-10-01 21:31:00 +02:00
Jaden Weiss 81c0f9af4e try adding assertions to CI 2019-09-27 10:57:35 +02:00
Jaden Weiss b5ecd9cab0 fix LLVM assertions from improved blocking 2019-09-25 23:04:53 +02:00
Elliott Sales de Andrade 9aace79298 Allow overridding Python used for generators. 2019-09-25 12:04:38 +02:00
Ron Evans c16e07469b machine/samd21,samd51: remove use of binary package to avoid reflection and reduce binary size
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-24 19:41:34 +02:00
Ayke van Laethem 582457b81e interp: implement runtime.sliceCopy
This implements the copy() built-in function. It may not work in all
cases, but should work in most cases.

This commit gets the following 3 packages to compile, according to
tinygo-site/imports/main.go:

  * encoding/base32
  * encoding/base64
  * encoding/pem (was blocked by encoding/base64)
2019-09-24 18:16:43 +02:00
Ayke van Laethem 4ea1559d46 interp: add basic test to interp package
More tests should be added in the future, but this is a start.
2019-09-24 18:16:43 +02:00
Ayke van Laethem da7f7eef00 interp: avoid an extra TargetData argument
This can be easily calculated from the module datalayout string.
2019-09-24 18:16:43 +02:00
Jaden Weiss 17ef7a5c32 all: add support for go 1.13 2019-09-24 16:13:26 +02:00
Jaden Weiss 6c9e55bd06 reflect: delete incorrect optimization 2019-09-24 16:13:19 +02:00
Ron Evans 368abeeb9a machine/stm32f103xx: prevent time.Sleep() issues by preventing sleeping for less than 200us, which is the current effictive minimum due to prescaler settings.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-24 12:16:38 +02:00
Ayke van Laethem 076699add8 Makefile: add md5sum to smoketest
This makes it much easier to check whether a binary changed at all, to
avoid having to test it on real hardware.
2019-09-24 10:23:58 +02:00
Jaden Weiss d843ebfe40 Improved blocking (#513)
core: major improvements to blocking, including support for buffered channels.
2019-09-22 17:58:00 +02:00
Jaden Weiss d17f500c8b replace reflect.interfaceHeader with strongly typed runtime functions 2019-09-22 16:36:11 +02:00
Ayke van Laethem 65beddafe8 compiler: move OptimizeStringToBytes to transform package
Unfortunately, while doing this I found that it doesn't actually apply
in any real-world programs (tested with `make smoketest`), apparently
because nil pointer checking messes with the functionattrs pass. I hope
to fix that after moving to LLVM 9, which has an optimization that makes
nil pointer checking easier to implement.
2019-09-22 08:25:50 +02:00
Ayke van Laethem cea0d9f864 Makefile: include the transform package in the gofmt list
To make sure it stays properly formatted.
2019-09-22 08:25:50 +02:00
Jaden Weiss 1913e3e1cd allow selecting a different go version and use ccache for LLVM whenever possible 2019-09-21 18:18:51 +02:00
Ayke van Laethem 9d47897368 gitignore: ignore llvm-project directory
It was ignored before when the directory was still just called llvm, but
now it isn't anymore. Fix that.
2019-09-21 16:59:14 +02:00
Ron Evans a7d00f1b6d machine/gameboy-advance: allow TinyGo to directly output GBA files that are ready for flashing by performing objcopy
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-21 15:03:18 +02:00
Jaden Weiss 7732c6f449 fix bug in IR regarding type aliases 2019-09-20 10:35:49 +02:00
Ayke van Laethem cf2a7b5089 compiler: add //go:align pragma
This is sometimes needed for globals. For example, the atsamd21 chip
requires the DMA buffers to be aligned on a 16 byte boundary.
2019-09-18 20:15:17 +02:00
Ayke van Laethem d40fb5bc46 main: switch to version 0.9.0-dev 2019-09-18 20:03:48 +02:00
Ayke van Laethem cc313798f3 main: version 0.8.0 2019-09-18 16:51:27 +02:00
Ayke van Laethem 86acfc7ef4 ci: add caching to macOS build
This should speed up testing.
2019-09-18 16:34:01 +02:00
Ron Evans a5fb785334 compiler: ensure that any passed in target, if it does not point to a .json file, is a full LLVM triple
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-18 16:11:09 +02:00
Ron Evans 86b888fdcb docs: update README with current list of supported boards
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-18 15:50:52 +02:00
Ron Evans ef1bfe359c runtime/samd51: set minimum sleep to 260us due to minimum delay in register synchronization
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-18 15:27:34 +02:00
Ron Evans 0a5601c3fc runtime/samd21: set minimum sleep to 214us due to minimum delay in register synchronization
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-18 15:26:16 +02:00
Ron Evans f5fd49bb74 machine/samd21: use PinMode for SPI SERCOM peripheral to allow for more configuration options on boards like Arduino Nano33-IoT that have many predefined pin mappings
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-17 21:28:05 +02:00
Ayke van Laethem d2856bd6bd nrf: improve SPI write-only speed, by making use of double buffering
The SPI peripheral in the nrf chips support double buffering, which
makes it possible to keep sending continuously. This change introduces
double buffering on the nrf chips, which should improve SPI performance.

Tested on the pca10040 (nrf52832).
2019-09-17 17:51:13 +02:00
Elliott Sales de Andrade 869d2c4524 Add a way to bake-in final TINYGOROOT.
This is similar to setting FINAL_GOROOT when building standard Go.
2019-09-17 17:28:17 +02:00
cn 9d35c1197f machine/stm32f103xx/nucleo-f103rb: add support for NUCLEO-F103RB STM32F1-based board
Compared to the already supported stm32f103xx "bluepill" board this:

- features 128 KiB flash memory size ("RB" suffix) instead of 64 KiB, see `targets/stm32f103rb.ld`
- has onboard ST-LINK/V2-1 programmer and debugger requiring different OpenOCD configuration file
- uses USART2 connected to ST-LINK/V2-1 debugger as virtual COM port over USB for `putchar()`
- has a user-accessible button besides the reset button
2019-09-17 13:26:26 +02:00
cn 688dd81400 machine/stm32f103xx: allow board specific UART usage
Motivation: The bluepill uses USART1 as UART0 but other boards like the
STM32 Nucleo boards (and disco as well) use USART2 for USB COM port.

To avoid duplication of code the same pattern as in `machine_atsamd21.go`
is applied where only UART-specific code is moved to `board_*.go`.
2019-09-17 10:28:03 +02:00
Jaden Weiss abca3132a9 fix bugs found by LLVM assertions 2019-09-16 18:31:33 +02:00
Ayke van Laethem e0ebc75df2 runtime: implement memcpy
A call to memcpy is sometimes created by the compiler, for example when
compiling with -opt=s or opt=2.
2019-09-16 15:34:16 +02:00
Ayke van Laethem b4859240e1 main: stop running the LLVM verifier for trivial changes
The above changes might indeed introduce inconsistencies in the IR, but
the code is small and doesn't change often so it's unnecessary to always
check for errors. It will be tested again later anyway.

The compile time impact was somewhere around 6%, so that's a nice
improvement.
2019-09-16 15:11:59 +02:00
Konstantin Itskov 4eb34b36f8 Add syscall/js.valueInvoke support 2019-09-16 13:36:37 +02:00
Konstantin Itskov 61750be9aa Add syscall/js.valueSetIndex support 2019-09-16 13:32:31 +02:00
cn efd9cf72ba machine/stm32f103xx: add machine.Pin.Get method for reading GPIO values
Writing pin values was already possible but reading was missing.
2019-09-16 13:24:21 +02:00
cn 500ee8067d machine/stm32f103xx: fix SPI frequency selection
The SPI frequency is broken since https://github.com/tinygo-org/tinygo/commit/b8c326d710a88877413e550b06100d9b79373dcd
added I2C interface and changed the unrelated `PCLK2 = HCLK/4` initialization
to `PCLK2 = HCLK/1` (but I2C uses PCLK1 anyways).

This commit changes all baud rate prescalers to be /4 compared to before.

Note: it is not possible to find an equivalent for 125 KHz SPI speed,
it will be too fast (`f = 72 MHz / 256`)
2019-09-16 11:08:59 +02:00
Ayke van Laethem 8cd2c7502e all: move OptimizeMaps to transforms and add tests 2019-09-15 21:26:27 +02:00
Ayke van Laethem d905476231 all: refactor heap-to-stack transform into the transform package
Also add unit tests.

This is the first of several transformation (optimization/lowering)
passes that I'd like to move to the new transform package. This
separates the compiler from the optimizer.

Also, it finally adds unit tests for the compiler, not just end-to-end
compilation tests. This should improve robustness and should make it
easier to change these transformation passes in the future.
While the heap-to-stack transform is relatively simple, other passes are
much more complex. Adding unit tests not only helps robustness over
time, but also doubles as documentation as to what these transformation
passes do exactly.
2019-09-15 21:26:27 +02:00
Ayke van Laethem 10ed3decb0 compiler: rename getZeroValue to llvm.ConstNull
It does the same thing but should be more complete, and it probably is
faster as well (just one CGo call instead of several).
2019-09-15 19:09:10 +02:00
Ayke van Laethem 8d959b7c63 all: replace dep with Go modules
We've moved to go modules, and keeping both working is burdensome.
In fact, I think dep already wasn't working.
2019-09-15 18:04:38 +02:00
Ron Evans 02c4020228 machine/atsamd51: add support for ATSAMD51 processor using Adafruit ItsyBitsy-M4 board
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-14 17:56:14 +02:00
Ron Evans bc41cc688f main: check the major/minor installed version of Go before tinygo compile, to ensure that it is a supported version.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-14 17:50:16 +02:00
Ron Evans 7d481c179f machine/atsamd21: uses different required magic values for BOSSA and UF2 bootloaders
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-14 16:18:30 +02:00
Ron Evans 69aaea44a0 machine/atsamd21: Add support for bootloader reset/programming and correct error in receiving endpoint 0 data for CDC Set Line Coding changes, implementing system reset on switch to 1200 baud connection speed with DTR false.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-14 16:18:30 +02:00
Ron Evans 6c1abfe047 device/arm: add support for System Control Block (SCB) registers and SystemReset() function
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-14 16:18:30 +02:00
Ron Evans 656fb4e372 tools/generator: correctly handle padding when it is 3 bytes long
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-13 00:40:25 +02:00
Ron Evans f4485f414f uf2: extract target address from ELF to accomodate the differences with M0 vs M4 processors
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-13 00:37:54 +02:00
Phil Kedy 55144ad608 WASM: Support for setting an imported function's module name (#455)
* wasm: add support for setting a function's Wasm import module name by using the //go:wasm-module comment.
2019-09-12 08:35:43 +02:00
Ron Evans 1dbfc976e8 docker: use Go 1.12 base Docker image for compatibility
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-09 09:37:43 +02:00
Ron Evans e26f0b35e3 machine/atsamd21: correct order of params for USB CDC descriptor
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-06 14:24:32 +02:00
Ron Evans 4ee26fd54b machine/atsamd21: correct issue with invalid first reading coming from ADC
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-09-06 14:22:24 +02:00
Jaden Weiss a718b10502 fix sleep queue corruption bug 2019-09-06 14:19:05 +02:00
BCG 8a5fa51f60 Updated SPI pins so that they are configurable, enabling multiple SPI sercoms for ATSAMD21 2019-08-28 11:19:52 +02:00
Elliott Sales de Andrade 7c5f943564 Fix a Python SyntaxWarning.
Using 'is' for literals is incorrect, and only should be done for
singletons.
2019-08-27 14:17:17 +02:00
Ayke van Laethem 7938740bf3 tools: make tools python2 compatible
This allows the tools to be used for Python 2 and Python 3.
2019-08-26 16:39:28 +02:00
Ayke van Laethem 319d21e662 runtime: don't mark the object right before a non-existing object
False positives (pointers that point to nowhere but happen to point into
the heap) would result in the block just before that pointer to be
marked. This is clearly not intended, so ignore such a pointer.
2019-08-25 13:12:27 +02:00
Ayke van Laethem 3bf2487dc5 runtime: add some more asserts to the GC 2019-08-25 13:12:27 +02:00
Ayke van Laethem 6917faabf5 runtime: fix GC to take goroutines into account
This fix is needed because with the new task-based scheduler, the
current stack pointer may not be on the system stack.
2019-08-25 13:12:27 +02:00
Ayke van Laethem db4de46d88 runtime: add dummy getCurrentStackPointer functions
This is useful for the next commit, to get it to compile on all systems.
2019-08-25 13:12:27 +02:00
Ayke van Laethem 9b1dd8e03d arm7tdmi: clean up architecture definition 2019-08-25 13:12:27 +02:00
Ayke van Laethem e356bad4d1 reflect: implement t.Comparable()
This is necessary to support the context package, which is a dependency
of a lot of packages.
2019-08-20 10:20:09 +02:00
Ayke van Laethem c19c738f52 reflect: implement support for array types 2019-08-19 11:08:26 +02:00
Ayke van Laethem bbc3046687 compiler: add support for 'go' on func values
This commit allows starting a new goroutine directly from a func value,
not just when the static callee is known.

This is necessary to support the whole time package, not just the
commonly used subset that was compiled with the SimpleDCE pass enabled.
2019-08-17 11:51:43 +02:00
Ayke van Laethem e4fc3bb66a compiler,runtime: fix new task-based scheduler
A bug was introduced in the previous commit that led to miscompilations
in the time.Sleep function when the scheduler was disabled, because
time.Sleep (implemented in the runtime) tried to switch to the scheduler
stack.

This commit restores the binary size of most examples to what it was
before, but still reduces static RAM consumption (.bss) slightly. This
gives me some confidence that it does indeed fix the introduced bug.
2019-08-17 08:42:23 +02:00
Ayke van Laethem 542135c357 compiler,runtime: implement stack-based scheduler
This scheduler is intended to live along the (stackless) coroutine based
scheduler which is needed for WebAssembly and unsupported platforms. The
stack based scheduler is somewhat simpler in implementation as it does
not require full program transform passes and supports things like
function pointers and interface methods out of the box with no changes.

Code size is reduced in most cases, even in the case where no scheduler
scheduler is used at all. I'm not exactly sure why but these changes
likely allowed some further optimizations somewhere. Even RAM is
slightly reduced, perhaps some global was elminated in the process as
well.
2019-08-15 17:31:54 +02:00
Brad Erickson 61f711ef26 Add common test logging methods such as Errorf/Fatalf/Printf
Implements nearly all of the test logging methods for both T and B
structs. Majority of the code has been copied from:
golang.org/src/testing/testing.go
then updated to match the existing testing.go structure.

Code structure/function/method order mimics upstream.

Both FailNow() and SkipNow() cannot be completely implemented,
because they require an early exit from the goroutine. Instead,
they call Error() to report the limitation.

This incomplete implementation allows more detailed test logging and
increases compatiblity with upstream.
2019-08-13 20:59:09 +02:00
Ayke van Laethem fd3309afa8 compiler,runtime: implement []rune to string conversion
This is used by a few packages in the standard library, at least
compress/gzip and regexp/syntax.
2019-08-11 15:45:35 +02:00
Ayke van Laethem fea56d4164 compiler: add support for full slice expression for slicing arrays
This was an oversight in the main commit for full slice expressions:
https://github.com/tinygo-org/tinygo/pull/472
This syntax is used by the regexp package, for example.
2019-08-11 15:37:05 +02:00
Ayke van Laethem ea8e4079bc reflect: add support for linked lists
Linked lists are usually implemented as follows:

    type linkedList struct {
        next *linkedList
        data int // whatever
    }

This caused a stack overflow while writing out the reflect run-time type
information. This has now been fixed by splitting the allocation of a
named type number from setting the underlying type in the sidetable.
2019-08-11 15:00:43 +02:00
Ayke van Laethem 0818a125c0 reflect: fix IsNil and Pointer for addressable pointer types 2019-08-11 15:00:43 +02:00
Brad Erickson 7c758b0dfc testing: Add Benchmark B struct stub
This struct allows test files containing basic benchmarks to compile
and run, but will not run the benchmarks themselves.

For #491
2019-08-11 14:51:02 +02:00
Daniel Esteban 4ea59742d7 CONTRIBUTORS: add name 2019-08-10 22:00:01 +02:00
Ayke van Laethem b7cb10519c compiler: fix some invalid IR
Compiling LLVM with assertions enabled revealed some bugs in TinyGo that
weren't caught by normal verifying of the LLVM module.
2019-08-09 21:27:23 +02:00
Ayke van Laethem 562ad740da compiler: make runtime.makeGoroutine AVR compatible
Previously it would use a bitcast, which cannot directly be used on AVR
because functions live in a different address space on AVR. To fix this,
use a ptrtoint/inttoptr pair.

This allows testdata/coroutines.go to be compiled, but due to what
appears to be an LLVM bug cannot be optimized and codegen'ed:

    tinygo: /home/ayke/src/github.com/tinygo-org/tinygo/llvm-project/llvm/lib/IR/Constants.cpp:1776: static llvm::Constant *llvm::ConstantExpr::getBitCast(llvm::Constant *, llvm::Type *, bool): Assertion `CastInst::castIsValid(Instruction::BitCast, C, DstTy) && "Invalid constantexpr bitcast!"' failed.

This happens as one of the function passes after the TinyGo passes and
after the module has been verified so most likely it is a bug somewhere
in LLVM.
2019-08-09 21:27:23 +02:00
Kyle Lemons (Loon, LLC) 92206558fb gameboy-advance: don't crash copying unaligned strings
Strings are emitted in .rodata sections, which are not yet mentioned in the linker script.

I can't exactly explain why it didn't work before, as these sections should have been included in .bss and thus properly aligned, but it appears to work reliably.
2019-08-09 20:14:17 +02:00
Kyle Lemons (Loon, LLC) fd6b671494 CONTRIBUTORS: Add Loon, LLC for kevlar@loon.com 2019-08-09 20:14:14 +02:00
Ayke van Laethem e2c8654237 reflect: add support for struct types 2019-08-08 15:23:47 +02:00
Ayke van Laethem 5012be337f reflect: implement Type.Align() 2019-08-08 15:23:47 +02:00
Ayke van Laethem 614433cb75 reflect: implement Type.AssignableTo 2019-08-08 15:23:47 +02:00
Ayke van Laethem b8cd8b6f25 reflect: add support for Type.Bits() 2019-08-08 15:23:47 +02:00
Ayke van Laethem f43d01bdc7 compiler: make struct types more unique
There are a lot more fields that are important when comparing structs
with each other. Take them into account when building the unique ID per
struct type.

Example code that differs between the compilers:
https://play.golang.org/p/nDX4tSHOf_T
2019-08-08 15:23:47 +02:00
Ayke van Laethem 9979cf2bbd CONTRIBUTING: add my name 2019-08-08 12:07:49 +02:00
Ayke van Laethem 79b470acee compiler: make goroutine lowering panic message more helpful
This is a workaround for an existing issue, to see which function is to
blame.
2019-08-08 11:59:53 +02:00
Ron Evans 0b7047cc0e governance: add CONTRIBUTORS file to repo
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-08-08 11:59:41 +02:00
Ron Evans 99add1571b docker: Debian stable-slim image now based on buster not stretch, so update the needed package repo used for LLVM.
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-08-07 11:27:44 +02:00
Elliott Sales de Andrade b7f4373681 Fix parsing of beta Go versions. 2019-08-05 17:45:45 +02:00
Ayke van Laethem 95721a8d8c reflect: add support for named types
With this change, it becomes possible to get the element type of named
slices, pointers, and channels.

This is a prerequisite to enable the common named struct types. There's
more to come.
2019-08-05 14:44:30 +02:00
Ayke van Laethem 33dc4b5121 compiler: fix crash with linked lists in interfaces
This commit fixes the following issue:
https://github.com/tinygo-org/tinygo/issues/309
Also, it prepares for some other reflect-related changes that should
make it easier to add support for named types (etc.) in the future.
2019-08-05 14:44:30 +02:00
Ayke van Laethem a04db67ea9 runtime: work around a bug in LLVM for .bss zeroing
See the following bug: https://bugs.llvm.org/show_bug.cgi?id=42881

I think this is a bug in LLVM, but the code in question wasn't the best
code anyway. By fixing this, about 16 bytes of code are saved on ARM
chips (and much more on AVR).
2019-08-05 10:07:46 +02:00
Ayke van Laethem 9846c062b3 targets: add support for GameBoy Advance
Only tested in an emulator (mGBA). Almost nothing is supported, but
drawing to the screen works.
2019-08-05 09:19:49 +02:00
Elliott Sales de Andrade 9878f7ebc4 Add support for linux/386 syscalls. 2019-08-04 18:15:06 +02:00
Ayke van Laethem 4688664b41 compiler: implement full slice expression
This feature was introduced in Go 1.2 and is used by some standard
library packages.
2019-08-04 17:51:16 +02:00
Ayke van Laethem 54169c714f all: use baremetal build tag
This simplifies adding more baremetal targets, like a GameBoy Advance,
or baremetal x86 for unikernels.
2019-08-04 17:12:07 +02:00
Justin Clift f76385850d main: fix outdated panic option text 2019-08-04 14:11:08 +02:00
Doug Daniels cb09531873 Update examples/wasm README.md
Modify instructions for running the wasm example HTTP server to `go run server.go`
2019-08-03 11:27:06 +02:00
Sven Sauleau 51f2a152d5 Update README.md 2019-08-03 11:24:34 +02:00
Ayke van Laethem c3880b3ec5 main: update version to 0.8.0-dev 2019-08-02 17:21:57 +02:00
Ayke van Laethem 7d5542dda7 main: version 0.7.1
This release contains a small fix for some atsamd21-based boards to make
them usable on macOS: it now allows to specify the serial port using the
-port flag.
2019-07-27 10:02:10 -07:00
Ayke van Laethem 8eb6039052 ci: fix Go image on the Debian Stretch images
The build broke because the images got upgraded from stretch to buster.
Specify the stretch images (for now) so that it works again.
We can upgrade to buster for go1.12 at a later time.
2019-07-27 09:51:38 -07:00
Trevor Rosen 64597de344 Device path at flash time for several boards 2019-07-25 10:17:16 -07:00
Ayke van Laethem 515daa7d3c main: version 0.7.0 2019-07-17 15:36:38 +02:00
Ron Evans ced964f039 docs: add Arduino Nano33 IoT to README
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-07-17 06:30:58 -07:00
Ayke van Laethem 8a704e43fc os: gofmt 2019-07-15 17:53:01 +02:00
Justin Clift 847681457f runtime: add several os package stubs 2019-07-15 01:18:37 +02:00
Ayke van Laethem 66d8899aa7 main: set the current working directory when calling an external linker
In particular, while LLVM lld supports -L for linker scripts imported
with the `INCLUDE` command, GNU ld does not seem to support this.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Also, it fixes this smelly code:

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

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

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

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

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

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

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

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

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

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

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

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

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

No real-world errors have been seen in a while and errors would likely
indicate a serious compiler bug anyway (not an external error), so make
it panic instead of returning an error.
2019-04-26 08:52:10 +02:00
Ayke van Laethem 024eceb476 runtime: print error when panicking with error interface type 2019-04-25 14:06:34 +02:00
Ayke van Laethem 0fd90c49cc compiler: make panic configurable
Currently defined: abort and trap. -panic=unwind should be implemented
in the future.
2019-04-25 13:56:19 +02:00
Ayke van Laethem d1efffe96b test: print better error messages on compilation failure 2019-04-25 12:55:52 +02:00
Ayke van Laethem 8e7ea92d44 cgo: improve error locations for cgo-constructed AST
This is mostly useful for debugging missing type conversions in CGo.
With this change, errors will have the correct source location in C
files.
2019-04-25 12:55:52 +02:00
Ayke van Laethem 2f2d62cc0c cgo: support builtin #include headers
Add support for header files bundled with the compiler by copying them
into the release tarball.
2019-04-25 12:55:52 +02:00
Ayke van Laethem d396abb690 cgo: add dummy implementation of __builtin_va_list
Every ABI has a slightly different implementation. Ideally, we would use
something like Clang TargetInfo or extract it by compiling some C code
and checking the IR, but this is a useful workaround for now.
2019-04-25 10:48:56 +02:00
Ayke van Laethem b815d3f760 cgo: implement void* pointer type
void* is translated to unsafe.Pointer on the Go side.
2019-04-25 10:48:56 +02:00
Ayke van Laethem 9c46ac4eed cgo: implement char type
This type is a bit more difficult because it can be signed or unsigned
depending on the target platform.
2019-04-25 10:48:56 +02:00
Ron Evans b2e96fc35a machine/atsamd21: select internal ground for ADC and scale result correctly to 16-bit
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-22 07:59:35 +02:00
Ayke van Laethem fa5df4f524 main: version 0.5.0 2019-04-20 20:17:41 +02:00
Ayke van Laethem 09db7ead50 cgo: better error message when using an undefined CGo function pointer 2019-04-20 10:18:38 +02:00
Ayke van Laethem 21a4c14e86 cgo: implement C.struct_ types
These types (called elaborated types in C) are used as part of linked
lists, among others.

This is part an extra feature (to be compatible with CGo C.struct_
types) and part a bugfix: linked lists would result in endless recursion
leading to a stack overflow.
2019-04-20 10:18:38 +02:00
Ayke van Laethem b716cf1afd loader/libclang: fix CGo-related crash
Sometimes when a GC happens while processing a C fragment with libclang,
a pointer-typed integer with value 0x1 ends up on the Go stack and the
GC will trip over it. This commit changes the offending struct type to
be uintptr_t instead of void*.

See https://go-review.googlesource.com/c/go/+/66332 for a similar
change.
2019-04-20 10:07:26 +02:00
Johan Brandhorst 586023b45d src/examples/wasm: Show both methods supported
Adds another example showing the simple case
of executing main, adds a README explaining how
everything fits together and how to execute the compiled
code in the browser. Include a minimal webserver for
local testing.
2019-04-19 17:46:46 +02:00
Ron Evans a00a51e70e examples: add microbit blink example
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-19 15:30:12 +02:00
Ron Evans 745b5dfb81 examples: remove colorlamp example that is better suited for the TinyGo Zoo (and already in it)
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-18 19:03:13 +02:00
Ayke van Laethem 9c50d47b82 BUILDING.md: update to commands included in Makefile
This should make it easier to make a working build of LLVM.
2019-04-17 23:15:37 +02:00
Ayke van Laethem 6c63a0d6e7 Makefile: build static binaries only
This replaces the older way which just does the following:

    go install .

and

    go test -v .

Instead, `make` and `make test` will now build TinyGo statically linked
against LLVM, so that `go install` and `go test -v` should be used
manually.
2019-04-17 23:15:37 +02:00
Ayke van Laethem 2a0a7722f9 compiler: lower func values to switch + direct call
This has several advantages, among them:
  - Many passes (heap-to-stack, dead arg elimination, inlining) do not
    work with function pointer calls. Making them normal function calls
    improves their effectiveness.
  - Goroutine lowering to LLVM coroutines does not currently support
    function pointers. By eliminating function pointers, coroutine
    lowering gets support for them for free.
    This is especially useful for WebAssembly.
Because of the second point, this work is currently only enabled for the
WebAssembly target.
2019-04-17 23:12:59 +02:00
Ayke van Laethem 1460877c28 compiler: refactor func value handling
This commit refactors all func value handling into a new file, which
makes it easier to comprehend it and extend it later.
2019-04-17 23:12:59 +02:00
Ayke van Laethem 0739775719 compiler: extract inline asm builtins into separate file
This commit refactors the compiler a bit to have all inline assembly in
a separate file.
2019-04-17 23:12:59 +02:00
Ron Evans 8d3b5a58d1 machine/atsamd21: correct pad/pin handling when using both UART and USBCDC interfaces at same time
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-17 22:50:08 +02:00
Ayke van Laethem 0d2a3ce532 Makefile: check whether LLVM was build before making a static binary 2019-04-17 19:13:42 +02:00
Ayke van Laethem d2b3a5486c cgo: implement C unions
Unions are somewhat hard to implement in Go because they are not a
native type. But it is actually possible with some compiler magic.

This commit inserts a special "C union" field at the start of a struct
to indicate that it is a union. As such a field cannot be written
directly in Go, this is a useful to distinguish structs and unions.
2019-04-17 11:56:40 +02:00
Ayke van Laethem 536086988c cgo: implement Go arrays (constant arrays in C) 2019-04-17 11:56:40 +02:00
Ayke van Laethem 684543b7f1 cgo: implement struct types
Not complete: packed structs are treated as regular structs.
2019-04-17 11:56:40 +02:00
Ayke van Laethem bd8e47af80 cgo: make libclang code thread-safe 2019-04-17 11:56:40 +02:00
Ayke van Laethem 5b34713d41 main: fix linker script includes when running outside TINYGOROOT
This commit adds the TinyGo root directory (`TINYGOROOT`) to the linker
script `-L` search path, so that linker scripts can be found when
running `tinygo` outside of the TinyGo root.

This was already working before when using an external linker by setting
the working directory, but this is not possible when using the internal
linker. However, by adding the root directory to the linker search path
(`-L`), it can now find these linker scripts.

fixes #265
2019-04-17 09:21:15 +02:00
Ron Evans f1aea13c51 tools: remove Makefile tasks that are redundant thanks to TinyGo improvements
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-16 14:08:31 +02:00
Ayke van Laethem 7bcabe53ca compiler: fix interface lowering pass
It was removing some globals that still had uses left.
2019-04-15 19:43:17 +02:00
Ayke van Laethem 7de3d4be2b all: support interface asserts in interp
This adds support for the math/rand package.
2019-04-13 20:55:56 +02:00
Ayke van Laethem 02ecab833f all: check formatting on CI 2019-04-13 19:58:58 +02:00
Yusuke Mitsuki 1322f404a6 stm32: add support for the STM32F4Discovery
Signed-off-by: Yusuke Mitsuki <mickey.happygolucky@gmail.com>
2019-04-13 13:32:05 +02:00
Ayke van Laethem 315cd4059f main: drop the dependency on llvm-ar
The ar file format is pretty simple and can be implemented by using a Go
library. Use that instead of calling out to llvm-ar.

There are a few limitations to the used package, but that doesn't seem
to matter for our use case (linking compiler-rt for use with ld.lld):

  * no index is created
  * long filenames are truncated
  * no support for archives bigger than 4GB
2019-04-11 11:53:58 +02:00
Ayke van Laethem 078dd9ff52 cgo: improve diagnostics
This makes CGo-emitted diagnostics very similar to regular errors
emitted while parsing/typechecking a package.
It's not complete, but after introducing some errors in testdata/cgo,
this is the resulting output:

    # ./testdata/cgo/
    testdata/cgo/main.h:18:11: error: a parameter list without types is only allowed in a function definition
    testdata/cgo/main.go:5:10: note: in file included from testdata/cgo/main.go!cgo.c:2:
    testdata/cgo/main.go:6:19: error: expected identifier or '('

Previously, this was the output:

    /home/ayke/src/github.com/tinygo-org/tinygo/testdata/cgo/main.h:18:11: error: a parameter list without types is only allowed in a function definition
    cgo-fake.c:3:19: error: expected identifier or '('
    # ./testdata/cgo/
    cgo: libclang cannot parse fragment
2019-04-11 10:11:09 +02:00
Ayke van Laethem e5029c63d1 compiler: optimize ptrtoint+add+inttoptr pattern
This pattern is often used in some runtime intrinsics (especially the
ones related to slices) to do pointer arithmetic with unsafe.Pointer and
uintptr because Go does not support pointer arithmetic.

Recognizing this pattern and replacing it with a gep instruction
improves code size in various tests.
2019-04-11 09:16:10 +02:00
Ayke van Laethem dcffbc49c4 compiler: add param attrs to memmove and memcpy
Add nocapture, readonly, and writeonly to runtime.memmove and
runtime.memcpy where appropriate. This teaches LLVM some more
optimizations it may perform, leading to reduced .text size in some
cases.
2019-04-11 09:16:10 +02:00
Ayke van Laethem 6a2a587dff compiler: fix MakeSlice bounds check and casting 2019-04-10 20:21:33 +02:00
Ayke van Laethem 3a76a49ddf compiler: fix int casting to use the source signedness
Previously, when casting an integer to a bigger integer, the destination
signedness was used. This is problematic when casting a negative int16
to uint32, for example, because it would cause zero-extension.
2019-04-10 20:21:33 +02:00
Ayke van Laethem 81a1114ee5 compiler: truncate string slice indices if necessary
This didn't trigger on most platforms but does trigger on AVR where
almost all slice operations on strings are with integers that are bigger
than uintptr.
2019-04-10 20:21:33 +02:00
Ayke van Laethem 30e192e7e8 compiler: let the default 'low' slice bound be a uintptr 2019-04-10 20:21:33 +02:00
Ayke van Laethem a965882a34 compiler: support i16 uintptr in cap builtin 2019-04-10 20:21:33 +02:00
Ayke van Laethem f967c6919a interp: fix segmentation fault in some builds
A call to .IsConstant() also returns true for constant globals, not just
constant expressions. Do an extra check that we're really operating on a
constant expression.
2019-04-10 18:29:22 +02:00
Ayke van Laethem 2e926789f5 circleci: do macOS builds here instead of on Travis CI
This provides several advantages. Among others:
  * Much faster and hopefully more reliable.
  * Good caching support to store LLVM builds.
  * Building and testing of release-ready artifacts.
2019-04-08 09:13:16 +02:00
Ayke van Laethem 38c3d0852e compiler: implement casting named structs and pointers to them 2019-04-06 08:32:28 +02:00
Ron Evans 85f2ef40f8 machine/itsybitsy-m0: set pins and pin mode mapping for i2c0 bus
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-05 15:25:11 +02:00
Ron Evans ae4ead8690 machine/circuitplay: correct pin and pin mode mapping for both i2c0 (external) and i2c1 (internal) buses
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-04-05 14:31:26 +02:00
Ayke van Laethem ebebdd5651 circleci: build LLVM statically 2019-04-05 13:30:38 +02:00
Ayke van Laethem 1778d92858 Makefile: automatically build LLVM when needed 2019-04-05 13:30:38 +02:00
Ayke van Laethem 86f8778748 darwin: use custom syscall pkg that uses libsystem
Go 1.12 switched to using libSystem.dylib for system calls, because
Apple recommends against doing direct system calls that Go 1.11 and
earlier did. For more information, see:
  https://github.com/golang/go/issues/17490
  https://developer.apple.com/library/archive/qa/qa1118/_index.html

While the old syscall package was relatively easy to support in TinyGo
(just implement syscall.Syscall*), this got a whole lot harder with Go
1.12 as all syscalls now go through CGo magic to call the underlying
libSystem functions. Therefore, this commit overrides the stdlib syscall
package with a custom package that performs calls with libc (libSystem).
This may be useful not just for darwin but for other platforms as well
that do not place the stable ABI at the syscall boundary like Linux but
at the libc boundary.

Only a very minimal part of the syscall package has been implemented, to
get the tests to pass. More calls can easily be added in the future.
2019-04-05 09:53:51 +02:00
Ayke van Laethem 2523772b5d arm: use the lld linker
LLD version 8 has added support for armv6m:
    https://reviews.llvm.org/D55555
This means we can use LLD instead of arm-none-eabi-ld, eliminating our
dependency on GNU binutils.

There are small differences in code size, but never more than a few
bytes.
2019-04-04 12:50:15 +02:00
Ayke van Laethem 31d57fd3d1 main: use wasm-ld instead of wasm-ld-8 on macOS
This commit does a few things:
  * remove the -8 suffix on macOS, where it is not necessary
  * add smoke tests for compiling wasm files on Linux and macOS
2019-04-04 12:50:15 +02:00
Ayke van Laethem d653088cbe compiler: fix escapes due to nil checks
Some tests get bigger, most get smaller. However, all tested driver
examples get smaller in size showing that this is a good change in the
real world.
2019-04-04 09:32:30 +02:00
Ayke van Laethem cd8471acae all: support Go 1.12 2019-04-03 21:32:29 +02:00
Ayke van Laethem b64fc5484a runtime: implement memmove intrinsic
This should fix the following issue:
https://github.com/tinygo-org/tinygo/issues/252
2019-04-03 17:04:13 +02:00
Ayke van Laethem 38f8cf7bee compiler: imporove escape analysis to allow icmp
The icmp instruction is often used in nil checks, so this instruction
happens very frequently now that TinyGo automatically inserts nil checks
everywhere. Escape analysis would conservatively mark such pointers as
escaping, which they obviously don't.
This commit improves escape analysis to allow icmp instructions.
2019-04-03 16:38:08 +02:00
Ayke van Laethem f484dddbc2 circleci: don't install the llvm package
This is not necessary anymore since d6c2d6e301: llvm-ar has been
replaced with llvm-ar-8.
2019-04-02 19:10:48 +02:00
Ayke van Laethem a2d0f79be3 all: pretend to be linux/arm in baremetal targets
So far, we've pretended to be js/wasm in baremetal targets to make the
stdlib happy. Unfortunately, this has various problems because
syscall/js (a dependency of many stdlib packages) thinks it can do JS
calls, and emulating them gets quite hard with all changes to the
syscall/js packages in Go 1.12.

This commit does a few things:
  * It lets baremetal targets pretend to be linux/arm instead of
    js/wasm.
  * It lets the loader only select particular packages from the src
    overlay, instead of inserting them just before GOROOT. This makes it
    possible to pick which packages to overlay for a given target.
  * It adds a baremetal-only syscall package that stubs out almost all
    syscalls.
2019-03-23 22:58:26 +01:00
Ayke van Laethem 792274e86f runtime/wasm: provide dummy runtime.setEventHandler
Event handlers aren't supported yet. This commit gets syscall/js to
compile for the WebAssembly target.
2019-03-23 16:16:19 +01:00
Ayke van Laethem 06aa88abfb reflect: add stubs for Value.MapRange
This object was added in Go 1.12 and is required by the fmt package.
2019-03-23 16:16:19 +01:00
Ayke van Laethem ad7297a539 all: implement trivial select statements
Implement two trivial uses of the select statement.

Always blocking:
    select {}

No-op:
    select {
    default:
    }

Go 1.12 added a `select {}` instruction to syscall/js, so this is needed
for Go 1.12 support. More complete support for select will be added in
the future.
2019-03-23 16:16:19 +01:00
Ayke van Laethem 4d82f42d61 runtime: add runtime.nanotime
This function returns the current timestamp, or 0 at compile time.

runtime.nanotime is used at package initialization by the time package
starting with Go 1.12.
2019-03-23 16:16:19 +01:00
Ayke van Laethem 9c41011e17 all: switch to LLVM 8 2019-03-22 22:55:11 +01:00
Ayke van Laethem 5569cd1b6b main: version 0.4.1 2019-03-15 13:37:37 +01:00
Ron Evans d6c2d6e301 main: use OS specific name for llvm-ar-7 tool to ensure that llvm7 toolchain works as expected
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-03-15 12:46:03 +01:00
Ayke van Laethem a466dd8f2b main: include .data section in .hex file
The function extracting the firmware image for .hex and .bin files
wasn't working correctly: it only extracted the .text segment and not
the .data segment.
This commit fixes this issue, so that it behaves (hopefully) just like
objcopy -O{ihex|binary}.

Another small change is that the formatting of the .hex file was made
more like the output of objcopy: no entry addres (old Intel CPU
holdover) and 16 bytes of data on each line.
2019-03-11 17:10:16 +01:00
Ayke van Laethem b1744db2c8 main: version 0.4.0 2019-03-09 20:41:38 +01:00
Ayke van Laethem bd6a7b69ce compiler: inline slice bounds checking
This improves code size in all tests by about 1% and up to 5% in some
cases, likely because LLVM can better reason about inline bounds checks.
2019-03-08 19:11:22 +01:00
Ayke van Laethem 051ad07755 compiler: refactor slice related asserts
Move these asserts into compiler/asserts.go, to keep them together.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* docs: add info about Slack channel to contributing guidelines

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

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

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

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

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

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

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

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

The biggest change, that was done together with the rewrite, was support
for goroutines in WebAssembly for JavaScript. The challenge in
JavaScript is that in general no blocking operations are allowed, which
means that programs that call time.Sleep() but do not start goroutines
also have to be scheduled by the scheduler.
2019-01-21 22:09:33 +01:00
Ayke van Laethem 072ef603fe wasm: add GOOS/GOARCH properties
This was an oversight in commit 107fccb288.
2019-01-21 22:08:12 +01:00
Ayke van Laethem 54baf48266 compiler/interface: fix LLVM context for boolean variable
Apparently the given code path was never properly tested.
2019-01-21 22:08:12 +01:00
Ayke van Laethem 072eb590a6 compiler/interface: correct comment on function 2019-01-21 22:08:12 +01:00
Ayke van Laethem c0ab91a263 interp: extra safety check in string emulation 2019-01-21 22:08:12 +01:00
Ron Evans 3ebf464da2 machine/samd21: I2C implementation
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-21 21:54:43 +01:00
Ron Evans 38c5e384af machine/itsybitsy-m0: specify which pins to use for UART0
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-21 21:14:06 +01:00
Ron Evans 65ea74bd84 machine/atsamd21: implements UART0 using the SERCOM0 interface
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-21 21:14:00 +01:00
Ron Evans 683e2a66e1 machine/atsamd21: correct clock calibration based on stored fuse value
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-20 20:26:54 +01:00
Ron Evans 8cbbbb0e76 machine/atsamd21: improve GPIO config to support all 32 pins on PORTA as well as correct handling for OUTPUT and SERCOM pin modes
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-20 18:42:52 +01:00
Ron Evans f89c695c8c generators: correctly handle clustered subtypes used in Atmel SAMD21 SVD for important peripherals
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-20 18:40:49 +01:00
Ron Evans e2be7ccf76 sam: add support for Atmel SAMD21 based ItsyBitsy M0
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-18 18:30:27 +01:00
Ron Evans 1f511786d3 lib/cmsis-svd: update to latest release with updated SAMD21 and SAMD51 SVD files
Signed-off-by: Ron Evans <ron@hybridgroup.com>
2019-01-18 18:30:27 +01:00
Seth Junot c9f4e41073 wasm: fix typo in wasm_exec.js preventing memory import
Seems to have been left over from the original copy. This correction
should fix calls to the "memory" variable in syscall/js.
2019-01-18 13:30:17 +01:00
Seth Junot 67fbfe6305 runtime/wasm: add memset()
Copied from the ARM runtime and modified to return a pointer.
https://pubs.opengroup.org/onlinepubs/9699919799/functions/memset.html
2019-01-18 13:20:22 +01:00
637 changed files with 57032 additions and 9620 deletions
+432
View File
@@ -0,0 +1,432 @@
version: 2.1
commands:
submodules:
steps:
- run:
name: "Pull submodules"
command: git submodule update --init
apt-dependencies:
parameters:
llvm:
type: string
steps:
- run:
name: "Install apt dependencies"
command: |
echo 'deb 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>> \
gcc-arm-linux-gnueabihf \
gcc-aarch64-linux-gnu \
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
install-node:
steps:
- run:
name: "Install node.js"
command: |
wget https://nodejs.org/dist/v10.15.1/node-v10.15.1-linux-x64.tar.xz
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
rm node-v10.15.1-linux-x64.tar.xz
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
llvm-source-linux:
steps:
- restore_cache:
keys:
- llvm-source-10-v0
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-v0
paths:
- llvm-project
build-wasi-libc:
steps:
- restore_cache:
keys:
- wasi-libc-sysroot-v2
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-v2
paths:
- lib/wasi-libc/sysroot
test-linux:
parameters:
llvm:
type: string
steps:
- checkout
- submodules
- apt-dependencies:
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 -tags=llvm<<parameters.llvm>> .
- restore_cache:
keys:
- wasi-libc-sysroot-systemclang-v1
- run: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-systemclang-v1
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 wasmtest
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- /go/pkg/mod
- run: make fmt-check
assert-test-linux:
steps:
- checkout
- submodules
- run:
name: "Install apt dependencies"
command: |
sudo apt-get install \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
- install-node
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-10-linux-v0-assert
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
sudo apt-get install cmake clang ninja-build
# make build faster
export CC=clang
export CXX=clang++
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
# build!
make ASSERT=1 llvm-build
fi
- save_cache:
key: llvm-build-10-linux-v0-assert
paths:
llvm-build
- run: make ASSERT=1
- build-wasi-libc
- run:
name: "Test TinyGo"
command: make ASSERT=1 test
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- /go/pkg/mod
- run: make gen-device -j4
- run: make smoketest TINYGO=build/tinygo
build-linux:
steps:
- checkout
- submodules
- run:
name: "Install apt dependencies"
command: |
sudo apt-get install \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
- install-node
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-10-linux-v0
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
sudo apt-get install cmake clang ninja-build
# make build faster
export CC=clang
export CXX=clang++
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
# build!
make llvm-build
fi
- save_cache:
key: llvm-build-10-linux-v0
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 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
- /go/pkg/mod
- run:
name: "Extract release tarball"
command: |
mkdir -p ~/lib
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
tinygo version
- run: make smoketest
build-macos:
steps:
- checkout
- submodules
- run:
name: "Install dependencies"
command: |
curl https://dl.google.com/go/go1.14.darwin-amd64.tar.gz -o go1.14.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- 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-10-macos-v0
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-10-macos-v0
paths:
- llvm-project
- restore_cache:
keys:
- llvm-build-10-macos-v0
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# build!
make llvm-build
fi
- save_cache:
key: llvm-build-10-macos-v0
paths:
llvm-build
- restore_cache:
keys:
- wasi-libc-sysroot-macos-v1
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-macos-v1
paths:
- lib/wasi-libc/sysroot
- run:
name: "Test TinyGo"
command: make test
- run:
name: "Build TinyGo release"
command: |
make release -j3
cp -p build/release.tar.gz /tmp/tinygo.darwin-amd64.tar.gz
- store_artifacts:
path: /tmp/tinygo.darwin-amd64.tar.gz
- run:
name: "Extract release tarball"
command: |
mkdir -p ~/lib
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
tinygo version
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- /go/pkg/mod
arch-release:
steps:
- run:
name: Install dependencies
command: pacman -Sy --noconfirm openssh git pacman-contrib binutils
- run:
name: Create TinyGo user
command: useradd -m tinygo
- run:
name: Become TinyGo user
command: su tinygo
- run:
name: Start SSH Agent
command: eval $(ssh-agent -s)
- run:
name: Add ARCH_RELEASE_SSH_PRIVATE_KEY identity
command: echo "${ARCH_RELEASE_SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add -
- run:
name: Create SSH directory
command: mkdir -p ~/.ssh && chmod 700 ~/.ssh
- run:
name: Add aur.archlinux.org to known hosts
command: ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts
- run:
name: Clone tinygo-bin repo
command: git clone ssh://aur@aur.archlinux.org/tinygo-bin.git ~/tinygo-bin
- run:
name: Update package version
command: sed -i -E "s/(pkgver=)(.*)$/\1${CIRCLE_TAG}/" ~/tinygo-bin/PKGBUILD
- run:
name: Update file checksums
command: cd ~/tinygo-bin && updpkgsums
- run:
name: Update .SRCINFO
command: cd ~/tinygo-bin && makepkg --printsrcinfo > .SRCINFO
# Commit the update
- run:
name: Set git commit config
command: |
git config --global user.email "tinygo-bot@tinygo.org" &&
git config --global user.name "TinyGo Release Bot"
- run:
name: Commit and push changes
command: |
cd ~/tinygo-bin &&
git commit -a -m "Update tinygo-bin to v${CIRCLE_TAG}" &&
git push origin master
jobs:
test-llvm9-go111:
docker:
- image: circleci/golang:1.11-buster
steps:
- test-linux:
llvm: "9"
test-llvm10-go112:
docker:
- image: circleci/golang:1.12-buster
steps:
- test-linux:
llvm: "10"
test-llvm10-go113:
docker:
- image: circleci/golang:1.13-buster
steps:
- test-linux:
llvm: "10"
test-llvm10-go114:
docker:
- image: circleci/golang:1.14-buster
steps:
- test-linux:
llvm: "10"
assert-test-linux:
docker:
- image: circleci/golang:1.14-stretch
steps:
- assert-test-linux
build-linux:
docker:
- image: circleci/golang:1.14-stretch
steps:
- build-linux
build-macos:
macos:
xcode: "10.1.0"
steps:
- build-macos
arch-release:
docker:
- image: archlinux:latest
steps:
- arch-release
workflows:
test-all:
jobs:
- test-llvm9-go111
- test-llvm10-go112
- test-llvm10-go113
- test-llvm10-go114
- 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]+)*(-.*)*/
+10 -2
View File
@@ -5,8 +5,16 @@ src/device/avr/*.ld
src/device/avr/*.s
src/device/nrf/*.go
src/device/nrf/*.s
src/device/stm32/*.go
src/device/stm32/*.s
src/device/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
+8 -2
View File
@@ -9,8 +9,14 @@
url = https://github.com/avr-rust/avr-mcu.git
[submodule "lib/cmsis-svd"]
path = lib/cmsis-svd
url = https://github.com/posborne/cmsis-svd
url = https://github.com/tinygo-org/cmsis-svd
[submodule "lib/compiler-rt"]
path = lib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt.git
branch = release_70
branch = release_80
[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
-33
View File
@@ -1,33 +0,0 @@
language: go
go:
- "1.11"
before_install:
- echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" | sudo tee -a /etc/apt/sources.list
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list
- sudo apt-get update -qq
- sudo apt-get install llvm-7-dev clang-7 libclang-7-dev binutils-arm-none-eabi qemu-system-arm --allow-unauthenticated -y
- sudo ln -s /usr/bin/clang-7 /usr/local/bin/cc # work around missing -no-pie in old GCC version
install:
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
- dep ensure --vendor-only
script:
- go install github.com/aykevl/tinygo
- go test -v .
- make gen-device
- tinygo build -o blinky1.nrf.elf -target=pca10040 examples/blinky1
- tinygo build -o blinky2.nrf.elf -target=pca10040 examples/blinky2
- tinygo build -o blinky2 examples/blinky2
- tinygo build -o test.nrf.elf -target=pca10040 examples/test
- tinygo build -o blinky1.nrf51.elf -target=microbit examples/echo
- tinygo build -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
- tinygo build -o blinky1.nrf51d.elf -target=pca10031 examples/blinky1
- tinygo build -o blinky1.stm32.elf -target=bluepill examples/blinky1
- tinygo build -o blinky1.avr.o -target=arduino examples/blinky1 # TODO: avr-as/avr-gcc doesn't work
- tinygo build -o blinky1.reel.elf -target=reelboard examples/blinky1
- tinygo build -o blinky2.reel.elf -target=reelboard examples/blinky2
- tinygo build -o blinky1.pca10056.elf -target=pca10056 examples/blinky1
- tinygo build -o blinky2.pca10056.elf -target=pca10056 examples/blinky2
+106
View File
@@ -0,0 +1,106 @@
# Building TinyGo
TinyGo depends on LLVM and libclang, which are both big C++ libraries. It can
also optionally use a built-in lld to ease cross compiling. There are two ways
these can be linked: dynamically and statically. An install with `go install` is
dynamic linking because it is fast and works almost out of the box on
Debian-based systems with the right packages installed.
This guide describes how to statically link TinyGo against LLVM, libclang and
lld so that the binary can be easily moved between systems. It also shows how to
build a release tarball that includes this binary and all necessary extra files.
**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
build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.11+)
* Standard build tools (gcc/clang)
* git
* CMake
* [Ninja](https://ninja-build.org/)
The rest of this guide assumes you're running Linux, but it should be equivalent
on a different system like Mac.
## Download the source
The first step is to download the TinyGo sources (use `--recursive` if you clone
the git repository). Then, inside the directory, download the LLVM source:
make llvm-source
You can also store LLVM outside of the TinyGo root directory by setting the
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
covered by this guide.
TinyGo uses Go modules, so if you clone TinyGo inside your GOPATH (and are using
Go below 1.13), make sure that Go modules are enabled:
export GO111MODULE=on
## Build LLVM, Clang, LLD
Before starting the build, you may want to set the following environment
variables to speed up the build. Most Linux distributions ship with GCC as the
default compiler, but Clang is significantly faster and uses much less memory
while producing binaries that are about as fast.
export CC=clang
export CXX=clang++
The Makefile includes a default configuration that is good for most users. It
builds a release version of LLVM (optimized, no asserts) and includes all
targets supported by TinyGo:
make llvm-build
This can take over an hour depending on the speed of your system.
## Build TinyGo
The last step of course is to build TinyGo itself. This can again be done with
make:
make
## Verify TinyGo
Try running TinyGo:
./build/tinygo help
Also, make sure the `tinygo` binary really is statically linked. Check this
using `ldd` (not to be confused with `lld`):
ldd ./build/tinygo
The result should not contain libclang or libLLVM.
## Make a release tarball
Now that we have a working static build, it's time to make a release tarball:
make release
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):
tar -xvf path/to/release.tar.gz
TinyGo will get extracted to a `tinygo` directory. You can then call it with:
./tinygo/bin/tinygo
+612
View File
@@ -0,0 +1,612 @@
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**
- add support for QEMU in `gdb` subcommand
- use builtin Clang when building statically, dropping the clang-9 dependency
- search for default serial port on both macOS and Linux
- windows: support `tinygo flash` directly by using win32 wmi
* **compiler**
- add location information to the IR checker
- make reflection sidetables constant globals
- improve error locations in goroutine lowering
- interp: improve support for maps with string keys
- interp: add runtime fallback for mapassign operations
* **standard library**
- `machine`: add support for `SPI.Tx()` on play.tinygo.org
- `machine`: rename `CPU_FREQUENCY` to `CPUFrequency()`
* **targets**
- `adafruit-pybadge`: add Adafruit Pybadge
- `arduino-nano33`: allow simulation on play.tinygo.org
- `arduino-nano33`: fix default SPI pin numbers to be D13/D11/D12
- `circuitplay-express`: allow simulation on play.tinygo.org
- `hifive1-qemu`: add target for testing RISC-V bare metal in QEMU
- `riscv`: fix heap corruption due to changes in LLVM 9
- `riscv`: add support for compiler-rt
- `qemu`: rename to `cortex-m-qemu`
0.10.0
---
* **command line**
- halt GDB after flashing with `gdb` subcommand
- fix a crash when using `-ocd-output`
- add `info` subcommand
- add `-programmer` flag
* **builder**
- macos: use llvm@8 instead of just llvm in paths
- add `linkerscript` key to target JSON files
- write a symbol table when writing out the compiler-rt lib
- make Clang header detection more robust
- switch to LLVM 9
* **compiler**
- fix interface miscompilation with reflect
- fix miscompile of static goroutine calls to closures
- fix `todo: store` panic
- fix incorrect starting value for optimized allocations in a loop
- optimize coroutines on non-Cortex-M targets
- fix crash for programs which have heap allocations but never hit the GC
- add support for async interface calls
- fix inserting non-const values in a const global
- interp: improve error reporting
- interp: implement comparing ptrtoint to 0
* **cgo**
- improve diagnostics
- implement the constant parser (for `#define`) as a real parser
- rename reserved field names such as `type`
- avoid `"unsafe" imported but not used` error
- include all enums in the CGo Go AST
- add support for nested structs and unions
- implement `#cgo CFLAGS`
* **standard library**
- `reflect`: add implementation of array alignment
- `runtime`: improve scheduler performance when no goroutines are queued
- `runtime`: add blocking select
- `runtime`: implement interface equality in non-trivial cases
- `runtime`: add AdjustTimeOffset to update current time
- `runtime`: only implement CountString for required platforms
- `runtime`: use MSP/PSP registers for scheduling on Cortex-M
* **targets**
- `arm`: add system timer registers
- `atmega`: add port C GPIO support
- `atsamd21`: correct handling of pins >= 32
- `atsamd21`: i2s initialization fixes
- `atsamd51`: fix clock init code
- `atsamd51`: correct initialization for RTC
- `atsamd51`: fix pin function selection
- `atsamd51`: pin method cleanup
- `atsamd51`: allow setting pin mode for each of the SPI pins
- `atsamd51`: correct channel init and pin map for ADC based on ItsyBitsy-M4
- `feather-m4`: add Adafruit Feather M4 board
- `hifive1b`: add support for SPI1
- `hifive1b`: fix compiling in simulation
- `linux`: fix time on arm32
- `metro-m4`: add support for Adafruit Metro M4 Express Airlift board
- `metro-m4`: fixes for UART2
- `pinetime-devkit0`: add support for the PineTime dev kit
- `x9pro`: add support for this smartwatch
- `pca10040-s132v6`: add support for SoftDevice
- `pca10056-s140v7`: add support for SoftDevice
- `arduino-nano33`: added SPI1 connected to NINA-W102 chip on Arduino Nano 33 IOT
0.9.0
---
* **command line**
- implement 1200-baud UART bootloader reset when flashing boards that support
it
- flash using mass-storage device for boards that support it
- implement `tinygo env`
- add support for Windows (but not yet producing Windows binaries)
- add Go version to `tinygo env`
- update SVD files for up-to-date peripheral interfaces
* **compiler**
- add `//go:align` pragma
- fix bug related to type aliases
- add support for buffered channels
- remove incorrect reflect optimization
- implement copying slices in init interpretation
- add support for constant indices with a named type
- add support for recursive types like linked lists
- fix miscompile of function nil panics
- fix bug related to goroutines
* **standard library**
- `machine`: do not check for nil slices in `SPI.Tx`
- `reflectlite`: add support for Go 1.13
- `runtime`: implement `internal/bytealg.CountString`
- `sync`: properly handle nil `New` func in `sync.Pool`
* **targets**
- `arduino`: fix .bss section initialization
- `fe310`: implement `Pin.Get`
- `gameboy-advance`: support directly outputting .gba files
- `samd`: reduce code size by avoiding reflection
- `samd21`: do not hardcode pin numbers for peripherals
- `stm32f103`: avoid issue with `time.Sleep` less than 200µs
0.8.0
---
* **command line**
- fix parsing of beta Go versions
- check the major/minor installed version of Go before compiling
- validate `-target` flag better to not panic on an invalid target
* **compiler**
- implement full slice expression: `s[:2:4]`
- fix a crash when storing a linked list in an interface
- fix comparing struct types by making type IDs more unique
- fix some bugs in IR generation
- add support for linked lists in reflect data
- implement `[]rune` to string conversion
- implement support for `go` on func values
* **standard library**
- `reflect`: add support for named types
- `reflect`: add support for `t.Bits()`
- `reflect`: add basic support for `t.AssignableTo()`
- `reflect`: implement `t.Align()`
- `reflect`: add support for struct types
- `reflect`: fix bug in `v.IsNil` and `v.Pointer` for addressable values
- `reflect`: implement support for array types
- `reflect`: implement `t.Comparable()`
- `runtime`: implement stack-based scheduler
- `runtime`: fix bug in the sleep queue of the scheduler
- `runtime`: implement `memcpy` for Cortex-M
- `testing`: implement stub `testing.B` struct
- `testing`: add common test logging methods such as Errorf/Fatalf/Printf
* **targets**
- `386`: add support for linux/386 syscalls
- `atsamd21`: make SPI pins configurable so that multiple SPI ports can be
used
- `atsamd21`: correct issue with invalid first reading coming from ADC
- `atsamd21`: add support for reset-to-bootloader using 1200baud over USB-CDC
- `atsamd21`: make pin selection more flexible for peripherals
- `atsamd21`: fix minimum delay in `time.Sleep`
- `atsamd51`: fix minimum delay in `time.Sleep`
- `nrf`: improve SPI write-only speed, by making use of double buffering
- `stm32f103`: fix SPI frequency selection
- `stm32f103`: add machine.Pin.Get method for reading GPIO values
- `stm32f103`: allow board specific UART usage
- `nucleo-f103rb`: add support for NUCLEO-F103RB board
- `itsybitsy-m4`: add support for this board with a SAMD51 family chip
- `cortex-m`: add support for `arm.SystemReset()`
- `gameboy-advance`: add initial support for the GameBoy Advance
- `wasm`: add `//go:wasm-module` magic comment to set the wasm module name
- `wasm`: add syscall/js.valueSetIndex support
- `wasm`: add syscall/js.valueInvoke support
0.7.1
---
* **targets**
- `atsamd21`: add support for the `-port` flag in the flash subcommand
0.7.0
---
* **command line**
- try more locations to find Clang built-in headers
- add support for `tinygo test`
- build current directory if no package is specified
- support custom .json target spec with `-target` flag
- use zversion.go to detect version of GOROOT version
- make initial heap size configurable for some targets (currently WebAssembly
only)
* **cgo**
- add support for bitfields using generated getters and setters
- add support for anonymous structs
* **compiler**
- show an error instead of panicking on duplicate function definitions
- allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it
- remove `//go:volatile` support
It has been replaced with the runtime/volatile package.
- allow poiners in map keys
- support non-constant syscall numbers
- implement non-blocking selects
- add support for the `-tags` flag
- add support for `string` to `[]rune` conversion
- implement a portable conservative garbage collector (with support for wasm)
- add the `//go:noinline` pragma
* **standard library**
- `os`: add `os.Exit` and `syscall.Exit`
- `os`: add several stubs
- `runtime`: fix heap corruption in conservative GC
- `runtime`: add support for math intrinsics where supported, massively
speeding up some benchmarks
- `testing`: add basic support for testing
* **targets**
- add support for a generic target that calls `__tinygo_*` functions for
peripheral access
- `arduino-nano33`: add support for this board
- `hifive1`: add support for this RISC-V board
- `reelboard`: add e-paper pins
- `reelboard`: add `PowerSupplyActive` to enable voltage for on-board devices
- `wasm`: put the stack at the start of linear memory, to detect stack
overflows
0.6.0
---
* **command line**
- some portability improvements
- make `$GOROOT` more robust and configurable
- check for Clang at the Homebrew install location as fallback
* **compiler driver**
- support multiple variations of LLVM commands, for non-Debian distributions
* **compiler**
- improve code quality in multiple ways
- make panic configurable, adding trap on panic
- refactor many internal parts of the compiler
- print all errors encountered during compilation
- implement calling function values of a named type
- implement returning values from blocking functions
- allow larger-than-int values to be sent across a channel
- implement complex arithmetic
- improve hashmap support
- add debuginfo for function arguments
- insert nil checks on stores (increasing code size)
- implement volatile operations as compiler builtins
- add `//go:inline` pragma
- add build tags for the Go stdlib version
* **cgo**
- implement `char`, `enum` and `void*` types
- support `#include` for builtin headers
- improve typedef/struct/enum support
- only include symbols that are necessary, for broader support
- mark external function args as `nocapture`
- implement support for some `#define` constants
- implement support for multiple CGo files in a single package
- **standard library**
- `machine`: remove microbit matrix (moved to drivers repository)
- `machine`: refactor pins to use `Pin` type instead of `GPIO`
- `runtime`: print more interface types on panic, including `error`
* **targets**
- `arm`: print an error on HardFault (including stack overflows)
- `atsamd21`: fix a bug in the ADC peripheral
- `atsamd21`: add support for I2S
- `feather-m0`: add support for this board
- `nrf51`: fix a bug in I2C
- `stm32f103xx`: fix a bug in I2C
- `syscall`: implement `Exit` on unix
- `trinket-m0`: add support for this board
- `wasm`: make _main_ example smaller
- `wasm`: don't cache wasm file in the server, for ease of debugging
- `wasm`: work around bug #41508 that caused a deadlock while linking
- `wasm`: add support for `js.FuncOf`
0.5.0
---
- **compiler driver**
- use `wasm-ld` instead of `wasm-ld-8` on macOS
- drop dependency on `llvm-ar`
- fix linker script includes when running outside `TINYGOROOT`
- **compiler**
- switch to LLVM 8
- add support for the Go 1.12 standard library (Go 1.11 is still supported)
- work around lack of escape analysis due to nil checks
- implement casting named structs and pointers to them
- fix int casting to use the source signedness
- fix some bugs around `make([]T, …)` with uncommon index types
- some other optimizations
- support interface asserts in interp for "math/rand" support
- resolve all func value targets at compile time (wasm-only at the moment)
- **cgo**
- improve diagnostics
- implement C `struct`, `union`, and arrays
- fix CGo-related crash in libclang
- implement `C.struct_` types
- **targets**
- all baremetal: pretend to be linux/arm instead of js/wasm
- `avr`: improve `uintptr` support
- `cortexm`: implement memmove intrinsic generated by LLVM
- `cortexm`: use the lld linker instead of `arm-none-eabi-ld`
- `darwin`: use custom syscall package that links to libSystem.dylib
- `microbit`: add blink example
- `samd21`: support I2C1
- `samd21`: machine/atsamd21: correct pad/pin handling when using both UART
and USBCDC interfaces at same time
- `stm32f4discovery`: add support for this board
- `wasm`: support async func values
- `wasm`: improve documentation and add extra example
0.4.1
---
- **compiler**
- fix `objcopy` replacement to include the .data section in the firmware image
- use `llvm-ar-7` on Linux to fix the Docker image
0.4.0
---
- **compiler**
- switch to the hardfloat ABI on ARM, which is more widely used
- avoid a dependency on `objcopy` (`arm-none-eabi-objcopy` etc.)
- fix a bug in `make([]T, n)` where `n` is 64-bits on a 32-bit platform
- adapt to a change in the AVR backend in LLVM 8
- directly support the .uf2 firmware format as used on Adafruit boards
- fix a bug when calling `panic()` at init time outside of the main package
- implement nil checks, which results in a ~5% increase in code size
- inline slice bounds checking, which results in a ~1% decrease in code size
- **targets**
- `samd21`: fix a bug in port B pins
- `samd21`: implement SPI peripheral
- `samd21`: implement ADC peripheral
- `stm32`: fix a bug in timekeeping
- `wasm`: fix a bug in `wasm_exec.js` that caused corruption in linear memory
when running on Node.js.
0.3.0
---
- **compiler**
- remove old `-initinterp` flag
- add support for macOS
- **cgo**
- add support for bool/float/complex types
- **standard library**
- `device/arm`: add support to disable/enable hardware interrupts
- `machine`: add CPU frequency for nrf-based boards
- `syscall`: add support for darwin/amd64
- **targets**
- `circuitplay_express`: add support for this board
- `microbit`: add regular pin constants
- `samd21`: fix time function for goroutine support
- `samd21`: add support for USB-CDC (serial over USB)
- `samd21`: add support for pins in port B
- `samd21`: add support for pullup and pulldown pins
- `wasm`: add support for Safari in example
0.2.0
---
- **command line**
- add version subcommand
- **compiler**
- fix a bug in floating point comparisons with NaN values
- fix a bug when calling `panic` in package initialization code
- add support for comparing `complex64` and `complex128`
- **cgo**
- add support for external globals
- add support for pointers and function pointers
- **standard library**
- `fmt`: initial support, `fmt.Println` works
- `math`: support for most/all functions
- `os`: initial support (only stdin/stdout/stderr)
- `reflect`: initial support
- `syscall`: add support for amd64, arm, and arm64
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [conduct@tinygo.org](mailto:conduct@tinygo.org). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+52
View File
@@ -0,0 +1,52 @@
# How to contribute
Thank you for your interest in improving TinyGo.
We would like your help to make this project better, so we appreciate any contributions. See if one of the following descriptions matches your situation:
### New to TinyGo
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
### Something in TinyGo is not working as you expect
Please open a Github issue with your problem, and we will be happy to assist.
### Something in Go that you want/need does not appear to be in TinyGo
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
### Some specific hardware you want to use does not appear to be in TinyGo
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
Please start by opening a Github issue. We want to help you to help us to help you.
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
## How to use our Github repository
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:
- Fork repo
- Create a feature branch off of the `dev` branch
- Make some useful change
- Make sure the tests still pass
- Submit a pull request against the `dev` branch.
- Be kind
## How to run tests
To run the tests:
```
make test
```
+18
View File
@@ -0,0 +1,18 @@
# This is the official list of TinyGo authors for copyright purposes.
#
# This file is not actively maintained.
# To be included, send a change adding the individual or
# company who owns a contribution's copyright.
#
# Names should be added to this file as one of
# Organization's name
# Individual's name <submission email address>
# Individual's name <submission email address> <email2> <emailN>
#
# Please keep the list sorted.
Ayke van Laethem <aykevanlaethem@gmail.com>
Daniel Esteban <conejo@conejo.me>
Loon, LLC.
Ron Evans <ron@hybridgroup.com>
Jaden Weiss <jaden@jadendw.dev>
+39 -43
View File
@@ -1,46 +1,48 @@
# TinyGo base stage just installs LLVM 7 and the TinyGo compiler itself.
FROM golang:latest 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/stretch/ llvm-toolchain-stretch-7 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-7-dev libclang-7-dev
apt-get install -y llvm-10-dev libclang-10-dev lld-10 git
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
COPY . /tinygo
COPY . /go/src/github.com/aykevl/tinygo
# remove submodules directories and re-init them to fix any hard-coded paths
# after copying the tinygo directory in the previous step.
RUN cd /tinygo/ && \
rm -rf ./lib/* && \
git submodule sync && \
git submodule update --init --recursive --force
RUN cd /go/src/github.com/aykevl/tinygo/ && \
dep ensure --vendor-only && \
go install /go/src/github.com/aykevl/tinygo/
COPY ./lib/picolibc-include/* /tinygo/lib/picolibc-include/
RUN cd /tinygo/ && \
go install /tinygo/
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
FROM tinygo-base AS tinygo-wasm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
COPY --from=tinygo-base /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/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y libllvm7 lld-7
RUN apt-get install -y libllvm10 lld-10
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
FROM tinygo-base AS tinygo-avr
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /go/src/github.com/aykevl/tinygo/ && \
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
apt-get install -y apt-utils make binutils-avr gcc-avr avr-libc && \
make gen-device-avr && \
apt-get remove -y python3 make && \
apt-get autoremove -y && \
apt-get clean
@@ -48,33 +50,27 @@ RUN cd /go/src/github.com/aykevl/tinygo/ && \
FROM tinygo-base AS tinygo-arm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /go/src/github.com/aykevl/tinygo/ && \
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 && \
make gen-device-nrf && make gen-device-stm32 && \
apt-get remove -y python3 make && \
apt-get autoremove -y && \
apt-get clean
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.
FROM tinygo-wasm AS tinygo-all
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/tinygo/lib
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /go/src/github.com/aykevl/tinygo/ && \
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 binutils-avr gcc-avr avr-libc && \
make gen-device && \
apt-get remove -y python3 make && \
apt-get autoremove -y && \
apt-get clean
apt-get install -y apt-utils make clang-10 binutils-avr gcc-avr avr-libc && \
make gen-device
CMD ["tinygo"]
Generated
-44
View File
@@ -1,44 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:f250e2a6d7e4f9ebc5ba37e5e2ec91b46eb1399ee43f2fdaeb20cd4fd1aeee59"
name = "github.com/aykevl/go-llvm"
packages = ["."]
pruneopts = "UT"
revision = "d8539684f173a591ea9474d6262ac47ef2277d64"
[[projects]]
branch = "master"
digest = "1:d1102ae84d8c9318db4ce2ad2673eb2bf54569ab2a4a5d57e70d8aef726b681d"
name = "golang.org/x/tools"
packages = [
"go/ast/astutil",
"go/buildutil",
"go/gcexportdata",
"go/internal/cgo",
"go/internal/gcimporter",
"go/loader",
"go/packages",
"go/ssa",
"go/ssa/ssautil",
"go/types/typeutil",
"internal/fastwalk",
"internal/gopathwalk",
"internal/semver",
]
pruneopts = "UT"
revision = "3e7aa9e59977626dc60433e9aeadf1bb63d28295"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/aykevl/go-llvm",
"golang.org/x/tools/go/loader",
"golang.org/x/tools/go/ssa",
"golang.org/x/tools/go/ssa/ssautil",
]
solver-name = "gps-cdcl"
solver-version = 1
-11
View File
@@ -1,11 +0,0 @@
[[constraint]]
branch = "master"
name = "github.com/aykevl/go-llvm"
[[constraint]]
branch = "master"
name = "golang.org/x/tools"
[prune]
go-tests = true
unused-packages = true
+7 -1
View File
@@ -1,4 +1,10 @@
Copyright (c) 2018 Ayke van Laethem. All rights reserved.
Copyright (c) 2018-2020 TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library.
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.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
+367 -85
View File
@@ -1,113 +1,395 @@
# aliases
all: tgo
tgo: build/tgo
all: tinygo
.PHONY: all tgo run-test run-blinky run-blinky2 clean fmt gen-device gen-device-nrf gen-device-avr
TARGET ?= unix
ifeq ($(TARGET),unix)
# Regular *nix system.
else ifeq ($(TARGET),pca10040)
# PCA10040: nRF52832 development board
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),microbit)
# BBC micro:bit
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),reelboard)
# reel board
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),bluepill)
# "blue pill" development board
# See: https://wiki.stm32duino.com/index.php?title=Blue_Pill
OBJCOPY = arm-none-eabi-objcopy
TGOFLAGS += -target $(TARGET)
else ifeq ($(TARGET),arduino)
OBJCOPY = avr-objcopy
TGOFLAGS += -target $(TARGET)
# Default build and source directories, as created by `make llvm-build`.
LLVM_BUILDDIR ?= llvm-build
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
$(error Unknown target)
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
export GOROOT = $(shell $(GO) env GOROOT)
# md5sum binary
MD5SUM = md5sum
run-test: build/test
./build/test
# tinygo binary for tests
TINYGO ?= tinygo
run-blinky: run-blinky2
run-blinky2: build/blinky2
./build/blinky2
ifeq ($(TARGET),pca10040)
flash-%: build/%.hex
nrfjprog -f nrf52 --sectorerase --program $< --reset
else ifeq ($(TARGET),microbit)
flash-%: build/%.hex
openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -c 'program $< reset exit'
else ifeq ($(TARGET),reelboard)
flash-%: build/%.hex
openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -c 'program $< reset exit'
else ifeq ($(TARGET),arduino)
flash-%: build/%.hex
avrdude -c arduino -p atmega328p -P /dev/ttyACM0 -U flash:w:$<
else ifeq ($(TARGET),bluepill)
flash-%: build/%.hex
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c 'program $< reset exit'
# Use CCACHE for LLVM if possible
ifneq (, $(shell command -v ccache 2> /dev/null))
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=ON'
endif
# Allow enabling LLVM assertions
ifeq (1, $(ASSERT))
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=ON'
else
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr
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
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
# LLVM compiled using MinGW on Windows appears to have problems with threads.
# Without this flag, linking results in errors like these:
# libLLVMSupport.a(Threading.cpp.obj):Threading.cpp:(.text+0x55): undefined reference to `std::thread::hardware_concurrency()'
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
CGO_LDFLAGS_EXTRA += -lversion
# Build libclang manually because the CMake-based build system on Windows
# doesn't allow building libclang as a static library.
LIBCLANG_PATH = $(abspath build/libclang-custom.a)
LIBCLANG_FILES = $(abspath $(wildcard $(LLVM_BUILDDIR)/tools/clang/tools/libclang/CMakeFiles/libclang.dir/*.cpp.obj))
# Add the libclang dependency to the tinygo binary target.
tinygo: $(LIBCLANG_PATH)
test: $(LIBCLANG_PATH)
# Build libclang.
$(LIBCLANG_PATH): $(LIBCLANG_FILES)
@mkdir -p build
ar rcs $(LIBCLANG_PATH) $^
else ifeq ($(shell uname -s),Darwin)
MD5SUM = md5
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
else ifeq ($(shell uname -s),FreeBSD)
MD5SUM = md5
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
else
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
endif
CLANG_LIBS = $(START_GROUP) -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
# 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++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
clean:
@rm -rf build
FMT_PATHS = ./*.go builder cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
fmt:
@go fmt . ./compiler ./interp ./loader ./ir ./src/device/arm ./src/examples/* ./src/machine ./src/runtime ./src/sync
@go fmt ./testdata/*.go
@gofmt -l -w $(FMT_PATHS)
fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
test:
@go test -v .
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32 gen-device-kendryte gen-device-nxp
gen-device-avr:
./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
./tools/gen-device-avr.py lib/avr/packs/tiny src/device/avr/
go fmt ./src/device/avr
$(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/
@GO111MODULE=off $(GO) fmt ./src/device/avr
gen-device-nrf:
./tools/gen-device-svd.py lib/nrfx/mdk/ src/device/nrf/ --source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk
go fmt ./src/device/nrf
build/gen-device-svd: ./tools/gen-device-svd/*.go
$(GO) build -o $@ ./tools/gen-device-svd/
gen-device-sam:
./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
go fmt ./src/device/sam
gen-device-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 -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_PROJECTDIR)/README.md:
git clone -b release/10.x --depth=1 https://github.com/llvm/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/README.md
# Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -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:
cd lib/wasi-libc && make -j4 WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
gen-device-stm32:
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
go fmt ./src/device/stm32
# Build the Go compiler.
build/tgo: *.go compiler/*.go interp/*.go loader/*.go ir/*.go
@mkdir -p build
go build -o build/tgo -i .
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 .
# Binary that can run on the host.
build/%: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go
./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform .
# ELF file that can run on a microcontroller.
build/%.elf: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go
./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
tinygo-test:
cd tests/tinygotest && tinygo test
# Convert executable to Intel hex file (for flashing).
build/%.hex: build/%.elf
$(OBJCOPY) -O ihex $^ $@
.PHONY: smoketest
smoketest:
$(TINYGO) version
# 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
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinkm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/echo
@$(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=pca10040 examples/mcp3008
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
@$(MD5SUM) test.hex
# 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
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=pca10056 examples/blinky2
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=circuitplay_express examples/blinky1
@$(MD5SUM) test.wasm
# test all targets/boards
$(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1
@$(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
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=trinket-m0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=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
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pybadge examples/blinky1
@$(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
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
@$(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
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
$(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.elf -target=nintendoswitch examples/serial
@$(MD5SUM) test.elf
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
@echo copying source files
@cp -p build/tinygo$(EXE) build/release/tinygo/bin
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
@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-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
+101 -131
View File
@@ -1,144 +1,99 @@
# TinyGo - Go compiler for microcontrollers
# TinyGo - Go compiler for small places
[![Build Status](https://travis-ci.com/aykevl/tinygo.svg?branch=master)](https://travis-ci.com/aykevl/tinygo)
[![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) [![Build Status](https://dev.azure.com/tinygo/tinygo/_apis/build/status/tinygo-CI?branchName=dev)](https://dev.azure.com/tinygo/tinygo/_build/latest?definitionId=1&branchName=dev)
> We never expected Go to be an embedded language and so it's got serious
> problems [...].
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
TinyGo is a project to bring Go to microcontrollers and small systems with a
single processor core. It is similar to [emgo](https://github.com/ziutek/emgo)
but a major difference is that I want to keep the Go memory model (which implies
garbage collection of some sort). Another difference is that TinyGo uses LLVM
internally instead of emitting C, which hopefully leads to smaller and more
efficient code and certainly leads to more flexibility.
My original reasoning was: if [Python](https://micropython.org/) can run on
microcontrollers, then certainly [Go](https://golang.org/) should be able to and
run on even lower level micros.
Example program (blinky):
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
```go
package main
import (
"machine"
"time"
"machine"
"time"
)
func main() {
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for {
led.Low()
time.Sleep(time.Millisecond * 1000)
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
for {
led.Low()
time.Sleep(time.Millisecond * 1000)
led.High()
time.Sleep(time.Millisecond * 1000)
}
led.High()
time.Sleep(time.Millisecond * 1000)
}
}
```
Currently supported features:
The above program can be compiled and run without modification on an Arduino Uno, an Adafruit ItsyBitsy M0, or any of the supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno:
* control flow
* many (but not all) basic types: most ints, floats, strings, structs
* function calling
* interfaces for basic types (with type switches and asserts)
* goroutines (very initial support)
* function pointers (non-blocking)
* interface methods
* standard library (but most packages won't work due to missing language
features)
* slices (partially)
* maps (very rough, unfinished)
* defer
* closures
* bound methods
* complex numbers (except for arithmetic)
Not yet supported:
* complex arithmetic
* garbage collection
* recover
* channels
* introspection (if it ever gets implemented)
* ...
```shell
tinygo flash -target arduino examples/blinky1
```
## Installation
See the [getting started instructions](https://tinygo.org/getting-started/).
See the [getting started instructions](https://tinygo.org/getting-started/) for information on how to install TinyGo, as well as how to run the TinyGo compiler using our Docker container.
### Running with Docker
## Supported boards/targets
A docker container exists for easy access to the `tinygo` CLI:
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
```sh
$ docker run --rm -v $(pwd):/src tinygo/tinygo tinygo build -o /src/wasm.wasm -target wasm examples/wasm
```
The following 39 microcontroller boards/devices are currently supported:
Note that you cannot run `tinygo flash` from inside the docker container,
so it is less useful for microcontroller development.
* [Adafruit Circuit Playground 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 ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [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)
* [BBC micro:bit](https://microbit.org/)
* [Digispark](http://digistump.com/products/1)
* [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)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
## Supported targets
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
The following architectures/systems are currently supported:
## Currently supported features:
* ARM (Cortex-M)
* AVR (Arduino Uno)
* Linux
* WebAssembly
For more information, see [this list of targets and
boards](https://tinygo.org/targets/). Pull requests for
broader support are welcome!
## Analysis and optimizations
The goal is to reduce code size (and increase performance) by performing all
kinds of whole-program analysis passes. The official Go compiler doesn't do a
whole lot of analysis (except for escape analysis) because it needs to be fast,
but embedded programs are necessarily smaller so it becomes practical. And I
think especially program size can be reduced by a large margin when actually
trying to optimize for it.
Implemented compiler passes:
* Analyse which functions are blocking. Blocking functions are functions that
call sleep, chan send, etc. Its parents are also blocking.
* Analyse whether the scheduler is needed. It is only needed when there are
`go` statements for blocking functions.
* Analyse whether a given type switch or type assert is possible with
[type-based alias analysis](https://en.wikipedia.org/wiki/Alias_analysis#Type-based_alias_analysis).
I would like to use flow-based alias analysis in the future, if feasible.
* Do basic dead code elimination of functions. This pass makes later passes
better and probably improves compile time as well.
## Scope
Goals:
* Have very small binary sizes. Don't pay for what you don't use.
* Support for most common microcontroller boards.
* Be usable on the web using WebAssembly.
* Good CGo support, with no more overhead than a regular function call.
* Support most standard library packages and compile most Go code without
modification.
Non-goals:
* Using more than one core.
* Be efficient while using zillions of goroutines. However, good goroutine
support is certainly a goal.
* Be as fast as `gc`. However, LLVM will probably be better at optimizing
certain things so TinyGo might actually turn out to be faster for number
crunching.
* Be able to compile every Go program out there.
For a description of currently supported Go language features, please see [https://tinygo.org/lang-support/](https://tinygo.org/lang-support/).
## Documentation
Documentation is currently maintained on a dedicated web site located at [https://tinygo.org/](https://tinygo.org/).
Documentation is located on our web site at [https://tinygo.org/](https://tinygo.org/).
You can find the web site code at [https://github.com/tinygo-org/tinygo-site](https://github.com/tinygo-org/tinygo-site).
@@ -153,26 +108,41 @@ should arrive fairly quickly (under 1 min): https://invite.slack.golangbridge.or
## Contributing
Patches are welcome!
Your contributions are welcome!
If you want to contribute, here are some suggestions:
Please take a look at our [CONTRIBUTING.md](./CONTRIBUTING.md) document for details.
* A long tail of small (and large) language features haven't been implemented
yet. In almost all cases, the compiler will show a `todo:` error from
`compiler/compiler.go` when you try to use it. You can try implementing it,
or open a bug report with a small code sample that fails to compile.
* Lots of targets/boards are still unsupported. Adding an architecture often
requires a few compiler changes, but if the architecture is supported you
can try implementing support for a new chip or board in `src/runtime`. For
details, see [this wiki entry on adding
archs/chips/boards](https://github.com/aykevl/tinygo/wiki/Adding-a-new-board).
* Microcontrollers have lots of peripherals and many don't have an
implementation yet in the `machine` package. Adding support for new
peripherals is very useful.
* Just raising bugs for things you'd like to see implemented is also a form of
contributing! It helps prioritization.
## Project Scope
Goals:
* Have very small binary sizes. Don't pay for what you don't use.
* Support for most common microcontroller boards.
* Be usable on the web using WebAssembly.
* Good CGo support, with no more overhead than a regular function call.
* Support most standard library packages and compile most Go code without modification.
Non-goals:
* Using more than one core.
* Be efficient while using zillions of goroutines. However, good goroutine support is certainly a goal.
* Be as fast as `gc`. However, LLVM will probably be better at optimizing certain things so TinyGo might actually turn out to be faster for number crunching.
* Be able to compile every Go program out there.
## Why this project exists
> We never expected Go to be an embedded language and so its got serious problems...
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
TinyGo is a project to bring Go to microcontrollers and small systems with a single processor core. It is similar to [emgo](https://github.com/ziutek/emgo) but a major difference is that we want to keep the Go memory model (which implies garbage collection of some sort). Another difference is that TinyGo uses LLVM internally instead of emitting C, which hopefully leads to smaller and more efficient code and certainly leads to more flexibility.
The original reasoning was: if [Python](https://micropython.org/) can run on microcontrollers, then certainly [Go](https://golang.org/) should be able to run on even lower level micros.
## License
This project is licensed under the BSD 3-clause license, just like the
[Go project](https://golang.org/LICENSE) itself.
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
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.
+83
View File
@@ -0,0 +1,83 @@
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
# be built anyway.
trigger:
- release
- dev
jobs:
- job: Build
timeoutInMinutes: 240 # 4h
pool:
vmImage: 'VS2017-Win2016'
steps:
- task: GoTool@0
inputs:
version: '1.14.1'
- checkout: self
- task: CacheBeta@0
displayName: Cache LLVM source
inputs:
key: llvm-source-10-windows-v0
path: llvm-project
- task: Bash@3
displayName: Download LLVM source
inputs:
targetType: inline
script: make llvm-source
- task: CacheBeta@0
displayName: Cache LLVM build
inputs:
key: llvm-build-10-windows-v0
path: llvm-build
- task: Bash@3
displayName: Build LLVM
inputs:
targetType: inline
script: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
choco install ninja
make llvm-build
fi
- task: Bash@3
displayName: Install QEMU
inputs:
targetType: inline
script: choco install qemu --version=2020.06.12
- task: CacheBeta@0
displayName: Cache wasi-libc sysroot
inputs:
key: wasi-libc-sysroot-v2
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="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make test
- task: Bash@3
displayName: Build TinyGo release tarball
inputs:
targetType: inline
script: |
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
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="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make smoketest TINYGO=build/tinygo AVR=0
+182
View File
@@ -0,0 +1,182 @@
package builder
import (
"bytes"
"debug/elf"
"encoding/binary"
"errors"
"io"
"os"
"path/filepath"
"time"
"github.com/blakesmith/ar"
)
// makeArchive creates an arcive for static linking from a list of object files
// given as a parameter. It is equivalent to the following command:
//
// ar -rcs <archivePath> <objs...>
func makeArchive(archivePath string, objs []string) error {
// Open the archive file.
arfile, err := os.Create(archivePath)
if err != nil {
return err
}
defer arfile.Close()
arwriter := ar.NewWriter(arfile)
err = arwriter.WriteGlobalHeader()
if err != nil {
return &os.PathError{"write ar header", archivePath, err}
}
// Open all object files and read the symbols for the symbol table.
symbolTable := []struct {
name string // symbol name
fileIndex int // index into objfiles
}{}
objfiles := make([]struct {
file *os.File
archiveOffset int32
}, len(objs))
for i, objpath := range objs {
objfile, err := os.Open(objpath)
if err != nil {
return err
}
objfiles[i].file = objfile
// Read the symbols and add them to the symbol table.
dbg, err := elf.NewFile(objfile)
if err != nil {
return err
}
symbols, err := dbg.Symbols()
if err != nil {
return err
}
for _, symbol := range symbols {
bind := elf.ST_BIND(symbol.Info)
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
// Don't include local symbols (STB_LOCAL).
continue
}
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC {
// Not a function.
// TODO: perhaps globals variables should also be included?
continue
}
// Include in archive.
symbolTable = append(symbolTable, struct {
name string
fileIndex int
}{symbol.Name, i})
}
}
// Create the symbol table buffer.
// For some (sparse) details on the file format:
// https://en.wikipedia.org/wiki/Ar_(Unix)#System_V_(or_GNU)_variant
buf := &bytes.Buffer{}
binary.Write(buf, binary.BigEndian, int32(len(symbolTable)))
for range symbolTable {
// This is a placeholder index, it will be updated after all files have
// been written to the archive (see the end of this function).
err = binary.Write(buf, binary.BigEndian, int32(0))
if err != nil {
return err
}
}
for _, sym := range symbolTable {
_, err := buf.Write([]byte(sym.name + "\x00"))
if err != nil {
return err
}
}
for buf.Len()%2 != 0 {
// The symbol table must be aligned.
// This appears to be required by lld.
buf.WriteByte(0)
}
// Write the symbol table.
err = arwriter.WriteHeader(&ar.Header{
Name: "/",
ModTime: time.Unix(0, 0),
Uid: 0,
Gid: 0,
Mode: 0,
Size: int64(buf.Len()),
})
if err != nil {
return err
}
// Keep track of the start of the symbol table.
symbolTableStart, err := arfile.Seek(0, os.SEEK_CUR)
if err != nil {
return err
}
// Write symbol table contents.
_, err = arfile.Write(buf.Bytes())
if err != nil {
return err
}
// Add all object files to the archive.
for i, objfile := range objfiles {
// Store the start index, for when we'll update the symbol table with
// the correct file start indices.
offset, err := arfile.Seek(0, os.SEEK_CUR)
if err != nil {
return err
}
if int64(int32(offset)) != offset {
return errors.New("large archives (4GB+) not supported: " + archivePath)
}
objfiles[i].archiveOffset = int32(offset)
// Write the file header.
st, err := objfile.file.Stat()
if err != nil {
return err
}
err = arwriter.WriteHeader(&ar.Header{
Name: filepath.Base(objfile.file.Name()),
ModTime: time.Unix(0, 0),
Uid: 0,
Gid: 0,
Mode: 0644,
Size: st.Size(),
})
if err != nil {
return err
}
// Copy the file contents into the archive.
n, err := io.Copy(arwriter, objfile.file)
if err != nil {
return err
}
if n != st.Size() {
return errors.New("file modified during ar creation: " + archivePath)
}
// File is not needed anymore.
objfile.file.Close()
}
// Create symbol indices.
indicesBuf := &bytes.Buffer{}
for _, sym := range symbolTable {
err = binary.Write(indicesBuf, binary.BigEndian, objfiles[sym.fileIndex].archiveOffset)
if err != nil {
return err
}
}
// Overwrite placeholder indices.
_, err = arfile.WriteAt(indicesBuf.Bytes(), symbolTableStart+4)
return err
}
+337
View File
@@ -0,0 +1,337 @@
// Package builder is the compiler driver of TinyGo. It takes in a package name
// and an output path, and outputs an executable. It manages the entire
// compilation pipeline in between.
package builder
import (
"debug/elf"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"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"
)
// 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 {
// Compile Go code to IR.
machine, err := compiler.NewTargetMachine(config)
if err != nil {
return err
}
mod, extraFiles, extraLDFlags, errs := compiler.Compile(pkgName, machine, config)
if errs != nil {
return newMultiError(errs)
}
if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:")
fmt.Println(mod.String())
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after IR construction")
}
err = interp.Run(mod, config.DumpSSA())
if err != nil {
return err
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after interpreting runtime.initAll")
}
if config.GOOS() != "darwin" {
transform.ApplyFunctionSections(mod) // -ffunction-sections
}
// Browsers cannot handle external functions that have type i64 because it
// cannot be represented exactly in JavaScript (JS only has doubles). To
// keep functions interoperable, pass int64 types as pointers to
// stack-allocated values.
// Use -wasm-abi=generic to disable this behaviour.
if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") {
err := transform.ExternalInt64AsPtr(mod)
if err != nil {
return err
}
}
// Optimization levels here are roughly the same as Clang, but probably not
// exactly.
errs = nil
switch config.Options.Opt {
/*
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 = transform.Optimize(mod, config, 1, 0, 0) // -O1
case "2":
errs = transform.Optimize(mod, config, 2, 0, 225) // -O2
case "s":
errs = transform.Optimize(mod, config, 2, 1, 225) // -Os
case "z":
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 := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification failure after LLVM optimization passes")
}
// On the AVR, pointers can point either to flash or to RAM, but we don't
// know. As a temporary fix, load all global variables in RAM.
// In the future, there should be a compiler pass that determines which
// 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") {
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")
}
}
// Generate output.
outext := filepath.Ext(outpath)
switch outext {
case ".o":
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666)
case ".bc":
data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes()
return ioutil.WriteFile(outpath, data, 0666)
case ".ll":
data := []byte(mod.String())
return ioutil.WriteFile(outpath, data, 0666)
default:
// Act as a compiler driver.
// Create a temporary directory for intermediary files.
dir, err := ioutil.TempDir("", "tinygo")
if err != nil {
return err
}
defer os.RemoveAll(dir)
// Write the object file.
objfile := filepath.Join(dir, "main.o")
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
err = ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
if err != nil {
return err
}
// Prepare link command.
executable := filepath.Join(dir, "main")
tmppath := executable // final file
ldflags := append(config.LDFlags(), "-o", executable, objfile)
// Load builtins library from the cache, possibly compiling it on the
// fly.
if config.Target.RTLib == "compiler-rt" {
librt, err := CompilerRT.Load(config.Triple())
if err != nil {
return err
}
ldflags = append(ldflags, librt)
}
// Add libc.
if config.Target.Libc == "picolibc" {
libc, err := Picolibc.Load(config.Triple())
if err != nil {
return err
}
ldflags = append(ldflags, libc)
}
// Compile extra files.
root := goenv.Get("TINYGOROOT")
for i, path := range config.ExtraFiles() {
abspath := filepath.Join(root, path)
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
if err != nil {
return &commandError{"failed to build", path, err}
}
ldflags = append(ldflags, outpath)
}
// Compile C files in packages.
for i, file := range 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(extraLDFlags) > 0 {
ldflags = append(ldflags, extraLDFlags...)
}
// Link the object files together.
err = link(config.Target.Linker, ldflags...)
if err != nil {
return &commandError{"failed to link", executable, err}
}
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable)
if err != nil {
return err
}
if config.Options.PrintSizes == "short" {
fmt.Printf(" code data bss | flash ram\n")
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
} else {
fmt.Printf(" code rodata data bss | flash ram | package\n")
for _, name := range sizes.sortedPackageNames() {
pkgSize := sizes.Packages[name]
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
}
fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
}
}
// Print goroutine stack sizes, as far as possible.
if config.Options.PrintStacks {
printStacks(mod, executable)
}
// Get an Intel .hex file or .bin file from the .elf file.
if outext == ".hex" || outext == ".bin" || outext == ".gba" {
tmppath = filepath.Join(dir, "main"+outext)
err := objcopy(executable, tmppath)
if err != nil {
return err
}
} else if outext == ".uf2" {
// Get UF2 from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
if err != nil {
return err
}
}
return action(tmppath)
}
}
// 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
// .Lexamples/blinky2.led1 92
// .Lruntime.run$1 300
func printStacks(mod llvm.Module, executable string) {
// Determine which functions call a function pointer.
var callsIndirectFunction []string
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
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())
}
}
}
}
// Load the ELF binary.
f, err := elf.Open(executable)
if err != nil {
fmt.Fprintln(os.Stderr, "could not load executable for stack size analysis:", err)
return
}
defer f.Close()
// Determine the frame size of each function (if available) and the callgraph.
functions, err := stacksize.CallGraph(f, callsIndirectFunction)
if err != nil {
fmt.Fprintln(os.Stderr, "could not parse executable for stack size analysis:", err)
return
}
// Get a list of "go wrappers", small wrapper functions that decode
// parameters when starting a new goroutine.
var gowrappers []string
for name := range functions {
if strings.HasSuffix(name, "$gowrapper") {
gowrappers = append(gowrappers, name)
}
}
sort.Strings(gowrappers)
switch f.Machine {
case elf.EM_ARM:
// Add the reset handler, which runs startup code and is the
// interrupt/scheduler stack with -scheduler=tasks.
// Note that because interrupts happen on this stack, the stack needed
// by just the Reset_Handler is not enough. Stacks needed by interrupt
// handlers should also be taken into account.
gowrappers = append([]string{"Reset_Handler"}, gowrappers...)
}
// Print the sizes of all stacks.
fmt.Printf("%-32s %s\n", "function", "stack usage (in bytes)")
for _, name := range gowrappers {
for _, fn := range functions[name] {
stackSize, stackSizeType, missingStackSize := fn.StackSize()
strippedName := name
if strings.HasSuffix(name, "$gowrapper") {
strippedName = name[:len(name)-len("$gowrapper")]
}
switch stackSizeType {
case stacksize.Bounded:
fmt.Printf("%-32s %d\n", strippedName, stackSize)
case stacksize.Unknown:
fmt.Printf("%-32s unknown, %s does not have stack frame information\n", strippedName, missingStackSize)
case stacksize.Recursive:
fmt.Printf("%-32s recursive, %s may call itself\n", strippedName, missingStackSize)
case stacksize.IndirectCall:
fmt.Printf("%-32s unknown, %s calls a function pointer\n", strippedName, missingStackSize)
}
}
}
}
+43 -33
View File
@@ -1,18 +1,13 @@
package main
package builder
import (
"io"
"os"
"path/filepath"
"time"
)
// Get the cache directory, usually ~/.cache/tinygo
func cacheDir() string {
home := getHomeDir()
dir := filepath.Join(home, ".cache", "tinygo")
return dir
}
"github.com/tinygo-org/tinygo/goenv"
)
// Return the newest timestamp of all the file paths passed in. Used to check
// for stale caches.
@@ -39,8 +34,7 @@ func cacheTimestamp(paths []string) (time.Time, error) {
// TODO: the configKey is currently ignored. It is supposed to be used as extra
// data for the cache key, like the compiler version and arguments.
func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
dir := cacheDir()
cachepath := filepath.Join(dir, name)
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
cacheStat, err := os.Stat(cachepath)
if os.IsNotExist(err) {
return "", nil // does not exist
@@ -74,35 +68,51 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,
// TODO: check the config key
dir := cacheDir()
dir := goenv.Get("GOCACHE")
err := os.MkdirAll(dir, 0777)
if err != nil {
return "", err
}
cachepath := filepath.Join(dir, name)
err = os.Rename(tmppath, cachepath)
err = moveFile(tmppath, cachepath)
if err != nil {
inf, err := os.Open(tmppath)
if err != nil {
return "", err
}
defer inf.Close()
outf, err := os.Create(cachepath + ".tmp")
if err != nil {
return "", err
}
_, err = io.Copy(outf, inf)
if err != nil {
return "", err
}
err = os.Rename(cachepath+".tmp", cachepath)
if err != nil {
return "", err
}
return cachepath, outf.Close()
return "", err
}
return cachepath, nil
}
// moveFile renames the file from src to dst. If renaming doesn't work (for
// example, the rename crosses a filesystem boundary), the file is copied and
// the old file is removed.
func moveFile(src, dst string) error {
err := os.Rename(src, dst)
if err == nil {
// Success!
return nil
}
// Failed to move, probably a different filesystem.
// Do a copy + remove.
inf, err := os.Open(src)
if err != nil {
return err
}
defer inf.Close()
outpath := dst + ".tmp"
outf, err := os.Create(outpath)
if err != nil {
return err
}
_, err = io.Copy(outf, inf)
if err != nil {
os.Remove(outpath)
return err
}
err = outf.Close()
if err != nil {
return err
}
return os.Rename(dst+".tmp", dst)
}
+16 -66
View File
@@ -1,10 +1,6 @@
package main
package builder
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
)
@@ -155,66 +151,20 @@ var aeabiBuiltins = []string{
"arm/aeabi_uldivmod.S",
}
func builtinFiles(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if target[:3] == "arm" {
builtins = append(builtins, aeabiBuiltins...)
}
return builtins
}
// Get the builtins archive, possibly generating it as needed.
func loadBuiltins(target string) (path string, err error) {
outfile := "librt-" + target + ".a"
builtinsDir := filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
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"], srcs); path != "" || err != nil {
return path, err
}
dir, err := ioutil.TempDir("", "tinygo-builtins")
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)
cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-o", objpath, srcpath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
err = cmd.Run()
if err != nil {
return "", err
}
}
// Put all builtins in an archive to link as a static library.
arpath := filepath.Join(dir, "librt.a")
cmd := exec.Command(commands["ar"], append([]string{"cr", arpath}, objs...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
err = cmd.Run()
if err != nil {
return "", err
}
return cacheStore(arpath, outfile, commands["clang"], srcs)
return builtins
},
}
+510
View File
@@ -0,0 +1,510 @@
// +build byollvm
//===-- cc1as.cpp - Clang Assembler --------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This is the entry point to the clang -cc1as functionality, which implements
// the direct interface to the LLVM MC based assembler.
//
//===----------------------------------------------------------------------===//
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Driver/Options.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/Utils.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/Triple.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCObjectWriter.h"
#include "llvm/MC/MCParser/MCAsmParser.h"
#include "llvm/MC/MCParser/MCTargetAsmParser.h"
#include "llvm/MC/MCRegisterInfo.h"
#include "llvm/MC/MCSectionMachO.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCTargetOptions.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormattedStream.h"
#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"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/Timer.h"
#include "llvm/Support/raw_ostream.h"
#include <memory>
#include <system_error>
using namespace clang;
using namespace clang::driver;
using namespace clang::driver::options;
using namespace llvm;
using namespace llvm::opt;
#include "cc1as.h"
bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
ArrayRef<const char *> Argv,
DiagnosticsEngine &Diags) {
bool Success = true;
// Parse the arguments.
const OptTable &OptTbl = getDriverOptTable();
const unsigned IncludedFlagsBitmask = options::CC1AsOption;
unsigned MissingArgIndex, MissingArgCount;
InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount,
IncludedFlagsBitmask);
// Check for missing argument error.
if (MissingArgCount) {
Diags.Report(diag::err_drv_missing_argument)
<< Args.getArgString(MissingArgIndex) << MissingArgCount;
Success = false;
}
// Issue errors on unknown arguments.
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
auto ArgString = A->getAsString(Args);
std::string Nearest;
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)
<< ArgString << Nearest;
Success = false;
}
// Construct the invocation.
// Target Options
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
Opts.CPU = Args.getLastArgValue(OPT_target_cpu);
Opts.Features = Args.getAllArgValues(OPT_target_feature);
// Use the default target triple if unspecified.
if (Opts.Triple.empty())
Opts.Triple = llvm::sys::getDefaultTargetTriple();
// Language Options
Opts.IncludePaths = Args.getAllArgValues(OPT_I);
Opts.NoInitialTextSection = Args.hasArg(OPT_n);
Opts.SaveTemporaryLabels = Args.hasArg(OPT_msave_temp_labels);
// Any DebugInfoKind implies GenDwarfForAssembly.
Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ);
if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections,
OPT_compress_debug_sections_EQ)) {
if (A->getOption().getID() == OPT_compress_debug_sections) {
// TODO: be more clever about the compression type auto-detection
Opts.CompressDebugSections = llvm::DebugCompressionType::GNU;
} else {
Opts.CompressDebugSections =
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
.Case("none", llvm::DebugCompressionType::None)
.Case("zlib", llvm::DebugCompressionType::Z)
.Case("zlib-gnu", llvm::DebugCompressionType::GNU)
.Default(llvm::DebugCompressionType::None);
}
}
Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
Opts.DwarfDebugFlags = Args.getLastArgValue(OPT_dwarf_debug_flags);
Opts.DwarfDebugProducer = Args.getLastArgValue(OPT_dwarf_debug_producer);
Opts.DebugCompilationDir = Args.getLastArgValue(OPT_fdebug_compilation_dir);
Opts.MainFileName = Args.getLastArgValue(OPT_main_file_name);
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ))
Opts.DebugPrefixMap.insert(StringRef(Arg).split('='));
// Frontend Options
if (Args.hasArg(OPT_INPUT)) {
bool First = true;
for (const Arg *A : Args.filtered(OPT_INPUT)) {
if (First) {
Opts.InputFile = A->getValue();
First = false;
} else {
Diags.Report(diag::err_drv_unknown_argument) << A->getAsString(Args);
Success = false;
}
}
}
Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm);
Opts.OutputPath = Args.getLastArgValue(OPT_o);
Opts.SplitDwarfOutput = Args.getLastArgValue(OPT_split_dwarf_output);
if (Arg *A = Args.getLastArg(OPT_filetype)) {
StringRef Name = A->getValue();
unsigned OutputType = StringSwitch<unsigned>(Name)
.Case("asm", FT_Asm)
.Case("null", FT_Null)
.Case("obj", FT_Obj)
.Default(~0U);
if (OutputType == ~0U) {
Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << Name;
Success = false;
} else
Opts.OutputType = FileType(OutputType);
}
Opts.ShowHelp = Args.hasArg(OPT_help);
Opts.ShowVersion = Args.hasArg(OPT_version);
// Transliterate Options
Opts.OutputAsmVariant =
getLastArgIntValue(Args, OPT_output_asm_variant, 0, Diags);
Opts.ShowEncoding = Args.hasArg(OPT_show_encoding);
Opts.ShowInst = Args.hasArg(OPT_show_inst);
// Assemble Options
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 =
Args.hasArg(OPT_mincremental_linker_compatible);
Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym);
// EmbedBitcode Option. If -fembed-bitcode is enabled, set the flag.
// EmbedBitcode behaves the same for all embed options for assembly files.
if (auto *A = Args.getLastArg(OPT_fembed_bitcode_EQ)) {
Opts.EmbedBitcode = llvm::StringSwitch<unsigned>(A->getValue())
.Case("all", 1)
.Case("bitcode", 1)
.Case("marker", 1)
.Default(0);
}
return Success;
}
static std::unique_ptr<raw_fd_ostream>
getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
// Make sure that the Out file gets unlinked from the disk if we get a
// SIGINT.
if (Path != "-")
sys::RemoveFileOnSignal(Path);
std::error_code EC;
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;
}
return Out;
}
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
// Get the target specific parser.
std::string Error;
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
if (!TheTarget)
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
MemoryBuffer::getFileOrSTDIN(Opts.InputFile);
if (std::error_code EC = Buffer.getError()) {
Error = EC.message();
return Diags.Report(diag::err_fe_error_reading) << Opts.InputFile;
}
SourceMgr SrcMgr;
// Tell SrcMgr about this buffer, which is what the parser will pick up.
unsigned BufferIndex = SrcMgr.AddNewSourceBuffer(std::move(*Buffer), SMLoc());
// Record the location of the include directories so that the lexer can find
// it later.
SrcMgr.setIncludeDirs(Opts.IncludePaths);
std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(Opts.Triple));
assert(MRI && "Unable to create target register info!");
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
// may be created with a combination of default and explicit settings.
MAI->setCompressDebugSections(Opts.CompressDebugSections);
MAI->setRelaxELFRelocations(Opts.RelaxELFRelocations);
bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj;
if (Opts.OutputPath.empty())
Opts.OutputPath = "-";
std::unique_ptr<raw_fd_ostream> FDOS =
getOutputStream(Opts.OutputPath, Diags, IsBinary);
if (!FDOS)
return true;
std::unique_ptr<raw_fd_ostream> DwoOS;
if (!Opts.SplitDwarfOutput.empty())
DwoOS = getOutputStream(Opts.SplitDwarfOutput, Diags, IsBinary);
// FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and
// 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, &MCOptions);
bool PIC = false;
if (Opts.RelocationModel == "static") {
PIC = false;
} else if (Opts.RelocationModel == "pic") {
PIC = true;
} else {
assert(Opts.RelocationModel == "dynamic-no-pic" &&
"Invalid PIC model!");
PIC = false;
}
MOFI->InitMCObjectFileInfo(Triple(Opts.Triple), PIC, Ctx);
if (Opts.SaveTemporaryLabels)
Ctx.setAllowTemporaryLabels(false);
if (Opts.GenDwarfForAssembly)
Ctx.setGenDwarfForAssembly(true);
if (!Opts.DwarfDebugFlags.empty())
Ctx.setDwarfDebugFlags(StringRef(Opts.DwarfDebugFlags));
if (!Opts.DwarfDebugProducer.empty())
Ctx.setDwarfDebugProducer(StringRef(Opts.DwarfDebugProducer));
if (!Opts.DebugCompilationDir.empty())
Ctx.setCompilationDir(Opts.DebugCompilationDir);
else {
// If no compilation dir is set, try to use the current directory.
SmallString<128> CWD;
if (!sys::fs::current_path(CWD))
Ctx.setCompilationDir(CWD);
}
if (!Opts.DebugPrefixMap.empty())
for (const auto &KV : Opts.DebugPrefixMap)
Ctx.addDebugPrefixMapEntry(KV.first, KV.second);
if (!Opts.MainFileName.empty())
Ctx.setMainFileName(StringRef(Opts.MainFileName));
Ctx.setDwarfVersion(Opts.DwarfVersion);
if (Opts.GenDwarfForAssembly)
Ctx.setGenDwarfRootFile(Opts.InputFile,
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
// Build up the feature string from the target feature list.
std::string FS;
if (!Opts.Features.empty()) {
FS = Opts.Features[0];
for (unsigned i = 1, e = Opts.Features.size(); i != e; ++i)
FS += "," + Opts.Features[i];
}
std::unique_ptr<MCStreamer> Str;
std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
std::unique_ptr<MCSubtargetInfo> STI(
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
raw_pwrite_stream *Out = FDOS.get();
std::unique_ptr<buffer_ostream> BOS;
MCOptions.MCNoWarn = Opts.NoWarn;
MCOptions.MCFatalWarnings = Opts.FatalWarnings;
MCOptions.ABIName = Opts.TargetABI;
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
if (Opts.OutputType == AssemblerInvocation::FT_Asm) {
MCInstPrinter *IP = TheTarget->createMCInstPrinter(
llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI);
std::unique_ptr<MCCodeEmitter> CE;
if (Opts.ShowEncoding)
CE.reset(TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
std::unique_ptr<MCAsmBackend> MAB(
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
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),
Opts.ShowInst));
} else if (Opts.OutputType == AssemblerInvocation::FT_Null) {
Str.reset(createNullStreamer(Ctx));
} else {
assert(Opts.OutputType == AssemblerInvocation::FT_Obj &&
"Invalid file type!");
if (!FDOS->supportsSeeking()) {
BOS = std::make_unique<buffer_ostream>(*FDOS);
Out = BOS.get();
}
std::unique_ptr<MCCodeEmitter> CE(
TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
std::unique_ptr<MCAsmBackend> MAB(
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
std::unique_ptr<MCObjectWriter> OW =
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
: MAB->createObjectWriter(*Out);
Triple T(Opts.Triple);
Str.reset(TheTarget->createMCObjectStreamer(
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI,
Opts.RelaxAll, Opts.IncrementalLinkerCompatible,
/*DWARFMustBeAtTheEnd*/ true));
Str.get()->InitSections(Opts.NoExecStack);
}
// When -fembed-bitcode is passed to clang_as, a 1-byte marker
// is emitted in __LLVM,__asm section if the object file is MachO format.
if (Opts.EmbedBitcode && Ctx.getObjectFileInfo()->getObjectFileType() ==
MCObjectFileInfo::IsMachO) {
MCSection *AsmLabel = Ctx.getMachOSection(
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
Str.get()->SwitchSection(AsmLabel);
Str.get()->EmitZeros(1);
}
// Assembly to object compilation should leverage assembly info.
Str->setUseAssemblerInfoForParsing(true);
bool Failed = false;
std::unique_ptr<MCAsmParser> Parser(
createMCAsmParser(SrcMgr, Ctx, *Str.get(), *MAI));
// FIXME: init MCTargetOptions from sanitizer flags here.
std::unique_ptr<MCTargetAsmParser> TAP(
TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions));
if (!TAP)
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
// Set values for symbols, if any.
for (auto &S : Opts.SymbolDefs) {
auto Pair = StringRef(S).split('=');
auto Sym = Pair.first;
auto Val = Pair.second;
int64_t Value;
// We have already error checked this in the driver.
Val.getAsInteger(0, Value);
Ctx.setSymbolValue(Parser->getStreamer(), Sym, Value);
}
if (!Failed) {
Parser->setTargetParser(*TAP.get());
Failed = Parser->Run(Opts.NoInitialTextSection);
}
// Close Streamer first.
// It might have a reference to the output stream.
Str.reset();
// Close the output stream early.
BOS.reset();
FDOS.reset();
// Delete output file if there were errors.
if (Failed) {
if (Opts.OutputPath != "-")
sys::fs::remove(Opts.OutputPath);
if (!Opts.SplitDwarfOutput.empty() && Opts.SplitDwarfOutput != "-")
sys::fs::remove(Opts.SplitDwarfOutput);
}
return Failed;
}
static void LLVMErrorHandler(void *UserData, const std::string &Message,
bool GenCrashDiag) {
DiagnosticsEngine &Diags = *static_cast<DiagnosticsEngine*>(UserData);
Diags.Report(diag::err_fe_error_backend) << Message;
// We cannot recover from llvm errors.
sys::Process::Exit(1);
}
int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
// Initialize targets and assembly printers/parsers.
InitializeAllTargetInfos();
InitializeAllTargetMCs();
InitializeAllAsmParsers();
// Construct our diagnostic client.
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
TextDiagnosticPrinter *DiagClient
= new TextDiagnosticPrinter(errs(), &*DiagOpts);
DiagClient->setPrefix("clang -cc1as");
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient);
// Set an error handler, so that any LLVM backend diagnostics go through our
// error handler.
ScopedFatalErrorHandler FatalErrorHandler
(LLVMErrorHandler, static_cast<void*>(&Diags));
// Parse the arguments.
AssemblerInvocation Asm;
if (!AssemblerInvocation::CreateFromArgs(Asm, Argv, Diags))
return 1;
if (Asm.ShowHelp) {
getDriverOptTable().PrintHelp(
llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler",
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
/*ShowAllAliases=*/false);
return 0;
}
// Honor -version.
//
// FIXME: Use a better -version message?
if (Asm.ShowVersion) {
llvm::cl::PrintVersionMessage();
return 0;
}
// Honor -mllvm.
//
// FIXME: Remove this, one day.
if (!Asm.LLVMArgs.empty()) {
unsigned NumArgs = Asm.LLVMArgs.size();
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();
Args[NumArgs + 1] = nullptr;
llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get());
}
// Execute the invocation, unless there were parsing errors.
bool Failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags);
// If any timers were active but haven't been destroyed yet, print their
// results now.
TimerGroup::printAll(errs());
TimerGroup::clearAll();
return !!Failed;
}
+120
View File
@@ -0,0 +1,120 @@
//===-- cc1as.h - Clang Assembler ----------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This is the entry point to the clang -cc1as functionality, which implements
// the direct interface to the LLVM MC based assembler.
//
//===----------------------------------------------------------------------===//
/// Helper class for representing a single invocation of the assembler.
struct AssemblerInvocation {
/// @name Target Options
/// @{
/// The name of the target triple to assemble for.
std::string Triple;
/// If given, the name of the target CPU to determine which instructions
/// are legal.
std::string CPU;
/// The list of target specific features to enable or disable -- this should
/// be a list of strings starting with '+' or '-'.
std::vector<std::string> Features;
/// The list of symbol definitions.
std::vector<std::string> SymbolDefs;
/// @}
/// @name Language Options
/// @{
std::vector<std::string> IncludePaths;
unsigned NoInitialTextSection : 1;
unsigned SaveTemporaryLabels : 1;
unsigned GenDwarfForAssembly : 1;
unsigned RelaxELFRelocations : 1;
unsigned DwarfVersion;
std::string DwarfDebugFlags;
std::string DwarfDebugProducer;
std::string DebugCompilationDir;
std::map<const std::string, const std::string> DebugPrefixMap;
llvm::DebugCompressionType CompressDebugSections =
llvm::DebugCompressionType::None;
std::string MainFileName;
std::string SplitDwarfOutput;
/// @}
/// @name Frontend Options
/// @{
std::string InputFile;
std::vector<std::string> LLVMArgs;
std::string OutputPath;
enum FileType {
FT_Asm, ///< Assembly (.s) output, transliterate mode.
FT_Null, ///< No output, for timing purposes.
FT_Obj ///< Object file output.
};
FileType OutputType;
unsigned ShowHelp : 1;
unsigned ShowVersion : 1;
/// @}
/// @name Transliterate Options
/// @{
unsigned OutputAsmVariant;
unsigned ShowEncoding : 1;
unsigned ShowInst : 1;
/// @}
/// @name Assembler Options
/// @{
unsigned RelaxAll : 1;
unsigned NoExecStack : 1;
unsigned FatalWarnings : 1;
unsigned NoWarn : 1;
unsigned IncrementalLinkerCompatible : 1;
unsigned EmbedBitcode : 1;
/// The name of the relocation model to use.
std::string RelocationModel;
/// The ABI targeted by the backend. Specified using -target-abi. Empty
/// otherwise.
std::string TargetABI;
/// @}
public:
AssemblerInvocation() {
Triple = "";
NoInitialTextSection = 0;
InputFile = "-";
OutputPath = "-";
OutputType = FT_Asm;
OutputAsmVariant = 0;
ShowInst = 0;
ShowEncoding = 0;
RelaxAll = 0;
NoExecStack = 0;
FatalWarnings = 0;
NoWarn = 0;
IncrementalLinkerCompatible = 0;
DwarfVersion = 0;
EmbedBitcode = 0;
}
static bool CreateFromArgs(AssemblerInvocation &Res,
ArrayRef<const char *> Argv,
DiagnosticsEngine &Diags);
};
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags);
+97
View File
@@ -0,0 +1,97 @@
// +build byollvm
#include <clang/Basic/DiagnosticOptions.h>
#include <clang/CodeGen/CodeGenAction.h>
#include <clang/Driver/Compilation.h>
#include <clang/Driver/Driver.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Frontend/CompilerInvocation.h>
#include <clang/Frontend/FrontendDiagnostic.h>
#include <clang/Frontend/TextDiagnosticPrinter.h>
#include <clang/FrontendTool/Utils.h>
#include <llvm/ADT/IntrusiveRefCntPtr.h>
#include <llvm/Option/Option.h>
using namespace llvm;
using namespace clang;
#include "cc1as.h"
// This file provides C wrappers for the builtin tools cc1 and cc1as
// provided by Clang, and calls them as the driver would call them.
extern "C" {
bool tinygo_clang_driver(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
// The compiler invocation needs a DiagnosticsEngine so it can report problems
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts = new clang::DiagnosticOptions();
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false);
// Create the clang driver
clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags);
// Create the set of actions to perform
std::unique_ptr<clang::driver::Compilation> C(TheDriver.BuildCompilation(args));
if (!C) {
return false;
}
const clang::driver::JobList &Jobs = C->getJobs();
// There may be more than one job, for example for .S files
// (preprocessor + assembler).
for (auto Cmd : Jobs) {
// Select the tool: cc1 or cc1as.
const llvm::opt::ArgStringList &CCArgs = Cmd.getArguments();
if (strcmp(*CCArgs.data(), "-cc1") == 0) {
// This is the C frontend.
// Initialize a compiler invocation object from the clang (-cc1) arguments.
std::unique_ptr<clang::CompilerInstance> Clang(new clang::CompilerInstance());
bool success = clang::CompilerInvocation::CreateFromArgs(
Clang->getInvocation(),
CCArgs,
Diags);
if (!success) {
return false;
}
// Create the actual diagnostics engine.
Clang->createDiagnostics();
if (!Clang->hasDiagnostics()) {
return false;
}
// Execute the frontend actions.
success = ExecuteCompilerInvocation(Clang.get());
if (!success) {
return false;
}
} else if (strcmp(*CCArgs.data(), "-cc1as") == 0) {
// This is the assembler frontend. Parse the arguments.
AssemblerInvocation Asm;
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.
bool failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags);
if (failed) {
return false;
}
} else {
// Unknown tool, print the tool and exit.
fprintf(stderr, "unknown tool: %s\n", *CCArgs.data());
return false;
}
}
// Commands executed successfully.
return true;
}
} // extern "C"
+65
View File
@@ -0,0 +1,65 @@
package builder
import (
"errors"
"os"
"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{}
func init() {
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" {
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
// variable.
if runtime.GOOS == "windows" {
commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe")
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 LLVM installed from ports.
if runtime.GOOS == "freebsd" {
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")
}
}
func execCommand(cmdNames []string, args ...string) error {
for _, cmdName := range cmdNames {
cmd := exec.Command(cmdName, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.Error); ok && (err.Err == exec.ErrNotFound || err.Err.Error() == "file does not exist") {
// this command was not found, try the next
continue
}
return err
}
return nil
}
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
}
+39
View File
@@ -0,0 +1,39 @@
package builder
import (
"errors"
"fmt"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
// NewConfig builds a new Config object from a set of compiler options. It also
// loads some information from the environment while doing that. For example, it
// uses the currently active GOPATH (from the goenv package) to determine the Go
// version to use.
func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
spec, err := compileopts.LoadTarget(options.Target)
if err != nil {
return nil, err
}
goroot := goenv.Get("GOROOT")
if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually")
}
major, minor, err := goenv.GetGorootVersion(goroot)
if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 || minor < 11 || minor > 14 {
return nil, fmt.Errorf("requires go version 1.11, 1.12, 1.13, or 1.14, got go%d.%d", major, minor)
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{
Options: options,
Target: spec,
GoMinorVersion: minor,
ClangHeaders: clangHeaderPath,
TestConfig: options.TestConfig,
}, nil
}
+69
View File
@@ -0,0 +1,69 @@
package builder
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sort"
)
// 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.
func getClangHeaderPath(TINYGOROOT string) string {
// Check whether we're running from the source directory.
path := filepath.Join(TINYGOROOT, "llvm-project", "clang", "lib", "Headers")
if _, err := os.Stat(path); !os.IsNotExist(err) {
return path
}
// Check whether we're running from the installation directory.
path = filepath.Join(TINYGOROOT, "lib", "clang", "include")
if _, err := os.Stat(path); !os.IsNotExist(err) {
return path
}
// It looks like we are built with a system-installed LLVM. Do a last
// attempt: try to use Clang headers relative to the clang binary.
for _, cmdName := range commands["clang"] {
binpath, err := exec.LookPath(cmdName)
if err == nil {
// This should be the command that will also be used by
// execCommand. To avoid inconsistencies, make sure we use the
// headers relative to this command.
binpath, err = filepath.EvalSymlinks(binpath)
if err != nil {
// Unexpected.
return ""
}
// Example executable:
// /usr/lib/llvm-9/bin/clang
// Example include path:
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
llvmRoot := filepath.Dir(filepath.Dir(binpath))
clangVersionRoot := filepath.Join(llvmRoot, "lib", "clang")
dirs, err := ioutil.ReadDir(clangVersionRoot)
if err != nil {
// Unexpected.
continue
}
dirnames := make([]string, len(dirs))
for i, d := range dirs {
dirnames[i] = d.Name()
}
sort.Strings(dirnames)
// Check for the highest version first.
for i := len(dirnames) - 1; i >= 0; i-- {
path := filepath.Join(clangVersionRoot, dirnames[i], "include")
_, err := os.Stat(filepath.Join(path, "stdint.h"))
if err == nil {
return path
}
}
}
}
// Could not find it.
return ""
}
+39
View File
@@ -0,0 +1,39 @@
package builder
// MultiError is a list of multiple errors (actually: diagnostics) returned
// during LLVM IR generation.
type MultiError struct {
Errs []error
}
func (e *MultiError) Error() string {
// Return the first error, to conform to the error interface. Clients should
// really do a type-assertion on *MultiError.
return e.Errs[0].Error()
}
// newMultiError returns a *MultiError if there is more than one error, or
// returns that error directly when there is only one. Passing an empty slice
// will lead to a panic.
func newMultiError(errs []error) error {
switch len(errs) {
case 0:
panic("attempted to create empty MultiError")
case 1:
return errs[0]
default:
return &MultiError{errs}
}
}
// commandError is an error type to wrap os/exec.Command errors. This provides
// some more information regarding what went wrong while running a command.
type commandError struct {
Msg string
File string
Err error
}
func (e *commandError) Error() string {
return e.Msg + " " + e.File + ": " + e.Err.Error()
}
+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))
}
+19
View File
@@ -0,0 +1,19 @@
// +build byollvm
// This file provides C wrappers for liblld.
#include <lld/Common/Driver.h>
extern "C" {
bool tinygo_link_elf(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::elf::link(args, false, 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, llvm::outs(), llvm::errs());
}
} // external "C"
+130
View File
@@ -0,0 +1,130 @@
package builder
import (
"debug/elf"
"io/ioutil"
"os"
"path/filepath"
"sort"
"github.com/marcinbor85/gohex"
)
// objcopyError is an error returned by functions that act like objcopy.
type objcopyError struct {
Op string
Err error
}
func (e objcopyError) Error() string {
if e.Err == nil {
return e.Op
}
return e.Op + ": " + e.Err.Error()
}
type progSlice []*elf.Prog
func (s progSlice) Len() int { return len(s) }
func (s progSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr }
func (s progSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// extractROM extracts a firmware image and the first load address from the
// given ELF file. It tries to emulate the behavior of objcopy.
func extractROM(path string) (uint64, []byte, error) {
f, err := elf.Open(path)
if err != nil {
return 0, nil, objcopyError{"failed to open ELF file to extract text segment", err}
}
defer f.Close()
// The GNU objcopy command does the following for firmware extraction (from
// the man page):
// > When objcopy generates a raw binary file, it will essentially produce a
// > memory dump of the contents of the input object file. All symbols and
// > relocation information will be discarded. The memory dump will start at
// > the load address of the lowest section copied into the output file.
// Find the lowest section address.
startAddr := ^uint64(0)
for _, section := range f.Sections {
if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 {
continue
}
if section.Addr < startAddr {
startAddr = section.Addr
}
}
progs := make(progSlice, 0, 2)
for _, prog := range f.Progs {
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
continue
}
progs = append(progs, prog)
}
if len(progs) == 0 {
return 0, nil, objcopyError{"file does not contain ROM segments: " + path, nil}
}
sort.Sort(progs)
var rom []byte
for _, prog := range progs {
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
}
data, err := ioutil.ReadAll(prog.Open())
if err != nil {
return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err}
}
rom = append(rom, data...)
}
if progs[0].Paddr < startAddr {
// The lowest memory address is before the first section. This means
// that there is some extra data loaded at the start of the image that
// should be discarded.
// Example: ELF files where .text doesn't start at address 0 because
// there is a bootloader at the start.
return startAddr, rom[startAddr-progs[0].Paddr:], nil
} else {
return progs[0].Paddr, rom, nil
}
}
// objcopy converts an ELF file to a different (simpler) output file format:
// .bin or .hex. It extracts only the .text section.
func objcopy(infile, outfile string) error {
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer f.Close()
// Read the .text segment.
addr, data, err := extractROM(infile)
if err != nil {
return err
}
// Write to the file, in the correct format.
switch filepath.Ext(outfile) {
case ".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":
mem := gohex.NewMemory()
err := mem.AddBinary(uint32(addr), data)
if err != nil {
return objcopyError{"failed to create .hex file", err}
}
return mem.DumpIntelHex(f, 16)
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",
}
+32 -19
View File
@@ -1,4 +1,4 @@
package main
package builder
import (
"debug/elf"
@@ -6,18 +6,18 @@ import (
"strings"
)
// Statistics about code size in a program.
type ProgramSize struct {
Packages map[string]*PackageSize
Sum *PackageSize
// programSize contains size statistics per package of a compiled program.
type programSize struct {
Packages map[string]*packageSize
Sum *packageSize
Code uint64
Data uint64
BSS uint64
}
// Return the list of package names (ProgramSize.Packages) sorted
// alphabetically.
func (ps *ProgramSize) SortedPackageNames() []string {
// sortedPackageNames returns the list of package names (ProgramSize.Packages)
// sorted alphabetically.
func (ps *programSize) sortedPackageNames() []string {
names := make([]string, 0, len(ps.Packages))
for name := range ps.Packages {
names = append(names, name)
@@ -26,8 +26,9 @@ func (ps *ProgramSize) SortedPackageNames() []string {
return names
}
// The size of a package, calculated from the linked object file.
type PackageSize struct {
// packageSize contains the size of a package, calculated from the linked object
// file.
type packageSize struct {
Code uint64
ROData uint64
Data uint64
@@ -35,12 +36,12 @@ type PackageSize struct {
}
// Flash usage in regular microcontrollers.
func (ps *PackageSize) Flash() uint64 {
func (ps *packageSize) Flash() uint64 {
return ps.Code + ps.ROData + ps.Data
}
// Static RAM usage in regular microcontrollers.
func (ps *PackageSize) RAM() uint64 {
func (ps *packageSize) RAM() uint64 {
return ps.Data + ps.BSS
}
@@ -64,8 +65,9 @@ func (l symbolList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
// Calculate program/data size breakdown of each package for a given ELF file.
func Sizes(path string) (*ProgramSize, error) {
// loadProgramSize calculate a program/data size breakdown of each package for a
// given ELF file.
func loadProgramSize(path string) (*programSize, error) {
file, err := elf.Open(path)
if err != nil {
return nil, err
@@ -82,7 +84,18 @@ func Sizes(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
@@ -115,7 +128,7 @@ func Sizes(path string) (*ProgramSize, error) {
}
sort.Sort(symbolList(symbols))
sizes := map[string]*PackageSize{}
sizes := map[string]*packageSize{}
var lastSymbolValue uint64
for _, symbol := range symbols {
symType := elf.ST_TYPE(symbol.Info)
@@ -129,7 +142,7 @@ func Sizes(path string) (*ProgramSize, error) {
}
pkgSize := sizes[pkgName]
if pkgSize == nil {
pkgSize = &PackageSize{}
pkgSize = &packageSize{}
sizes[pkgName] = pkgSize
}
if lastSymbolValue != symbol.Value || lastSymbolValue == 0 {
@@ -148,7 +161,7 @@ func Sizes(path string) (*ProgramSize, error) {
lastSymbolValue = symbol.Value
}
sum := &PackageSize{}
sum := &packageSize{}
for _, pkg := range sizes {
sum.Code += pkg.Code
sum.ROData += pkg.ROData
@@ -156,5 +169,5 @@ func Sizes(path string) (*ProgramSize, error) {
sum.BSS += pkg.BSS
}
return &ProgramSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
return &programSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
}
+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()
}
+151
View File
@@ -0,0 +1,151 @@
package builder
// This file converts firmware files from BIN to UF2 format before flashing.
//
// For more information about the UF2 firmware file format, please see:
// https://github.com/Microsoft/uf2
//
//
import (
"bytes"
"encoding/binary"
"io/ioutil"
"strconv"
)
// convertELFFileToUF2File converts an ELF file to a UF2 file.
func convertELFFileToUF2File(infile, outfile string, uf2FamilyID string) error {
// Read the .text segment.
targetAddress, data, err := extractROM(infile)
if err != nil {
return err
}
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, uf2FamilyID string) ([]byte, int, error) {
blocks := split(input, 256)
output := make([]byte, 0)
bl, err := newUF2Block(targetAddr, uf2FamilyID)
if err != nil {
return nil, 0, err
}
bl.SetNumBlocks(len(blocks))
for i := 0; i < len(blocks); i++ {
bl.SetBlockNo(i)
bl.SetData(blocks[i])
output = append(output, bl.Bytes()...)
bl.IncrementAddress(bl.payloadSize)
}
return output, len(blocks), nil
}
const (
uf2MagicStart0 = 0x0A324655 // "UF2\n"
uf2MagicStart1 = 0x9E5D5157 // Randomly selected
uf2MagicEnd = 0x0AB16F30 // Ditto
)
// uf2Block is the structure used for each UF2 code block sent to device.
type uf2Block struct {
magicStart0 uint32
magicStart1 uint32
flags uint32
targetAddr uint32
payloadSize uint32
blockNo uint32
numBlocks uint32
familyID uint32
data []uint8
magicEnd uint32
}
// newUF2Block returns a new uf2Block struct that has been correctly populated
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: 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))
binary.Write(buf, binary.LittleEndian, b.magicStart0)
binary.Write(buf, binary.LittleEndian, b.magicStart1)
binary.Write(buf, binary.LittleEndian, b.flags)
binary.Write(buf, binary.LittleEndian, b.targetAddr)
binary.Write(buf, binary.LittleEndian, b.payloadSize)
binary.Write(buf, binary.LittleEndian, b.blockNo)
binary.Write(buf, binary.LittleEndian, b.numBlocks)
binary.Write(buf, binary.LittleEndian, b.familyID)
binary.Write(buf, binary.LittleEndian, b.data)
binary.Write(buf, binary.LittleEndian, b.magicEnd)
return buf.Bytes()
}
// IncrementAddress moves the target address pointer forward by count bytes.
func (b *uf2Block) IncrementAddress(count uint32) {
b.targetAddr += b.payloadSize
}
// SetData sets the data to be used for the current block.
func (b *uf2Block) SetData(d []byte) {
b.data = make([]byte, 476)
copy(b.data[:], d)
}
// SetBlockNo sets the current block number to be used.
func (b *uf2Block) SetBlockNo(bn int) {
b.blockNo = uint32(bn)
}
// SetNumBlocks sets the total number of blocks for this UF2 file.
func (b *uf2Block) SetNumBlocks(total int) {
b.numBlocks = uint32(total)
}
// split splits a slice of bytes into a slice of byte slices of a specific size limit.
func split(input []byte, limit int) [][]byte {
var block []byte
output := make([][]byte, 0, len(input)/limit+1)
for len(input) >= limit {
block, input = input[:limit], input[limit:]
output = append(output, block)
}
if len(input) > 0 {
output = append(output, input[:len(input)])
}
return output
}
+1387
View File
File diff suppressed because it is too large Load Diff
+145
View File
@@ -0,0 +1,145 @@
package cgo
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
)
// 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"}
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
name := name // avoid a race condition
t.Run(name, func(t *testing.T) {
// Read the AST in memory.
path := filepath.Join("testdata", name+".go")
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
t.Fatal("could not parse Go source file:", err)
}
// Process the AST with CGo.
cgoAST, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
// Check the AST for type errors.
var typecheckErrors []error
config := types.Config{
Error: func(err error) {
typecheckErrors = append(typecheckErrors, err)
},
Importer: simpleImporter{},
Sizes: types.SizesFor("gccgo", "arm"),
}
_, err = config.Check("", fset, []*ast.File{f, cgoAST}, nil)
if err != nil && len(typecheckErrors) == 0 {
// Only report errors when no type errors are found (an
// unexpected condition).
t.Error(err)
}
// Store the (formatted) output in a buffer. Format it, so it
// becomes easier to read (and will hopefully change less with CGo
// changes).
buf := &bytes.Buffer{}
if len(cgoErrors) != 0 {
buf.WriteString("// CGo errors:\n")
for _, err := range cgoErrors {
buf.WriteString(formatDiagnostic(err))
}
buf.WriteString("\n")
}
if len(typecheckErrors) != 0 {
buf.WriteString("// Type checking errors after CGo processing:\n")
for _, err := range typecheckErrors {
buf.WriteString(formatDiagnostic(err))
}
buf.WriteString("\n")
}
err = format.Node(buf, fset, cgoAST)
if err != nil {
t.Errorf("could not write out CGo AST: %v", err)
}
actual := normalizeResult(string(buf.Bytes()))
// Read the file with the expected output, to compare against.
outfile := filepath.Join("testdata", name+".out.go")
expectedBytes, err := ioutil.ReadFile(outfile)
if err != nil {
t.Fatalf("could not read expected output: %v", err)
}
expected := strings.Replace(string(expectedBytes), "\r\n", "\n", -1)
// Check whether the output is as expected.
if expected != actual {
// It is not. Test failed.
if *flagUpdate {
// Update the file with the expected data.
err := ioutil.WriteFile(outfile, []byte(actual), 0666)
if err != nil {
t.Error("could not write updated output file:", err)
}
return
}
t.Errorf("output did not match:\n%s", string(actual))
}
})
}
}
// simpleImporter implements the types.Importer interface, but only allows
// importing the unsafe package.
type simpleImporter struct {
}
// Import implements the Importer interface. For testing usage only: it only
// supports importing the unsafe package.
func (i simpleImporter) Import(path string) (*types.Package, error) {
switch path {
case "unsafe":
return types.Unsafe, nil
default:
return nil, fmt.Errorf("importer not implemented for package %s", path)
}
}
// formatDiagnostics formats the error message to be an indented comment. It
// also fixes Windows path name issues (backward slashes).
func formatDiagnostic(err error) string {
msg := err.Error()
if runtime.GOOS == "windows" {
// Fix Windows path slashes.
msg = strings.Replace(msg, "testdata\\", "testdata/", -1)
}
return "// " + msg + "\n"
}
+218
View File
@@ -0,0 +1,218 @@
package cgo
// This file implements a parser of a subset of the C language, just enough to
// parse common #define statements to Go constant expressions.
import (
"fmt"
"go/ast"
"go/scanner"
"go/token"
"strings"
)
// parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value)
expr, err := parseConstExpr(t)
if t.token != token.EOF {
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: "unexpected token " + t.token.String(),
}
}
return expr, err
}
// parseConstExpr parses a stream of C tokens to a Go expression.
func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
switch t.token {
case token.LPAREN:
lparen := t.pos
t.Next()
x, err := parseConstExpr(t)
if err != nil {
return nil, err
}
if t.token != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.pos,
}
t.Next()
return expr, nil
case token.INT, token.FLOAT, token.STRING, token.CHAR:
expr := &ast.BasicLit{
ValuePos: t.pos,
Kind: t.token,
Value: t.value,
}
t.Next()
return expr, nil
case token.IDENT:
expr := &ast.Ident{
NamePos: t.pos,
Name: "C." + t.value,
}
t.Next()
return expr, nil
case token.EOF:
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: "empty constant",
}
default:
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s", t.token),
}
}
}
// unexpectedToken returns an error of the form "unexpected token FOO, expected
// BAR".
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
return &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
}
}
// tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct {
pos token.Pos
fset *token.FileSet
token token.Token
value string
buf string
}
// newTokenizer initializes a new tokenizer, positioned at the first token in
// the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
t := &tokenizer{
pos: start,
fset: fset,
buf: buf,
token: token.ILLEGAL,
}
t.Next() // Parse the first token.
return t
}
// Next consumes the next token in the stream. There is no return value, read
// the next token from the pos, token and value properties.
func (t *tokenizer) Next() {
t.pos += token.Pos(len(t.value))
for {
if len(t.buf) == 0 {
t.token = token.EOF
return
}
c := t.buf[0]
switch {
case c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v':
// Skip whitespace.
// Based on this source, not sure whether it represents C whitespace:
// https://en.cppreference.com/w/cpp/string/byte/isspace
t.pos++
t.buf = t.buf[1:]
case c == '(' || c == ')':
// Single-character tokens.
switch c {
case '(':
t.token = token.LPAREN
case ')':
t.token = token.RPAREN
}
t.value = t.buf[:1]
t.buf = t.buf[1:]
return
case c >= '0' && c <= '9':
// Numeric constant (int, float, etc.).
// Find the last non-numeric character.
tokenLen := len(t.buf)
hasDot := false
for i, c := range t.buf {
if c == '.' {
hasDot = true
}
if c >= '0' && c <= '9' || c == '.' || c == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
tokenLen = i + 1
} else {
break
}
}
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
if hasDot {
// Integer constants are more complicated than this but this is
// a close approximation.
// https://en.cppreference.com/w/cpp/language/integer_literal
t.token = token.FLOAT
t.value = strings.TrimRight(t.value, "f")
} else {
t.token = token.INT
t.value = strings.TrimRight(t.value, "uUlL")
}
return
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
// Identifier. Find all remaining tokens that are part of this
// identifier.
tokenLen := len(t.buf)
for i, c := range t.buf {
if c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_' {
tokenLen = i + 1
} else {
break
}
}
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
t.token = token.IDENT
return
case c == '"':
// String constant. Find the first '"' character that is not
// preceded by a backslash.
escape := false
tokenLen := len(t.buf)
for i, c := range t.buf {
if i != 0 && c == '"' && !escape {
tokenLen = i + 1
break
}
if !escape {
escape = c == '\\'
}
}
t.token = token.STRING
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
case c == '\'':
// Char (rune) constant. Find the first '\'' character that is not
// preceded by a backslash.
escape := false
tokenLen := len(t.buf)
for i, c := range t.buf {
if i != 0 && c == '\'' && !escape {
tokenLen = i + 1
break
}
if !escape {
escape = c == '\\'
}
}
t.token = token.CHAR
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
default:
t.token = token.ILLEGAL
return
}
}
}
+60
View File
@@ -0,0 +1,60 @@
package cgo
import (
"bytes"
"go/format"
"go/token"
"strings"
"testing"
)
func TestParseConst(t *testing.T) {
// Test converting a C constant to a Go constant.
for _, tc := range []struct {
C string
Go string
}{
{`5`, `5`},
{`(5)`, `(5)`},
{`(((5)))`, `(5)`},
{`)`, `error: 1:1: unexpected token )`},
{`5)`, `error: 1:2: unexpected token )`},
{" \t)", `error: 1:4: unexpected token )`},
{`5.8f`, `5.8`},
{`foo`, `C.foo`},
{``, `error: 1:1: empty constant`}, // empty constants not allowed in Go
{`"foo"`, `"foo"`},
{`"a\\n"`, `"a\\n"`},
{`"a\n"`, `"a\n"`},
{`"a\""`, `"a\""`},
{`'a'`, `'a'`},
{`0b10`, `0b10`},
{`0x1234_5678`, `0x1234_5678`},
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
} {
fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0)
expr, err := parseConst(startPos, fset, tc.C)
s := "<invalid>"
if err != nil {
if !strings.HasPrefix(tc.Go, "error: ") {
t.Errorf("expected value %#v for C constant %#v but got error %#v", tc.Go, tc.C, err.Error())
continue
}
s = "error: " + err.Error()
} else if expr != nil {
// Serialize the Go constant to a string, for more readable test
// cases.
buf := &bytes.Buffer{}
err := format.Node(buf, fset, expr)
if err != nil {
t.Errorf("could not format expr from C constant %#v: %v", tc.C, err)
continue
}
s = buf.String()
}
if s != tc.Go {
t.Errorf("C constant %#v was parsed to %#v while expecting %#v", tc.C, s, tc.Go)
}
}
}
+776
View File
@@ -0,0 +1,776 @@
package cgo
// This file parses a fragment of C with libclang and stores the result for AST
// modification. It does not touch the AST itself.
import (
"fmt"
"go/ast"
"go/scanner"
"go/token"
"path/filepath"
"strconv"
"strings"
"unsafe"
)
/*
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
#include <stdlib.h>
#include <stdint.h>
// This struct should be ABI-compatible on all platforms (uintptr_t has the same
// alignment etc. as void*) but does not include void* pointers that are not
// always real pointers.
// The Go garbage collector assumes that all non-nil pointer-typed integers are
// actually pointers. This is not always true, as data[1] often contains 0x1,
// which is clearly not a valid pointer. Usually the GC won't catch this issue,
// but occasionally it will leading to a crash with a vague error message.
typedef struct {
enum CXCursorKind kind;
int xdata;
uintptr_t data[3];
} GoCXCursor;
// Forwarding functions. They are implemented in libclang_stubs.c and forward to
// the real functions without doing anything else, thus they are entirely
// compatible with the versions without tinygo_ prefix. The only difference is
// the CXCursor type, which has been replaced with GoCXCursor.
GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu);
unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data);
CXString tinygo_clang_getCursorSpelling(GoCXCursor c);
enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c);
CXType tinygo_clang_getCursorType(GoCXCursor c);
GoCXCursor tinygo_clang_getTypeDeclaration(CXType t);
CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
CXType tinygo_clang_getCursorResultType(GoCXCursor c);
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
*/
import "C"
// storedRefs stores references to types, used for clang_visitChildren.
var storedRefs refMap
var diagnosticSeverity = [...]string{
C.CXDiagnostic_Ignored: "ignored",
C.CXDiagnostic_Note: "note",
C.CXDiagnostic_Warning: "warning",
C.CXDiagnostic_Error: "error",
C.CXDiagnostic_Fatal: "fatal",
}
func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) {
index := C.clang_createIndex(0, 0)
defer C.clang_disposeIndex(index)
// pretend to be a .c file
filenameC := C.CString(posFilename + "!cgo.c")
defer C.free(unsafe.Pointer(filenameC))
// fix up error locations
fragment = fmt.Sprintf("# %d %#v\n", posLine+1, posFilename) + fragment
fragmentC := C.CString(fragment)
defer C.free(unsafe.Pointer(fragmentC))
unsavedFile := C.struct_CXUnsavedFile{
Filename: filenameC,
Length: C.ulong(len(fragment)),
Contents: fragmentC,
}
// convert Go slice of strings to C array of strings.
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
defer C.free(cmdargsC)
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
for i, cflag := range cflags {
s := C.CString(cflag)
cmdargs[i] = s
defer C.free(unsafe.Pointer(s))
}
var unit C.CXTranslationUnit
errCode := C.clang_parseTranslationUnit2(
index,
filenameC,
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
&unsavedFile, 1, // unsaved files
C.CXTranslationUnit_DetailedPreprocessingRecord,
&unit)
if errCode != 0 {
// This is probably a bug in the usage of libclang.
panic("cgo: failed to parse source with libclang")
}
defer C.clang_disposeTranslationUnit(unit)
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
addDiagnostic := func(diagnostic C.CXDiagnostic) {
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
location := C.clang_getDiagnosticLocation(diagnostic)
pos := p.getClangLocationPosition(location, unit)
p.addError(pos, severity+": "+spelling)
}
for i := 0; i < numDiagnostics; i++ {
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
addDiagnostic(diagnostic)
// Child diagnostics (like notes on redefinitions).
diagnostics := C.clang_getChildDiagnostics(diagnostic)
for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ {
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
}
}
}
ref := storedRefs.Put(p)
defer storedRefs.Remove(ref)
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
}
//export tinygo_clang_globals_visitor
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
kind := C.tinygo_clang_getCursorKind(c)
pos := p.getCursorPosition(c)
switch kind {
case C.CXCursor_FunctionDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
cursorType := C.tinygo_clang_getCursorType(c)
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
return C.CXChildVisit_Continue // not supported
}
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
fn := &functionInfo{
pos: pos,
}
p.functions[name] = fn
for i := 0; i < numArgs; i++ {
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
argType := C.clang_getArgType(cursorType, C.uint(i))
if argName == "" {
argName = "$" + strconv.Itoa(i)
}
fn.args = append(fn.args, paramInfo{
name: argName,
typeExpr: p.makeASTType(argType, pos),
})
}
resultType := C.tinygo_clang_getCursorResultType(c)
if resultType.kind != C.CXType_Void {
fn.results = &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: p.makeASTType(resultType, pos),
},
},
}
}
case C.CXCursor_StructDecl:
typ := C.tinygo_clang_getCursorType(c)
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := p.missingSymbols["struct_"+name]; !required {
return C.CXChildVisit_Continue
}
p.makeASTType(typ, pos)
case C.CXCursor_TypedefDecl:
typedefType := C.tinygo_clang_getCursorType(c)
name := getString(C.clang_getTypedefName(typedefType))
if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
p.makeASTType(typedefType, pos)
case C.CXCursor_VarDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
cursorType := C.tinygo_clang_getCursorType(c)
p.globals[name] = globalInfo{
typeExpr: p.makeASTType(cursorType, pos),
pos: pos,
}
case C.CXCursor_MacroDefinition:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
sourceRange := C.tinygo_clang_getCursorExtent(c)
start := C.clang_getRangeStart(sourceRange)
end := C.clang_getRangeEnd(sourceRange)
var file, endFile C.CXFile
var startOffset, endOffset C.unsigned
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
if file == nil {
p.addError(pos, "internal error: could not find file where macro is defined")
break
}
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
if file != endFile {
p.addError(pos, "internal error: expected start and end location of a macro to be in the same file")
break
}
if startOffset > endOffset {
p.addError(pos, "internal error: start offset of macro is after end offset")
break
}
// read file contents and extract the relevant byte range
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
if endOffset >= C.uint(size) {
p.addError(pos, "internal error: end offset of macro lies after end of file")
break
}
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
if !strings.HasPrefix(source, name) {
p.addError(pos, fmt.Sprintf("internal error: expected macro value to start with %#v, got %#v", name, source))
break
}
value := source[len(name):]
// Try to convert this #define into a Go constant expression.
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.
p.constants[name] = constantInfo{expr, pos}
}
case C.CXCursor_EnumDecl:
// Visit all enums, because the fields may be used even when the enum
// type itself is not.
typ := C.tinygo_clang_getCursorType(c)
p.makeASTType(typ, pos)
}
return C.CXChildVisit_Continue
}
func getString(clangString C.CXString) (s string) {
rawString := C.clang_getCString(clangString)
s = C.GoString(rawString)
C.clang_disposeString(clangString)
return
}
// getCursorPosition returns a usable token.Pos from a libclang cursor.
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
return p.getClangLocationPosition(C.tinygo_clang_getCursorLocation(cursor), C.tinygo_clang_Cursor_getTranslationUnit(cursor))
}
// getClangLocationPosition returns a usable token.Pos based on a libclang
// location and translation unit. If the file for this cursor has not been seen
// before, it is read from libclang (which already has the file in memory) and
// added to the token.FileSet.
func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.CXTranslationUnit) token.Pos {
var file C.CXFile
var line C.unsigned
var column C.unsigned
var offset C.unsigned
C.clang_getExpansionLocation(location, &file, &line, &column, &offset)
if line == 0 || file == nil {
// Invalid token.
return token.NoPos
}
filename := getString(C.clang_getFileName(file))
if _, ok := p.tokenFiles[filename]; !ok {
// File has not been seen before in this package, add line information
// now by reading the file from libclang.
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
lines := []int{0}
for i := 0; i < len(source)-1; i++ {
if source[i] == '\n' {
lines = append(lines, i+1)
}
}
f := p.fset.AddFile(filename, -1, int(size))
f.SetLines(lines)
p.tokenFiles[filename] = f
}
positionFile := p.tokenFiles[filename]
// Check for alternative line/column information (set with a line directive).
var filename2String C.CXString
var line2 C.unsigned
var column2 C.unsigned
C.clang_getPresumedLocation(location, &filename2String, &line2, &column2)
filename2 := getString(filename2String)
if filename2 != filename || line2 != line || column2 != column {
// The location was changed with a preprocessor directive.
// TODO: this only works for locations that are added in order. Adding
// line/column info to a file that already has line/column info after
// the given offset is ignored.
positionFile.AddLineColumnInfo(int(offset), filename2, int(line2), int(column2))
}
return positionFile.Pos(int(offset))
}
// addError is a utility function to add an error to the list of errors. It will
// convert the token position to a line/column position first, and call
// addErrorAt.
func (p *cgoPackage) addError(pos token.Pos, msg string) {
p.addErrorAt(p.fset.PositionFor(pos, true), msg)
}
// addErrorAfter is like addError, but adds the text `after` to the source
// location.
func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
position := p.fset.PositionFor(pos, true)
lines := strings.Split(after, "\n")
if len(lines) != 1 {
// Adjust lines.
// For why we can't just do pos+token.Pos(len(after)), see:
// https://github.com/golang/go/issues/35803
position.Line += len(lines) - 1
position.Column = len(lines[len(lines)-1]) + 1
} else {
position.Column += len(after)
}
p.addErrorAt(position, msg)
}
// addErrorAt is a utility function to add an error to the list of errors.
func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
if filepath.IsAbs(position.Filename) {
// Relative paths for readability, like other Go parser errors.
relpath, err := filepath.Rel(p.dir, position.Filename)
if err == nil {
position.Filename = relpath
}
}
p.errors = append(p.errors, scanner.Error{
Pos: position,
Msg: msg,
})
}
// makeASTType return the ast.Expr for the given libclang type. In other words,
// it converts a libclang type to a type in the Go AST.
func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
var typeName string
switch typ.kind {
case C.CXType_Char_S, C.CXType_Char_U:
typeName = "C.char"
case C.CXType_SChar:
typeName = "C.schar"
case C.CXType_UChar:
typeName = "C.uchar"
case C.CXType_Short:
typeName = "C.short"
case C.CXType_UShort:
typeName = "C.ushort"
case C.CXType_Int:
typeName = "C.int"
case C.CXType_UInt:
typeName = "C.uint"
case C.CXType_Long:
typeName = "C.long"
case C.CXType_ULong:
typeName = "C.ulong"
case C.CXType_LongLong:
typeName = "C.longlong"
case C.CXType_ULongLong:
typeName = "C.ulonglong"
case C.CXType_Bool:
typeName = "bool"
case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
switch C.clang_Type_getSizeOf(typ) {
case 4:
typeName = "float32"
case 8:
typeName = "float64"
default:
// Don't do anything, rely on the fallback code to show a somewhat
// sensible error message like "undeclared name: C.long double".
}
case C.CXType_Complex:
switch C.clang_Type_getSizeOf(typ) {
case 8:
typeName = "complex64"
case 16:
typeName = "complex128"
}
case C.CXType_Pointer:
pointeeType := C.clang_getPointeeType(typ)
if pointeeType.kind == C.CXType_Void {
// void* type is translated to Go as unsafe.Pointer
return &ast.SelectorExpr{
X: &ast.Ident{
NamePos: pos,
Name: "unsafe",
},
Sel: &ast.Ident{
NamePos: pos,
Name: "Pointer",
},
}
}
return &ast.StarExpr{
Star: pos,
X: p.makeASTType(pointeeType, pos),
}
case C.CXType_ConstantArray:
return &ast.ArrayType{
Lbrack: pos,
Len: &ast.BasicLit{
ValuePos: pos,
Kind: token.INT,
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
},
Elt: p.makeASTType(C.clang_getElementType(typ), pos),
}
case C.CXType_FunctionProto:
// Be compatible with gc, which uses the *[0]byte type for function
// pointer types.
// Return type [0]byte because this is a function type, not a pointer to
// this function type.
return &ast.ArrayType{
Lbrack: pos,
Len: &ast.BasicLit{
ValuePos: pos,
Kind: token.INT,
Value: "0",
},
Elt: &ast.Ident{
NamePos: pos,
Name: "byte",
},
}
case C.CXType_Typedef:
name := getString(C.clang_getTypedefName(typ))
if _, ok := p.typedefs[name]; !ok {
p.typedefs[name] = nil // don't recurse
c := C.tinygo_clang_getTypeDeclaration(typ)
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
expr := p.makeASTType(underlyingType, pos)
if strings.HasPrefix(name, "_Cgo_") {
expr := expr.(*ast.Ident)
typeSize := C.clang_Type_getSizeOf(underlyingType)
switch expr.Name {
case "C.char":
if typeSize != 1 {
// This happens for some very special purpose architectures
// (DSPs etc.) that are not currently targeted.
// https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
p.addError(pos, fmt.Sprintf("unknown char width: %d", typeSize))
}
switch underlyingType.kind {
case C.CXType_Char_S:
expr.Name = "int8"
case C.CXType_Char_U:
expr.Name = "uint8"
}
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
switch typeSize {
case 1:
expr.Name = "int8"
case 2:
expr.Name = "int16"
case 4:
expr.Name = "int32"
case 8:
expr.Name = "int64"
}
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
switch typeSize {
case 1:
expr.Name = "uint8"
case 2:
expr.Name = "uint16"
case 4:
expr.Name = "uint32"
case 8:
expr.Name = "uint64"
}
}
}
p.typedefs[name] = &typedefInfo{
typeExpr: expr,
pos: pos,
}
}
return &ast.Ident{
NamePos: pos,
Name: "C." + name,
}
case C.CXType_Elaborated:
underlying := C.clang_Type_getNamedType(typ)
switch underlying.kind {
case C.CXType_Record:
return p.makeASTType(underlying, pos)
case C.CXType_Enum:
return p.makeASTType(underlying, pos)
default:
typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind))
p.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
typeName = "<unknown>"
}
case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
var cgoRecordPrefix string
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
cgoRecordPrefix = "struct_"
case C.CXCursor_UnionDecl:
cgoRecordPrefix = "union_"
default:
// makeASTRecordType will create an appropriate error.
cgoRecordPrefix = "record_"
}
if name == "" {
// Anonymous record, probably inside a typedef.
typeInfo := p.makeASTRecordType(cursor, pos)
if typeInfo.bitfields != nil || typeInfo.unionSize != 0 {
// This record is a union or is a struct with bitfields, so we
// have to declare it as a named type (for getters/setters to
// work).
p.anonStructNum++
cgoName := cgoRecordPrefix + strconv.Itoa(p.anonStructNum)
p.elaboratedTypes[cgoName] = typeInfo
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
}
return typeInfo.typeExpr
} else {
cgoName := cgoRecordPrefix + name
if _, ok := p.elaboratedTypes[cgoName]; !ok {
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
p.elaboratedTypes[cgoName] = p.makeASTRecordType(cursor, pos)
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
}
case C.CXType_Enum:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
underlying := C.tinygo_clang_getEnumDeclIntegerType(cursor)
if name == "" {
// anonymous enum
ref := storedRefs.Put(p)
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
return p.makeASTType(underlying, pos)
} else {
// named enum
if _, ok := p.enums[name]; !ok {
ref := storedRefs.Put(p)
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
p.enums[name] = enumInfo{
typeExpr: p.makeASTType(underlying, pos),
pos: pos,
}
}
return &ast.Ident{
NamePos: pos,
Name: "C.enum_" + name,
}
}
}
if typeName == "" {
// Report this as an error.
typeSpelling := getString(C.clang_getTypeSpelling(typ))
typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind))
p.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling))
typeName = "C.<unknown>"
}
return &ast.Ident{
NamePos: pos,
Name: typeName,
}
}
// makeASTRecordType parses a C record (struct or union) and translates it into
// a Go struct type.
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo {
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
}
var bitfieldList []bitfieldInfo
inBitfield := false
bitfieldNum := 0
ref := storedRefs.Put(struct {
fieldList *ast.FieldList
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
}{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList})
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
renameFieldKeywords(fieldList)
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
return &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
bitfields: bitfieldList,
}
case C.CXCursor_UnionDecl:
typeInfo := &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
bitfields: bitfieldList,
}
if len(fieldList.List) <= 1 {
// Useless union, treat it as a regular struct.
return typeInfo
}
if bitfieldList != nil {
// This is valid C... but please don't do this.
p.addError(pos, "bitfield in a union is not supported")
}
typ := C.tinygo_clang_getCursorType(cursor)
alignInBytes := int64(C.clang_Type_getAlignOf(typ))
sizeInBytes := int64(C.clang_Type_getSizeOf(typ))
if sizeInBytes == 0 {
p.addError(pos, "zero-length union is not supported")
}
typeInfo.unionSize = sizeInBytes
typeInfo.unionAlign = alignInBytes
return typeInfo
default:
cursorKind := C.tinygo_clang_getCursorKind(cursor)
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
p.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling))
return &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
},
pos: pos,
}
}
}
//export tinygo_clang_struct_visitor
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
fieldList *ast.FieldList
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
})
fieldList := passed.fieldList
p := passed.pkg
inBitfield := passed.inBitfield
bitfieldNum := passed.bitfieldNum
bitfieldList := passed.bitfieldList
pos := p.getCursorPosition(c)
switch cursorKind := C.tinygo_clang_getCursorKind(c); cursorKind {
case C.CXCursor_FieldDecl:
// Expected. This is a regular field.
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
// Ignore. The next field will be the struct/union itself.
return C.CXChildVisit_Continue
default:
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
p.addError(pos, fmt.Sprintf("expected FieldDecl in struct or union, not %s", cursorKindSpelling))
return C.CXChildVisit_Continue
}
name := getString(C.tinygo_clang_getCursorSpelling(c))
if name == "" {
// Assume this is a bitfield of 0 bits.
// Warning: this is not necessarily true!
return C.CXChildVisit_Continue
}
typ := C.tinygo_clang_getCursorType(c)
field := &ast.Field{
Type: p.makeASTType(typ, p.getCursorPosition(c)),
}
offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name)))
alignOf := int64(C.clang_Type_getAlignOf(typ) * 8)
bitfieldOffset := offsetof % alignOf
if bitfieldOffset != 0 {
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
p.addError(pos, "expected a bitfield")
return C.CXChildVisit_Continue
}
if !*inBitfield {
*bitfieldNum++
}
bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum)
prevField := fieldList.List[len(fieldList.List)-1]
if !*inBitfield {
// The previous element also was a bitfield, but wasn't noticed
// then. Add it now.
*inBitfield = true
*bitfieldList = append(*bitfieldList, bitfieldInfo{
field: prevField,
name: prevField.Names[0].Name,
startBit: 0,
pos: prevField.Names[0].NamePos,
})
prevField.Names[0].Name = bitfieldName
prevField.Names[0].Obj.Name = bitfieldName
}
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
prevBitfield.endBit = bitfieldOffset
*bitfieldList = append(*bitfieldList, bitfieldInfo{
field: prevField,
name: name,
startBit: bitfieldOffset,
pos: pos,
})
return C.CXChildVisit_Continue
}
*inBitfield = false
field.Names = []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: name,
Obj: &ast.Object{
Kind: ast.Var,
Name: name,
Decl: field,
},
},
}
fieldList.List = append(fieldList.List, field)
return C.CXChildVisit_Continue
}
//export tinygo_clang_enum_visitor
func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
name := getString(C.tinygo_clang_getCursorSpelling(c))
pos := p.getCursorPosition(c)
value := C.tinygo_clang_getEnumConstantDeclValue(c)
p.constants[name] = constantInfo{
expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)},
pos: pos,
}
return C.CXChildVisit_Continue
}
+14
View File
@@ -0,0 +1,14 @@
// +build !byollvm
// +build !llvm9
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
*/
import "C"
+14
View File
@@ -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"
+70
View File
@@ -0,0 +1,70 @@
// This file implements some small trampoline functions. The signatures
// are slightly different from the ones defined in libclang.go, but they
// should be ABI compatible.
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
return clang_getTranslationUnitCursor(tu);
}
unsigned tinygo_clang_visitChildren(CXCursor parent, CXCursorVisitor visitor, CXClientData client_data) {
return clang_visitChildren(parent, visitor, client_data);
}
CXString tinygo_clang_getCursorSpelling(CXCursor c) {
return clang_getCursorSpelling(c);
}
enum CXCursorKind tinygo_clang_getCursorKind(CXCursor c) {
return clang_getCursorKind(c);
}
CXType tinygo_clang_getCursorType(CXCursor c) {
return clang_getCursorType(c);
}
CXCursor tinygo_clang_getTypeDeclaration(CXType t) {
return clang_getTypeDeclaration(t);
}
CXType tinygo_clang_getTypedefDeclUnderlyingType(CXCursor c) {
return clang_getTypedefDeclUnderlyingType(c);
}
CXType tinygo_clang_getCursorResultType(CXCursor c) {
return clang_getCursorResultType(c);
}
int tinygo_clang_Cursor_getNumArguments(CXCursor c) {
return clang_Cursor_getNumArguments(c);
}
CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
return clang_Cursor_getArgument(c, i);
}
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
return clang_getCursorLocation(c);
}
CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
return clang_getCursorExtent(c);
}
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
return clang_Cursor_getTranslationUnit(c);
}
long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) {
return clang_getEnumConstantDeclValue(c);
}
CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
return clang_getEnumDeclIntegerType(c);
}
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
return clang_Cursor_isBitField(c);
}
+301
View File
@@ -0,0 +1,301 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file has been copied from the Go 1.13 release tree.
// Checking of compiler and linker flags.
// We must avoid flags like -fplugin=, which can allow
// arbitrary code execution during the build.
// Do not make changes here without carefully
// considering the implications.
// (That's why the code is isolated in a file named security.go.)
//
// Note that -Wl,foo means split foo on commas and pass to
// the linker, so that -Wl,-foo,bar means pass -foo bar to
// the linker. Similarly -Wa,foo for the assembler and so on.
// If any of these are permitted, the wildcard portion must
// disallow commas.
//
// Note also that GNU binutils accept any argument @foo
// as meaning "read more flags from the file foo", so we must
// guard against any command-line argument beginning with @,
// even things like "-I @foo".
// We use safeArg (which is even more conservative)
// to reject these.
//
// Even worse, gcc -I@foo (one arg) turns into cc1 -I @foo (two args),
// so although gcc doesn't expand the @foo, cc1 will.
// So out of paranoia, we reject @ at the beginning of every
// flag argument that might be split into its own argument.
package cgo
import (
"fmt"
"os"
"regexp"
"strings"
"unicode/utf8"
)
var re = regexp.MustCompile
var validCompilerFlags = []*regexp.Regexp{
re(`-D([A-Za-z_].*)`),
re(`-F([^@\-].*)`),
re(`-I([^@\-].*)`),
re(`-O`),
re(`-O([^@\-].*)`),
re(`-W`),
re(`-W([^@,]+)`), // -Wall but not -Wa,-foo.
re(`-Wa,-mbig-obj`),
re(`-Wp,-D([A-Za-z_].*)`),
re(`-ansi`),
re(`-f(no-)?asynchronous-unwind-tables`),
re(`-f(no-)?blocks`),
re(`-f(no-)builtin-[a-zA-Z0-9_]*`),
re(`-f(no-)?common`),
re(`-f(no-)?constant-cfstrings`),
re(`-fdiagnostics-show-note-include-stack`),
re(`-f(no-)?eliminate-unused-debug-types`),
re(`-f(no-)?exceptions`),
re(`-f(no-)?fast-math`),
re(`-f(no-)?inline-functions`),
re(`-finput-charset=([^@\-].*)`),
re(`-f(no-)?fat-lto-objects`),
re(`-f(no-)?keep-inline-dllexport`),
re(`-f(no-)?lto`),
re(`-fmacro-backtrace-limit=(.+)`),
re(`-fmessage-length=(.+)`),
re(`-f(no-)?modules`),
re(`-f(no-)?objc-arc`),
re(`-f(no-)?objc-nonfragile-abi`),
re(`-f(no-)?objc-legacy-dispatch`),
re(`-f(no-)?omit-frame-pointer`),
re(`-f(no-)?openmp(-simd)?`),
re(`-f(no-)?permissive`),
re(`-f(no-)?(pic|PIC|pie|PIE)`),
re(`-f(no-)?plt`),
re(`-f(no-)?rtti`),
re(`-f(no-)?split-stack`),
re(`-f(no-)?stack-(.+)`),
re(`-f(no-)?strict-aliasing`),
re(`-f(un)signed-char`),
re(`-f(no-)?use-linker-plugin`), // safe if -B is not used; we don't permit -B
re(`-f(no-)?visibility-inlines-hidden`),
re(`-fsanitize=(.+)`),
re(`-ftemplate-depth-(.+)`),
re(`-fvisibility=(.+)`),
re(`-g([^@\-].*)?`),
re(`-m32`),
re(`-m64`),
re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
re(`-m(no-)?v?aes`),
re(`-marm`),
re(`-m(no-)?avx[0-9a-z]*`),
re(`-mfloat-abi=([^@\-].*)`),
re(`-mfpmath=[0-9a-z,+]*`),
re(`-m(no-)?avx[0-9a-z.]*`),
re(`-m(no-)?ms-bitfields`),
re(`-m(no-)?stack-(.+)`),
re(`-mmacosx-(.+)`),
re(`-mios-simulator-version-min=(.+)`),
re(`-miphoneos-version-min=(.+)`),
re(`-mtvos-simulator-version-min=(.+)`),
re(`-mtvos-version-min=(.+)`),
re(`-mwatchos-simulator-version-min=(.+)`),
re(`-mwatchos-version-min=(.+)`),
re(`-mnop-fun-dllimport`),
re(`-m(no-)?sse[0-9.]*`),
re(`-m(no-)?ssse3`),
re(`-mthumb(-interwork)?`),
re(`-mthreads`),
re(`-mwindows`),
re(`--param=ssp-buffer-size=[0-9]*`),
re(`-pedantic(-errors)?`),
re(`-pipe`),
re(`-pthread`),
re(`-?-std=([^@\-].*)`),
re(`-?-stdlib=([^@\-].*)`),
re(`--sysroot=([^@\-].*)`),
re(`-w`),
re(`-x([^@\-].*)`),
re(`-v`),
}
var validCompilerFlagsWithNextArg = []string{
"-arch",
"-D",
"-I",
"-framework",
"-isysroot",
"-isystem",
"--sysroot",
"-target",
"-x",
}
var validLinkerFlags = []*regexp.Regexp{
re(`-F([^@\-].*)`),
re(`-l([^@\-].*)`),
re(`-L([^@\-].*)`),
re(`-O`),
re(`-O([^@\-].*)`),
re(`-f(no-)?(pic|PIC|pie|PIE)`),
re(`-f(no-)?openmp(-simd)?`),
re(`-fsanitize=([^@\-].*)`),
re(`-flat_namespace`),
re(`-g([^@\-].*)?`),
re(`-headerpad_max_install_names`),
re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
re(`-mfloat-abi=([^@\-].*)`),
re(`-mmacosx-(.+)`),
re(`-mios-simulator-version-min=(.+)`),
re(`-miphoneos-version-min=(.+)`),
re(`-mthreads`),
re(`-mwindows`),
re(`-(pic|PIC|pie|PIE)`),
re(`-pthread`),
re(`-rdynamic`),
re(`-shared`),
re(`-?-static([-a-z0-9+]*)`),
re(`-?-stdlib=([^@\-].*)`),
re(`-v`),
// Note that any wildcards in -Wl need to exclude comma,
// since -Wl splits its argument at commas and passes
// them all to the linker uninterpreted. Allowing comma
// in a wildcard would allow tunnelling arbitrary additional
// linker arguments through one of these.
re(`-Wl,--(no-)?allow-multiple-definition`),
re(`-Wl,--(no-)?allow-shlib-undefined`),
re(`-Wl,--(no-)?as-needed`),
re(`-Wl,-Bdynamic`),
re(`-Wl,-berok`),
re(`-Wl,-Bstatic`),
re(`-WL,-O([^@,\-][^,]*)?`),
re(`-Wl,-d[ny]`),
re(`-Wl,--disable-new-dtags`),
re(`-Wl,-e[=,][a-zA-Z0-9]*`),
re(`-Wl,--enable-new-dtags`),
re(`-Wl,--end-group`),
re(`-Wl,--(no-)?export-dynamic`),
re(`-Wl,-framework,[^,@\-][^,]+`),
re(`-Wl,-headerpad_max_install_names`),
re(`-Wl,--no-undefined`),
re(`-Wl,-R([^@\-][^,@]*$)`),
re(`-Wl,--just-symbols[=,]([^,@\-][^,@]+)`),
re(`-Wl,-rpath(-link)?[=,]([^,@\-][^,]+)`),
re(`-Wl,-s`),
re(`-Wl,-search_paths_first`),
re(`-Wl,-sectcreate,([^,@\-][^,]+),([^,@\-][^,]+),([^,@\-][^,]+)`),
re(`-Wl,--start-group`),
re(`-Wl,-?-static`),
re(`-Wl,-?-subsystem,(native|windows|console|posix|xbox)`),
re(`-Wl,-syslibroot[=,]([^,@\-][^,]+)`),
re(`-Wl,-undefined[=,]([^,@\-][^,]+)`),
re(`-Wl,-?-unresolved-symbols=[^,]+`),
re(`-Wl,--(no-)?warn-([^,]+)`),
re(`-Wl,-z,(no)?execstack`),
re(`-Wl,-z,relro`),
re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o)
re(`\./.*\.(a|o|obj|dll|dylib|so)`),
}
var validLinkerFlagsWithNextArg = []string{
"-arch",
"-F",
"-l",
"-L",
"-framework",
"-isysroot",
"--sysroot",
"-target",
"-Wl,-framework",
"-Wl,-rpath",
"-Wl,-R",
"-Wl,--just-symbols",
"-Wl,-undefined",
}
func checkCompilerFlags(name string, list []string) error {
return checkFlags(name, list, validCompilerFlags, validCompilerFlagsWithNextArg)
}
func checkLinkerFlags(name string, list []string) error {
return checkFlags(name, list, validLinkerFlags, validLinkerFlagsWithNextArg)
}
func checkFlags(name string, list []string, valid []*regexp.Regexp, validNext []string) error {
// Let users override rules with $CGO_CFLAGS_ALLOW, $CGO_CFLAGS_DISALLOW, etc.
var (
allow *regexp.Regexp
disallow *regexp.Regexp
)
if env := os.Getenv("CGO_" + name + "_ALLOW"); env != "" {
r, err := regexp.Compile(env)
if err != nil {
return fmt.Errorf("parsing $CGO_%s_ALLOW: %v", name, err)
}
allow = r
}
if env := os.Getenv("CGO_" + name + "_DISALLOW"); env != "" {
r, err := regexp.Compile(env)
if err != nil {
return fmt.Errorf("parsing $CGO_%s_DISALLOW: %v", name, err)
}
disallow = r
}
Args:
for i := 0; i < len(list); i++ {
arg := list[i]
if disallow != nil && disallow.FindString(arg) == arg {
goto Bad
}
if allow != nil && allow.FindString(arg) == arg {
continue Args
}
for _, re := range valid {
if re.FindString(arg) == arg { // must be complete match
continue Args
}
}
for _, x := range validNext {
if arg == x {
if i+1 < len(list) && safeArg(list[i+1]) {
i++
continue Args
}
// Permit -Wl,-framework -Wl,name.
if i+1 < len(list) &&
strings.HasPrefix(arg, "-Wl,") &&
strings.HasPrefix(list[i+1], "-Wl,") &&
safeArg(list[i+1][4:]) &&
!strings.Contains(list[i+1][4:], ",") {
i++
continue Args
}
if i+1 < len(list) {
return fmt.Errorf("invalid flag: %s %s (see https://golang.org/s/invalidflag)", arg, list[i+1])
}
return fmt.Errorf("invalid flag: %s without argument (see https://golang.org/s/invalidflag)", arg)
}
}
Bad:
return fmt.Errorf("invalid flag: %s", arg)
}
return nil
}
func safeArg(name string) bool {
if name == "" {
return false
}
c := name[0]
return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf
}
+260
View File
@@ -0,0 +1,260 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file has been copied from the Go 1.13 release tree.
package cgo
import (
"os"
"testing"
)
var goodCompilerFlags = [][]string{
{"-DFOO"},
{"-Dfoo=bar"},
{"-F/Qt"},
{"-I/"},
{"-I/etc/passwd"},
{"-I."},
{"-O"},
{"-O2"},
{"-Osmall"},
{"-W"},
{"-Wall"},
{"-fobjc-arc"},
{"-fno-objc-arc"},
{"-fomit-frame-pointer"},
{"-fno-omit-frame-pointer"},
{"-fpic"},
{"-fno-pic"},
{"-fPIC"},
{"-fno-PIC"},
{"-fpie"},
{"-fno-pie"},
{"-fPIE"},
{"-fno-PIE"},
{"-fsplit-stack"},
{"-fno-split-stack"},
{"-fstack-xxx"},
{"-fno-stack-xxx"},
{"-fsanitize=hands"},
{"-g"},
{"-ggdb"},
{"-march=souza"},
{"-mcpu=123"},
{"-mfpu=123"},
{"-mtune=happybirthday"},
{"-mstack-overflow"},
{"-mno-stack-overflow"},
{"-mmacosx-version"},
{"-mnop-fun-dllimport"},
{"-pthread"},
{"-std=c99"},
{"-xc"},
{"-D", "FOO"},
{"-D", "foo=bar"},
{"-I", "."},
{"-I", "/etc/passwd"},
{"-I", "世界"},
{"-framework", "Chocolate"},
{"-x", "c"},
{"-v"},
}
var badCompilerFlags = [][]string{
{"-D@X"},
{"-D-X"},
{"-F@dir"},
{"-F-dir"},
{"-I@dir"},
{"-I-dir"},
{"-O@1"},
{"-Wa,-foo"},
{"-W@foo"},
{"-g@gdb"},
{"-g-gdb"},
{"-march=@dawn"},
{"-march=-dawn"},
{"-std=@c99"},
{"-std=-c99"},
{"-x@c"},
{"-x-c"},
{"-D", "@foo"},
{"-D", "-foo"},
{"-I", "@foo"},
{"-I", "-foo"},
{"-framework", "-Caffeine"},
{"-framework", "@Home"},
{"-x", "--c"},
{"-x", "@obj"},
}
func TestCheckCompilerFlags(t *testing.T) {
for _, f := range goodCompilerFlags {
if err := checkCompilerFlags("test", f); err != nil {
t.Errorf("unexpected error for %q: %v", f, err)
}
}
for _, f := range badCompilerFlags {
if err := checkCompilerFlags("test", f); err == nil {
t.Errorf("missing error for %q", f)
}
}
}
var goodLinkerFlags = [][]string{
{"-Fbar"},
{"-lbar"},
{"-Lbar"},
{"-fpic"},
{"-fno-pic"},
{"-fPIC"},
{"-fno-PIC"},
{"-fpie"},
{"-fno-pie"},
{"-fPIE"},
{"-fno-PIE"},
{"-fsanitize=hands"},
{"-g"},
{"-ggdb"},
{"-march=souza"},
{"-mcpu=123"},
{"-mfpu=123"},
{"-mtune=happybirthday"},
{"-pic"},
{"-pthread"},
{"-Wl,-rpath,foo"},
{"-Wl,-rpath,$ORIGIN/foo"},
{"-Wl,-R", "/foo"},
{"-Wl,-R", "foo"},
{"-Wl,-R,foo"},
{"-Wl,--just-symbols=foo"},
{"-Wl,--just-symbols,foo"},
{"-Wl,--warn-error"},
{"-Wl,--no-warn-error"},
{"foo.so"},
{"_世界.dll"},
{"./x.o"},
{"libcgosotest.dylib"},
{"-F", "framework"},
{"-l", "."},
{"-l", "/etc/passwd"},
{"-l", "世界"},
{"-L", "framework"},
{"-framework", "Chocolate"},
{"-v"},
{"-Wl,-framework", "-Wl,Chocolate"},
{"-Wl,-framework,Chocolate"},
{"-Wl,-unresolved-symbols=ignore-all"},
}
var badLinkerFlags = [][]string{
{"-DFOO"},
{"-Dfoo=bar"},
{"-W"},
{"-Wall"},
{"-fobjc-arc"},
{"-fno-objc-arc"},
{"-fomit-frame-pointer"},
{"-fno-omit-frame-pointer"},
{"-fsplit-stack"},
{"-fno-split-stack"},
{"-fstack-xxx"},
{"-fno-stack-xxx"},
{"-mstack-overflow"},
{"-mno-stack-overflow"},
{"-mnop-fun-dllimport"},
{"-std=c99"},
{"-xc"},
{"-D", "FOO"},
{"-D", "foo=bar"},
{"-I", "FOO"},
{"-L", "@foo"},
{"-L", "-foo"},
{"-x", "c"},
{"-D@X"},
{"-D-X"},
{"-I@dir"},
{"-I-dir"},
{"-O@1"},
{"-Wa,-foo"},
{"-W@foo"},
{"-g@gdb"},
{"-g-gdb"},
{"-march=@dawn"},
{"-march=-dawn"},
{"-std=@c99"},
{"-std=-c99"},
{"-x@c"},
{"-x-c"},
{"-D", "@foo"},
{"-D", "-foo"},
{"-I", "@foo"},
{"-I", "-foo"},
{"-l", "@foo"},
{"-l", "-foo"},
{"-framework", "-Caffeine"},
{"-framework", "@Home"},
{"-Wl,-framework,-Caffeine"},
{"-Wl,-framework", "-Wl,@Home"},
{"-Wl,-framework", "@Home"},
{"-Wl,-framework,Chocolate,@Home"},
{"-x", "--c"},
{"-x", "@obj"},
{"-Wl,-rpath,@foo"},
{"-Wl,-R,foo,bar"},
{"-Wl,-R,@foo"},
{"-Wl,--just-symbols,@foo"},
{"../x.o"},
}
func TestCheckLinkerFlags(t *testing.T) {
for _, f := range goodLinkerFlags {
if err := checkLinkerFlags("test", f); err != nil {
t.Errorf("unexpected error for %q: %v", f, err)
}
}
for _, f := range badLinkerFlags {
if err := checkLinkerFlags("test", f); err == nil {
t.Errorf("missing error for %q", f)
}
}
}
func TestCheckFlagAllowDisallow(t *testing.T) {
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
t.Fatalf("missing error for -disallow")
}
os.Setenv("CGO_TEST_ALLOW", "-disallo")
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
t.Fatalf("missing error for -disallow with CGO_TEST_ALLOW=-disallo")
}
os.Setenv("CGO_TEST_ALLOW", "-disallow")
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err != nil {
t.Fatalf("unexpected error for -disallow with CGO_TEST_ALLOW=-disallow: %v", err)
}
os.Unsetenv("CGO_TEST_ALLOW")
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err != nil {
t.Fatalf("unexpected error for -Wall: %v", err)
}
os.Setenv("CGO_TEST_DISALLOW", "-Wall")
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall")
}
os.Setenv("CGO_TEST_ALLOW", "-Wall") // disallow wins
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall and CGO_TEST_ALLOW=-Wall")
}
os.Setenv("CGO_TEST_ALLOW", "-fplugin.*")
os.Setenv("CGO_TEST_DISALLOW", "-fplugin=lint.so")
if err := checkCompilerFlags("TEST", []string{"-fplugin=faster.so"}); err != nil {
t.Fatalf("unexpected error for -fplugin=faster.so: %v", err)
}
if err := checkCompilerFlags("TEST", []string{"-fplugin=lint.so"}); err == nil {
t.Fatalf("missing error for -fplugin=lint.so: %v", err)
}
}
+46
View File
@@ -0,0 +1,46 @@
package cgo
import (
"sync"
"unsafe"
)
// #include <stdlib.h>
import "C"
// refMap is a convenient way to store opaque references that can be passed to
// C. It is useful if an API uses function pointers and you cannot pass a Go
// pointer but only a C pointer.
type refMap struct {
refs map[unsafe.Pointer]interface{}
lock sync.Mutex
}
// Put stores a value in the map. It can later be retrieved using Get. It must
// be removed using Remove to avoid memory leaks.
func (m *refMap) Put(v interface{}) unsafe.Pointer {
m.lock.Lock()
defer m.lock.Unlock()
if m.refs == nil {
m.refs = make(map[unsafe.Pointer]interface{}, 1)
}
ref := C.malloc(1)
m.refs[ref] = v
return ref
}
// Get returns a stored value previously inserted with Put. Use the same
// reference as you got from Put.
func (m *refMap) Get(ref unsafe.Pointer) interface{} {
m.lock.Lock()
defer m.lock.Unlock()
return m.refs[ref]
}
// Remove deletes a single reference from the map.
func (m *refMap) Remove(ref unsafe.Pointer) {
m.lock.Lock()
defer m.lock.Unlock()
delete(m.refs, ref)
C.free(ref)
}
+3
View File
@@ -0,0 +1,3 @@
package main
import "C"
+26
View File
@@ -0,0 +1,26 @@
package main
import "unsafe"
var _ unsafe.Pointer
type C.int16_t = int16
type C.int32_t = int32
type C.int64_t = int64
type C.int8_t = int8
type C.uint16_t = uint16
type C.uint32_t = uint32
type C.uint64_t = uint64
type C.uint8_t = uint8
type C.uintptr_t = uintptr
type C.char uint8
type C.int int32
type C.long int32
type C.longlong int64
type C.schar int8
type C.short int16
type C.uchar uint8
type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
+12
View File
@@ -0,0 +1,12 @@
package main
/*
#define foo 3
#define bar foo
*/
import "C"
const (
Foo = C.foo
Bar = C.bar
)
+29
View File
@@ -0,0 +1,29 @@
package main
import "unsafe"
var _ unsafe.Pointer
const C.bar = C.foo
const C.foo = 3
type C.int16_t = int16
type C.int32_t = int32
type C.int64_t = int64
type C.int8_t = int8
type C.uint16_t = uint16
type C.uint32_t = uint32
type C.uint64_t = uint64
type C.uint8_t = uint8
type C.uintptr_t = uintptr
type C.char uint8
type C.int int32
type C.long int32
type C.longlong int64
type C.schar int8
type C.short int16
type C.uchar uint8
type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
+33
View File
@@ -0,0 +1,33 @@
package main
/*
#warning some warning
typedef struct {
int x;
int y;
} point_t;
typedef someType noType; // undefined type
#define SOME_CONST_1 5) // invalid const syntax
#define SOME_CONST_2 6) // const not used (so no error)
#define SOME_CONST_3 1234 // const too large for byte
*/
import "C"
// Make sure that errors for the following lines won't change with future
// additions to the CGo preamble.
//line errors.go:100
var (
// constant too large
_ C.uint8_t = 2 << 10
// z member does not exist
_ C.point_t = C.point_t{z: 3}
// constant has syntax error
_ = C.SOME_CONST_1
_ byte = C.SOME_CONST_3
)
+43
View File
@@ -0,0 +1,43 @@
// CGo errors:
// testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:13:23: unexpected token )
// Type checking errors after CGo processing:
// testdata/errors.go:102: 2 << 10 (untyped int constant 2048) overflows uint8
// testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1
// testdata/errors.go:110: C.SOME_CONST_3 (untyped int constant 1234) overflows byte
package main
import "unsafe"
var _ unsafe.Pointer
const C.SOME_CONST_3 = 1234
type C.int16_t = int16
type C.int32_t = int32
type C.int64_t = int64
type C.int8_t = int8
type C.uint16_t = uint16
type C.uint32_t = uint32
type C.uint64_t = uint64
type C.uint8_t = uint8
type C.uintptr_t = uintptr
type C.char uint8
type C.int int32
type C.long int32
type C.longlong int64
type C.schar int8
type C.short int16
type C.uchar uint8
type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
type C.point_t = struct {
x C.int
y C.int
}
+37
View File
@@ -0,0 +1,37 @@
package main
/*
// this name doesn't exist
#cgo NOFLAGS: -foo
// unknown flag
#cgo CFLAGS: -fdoes-not-exist -DNOTDEFINED
#cgo CFLAGS: -DFOO
#cgo CFLAGS: -Iinclude
#include "foo.h"
#if defined(FOO)
#define BAR 3
#else
#define BAR 5
#endif
#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
)
+34
View File
@@ -0,0 +1,34 @@
// 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
import "unsafe"
var _ unsafe.Pointer
const C.BAR = 3
const C.FOO_H = 1
type C.int16_t = int16
type C.int32_t = int32
type C.int64_t = int64
type C.int8_t = int8
type C.uint16_t = uint16
type C.uint32_t = uint32
type C.uint64_t = uint64
type C.uint8_t = uint8
type C.uintptr_t = uintptr
type C.char uint8
type C.int int32
type C.long int32
type C.longlong int64
type C.schar int8
type C.short int16
type C.uchar uint8
type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
+1
View File
@@ -0,0 +1 @@
#define FOO_H 1
+165
View File
@@ -0,0 +1,165 @@
package main
/*
// Simple typedef.
typedef int myint;
// Structs, with or without name.
typedef struct {
int x;
int y;
} point2d_t;
typedef struct point3d {
int x;
int y;
int z;
} point3d_t;
// Structs with reserved field names.
struct type1 {
// All these fields should be renamed.
int type;
int _type;
int __type;
};
struct type2 {
// This field should not be renamed.
int _type;
};
// Unions.
typedef union {
// Union should be treated as a struct.
int i;
} union1_t;
typedef union {
// Union must contain a single field and have special getters/setters.
int i;
double d;
short s;
} union3_t;
typedef union union2d {
int i;
double d[2];
} union2d_t;
typedef union {
unsigned char arr[10];
} unionarray_t;
// Nested structs and unions.
typedef struct {
point2d_t begin;
point2d_t end;
int tag;
union {
point2d_t area;
point3d_t solid;
} coord;
} struct_nested_t;
typedef union {
point3d_t point;
unionarray_t array;
union3_t thing;
} union_nested_t;
// Enums. These define constant numbers. All these constants must be given the
// correct number.
typedef enum option {
optionA,
optionB,
optionC = -5,
optionD,
optionE = 10,
optionF,
optionG,
} option_t;
enum unused {
unused1 = 5,
};
// Anonymous enum.
typedef enum {
option2A = 20,
} option2_t;
// Various types that are usually translated directly to Go types, but storing
// them in a struct reveals them.
typedef struct {
float f;
double d;
int *ptr;
} types_t;
// Arrays.
typedef int myIntArray[10];
// Bitfields.
typedef struct {
unsigned char start;
unsigned char a : 5;
unsigned char b : 1;
unsigned char c : 2;
unsigned char :0; // new field
unsigned char d : 6;
unsigned char e : 3;
// Note that C++ allows bitfields bigger than the underlying type.
} bitfield_t;
*/
import "C"
var (
// Simple typedefs.
_ C.myint
// Structs.
_ C.point2d_t
_ C.point3d_t
_ C.struct_point3d
// Structs with reserved field names.
_ C.struct_type1
_ C.struct_type2
// Unions.
_ C.union1_t
_ C.union3_t
_ C.union2d_t
_ C.unionarray_t
// Nested structs and unions.
_ C.struct_nested_t
_ C.union_nested_t
// Enums (anonymous and named).
_ C.option_t
_ C.enum_option
_ C.option2_t
// Various types.
_ C.types_t
// Arrays.
_ C.myIntArray
)
// Test bitfield accesses.
func accessBitfields() {
var x C.bitfield_t
x.start = 3
x.set_bitfield_a(4)
x.set_bitfield_b(1)
x.set_bitfield_c(2)
x.d = 10
x.e = 5
var _ C.uchar = x.bitfield_a()
}
// Test union accesses.
func accessUnion() {
var union1 C.union1_t
union1.i = 5
var union2d C.union2d_t
var _ *C.int = union2d.unionfield_i()
var _ *[2]float64 = union2d.unionfield_d()
}
+145
View File
@@ -0,0 +1,145 @@
package main
import "unsafe"
var _ unsafe.Pointer
const C.option2A = 20
const C.optionA = 0
const C.optionB = 1
const C.optionC = -5
const C.optionD = -4
const C.optionE = 10
const C.optionF = 11
const C.optionG = 12
const C.unused1 = 5
type C.int16_t = int16
type C.int32_t = int32
type C.int64_t = int64
type C.int8_t = int8
type C.uint16_t = uint16
type C.uint32_t = uint32
type C.uint64_t = uint64
type C.uint8_t = uint8
type C.uintptr_t = uintptr
type C.char uint8
type C.int int32
type C.long int32
type C.longlong int64
type C.schar int8
type C.short int16
type C.uchar uint8
type C.uint uint32
type C.ulong uint32
type C.ulonglong uint64
type C.ushort uint16
type C.bitfield_t = C.struct_4
type C.myIntArray = [10]C.int
type C.myint = C.int
type C.option2_t = C.uint
type C.option_t = C.enum_option
type C.point2d_t = struct {
x C.int
y C.int
}
type C.point3d_t = C.struct_point3d
type C.struct_nested_t = struct {
begin C.point2d_t
end C.point2d_t
tag C.int
coord C.union_2
}
type C.types_t = struct {
f float32
d float64
ptr *C.int
}
type C.union1_t = struct{ i C.int }
type C.union2d_t = C.union_union2d
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_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) bitfield_c() C.uchar {
return s.__bitfield_1 >> 6
}
func (s *C.struct_4) set_bitfield_c(value C.uchar,
) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 }
type C.struct_4 struct {
start C.uchar
__bitfield_1 C.uchar
d C.uchar
e C.uchar
}
type C.struct_point3d struct {
x C.int
y C.int
z C.int
}
type C.struct_type1 struct {
_type C.int
__type C.int
___type C.int
}
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))
}
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))
}
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))
}
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))
}
type C.union_union2d struct{ $union [2]uint64 }
type C.enum_option C.int
type C.enum_unused C.uint
+300
View File
@@ -0,0 +1,300 @@
// Package compileopts contains the configuration for a single to-be-built
// binary.
package compileopts
import (
"errors"
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
// Config keeps all configuration affecting the build in a single struct.
type Config struct {
Options *Options
Target *TargetSpec
GoMinorVersion int
ClangHeaders string // Clang built-in header include path
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
}
// CPU returns the LLVM CPU name, like atmega328p or arm7tdmi. It may return an
// empty string if the CPU name is not known.
func (c *Config) CPU() string {
return c.Target.CPU
}
// Features returns a list of features this CPU supports. For example, for a
// RISC-V processor, that could be ["+a", "+c", "+m"]. For many targets, an
// empty list will be returned.
func (c *Config) Features() []string {
return c.Target.Features
}
// GOOS returns the GOOS of the target. This might not always be the actual OS:
// for example, bare-metal targets will usually pretend to be linux to get the
// standard library to compile.
func (c *Config) GOOS() string {
return c.Target.GOOS
}
// GOARCH returns the GOARCH of the target. This might not always be the actual
// archtecture: for example, the AVR target is not supported by the Go standard
// library so such targets will usually pretend to be linux/arm.
func (c *Config) GOARCH() string {
return c.Target.GOARCH
}
// BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string {
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
if extraTags := strings.Fields(c.Options.Tags); len(extraTags) != 0 {
tags = append(tags, extraTags...)
}
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", "extalloc", and "conservative".
func (c *Config) GC() string {
if c.Options.GC != "" {
return c.Options.GC
}
if c.Target.GC != "" {
return c.Target.GC
}
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 {
switch c.GC() {
case "conservative", "extalloc":
for _, tag := range c.BuildTags() {
if tag == "baremetal" {
return false
}
}
return true
default:
return false
}
}
// 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
}
if c.Target.Scheduler != "" {
return c.Target.Scheduler
}
// Fall back to coroutines, which are supported everywhere.
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).
func (c *Config) PanicStrategy() string {
return c.Options.PanicStrategy
}
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing.
func (c *Config) CFlags() []string {
cflags := append([]string{}, c.Options.CFlags...)
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
}
// LDFlags returns the flags to pass to the linker. A few more flags are needed
// (like the one for the compiler runtime), but this represents the majority of
// the flags.
func (c *Config) LDFlags() []string {
root := goenv.Get("TINYGOROOT")
// Merge and adjust LDFlags.
ldflags := append([]string{}, c.Options.LDFlags...)
for _, flag := range c.Target.LDFlags {
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
}
ldflags = append(ldflags, "-L", root)
if c.Target.GOARCH == "wasm" {
// Round heap size to next multiple of 65536 (the WebAssembly page
// size).
heapSize := (c.Options.HeapSize + (65536 - 1)) &^ (65536 - 1)
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
}
if c.Target.LinkerScript != "" {
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
}
return ldflags
}
// ExtraFiles returns the list of extra files to be built and linked with the
// executable. This can include extra C and assembly files.
func (c *Config) ExtraFiles() []string {
return c.Target.ExtraFiles
}
// DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only
// enable this for debugging.
func (c *Config) DumpSSA() bool {
return c.Options.DumpSSA
}
// VerifyIR returns whether to run extra checks on the IR. This is normally
// disabled but enabled during testing.
func (c *Config) VerifyIR() bool {
return c.Options.VerifyIR
}
// Debug returns whether to add debug symbols to the IR, for debugging with GDB
// and similar.
func (c *Config) Debug() bool {
return c.Options.Debug
}
// 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.
func (c *Config) Programmer() (method, openocdInterface string) {
switch c.Options.Programmer {
case "":
// No configuration supplied.
return c.Target.FlashMethod, c.Target.OpenOCDInterface
case "openocd", "msd", "command":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface
default:
// The -programmer flag specifies something else, assume it specifies
// the OpenOCD interface name.
return "openocd", c.Options.Programmer
}
}
// OpenOCDConfiguration returns a list of command line arguments to OpenOCD.
// This list of command-line arguments is based on the various OpenOCD-related
// flags in the target specification.
func (c *Config) OpenOCDConfiguration() (args []string, err error) {
_, openocdInterface := c.Programmer()
if openocdInterface == "" {
return nil, errors.New("OpenOCD programmer not set")
}
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(openocdInterface) {
return nil, fmt.Errorf("OpenOCD programmer has an invalid name: %#v", openocdInterface)
}
if c.Target.OpenOCDTarget == "" {
return nil, errors.New("OpenOCD chip not set")
}
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(c.Target.OpenOCDTarget) {
return nil, fmt.Errorf("OpenOCD target has an invalid name: %#v", c.Target.OpenOCDTarget)
}
if c.Target.OpenOCDTransport != "" && c.Target.OpenOCDTransport != "swd" {
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
}
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
if c.Target.OpenOCDTransport != "" {
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
}
args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg")
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
}
+86
View File
@@ -0,0 +1,86 @@
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 {
Target string
Opt string
GC string
PanicStrategy string
Scheduler string
PrintIR bool
DumpSSA bool
VerifyIR bool
Debug bool
PrintSizes string
PrintStacks bool
CFlags []string
LDFlags []string
Tags string
WasmAbi string
HeapSize int64
TestConfig TestConfig
Programmer string
}
// 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)
}
}
})
}
}
+310
View File
@@ -0,0 +1,310 @@
package compileopts
// This file loads a target specification from a JSON file.
import (
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
// Target specification for a given target. Used for bare metal targets.
//
// The target specification is mostly inspired by Rust:
// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html
// https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json
type TargetSpec struct {
Inherits []string `json:"inherits"`
Triple string `json:"llvm-target"`
CPU string `json:"cpu"`
Features []string `json:"features"`
GOOS string `json:"goos"`
GOARCH string `json:"goarch"`
BuildTags []string `json:"build-tags"`
GC string `json:"gc"`
Scheduler string `json:"scheduler"`
Compiler string `json:"compiler"`
Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"`
CFlags []string `json:"cflags"`
LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"`
ExtraFiles []string `json:"extra-files"`
Emulator []string `json:"emulator"`
FlashCommand string `json:"flash-command"`
GDB string `json:"gdb"`
PortReset string `json:"flash-1200-bps-reset"`
FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"`
UF2FamilyID string `json:"uf2-family-id"`
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.
func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
// TODO: simplify this using reflection? Inherits and BuildTags are special
// cases, but the rest can simply be copied if set.
spec.Inherits = append(spec.Inherits, spec2.Inherits...)
if spec2.Triple != "" {
spec.Triple = spec2.Triple
}
if spec2.CPU != "" {
spec.CPU = spec2.CPU
}
spec.Features = append(spec.Features, spec2.Features...)
if spec2.GOOS != "" {
spec.GOOS = spec2.GOOS
}
if spec2.GOARCH != "" {
spec.GOARCH = spec2.GOARCH
}
spec.BuildTags = append(spec.BuildTags, spec2.BuildTags...)
if spec2.GC != "" {
spec.GC = spec2.GC
}
if spec2.Scheduler != "" {
spec.Scheduler = spec2.Scheduler
}
if spec2.Compiler != "" {
spec.Compiler = spec2.Compiler
}
if spec2.Linker != "" {
spec.Linker = spec2.Linker
}
if spec2.RTLib != "" {
spec.RTLib = spec2.RTLib
}
if spec2.Libc != "" {
spec.Libc = spec2.Libc
}
spec.CFlags = append(spec.CFlags, spec2.CFlags...)
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
if spec2.LinkerScript != "" {
spec.LinkerScript = spec2.LinkerScript
}
spec.ExtraFiles = append(spec.ExtraFiles, spec2.ExtraFiles...)
if len(spec2.Emulator) != 0 {
spec.Emulator = spec2.Emulator
}
if spec2.FlashCommand != "" {
spec.FlashCommand = spec2.FlashCommand
}
if spec2.GDB != "" {
spec.GDB = spec2.GDB
}
if spec2.PortReset != "" {
spec.PortReset = spec2.PortReset
}
if spec2.FlashMethod != "" {
spec.FlashMethod = spec2.FlashMethod
}
if spec2.FlashVolume != "" {
spec.FlashVolume = spec2.FlashVolume
}
if spec2.FlashFilename != "" {
spec.FlashFilename = spec2.FlashFilename
}
if spec2.UF2FamilyID != "" {
spec.UF2FamilyID = spec2.UF2FamilyID
}
if spec2.OpenOCDInterface != "" {
spec.OpenOCDInterface = spec2.OpenOCDInterface
}
if spec2.OpenOCDTarget != "" {
spec.OpenOCDTarget = spec2.OpenOCDTarget
}
if spec2.OpenOCDTransport != "" {
spec.OpenOCDTransport = spec2.OpenOCDTransport
}
if spec2.JLinkDevice != "" {
spec.JLinkDevice = spec2.JLinkDevice
}
if spec2.CodeModel != "" {
spec.CodeModel = spec2.CodeModel
}
if spec2.RelocationModel != "" {
spec.RelocationModel = spec2.RelocationModel
}
}
// load reads a target specification from the JSON in the given io.Reader. It
// may load more targets specified using the "inherits" property.
func (spec *TargetSpec) load(r io.Reader) error {
err := json.NewDecoder(r).Decode(spec)
if err != nil {
return err
}
return nil
}
// loadFromGivenStr loads the TargetSpec from the given string that could be:
// - targets/ directory inside the compiler sources
// - a relative or absolute path to custom (project specific) target specification .json file;
// the Inherits[] could contain the files from target folder (ex. stm32f4disco)
// as well as path to custom files (ex. myAwesomeProject.json)
func (spec *TargetSpec) loadFromGivenStr(str string) error {
path := ""
if strings.HasSuffix(str, ".json") {
path, _ = filepath.Abs(str)
} else {
path = filepath.Join(goenv.Get("TINYGOROOT"), "targets", strings.ToLower(str)+".json")
}
fp, err := os.Open(path)
if err != nil {
return err
}
defer fp.Close()
return spec.load(fp)
}
// resolveInherits loads inherited targets, recursively.
func (spec *TargetSpec) resolveInherits() error {
// First create a new spec with all the inherited properties.
newSpec := &TargetSpec{}
for _, name := range spec.Inherits {
subtarget := &TargetSpec{}
err := subtarget.loadFromGivenStr(name)
if err != nil {
return err
}
err = subtarget.resolveInherits()
if err != nil {
return err
}
newSpec.copyProperties(subtarget)
}
// When all properties are loaded, make sure they are properly inherited.
newSpec.copyProperties(spec)
*spec = *newSpec
return nil
}
// Load a target specification.
func LoadTarget(target string) (*TargetSpec, error) {
if target == "" {
// Configure based on GOOS/GOARCH environment variables (falling back to
// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
goos := goenv.Get("GOOS")
goarch := goenv.Get("GOARCH")
llvmos := goos
llvmarch := map[string]string{
"386": "i386",
"amd64": "x86_64",
"arm64": "aarch64",
}[goarch]
if llvmarch == "" {
llvmarch = goarch
}
target = llvmarch + "--" + llvmos
if goarch == "arm" {
target += "-gnueabihf"
}
return defaultTarget(goos, goarch, target)
}
// See whether there is a target specification for this target (e.g.
// Arduino).
spec := &TargetSpec{}
err := spec.loadFromGivenStr(target)
if err == nil {
// Successfully loaded this target from a built-in .json file. Make sure
// it includes all parents as specified in the "inherits" key.
err = spec.resolveInherits()
if err != nil {
return nil, err
}
return spec, nil
} else if !os.IsNotExist(err) {
// Expected a 'file not found' error, got something else. Report it as
// an error.
return nil, err
} else {
// Load target from given triple, ignore GOOS/GOARCH environment
// variables.
tripleSplit := strings.Split(target, "-")
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"
}
goarch := map[string]string{ // map from LLVM arch to Go arch
"i386": "386",
"x86_64": "amd64",
"aarch64": "arm64",
"armv7": "arm",
}[tripleSplit[0]]
if goarch == "" {
goarch = tripleSplit[0]
}
return defaultTarget(goos, goarch, strings.Join(tripleSplit, "-"))
}
}
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
// No target spec available. Use the default one, useful on most systems
// with a regular OS.
spec := TargetSpec{
Triple: triple,
GOOS: goos,
GOARCH: goarch,
BuildTags: []string{goos, goarch},
Compiler: "clang",
Linker: "cc",
CFlags: []string{"--target=" + triple},
GDB: "gdb",
PortReset: "false",
FlashMethod: "native",
}
if goos == "darwin" {
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
} else {
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
}
if goarch != 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"}
}
if goarch == "386" {
spec.CFlags = []string{"-m32"}
spec.LDFlags = []string{"-m32"}
}
}
return &spec, nil
}
+19
View File
@@ -0,0 +1,19 @@
package compileopts
import "testing"
func TestLoadTarget(t *testing.T) {
_, err := LoadTarget("arduino")
if err != nil {
t.Error("LoadTarget test failed:", err)
}
_, err = LoadTarget("notexist")
if err == nil {
t.Error("LoadTarget should have failed with non existing target")
}
if err.Error() != "expected a full LLVM target or a custom target in -target flag" {
t.Error("LoadTarget failed for wrong reason:", err)
}
}
+229
View File
@@ -0,0 +1,229 @@
package compiler
// This file implements functions that do certain safety checks that are
// required by the Go programming language.
import (
"fmt"
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// 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 (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
}
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
// correctly extend that type.
if indexType.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 {
index = b.CreateZExt(index, arrayLen.Type(), "")
} else {
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 = b.CreateZExt(arrayLen, index.Type(), "")
}
// Now do the bounds check: index >= arrayLen
outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "")
b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic")
}
// 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 (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
}
// Extend the capacity integer to be at least as wide as low and high.
capacityType := capacity.Type()
if low.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = low.Type()
}
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = high.Type()
}
if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = max.Type()
}
if capacityType != capacity.Type() {
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 = b.CreateZExt(low, capacityType, "")
} else {
low = b.CreateSExt(low, capacityType, "")
}
}
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if highType.Info()&types.IsUnsigned != 0 {
high = b.CreateZExt(high, capacityType, "")
} else {
high = b.CreateSExt(high, capacityType, "")
}
}
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if maxType.Info()&types.IsUnsigned != 0 {
max = b.CreateZExt(max, capacityType, "")
} else {
max = b.CreateSExt(max, capacityType, "")
}
}
// Now do the bounds check: low > high || high > capacity
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")
}
// 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 (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
}
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.
// 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, "")
// 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
}
}
+200 -58
View File
@@ -1,8 +1,10 @@
package compiler
import (
"github.com/aykevl/go-llvm"
"golang.org/x/tools/go/ssa"
"go/types"
"strconv"
"tinygo.org/x/go-llvm"
)
// For a description of the calling convention in prose, see:
@@ -10,58 +12,102 @@ 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.IsExported() {
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
}
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
// paramter list.
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
// parameter list.
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),
},
}
}
// Equivalent of expandFormalParamType for parameter values.
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
// 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 := b.flattenAggregateTypeOffsets(t)
if len(fields) <= maxFieldsPerParam {
return fields
} else {
// failed to lower
return []uint64{0}
}
default:
// TODO: split small arrays
return []uint64{0}
}
}
// 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
@@ -77,29 +123,124 @@ 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,
},
}
}
}
// 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 {
// 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())
for fieldIndex, field := range t.StructElementTypes() {
suboffsets := c.flattenAggregateTypeOffsets(field)
offset := c.targetData.ElementOffset(t, fieldIndex)
for i := range suboffsets {
suboffsets[i] += offset
}
fields = append(fields, suboffsets...)
}
return fields
default:
return []uint64{0}
}
}
// 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
@@ -108,28 +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 {
value, err := c.getZeroValue(t)
if err != nil {
panic("could not get zero value of struct: " + err.Error())
}
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 {
+253
View File
@@ -0,0 +1,253 @@
package compiler
// This file lowers channel operations (make/send/recv/close) to runtime calls
// or pseudo-operations that are lowered during goroutine lowering.
import (
"go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
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}, "")
}
// 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 := 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.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
// 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
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
// actual channel receive operation during goroutine lowering.
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 := 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 := 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(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
}
}
// createChanClose closes the given channel.
func (b *builder) createChanClose(param ssa.Value) {
ch := b.getValue(param)
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
}
// 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 (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := b.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
b.createRuntimeCall("deadlock", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:
// select {
// default:
// }
retval := llvm.Undef(llvmType)
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "")
return retval // {-1, false}
}
}
// This code create a (stack-allocated) slice containing all the select
// cases and then calls runtime.chanSelect to perform the actual select
// statement.
// Simple selects (blocking and with just one case) are already transformed
// into regular chan operations during SSA construction so we don't have to
// optimize such small selects.
// Go through all the cases. Create the selectStates slice and and
// determine the receive buffer size and alignment.
recvbufSize := uint64(0)
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := b.getValue(state.Chan)
selectState := llvm.ConstNull(chanSelectStateType)
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 := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem())
if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
recvbufSize = size
}
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 := 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")
}
selectStates = append(selectStates, selectState)
}
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.i8ptrType)
if hasReceives {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
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 := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
for i, state := range selectStates {
// Set each slice element to the appropriate channel.
gep := b.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
}, "")
b.CreateStore(state, gep)
}
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(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(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 = b.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
}, "select.result")
// Terminate the lifetime of the operation structures.
b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
} else {
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
}, "select.result")
}
// Terminate the lifetime of the states alloca.
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 b.selectRecvBuf == nil {
b.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
}
b.selectRecvBuf[expr] = recvbuf
return results
}
// getChanSelectResult returns the special values from a *ssa.Extract expression
// when extracting a value from a select statement (*ssa.Select). Because
// *ssa.Select cannot load all values in advance, it does this later in the
// *ssa.Extract expression.
func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
if expr.Index == 0 {
// index
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 := 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 := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0)
ptr := b.CreateBitCast(recvbuf, typ, "")
return b.CreateLoad(ptr, "")
}
}
+1674 -2327
View File
File diff suppressed because it is too large Load Diff
+275 -149
View File
@@ -14,82 +14,112 @@ package compiler
// frames.
import (
"github.com/aykevl/go-llvm"
"github.com/aykevl/tinygo/ir"
"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"
)
// 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.mod.GetTypeByName("runtime._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)
}
// emitDefer emits a single defer instruction, to be run when this function
// isInLoop checks if there is a path from a basic block to itself.
func isInLoop(start *ssa.BasicBlock) bool {
// Use a breadth-first search to scan backwards through the block graph.
queue := []*ssa.BasicBlock{start}
checked := map[*ssa.BasicBlock]struct{}{}
for len(queue) > 0 {
// pop a block off of the queue
block := queue[len(queue)-1]
queue = queue[:len(queue)-1]
// Search through predecessors.
// Searching backwards means that this is pretty fast when the block is close to the start of the function.
// Defers are often placed near the start of the function.
for _, pred := range block.Preds {
if pred == start {
// cycle found
return true
}
if _, ok := checked[pred]; ok {
// block already checked
continue
}
// add to queue and checked map
queue = append(queue, pred)
checked[pred] = struct{}{}
}
}
return false
}
// createDefer emits a single defer instruction, to be run when this function
// returns.
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
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, err := c.parseExpr(frame, instr.Call.Value) // interface
if err != nil {
return err
}
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, err := c.parseExpr(frame, arg)
if err != nil {
return err
}
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, err := c.parseExpr(frame, param)
if err != nil {
return err
}
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -101,61 +131,110 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
// pointer.
// TODO: ignore this closure entirely and put pointers to the free
// variables directly in the defer struct, avoiding a memory allocation.
closure, err := c.parseExpr(frame, instr.Call.Value)
if err != nil {
return err
}
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, err := c.parseExpr(frame, param)
if err != nil {
return err
}
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 {
return c.makeError(instr.Pos(), "todo: defer on uncommon function call type")
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)
deferFrame, err := c.getZeroValue(deferFrameType)
if err != nil {
return err
}
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 alloca.
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
c.builder.CreateStore(deferFrame, alloca)
// Put this struct in an allocation.
var alloca llvm.Value
if !isInLoop(instr.Block()) {
// This can safely use a stack allocation.
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferFrameType, "defer.alloca")
} else {
// This may be hit a variable number of times, so use a heap allocation.
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 b.NeedsStackObjects() {
b.trackPointer(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)
return nil
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) error {
// createRunDefers emits code to run all deferred functions.
func (b *builder) createRunDefers() {
// Add a loop like the following:
// for stack != nil {
// _stack := stack
@@ -172,158 +251,205 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
// }
// Create loop.
loophead := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := llvm.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.CreateGEP(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.CreateGEP(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 := llvm.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.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
for _, arg := range callback.Args {
llvmType, err := c.getLLVMType(arg.Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
valueTypes := []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.CreateGEP(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
fnPtr, _, err := c.getInvokeCall(frame, callback)
if err != nil {
return err
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))
}
c.createCall(fnPtr, forwardParams, "")
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
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.mod.GetTypeByName("runtime._defer"), 0)}
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
for _, param := range callback.Params {
llvmType, err := c.getLLVMType(param.Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
valueTypes = append(valueTypes, 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.CreateGEP(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(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.mod.GetTypeByName("runtime._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++ {
llvmType, err := c.getLLVMType(params.At(i).Type())
if err != nil {
return err
}
valueTypes = append(valueTypes, llvmType)
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.CreateGEP(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)
}
// Call deferred function.
c.createCall(fn.LLVMFn, forwardParams, "")
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Call deferred function.
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)
return nil
b.SetInsertPointAtEnd(end)
}
+51 -1
View File
@@ -1,14 +1,64 @@
package compiler
// This file contains some utility functions related to error handling.
import (
"go/scanner"
"go/token"
"go/types"
"path/filepath"
"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,
Msg: msg,
}
}
func (c *compilerContext) addError(pos token.Pos, msg string) {
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
}
// 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{}
}
}
+168
View File
@@ -0,0 +1,168 @@
package compiler
// This file implements function values and closures. It may need some lowering
// in a later step, see func-lowering.go.
import (
"go/types"
"github.com/tinygo-org/tinygo/compileopts"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// 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 *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
var funcValueScalar llvm.Value
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
case compileopts.FuncValueSwitch:
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
sigGlobal,
})
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
funcValueWithSignatureGlobal.SetGlobalConstant(true)
funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage)
}
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
default:
panic("unimplemented func value variant")
}
funcValueType := c.getFuncType(sig)
funcValue := llvm.Undef(funcValueType)
funcValue = 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 (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 (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 (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")
}
return
}
// getFuncType returns the type of a func value given a signature.
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case compileopts.FuncValueSwitch:
return c.getLLVMRuntimeType("funcValue")
default:
panic("unimplemented func value variant")
}
}
// getRawFuncType returns a LLVM function pointer type for a given signature.
func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// Get the return type.
var returnType llvm.Type
switch typ.Results().Len() {
case 0:
// No return values.
returnType = c.ctx.VoidType()
case 1:
// Just one return value.
returnType = c.getLLVMType(typ.Results().At(0).Type())
default:
// Multiple return values. Put them together in a struct.
// This appears to be the common way to handle multiple return values in
// LLVM.
members := make([]llvm.Type, typ.Results().Len())
for i := 0; i < typ.Results().Len(); i++ {
members[i] = c.getLLVMType(typ.Results().At(i).Type())
}
returnType = c.ctx.StructType(members, false)
}
// Get the parameter types.
var paramTypes []llvm.Type
if typ.Recv() != nil {
recv := c.getLLVMType(typ.Recv().Type())
if recv.StructName() == "runtime._interface" {
// This is a call on an interface, not a concrete type.
// The receiver is not an interface, but a i8* type.
recv = c.i8ptrType
}
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())
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
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
// Make a func type out of the signature.
return llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace)
}
// parseMakeClosure makes a function value (with context) from the given
// closure expression.
func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables")
}
f := b.ir.GetFunction(expr.Fn.(*ssa.Function))
// Collect all bound variables.
boundVars := make([]llvm.Value, len(expr.Bindings))
for i, binding := range expr.Bindings {
// The context stores the bound variables.
llvmBoundVar := b.getValue(binding)
boundVars[i] = llvmBoundVar
}
// Store the bound variables in a single object, allocating it on the heap
// if necessary.
context := b.emitPointerPack(boundVars)
// Create the closure.
return b.createFuncValue(f.LLVMFn, context, f.Signature), nil
}
+112
View File
@@ -0,0 +1,112 @@
package compiler
// This file provides IR transformations necessary for precise and portable
// garbage collectors.
import (
"go/token"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
// one of the expressions that need this.
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.
b.trackPointer(value)
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
if !value.IsNil() {
b.trackValue(value)
}
case *ssa.Select:
if alloca, ok := b.selectRecvBuf[expr]; ok {
if alloca.IsAUndefValue().IsNil() {
b.trackPointer(alloca)
}
}
case *ssa.UnOp:
switch expr.Op {
case token.MUL:
// Pointer dereference.
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).
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 (b *builder) trackValue(value llvm.Value) {
typ := value.Type()
switch typ.TypeKind() {
case llvm.PointerTypeKind:
b.trackPointer(value)
case llvm.StructTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.StructElementTypesCount()
for i := 0; i < numElements; i++ {
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
}
case llvm.ArrayTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
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 (b *builder) trackPointer(value llvm.Value) {
if value.Type() != b.i8ptrType {
value = b.CreateBitCast(value, b.i8ptrType, "")
}
b.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
}
// typeHasPointers returns whether this type is a pointer or contains pointers.
// If the type is an aggregate type, it will check whether there is a pointer
// inside.
func typeHasPointers(t llvm.Type) bool {
switch t.TypeKind() {
case llvm.PointerTypeKind:
return true
case llvm.StructTypeKind:
for _, subType := range t.StructElementTypes() {
if typeHasPointers(subType) {
return true
}
}
return false
case llvm.ArrayTypeKind:
if typeHasPointers(t.ElementType()) {
return true
}
return false
default:
return false
}
}
+175
View File
@@ -0,0 +1,175 @@
package compiler
// This file implements the 'go' keyword to start a new goroutine. See
// goroutine-lowering.go for more details.
import (
"go/token"
"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 (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
paramBundle := b.emitPointerPack(params)
var callee llvm.Value
switch b.Scheduler() {
case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
case "coroutines":
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
default:
panic("unreachable")
}
b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
// createGoroutineStartWrapper creates a wrapper for the task-based
// implementation of goroutines. For example, to call a function like this:
//
// func add(x, y int) int { ... }
//
// It creates a wrapper like this:
//
// func add$gowrapper(ptr *unsafe.Pointer) {
// args := (*struct{
// x, y int
// })(ptr)
// add(args.x, args.y)
// }
//
// This is useful because the task-based goroutine start implementation only
// allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a
// return value.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
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 llvm.ConstPtrToInt(wrapper, c.uintptrType)
}
// 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.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "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 := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
params = append(params, llvm.Undef(c.i8ptrType))
// Create the call.
builder.CreateCall(fn, params, "")
} else {
// For a function pointer like this:
//
// var funcPtr func(x, y int) int
//
// A wrapper like the following is created:
//
// func .gowrapper(ptr *unsafe.Pointer) {
// args := (*struct{
// x, y int
// fn func(x, y int) int
// })(ptr)
// args.fn(x, y)
// }
//
// With a bit of luck, identical wrapper functions like these can be
// merged into one.
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "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 := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
// Get the function pointer.
fnPtr := params[len(params)-1]
// Ignore the last param, which isn't used anymore.
// TODO: avoid this extra "parent handle" parameter in most functions.
params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call.
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.
builder.CreateRetVoid()
// Return a ptrtoint of the wrapper, not the function itself.
return builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
+209
View File
@@ -0,0 +1,209 @@
package compiler
// This file implements inline asm support by calling special functions.
import (
"fmt"
"go/constant"
"regexp"
"strconv"
"strings"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// This is a compiler builtin, which emits a piece of inline assembly with no
// operands or return values. It is useful for trivial instructions, like wfi in
// ARM or sleep in AVR.
//
// func Asm(asm string)
//
// The provided assembly must be a constant.
func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
// Magic function: insert inline assembly instead of calling it.
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 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{}) uintptr
//
// The asm parameter must be a constant string. The regs parameter must be
// provided immediately. For example:
//
// arm.AsmFull(
// "str {value}, {result}",
// map[string]interface{}{
// "value": 1
// "result": &dest,
// })
func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) {
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
registers := map[string]llvm.Value{}
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())
}
}
}
// TODO: handle dollar signs in asm string
registerNumbers := map[string]int{}
var err error
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 = b.makeError(instr.Pos(), "unknown register name: "+name)
}
return s
}
if _, ok := registerNumbers[name]; !ok {
registerNumbers[name] = len(registerNumbers)
argTypes = append(argTypes, registers[name].Type())
args = append(args, registers[name])
switch registers[name].Type().TypeKind() {
case llvm.IntegerTypeKind:
constraints = append(constraints, "r")
case llvm.PointerTypeKind:
constraints = append(constraints, "*m")
default:
err = b.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
return s
}
}
return fmt.Sprintf("${%v}", registerNumbers[name])
})
if err != nil {
return llvm.Value{}, err
}
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)
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
// be one of:
//
// func SVCall0(num uintptr) uintptr
// func SVCall1(num uintptr, a1 interface{}) uintptr
// func SVCall2(num uintptr, a1, a2 interface{}) uintptr
// func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
// func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
//
// The num parameter must be a constant. All other parameters may be any scalar
// value supported by LLVM inline assembly.
func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
asm := "svc #" + strconv.FormatUint(num, 10)
constraints := "={r0}"
for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X
if i == 0 {
constraints += ",0"
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
}
llvmValue := b.getValue(arg)
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
// Implement the ARM calling convention by marking r1-r3 as
// clobbered. r0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{r1},~{r2},~{r3}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
return b.CreateCall(target, llvmArgs, ""), nil
}
// This is a compiler builtin which emits CSR instructions. It can be one of:
//
// func (csr CSR) Get() uintptr
// 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)
}
}
+303 -241
View File
@@ -8,103 +8,242 @@ package compiler
import (
"go/token"
"go/types"
"strconv"
"strings"
"github.com/aykevl/go-llvm"
"github.com/aykevl/tinygo/ir"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// parseMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
// 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, global string, pos token.Pos) (llvm.Value, error) {
var itfValue llvm.Value
size := c.targetData.TypeAllocSize(val.Type())
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
if global != "" {
// Allocate in a global variable.
global := llvm.AddGlobal(c.mod, val.Type(), global+"$itfvalue")
global.SetInitializer(val)
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
itfValueRaw := llvm.ConstInBoundsGEP(global, []llvm.Value{zero, zero})
itfValue = llvm.ConstBitCast(itfValueRaw, c.i8ptrType)
} else {
// Allocate on the heap and put a pointer in the interface.
// TODO: escape analysis.
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "makeinterface.alloc")
itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "makeinterface.cast.value")
c.builder.CreateStore(val, itfValueCast)
itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "makeinterface.cast.i8ptr")
}
} else if size == 0 {
itfValue = llvm.ConstPointerNull(c.i8ptrType)
} else {
// Directly place the value in the interface.
switch val.Type().TypeKind() {
case llvm.IntegerTypeKind:
itfValue = c.builder.CreateIntToPtr(val, c.i8ptrType, "makeinterface.cast.int")
case llvm.PointerTypeKind:
itfValue = c.builder.CreateBitCast(val, c.i8ptrType, "makeinterface.cast.ptr")
case llvm.StructTypeKind:
// A bitcast would be useful here, but bitcast doesn't allow
// aggregate types. So we'll bitcast it using an alloca.
// Hopefully this will get optimized away.
mem := c.builder.CreateAlloca(c.i8ptrType, "makeinterface.cast.struct")
memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(val.Type(), 0), "makeinterface.cast.struct.cast")
c.builder.CreateStore(val, memStructPtr)
itfValue = c.builder.CreateLoad(mem, "makeinterface.cast.load")
default:
return llvm.Value{}, c.makeError(pos, "todo: makeinterface: cast small type to i8*")
}
// 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 := 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)
}
itfTypeCodeGlobal := c.getTypeCode(typ)
itfMethodSetGlobal, err := c.getTypeMethodSet(typ)
if err != nil {
return llvm.Value{}, nil
}
itfTypeCode := c.createRuntimeCall("makeInterface", []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}, "makeinterface.typecode")
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
return itf, nil
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 {
global := c.mod.NamedGlobal(typ.String() + "$type")
func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
globalName := "reflect/types.type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), typ.String()+"$type")
// Create a new typecode global.
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
// Some type classes contain more information for underlying types or
// element types. Store it directly in the typecode global to make
// reflect lowering simpler.
var references llvm.Value
var length int64
switch typ := typ.(type) {
case *types.Named:
references = c.getTypeCode(typ.Underlying())
case *types.Chan:
references = c.getTypeCode(typ.Elem())
case *types.Pointer:
references = c.getTypeCode(typ.Elem())
case *types.Slice:
references = c.getTypeCode(typ.Elem())
case *types.Array:
references = c.getTypeCode(typ.Elem())
length = typ.Len()
case *types.Struct:
// Take a pointer to the typecodeID of the first field (if it exists).
structGlobal := c.makeStructTypeFields(typ)
references = llvm.ConstBitCast(structGlobal, global.Type())
}
if !references.IsNil() {
// Set the 'references' field of the runtime.typecodeID struct.
globalValue := llvm.ConstNull(global.Type().ElementType())
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.PrivateLinkage)
}
global.SetGlobalConstant(true)
}
return global
}
// 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 *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())
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
structGlobalValue := llvm.ConstNull(structGlobalType)
for i := 0; i < typ.NumFields(); i++ {
fieldGlobalValue := llvm.ConstNull(runtimeStructField)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
fieldName.SetLinkage(llvm.PrivateLinkage)
fieldName.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
if typ.Tag(i) != "" {
fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
fieldTag.SetLinkage(llvm.PrivateLinkage)
fieldTag.SetUnnamedAddr(true)
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
}
if typ.Field(i).Embedded() {
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldEmbedded, []uint32{3})
}
structGlobalValue = llvm.ConstInsertValue(structGlobalValue, fieldGlobalValue, []uint32{uint32(i)})
}
structGlobal.SetInitializer(structGlobalValue)
structGlobal.SetUnnamedAddr(true)
structGlobal.SetLinkage(llvm.PrivateLinkage)
return structGlobal
}
// getTypeCodeName returns a name for this type that can be used in the
// interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum.
func getTypeCodeName(t types.Type) string {
switch t := t.(type) {
case *types.Named:
return "named:" + t.String()
case *types.Array:
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
case *types.Basic:
var kind string
switch t.Kind() {
case types.Bool:
kind = "bool"
case types.Int:
kind = "int"
case types.Int8:
kind = "int8"
case types.Int16:
kind = "int16"
case types.Int32:
kind = "int32"
case types.Int64:
kind = "int64"
case types.Uint:
kind = "uint"
case types.Uint8:
kind = "uint8"
case types.Uint16:
kind = "uint16"
case types.Uint32:
kind = "uint32"
case types.Uint64:
kind = "uint64"
case types.Uintptr:
kind = "uintptr"
case types.Float32:
kind = "float32"
case types.Float64:
kind = "float64"
case types.Complex64:
kind = "complex64"
case types.Complex128:
kind = "complex128"
case types.String:
kind = "string"
case types.UnsafePointer:
kind = "unsafeptr"
default:
panic("unknown basic type: " + t.Name())
}
return "basic:" + kind
case *types.Chan:
return "chan:" + getTypeCodeName(t.Elem())
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ {
methods[i] = getTypeCodeName(t.Method(i).Type())
}
return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map:
keyType := getTypeCodeName(t.Key())
elemType := getTypeCodeName(t.Elem())
return "map:" + "{" + keyType + "," + elemType + "}"
case *types.Pointer:
return "pointer:" + getTypeCodeName(t.Elem())
case *types.Signature:
params := make([]string, t.Params().Len())
for i := 0; i < t.Params().Len(); i++ {
params[i] = getTypeCodeName(t.Params().At(i).Type())
}
results := make([]string, t.Results().Len())
for i := 0; i < t.Results().Len(); i++ {
results[i] = getTypeCodeName(t.Results().At(i).Type())
}
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
case *types.Slice:
return "slice:" + getTypeCodeName(t.Elem())
case *types.Struct:
elems := make([]string, t.NumFields())
for i := 0; i < t.NumFields(); i++ {
embedded := ""
if t.Field(i).Embedded() {
embedded = "#"
}
elems[i] = embedded + t.Field(i).Name() + ":" + getTypeCodeName(t.Field(i).Type())
if t.Tag(i) != "" {
elems[i] += "`" + t.Tag(i) + "`"
}
}
return "struct:" + "{" + strings.Join(elems, ",") + "}"
default:
panic("unknown type: " + t.String())
}
}
// getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass.
func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
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() {
// the method set already exists
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
ms := c.ir.Program.MethodSets.MethodSet(typ)
if ms.Len() == 0 {
// no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0)), nil
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
}
methods := make([]llvm.Value, ms.Len())
interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
@@ -113,13 +252,10 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
// compiler error, so panic
panic("cannot find function: " + f.LinkName())
}
fn, err := c.getInterfaceInvokeWrapper(f)
if err != nil {
return llvm.Value{}, err
}
fn := c.getInterfaceInvokeWrapper(f)
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal,
llvm.ConstBitCast(fn, c.i8ptrType),
llvm.ConstPtrToInt(fn, c.uintptrType),
})
methods[i] = methodInfo
}
@@ -129,29 +265,34 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
// getInterfaceMethodSet returns a global variable with the method set of the
// 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
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
// Every method is a *i16 reference indicating the signature of this method.
// Every method is a *i8 reference indicating the signature of this method.
methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods())
for i := range methods {
method := typ.Underlying().(*types.Interface).Method(i)
methods[i] = c.getMethodSignature(method)
}
value := llvm.ConstArray(methods[0].Type(), methods)
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
value := llvm.ConstArray(c.i8ptrType, methods)
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.PrivateLinkage)
@@ -159,9 +300,9 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
}
// getMethodSignature returns a global variable which is a reference to an
// external *i16 indicating the indicating the signature of this method. It is
// 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() {
@@ -171,28 +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, error) {
itf, err := c.parseExpr(frame, expr.X)
if err != nil {
return llvm.Value{}, err
}
assertedType, err := c.getLLVMType(expr.AssertedType)
if err != nil {
return llvm.Value{}, err
}
valueNil, err := c.getZeroValue(assertedType)
if err != nil {
return llvm.Value{}, err
}
func (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.
@@ -203,15 +334,15 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
// 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
@@ -225,15 +356,15 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
// 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
@@ -242,125 +373,83 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
} 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")
size := c.targetData.TypeAllocSize(assertedType)
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
// Value was stored in an allocated buffer, load it from there.
valuePtrCast := c.builder.CreateBitCast(valuePtr, llvm.PointerType(assertedType, 0), "")
valueOk = c.builder.CreateLoad(valuePtrCast, "typeassert.value.ok")
} else if size == 0 {
valueOk, err = c.getZeroValue(assertedType)
if err != nil {
return llvm.Value{}, err
}
} else {
// Value was stored directly in the interface.
switch assertedType.TypeKind() {
case llvm.IntegerTypeKind:
valueOk = c.builder.CreatePtrToInt(valuePtr, assertedType, "typeassert.value.ok")
case llvm.PointerTypeKind:
valueOk = c.builder.CreateBitCast(valuePtr, assertedType, "typeassert.value.ok")
default: // struct, float, etc.
// A bitcast would be useful here, but bitcast doesn't allow
// aggregate types. So we'll bitcast it using an alloca.
// Hopefully this will get optimized away.
mem := c.builder.CreateAlloca(c.i8ptrType, "")
c.builder.CreateStore(valuePtr, mem)
memCast := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "")
valueOk = c.builder.CreateLoad(memCast, "typeassert.value.ok")
}
}
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")
phi.AddIncoming([]llvm.Value{valueNil, valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
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
return tuple, nil
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}, "")
return phi, nil
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
return phi
}
}
// 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,
b.getInterfaceMethodSet(instr.Value.Type()),
b.getMethodSignature(instr.Method),
}
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. It can be used in a call or defer instruction.
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value, error) {
// interface call.
func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf, err := c.parseExpr(frame, instr.Value) // interface
if err != nil {
return llvm.Value{}, nil, err
}
itf := b.getValue(instr.Value) // interface
llvmFnType, err := c.getLLVMType(instr.Method.Type())
if err != nil {
return llvm.Value{}, nil, err
}
// getLLVMType() has created a closure type for us, but we don't actually
// want a closure type as an interface call can never be a closure call. So
// extract the function pointer type from the closure.
llvmFnType = llvmFnType.Subtypes()[1]
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
values := []llvm.Value{
typecode,
c.getInterfaceMethodSet(instr.Value.Type().(*types.Named)),
c.getMethodSignature(instr.Method),
}
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast")
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
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 {
val, err := c.parseExpr(frame, arg)
if err != nil {
return llvm.Value{}, nil, err
}
args = append(args, val)
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(b.i8ptrType))
return fnCast, args, nil
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, error) {
// 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() {
// Wrapper already created. Return it directly.
return wrapper, nil
return wrapper
}
// Get the expanded receiver type.
receiverType, err := c.getLLVMType(f.Params[0].Type())
if err != nil {
return llvm.Value{}, err
receiverType := c.getLLVMType(f.Params[0].Type())
var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
}
expandedReceiverType := c.expandFormalParamType(receiverType)
// Does this method even need any wrapping?
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {
@@ -368,7 +457,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
// Casting a function signature to a different signature and calling it
// with a receiver pointer bitcasted to *i8 (as done in calls on an
// interface) is hopefully a safe (defined) operation.
return f.LLVMFn, nil
return f.LLVMFn
}
// create wrapper function
@@ -376,67 +465,40 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
fn: f,
wrapper: wrapper,
receiverType: receiverType,
})
return wrapper, nil
}
if f.LLVMFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle")
}
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
// see that function for details.
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) error {
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, err := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
if err != nil {
return err
}
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
if c.Debug() {
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)
var receiverPtr llvm.Value
if c.targetData.TypeAllocSize(receiverType) > c.targetData.TypeAllocSize(c.i8ptrType) {
// The receiver is passed in using a pointer. We have to load it here
// and pass it by value to the real function.
// Load the underlying value.
receiverPtrType := llvm.PointerType(receiverType, 0)
receiverPtr = c.builder.CreateBitCast(wrapper.Param(0), receiverPtrType, "receiver.ptr")
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 {
// The value is stored in the interface, but it is of type struct which
// is expanded to multiple parameters (e.g. {i8, i8}). So we have to
// receive the struct as parameter, expand it, and pass it on to the
// real function.
// Cast the passed-in i8* to the struct value (using an alloca) and
// extract its values.
alloca := c.builder.CreateAlloca(c.i8ptrType, "receiver.alloca")
c.builder.CreateStore(wrapper.Param(0), alloca)
receiverPtr = c.builder.CreateBitCast(alloca, llvm.PointerType(receiverType, 0), "receiver.ptr")
ret := b.CreateCall(f.LLVMFn, params, "ret")
b.CreateRet(ret)
}
receiverValue := c.builder.CreateLoad(receiverPtr, "receiver")
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()
} else {
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
c.builder.CreateRet(ret)
}
return nil
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
}
+194
View File
@@ -0,0 +1,194 @@
// 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"
"fmt"
"tinygo.org/x/go-llvm"
)
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
}
checked[t] = struct{}{}
// check for any context mismatches
switch {
case t.Context() == c.ctx:
// this is correct
case t.Context() == llvm.GlobalContext():
// somewhere we accidentally used the global context instead of a real context
return fmt.Errorf("type %q uses global context", t.String())
default:
// we used some other context by accident
return fmt.Errorf("type %q uses context %v instead of the main context %v", t.Context(), c.ctx)
}
// if this is a composite type, check the components of the type
switch t.TypeKind() {
case llvm.VoidTypeKind, llvm.LabelTypeKind, llvm.TokenTypeKind, llvm.MetadataTypeKind:
// there should only be one of any of these
if s, ok := specials[t.TypeKind()]; !ok {
specials[t.TypeKind()] = t
} else if s != t {
return fmt.Errorf("duplicate special type %q: %v and %v", t.TypeKind().String(), t, s)
}
case llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.X86_FP80TypeKind, llvm.FP128TypeKind, llvm.PPC_FP128TypeKind:
// floating point numbers are primitives - nothing to recurse
case llvm.IntegerTypeKind:
// integers are primitives - nothing to recurse
case llvm.FunctionTypeKind:
// check arguments and return(s)
for i, v := range t.ParamTypes() {
if err := c.checkType(v, checked, specials); err != nil {
return fmt.Errorf("failed to verify argument %d of type %s: %s", i, t.String(), err.Error())
}
}
if err := c.checkType(t.ReturnType(), checked, specials); err != nil {
return fmt.Errorf("failed to verify return type of type %s: %s", t.String(), err.Error())
}
case llvm.StructTypeKind:
// check all elements
for i, v := range t.StructElementTypes() {
if err := c.checkType(v, checked, specials); err != nil {
return fmt.Errorf("failed to verify type of field %d of struct type %s: %s", i, t.String(), err.Error())
}
}
case llvm.ArrayTypeKind:
// check element type
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
return fmt.Errorf("failed to verify element type of array type %s: %s", t.String(), err.Error())
}
case llvm.PointerTypeKind:
// check underlying type
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
return fmt.Errorf("failed to verify underlying type of pointer type %s: %s", t.String(), err.Error())
}
case llvm.VectorTypeKind:
// check element type
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
return fmt.Errorf("failed to verify element type of vector type %s: %s", t.String(), err.Error())
}
default:
return fmt.Errorf("unrecognized kind %q of type %s", t.TypeKind(), t.String())
}
return nil
}
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())
}
// check if this is an undefined void
if v.IsUndef() && v.Type().TypeKind() == llvm.VoidTypeKind {
return errors.New("encountered undefined void value")
}
return nil
}
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 {
return errorAt(inst, fmt.Sprintf("failed to validate operand %d of instruction %q: %s", i, inst.Name(), err.Error()))
}
}
return nil
}
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 {
errs = append(errs, errorAt(bb.Parent(), fmt.Sprintf("failed to validate value of basic block %s: %v", bb.AsValue().Name(), err)))
}
// check instructions
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if err := c.checkInstruction(inst, types, specials); err != nil {
errs = append(errs, err)
}
}
return errs
}
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 {
errs = append(errs, fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error()))
}
// check basic blocks
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
errs = append(errs, c.checkBasicBlock(bb, types, specials)...)
}
return errs
}
// 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
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"))
}
types := map[llvm.Type]struct{}{}
specials := map[llvm.TypeKind]llvm.Type{}
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
errs = append(errs, c.checkFunction(fn, types, specials)...)
}
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()))
}
}
return errs
}
+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{}
}
}
+69
View File
@@ -0,0 +1,69 @@
package compiler
import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
// This file contains helper functions for LLVM that are not exposed in the Go
// bindings.
// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
if value.IsNil() {
return nil
}
var uses []llvm.Value
use := value.FirstUse()
for !use.IsNil() {
uses = append(uses, use.User())
use = use.NextUse()
}
return uses
}
// createTemporaryAlloca creates a new alloca in the entry block and adds
// 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 (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 (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 (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 (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 *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)
for i := 0; i < len(buf); i++ {
ch := uint64(buf[i])
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
}
global.SetInitializer(value)
return global
}
+168
View File
@@ -0,0 +1,168 @@
// Package llvmutil contains utility functions used across multiple compiler
// packages. For example, they may be used by both the compiler pacakge and
// transformation packages.
//
// Normally, utility packages are avoided. However, in this case, the utility
// functions are non-trivial and hard to get right. Copying them to multiple
// places would be a big risk if only one of them is updated.
package llvmutil
import "tinygo.org/x/go-llvm"
// CreateEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is
// at the end of the current block.
func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm.Value {
currentBlock := builder.GetInsertBlock()
entryBlock := currentBlock.Parent().EntryBasicBlock()
if entryBlock.FirstInstruction().IsNil() {
builder.SetInsertPointAtEnd(entryBlock)
} else {
builder.SetInsertPointBefore(entryBlock.FirstInstruction())
}
alloca := builder.CreateAlloca(t, name)
builder.SetInsertPointAtEnd(currentBlock)
return alloca
}
// CreateTemporaryAlloca creates a new alloca in the entry block and adds
// lifetime start infromation in the IR signalling that the alloca won't be used
// before this point.
//
// This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it.
func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
ctx := t.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca = CreateEntryBlockAlloca(builder, t, name)
bitcast = builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
return
}
// CreateInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction
func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, inst llvm.Value, name string) llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca := CreateEntryBlockAlloca(builder, t, name)
builder.SetInsertPointBefore(inst)
bitcast := builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
if next := llvm.NextInstruction(inst); !next.IsNil() {
builder.SetInsertPointBefore(next)
} else {
builder.SetInsertPointAtEnd(inst.InstructionParent())
}
builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, bitcast}, "")
return alloca
}
// EmitLifetimeEnd signals the end of an (alloca) lifetime by calling the
// llvm.lifetime.end intrinsic. It is commonly used together with
// createTemporaryAlloca.
func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, ptr}, "")
}
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
// first if it doesn't exist yet.
func getLifetimeStartFunc(mod llvm.Module) llvm.Value {
fn := mod.NamedFunction("llvm.lifetime.start.p0i8")
ctx := mod.Context()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
if fn.IsNil() {
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
fn = llvm.AddFunction(mod, "llvm.lifetime.start.p0i8", fnType)
}
return fn
}
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
// first if it doesn't exist yet.
func getLifetimeEndFunc(mod llvm.Module) llvm.Value {
fn := mod.NamedFunction("llvm.lifetime.end.p0i8")
ctx := mod.Context()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
if fn.IsNil() {
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
fn = llvm.AddFunction(mod, "llvm.lifetime.end.p0i8", fnType)
}
return fn
}
// SplitBasicBlock splits a LLVM basic block into two parts. All instructions
// after afterInst are moved into a new basic block (created right after the
// current one) with the given name.
func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
oldBlock := afterInst.InstructionParent()
newBlock := afterInst.Type().Context().InsertBasicBlock(insertAfter, name)
var nextInstructions []llvm.Value // values to move
// Collect to-be-moved instructions.
inst := afterInst
for {
inst = llvm.NextInstruction(inst)
if inst.IsNil() {
break
}
nextInstructions = append(nextInstructions, inst)
}
// Move instructions.
builder.SetInsertPointAtEnd(newBlock)
for _, inst := range nextInstructions {
inst.RemoveFromParentAsInstruction()
builder.Insert(inst)
}
// Find PHI nodes to update.
var phiNodes []llvm.Value // PHI nodes to update
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst.IsAPHINode().IsNil() {
continue
}
needsUpdate := false
incomingCount := inst.IncomingCount()
for i := 0; i < incomingCount; i++ {
if inst.IncomingBlock(i) == oldBlock {
needsUpdate = true
break
}
}
if !needsUpdate {
// PHI node has no incoming edge from the old block.
continue
}
phiNodes = append(phiNodes, inst)
}
}
// Update PHI nodes.
for _, phi := range phiNodes {
builder.SetInsertPointBefore(phi)
newPhi := builder.CreatePHI(phi.Type(), "")
incomingCount := phi.IncomingCount()
incomingVals := make([]llvm.Value, incomingCount)
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
for i := 0; i < incomingCount; i++ {
value := phi.IncomingValue(i)
block := phi.IncomingBlock(i)
if block == oldBlock {
block = newBlock
}
incomingVals[i] = value
incomingBlocks[i] = block
}
newPhi.AddIncoming(incomingVals, incomingBlocks)
phi.ReplaceAllUsesWith(newPhi)
phi.EraseFromParentAsInstruction()
}
return newBlock
}
+163
View File
@@ -0,0 +1,163 @@
package llvmutil
// This file contains utility functions to pack and unpack sets of values. It
// can take in a list of values and tries to store it efficiently in the pointer
// itself if possible and legal.
import (
"github.com/tinygo-org/tinygo/compileopts"
"tinygo.org/x/go-llvm"
)
// EmitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
// 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())
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
valueTypes := make([]llvm.Type, len(values))
for i, value := range values {
valueTypes[i] = value.Type()
}
packedType := ctx.StructType(valueTypes, false)
// Allocate memory for the packed data.
var packedAlloc, packedHeapAlloc llvm.Value
size := targetData.TypeAllocSize(packedType)
if size == 0 {
return llvm.ConstPointerNull(i8ptrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return builder.CreateBitCast(values[0], i8ptrType, "pack.ptr")
} else if size <= targetData.TypeAllocSize(i8ptrType) {
// Packed data fits in a pointer, so store it directly inside the
// pointer.
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
// Try to keep this cast in SSA form.
return builder.CreateIntToPtr(values[0], i8ptrType, "pack.int")
}
// Because packedType is a struct and we have to cast it to a *i8, store
// it in an alloca first for bitcasting (store+bitcast+load).
packedAlloc, _, _ = CreateTemporaryAlloca(builder, mod, packedType, "")
} 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")
packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{
sizeValue,
llvm.Undef(i8ptrType), // unused context parameter
llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "")
if config.NeedsStackObjects() {
trackPointer := mod.NamedFunction("runtime.trackPointer")
builder.CreateCall(trackPointer, []llvm.Value{
packedHeapAlloc,
llvm.Undef(i8ptrType), // unused context parameter
llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "")
}
packedAlloc = builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
}
// Store all values in the alloca or heap pointer.
for i, value := range values {
indices := []llvm.Value{
llvm.ConstInt(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
}
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
builder.CreateStore(value, gep)
}
if packedHeapAlloc.IsNil() {
// Load value (as *i8) from the alloca.
packedAlloc = builder.CreateBitCast(packedAlloc, llvm.PointerType(i8ptrType, 0), "")
result := builder.CreateLoad(packedAlloc, "")
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
return result
} else {
// Get the original heap allocation pointer, which already is an *i8.
return packedHeapAlloc
}
}
// EmitPointerUnpack extracts a list of values packed using EmitPointerPack.
func EmitPointerUnpack(builder llvm.Builder, mod llvm.Module, ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
packedType := ctx.StructType(valueTypes, false)
// Get a correctly-typed pointer to the packed data.
var packedAlloc, packedRawAlloc llvm.Value
size := targetData.TypeAllocSize(packedType)
if size == 0 {
// No data to unpack.
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly.
return []llvm.Value{builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
} else if size <= targetData.TypeAllocSize(i8ptrType) {
// Packed data stored directly in pointer.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form.
return []llvm.Value{builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
}
// Fallback: load it using an alloca.
packedRawAlloc, _, _ = CreateTemporaryAlloca(builder, mod, llvm.PointerType(i8ptrType, 0), "unpack.raw.alloc")
packedRawValue := builder.CreateBitCast(ptr, llvm.PointerType(i8ptrType, 0), "unpack.raw.value")
builder.CreateStore(packedRawValue, packedRawAlloc)
packedAlloc = builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
} else {
// Packed data stored on the heap. Bitcast the passed-in pointer to the
// correct pointer type.
packedAlloc = builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
}
// Load each value from the packed data.
values := make([]llvm.Value, len(valueTypes))
for i, valueType := range valueTypes {
if targetData.TypeAllocSize(valueType) == 0 {
// This value has length zero, so there's nothing to load.
values[i] = llvm.ConstNull(valueType)
continue
}
indices := []llvm.Value{
llvm.ConstInt(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
}
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
values[i] = builder.CreateLoad(gep, "")
}
if !packedRawAlloc.IsNil() {
allocPtr := builder.CreateBitCast(packedRawAlloc, i8ptrType, "")
allocSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(uintptrType), false)
EmitLifetimeEnd(builder, mod, allocPtr, allocSize)
}
return values
}
+127 -60
View File
@@ -6,112 +6,175 @@ import (
"go/token"
"go/types"
"github.com/aykevl/go-llvm"
"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, err := c.getLLVMType(valueType)
if err != nil {
return llvm.Value{}, err
// 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")
}
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value")
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr")
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, 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
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
params := []llvm.Value{m, keyPtr, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
// Store the key in an alloca, in the entry block to avoid dynamic stack
// growth.
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, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} else {
return llvm.Value{}, c.makeError(pos, "todo: map lookup key type: "+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, "")
}
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
// 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 := 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) error {
valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value")
c.builder.CreateStore(value, valueAlloca)
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr")
// 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, "")
return nil
b.createRuntimeCall("hashmapStringSet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr, valuePtr}
c.createRuntimeCall("hashmapBinarySet", params, "")
return nil
b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
} else {
return c.makeError(pos, "todo: map update key type: "+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, "")
}
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 := c.builder.CreateAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr}
c.createRuntimeCall("hashmapBinaryDelete", params, "")
b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
return nil
} else {
return c.makeError(pos, "todo: map delete key type: "+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 {
switch keyType := keyType.(type) {
case *types.Basic:
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
case *types.Pointer:
return true
case *types.Struct:
for i := 0; i < keyType.NumFields(); i++ {
fieldType := keyType.Field(i).Type().Underlying()
@@ -120,6 +183,10 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
}
}
return true
case *types.Array:
return hashmapIsBinaryKey(keyType.Elem())
case *types.Named:
return hashmapIsBinaryKey(keyType.Underlying())
default:
return false
}
-341
View File
@@ -1,341 +0,0 @@
package compiler
import (
"errors"
"github.com/aykevl/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()
// 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.
c.OptimizeMaps()
c.OptimizeStringToBytes()
c.OptimizeAllocs()
c.LowerInterfaces()
// 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.
c.OptimizeAllocs()
c.OptimizeStringToBytes()
} else {
// Must be run at any optimization level.
c.LowerInterfaces()
}
if err := c.Verify(); err != nil {
return 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)
}
}
// Run module passes.
modPasses := llvm.NewPassManager()
defer modPasses.Dispose()
builder.Populate(modPasses)
modPasses.Run(c.mod)
return nil
}
// Eliminate created but not used maps.
//
// In the future, this should statically allocate created but never modified
// maps. This has not yet been implemented, however.
func (c *Compiler) OptimizeMaps() {
hashmapMake := c.mod.NamedFunction("runtime.hashmapMake")
if hashmapMake.IsNil() {
// nothing to optimize
return
}
hashmapBinarySet := c.mod.NamedFunction("runtime.hashmapBinarySet")
hashmapStringSet := c.mod.NamedFunction("runtime.hashmapStringSet")
for _, makeInst := range getUses(hashmapMake) {
updateInsts := []llvm.Value{}
unknownUses := false // are there any uses other than setting a value?
for _, use := range getUses(makeInst) {
if use := use.IsACallInst(); !use.IsNil() {
switch use.CalledValue() {
case hashmapBinarySet, hashmapStringSet:
updateInsts = append(updateInsts, use)
default:
unknownUses = true
}
} else {
unknownUses = true
}
}
if !unknownUses {
// This map can be entirely removed, as it is only created but never
// used.
for _, inst := range updateInsts {
inst.EraseFromParentAsInstruction()
}
makeInst.EraseFromParentAsInstruction()
}
}
}
// Transform runtime.stringToBytes(...) calls into const []byte slices whenever
// possible. This optimizes the following pattern:
// w.Write([]byte("foo"))
// where Write does not store to the slice.
func (c *Compiler) OptimizeStringToBytes() {
stringToBytes := c.mod.NamedFunction("runtime.stringToBytes")
if stringToBytes.IsNil() {
// nothing to optimize
return
}
for _, call := range getUses(stringToBytes) {
strptr := call.Operand(0)
strlen := call.Operand(1)
// strptr is always constant because strings are always constant.
convertedAllUses := true
for _, use := range getUses(call) {
nilValue := llvm.Value{}
if use.IsAExtractValueInst() == nilValue {
convertedAllUses = false
continue
}
switch use.Type().TypeKind() {
case llvm.IntegerTypeKind:
// A length (len or cap). Propagate the length value.
use.ReplaceAllUsesWith(strlen)
use.EraseFromParentAsInstruction()
case llvm.PointerTypeKind:
// The string pointer itself.
if !c.isReadOnly(use) {
convertedAllUses = false
continue
}
use.ReplaceAllUsesWith(strptr)
use.EraseFromParentAsInstruction()
default:
// should not happen
panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
}
}
if convertedAllUses {
// Call to runtime.stringToBytes can be eliminated: both the input
// and the output is constant.
call.EraseFromParentAsInstruction()
}
}
}
// Basic escape analysis: translate runtime.alloc calls into alloca
// instructions.
func (c *Compiler) OptimizeAllocs() {
allocator := c.mod.NamedFunction("runtime.alloc")
if allocator.IsNil() {
// nothing to optimize
return
}
heapallocs := getUses(allocator)
for _, heapalloc := range heapallocs {
nilValue := llvm.Value{}
if heapalloc.Operand(0).IsAConstant() == nilValue {
// Do not allocate variable length arrays on the stack.
continue
}
size := heapalloc.Operand(0).ZExtValue()
if size > 256 {
// The maximum value for a stack allocation.
// TODO: tune this, this is just a random value.
continue
}
// In general the pattern is:
// %0 = call i8* @runtime.alloc(i32 %size)
// %1 = bitcast i8* %0 to type*
// (use %1 only)
// But the bitcast might sometimes be dropped when allocating an *i8.
// The 'bitcast' variable below is thus usually a bitcast of the
// heapalloc but not always.
bitcast := heapalloc // instruction that creates the value
if uses := getUses(heapalloc); len(uses) == 1 && uses[0].IsABitCastInst() != nilValue {
// getting only bitcast use
bitcast = uses[0]
}
if !c.doesEscape(bitcast) {
// Insert alloca in the entry block. Do it here so that mem2reg can
// promote it to a SSA value.
fn := bitcast.InstructionParent().Parent()
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
alignment := c.targetData.ABITypeAlignment(c.i8ptrType)
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
zero, _ := c.getZeroValue(alloca.Type().ElementType())
c.builder.CreateStore(zero, alloca)
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
bitcast.ReplaceAllUsesWith(stackalloc)
if heapalloc != bitcast {
bitcast.EraseFromParentAsInstruction()
}
heapalloc.EraseFromParentAsInstruction()
}
}
}
// Very basic escape analysis.
func (c *Compiler) doesEscape(value llvm.Value) bool {
uses := getUses(value)
for _, use := range uses {
nilValue := llvm.Value{}
if use.IsAGetElementPtrInst() != nilValue {
if c.doesEscape(use) {
return true
}
} else if use.IsABitCastInst() != nilValue {
// A bitcast escapes if the casted-to value escapes.
if c.doesEscape(use) {
return true
}
} else if use.IsALoadInst() != nilValue {
// Load does not escape.
} else if use.IsAStoreInst() != nilValue {
// Store only escapes when the value is stored to, not when the
// value is stored into another value.
if use.Operand(0) == value {
return true
}
} else if use.IsACallInst() != nilValue {
// Call only escapes when the (pointer) parameter is not marked
// "nocapture". This flag means that the parameter does not escape
// the give function.
if use.CalledValue().IsAFunction() != nilValue {
if use.CalledValue().IsDeclaration() {
// Kind of dirty: assume external functions don't let
// pointers escape.
// TODO: introduce //go:noescape that sets the 'nocapture'
// flag on each input parameter.
continue
}
}
if !c.hasFlag(use, value, "nocapture") {
return true
}
} else {
// Unknown instruction, might escape.
return true
}
}
// does not escape
return false
}
// Check whether the given value (which is of pointer type) is never stored to.
func (c *Compiler) isReadOnly(value llvm.Value) bool {
uses := getUses(value)
for _, use := range uses {
nilValue := llvm.Value{}
if use.IsAGetElementPtrInst() != nilValue {
if !c.isReadOnly(use) {
return false
}
} else if use.IsACallInst() != nilValue {
if !c.hasFlag(use, value, "readonly") {
return false
}
} else {
// Unknown instruction, might not be readonly.
return false
}
}
return true
}
// Check whether all uses of this param as parameter to the call have the given
// flag. In most cases, there will only be one use but a function could take the
// same parameter twice, in which case both must have the flag.
// A flag can be any enum flag, like "readonly".
func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool {
fn := call.CalledValue()
nilValue := llvm.Value{}
if fn.IsAFunction() == nilValue {
// This is not a function but something else, like a function pointer.
return false
}
kindID := llvm.AttributeKindID(kind)
for i := 0; i < fn.ParamsCount(); i++ {
if call.Operand(i) != param {
// This is not the parameter we're checking.
continue
}
index := i + 1 // param attributes start at 1
attr := fn.GetEnumAttributeAtIndex(index, kindID)
nilAttribute := llvm.Attribute{}
if attr == nilAttribute {
// At least one parameter doesn't have the flag (there may be
// multiple).
return false
}
}
return true
}
// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
if value.IsNil() {
return nil
}
var uses []llvm.Value
use := value.FirstUse()
for !use.IsNil() {
uses = append(uses, use.User())
use = use.NextUse()
}
return uses
}
+22 -6
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()
@@ -107,6 +107,12 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
if k == types.Uintptr {
return s.PtrSize
}
if k == types.UnsafePointer {
return s.PtrSize
}
if k == types.Invalid {
return 0 // only relevant when there is a type error somewhere
}
panic("unknown basic type: " + t.String())
case *types.Array:
n := t.Len()
@@ -125,11 +131,21 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
return 0
}
fields := make([]*types.Var, t.NumFields())
maxAlign := int64(1)
for i := range fields {
fields[i] = t.Field(i)
field := t.Field(i)
fields[i] = field
al := s.Alignof(field.Type())
if al > maxAlign {
maxAlign = al
}
}
// Pick the size that fits this struct and add some alignment. Some
// structs have some extra padding at the end which should also be taken
// care of:
// struct { int32 n; byte b }
offsets := s.Offsetsof(fields)
return offsets[n-1] + s.Sizeof(fields[n-1].Type())
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
case *types.Interface:
return s.PtrSize * 2
case *types.Pointer:
+147
View File
@@ -0,0 +1,147 @@
package compiler
// This file manages symbols, that is, functions and globals. It reads their
// pragmas, determines the link name, etc.
import (
"go/ast"
"go/token"
"go/types"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// globalInfo contains some information about a specific global. By default,
// linkName is equal to .RelString(nil) on a global and extern is false, but for
// some symbols this is different (due to //go:extern for example).
type globalInfo struct {
linkName string // go:extern
extern bool // go:extern
align int // go:align
}
// loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals.
func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
c.astComments[id] = decl.Doc
}
}
}
}
}
}
}
}
}
// 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 *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
info := c.getGlobalInfo(g)
llvmGlobal := c.mod.NamedGlobal(info.linkName)
if llvmGlobal.IsNil() {
typ := g.Type().(*types.Pointer).Elem()
llvmType := c.getLLVMType(typ)
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
if !info.extern {
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
llvmGlobal.SetLinkage(llvm.InternalLinkage)
}
// Set alignment from the //go:align comment.
var alignInBits uint32
if info.align < 0 || info.align&(info.align-1) != 0 {
// 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 *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.
info.linkName = g.Name()[2:]
info.extern = true
} else {
// Pick the default linkName.
info.linkName = g.RelString(nil)
// Check for //go: pragmas, which may change the link name (among
// others).
doc := c.astComments[info.linkName]
if doc != nil {
info.parsePragmas(doc)
}
}
return info
}
// Parse //go: pragma comments from the source. In particular, it parses the
// //go:extern pragma on globals.
func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
for _, comment := range doc.List {
if !strings.HasPrefix(comment.Text, "//go:") {
continue
}
parts := strings.Fields(comment.Text)
switch parts[0] {
case "//go:extern":
info.extern = true
if len(parts) == 2 {
info.linkName = parts[1]
}
case "//go:align":
align, err := strconv.Atoi(parts[1])
if err == nil {
info.align = align
}
}
}
}
+192
View File
@@ -0,0 +1,192 @@
package compiler
// This file implements the syscall.Syscall and syscall.Syscall6 instructions as
// compiler builtins.
import (
"strconv"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createSyscall emits an inline system call instruction, depending on the
// target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
num := b.getValue(call.Args[0])
var syscallResult llvm.Value
switch {
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.
// > For 64-bit users, the 32-bit syscall number is partitioned
// > with the high-order bits representing the class and low-order
// > bits being the syscall number within that class.
// > The high-order 32-bits of the 64-bit syscall number are unused.
// > All system classes enter the kernel via the syscall instruction.
//
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
num = 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{b.uintptrType}
// Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
constraints := "={rax},0"
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
"{rdi}",
"{rsi}",
"{rdx}",
"{r10}",
"{r8}",
"{r9}",
"{r11}",
"{r12}",
"{r13}",
}[i]
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "386" && b.GOOS() == "linux":
// 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{b.uintptrType}
// Constraints will look something like:
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
constraints := "={eax},0"
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
"{ebx}",
"{ecx}",
"{edx}",
"{esi}",
"{edi}",
"{ebp}",
}[i]
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "arm" && b.GOOS() == "linux":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
args := []llvm.Value{}
argTypes := []llvm.Type{}
// Constraints will look something like:
// ={r0},0,{r1},{r2},{r7},~{r3}
constraints := "={r0}"
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
"0", // tie to output
"{r1}",
"{r2}",
"{r3}",
"{r4}",
"{r5}",
"{r6}",
}[i]
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
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(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "arm64" && b.GOOS() == "linux":
// Source: syscall(2) man page.
args := []llvm.Value{}
argTypes := []llvm.Type{}
// Constraints will look something like:
// ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
constraints := "={x0}"
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
"0", // tie to output
"{x1}",
"{x2}",
"{x3}",
"{x4}",
"{x5}",
}[i]
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
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
// calling convention.
constraints += ",~{x" + strconv.Itoa(i) + "}"
}
constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "")
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
}
switch b.GOOS() {
case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno
// Pseudocode:
// var err uintptr
// if syscallResult < 0 && syscallResult > -4096 {
// err = -syscallResult
// }
// return syscallResult, 0, err
zero := llvm.ConstInt(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
// Pseudocode:
// var err uintptr
// if syscallResult != 0 {
// err = syscallResult
// }
// return syscallResult, 0, err
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{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
}
}
+30
View File
@@ -0,0 +1,30 @@
package compiler
// This file implements volatile loads/stores in runtime/volatile.LoadT and
// runtime/volatile.StoreT as compiler builtins.
import (
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// 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
}
// 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
}
+14
View File
@@ -0,0 +1,14 @@
module github.com/tinygo-org/tinygo
go 1.11
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac
github.com/chromedp/chromedp v0.5.3
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
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
)
+50
View File
@@ -0,0 +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/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/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-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=
+197
View File
@@ -0,0 +1,197 @@
// Package goenv returns environment variables that are used in various parts of
// the compiler. You can query it manually with the `tinygo env` subcommand.
package goenv
import (
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
)
// Keys is a slice of all available environment variable keys.
var Keys = []string{
"GOOS",
"GOARCH",
"GOROOT",
"GOPATH",
"GOCACHE",
"CGO_ENABLED",
"TINYGOROOT",
}
// TINYGOROOT is the path to the final location for checking tinygo files. If
// unset (by a -X ldflag), then sourceDir() will fallback to the original build
// directory.
var TINYGOROOT string
// Get returns a single environment variable, possibly calculating it on-demand.
// The empty string is returned for unknown environment variables.
func Get(name string) string {
switch name {
case "GOOS":
if dir := os.Getenv("GOOS"); dir != "" {
return dir
}
return runtime.GOOS
case "GOARCH":
if dir := os.Getenv("GOARCH"); dir != "" {
return dir
}
return runtime.GOARCH
case "GOROOT":
return getGoroot()
case "GOPATH":
if dir := os.Getenv("GOPATH"); dir != "" {
return dir
}
// fallback
home := getHomeDir()
return filepath.Join(home, "go")
case "GOCACHE":
// Get the cache directory, usually ~/.cache/tinygo
dir, err := os.UserCacheDir()
if err != nil {
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:
return ""
}
}
// Return the TINYGOROOT, or exit with an error.
func sourceDir() string {
// Use $TINYGOROOT as root, if available.
root := os.Getenv("TINYGOROOT")
if root != "" {
if !isSourceDir(root) {
fmt.Fprintln(os.Stderr, "error: $TINYGOROOT was not set to the correct root")
os.Exit(1)
}
return root
}
if TINYGOROOT != "" {
if !isSourceDir(TINYGOROOT) {
fmt.Fprintln(os.Stderr, "error: TINYGOROOT was not set to the correct root")
os.Exit(1)
}
return TINYGOROOT
}
// Find root from executable path.
path, err := os.Executable()
if err != nil {
// Very unlikely. Bail out if it happens.
panic("could not get executable path: " + err.Error())
}
root = filepath.Dir(filepath.Dir(path))
if isSourceDir(root) {
return root
}
// Fallback: use the original directory from where it was built
// https://stackoverflow.com/a/32163888/559350
_, path, _, _ = runtime.Caller(0)
root = filepath.Dir(filepath.Dir(path))
if isSourceDir(root) {
return root
}
fmt.Fprintln(os.Stderr, "error: could not autodetect root directory, set the TINYGOROOT environment variable to override")
os.Exit(1)
panic("unreachable")
}
// isSourceDir returns true if the directory looks like a TinyGo source directory.
func isSourceDir(root string) bool {
_, err := os.Stat(filepath.Join(root, "src/runtime/internal/sys/zversion.go"))
if err != nil {
return false
}
_, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go"))
return err == nil
}
func getHomeDir() string {
u, err := user.Current()
if err != nil {
panic("cannot get current user: " + err.Error())
}
if u.HomeDir == "" {
// This is very unlikely, so panic here.
// Not the nicest solution, however.
panic("could not find home directory")
}
return u.HomeDir
}
// getGoroot returns an appropriate GOROOT from various sources. If it can't be
// found, it returns an empty string.
func getGoroot() string {
goroot := os.Getenv("GOROOT")
if goroot != "" {
// An explicitly set GOROOT always has preference.
return goroot
}
// Check for the location of the 'go' binary and base GOROOT on that.
binpath, err := exec.LookPath("go")
if err == nil {
binpath, err = filepath.EvalSymlinks(binpath)
if err == nil {
goroot := filepath.Dir(filepath.Dir(binpath))
if isGoroot(goroot) {
return goroot
}
}
}
// Check what GOROOT was at compile time.
if isGoroot(runtime.GOROOT()) {
return runtime.GOROOT()
}
// Check for some standard locations, as a last resort.
var candidates []string
switch runtime.GOOS {
case "linux":
candidates = []string{
"/usr/local/go", // manually installed
"/usr/lib/go", // from the distribution
}
case "darwin":
candidates = []string{
"/usr/local/go", // manually installed
"/usr/local/opt/go/libexec", // from Homebrew
}
}
for _, candidate := range candidates {
if isGoroot(candidate) {
return candidate
}
}
// Can't find GOROOT...
return ""
}
// isGoroot checks whether the given path looks like a GOROOT.
func isGoroot(goroot string) bool {
_, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go"))
return err == nil
}
+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.14.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
+72 -6
View File
@@ -3,15 +3,81 @@ package interp
// This file provides useful types for errors encountered during IR evaluation.
import (
"github.com/aykevl/go-llvm"
"errors"
"go/scanner"
"go/token"
"path/filepath"
"tinygo.org/x/go-llvm"
)
type Unsupported struct {
// errUnreachable is returned when an unreachable instruction is executed. This
// error should not be visible outside of the interp package.
var errUnreachable = &Error{Err: errors.New("interp: unreachable executed")}
// unsupportedInstructionError returns a new "unsupported instruction" error for
// the given instruction. It includes source location information, when
// available.
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Error {
return e.errorAt(inst, errors.New("interp: unsupported instruction"))
}
// ErrorLine is one line in a traceback. The position may be missing.
type ErrorLine struct {
Pos token.Position
Inst llvm.Value
}
func (e Unsupported) Error() string {
// TODO: how to return the actual instruction string?
// It looks like LLVM provides no function for that...
return "interp: unsupported instruction"
// 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
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.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, err error) *Error {
return &Error{
ImportPath: e.packagePath,
Pos: getPosition(inst),
Err: err,
}
}
// 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 instruction, as
// far as it is available.
func getPosition(inst llvm.Value) token.Position {
if inst.IsAInstruction().IsNil() {
return token.Position{}
}
loc := inst.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()),
}
}
+284 -60
View File
@@ -7,25 +7,22 @@ import (
"errors"
"strings"
"github.com/aykevl/go-llvm"
"tinygo.org/x/go-llvm"
)
type frame struct {
*Eval
fn llvm.Value
pkgName string
locals map[llvm.Value]Value
*evalPackage
fn llvm.Value
locals map[llvm.Value]Value
}
var ErrUnreachable = errors.New("interp: unreachable executed")
// evalBasicBlock evaluates a single basic block, returning the return value (if
// ending with a ret instruction), a list of outgoing basic blocks (if not
// ending with a ret instruction), or an error on failure.
// Most of it works at compile time. Some calls get translated into calls to be
// executed at runtime: calls to functions with side effects, external calls,
// and operations on the result of such instructions.
func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (retval Value, outgoing []llvm.Value, err error) {
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)
@@ -79,26 +76,29 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateXor(lhs, rhs, "")}
default:
return nil, nil, &Unsupported{inst}
return nil, nil, fr.unsupportedInstructionError(inst)
}
// Memory operators
case !inst.IsAAllocaInst().IsNil():
fr.locals[inst] = &AllocaValue{
Underlying: getZeroValue(inst.Type().ElementType()),
Dirty: false,
allocType := inst.Type().ElementType()
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloca")
alloca.SetInitializer(llvm.ConstNull(allocType))
alloca.SetLinkage(llvm.InternalLinkage)
fr.locals[inst] = &LocalValue{
Underlying: alloca,
Eval: fr.Eval,
}
case !inst.IsALoadInst().IsNil():
operand := fr.getLocal(inst.Operand(0))
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
var value llvm.Value
if !operand.IsConstant() || inst.IsVolatile() {
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
} else {
value = operand.Load()
}
if value.Type() != inst.Type() {
panic("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():
@@ -122,15 +122,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.
panic("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() {
println(" expected:", inst.Type().String())
println(" actual: ", result.Type().String())
panic("interp: gep: type does not match")
return nil, nil, fr.errorAt(inst, errors.New("interp: gep: type does not match"))
}
fr.locals[inst] = result
@@ -173,17 +174,55 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
continue // special case: bitcast of alloc
}
}
value := fr.getLocal(operand)
if bc, ok := value.(*PointerCastValue); ok {
value = bc.Underlying // avoid double bitcasts
if _, ok := fr.getLocal(operand).(*MapValue); ok {
// Special case for runtime.trackPointer calls.
// Note: this might not be entirely sound in some rare cases
// where the map is stored in a dirty global.
uses := getUses(inst)
if len(uses) == 1 {
use := uses[0]
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
continue
}
}
// It is not possible in Go to bitcast a map value to a pointer.
return nil, nil, fr.errorAt(inst, errors.New("unimplemented: bitcast of map"))
}
fr.locals[inst] = &PointerCastValue{Eval: fr.Eval, Underlying: value, CastType: inst.Type()}
value := fr.getLocal(operand).(*LocalValue)
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
// Other operators
case !inst.IsAICmpInst().IsNil():
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
predicate := inst.IntPredicate()
if predicate == llvm.IntEQ {
var lhsZero, rhsZero bool
var ok1, ok2 bool
if lhs.Type().TypeKind() == llvm.PointerTypeKind {
// Unfortunately, the const propagation in the IR builder
// doesn't handle pointer compares of inttoptr values. So we
// implement it manually here.
lhsZero, ok1 = isPointerNil(lhs)
rhsZero, ok2 = isPointerNil(rhs)
}
if lhs.Type().TypeKind() == llvm.IntegerTypeKind {
lhsZero, ok1 = isZero(lhs)
rhsZero, ok2 = isZero(rhs)
}
if ok1 && ok2 {
if lhsZero && rhsZero {
// Both are zero, so this icmp is always evaluated to true.
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
continue
}
if lhsZero != rhsZero {
// Only one of them is zero, so this comparison must return false.
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false)}
continue
}
}
}
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateICmp(predicate, lhs, rhs, "")}
case !inst.IsAFCmpInst().IsNil():
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
@@ -214,22 +253,26 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
if size != typeSize {
// allocate an array
if size%typeSize != 0 {
return nil, nil, &Unsupported{inst}
return nil, nil, fr.unsupportedInstructionError(inst)
}
elementCount = int(size / typeSize)
allocType = llvm.ArrayType(allocType, elementCount)
}
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
alloc.SetInitializer(getZeroValue(allocType))
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloc")
alloc.SetInitializer(llvm.ConstNull(allocType))
alloc.SetLinkage(llvm.InternalLinkage)
result := &GlobalValue{
result := &LocalValue{
Underlying: alloc,
Eval: fr.Eval,
}
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
@@ -237,20 +280,56 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
valueSize := inst.Operand(1).ZExtValue()
fr.locals[inst] = &MapValue{
Eval: fr.Eval,
PkgName: fr.pkgName,
PkgName: fr.packagePath,
KeySize: int(keySize),
ValueSize: int(valueSize),
}
case callee.Name() == "runtime.hashmapStringSet":
// set a string key in the map
m := fr.getLocal(inst.Operand(0)).(*MapValue)
keyBuf := fr.getLocal(inst.Operand(1))
keyLen := fr.getLocal(inst.Operand(2))
valPtr := fr.getLocal(inst.Operand(3))
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
keyLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
valPtr := fr.getLocal(inst.Operand(3)).(*LocalValue)
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
if !ok || !keyBuf.IsConstant() || !keyLen.IsConstant() || !valPtr.IsConstant() {
// The mapassign operation could not be done at compile
// time. Do it at runtime instead.
m := fr.getLocal(inst.Operand(0)).Value()
fr.markDirty(m)
llvmParams := []llvm.Value{
m, // *runtime.hashmap
fr.getLocal(inst.Operand(1)).Value(), // key.ptr
fr.getLocal(inst.Operand(2)).Value(), // key.len
fr.getLocal(inst.Operand(3)).Value(), // value (unsafe.Pointer)
fr.getLocal(inst.Operand(4)).Value(), // context
fr.getLocal(inst.Operand(5)).Value(), // parentHandle
}
fr.builder.CreateCall(callee, llvmParams, "")
continue
}
// "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters.
m.PutString(keyBuf, keyLen, valPtr)
case callee.Name() == "runtime.hashmapBinarySet":
// set a binary (int etc.) key in the map
// TODO: unimplemented
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
if !ok || !keyBuf.IsConstant() || !valPtr.IsConstant() {
// The mapassign operation could not be done at compile
// time. Do it at runtime instead.
m := fr.getLocal(inst.Operand(0)).Value()
fr.markDirty(m)
llvmParams := []llvm.Value{
m, // *runtime.hashmap
fr.getLocal(inst.Operand(1)).Value(), // key
fr.getLocal(inst.Operand(2)).Value(), // value
fr.getLocal(inst.Operand(3)).Value(), // context
fr.getLocal(inst.Operand(4)).Value(), // parentHandle
}
fr.builder.CreateCall(callee, llvmParams, "")
continue
}
m.PutBinary(keyBuf, valPtr)
case callee.Name() == "runtime.stringConcat":
// adding two strings together
buf1Ptr := fr.getLocal(inst.Operand(0))
@@ -266,7 +345,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
global := llvm.AddGlobal(fr.Mod, globalType, fr.pkgName+"$stringconcat")
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$stringconcat")
global.SetInitializer(globalValue)
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
@@ -274,10 +353,70 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
stringType := fr.Mod.GetTypeByName("runtime._string")
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
ret := getZeroValue(stringType)
ret := llvm.ConstNull(stringType)
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0})
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1})
fr.locals[inst] = &LocalValue{fr.Eval, ret}
case callee.Name() == "runtime.sliceCopy":
elementSize := fr.getLocal(inst.Operand(4)).(*LocalValue).Value().ZExtValue()
dstArray := fr.getLocal(inst.Operand(0)).(*LocalValue).stripPointerCasts()
srcArray := fr.getLocal(inst.Operand(1)).(*LocalValue).stripPointerCasts()
dstLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
srcLen := fr.getLocal(inst.Operand(3)).(*LocalValue)
if elementSize != 1 && dstArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind && srcArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind {
// Slice data pointers are created by adding a global array
// and getting the address of the first element using a GEP.
// However, before the compiler can pass it to
// runtime.sliceCopy, it has to perform a bitcast to a *i8,
// to make it a unsafe.Pointer. Now, when the IR builder
// sees a bitcast of a GEP with zero indices, it will make
// a bitcast of the original array instead of the GEP,
// which breaks our assumptions.
// Re-add this GEP, in the hope that it it is then of the correct type...
dstArrayValue, err := dstArray.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
dstArray = dstArrayValue.(*LocalValue)
srcArrayValue, err := srcArray.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
srcArray = srcArrayValue.(*LocalValue)
}
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
return nil, nil, fr.errorAt(inst, errors.New("interp: slice dst element size does not match pointer type"))
}
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
return nil, nil, fr.errorAt(inst, errors.New("interp: slice src element size does not match pointer type"))
}
if dstArray.Type() != srcArray.Type() {
return nil, nil, fr.errorAt(inst, errors.New("interp: slice element types don't match"))
}
length := dstLen.Value().SExtValue()
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
length = srcLength
}
if length < 0 {
return nil, nil, fr.errorAt(inst, errors.New("interp: trying to copy a slice with negative length?"))
}
for i := int64(0); i < length; i++ {
var err error
// *dst = *src
dstArray.Store(srcArray.Load())
// dst++
dstArrayValue, err := dstArray.GetElementPtr([]uint32{1})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
dstArray = dstArrayValue.(*LocalValue)
// src++
srcArrayValue, err := srcArray.GetElementPtr([]uint32{1})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
srcArray = srcArrayValue.(*LocalValue)
}
case callee.Name() == "runtime.stringToBytes":
// convert a string to a []byte
bufPtr := fr.getLocal(inst.Operand(0))
@@ -289,7 +428,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
global := llvm.AddGlobal(fr.Mod, globalType, fr.pkgName+"$bytes")
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$bytes")
global.SetInitializer(globalValue)
global.SetLinkage(llvm.InternalLinkage)
global.SetGlobalConstant(true)
@@ -297,13 +436,72 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
sliceType := inst.Type()
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
ret := getZeroValue(sliceType)
ret := llvm.ConstNull(sliceType)
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
fr.locals[inst] = &LocalValue{fr.Eval, ret}
case callee.Name() == "runtime.makeInterface":
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), fr.TargetData.IntPtrType())}
case callee.Name() == "runtime.typeAssert":
actualTypeInt := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
assertedType := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
if actualTypeInt.IsAConstantExpr().IsNil() || actualTypeInt.Opcode() != llvm.PtrToInt {
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode in runtime.typeAssert to be a ptrtoint"))
}
actualType := actualTypeInt.Operand(0)
if actualType.IsAConstant().IsNil() || assertedType.IsAConstant().IsNil() {
return nil, nil, fr.errorAt(inst, errors.New("interp: unimplemented: type assert with non-constant interface value"))
}
assertOk := uint64(0)
if llvm.ConstExtractValue(actualType.Initializer(), []uint32{0}) == assertedType {
assertOk = 1
}
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), assertOk, false)}
case callee.Name() == "runtime.interfaceImplements":
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode to be a ptrtoint"))
}
typecode = typecode.Operand(0)
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
return nil, nil, fr.errorAt(inst, errors.New("interp: expected method set in runtime.interfaceImplements to be a constant gep"))
}
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
return nil, nil, fr.errorAt(inst, errors.New("interp: expected method set to be a constant gep"))
}
methodSet = methodSet.Operand(0).Initializer()
// Make a set of all the methods on the concrete type, for
// easier checking in the next step.
definedMethods := map[string]struct{}{}
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
methodInfo := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)})
name := llvm.ConstExtractValue(methodInfo, []uint32{0}).Name()
definedMethods[name] = struct{}{}
}
// Check whether all interface methods are also in the list
// of defined methods calculated above.
implements := uint64(1) // i1 true
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
if _, ok := definedMethods[name]; !ok {
// There is a method on the interface that is not
// implemented by the type.
implements = 0 // i1 false
break
}
}
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)}
case callee.Name() == "runtime.nanotime":
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
case callee.Name() == "llvm.dbg.value":
// do nothing
case strings.HasPrefix(callee.Name(), "llvm.lifetime."):
// do nothing
case callee.Name() == "runtime.trackPointer":
// do nothing
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
// This are all print instructions, which necessarily have side
// effects but no results.
@@ -343,7 +541,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
params = append(params, local)
}
var ret Value
scanResult := fr.Eval.hasSideEffects(callee)
scanResult, err := fr.hasSideEffects(callee)
if err != nil {
return nil, nil, err
}
if scanResult.severity == sideEffectLimited || dirtyParams && scanResult.severity != sideEffectAll {
// Side effect is bounded. This means the operation invokes
// side effects (like calling an external function) but it
@@ -366,8 +567,13 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// compile time.
// * Unbounded: cannot call at runtime so we'll try to
// interpret anyway and hope for the best.
ret, err = fr.function(callee, params, fr.pkgName, indent+" ")
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
}
}
@@ -376,7 +582,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
default:
// function pointers, etc.
return nil, nil, &Unsupported{inst}
return nil, nil, fr.unsupportedInstructionError(inst)
}
case !inst.IsAExtractValueInst().IsNil():
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
@@ -386,7 +592,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, errors.New("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())}
}
@@ -399,10 +605,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, errors.New("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
@@ -412,21 +630,25 @@ 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() {
panic("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.IsConstant() {
return nil, nil, errors.New("interp: branch on a non-constant")
} else {
switch cond.ZExtValue() {
case 0: // false
return nil, []llvm.Value{thenBB}, nil // then
case 1: // true
return nil, []llvm.Value{elseBB}, nil // else
default:
panic("branch was not true or false")
}
if !cond.IsAInstruction().IsNil() {
return nil, nil, fr.errorAt(inst, errors.New("interp: branch on a non-constant"))
}
if !cond.IsAConstantExpr().IsNil() {
// This may happen when the instruction builder could not
// const-fold some instructions.
return nil, nil, fr.errorAt(inst, errors.New("interp: branch on a non-const-propagated constant expression"))
}
switch cond {
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
return nil, []llvm.Value{thenBB}, nil // then
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
return nil, []llvm.Value{elseBB}, nil // else
default:
return nil, nil, fr.errorAt(inst, errors.New("branch was not true or false"))
}
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
// unconditional branch (goto)
@@ -434,10 +656,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case !inst.IsAUnreachableInst().IsNil():
// Unreachable was reached (e.g. after a call to panic()).
// Report this as an error, as it is not supposed to happen.
return nil, nil, ErrUnreachable
// This is a sentinel error value.
return nil, nil, errUnreachable
default:
return nil, nil, &Unsupported{inst}
return nil, nil, fr.unsupportedInstructionError(inst)
}
}
@@ -451,6 +674,7 @@ func (fr *frame) getLocal(v llvm.Value) Value {
} else if value := fr.getValue(v); value != nil {
return value
} else {
// This should not happen under normal circumstances.
panic("cannot find value")
}
}
+39 -27
View File
@@ -7,10 +7,9 @@ package interp
// methods.
import (
"errors"
"strings"
"github.com/aykevl/go-llvm"
"tinygo.org/x/go-llvm"
)
type Eval struct {
@@ -18,14 +17,21 @@ type Eval struct {
TargetData llvm.TargetData
Debug bool
builder llvm.Builder
dibuilder *llvm.DIBuilder
dirtyGlobals map[llvm.Value]struct{}
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
}
// evalPackage encapsulates the Eval type for just a single package. The Eval
// type keeps state across the whole program, the evalPackage type keeps extra
// state for the currently interpreted package.
type evalPackage struct {
*Eval
packagePath string
}
// Run evaluates the function with the given name and then eliminates all
// callers.
func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
func Run(mod llvm.Module, debug bool) error {
if debug {
println("\ncompile-time evaluation:")
}
@@ -33,24 +39,31 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
name := "runtime.initAll"
e := &Eval{
Mod: mod,
TargetData: targetData,
TargetData: llvm.NewTargetData(mod.DataLayout()),
Debug: debug,
dirtyGlobals: map[llvm.Value]struct{}{},
}
e.builder = mod.Context().NewBuilder()
e.dibuilder = llvm.NewDIBuilder(mod)
initAll := mod.NamedFunction(name)
bb := initAll.EntryBasicBlock()
e.builder.SetInsertPointBefore(bb.LastInstruction())
e.builder.SetInstDebugLocation(bb.FirstInstruction())
// Create a dummy alloca in the entry block that we can set the insert point
// to. This is necessary because otherwise we might be removing the
// instruction (init call) that we are removing after successful
// interpretation.
e.builder.SetInsertPointBefore(bb.FirstInstruction())
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
e.builder.SetInsertPointBefore(dummy)
var initCalls []llvm.Value
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst == dummy {
continue
}
if !inst.IsAReturnInst().IsNil() {
break // ret void
}
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
return errors.New("expected all instructions in " + name + " to be direct calls")
return errorAt(inst, "interp: expected all instructions in "+name+" to be direct calls")
}
initCalls = append(initCalls, inst)
}
@@ -60,32 +73,35 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
for _, call := range initCalls {
initName := call.CalledValue().Name()
if !strings.HasSuffix(initName, ".init") {
return errors.New("expected all instructions in " + name + " to be *.init() calls")
return errorAt(call, "interp: expected all instructions in "+name+" to be *.init() calls")
}
pkgName := initName[:len(initName)-5]
_, err := e.Function(call.CalledValue(), []Value{&LocalValue{e, undefPtr}}, pkgName)
if err == ErrUnreachable {
fn := call.CalledValue()
call.EraseFromParentAsInstruction()
evalPkg := evalPackage{
Eval: e,
packagePath: pkgName,
}
_, err := evalPkg.function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, "")
if err == errUnreachable {
break
}
if err != nil {
return err
}
call.EraseFromParentAsInstruction()
}
return nil
}
func (e *Eval) Function(fn llvm.Value, params []Value, pkgName string) (Value, error) {
return e.function(fn, params, pkgName, "")
}
func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (Value, 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) {
fr := frame{
Eval: e,
fn: fn,
pkgName: pkgName,
locals: make(map[llvm.Value]Value),
evalPackage: e,
fn: fn,
locals: make(map[llvm.Value]Value),
}
for i, param := range fn.Params() {
fr.locals[param] = params[i]
@@ -114,11 +130,7 @@ func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (
// getValue determines what kind of LLVM value it gets and returns the
// appropriate Value type.
func (e *Eval) getValue(v llvm.Value) Value {
if !v.IsAGlobalVariable().IsNil() {
return &GlobalValue{e, v}
} else {
return &LocalValue{e, v}
}
return &LocalValue{e, v}
}
// markDirty marks the passed-in LLVM value dirty, recursively. For example,
+103
View File
@@ -0,0 +1,103 @@
package interp
import (
"io/ioutil"
"os"
"strings"
"testing"
"tinygo.org/x/go-llvm"
)
func TestInterp(t *testing.T) {
for _, name := range []string{
"basic",
"slice-copy",
"consteval",
"map",
"interface",
} {
name := name // make tc local to this closure
t.Run(name, func(t *testing.T) {
t.Parallel()
runTest(t, "testdata/"+name)
})
}
}
func runTest(t *testing.T, pathPrefix string) {
// Read the input IR.
ctx := llvm.NewContext()
buf, err := llvm.NewMemoryBufferFromFile(pathPrefix + ".ll")
os.Stat(pathPrefix + ".ll") // make sure this file is tracked by `go test` caching
if err != nil {
t.Fatalf("could not read file %s: %v", pathPrefix+".ll", err)
}
mod, err := ctx.ParseIR(buf)
if err != nil {
t.Fatalf("could not load module:\n%v", err)
}
// Perform the transform.
err = Run(mod, false)
if err != nil {
t.Fatal(err)
}
// Run some cleanup passes to get easy-to-read outputs.
pm := llvm.NewPassManager()
defer pm.Dispose()
pm.AddGlobalOptimizerPass()
pm.AddDeadStoreEliminationPass()
pm.Run(mod)
// Read the expected output IR.
out, err := ioutil.ReadFile(pathPrefix + ".out.ll")
if err != nil {
t.Fatalf("could not read output file %s: %v", pathPrefix+".out.ll", err)
}
// See whether the transform output matches with the expected output IR.
expected := string(out)
actual := mod.String()
if !fuzzyEqualIR(expected, actual) {
t.Logf("output does not match expected output:\n%s", actual)
t.Fail()
}
}
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
// equal. That means, only relevant lines are compared (excluding comments
// etc.).
func fuzzyEqualIR(s1, s2 string) bool {
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
if len(lines1) != len(lines2) {
return false
}
for i, line := range lines1 {
if line != lines2[i] {
return false
}
}
return true
}
// filterIrrelevantIRLines removes lines from the input slice of strings that
// are not relevant in comparing IR. For example, empty lines and comments are
// stripped out.
func filterIrrelevantIRLines(lines []string) []string {
var out []string
for _, line := range lines {
line = strings.TrimSpace(line) // drop '\r' on Windows
if line == "" || line[0] == ';' {
continue
}
if strings.HasPrefix(line, "source_filename = ") {
continue
}
out = append(out, line)
}
return out
}
+95 -20
View File
@@ -1,11 +1,29 @@
package interp
import (
"github.com/aykevl/go-llvm"
"errors"
"strings"
"tinygo.org/x/go-llvm"
)
type sideEffectSeverity int
func (severity sideEffectSeverity) String() string {
switch severity {
case sideEffectInProgress:
return "in progress"
case sideEffectNone:
return "none"
case sideEffectLimited:
return "limited"
case sideEffectAll:
return "all"
default:
return "unknown"
}
}
const (
sideEffectInProgress sideEffectSeverity = iota // computing side effects is in progress (for recursive functions)
sideEffectNone // no side effects at all (pure)
@@ -23,12 +41,42 @@ 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 *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error) {
name := fn.Name()
switch {
case name == "runtime.alloc":
// Cannot be scanned but can be interpreted.
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.nanotime":
// Fixed value at compile time.
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime._panic":
return &sideEffectResult{severity: sideEffectLimited}, nil
case name == "runtime.typeAssert":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.sliceCopy":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.trackPointer":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "(*sync/atomic.Value).Load" || name == "(*sync/atomic.Value).Store":
// These functions do some unsafe pointer loading/storing but are
// otherwise safe.
return &sideEffectResult{severity: sideEffectLimited}, nil
case strings.HasPrefix(name, "llvm.lifetime."):
return &sideEffectResult{severity: sideEffectNone}, nil
}
if fn.IsDeclaration() {
return &sideEffectResult{severity: sideEffectLimited}, nil
}
if e.sideEffectFuncs == nil {
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
}
if se, ok := e.sideEffectFuncs[fn]; ok {
return se
return se, nil
}
result := &sideEffectResult{
severity: sideEffectInProgress,
@@ -39,6 +87,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if inst.IsAInstruction().IsNil() {
// Should not happen in valid IR.
panic("not an instruction")
}
@@ -55,7 +104,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
switch inst.InstructionOpcode() {
case llvm.IndirectBr, llvm.Invoke:
// Not emitted by the compiler.
panic("unknown instructions")
return nil, e.errorAt(inst, errors.New("unknown instructions"))
case llvm.Call:
child := inst.CalledValue()
if !child.IsAInlineAsm().IsNil() {
@@ -73,27 +122,49 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
result.updateSeverity(sideEffectAll)
continue
}
name := child.Name()
if child.IsDeclaration() {
if name == "runtime.makeInterface" {
// Can be interpreted so does not have side effects.
continue
}
// External function call. Assume only limited side effects
// (no affected globals, etc.).
if result.hasLocalSideEffects(dirtyLocals, inst) {
switch child.Name() {
case "runtime.alloc":
continue
case "runtime.typeAssert":
continue // implemented in interp
case "runtime.interfaceImplements":
continue // implemented in interp
}
if e.hasLocalSideEffects(dirtyLocals, inst) {
result.updateSeverity(sideEffectLimited)
}
continue
}
childSideEffects := e.hasSideEffects(fn)
childSideEffects, err := e.hasSideEffects(child)
if err != nil {
return nil, err
}
switch childSideEffects.severity {
case sideEffectInProgress, sideEffectNone:
// no side effects or recursive function - continue scanning
case sideEffectLimited:
// The return value may be problematic.
if e.hasLocalSideEffects(dirtyLocals, inst) {
result.updateSeverity(sideEffectLimited)
}
case sideEffectAll:
result.updateSeverity(sideEffectAll)
default:
result.update(childSideEffects)
panic("unreachable")
}
case llvm.Load, llvm.Store:
case llvm.Load:
if inst.IsVolatile() {
result.updateSeverity(sideEffectLimited)
}
if _, ok := e.dirtyGlobals[inst.Operand(0)]; ok {
if e.hasLocalSideEffects(dirtyLocals, inst) {
result.updateSeverity(sideEffectLimited)
}
}
case llvm.Store:
if inst.IsVolatile() {
result.updateSeverity(sideEffectLimited)
}
@@ -112,13 +183,13 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
// No side effect was reported for this function.
result.severity = sideEffectNone
}
return result
return result, nil
}
// hasLocalSideEffects checks whether the given instruction flows into a branch
// or return instruction, in which case the whole function must be marked as
// having side effects and be called at runtime.
func (r *sideEffectResult) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
if _, ok := dirtyLocals[inst]; ok {
// It is already known that this local is dirty.
return true
@@ -127,6 +198,7 @@ func (r *sideEffectResult) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct
for use := inst.FirstUse(); !use.IsNil(); use = use.NextUse() {
user := use.User()
if user.IsAInstruction().IsNil() {
// Should not happen in valid IR.
panic("user not an instruction")
}
switch user.InstructionOpcode() {
@@ -146,17 +218,20 @@ func (r *sideEffectResult) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct
// Already handled in (*Eval).hasSideEffects.
continue
}
// But a store might also store to an alloca, in which case all uses
// of the alloca (possibly indirect through a GEP, bitcast, etc.)
// must be marked dirty.
panic("todo: store")
// This store might affect all kinds of values. While it is
// certainly possible to traverse through all of them, the easiest
// option right now is to just assume the worst and say that this
// function has side effects.
// TODO: traverse through all stores and mark all relevant allocas /
// globals dirty.
return true
default:
// All instructions that take 0 or more operands (1 or more if it
// was a use) and produce a result.
// For a list:
// https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode
dirtyLocals[user] = struct{}{}
if r.hasLocalSideEffects(dirtyLocals, user) {
if e.hasLocalSideEffects(dirtyLocals, user) {
return true
}
}
+95
View File
@@ -0,0 +1,95 @@
package interp
import (
"os"
"sort"
"testing"
"tinygo.org/x/go-llvm"
)
var scanTestTable = []struct {
name string
severity sideEffectSeverity
mentionsGlobals []string
}{
{"returnsConst", sideEffectNone, nil},
{"returnsArg", sideEffectNone, nil},
{"externalCallOnly", sideEffectNone, nil},
{"externalCallAndReturn", sideEffectLimited, nil},
{"externalCallBranch", sideEffectLimited, nil},
{"readCleanGlobal", sideEffectNone, []string{"cleanGlobalInt"}},
{"readDirtyGlobal", sideEffectLimited, []string{"dirtyGlobalInt"}},
{"callFunctionPointer", sideEffectAll, []string{"functionPointer"}},
{"getDirtyPointer", sideEffectLimited, nil},
{"storeToPointer", sideEffectLimited, nil},
{"callTypeAssert", sideEffectNone, nil},
{"callInterfaceImplements", sideEffectNone, nil},
}
func TestScan(t *testing.T) {
t.Parallel()
// Read the input IR.
path := "testdata/scan.ll"
ctx := llvm.NewContext()
buf, err := llvm.NewMemoryBufferFromFile(path)
os.Stat(path) // make sure this file is tracked by `go test` caching
if err != nil {
t.Fatalf("could not read file %s: %v", path, err)
}
mod, err := ctx.ParseIR(buf)
if err != nil {
t.Fatalf("could not load module:\n%v", err)
}
// Check all to-be-tested functions.
for _, tc := range scanTestTable {
// Create an eval object, for testing.
e := &Eval{
Mod: mod,
TargetData: llvm.NewTargetData(mod.DataLayout()),
dirtyGlobals: map[llvm.Value]struct{}{},
}
// Mark some globals dirty, for testing.
e.markDirty(mod.NamedGlobal("dirtyGlobalInt"))
// Scan for side effects.
fn := mod.NamedFunction(tc.name)
if fn.IsNil() {
t.Errorf("scan test: could not find tested function %s in the IR", tc.name)
continue
}
evalPkg := &evalPackage{e, "testdata"}
result, err := evalPkg.hasSideEffects(fn)
if err != nil {
t.Errorf("scan test: failed to scan %s for side effects: %v", fn.Name(), err)
}
// Check whether the result is what we expect.
if result.severity != tc.severity {
t.Errorf("scan test: function %s should have severity %s but it has %s", tc.name, tc.severity, result.severity)
}
// Check whether the mentioned globals match with what we'd expect.
mentionsGlobalNames := make([]string, 0, len(result.mentionsGlobals))
for global := range result.mentionsGlobals {
mentionsGlobalNames = append(mentionsGlobalNames, global.Name())
}
sort.Strings(mentionsGlobalNames)
globalsMismatch := false
if len(result.mentionsGlobals) != len(tc.mentionsGlobals) {
globalsMismatch = true
} else {
for i, globalName := range mentionsGlobalNames {
if tc.mentionsGlobals[i] != globalName {
globalsMismatch = true
}
}
}
if globalsMismatch {
t.Errorf("scan test: expected %s to mention globals %v, but it mentions globals %v", tc.name, tc.mentionsGlobals, mentionsGlobalNames)
}
}
}

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