Compare commits

...

396 Commits

Author SHA1 Message Date
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
400 changed files with 26363 additions and 6161 deletions
+149 -50
View File
@@ -18,15 +18,12 @@ commands:
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
sudo apt-get update
sudo apt-get install \
python3 \
llvm<<parameters.llvm>>-dev \
clang<<parameters.llvm>> \
libclang<<parameters.llvm>>-dev \
lld<<parameters.llvm>> \
gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \
qemu-user \
gcc-avr \
@@ -44,43 +41,58 @@ commands:
steps:
- restore_cache:
keys:
- llvm-source-8-v3
- llvm-source-9-v0
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-8-v3
key: llvm-source-9-v0
paths:
- llvm
smoketest:
- llvm-project
build-wasi-libc:
steps:
- run: make smoketest
smoketest-no-avr:
steps:
- run: make smoketest-no-avr
- restore_cache:
keys:
- wasi-libc-sysroot-v0
- run:
name: "Build wasi-libc"
command: make wasi-libc CLANG=$PWD/llvm-build/bin/clang LLVM_AR=$PWD/llvm-build/bin/llvm-ar LLVM_NM=$PWD/llvm-build/bin/llvm-nm
- save_cache:
key: wasi-libc-sysroot-v0
paths:
- lib/wasi-libc/sysroot
test-linux:
steps:
- checkout
- submodules
- apt-dependencies:
llvm: "-8"
llvm: "-9"
- install-node
- restore_cache:
keys:
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- llvm-source-linux
- run: go install .
- run: go test -v
- run: make gen-device -j4
- smoketest
- restore_cache:
keys:
- wasi-libc-sysroot-systemclang-v0
- run: make wasi-libc
- save_cache:
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: wasi-libc-sysroot-systemclang-v0
paths:
- lib/wasi-libc/sysroot
- run: go test -v ./cgo ./compileopts ./interp ./transform .
- run: make gen-device -j4
- run: make smoketest
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run: make fmt-check
build-linux:
assert-test-linux:
steps:
- checkout
- submodules
@@ -88,7 +100,6 @@ commands:
name: "Install apt dependencies"
command: |
sudo apt-get install \
python3 \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
libc6-dev-armel-cross \
@@ -101,12 +112,71 @@ commands:
- install-node
- restore_cache:
keys:
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- 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-8-linux-v5
- llvm-build-9-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-9-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
- ~/.cache/tinygo
- /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-9-linux-v0
- run:
name: "Build LLVM"
command: |
@@ -124,15 +194,10 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-8-linux-v5
key: llvm-build-9-linux-v0
paths:
llvm-build
- run:
name: "Create LLVM symlinks"
command: |
ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8
- build-wasi-libc
- run:
name: "Test TinyGo"
command: make test
@@ -144,10 +209,11 @@ commands:
- store_artifacts:
path: /tmp/tinygo.linux-amd64.tar.gz
- save_cache:
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run:
name: "Extract release tarball"
command: |
@@ -155,7 +221,7 @@ commands:
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
tinygo version
- smoketest
- run: make smoketest
build-macos:
steps:
- checkout
@@ -163,23 +229,27 @@ commands:
- run:
name: "Install dependencies"
command: |
curl https://dl.google.com/go/go1.12.5.darwin-amd64.tar.gz -o go1.12.5.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.12.5.darwin-amd64.tar.gz
curl https://dl.google.com/go/go1.13.darwin-amd64.tar.gz -o go1.13.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.13.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- restore_cache:
keys:
- llvm-source-8-macos-v3
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-9-macos-v0
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-8-macos-v3
key: llvm-source-9-macos-v0
paths:
- llvm
- llvm-project
- restore_cache:
keys:
- llvm-build-8-macos-v4
- llvm-build-9-macos-v0
- run:
name: "Build LLVM"
command: |
@@ -191,13 +261,19 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-8-macos-v4
key: llvm-build-9-macos-v0
paths:
llvm-build
- restore_cache:
keys:
- wasi-libc-sysroot-macos-v0
- run:
name: "Create LLVM symlinks"
command: |
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
name: "Build wasi-libc"
command: make wasi-libc CLANG=$PWD/llvm-build/bin/clang LLVM_AR=$PWD/llvm-build/bin/llvm-ar LLVM_NM=$PWD/llvm-build/bin/llvm-nm
- save_cache:
key: wasi-libc-sysroot-macos-v0
paths:
- lib/wasi-libc/sysroot
- run:
name: "Test TinyGo"
command: make test
@@ -215,23 +291,44 @@ commands:
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
tinygo version
- smoketest-no-avr
- run:
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
jobs:
test-llvm8-go111:
test-llvm9-go111:
docker:
- image: circleci/golang:1.11
- image: circleci/golang:1.11-stretch
steps:
- test-linux
test-llvm8-go112:
test-llvm9-go112:
docker:
- image: circleci/golang:1.12
- image: circleci/golang:1.12-stretch
steps:
- test-linux
test-llvm9-go113:
docker:
- image: circleci/golang:1.13-stretch
steps:
- test-linux
assert-test-linux:
docker:
- image: circleci/golang:1.13-stretch
steps:
- assert-test-linux
build-linux:
docker:
- image: circleci/golang:1.12
- image: circleci/golang:1.13-stretch
steps:
- build-linux
build-macos:
@@ -246,7 +343,9 @@ jobs:
workflows:
test-all:
jobs:
- test-llvm8-go111
- test-llvm8-go112
- test-llvm9-go111
- test-llvm9-go112
- test-llvm9-go113
- build-linux
- build-macos
- assert-test-linux
+5 -3
View File
@@ -5,10 +5,12 @@ src/device/avr/*.ld
src/device/avr/*.s
src/device/nrf/*.go
src/device/nrf/*.s
src/device/stm32/*.go
src/device/stm32/*.s
src/device/sam/*.go
src/device/sam/*.s
src/device/sifive/*.go
src/device/sifive/*.s
src/device/stm32/*.go
src/device/stm32/*.s
vendor
llvm
llvm-build
llvm-project
+4 -1
View File
@@ -9,8 +9,11 @@
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_80
[submodule "lib/wasi-libc"]
path = lib/wasi-libc
url = https://github.com/CraneStation/wasi-libc
+8 -5
View File
@@ -16,7 +16,6 @@ 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+)
* [dep](https://golang.github.io/dep/)
* Standard build tools (gcc/clang)
* git
* CMake
@@ -27,16 +26,20 @@ on a different system like Mac.
## Download the source
The first step is to download the TinyGo sources. Then, inside the directory,
perform these steps:
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:
dep ensure -vendor-only # download Go dependencies
make llvm-source # download LLVM
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
+282
View File
@@ -1,3 +1,285 @@
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**
+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
+4
View File
@@ -16,12 +16,16 @@ Please open a Github issue with your problem, and we will be happy to assist.
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
### Some specific hardware you want to use does not appear to be in TinyGo
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
Please start by opening a Github issue. We want to help you to help us to help you.
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
+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>
+33 -43
View File
@@ -1,52 +1,48 @@
# TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
FROM golang:latest AS tinygo-base
# TinyGo base stage installs Go 1.13, LLVM 9 and the TinyGo compiler itself.
FROM golang:1.13 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-8 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-8-dev libclang-8-dev git
apt-get install -y llvm-9-dev libclang-9-dev git
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
COPY . /go/src/github.com/tinygo-org/tinygo
COPY . /tinygo
# remove submodules directories and re-init them to fix any hard-coded paths
# after copying the tinygo directory in the previous step.
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
RUN cd /tinygo/ && \
rm -rf ./lib/* && \
git submodule update --init --recursive --force
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
dep ensure --vendor-only && \
go install /go/src/github.com/tinygo-org/tinygo/
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/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/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-8 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y libllvm8 lld-8
apt-get install -y libllvm9 lld-9
# 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/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/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/tinygo-org/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 && \
apt-get autoremove -y && \
apt-get clean
@@ -54,33 +50,27 @@ RUN cd /go/src/github.com/tinygo-org/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/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/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/tinygo-org/tinygo/ && \
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make clang-8 && \
make gen-device-nrf && make gen-device-stm32 && \
apt-get remove -y python3 && \
apt-get autoremove -y && \
apt-get clean
apt-get install -y apt-utils make clang-9 && \
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/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/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/tinygo-org/tinygo/ && \
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
make gen-device && \
apt-get remove -y python3 && \
apt-get autoremove -y && \
apt-get clean
apt-get install -y apt-utils make clang-9 binutils-avr gcc-avr avr-libc && \
make gen-device
CMD ["tinygo"]
Generated
-51
View File
@@ -1,51 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
name = "github.com/blakesmith/ar"
packages = ["."]
pruneopts = "UT"
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
[[projects]]
branch = "master"
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
name = "github.com/marcinbor85/gohex"
packages = ["."]
pruneopts = "UT"
revision = "7a43cd876e46e0f6ddc553f10f91731a78e6e949"
[[projects]]
branch = "master"
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
name = "golang.org/x/tools"
packages = [
"go/ast/astutil",
"go/ssa",
"go/types/typeutil",
]
pruneopts = "UT"
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
[[projects]]
branch = "llvm8"
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
name = "tinygo.org/x/go-llvm"
packages = ["."]
pruneopts = "UT"
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/blakesmith/ar",
"github.com/marcinbor85/gohex",
"golang.org/x/tools/go/ast/astutil",
"golang.org/x/tools/go/ssa",
"tinygo.org/x/go-llvm",
]
solver-name = "gps-cdcl"
solver-version = 1
-11
View File
@@ -1,11 +0,0 @@
[[constraint]]
branch = "llvm8"
name = "tinygo.org/x/go-llvm"
[[constraint]]
branch = "master"
name = "golang.org/x/tools"
[prune]
go-tests = true
unused-packages = true
+3
View File
@@ -3,6 +3,9 @@ Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library.
Copyright (c) 2009-2019 The Go Authors. All rights reserved.
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
met:
+236 -80
View File
@@ -1,139 +1,294 @@
# aliases
all: tinygo
tinygo: build/tinygo
.PHONY: all tinygo build/tinygo test llvm-build llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
# Default build and source directories, as created by `make llvm-build`.
LLVM_BUILDDIR ?= llvm-build
CLANG_SRC ?= llvm/tools/clang
LLD_SRC ?= llvm/tools/lld
CLANG_SRC ?= llvm-project/clang
LLD_SRC ?= llvm-project/lld
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
# Default tool selection.
CLANG ?= clang-9
LLVM_AR ?= llvm-ar-9
LLVM_NM ?= llvm-nm-9
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
# Go binary and GOROOT to select
GO ?= go
export GOROOT = $(shell $(GO) env GOROOT)
# md5sum binary
MD5SUM = md5sum
# tinygo binary for tests
TINYGO ?= tinygo
# Use CCACHE for LLVM if possible
ifneq (, $(shell which ccache))
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-avr
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
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) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor $(END_GROUP) -lstdc++
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.
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++11
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++11
CGO_LDFLAGS+=$(LIBCLANG_PATH) -std=c++11 -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
endif
clean:
@rm -rf build
FMT_PATHS = ./*.go cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
FMT_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:
@gofmt -l -w $(FMT_PATHS)
fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
gen-device-avr:
./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
./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-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
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-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/README.txt:
git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
llvm/tools/clang/README.txt:
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
llvm/tools/lld/README.md:
git clone -b release_80 https://github.com/llvm-mirror/lld.git llvm/tools/lld
llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.md
llvm-project/README.md:
git clone -b release/9.x https://github.com/llvm/llvm-project
llvm-source: llvm-project/README.md
# Configure LLVM.
llvm-build/build.ninja: llvm-source
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
# Build LLVM.
llvm-build: llvm-build/build.ninja
cd llvm-build; ninja
$(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)
# Build the Go compiler.
build/tinygo:
@if [ ! -f llvm-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
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 .
test:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform .
.PHONY: smoketest smoketest-no-avr
smoketest: smoketest-no-avr
tinygo build -size short -o test.elf -target=arduino examples/blinky1
tinygo build -size short -o test.elf -target=digispark examples/blinky1
smoketest-no-avr:
tinygo-test:
cd tests/tinygotest && tinygo test
.PHONY: smoketest
smoketest:
$(TINYGO) version
# test all examples
tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
tinygo build -size short -o test.elf -target=pca10040 examples/adc
tinygo build -size short -o test.elf -target=pca10040 examples/blinkm
tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
tinygo build -size short -o test.elf -target=pca10040 examples/button
tinygo build -size short -o test.elf -target=pca10040 examples/button2
tinygo build -size short -o test.elf -target=pca10040 examples/echo
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
tinygo build -size short -o test.elf -target=pca10040 examples/mcp3008
tinygo build -size short -o test.elf -target=microbit examples/microbit-blink
tinygo build -size short -o test.elf -target=pca10040 examples/pwm
tinygo build -size short -o test.elf -target=pca10040 examples/serial
tinygo build -size short -o test.elf -target=pca10040 examples/test
$(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/pwm
@$(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=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 -o test.elf examples/blinky2 # TODO: re-enable -size flag with MachO support
tinygo build -size short -o test.elf -target=microbit examples/echo
tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
tinygo build -size short -o test.elf -target=bluepill examples/blinky1
tinygo build -size short -o test.elf -target=reelboard examples/blinky1
tinygo build -size short -o test.elf -target=reelboard examples/blinky2
tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
tinygo build -size short -o test.elf -target=feather-m0 examples/blinky1
tinygo build -size short -o test.elf -target=trinket-m0 examples/blinky1
tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
tinygo build -o wasm.wasm -target=wasm examples/wasm/export
tinygo build -o wasm.wasm -target=wasm examples/wasm/main
$(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=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=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.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=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
ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=arduino 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 -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
release: build/tinygo gen-device
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/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 build/release/tinygo/bin
@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
@@ -141,6 +296,7 @@ release: build/tinygo gen-device
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
@cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets
./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a
+21 -6
View File
@@ -1,8 +1,8 @@
# TinyGo - Go compiler for small places
[![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
[![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)
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
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.
@@ -43,22 +43,35 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 14 microcontroller boards are currently supported:
The following 27 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [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 PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [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/)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
* [Digispark](http://digistump.com/products/1)
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [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)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
* [PineTime DevKit](https://www.pine64.org/pinetime/)
* [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/)
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
@@ -117,3 +130,5 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic
## License
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/9.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
+84
View File
@@ -0,0 +1,84 @@
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
# be built anyway.
trigger:
- master
- dev
jobs:
- job: Build
timeoutInMinutes: 240 # 4h
pool:
vmImage: 'VS2017-Win2016'
steps:
- checkout: self
- task: CacheBeta@0
displayName: Cache LLVM source
inputs:
key: llvm-source-9-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-9-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
# LLVM 9 cannot be built with MinGW 8.
# For details: https://reviews.llvm.org/D70266
choco uninstall mingw
choco install mingw --version=7.3.0
make llvm-build
fi
- task: Bash@3
displayName: Install QEMU
inputs:
targetType: inline
script: choco install qemu
- task: CacheBeta@0
displayName: Cache wasi-libc sysroot
inputs:
key: wasi-libc-sysroot-v0
path: lib/wasi-libc/sysroot
- task: Bash@3
displayName: Build wasi-libc
inputs:
targetType: inline
script: PATH=/usr/bin:$PATH make wasi-libc CLANG=$PWD/llvm-build/bin/clang LLVM_AR=$PWD/llvm-build/bin/llvm-ar LLVM_NM=$PWD/llvm-build/bin/llvm-nm
- task: Bash@3
displayName: Test TinyGo
inputs:
targetType: inline
script: |
export PATH="/c/Go1.13/bin:$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="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make release -j4
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
displayName: Publish zip as artifact
artifact: tinygo
- task: Bash@3
displayName: Smoke tests
inputs:
targetType: inline
script: |
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
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
}
+218
View File
@@ -0,0 +1,218 @@
// 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 (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"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/transform"
)
// Build performs a single package to executable Go build. It takes in a package
// name, an output path, and set of compile options and from that it manages the
// whole compilation process.
//
// The error value may be of type *MultiError. Callers will likely want to check
// for this case and print such errors individually.
func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error {
c, err := compiler.NewCompiler(pkgName, config)
if err != nil {
return err
}
// Compile Go code to IR.
errs := c.Compile(pkgName)
if len(errs) != 0 {
return newMultiError(errs)
}
if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:")
fmt.Println(c.IR())
}
if err := c.Verify(); err != nil {
return errors.New("verification error after IR construction")
}
err = interp.Run(c.Module(), config.DumpSSA())
if err != nil {
return err
}
if err := c.Verify(); err != nil {
return errors.New("verification error after interpreting runtime.initAll")
}
if config.GOOS() != "darwin" {
c.ApplyFunctionSections() // -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(c.Module())
if err != nil {
return err
}
}
// Optimization levels here are roughly the same as Clang, but probably not
// exactly.
errs = nil
switch config.Options.Opt {
case "none:", "0":
errs = c.Optimize(0, 0, 0) // -O0
case "1":
errs = c.Optimize(1, 0, 0) // -O1
case "2":
errs = c.Optimize(2, 0, 225) // -O2
case "s":
errs = c.Optimize(2, 1, 225) // -Os
case "z":
errs = c.Optimize(2, 2, 5) // -Oz, default
default:
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
}
if len(errs) > 0 {
return newMultiError(errs)
}
if err := c.Verify(); err != nil {
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") {
c.NonConstGlobals()
if err := c.Verify(); 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":
return c.EmitObject(outpath)
case ".bc":
return c.EmitBitcode(outpath)
case ".ll":
return c.EmitText(outpath)
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")
err = c.EmitObject(objfile)
if err != nil {
return err
}
// Load builtins library from the cache, possibly compiling it on the
// fly.
var librt string
if config.Target.RTLib == "compiler-rt" {
librt, err = loadBuiltins(config.Triple())
if err != nil {
return err
}
}
// Prepare link command.
executable := filepath.Join(dir, "main")
tmppath := executable // final file
ldflags := append(config.LDFlags(), "-o", executable, objfile)
if config.Target.RTLib == "compiler-rt" {
ldflags = append(ldflags, librt)
}
// 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, pkg := range c.Packages() {
for _, file := range pkg.CFiles {
path := filepath.Join(pkg.Package.Dir, file)
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, path)...)
if err != nil {
return &commandError{"failed to build", path, err}
}
ldflags = append(ldflags, outpath)
}
}
// 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)
}
}
// 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)
}
}
+7 -15
View File
@@ -1,20 +1,13 @@
package main
package builder
import (
"io"
"os"
"path/filepath"
"time"
)
// Get the cache directory, usually ~/.cache/tinygo
func cacheDir() string {
dir, err := os.UserCacheDir()
if err != nil {
panic("could not find cache dir: " + err.Error())
}
return filepath.Join(dir, "tinygo")
}
"github.com/tinygo-org/tinygo/goenv"
)
// Return the newest timestamp of all the file paths passed in. Used to check
// for stale caches.
@@ -41,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
@@ -76,7 +68,7 @@ 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
@@ -117,10 +109,10 @@ func moveFile(src, dst string) error {
return err
}
err = os.Rename(dst+".tmp", dst)
err = outf.Close()
if err != nil {
return err
}
return outf.Close()
return os.Rename(dst+".tmp", dst)
}
+16 -50
View File
@@ -1,15 +1,12 @@
package main
package builder
import (
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"github.com/blakesmith/ar"
"github.com/tinygo-org/tinygo/goenv"
)
// These are the GENERIC_SOURCES according to CMakeList.txt.
@@ -161,7 +158,7 @@ var aeabiBuiltins = []string{
func builtinFiles(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if strings.HasPrefix(target, "arm") {
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
builtins = append(builtins, aeabiBuiltins...)
}
return builtins
@@ -169,13 +166,13 @@ func builtinFiles(target string) []string {
// builtinsDir returns the directory where the sources for compiler-rt are kept.
func builtinsDir() string {
return filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
return filepath.Join(goenv.Get("TINYGOROOT"), "lib", "compiler-rt", "lib", "builtins")
}
// Get the builtins archive, possibly generating it as needed.
func loadBuiltins(target string) (path string, err error) {
// Try to load a precompiled compiler-rt library.
precompiledPath := filepath.Join(sourceDir(), "pkg", target, "compiler-rt.a")
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, "compiler-rt.a")
if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled compiler-rt for this OS/architecture. Return the
// path directly.
@@ -196,7 +193,7 @@ func loadBuiltins(target string) (path string, err error) {
}
var cachepath string
err = compileBuiltins(target, func(path string) error {
err = CompileBuiltins(target, func(path string) error {
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
cachepath = path
return err
@@ -204,11 +201,11 @@ func loadBuiltins(target string) (path string, err error) {
return cachepath, err
}
// compileBuiltins compiles builtins from compiler-rt into a static library.
// CompileBuiltins compiles builtins from compiler-rt into a static library.
// When it succeeds, it will call the callback with the resulting path. The path
// will be removed after callback returns. If callback returns an error, this is
// passed through to the return value of this function.
func compileBuiltins(target string, callback func(path string) error) error {
func CompileBuiltins(target string, callback func(path string) error) error {
builtinsDir := builtinsDir()
builtins := builtinFiles(target)
@@ -239,56 +236,25 @@ func compileBuiltins(target string, callback func(path string) error) error {
// Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the
// archive itself, which varies each run.
err := execCommand(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
args := []string{"-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target=" + target, "-fdebug-prefix-map=" + dir + "=" + remapDir}
if strings.HasPrefix(target, "riscv32-") {
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
}
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
}
// Put all builtins in an archive to link as a static library.
// Note: this does not create a symbol index, but ld.lld doesn't seem to
// care.
// Put all the object files in a single archive. This archive file will be
// used to statically link compiler-rt.
arpath := filepath.Join(dir, "librt.a")
arfile, err := os.Create(arpath)
err = makeArchive(arpath, objs)
if err != nil {
return err
}
defer arfile.Close()
arwriter := ar.NewWriter(arfile)
err = arwriter.WriteGlobalHeader()
if err != nil {
return &os.PathError{"write ar header", arpath, err}
}
for _, objpath := range objs {
name := filepath.Base(objpath)
objfile, err := os.Open(objpath)
if err != nil {
return err
}
defer objfile.Close()
st, err := objfile.Stat()
if err != nil {
return err
}
arwriter.WriteHeader(&ar.Header{
Name: name,
ModTime: time.Unix(0, 0),
Uid: 0,
Gid: 0,
Mode: 0644,
Size: st.Size(),
})
n, err := io.Copy(arwriter, objfile)
if err != nil {
return err
}
if n != st.Size() {
return errors.New("file modified during ar creation: " + arpath)
}
}
// Give the caller the resulting file. The callback must copy the file,
// because after it returns the temporary directory will be removed.
arfile.Close()
return callback(arpath)
}
+504
View File
@@ -0,0 +1,504 @@
// +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/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.
std::unique_ptr<OptTable> OptTbl(createDriverOptTable());
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.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 = llvm::make_unique<raw_fd_ostream>(
Path, EC, (Binary ? sys::fs::F_None : sys::fs::F_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!");
std::unique_ptr<MCAsmInfo> MAI(TheTarget->createMCAsmInfo(*MRI, Opts.Triple));
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);
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;
MCTargetOptions MCOptions;
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 = llvm::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 = 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.
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) {
std::unique_ptr<OptTable> Opts(driver::createDriverOptTable());
Opts->PrintHelp(llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler",
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
/*ShowAllAliases=*/false);
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 = llvm::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());
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.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/ArrayRef.h"
/// 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 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;
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(),
const_cast<const char **>(CCArgs.data()),
const_cast<const char **>(CCArgs.data()) + CCArgs.size(),
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;
if (!AssemblerInvocation::CreateFromArgs(Asm, llvm::ArrayRef<const char*>(CCArgs).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"
+61
View File
@@ -0,0 +1,61 @@
package builder
import (
"errors"
"os"
"os/exec"
"runtime"
"strings"
)
// Commands lists command alternatives for various operating systems. These
// commands may have a slightly different name across operating systems and
// distributions or may not even exist in $PATH, in which case absolute paths
// may be used.
var commands = map[string][]string{
"clang": {"clang-9"},
"ld.lld": {"ld.lld-9", "ld.lld"},
"wasm-ld": {"wasm-ld-9", "wasm-ld"},
}
func init() {
// Add the path to a Homebrew-installed LLVM 9 for ease of use (no need to
// manually set $PATH).
if runtime.GOOS == "darwin" {
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm@9/bin/clang-9")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm@9/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm@9/bin/wasm-ld")
}
// 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 the llvm90 installed from ports
if runtime.GOOS == "freebsd" {
commands["clang"] = append(commands["clang"], "/usr/local/llvm90/bin/clang-9")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/llvm90/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/llvm90/bin/wasm-ld")
}
}
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, " "))
}
+60
View File
@@ -0,0 +1,60 @@
// +build byollvm
package builder
import (
"errors"
"os"
"os/exec"
"unsafe"
"github.com/tinygo-org/tinygo/goenv"
)
/*
#cgo CXXFLAGS: -fno-rtti
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_clang_driver(int argc, char **argv);
*/
import "C"
// runCCompiler invokes a C compiler with the given arguments.
//
// This version invokes the built-in Clang when trying to run the Clang compiler.
func runCCompiler(command string, flags ...string) error {
switch command {
case "clang":
// Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
return errors.New("could not locate Clang headers")
}
flags = append(flags, "-I"+headerPath)
flags = append([]string{"tinygo:" + command}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_clang_driver(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to compile using built-in clang")
}
return nil
default:
// Running some other compiler. Maybe it has been defined in the
// commands map (unlikely).
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
// Alternatively, run the compiler directly.
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
}
+24
View File
@@ -0,0 +1,24 @@
// +build !byollvm
package builder
// This file provides a way to a C compiler as an external command. See also:
// clang-external.go
import (
"os"
"os/exec"
)
// runCCompiler invokes a C compiler with the given arguments.
//
// This version always runs the compiler as an external command.
func runCCompiler(command string, flags ...string) error {
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
+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 := getGorootVersion(goroot)
if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 || (minor != 11 && minor != 12 && minor != 13) {
return nil, fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor)
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{
Options: options,
Target: spec,
GoMinorVersion: minor,
ClangHeaders: clangHeaderPath,
TestConfig: options.TestConfig,
}, nil
}
+127
View File
@@ -0,0 +1,127 @@
package builder
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
)
// getGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
func getGorootVersion(goroot string) (major, minor int, err error) {
s, err := GorootVersionString(goroot)
if err != nil {
return 0, 0, err
}
if s == "" || s[:2] != "go" {
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
}
parts := strings.Split(s[2:], ".")
if len(parts) < 2 {
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
}
// Ignore the errors, we don't really handle errors here anyway.
var trailing string
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
if n == 2 && err == io.EOF {
// Means there were no trailing characters (i.e., not an alpha/beta)
err = nil
}
if err != nil {
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
}
return
}
// GorootVersionString returns the version string as reported by the Go
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
// can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`")
matches := r.FindSubmatch(data)
if len(matches) != 2 {
return "", errors.New("Invalid go version output:\n" + string(data))
}
return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else {
return "", err
}
}
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
// multiple locations, which should make it find the directory when installed in
// various ways.
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()
}
@@ -1,6 +1,6 @@
// +build byollvm
package main
package builder
// This file provides a Link() function that uses the bundled lld if possible.
@@ -9,6 +9,8 @@ import (
"os"
"os/exec"
"unsafe"
"github.com/tinygo-org/tinygo/goenv"
)
/*
@@ -19,10 +21,10 @@ bool tinygo_link_wasm(int argc, char **argv);
*/
import "C"
// Link invokes a linker with the given name and flags.
// link invokes a linker with the given name and flags.
//
// This version uses the built-in linker when trying to use lld.
func Link(linker string, flags ...string) error {
func link(linker string, flags ...string) error {
switch linker {
case "ld.lld":
flags = append([]string{"tinygo:" + linker}, flags...)
@@ -63,6 +65,7 @@ func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
}
@@ -1,6 +1,6 @@
// +build !byollvm
package main
package builder
// This file provides a Link() function that always runs an external command. It
// is provided for when tinygo is built without linking to liblld.
@@ -8,17 +8,20 @@ package main
import (
"os"
"os/exec"
"github.com/tinygo-org/tinygo/goenv"
)
// Link invokes a linker with the given name and arguments.
// link invokes a linker with the given name and arguments.
//
// This version always runs the linker as an external command.
func Link(linker string, flags ...string) error {
func link(linker string, flags ...string) error {
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
View File
+23 -19
View File
@@ -1,4 +1,4 @@
package main
package builder
import (
"debug/elf"
@@ -10,31 +10,31 @@ import (
"github.com/marcinbor85/gohex"
)
// ObjcopyError is an error returned by functions that act like objcopy.
type ObjcopyError struct {
// objcopyError is an error returned by functions that act like objcopy.
type objcopyError struct {
Op string
Err error
}
func (e ObjcopyError) Error() string {
func (e objcopyError) Error() string {
if e.Err == nil {
return e.Op
}
return e.Op + ": " + e.Err.Error()
}
type ProgSlice []*elf.Prog
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] }
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
// 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) {
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}
return 0, nil, objcopyError{"failed to open ELF file to extract text segment", err}
}
defer f.Close()
@@ -56,7 +56,7 @@ func ExtractROM(path string) (uint64, []byte, error) {
}
}
progs := make(ProgSlice, 0, 2)
progs := make(progSlice, 0, 2)
for _, prog := range f.Progs {
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
continue
@@ -64,18 +64,18 @@ func ExtractROM(path string) (uint64, []byte, error) {
progs = append(progs, prog)
}
if len(progs) == 0 {
return 0, nil, ObjcopyError{"file does not contain ROM segments: " + path, nil}
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}
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}
return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err}
}
rom = append(rom, data...)
}
@@ -91,9 +91,9 @@ func ExtractROM(path string) (uint64, []byte, error) {
}
}
// Objcopy converts an ELF file to a different (simpler) output file format:
// objcopy converts an ELF file to a different (simpler) output file format:
// .bin or .hex. It extracts only the .text section.
func Objcopy(infile, outfile string) error {
func objcopy(infile, outfile string) error {
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
@@ -101,13 +101,17 @@ func Objcopy(infile, outfile string) error {
defer f.Close()
// Read the .text segment.
addr, data, err := ExtractROM(infile)
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).
@@ -117,7 +121,7 @@ func Objcopy(infile, outfile string) error {
mem := gohex.NewMemory()
err := mem.AddBinary(uint32(addr), data)
if err != nil {
return ObjcopyError{"failed to create .hex file", err}
return objcopyError{"failed to create .hex file", err}
}
mem.DumpIntelHex(f, 16) // TODO: handle error
return nil
+20 -18
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
@@ -115,7 +117,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 +131,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 +150,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 +158,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
}
+50 -29
View File
@@ -1,35 +1,43 @@
// Converts firmware files from BIN to UF2 format before flashing.
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
//
//
package main
import (
"bytes"
"encoding/binary"
"io/ioutil"
"strconv"
)
// ConvertELFFileToUF2File converts an ELF file to a UF2 file.
func ConvertELFFileToUF2File(infile, outfile string) error {
// convertELFFileToUF2File converts an ELF file to a UF2 file.
func convertELFFileToUF2File(infile, outfile string, uf2FamilyID string) error {
// Read the .text segment.
_, data, err := ExtractROM(infile)
targetAddress, data, err := extractROM(infile)
if err != nil {
return err
}
output, _ := ConvertBinToUF2(data)
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) ([]byte, int) {
// 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 := NewUF2Block()
bl, err := newUF2Block(targetAddr, uf2FamilyID)
if err != nil {
return nil, 0, err
}
bl.SetNumBlocks(len(blocks))
for i := 0; i < len(blocks); i++ {
@@ -40,18 +48,17 @@ func ConvertBinToUF2(input []byte) ([]byte, int) {
bl.IncrementAddress(bl.payloadSize)
}
return output, len(blocks)
return output, len(blocks), nil
}
const (
uf2MagicStart0 = 0x0A324655 // "UF2\n"
uf2MagicStart1 = 0x9E5D5157 // Randomly selected
uf2MagicEnd = 0x0AB16F30 // Ditto
uf2StartAddress = 0x2000
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 {
// uf2Block is the structure used for each UF2 code block sent to device.
type uf2Block struct {
magicStart0 uint32
magicStart1 uint32
flags uint32
@@ -64,21 +71,35 @@ type UF2Block struct {
magicEnd uint32
}
// NewUF2Block returns a new UF2Block struct that has been correctly populated
func NewUF2Block() *UF2Block {
return &UF2Block{magicStart0: uf2MagicStart0,
// 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: uf2StartAddress,
flags: 0x0,
familyID: 0x0,
targetAddr: targetAddr,
flags: flags,
familyID: familyID,
payloadSize: 256,
data: make([]byte, 476),
}
}, nil
}
// Bytes converts the UF2Block to a slice of bytes that can be written to file.
func (b *UF2Block) Bytes() []byte {
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)
@@ -95,23 +116,23 @@ func (b *UF2Block) Bytes() []byte {
}
// IncrementAddress moves the target address pointer forward by count bytes.
func (b *UF2Block) IncrementAddress(count uint32) {
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) {
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) {
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) {
func (b *uf2Block) SetNumBlocks(total int) {
b.numBlocks = uint32(total)
}
+702 -68
View File
@@ -12,12 +12,14 @@ package cgo
// source file parsing.
import (
"fmt"
"go/ast"
"go/token"
"sort"
"strconv"
"strings"
"github.com/google/shlex"
"golang.org/x/tools/go/ast/astutil"
)
@@ -36,12 +38,13 @@ type cgoPackage struct {
typedefs map[string]*typedefInfo
elaboratedTypes map[string]*elaboratedTypeInfo
enums map[string]enumInfo
anonStructNum int
}
// constantInfo stores some information about a CGo constant found by libclang
// and declared in the Go AST.
type constantInfo struct {
expr *ast.BasicLit
expr ast.Expr
pos token.Pos
}
@@ -68,8 +71,22 @@ type typedefInfo struct {
// elaboratedTypeInfo contains some information about an elaborated type
// (struct, union) found in the C AST.
type elaboratedTypeInfo struct {
typeExpr ast.Expr
typeExpr *ast.StructType
pos token.Pos
bitfields []bitfieldInfo
unionSize int64 // union size in bytes, nonzero when union getters/setters should be created
unionAlign int64 // union alignment in bytes
}
// bitfieldInfo contains information about a single bitfield in a struct. It
// keeps information about the start, end, and the special (renamed) base field
// of this bitfield.
type bitfieldInfo struct {
field *ast.Field
name string
pos token.Pos
startBit int64
endBit int64 // may be 0 meaning "until the end of the field"
}
// enumInfo contains information about an enum in the C.
@@ -118,6 +135,7 @@ var builtinAliases = map[string]struct{}{
// somehow from C. This is done by adding some typedefs to get the size of each
// type.
const cgoTypes = `
# 1 "<cgo>"
typedef char _Cgo_char;
typedef signed char _Cgo_schar;
typedef unsigned char _Cgo_uchar;
@@ -171,6 +189,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
Name: files[0].Name.Name,
},
Decls: []ast.Decl{
// import "unsafe"
&ast.GenDecl{
TokPos: p.generatedPos,
Tok: token.IMPORT,
@@ -178,6 +197,32 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
unsafeImport,
},
},
// var _ unsafe.Pointer
// This avoids type errors when the unsafe package is never used.
&ast.GenDecl{
Tok: token.VAR,
Specs: []ast.Spec{
&ast.ValueSpec{
Names: []*ast.Ident{
&ast.Ident{
Name: "_",
Obj: &ast.Object{
Kind: ast.Var,
Name: "_",
},
},
},
Type: &ast.SelectorExpr{
X: &ast.Ident{
Name: "unsafe",
},
Sel: &ast.Ident{
Name: "Pointer",
},
},
},
},
},
},
Imports: []*ast.ImportSpec{unsafeImport},
}
@@ -191,6 +236,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
}
// Find `import "C"` statements in the file.
var statements []*ast.GenDecl
for _, f := range files {
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
@@ -207,19 +253,16 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
}
path, err := strconv.Unquote(spec.Path.Value)
if err != nil {
// This should not happen. An import path that is not properly
// quoted should not exist in a correct AST.
panic("could not parse import path: " + err.Error())
}
if path != "C" {
continue
}
cgoComment := genDecl.Doc.Text()
pos := genDecl.Pos()
if genDecl.Doc != nil {
pos = genDecl.Doc.Pos()
}
position := fset.PositionFor(pos, true)
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
// Found a CGo statement.
statements = append(statements, genDecl)
// Remove this import declaration.
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
@@ -230,6 +273,93 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
//ast.Print(fset, f)
}
// Find all #cgo lines.
for _, genDecl := range statements {
if genDecl.Doc == nil {
continue
}
for _, comment := range genDecl.Doc.List {
for {
// Extract the #cgo line, and replace it with spaces.
// Replacing with spaces makes sure that error locations are
// still correct, while not interfering with parsing in any way.
lineStart := strings.Index(comment.Text, "#cgo ")
if lineStart < 0 {
break
}
lineLen := strings.IndexByte(comment.Text[lineStart:], '\n')
if lineLen < 0 {
lineLen = len(comment.Text) - lineStart
}
lineEnd := lineStart + lineLen
line := comment.Text[lineStart:lineEnd]
spaces := make([]byte, len(line))
for i := range spaces {
spaces[i] = ' '
}
lenBefore := len(comment.Text)
comment.Text = comment.Text[:lineStart] + string(spaces) + comment.Text[lineEnd:]
if len(comment.Text) != lenBefore {
println(lenBefore, len(comment.Text))
panic("length of preamble changed!")
}
// Get the text before the colon in the #cgo directive.
colon := strings.IndexByte(line, ':')
if colon < 0 {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart], "missing colon in #cgo line")
continue
}
// Extract the fields before the colon. These fields are a list
// of build tags and the C environment variable.
fields := strings.Fields(line[4:colon])
if len(fields) == 0 {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon-1], "invalid #cgo line")
continue
}
if len(fields) > 1 {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+5], "not implemented: build constraints in #cgo line")
continue
}
name := fields[len(fields)-1]
value := line[colon+1:]
switch name {
case "CFLAGS":
flags, err := shlex.Split(value)
if err != nil {
// TODO: find the exact location where the error happened.
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
continue
}
if err := checkCompilerFlags(name, flags); err != nil {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
continue
}
cflags = append(cflags, flags...)
default:
startPos := strings.LastIndex(line[4:colon], name) + 4
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
continue
}
}
}
}
// Process all CGo imports.
for _, genDecl := range statements {
cgoComment := genDecl.Doc.Text()
pos := genDecl.Pos()
if genDecl.Doc != nil {
pos = genDecl.Doc.Pos()
}
position := fset.PositionFor(pos, true)
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
}
// Declare functions found by libclang.
p.addFuncDecls()
@@ -330,18 +460,18 @@ func (p *cgoPackage) addFuncPtrDecls() {
if len(p.functions) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.VAR,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
names := make([]string, 0, len(p.functions))
for name := range p.functions {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.VAR,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
fn := p.functions[name]
obj := &ast.Object{
Kind: ast.Typ,
@@ -366,34 +496,32 @@ func (p *cgoPackage) addFuncPtrDecls() {
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// addConstDecls declares external C constants in the Go source.
// It adds code like the following to the AST:
//
// const (
// C.CONST_INT = 5
// C.CONST_FLOAT = 5.8
// // ...
// )
// const C.CONST_INT = 5
// const C.CONST_FLOAT = 5.8
// // ...
func (p *cgoPackage) addConstDecls() {
if len(p.constants) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.CONST,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
names := make([]string, 0, len(p.constants))
for name := range p.constants {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.CONST,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
constVal := p.constants[name]
obj := &ast.Object{
Kind: ast.Con,
@@ -409,34 +537,32 @@ func (p *cgoPackage) addConstDecls() {
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// addVarDecls declares external C globals in the Go source.
// It adds code like the following to the AST:
//
// var (
// C.globalInt int
// C.globalBool bool
// // ...
// )
// var C.globalInt int
// var C.globalBool bool
// // ...
func (p *cgoPackage) addVarDecls() {
if len(p.globals) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.VAR,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
names := make([]string, 0, len(p.globals))
for name := range p.globals {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.VAR,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
global := p.globals[name]
obj := &ast.Object{
Kind: ast.Var,
@@ -452,31 +578,29 @@ func (p *cgoPackage) addVarDecls() {
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// addTypeAliases aliases some built-in Go types with their equivalent C types.
// It adds code like the following to the AST:
//
// type (
// C.int8_t = int8
// C.int16_t = int16
// // ...
// )
// type C.int8_t = int8
// type C.int16_t = int16
// // ...
func (p *cgoPackage) addTypeAliases() {
aliasKeys := make([]string, 0, len(cgoAliases))
for key := range cgoAliases {
aliasKeys = append(aliasKeys, key)
}
sort.Strings(aliasKeys)
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
for _, typeName := range aliasKeys {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
goTypeName := cgoAliases[typeName]
obj := &ast.Object{
Kind: ast.Typ,
@@ -496,24 +620,24 @@ func (p *cgoPackage) addTypeAliases() {
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
func (p *cgoPackage) addTypedefs() {
if len(p.typedefs) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
names := make([]string, 0, len(p.typedefs))
for name := range p.typedefs {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
typedef := p.typedefs[name]
typeName := "C." + name
isAlias := true
@@ -542,8 +666,8 @@ func (p *cgoPackage) addTypedefs() {
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
@@ -555,34 +679,518 @@ func (p *cgoPackage) addElaboratedTypes() {
if len(p.elaboratedTypes) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
names := make([]string, 0, len(p.elaboratedTypes))
for name := range p.elaboratedTypes {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
typ := p.elaboratedTypes[name]
typeName := "C." + name
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeExpr := typ.typeExpr
if typ.unionSize != 0 {
// Create getters/setters.
for _, field := range typ.typeExpr.Fields.List {
if len(field.Names) != 1 {
p.addError(typ.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names)))
continue
}
p.createUnionAccessor(field, typeName)
}
// Convert to a single-field struct type.
typeExpr = p.makeUnionField(typ)
if typeExpr == nil {
// There was an error, that was already added to the list of
// errors.
continue
}
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: typ.pos,
Name: typeName,
Obj: obj,
},
Type: typ.typeExpr,
Type: typeExpr,
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
// If this struct has bitfields, create getters for them.
for _, bitfield := range typ.bitfields {
p.createBitfieldGetter(bitfield, typeName)
p.createBitfieldSetter(bitfield, typeName)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// makeUnionField creates a new struct from an existing *elaboratedTypeInfo,
// that has just a single field that must be accessed through special accessors.
// It returns nil when there is an error. In case of an error, that error has
// already been added to the list of errors using p.addError.
func (p *cgoPackage) makeUnionField(typ *elaboratedTypeInfo) *ast.StructType {
unionFieldTypeName, ok := map[int64]string{
1: "uint8",
2: "uint16",
4: "uint32",
8: "uint64",
}[typ.unionAlign]
if !ok {
p.addError(typ.typeExpr.Struct, fmt.Sprintf("expected union alignment to be one of 1, 2, 4, or 8, but got %d", typ.unionAlign))
return nil
}
var unionFieldType ast.Expr = &ast.Ident{
NamePos: token.NoPos,
Name: unionFieldTypeName,
}
if typ.unionSize != typ.unionAlign {
// A plain struct{uintX} isn't enough, we have to make a
// struct{[N]uintX} to make the union big enough.
if typ.unionSize/typ.unionAlign*typ.unionAlign != typ.unionSize {
p.addError(typ.typeExpr.Struct, fmt.Sprintf("union alignment (%d) must be a multiple of union alignment (%d)", typ.unionSize, typ.unionAlign))
return nil
}
unionFieldType = &ast.ArrayType{
Len: &ast.BasicLit{
Kind: token.INT,
Value: strconv.FormatInt(typ.unionSize/typ.unionAlign, 10),
},
Elt: unionFieldType,
}
}
return &ast.StructType{
Struct: typ.typeExpr.Struct,
Fields: &ast.FieldList{
Opening: typ.typeExpr.Fields.Opening,
List: []*ast.Field{&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: typ.typeExpr.Fields.Opening,
Name: "$union",
},
},
Type: unionFieldType,
}},
Closing: typ.typeExpr.Fields.Closing,
},
}
}
// createUnionAccessor creates a function that returns a typed pointer to a
// union field for each field in a union. For example:
//
// func (union *C.union_1) unionfield_d() *float64 {
// return (*float64)(unsafe.Pointer(&union.$union))
// }
//
// Where C.union_1 is defined as:
//
// type C.union_1 struct{
// $union uint64
// }
//
// The returned pointer can be used to get or set the field, or get the pointer
// to a subfield.
func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
if len(field.Names) != 1 {
panic("number of names in union field must be exactly 1")
}
fieldName := field.Names[0]
pos := fieldName.NamePos
// The method receiver.
receiver := &ast.SelectorExpr{
X: &ast.Ident{
NamePos: pos,
Name: "union",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: pos,
Name: "$union",
},
}
// Get the address of the $union field.
receiverPtr := &ast.UnaryExpr{
Op: token.AND,
X: receiver,
}
// Cast to unsafe.Pointer.
sourcePointer := &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: &ast.Ident{Name: "unsafe"},
Sel: &ast.Ident{Name: "Pointer"},
},
Args: []ast.Expr{receiverPtr},
}
// Cast to the target pointer type.
targetPointer := &ast.CallExpr{
Lparen: pos,
Fun: &ast.ParenExpr{
Lparen: pos,
X: &ast.StarExpr{
X: field.Type,
},
Rparen: pos,
},
Args: []ast.Expr{sourcePointer},
Rparen: pos,
}
// Create the accessor function.
accessor := &ast.FuncDecl{
Recv: &ast.FieldList{
Opening: pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: "union",
},
},
Type: &ast.StarExpr{
Star: pos,
X: &ast.Ident{
NamePos: pos,
Name: typeName,
Obj: nil,
},
},
},
},
Closing: pos,
},
Name: &ast.Ident{
NamePos: pos,
Name: "unionfield_" + fieldName.Name,
},
Type: &ast.FuncType{
Func: pos,
Params: &ast.FieldList{
Opening: pos,
Closing: pos,
},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: &ast.StarExpr{
Star: pos,
X: field.Type,
},
},
},
},
},
Body: &ast.BlockStmt{
Lbrace: pos,
List: []ast.Stmt{
&ast.ReturnStmt{
Return: pos,
Results: []ast.Expr{
targetPointer,
},
},
},
Rbrace: pos,
},
}
p.generated.Decls = append(p.generated.Decls, accessor)
}
// createBitfieldGetter creates a bitfield getter function like the following:
//
// func (s *C.struct_foo) bitfield_b() byte {
// return (s.__bitfield_1 >> 5) & 0x1
// }
func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string) {
// The value to return from the getter.
// Not complete: this is just an expression to get the complete field.
var result ast.Expr = &ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
}
if bitfield.startBit != 0 {
// Shift to the right by .startBit so that fields that come before are
// shifted off.
result = &ast.BinaryExpr{
X: result,
OpPos: bitfield.pos,
Op: token.SHR,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: strconv.FormatInt(bitfield.startBit, 10),
},
}
}
if bitfield.endBit != 0 {
// Mask off the high bits so that fields that come after this field are
// masked off.
and := (uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1
result = &ast.BinaryExpr{
X: result,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(and, 16),
},
}
}
// Create the getter function.
getter := &ast.FuncDecl{
Recv: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
},
},
Type: &ast.StarExpr{
Star: bitfield.pos,
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
},
Closing: bitfield.pos,
},
Name: &ast.Ident{
NamePos: bitfield.pos,
Name: "bitfield_" + bitfield.name,
},
Type: &ast.FuncType{
Func: bitfield.pos,
Params: &ast.FieldList{
Opening: bitfield.pos,
Closing: bitfield.pos,
},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: bitfield.field.Type,
},
},
},
},
Body: &ast.BlockStmt{
Lbrace: bitfield.pos,
List: []ast.Stmt{
&ast.ReturnStmt{
Return: bitfield.pos,
Results: []ast.Expr{
result,
},
},
},
Rbrace: bitfield.pos,
},
}
p.generated.Decls = append(p.generated.Decls, getter)
}
// createBitfieldSetter creates a bitfield setter function like the following:
//
// func (s *C.struct_foo) set_bitfield_b(value byte) {
// s.__bitfield_1 = s.__bitfield_1 ^ 0x60 | ((value & 1) << 5)
// }
//
// Or the following:
//
// func (s *C.struct_foo) set_bitfield_c(value byte) {
// s.__bitfield_1 = s.__bitfield_1 & 0x3f | (value << 6)
// }
func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string) {
// The full field with all bitfields.
var field ast.Expr = &ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
}
// The value to insert into the field.
var valueToInsert ast.Expr = &ast.Ident{
NamePos: bitfield.pos,
Name: "value",
}
if bitfield.endBit != 0 {
// Make sure the value is in range with a mask.
valueToInsert = &ast.BinaryExpr{
X: valueToInsert,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint((uint64(1)<<uint64(bitfield.endBit-bitfield.startBit))-1, 16),
},
}
// Create a mask for the AND NOT operation.
mask := ((uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1) << uint64(bitfield.startBit)
// Zero the bits in the field that will soon be inserted.
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.AND_NOT,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(mask, 16),
},
}
} else { // bitfield.endBit == 0
// We don't know exactly how many high bits should be zeroed. So we do
// something different: keep the low bits with a mask and OR the new
// value with it.
mask := (uint64(1) << uint64(bitfield.startBit)) - 1
// Extract the lower bits.
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(mask, 16),
},
}
}
// Bitwise OR with the new value (after the new value has been shifted).
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.OR,
Y: &ast.BinaryExpr{
X: valueToInsert,
OpPos: bitfield.pos,
Op: token.SHL,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: strconv.FormatInt(bitfield.startBit, 10),
},
},
}
// Create the setter function.
setter := &ast.FuncDecl{
Recv: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
},
},
Type: &ast.StarExpr{
Star: bitfield.pos,
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
},
Closing: bitfield.pos,
},
Name: &ast.Ident{
NamePos: bitfield.pos,
Name: "set_bitfield_" + bitfield.name,
},
Type: &ast.FuncType{
Func: bitfield.pos,
Params: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "value",
Obj: nil,
},
},
Type: bitfield.field.Type,
},
},
Closing: bitfield.pos,
},
},
Body: &ast.BlockStmt{
Lbrace: bitfield.pos,
List: []ast.Stmt{
&ast.AssignStmt{
Lhs: []ast.Expr{
&ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
},
},
TokPos: bitfield.pos,
Tok: token.ASSIGN,
Rhs: []ast.Expr{
field,
},
},
},
Rbrace: bitfield.pos,
},
}
p.generated.Decls = append(p.generated.Decls, setter)
}
// addEnumTypes adds C enums to the AST. For example, the following C code:
@@ -603,16 +1211,16 @@ func (p *cgoPackage) addEnumTypes() {
if len(p.enums) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
names := make([]string, 0, len(p.enums))
for name := range p.enums {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
typ := p.enums[name]
typeName := "C.enum_" + name
obj := &ast.Object{
@@ -629,8 +1237,8 @@ func (p *cgoPackage) addEnumTypes() {
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
p.generated.Decls = append(p.generated.Decls, gen)
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// findMissingCGoNames traverses the AST and finds all C.something names. Only
@@ -693,3 +1301,29 @@ func (p *cgoPackage) walker(cursor *astutil.Cursor) bool {
}
return true
}
// renameFieldKeywords renames all reserved words in Go to some other field name
// with a "_" prefix. For example, it renames `type` to `_type`.
//
// See: https://golang.org/cmd/cgo/#hdr-Go_references_to_C
func renameFieldKeywords(fieldList *ast.FieldList) {
renameFieldName(fieldList, "type")
}
// renameFieldName renames a given field name to a name with a "_" prepended. It
// makes sure to do the same thing for any field sharing the same name.
func renameFieldName(fieldList *ast.FieldList, name string) {
var ident *ast.Ident
for _, f := range fieldList.List {
for _, n := range f.Names {
if n.Name == name {
ident = n
}
}
}
if ident == nil {
return
}
renameFieldName(fieldList, "_"+name)
ident.Name = "_" + ident.Name
}
+129
View File
@@ -0,0 +1,129 @@
package cgo
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"go/types"
"io/ioutil"
"path/filepath"
"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.")
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 := strings.Replace(string(buf.Bytes()), "\r\n", "\n", -1)
// 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)
}
}
}
+262 -160
View File
@@ -15,7 +15,7 @@ import (
)
/*
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
#include <stdlib.h>
#include <stdint.h>
@@ -51,6 +51,7 @@ 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);
@@ -108,7 +109,8 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
C.CXTranslationUnit_DetailedPreprocessingRecord,
&unit)
if errCode != 0 {
panic("loader: failed to parse source with libclang")
// This is probably a bug in the usage of libclang.
panic("cgo: failed to parse source with libclang")
}
defer C.clang_disposeTranslationUnit(unit)
@@ -117,27 +119,8 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
location := C.clang_getDiagnosticLocation(diagnostic)
var libclangFilename C.CXString
var line C.unsigned
var column C.unsigned
C.clang_getPresumedLocation(location, &libclangFilename, &line, &column)
filename := getString(libclangFilename)
if filepath.IsAbs(filename) {
// Relative paths for readability, like other Go parser errors.
relpath, err := filepath.Rel(p.dir, filename)
if err == nil {
filename = relpath
}
}
p.errors = append(p.errors, &scanner.Error{
Pos: token.Position{
Filename: filename,
Offset: 0, // not provided by clang_getPresumedLocation
Line: int(line),
Column: int(column),
},
Msg: severity + ": " + spelling,
})
pos := p.getClangLocationPosition(location, unit)
p.addError(pos, severity+": "+spelling)
}
for i := 0; i < numDiagnostics; i++ {
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
@@ -149,7 +132,6 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
}
}
return
}
ref := storedRefs.Put(p)
@@ -236,14 +218,17 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
var startOffset, endOffset C.unsigned
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
if file == nil {
panic("could not find file where macro is defined")
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 {
panic("expected start and end location of a #define to be in the same file")
p.addError(pos, "internal error: expected start and end location of a macro to be in the same file")
break
}
if startOffset > endOffset {
panic("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
@@ -251,59 +236,29 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
if endOffset >= C.uint(size) {
panic("endOffset lies after end of file")
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) {
panic(fmt.Sprintf("expected #define value to start with %#v, got %#v", name, source))
p.addError(pos, fmt.Sprintf("internal error: expected macro value to start with %#v, got %#v", name, source))
break
}
value := strings.TrimSpace(source[len(name):])
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
value = strings.TrimSpace(value[1 : len(value)-1])
value := source[len(name):]
// Try to convert this #define into a Go constant expression.
expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value)
if err != nil {
p.errors = append(p.errors, err)
}
if len(value) == 0 {
// Pretend it doesn't exist at all.
return C.CXChildVisit_Continue
}
// For information about integer literals:
// https://en.cppreference.com/w/cpp/language/integer_literal
if value[0] == '"' {
// string constant
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.STRING, value}, pos}
return C.CXChildVisit_Continue
}
if value[0] == '\'' {
// char constant
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.CHAR, value}, pos}
return C.CXChildVisit_Continue
}
// assume it's a number (int or float)
value = strings.Replace(value, "'", "", -1) // remove ' chars
value = strings.TrimRight(value, "lu") // remove llu suffixes etc.
// find the first non-number
nonnum := byte(0)
for i := 0; i < len(value); i++ {
if value[i] < '0' || value[i] > '9' {
nonnum = value[i]
break
}
}
// determine number type based on the first non-number
switch nonnum {
case 0:
// no non-number found, must be an integer
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
case 'x', 'X':
// hex integer constant
// TODO: may also be a floating point number per C++17.
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
case '.', 'e':
// float constant
value = strings.TrimRight(value, "fFlL")
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.FLOAT, value}, pos}
default:
// unknown type, ignore
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
}
@@ -315,11 +270,16 @@ func getString(clangString C.CXString) (s string) {
return
}
// getCursorPosition returns a usable token.Pos from a libclang cursor. If the
// file for this cursor has not been seen before, it is read from libclang
// (which already has the file in memory) and added to the token.FileSet.
// getCursorPosition returns a usable token.Pos from a libclang cursor.
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
location := C.tinygo_clang_getCursorLocation(cursor)
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
@@ -333,7 +293,6 @@ func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
if _, ok := p.tokenFiles[filename]; !ok {
// File has not been seen before in this package, add line information
// now by reading the file from libclang.
tu := C.tinygo_clang_Cursor_getTranslationUnit(cursor)
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
@@ -347,7 +306,62 @@ func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
f.SetLines(lines)
p.tokenFiles[filename] = f
}
return p.tokenFiles[filename].Pos(int(offset))
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,
@@ -458,7 +472,7 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
// This happens for some very special purpose architectures
// (DSPs etc.) that are not currently targeted.
// https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
panic("unknown char width")
p.addError(pos, fmt.Sprintf("unknown char width: %d", typeSize))
}
switch underlyingType.kind {
case C.CXType_Char_S:
@@ -507,83 +521,49 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
case C.CXType_Enum:
return p.makeASTType(underlying, pos)
default:
panic("unknown elaborated type")
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 cgoName string
var cgoRecordPrefix string
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
cgoName = "struct_" + name
cgoRecordPrefix = "struct_"
case C.CXCursor_UnionDecl:
cgoName = "union_" + name
cgoRecordPrefix = "union_"
default:
panic("unknown record declaration")
// makeASTRecordType will create an appropriate error.
cgoRecordPrefix = "record_"
}
if _, ok := p.elaboratedTypes[cgoName]; !ok {
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
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,
}
}
ref := storedRefs.Put(struct {
fieldList *ast.FieldList
pkg *cgoPackage
}{fieldList, p})
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
}
case C.CXCursor_UnionDecl:
if len(fieldList.List) > 1 {
// Insert a special field at the front (of zero width) as a
// marker that this is struct is actually a union. This is done
// by giving the field a name that cannot be expressed directly
// in Go.
// Other parts of the compiler look at the first element in a
// struct (of size > 2) to know whether this is a union.
// Note that we don't have to insert it for single-element
// unions as they're basically equivalent to a struct.
unionMarker := &ast.Field{
Type: &ast.StructType{
Struct: pos,
},
}
unionMarker.Names = []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: "C union",
Obj: &ast.Object{
Kind: ast.Var,
Name: "C union",
Decl: unionMarker,
},
},
}
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
}
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
}
default:
panic("unreachable")
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,
}
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
case C.CXType_Enum:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
@@ -614,14 +594,10 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
if typeName == "" {
// Report this as an error.
spelling := getString(C.clang_getTypeSpelling(typ))
p.errors = append(p.errors, scanner.Error{
Pos: p.fset.PositionFor(pos, true),
Msg: fmt.Sprintf("unknown C type: %v (libclang type kind %d)", spelling, typ.kind),
})
// Fallback, probably incorrect but at least the error points to an odd
// type name.
typeName = "C." + spelling
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,
@@ -629,25 +605,151 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
}
// 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
fieldList *ast.FieldList
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
})
fieldList := passed.fieldList
p := passed.pkg
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
panic("expected field inside cursor")
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: p.getCursorPosition(c),
NamePos: pos,
Name: name,
Obj: &ast.Object{
Kind: ast.Var,
+6 -4
View File
@@ -3,9 +3,11 @@
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-8/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm/lib -lclang -lffi
#cgo linux CFLAGS: -I/usr/lib/llvm-9/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@9/include
#cgo freebsd CFLAGS: -I/usr/local/llvm90/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-9/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm90/lib -lclang
*/
import "C"
+5 -1
View File
@@ -3,7 +3,7 @@
// are slightly different from the ones defined in libclang.go, but they
// should be ABI compatible.
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
return clang_getTranslationUnitCursor(tu);
@@ -64,3 +64,7 @@ long long tinygo_clang_getEnumConstantDeclValue(CXCursor 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)
}
}
+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
}
+26
View File
@@ -0,0 +1,26 @@
package main
/*
// this name doesn't exist
#cgo NOFLAGS: -foo
// unknown flag
#cgo CFLAGS: -fdoes-not-exist -DNOTDEFINED
#cgo CFLAGS: -DFOO
#if defined(FOO)
#define BAR 3
#else
#define BAR 5
#endif
#if defined(NOTDEFINED)
#warning flag must not be defined
#endif
*/
import "C"
var (
_ = C.BAR
)
+32
View File
@@ -0,0 +1,32 @@
// CGo errors:
// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
package main
import "unsafe"
var _ unsafe.Pointer
const C.BAR = 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
+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()
}
+119
View File
@@ -0,0 +1,119 @@
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
-44
View File
@@ -1,44 +0,0 @@
package main
import (
"errors"
"os"
"os/exec"
"runtime"
"strings"
)
// Commands used by the compilation process might have different file names
// across operating systems and distributions.
var commands = map[string][]string{
"clang": {"clang-8"},
"ld.lld": {"ld.lld-8", "ld.lld"},
"wasm-ld": {"wasm-ld-8", "wasm-ld"},
}
func init() {
// Add the path to a Homebrew-installed LLVM 8 for ease of use (no need to
// manually set $PATH).
if runtime.GOOS == "darwin" {
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm/bin/clang-8")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm/bin/wasm-ld")
}
}
func execCommand(cmdNames []string, args ...string) error {
for _, cmdName := range cmdNames {
cmd := exec.Command(cmdName, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.Error); ok && err.Err == exec.ErrNotFound {
// this command was not found, try the next
continue
}
}
return nil
}
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
}
+227
View File
@@ -0,0 +1,227 @@
// Package compileopts contains the configuration for a single to-be-built
// binary.
package compileopts
import (
"errors"
"fmt"
"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
}
// 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", and "conservative".
func (c *Config) GC() string {
if c.Options.GC != "" {
return c.Options.GC
}
if c.Target.GC != "" {
return c.Target.GC
}
return "conservative"
}
// NeedsStackObjects returns true if the compiler should insert stack objects
// that can be traced by the garbage collector.
func (c *Config) NeedsStackObjects() bool {
if c.GC() != "conservative" {
return false
}
for _, tag := range c.BuildTags() {
if tag == "baremetal" {
return false
}
}
return true
}
// Scheduler returns the scheduler implementation. Valid values are "coroutines"
// and "tasks".
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"
}
// 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))
}
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
}
type TestConfig struct {
CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc
}
+23
View File
@@ -0,0 +1,23 @@
package compileopts
// 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
CFlags []string
LDFlags []string
Tags string
WasmAbi string
HeapSize int64
TestConfig TestConfig
Programmer string
}
+297
View File
@@ -0,0 +1,297 @@
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)
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"`
}
// 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
}
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
}
}
// 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)
}
}
+16 -4
View File
@@ -22,7 +22,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
// correctly extend that type.
if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 {
if indexType.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 {
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
} else {
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
@@ -56,7 +56,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
// normal meaning) and for creating a new slice, where 'capacity' means the
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases.
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if frame.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
@@ -71,6 +71,9 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = high.Type()
}
if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = max.Type()
}
if capacityType != capacity.Type() {
capacity = c.builder.CreateZExt(capacity, capacityType, "")
}
@@ -90,6 +93,13 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
high = c.builder.CreateSExt(high, capacityType, "")
}
}
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if maxType.Info()&types.IsUnsigned != 0 {
max = c.builder.CreateZExt(max, capacityType, "")
} else {
max = c.builder.CreateSExt(max, capacityType, "")
}
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
@@ -97,8 +107,10 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
// Now do the bounds check: low > high || high > capacity
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
+6 -2
View File
@@ -1,6 +1,7 @@
package compiler
import (
"fmt"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -20,6 +21,9 @@ func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name stri
panic("trying to call runtime." + fnName)
}
fn := c.ir.GetFunction(member.(*ssa.Function))
if fn.LLVMFn.IsNil() {
panic(fmt.Errorf("function %s does not appear in LLVM IR", fnName))
}
if !fn.IsExported() {
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
@@ -38,7 +42,7 @@ func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llv
}
// Expand an argument type to a list that can be used in a function call
// paramter list.
// parameter list.
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
switch t.TypeKind() {
case llvm.StructTypeKind:
@@ -163,7 +167,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
switch t.TypeKind() {
case llvm.StructTypeKind:
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
value := c.getZeroValue(t)
value := llvm.ConstNull(t)
for i, subtyp := range t.StructElementTypes() {
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
fields = remaining
+177 -19
View File
@@ -6,18 +6,16 @@ package compiler
import (
"go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// emitMakeChan returns a new channel value for the given channel type.
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
chanType := c.mod.GetTypeByName("runtime.channel")
size := c.targetData.TypeAllocSize(chanType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan")
return ptr, nil
func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value {
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false)
bufSize := c.getValue(frame, expr.Size)
return c.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
}
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
@@ -32,9 +30,7 @@ func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
c.builder.CreateStore(chanValue, valueAlloca)
// Do the send.
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
c.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
// End the lifetime of the alloca.
// This also works around a bug in CoroSplit, at least in LLVM 8:
@@ -52,15 +48,11 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
// Do the receive.
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
commaOk := c.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received")
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
@@ -72,8 +64,174 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
// emitChanClose closes the given channel.
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
ch := c.getValue(frame, param)
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
}
// emitSelect emits all IR necessary for a select statements. That's a
// non-trivial amount of code because select is very complex to implement.
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := c.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
c.createRuntimeCall("deadlock", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:
// select {
// default:
// }
retval := llvm.Undef(llvmType)
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
return retval // {-1, false}
}
}
// This code create a (stack-allocated) slice containing all the select
// cases and then calls runtime.chanSelect to perform the actual select
// statement.
// Simple selects (blocking and with just one case) are already transformed
// into regular chan operations during SSA construction so we don't have to
// optimize such small selects.
// Go through all the cases. Create the selectStates slice and and
// determine the receive buffer size and alignment.
recvbufSize := uint64(0)
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := c.getValue(frame, state.Chan)
selectState := llvm.ConstNull(chanSelectStateType)
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
switch state.Dir {
case types.RecvOnly:
// Make sure the receive buffer is big enough and has the correct alignment.
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
recvbufSize = size
}
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align
}
hasReceives = true
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
sendValue := c.getValue(frame, state.Send)
alloca := llvmutil.CreateEntryBlockAlloca(c.builder, sendValue.Type(), "select.send.value")
c.builder.CreateStore(sendValue, alloca)
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
default:
panic("unreachable")
}
selectStates = append(selectStates, selectState)
}
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(c.i8ptrType)
if hasReceives {
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := c.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.recvbuf")
}
// Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
statesAlloca, statesI8, statesSize := c.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
for i, state := range selectStates {
// Set each slice element to the appropriate channel.
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}, "")
c.builder.CreateStore(state, gep)
}
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.states")
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
// Do the select in the runtime.
var results llvm.Value
if expr.Blocking {
// Stack-allocate operation structures.
// If these were simply created as a slice, they would heap-allocate.
chBlockAllocaType := llvm.ArrayType(c.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockAllocaPtr, chBlockSize := c.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := c.builder.CreateGEP(chBlockAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.block")
results = c.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
}, "select.result")
// Terminate the lifetime of the operation structures.
c.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
} else {
results = c.createRuntimeCall("tryChanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
}, "select.result")
}
// Terminate the lifetime of the states alloca.
c.emitLifetimeEnd(statesI8, statesSize)
// The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract
// instruction will treat a *ssa.Select specially and load it there inline.
// Store the receive alloca in a sidetable until we hit this extract
// instruction.
if frame.selectRecvBuf == nil {
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
}
frame.selectRecvBuf[expr] = recvbuf
return results
}
// getChanSelectResult returns the special values from a *ssa.Extract expression
// when extracting a value from a select statement (*ssa.Select). Because
// *ssa.Select cannot load all values in advance, it does this later in the
// *ssa.Extract expression.
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
if expr.Index == 0 {
// index
value := c.getValue(frame, expr.Tuple)
index := c.builder.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
index = c.builder.CreateSExt(index, c.intType, "")
}
return index
} else if expr.Index == 1 {
// comma-ok
value := c.getValue(frame, expr.Tuple)
return c.builder.CreateExtractValue(value, expr.Index, "")
} else {
// Select statements are (index, ok, ...) where ... is a number of
// received values, depending on how many receive statements there
// are. They are all combined into one alloca (because only one
// receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it.
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
return c.builder.CreateLoad(ptr, "")
}
}
+172
View File
@@ -0,0 +1,172 @@
package compiler
// This file implements a set of sanity checks for the IR that is generated.
// It can catch some mistakes that LLVM's verifier cannot.
import (
"errors"
"fmt"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) 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 *Compiler) 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 *Compiler) 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())
}
// 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 *Compiler) 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 *Compiler) 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
}
func (c *Compiler) checkModule() []error {
// check for any context mismatches
var errs []error
switch {
case c.mod.Context() == c.ctx:
// this is correct
case c.mod.Context() == llvm.GlobalContext():
// somewhere we accidentally used the global context instead of a real context
errs = append(errs, errors.New("module uses global context"))
default:
// we used some other context by accident
errs = append(errs, fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
}
types := map[llvm.Type]struct{}{}
specials := map[llvm.TypeKind]llvm.Type{}
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
errs = append(errs, c.checkFunction(fn, types, specials)...)
}
for g := c.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
}
+338 -482
View File
File diff suppressed because it is too large Load Diff
+60 -12
View File
@@ -14,6 +14,7 @@ package compiler
// frames.
import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
@@ -29,11 +30,45 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
frame.deferClosureFuncs = make(map[*ir.Function]int)
// Create defer list pointer.
deferType := llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
}
// 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
}
// emitDefer emits a single defer instruction, to be run when this function
// returns.
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
@@ -122,13 +157,26 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
// Make a struct out of the collected values to put in the defer frame.
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFrame := c.getZeroValue(deferFrameType)
deferFrame := llvm.ConstNull(deferFrameType)
for i, value := range values {
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
}
// Put this struct in an alloca.
alloca := c.builder.CreateAlloca(deferFrameType, "defer.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(c.builder, deferFrameType, "defer.alloca")
} else {
// This may be hit a variable number of times, so use a heap allocation.
size := c.targetData.TypeAllocSize(deferFrameType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
allocCall := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
alloca = c.builder.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
}
if c.NeedsStackObjects() {
c.trackPointer(alloca)
}
c.builder.CreateStore(deferFrame, alloca)
// Push it on top of the linked list by replacing deferPtr.
@@ -154,10 +202,10 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// }
// 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")
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
c.builder.CreateBr(loophead)
// Create loop head:
@@ -189,7 +237,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// Create switch case, for example:
// case 0:
// // run first deferred call
block := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
c.builder.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
@@ -200,7 +248,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
}
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType}
for _, arg := range callback.Args {
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
}
@@ -231,7 +279,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// Direct call.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
for _, param := range callback.Params {
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
}
@@ -260,7 +308,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
+43
View File
@@ -1,8 +1,12 @@
package compiler
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 {
@@ -16,3 +20,42 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
func (c *Compiler) 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{}
}
}
-269
View File
@@ -1,269 +0,0 @@
package compiler
// This file lowers func values into their final form. This is necessary for
// funcValueSwitch, which needs full program analysis.
import (
"sort"
"strconv"
"tinygo.org/x/go-llvm"
)
// funcSignatureInfo keeps information about a single signature and its uses.
type funcSignatureInfo struct {
sig llvm.Value // *uint8 to identify the signature
funcValueWithSignatures []llvm.Value // slice of runtime.funcValueWithSignature
}
// funcWithUses keeps information about a single function used as func value and
// the assigned function ID. More commonly used functions are assigned a lower
// ID.
type funcWithUses struct {
funcPtr llvm.Value
useCount int // how often this function is used in a func value
id int // assigned ID
}
// Slice to sort functions by their use counts, or else their name if they're
// used equally often.
type funcWithUsesList []*funcWithUses
func (l funcWithUsesList) Len() int { return len(l) }
func (l funcWithUsesList) Less(i, j int) bool {
if l[i].useCount != l[j].useCount {
// return the reverse: we want the highest use counts sorted first
return l[i].useCount > l[j].useCount
}
iName := l[i].funcPtr.Name()
jName := l[j].funcPtr.Name()
return iName < jName
}
func (l funcWithUsesList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
// LowerFuncValue lowers the runtime.funcValueWithSignature type and
// runtime.getFuncPtr function to their final form.
func (c *Compiler) LowerFuncValues() {
if c.funcImplementation() != funcValueSwitch {
return
}
// Find all func values used in the program with their signatures.
funcValueWithSignaturePtr := llvm.PointerType(c.mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
signatures := map[string]*funcSignatureInfo{}
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.Type() != funcValueWithSignaturePtr {
continue
}
sig := llvm.ConstExtractValue(global.Initializer(), []uint32{1})
name := sig.Name()
if info, ok := signatures[name]; ok {
info.funcValueWithSignatures = append(info.funcValueWithSignatures, global)
} else {
signatures[name] = &funcSignatureInfo{
sig: sig,
funcValueWithSignatures: []llvm.Value{global},
}
}
}
// Sort the signatures, for deterministic execution.
names := make([]string, 0, len(signatures))
for name := range signatures {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
info := signatures[name]
functions := make(funcWithUsesList, len(info.funcValueWithSignatures))
for i, use := range info.funcValueWithSignatures {
var useCount int
for _, use2 := range getUses(use) {
useCount += len(getUses(use2))
}
functions[i] = &funcWithUses{
funcPtr: llvm.ConstExtractValue(use.Initializer(), []uint32{0}).Operand(0),
useCount: useCount,
}
}
sort.Sort(functions)
for i, fn := range functions {
fn.id = i + 1
for _, ptrtoint := range getUses(fn.funcPtr) {
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
continue
}
for _, funcValueWithSignatureConstant := range getUses(ptrtoint) {
for _, funcValueWithSignatureGlobal := range getUses(funcValueWithSignatureConstant) {
for _, use := range getUses(funcValueWithSignatureGlobal) {
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
panic("expected const ptrtoint")
}
use.ReplaceAllUsesWith(llvm.ConstInt(c.uintptrType, uint64(fn.id), false))
}
}
}
}
}
for _, getFuncPtrCall := range getUses(info.sig) {
if getFuncPtrCall.IsACallInst().IsNil() {
continue
}
if getFuncPtrCall.CalledValue().Name() != "runtime.getFuncPtr" {
panic("expected all call uses to be runtime.getFuncPtr")
}
funcID := getFuncPtrCall.Operand(1)
switch len(functions) {
case 0:
// There are no functions used in a func value that implement
// this signature. The only possible value is a nil value.
for _, inttoptr := range getUses(getFuncPtrCall) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
nilptr := llvm.ConstPointerNull(inttoptr.Type())
inttoptr.ReplaceAllUsesWith(nilptr)
inttoptr.EraseFromParentAsInstruction()
}
getFuncPtrCall.EraseFromParentAsInstruction()
case 1:
// There is exactly one function with this signature that is
// used in a func value. The func value itself can be either nil
// or this one function.
c.builder.SetInsertPointBefore(getFuncPtrCall)
zero := llvm.ConstInt(c.uintptrType, 0, false)
isnil := c.builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
funcPtrNil := llvm.ConstPointerNull(functions[0].funcPtr.Type())
funcPtr := c.builder.CreateSelect(isnil, funcPtrNil, functions[0].funcPtr, "")
for _, inttoptr := range getUses(getFuncPtrCall) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
inttoptr.ReplaceAllUsesWith(funcPtr)
inttoptr.EraseFromParentAsInstruction()
}
getFuncPtrCall.EraseFromParentAsInstruction()
default:
// There are multiple functions used in a func value that
// implement this signature.
// What we'll do is transform the following:
// rawPtr := runtime.getFuncPtr(fn)
// if func.rawPtr == nil {
// runtime.nilPanic()
// }
// result := func.rawPtr(...args, func.context)
// into this:
// if false {
// runtime.nilPanic()
// }
// var result // Phi
// switch fn.id {
// case 0:
// runtime.nilPanic()
// case 1:
// result = call first implementation...
// case 2:
// result = call second implementation...
// default:
// unreachable
// }
// Remove some casts, checks, and the old call which we're going
// to replace.
var funcCall llvm.Value
for _, inttoptr := range getUses(getFuncPtrCall) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
for _, ptrUse := range getUses(inttoptr) {
if !ptrUse.IsABitCastInst().IsNil() {
for _, bitcastUse := range getUses(ptrUse) {
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().Name() != "runtime.isnil" {
panic("expected a call to runtime.isnil")
}
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
bitcastUse.EraseFromParentAsInstruction()
}
ptrUse.EraseFromParentAsInstruction()
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == inttoptr {
if !funcCall.IsNil() {
panic("multiple calls on a single runtime.getFuncPtr")
}
funcCall = ptrUse
} else {
panic("unexpected getFuncPtrCall")
}
}
}
if funcCall.IsNil() {
panic("expected exactly one call use of a runtime.getFuncPtr")
}
// The block that cannot be reached with correct funcValues (to
// help the optimizer).
c.builder.SetInsertPointBefore(funcCall)
defaultBlock := llvm.AddBasicBlock(funcCall.InstructionParent().Parent(), "func.default")
c.builder.SetInsertPointAtEnd(defaultBlock)
c.builder.CreateUnreachable()
// Create the switch.
c.builder.SetInsertPointBefore(funcCall)
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
// Split right after the switch. We will need to insert a few
// basic blocks in this gap.
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
// The 0 case, which is actually a nil check.
nilBlock := llvm.InsertBasicBlock(nextBlock, "func.nil")
c.builder.SetInsertPointAtEnd(nilBlock)
c.createRuntimeCall("nilPanic", nil, "")
c.builder.CreateUnreachable()
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
// Gather the list of parameters for every call we're going to
// make.
callParams := make([]llvm.Value, funcCall.OperandsCount()-1)
for i := range callParams {
callParams[i] = funcCall.Operand(i)
}
// If the call produces a value, we need to get it using a PHI
// node.
phiBlocks := make([]llvm.BasicBlock, len(functions))
phiValues := make([]llvm.Value, len(functions))
for i, fn := range functions {
// Insert a switch case.
bb := llvm.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
c.builder.SetInsertPointAtEnd(bb)
result := c.builder.CreateCall(fn.funcPtr, callParams, "")
c.builder.CreateBr(nextBlock)
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
phiBlocks[i] = bb
phiValues[i] = result
}
// Create the PHI node so that the call result flows into the
// next block (after the split). This is only necessary when the
// call produced a value.
if funcCall.Type().TypeKind() != llvm.VoidTypeKind {
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
phi := c.builder.CreatePHI(funcCall.Type(), "")
phi.AddIncoming(phiValues, phiBlocks)
funcCall.ReplaceAllUsesWith(phi)
}
// Finally, remove the old instructions.
funcCall.EraseFromParentAsInstruction()
for _, inttoptr := range getUses(getFuncPtrCall) {
inttoptr.EraseFromParentAsInstruction()
}
getFuncPtrCall.EraseFromParentAsInstruction()
}
}
}
}
+12 -23
View File
@@ -32,10 +32,15 @@ const (
// funcImplementation picks an appropriate func value implementation for the
// target.
func (c *Compiler) funcImplementation() funcValueImplementation {
if c.GOARCH == "wasm" {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
switch c.Scheduler() {
case "coroutines":
return funcValueSwitch
} else {
case "tasks":
return funcValueDoubleword
default:
panic("unknown scheduler type")
}
}
@@ -48,11 +53,11 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
case funcValueSwitch:
sigGlobal := c.getFuncSignature(sig)
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.mod.GetTypeByName("runtime.funcValueWithSignature")
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
sigGlobal,
@@ -73,22 +78,6 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
return funcValue
}
// getFuncSignature returns a global for identification of a particular function
// signature. It is used in runtime.funcValueWithSignature and in calls to
// getFuncPtr.
func (c *Compiler) getFuncSignature(sig *types.Signature) llvm.Value {
typeCodeName := getTypeCodeName(sig)
sigGlobalName := "reflect/types.type:" + typeCodeName
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetInitializer(llvm.Undef(c.ctx.Int8Type()))
sigGlobal.SetGlobalConstant(true)
sigGlobal.SetLinkage(llvm.InternalLinkage)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation.
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@@ -103,14 +92,14 @@ func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
// decodeFuncValue extracts the context and the function pointer from this func
// value. This may be an expensive operation.
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value, err error) {
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = c.builder.CreateExtractValue(funcValue, 0, "")
switch c.funcImplementation() {
case funcValueDoubleword:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch:
llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getFuncSignature(sig)
sigGlobal := c.getTypeCode(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
default:
@@ -126,7 +115,7 @@ func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case funcValueSwitch:
return c.mod.GetTypeByName("runtime.funcValue")
return c.getLLVMRuntimeType("funcValue")
default:
panic("unimplemented func value variant")
}
+106
View File
@@ -0,0 +1,106 @@
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 (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
// There are uses of this expression, Make sure the pointers
// are tracked during GC.
switch expr := expr.(type) {
case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap:
// These values are always of pointer type in IR.
c.trackPointer(value)
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
if !value.IsNil() {
c.trackValue(value)
}
case *ssa.Select:
if alloca, ok := frame.selectRecvBuf[expr]; ok {
if alloca.IsAUndefValue().IsNil() {
c.trackPointer(alloca)
}
}
case *ssa.UnOp:
switch expr.Op {
case token.MUL:
// Pointer dereference.
c.trackValue(value)
case token.ARROW:
// Channel receive operator.
// It's not necessary to look at commaOk here, because in that
// case it's just an aggregate and trackValue will extract the
// pointer in there (if there is one).
c.trackValue(value)
}
}
}
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
// individual pointers
func (c *Compiler) trackValue(value llvm.Value) {
typ := value.Type()
switch typ.TypeKind() {
case llvm.PointerTypeKind:
c.trackPointer(value)
case llvm.StructTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.StructElementTypesCount()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
case llvm.ArrayTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
}
}
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
// first if needed. The input value must be of LLVM pointer type.
func (c *Compiler) trackPointer(value llvm.Value) {
if value.Type() != c.i8ptrType {
value = c.builder.CreateBitCast(value, c.i8ptrType, "")
}
c.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
}
// typeHasPointers returns whether this type is a pointer or contains pointers.
// If the type is an aggregate type, it will check whether there is a pointer
// inside.
func typeHasPointers(t llvm.Type) bool {
switch t.TypeKind() {
case llvm.PointerTypeKind:
return true
case llvm.StructTypeKind:
for _, subType := range t.StructElementTypes() {
if typeHasPointers(subType) {
return true
}
}
return false
case llvm.ArrayTypeKind:
if typeHasPointers(t.ElementType()) {
return true
}
return false
default:
return false
}
}
File diff suppressed because it is too large Load Diff
+138
View File
@@ -0,0 +1,138 @@
package compiler
// This file implements the 'go' keyword to start a new goroutine. See
// goroutine-lowering.go for more details.
import "tinygo.org/x/go-llvm"
// emitStartGoroutine starts a new goroutine with the provided function pointer
// and parameters.
// In general, you should pass all regular parameters plus the context parameter.
// There is one exception: the task-based scheduler needs to have the function
// pointer passed in as a parameter too in addition to the context.
//
// Because a go statement doesn't return anything, return undef.
func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
switch c.Scheduler() {
case "tasks":
paramBundle := c.emitPointerPack(params)
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
calleeValue := c.createGoroutineStartWrapper(funcPtr)
c.createRuntimeCall("startGoroutine", []llvm.Value{calleeValue, paramBundle}, "")
case "coroutines":
// We roundtrip through runtime.makeGoroutine as a signal (to find these
// calls) and to break any optimizations LLVM will try to do: they are
// invalid if we called this as a regular function to be updated later.
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
c.createCall(calleeValue, append(params, llvm.ConstPointerNull(c.i8ptrType)), "")
default:
panic("unreachable")
}
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 *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
var wrapper llvm.Value
if !fn.IsAFunction().IsNil() {
// See whether this wrapper has already been created. If so, return it.
name := fn.Name()
wrapper = c.mod.NamedFunction(name + "$gowrapper")
if !wrapper.IsNil() {
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
wrapper.SetLinkage(llvm.PrivateLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(entry)
// Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes()
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-1])
params = append(params, llvm.Undef(c.i8ptrType))
// Create the call.
c.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.
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, ".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(entry)
// Get the list of parameters, with the extra parameters at the end.
paramTypes := fn.Type().ElementType().ParamTypes()
paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes)
// 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.
c.builder.CreateCall(fnPtr, params, "")
}
// Finish the function. Every basic block must end in a terminator, and
// because goroutines never return a value we can simply return void.
c.builder.CreateRetVoid()
// Return a ptrtoint of the wrapper, not the function itself.
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
+56 -2
View File
@@ -18,10 +18,19 @@ import (
// func ReadRegister(name string) uintptr
//
// The register name must be a constant, for example "sp".
func (c *Compiler) emitReadRegister(args []ssa.Value) (llvm.Value, error) {
func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
var asm string
switch name {
case "device/arm.ReadRegister":
asm = "mov $0, " + regname
case "device/riscv.ReadRegister":
asm = "mv $0, " + regname
default:
panic("unknown architecture")
}
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
}
@@ -153,3 +162,48 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
return c.builder.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 (c *Compiler) emitCSROperation(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
csrConst, ok := call.Args[0].(*ssa.Const)
if !ok {
return llvm.Value{}, c.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(c.uintptrType, nil, false)
asm := fmt.Sprintf("csrr $0, %d", csr)
target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
case "Set":
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.uintptrType}, false)
asm := fmt.Sprintf("csrw %d, $0", csr)
target := llvm.InlineAsm(fnType, asm, "r", true, false, 0)
return c.builder.CreateCall(target, []llvm.Value{c.getValue(frame, call.Args[1])}, ""), nil
case "SetBits":
// Note: it may be possible to optimize this to csrrsi in many cases.
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{c.uintptrType}, false)
asm := fmt.Sprintf("csrrs $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
return c.builder.CreateCall(target, []llvm.Value{c.getValue(frame, call.Args[1])}, ""), nil
case "ClearBits":
// Note: it may be possible to optimize this to csrrci in many cases.
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{c.uintptrType}, false)
asm := fmt.Sprintf("csrrc $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
return c.builder.CreateCall(target, []llvm.Value{c.getValue(frame, call.Args[1])}, ""), nil
default:
return llvm.Value{}, c.makeError(call.Pos(), "unknown CSR operation: "+name)
}
}
+109 -30
View File
@@ -28,14 +28,14 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
itfMethodSetGlobal := c.getTypeMethodSet(typ)
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
typeInInterface := c.getLLVMRuntimeType("typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
itfConcreteTypeGlobal.SetGlobalConstant(true)
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
}
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
return itf
@@ -45,27 +45,100 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
// 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 {
globalName := "type:" + getTypeCodeName(typ)
globalName := "reflect/types.type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
// 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 *Compiler) 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 {
name := ""
if named, ok := t.(*types.Named); ok {
name = "~" + named.String() + ":"
t = t.Underlying()
}
switch t := t.(type) {
case *types.Named:
return "named:" + t.String()
case *types.Array:
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
case *types.Basic:
var kind string
switch t.Kind() {
@@ -108,21 +181,21 @@ func getTypeCodeName(t types.Type) string {
default:
panic("unknown basic type: " + t.Name())
}
return "basic:" + name + kind
return "basic:" + kind
case *types.Chan:
return "chan:" + name + getTypeCodeName(t.Elem())
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:" + name + "{" + strings.Join(methods, ",") + "}"
return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map:
keyType := getTypeCodeName(t.Key())
elemType := getTypeCodeName(t.Elem())
return "map:" + name + "{" + keyType + "," + elemType + "}"
return "map:" + "{" + keyType + "," + elemType + "}"
case *types.Pointer:
return "pointer:" + name + getTypeCodeName(t.Elem())
return "pointer:" + getTypeCodeName(t.Elem())
case *types.Signature:
params := make([]string, t.Params().Len())
for i := 0; i < t.Params().Len(); i++ {
@@ -132,19 +205,22 @@ func getTypeCodeName(t types.Type) string {
for i := 0; i < t.Results().Len(); i++ {
results[i] = getTypeCodeName(t.Results().At(i).Type())
}
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
case *types.Slice:
return "slice:" + name + getTypeCodeName(t.Elem())
return "slice:" + getTypeCodeName(t.Elem())
case *types.Struct:
elems := make([]string, t.NumFields())
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
// TODO: report this as a normal error instead of panicking.
panic("cgo unions are not allowed in interfaces")
}
for i := 0; i < t.NumFields(); i++ {
elems[i] = getTypeCodeName(t.Field(i).Type())
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:" + name + "{" + strings.Join(elems, ",") + "}"
return "struct:" + "{" + strings.Join(elems, ",") + "}"
default:
panic("unknown type: " + t.String())
}
@@ -163,11 +239,11 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
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))
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))
@@ -203,14 +279,14 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
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)
value := llvm.ConstArray(c.i8ptrType, methods)
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
global.SetInitializer(value)
global.SetGlobalConstant(true)
@@ -219,7 +295,7 @@ 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 {
signature := ir.MethodSignature(method)
@@ -300,7 +376,7 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
// Continue after the if statement.
c.builder.SetInsertPointAtEnd(nextBlock)
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{c.getZeroValue(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
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
@@ -385,6 +461,9 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
if f.LLVMFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle")
}
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
fn: f,
wrapper: wrapper,
@@ -403,7 +482,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
wrapper.SetUnnamedAddr(true)
// add debug info if needed
if c.Debug {
if c.Debug() {
pos := c.ir.Program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
+92
View File
@@ -0,0 +1,92 @@
package compiler
import (
"strconv"
"strings"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// emitInterruptGlobal 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 (c *Compiler) emitInterruptGlobal(frame *Frame, 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{}, c.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 := c.getValue(frame, 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{}, c.makeError(instr.Pos(), "closures are not supported in interrupt.New")
}
// Fall back to a generic error.
return llvm.Value{}, c.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 := c.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := c.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := c.mod.NamedGlobal(globalName); !global.IsNil() {
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt redeclared in this program")
}
global := llvm.AddGlobal(c.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(c.intType, uint64(id.Int64()), true), []uint32{1, 0})
global.SetInitializer(initializer)
// Add debug info to the interrupt global.
if c.Debug() {
pos := c.ir.Program.Fset.Position(instr.Pos())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
LinkageName: globalName,
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: c.getDIType(globalType),
Expr: c.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, c.intType)
interrupt := llvm.ConstNamedStruct(c.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(c.Triple(), "avr") {
useFn := c.mod.NamedFunction("runtime/interrupt.use")
if useFn.IsNil() {
useFnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
useFn = llvm.AddFunction(c.mod, "runtime/interrupt.use", useFnType)
}
c.builder.CreateCall(useFn, []llvm.Value{interrupt}, "")
}
return interrupt, nil
}
+24 -109
View File
@@ -1,6 +1,7 @@
package compiler
import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
@@ -22,22 +23,6 @@ func getUses(value llvm.Value) []llvm.Value {
return uses
}
// createEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is
// at the end of the current block.
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value {
currentBlock := c.builder.GetInsertBlock()
entryBlock := currentBlock.Parent().EntryBasicBlock()
if entryBlock.FirstInstruction().IsNil() {
c.builder.SetInsertPointAtEnd(entryBlock)
} else {
c.builder.SetInsertPointBefore(entryBlock.FirstInstruction())
}
alloca := c.builder.CreateAlloca(t, name)
c.builder.SetInsertPointAtEnd(currentBlock)
return alloca
}
// createTemporaryAlloca creates a new alloca in the entry block and adds
// lifetime start infromation in the IR signalling that the alloca won't be used
// before this point.
@@ -45,110 +30,40 @@ func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value {
// This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it.
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
alloca = c.createEntryBlockAlloca(t, name)
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
return
return llvmutil.CreateTemporaryAlloca(c.builder, c.mod, t, name)
}
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
// llvm.lifetime.end intrinsic. It is commonly used together with
// createTemporaryAlloca.
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, ptr}, "")
llvmutil.EmitLifetimeEnd(c.builder, c.mod, ptr, size)
}
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
// first if it doesn't exist yet.
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
fn := c.mod.NamedFunction("llvm.lifetime.start.p0i8")
if fn.IsNil() {
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
fn = llvm.AddFunction(c.mod, "llvm.lifetime.start.p0i8", fnType)
}
return fn
// emitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(c.builder, c.mod, c.Config, values)
}
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
// first if it doesn't exist yet.
func (c *Compiler) getLifetimeEndFunc() llvm.Value {
fn := c.mod.NamedFunction("llvm.lifetime.end.p0i8")
if fn.IsNil() {
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
fn = llvm.AddFunction(c.mod, "llvm.lifetime.end.p0i8", fnType)
}
return fn
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes)
}
// 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 (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
oldBlock := afterInst.InstructionParent()
newBlock := c.ctx.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)
// makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global.
// Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself.
func (c *Compiler) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
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)})
}
// Move instructions.
c.builder.SetInsertPointAtEnd(newBlock)
for _, inst := range nextInstructions {
inst.RemoveFromParentAsInstruction()
c.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 {
c.builder.SetInsertPointBefore(phi)
newPhi := c.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
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
}
+141
View File
@@ -0,0 +1,141 @@
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.
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 {
// 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
}
+65 -26
View File
@@ -6,9 +6,45 @@ import (
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// emitMakeMap creates a new map object (runtime.hashmap) by allocating and
// initializing an appropriately sized object.
func (c *Compiler) emitMakeMap(frame *Frame, expr *ssa.MakeMap) (llvm.Value, error) {
mapType := expr.Type().Underlying().(*types.Map)
keyType := mapType.Key().Underlying()
llvmValueType := c.getLLVMType(mapType.Elem().Underlying())
var llvmKeyType llvm.Type
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// String keys.
llvmKeyType = c.getLLVMType(keyType)
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = c.getLLVMType(keyType)
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = c.getLLVMRuntimeType("_interface")
}
keySize := c.targetData.TypeAllocSize(llvmKeyType)
valueSize := c.targetData.TypeAllocSize(llvmValueType)
llvmKeySize := llvm.ConstInt(c.ctx.Int8Type(), keySize, false)
llvmValueSize := llvm.ConstInt(c.ctx.Int8Type(), valueSize, false)
sizeHint := llvm.ConstInt(c.uintptrType, 8, false)
if expr.Reserve != nil {
sizeHint = c.getValue(frame, expr.Reserve)
var err error
sizeHint, err = c.parseConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos())
if err != nil {
return llvm.Value{}, err
}
}
hashmap := c.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "")
return hashmap, nil
}
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType := c.getLLVMType(valueType)
@@ -19,6 +55,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
// 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}
@@ -34,8 +71,14 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} else {
// Not trivially comparable using memcmp.
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// Not trivially comparable using memcmp. 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 = c.parseMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapInterfaceGet", params, "")
}
// Load the resulting value from the hashmap. The value is set to the zero
@@ -69,7 +112,14 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
c.createRuntimeCall("hashmapBinarySet", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
} else {
c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// Key is not trivially comparable, so compare it as an interface instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first.
itfKey = c.parseMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey, valuePtr}
c.createRuntimeCall("hashmapInterfaceSet", params, "")
}
c.emitLifetimeEnd(valuePtr, valueSize)
}
@@ -89,38 +139,27 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
c.emitLifetimeEnd(keyPtr, keySize)
return nil
} else {
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// 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 = c.parseMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey}
c.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()
+61 -271
View File
@@ -3,12 +3,13 @@ package compiler
import (
"errors"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
// Run the LLVM optimizer over the module.
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) error {
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []error {
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
@@ -18,8 +19,16 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
}
builder.AddCoroutinePassesToExtensionPoints()
if c.PanicStrategy == "trap" {
c.replacePanicsWithTrap() // -panic=trap
if c.PanicStrategy() == "trap" {
transform.ReplacePanicsWithTrap(c.mod) // -panic=trap
}
// run a check of all of our code
if c.VerifyIR() {
errs := c.checkModule()
if errs != nil {
return errs
}
}
// Run function passes for each function.
@@ -36,6 +45,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
// Run some preparatory passes for the Go optimizer.
goPasses := llvm.NewPassManager()
defer goPasses.Dispose()
goPasses.AddGlobalDCEPass()
goPasses.AddGlobalOptimizerPass()
goPasses.AddConstantPropagationPass()
goPasses.AddAggressiveDCEPass()
@@ -43,11 +53,19 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
goPasses.Run(c.mod)
// Run Go-specific optimization passes.
c.OptimizeMaps()
c.OptimizeStringToBytes()
c.OptimizeAllocs()
c.LowerInterfaces()
c.LowerFuncValues()
transform.OptimizeMaps(c.mod)
transform.OptimizeStringToBytes(c.mod)
transform.OptimizeAllocs(c.mod)
transform.LowerInterfaces(c.mod)
errs := transform.LowerInterrupts(c.mod)
if len(errs) > 0 {
return errs
}
if c.funcImplementation() == funcValueSwitch {
transform.LowerFuncValues(c.mod)
}
// After interfaces are lowered, there are many more opportunities for
// interprocedural optimizations. To get them to work, function
@@ -55,8 +73,8 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
goPasses.Run(c.mod)
// Run TinyGo-specific interprocedural optimizations.
c.OptimizeAllocs()
c.OptimizeStringToBytes()
transform.OptimizeAllocs(c.mod)
transform.OptimizeStringToBytes(c.mod)
// Lower runtime.isnil calls to regular nil comparisons.
isnil := c.mod.NamedFunction("runtime.isnil")
@@ -76,19 +94,30 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
err := c.LowerGoroutines()
if err != nil {
return err
return []error{err}
}
} else {
// Must be run at any optimization level.
c.LowerInterfaces()
c.LowerFuncValues()
transform.LowerInterfaces(c.mod)
if c.funcImplementation() == funcValueSwitch {
transform.LowerFuncValues(c.mod)
}
err := c.LowerGoroutines()
if err != nil {
return err
return []error{err}
}
errs := transform.LowerInterrupts(c.mod)
if len(errs) > 0 {
return errs
}
}
if c.VerifyIR() {
if errs := c.checkModule(); errs != nil {
return errs
}
}
if err := c.Verify(); err != nil {
return errors.New("optimizations caused a verification failure")
return []error{errors.New("optimizations caused a verification failure")}
}
if sizeLevel >= 2 {
@@ -101,6 +130,15 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
}
}
// After TinyGo-specific transforms have finished, undo exporting these functions.
for _, name := range c.getFunctionsUsedInTransforms() {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
fn.SetLinkage(llvm.InternalLinkage)
}
// Run function passes again, because without it, llvm.coro.size.i32()
// doesn't get lowered.
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
@@ -114,261 +152,13 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
builder.Populate(modPasses)
modPasses.Run(c.mod)
hasGCPass := transform.AddGlobalsBitmap(c.mod)
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
if hasGCPass {
if err := c.Verify(); err != nil {
return []error{errors.New("GC pass caused a verification failure")}
}
}
return nil
}
// Replace panic calls with calls to llvm.trap, to reduce code size. This is the
// -panic=trap intrinsic.
func (c *Compiler) replacePanicsWithTrap() {
trap := c.mod.NamedFunction("llvm.trap")
for _, name := range []string{"runtime._panic", "runtime.runtimePanic"} {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
for _, use := range getUses(fn) {
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
panic("expected use of a panic function to be a call")
}
c.builder.SetInsertPointBefore(use)
c.builder.CreateCall(trap, nil, "")
}
}
}
// Eliminate created but not used maps.
//
// In the future, this should statically allocate created but never modified
// 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 {
if !c.hasFlag(use, value, "nocapture") {
return true
}
} else if use.IsAICmpInst() != nilValue {
// Comparing pointers don't let the pointer escape.
// This is often a compiler-inserted nil check.
} else {
// Unknown instruction, might escape.
return true
}
}
// 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
}
-156
View File
@@ -1,156 +0,0 @@
package compiler
import (
"math/big"
"strings"
)
var basicTypes = map[string]int64{
"bool": 1,
"int": 2,
"int8": 3,
"int16": 4,
"int32": 5,
"int64": 6,
"uint": 7,
"uint8": 8,
"uint16": 9,
"uint32": 10,
"uint64": 11,
"uintptr": 12,
"float32": 13,
"float64": 14,
"complex64": 15,
"complex128": 16,
"string": 17,
"unsafeptr": 18,
}
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
fn := c.mod.NamedFunction("reflect.ValueOf")
if fn.IsNil() {
// reflect.ValueOf is never used, so we can use the most efficient
// encoding possible.
for i, t := range typeSlice {
t.num = uint64(i + 1)
}
return
}
// Assign typecodes the way the reflect package expects.
fallbackIndex := 1
namedTypes := make(map[string]int)
for _, t := range typeSlice {
if t.name[:5] != "type:" {
panic("expected type name to start with 'type:'")
}
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
// TODO: support this in some way, using a side table for example.
// That's less efficient but better than not working at all.
// Particularly important on systems with 16-bit pointers (e.g.
// AVR).
panic("compiler: could not store type code number inside interface type code")
}
t.num = num.Uint64()
}
}
// getTypeCodeNum returns the typecode for a given type as expected by the
// reflect package. Also see getTypeCodeName, which serializes types to a string
// based on a types.Type value for this function.
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
// Note: see src/reflect/type.go for bit allocations.
// A type can be named or unnamed. Example of both:
// basic:~foo:uint64
// basic:uint64
// Extract the class (basic, slice, pointer, etc.), the name, and the
// contents of this type ID string. Allocate bits based on that, as
// src/runtime/types.go expects.
class := id[:strings.IndexByte(id, ':')]
value := id[len(class)+1:]
name := ""
if value[0] == '~' {
name = value[1:strings.IndexByte(value, ':')]
value = value[len(name)+2:]
}
if class == "basic" {
// Basic types follow the following bit pattern:
// ...xxxxx0
// where xxxxx is allocated for the 18 possible basic types and all the
// upper bits are used to indicate the named type.
num, ok := basicTypes[value]
if !ok {
panic("invalid basic type: " + id)
}
if name != "" {
// This type is named, set the upper bits to the name ID.
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
}
return big.NewInt(num << 1)
} else {
// Complex types use the following bit pattern:
// ...nxxx1
// where xxx indicates the complex type (any non-basic type). The upper
// bits contain whatever the type contains. Types that wrap a single
// other type (channel, interface, pointer, slice) just contain the bits
// of the wrapped type. Other types (like struct) have a different
// method of encoding the contents of the type.
var num *big.Int
var classNumber int64
switch class {
case "chan":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 0
case "interface":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 1
case "pointer":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 2
case "slice":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 3
case "array":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 4
case "func":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 5
case "map":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 6
case "struct":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 7
default:
panic("unknown type kind: " + id)
}
if name == "" {
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
} else {
// TODO: store num in a sidetable
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
}
return num
}
}
// getNamedTypeNum returns an appropriate (unique) number for the given named
// type. If the name already has a number that number is returned, else a new
// number is returned. The number is always non-zero.
func getNamedTypeNum(namedTypes map[string]int, name string) int {
if num, ok := namedTypes[name]; ok {
return num
} else {
num = len(namedTypes) + 1
namedTypes[name] = num
return num
}
}
+9 -29
View File
@@ -63,12 +63,6 @@ func (s *StdSizes) Alignof(T types.Type) int64 {
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
offsets := make([]int64, len(fields))
if len(fields) > 1 && fields[0].Name() == "C union" {
// This struct contains the magic "C union" field which indicates that
// this is actually a union from CGo.
// All fields in the union start at 0 so return that.
return offsets // all fields are still set to 0
}
var o int64
for i, f := range fields {
a := s.Alignof(f.Type())
@@ -116,6 +110,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
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()
@@ -143,29 +140,12 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
maxAlign = al
}
}
if fields[0].Name() == "C union" {
// Magic field that indicates this is a CGo union and not a struct.
// The size is the biggest element, aligned to the element with the
// biggest alignment. This is not necessarily the same, for example
// in the following union:
// union { int32_t l; int16_t s[3] }
maxSize := int64(0)
for _, field := range fields[1:] {
si := s.Sizeof(field.Type())
if si > maxSize {
maxSize = si
}
}
return align(maxSize, maxAlign)
} else {
// This is a regular struct.
// Pick the size that fits this struct and add some alignment. Some
// structs have some extra padding at the end which should also be
// taken care of:
// struct { int32 n; byte b }
offsets := s.Offsetsof(fields)
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
}
// Pick the size that fits this struct and add some alignment. Some
// structs have some extra padding at the end which should also be taken
// care of:
// struct { int32 n; byte b }
offsets := s.Offsetsof(fields)
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
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 *Compiler) loadASTComments(lprogram *loader.Program) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
c.astComments[id] = decl.Doc
}
}
}
}
}
}
}
}
}
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
// the LLVM IR if it has not been added already.
func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
info := c.getGlobalInfo(g)
llvmGlobal := c.mod.NamedGlobal(info.linkName)
if llvmGlobal.IsNil() {
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 *Compiler) getGlobalInfo(g *ssa.Global) globalInfo {
info := globalInfo{}
if strings.HasPrefix(g.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
info.linkName = g.Name()[2:]
info.extern = true
} else {
// Pick the default linkName.
info.linkName = g.RelString(nil)
// Check for //go: pragmas, which may change the link name (among
// others).
doc := c.astComments[info.linkName]
if doc != nil {
info.parsePragmas(doc)
}
}
return info
}
// Parse //go: pragma comments from the source. In particular, it parses the
// //go:extern pragma on globals.
func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
for _, comment := range doc.List {
if !strings.HasPrefix(comment.Text, "//go:") {
continue
}
parts := strings.Fields(comment.Text)
switch parts[0] {
case "//go:extern":
info.extern = true
if len(parts) == 2 {
info.linkName = parts[1]
}
case "//go:align":
align, err := strconv.Atoi(parts[1])
if err == nil {
info.align = align
}
}
}
}
+41 -16
View File
@@ -4,7 +4,6 @@ package compiler
// compiler builtins.
import (
"go/constant"
"strconv"
"golang.org/x/tools/go/ssa"
@@ -14,11 +13,11 @@ import (
// emitSyscall emits an inline system call instruction, depending on the target
// OS/arch.
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
num := c.getValue(frame, call.Args[0])
var syscallResult llvm.Value
switch {
case c.GOARCH == "amd64":
if c.GOOS == "darwin" {
case c.GOARCH() == "amd64":
if c.GOOS() == "darwin" {
// Darwin adds this magic number to system call numbers:
//
// > Syscall classes for 64-bit system call entry.
@@ -29,12 +28,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
// > All system classes enter the kernel via the syscall instruction.
//
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
num += 0x2000000
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
}
// Sources:
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
args := []llvm.Value{num}
argTypes := []llvm.Type{c.uintptrType}
// Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
@@ -59,7 +58,33 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH == "arm" && c.GOOS == "linux":
case c.GOARCH() == "386" && c.GOOS() == "linux":
// Sources:
// syscall(2) man page
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
args := []llvm.Value{num}
argTypes := []llvm.Type{c.uintptrType}
// 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 := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH() == "arm" && c.GOOS() == "linux":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
args := []llvm.Value{}
@@ -81,7 +106,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
args = append(args, num)
argTypes = append(argTypes, c.uintptrType)
constraints += ",{r7}" // syscall number
for i := len(call.Args) - 1; i < 4; i++ {
@@ -91,7 +116,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH == "arm64" && c.GOOS == "linux":
case c.GOARCH() == "arm64" && c.GOOS() == "linux":
// Source: syscall(2) man page.
args := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -111,7 +136,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
args = append(args, num)
argTypes = append(argTypes, c.uintptrType)
constraints += ",{x8}" // syscall number
for i := len(call.Args) - 1; i < 8; i++ {
@@ -124,10 +149,10 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = c.builder.CreateCall(target, args, "")
default:
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
}
switch c.GOOS {
case "linux":
switch c.GOOS() {
case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno
// Pseudocode:
// var err uintptr
@@ -140,7 +165,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
@@ -156,12 +181,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
zero := llvm.ConstInt(c.uintptrType, 0, false)
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
default:
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
}
}
-117
View File
@@ -1,117 +0,0 @@
package compiler
// This file contains utility functions to pack and unpack sets of values. It
// can take in a list of values and tries to store it efficiently in the pointer
// itself if possible and legal.
import (
"tinygo.org/x/go-llvm"
)
// emitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
valueTypes := make([]llvm.Type, len(values))
for i, value := range values {
valueTypes[i] = value.Type()
}
packedType := c.ctx.StructType(valueTypes, false)
// Allocate memory for the packed data.
var packedAlloc, packedHeapAlloc llvm.Value
size := c.targetData.TypeAllocSize(packedType)
if size == 0 {
return llvm.ConstPointerNull(c.i8ptrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
// Packed data fits in a pointer, so store it directly inside the
// pointer.
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
// Try to keep this cast in SSA form.
return c.builder.CreateIntToPtr(values[0], c.i8ptrType, "pack.int")
}
// Because packedType is a struct and we have to cast it to a *i8, store
// it in an alloca first for bitcasting (store+bitcast+load).
packedAlloc, _, _ = c.createTemporaryAlloca(packedType, "")
} else {
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
}
// Store all values in the alloca or heap pointer.
for i, value := range values {
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
c.builder.CreateStore(value, gep)
}
if packedHeapAlloc.IsNil() {
// Load value (as *i8) from the alloca.
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
result := c.builder.CreateLoad(packedAlloc, "")
packedPtr := c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
packedSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(packedAlloc.Type()), false)
c.emitLifetimeEnd(packedPtr, packedSize)
return result
} else {
// Get the original heap allocation pointer, which already is an *i8.
return packedHeapAlloc
}
}
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
packedType := c.ctx.StructType(valueTypes, false)
// Get a correctly-typed pointer to the packed data.
var packedAlloc, packedRawAlloc llvm.Value
size := c.targetData.TypeAllocSize(packedType)
if size == 0 {
// No data to unpack.
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly.
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
// Packed data stored directly in pointer.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form.
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
}
// Fallback: load it using an alloca.
packedRawAlloc, _, _ = c.createTemporaryAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
c.builder.CreateStore(packedRawValue, packedRawAlloc)
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
} else {
// Packed data stored on the heap. Bitcast the passed-in pointer to the
// correct pointer type.
packedAlloc = c.builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
}
// Load each value from the packed data.
values := make([]llvm.Value, len(valueTypes))
for i, valueType := range valueTypes {
if c.targetData.TypeAllocSize(valueType) == 0 {
// This value has length zero, so there's nothing to load.
values[i] = c.getZeroValue(valueType)
continue
}
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
values[i] = c.builder.CreateLoad(gep, "")
}
if !packedRawAlloc.IsNil() {
allocPtr := c.builder.CreateBitCast(packedRawAlloc, c.i8ptrType, "")
allocSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(c.uintptrType), false)
c.emitLifetimeEnd(allocPtr, allocSize)
}
return values
}
+5 -1
View File
@@ -4,7 +4,11 @@ go 1.11
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/creack/goselect v0.1.0 // indirect
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566
)
+22
View File
@@ -1,14 +1,36 @@
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 h1:mACY1anK6HNCZtm/DK2Rf2ZPHggVqeB0+7rY9Gl6wyI=
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9 h1:d6rAX39a3C0pKrY5HcojEGyN8w9ocU0v7X28lC/TRKU=
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2 h1:Q5Hv3e5cLMGkiYwYgZL1Zrv6nb/EY+DJpRWrdO6ws6o=
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8 h1:9Bfvso+tTVQg16UzOA614NaYA4x8vsRBNtd3eBrXwp0=
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc=
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae h1:s8J5EyxCkHxXB08UI3gk9W9IS/ekizRvSX+PfZxnAB0=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566 h1:a4y30bTf7U0zDA75v2PTL+XQ2OzJetj19gK8XwQpUNY=
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566/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
}
+79 -2
View File
@@ -3,15 +3,92 @@ package interp
// This file provides useful types for errors encountered during IR evaluation.
import (
"errors"
"go/scanner"
"go/token"
"path/filepath"
"tinygo.org/x/go-llvm"
)
// errUnreachable is returned when an unreachable instruction is executed. This
// error should not be visible outside of the interp package.
var errUnreachable = errors.New("interp: unreachable executed")
// Unsupported is the specific error that is returned when an unsupported
// instruction is hit while trying to interpret all initializers.
type Unsupported struct {
Inst llvm.Value
ImportPath string
Inst llvm.Value
Pos token.Position
}
func (e Unsupported) Error() string {
// TODO: how to return the actual instruction string?
// It looks like LLVM provides no function for that...
return "interp: unsupported instruction"
return scanner.Error{
Pos: e.Pos,
Msg: "interp: unsupported instruction",
}.Error()
}
// unsupportedInstructionError returns a new "unsupported instruction" error for
// the given instruction. It includes source location information, when
// available.
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Unsupported {
return &Unsupported{
ImportPath: e.packagePath,
Inst: inst,
Pos: getPosition(inst),
}
}
// Error encapsulates compile-time interpretation errors with an associated
// import path. The errors may not have a precise location attached.
type Error struct {
ImportPath string
Errs []scanner.Error
}
// Error returns the string of the first error in the list of errors.
func (e Error) Error() string {
return e.Errs[0].Error()
}
// errorAt returns an error value for the currently interpreted package at the
// location of the instruction. The location information may not be complete as
// it depends on debug information in the IR.
func (e *evalPackage) errorAt(inst llvm.Value, msg string) Error {
return Error{
ImportPath: e.packagePath,
Errs: []scanner.Error{errorAt(inst, 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 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()),
}
}
+176 -53
View File
@@ -4,21 +4,17 @@ package interp
// functions.
import (
"errors"
"strings"
"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.
@@ -79,14 +75,14 @@ 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():
allocType := inst.Type().ElementType()
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
alloca.SetInitializer(getZeroValue(allocType))
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloca")
alloca.SetInitializer(llvm.ConstNull(allocType))
alloca.SetLinkage(llvm.InternalLinkage)
fr.locals[inst] = &LocalValue{
Underlying: alloca,
@@ -101,7 +97,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
value = operand.Load()
}
if value.Type() != inst.Type() {
panic("interp: load: type does not match")
return nil, nil, fr.errorAt(inst, "interp: load: type does not match")
}
fr.locals[inst] = fr.getValue(value)
case !inst.IsAStoreInst().IsNil():
@@ -125,15 +121,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, "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.Error())
}
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, "interp: gep: type does not match")
}
fr.locals[inst] = result
@@ -176,6 +173,20 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
continue // special case: bitcast of alloc
}
}
if _, ok := fr.getLocal(operand).(*MapValue); ok {
// Special case for runtime.trackPointer calls.
// Note: this might not be entirely sound in some rare cases
// where the map is stored in a dirty global.
uses := getUses(inst)
if len(uses) == 1 {
use := uses[0]
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
continue
}
}
// It is not possible in Go to bitcast a map value to a pointer.
return nil, nil, fr.errorAt(inst, "unimplemented: bitcast of map")
}
value := fr.getLocal(operand).(*LocalValue)
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
@@ -184,20 +195,28 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
predicate := inst.IntPredicate()
if predicate == llvm.IntEQ && lhs.Type().TypeKind() == llvm.PointerTypeKind {
// Unfortunately, the const propagation in the IR builder
// doesn't handle pointer compares of inttoptr values. So we
// implement it manually here.
lhsNil, ok1 := isPointerNil(lhs)
rhsNil, ok2 := isPointerNil(rhs)
if 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 lhsNil && rhsNil {
// Both are nil, so this icmp is always evaluated to true.
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 lhsNil != rhsNil {
// Only one of them is nil, so this comparison must return false.
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
}
@@ -233,13 +252,13 @@ 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 := &LocalValue{
Underlying: alloc,
@@ -248,7 +267,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
if elementCount == 1 {
fr.locals[resultInst] = result
} else {
fr.locals[resultInst] = result.GetElementPtr([]uint32{0, 0})
result, err := result.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, errorAt(inst, err.Error())
}
fr.locals[resultInst] = result
}
case callee.Name() == "runtime.hashmapMake":
// create a map
@@ -256,24 +279,55 @@ 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)
// "key" is a Go string value, which in the TinyGo calling convention is split up
// into separate pointer and length parameters.
keyBuf := fr.getLocal(inst.Operand(1)).(*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
m := fr.getLocal(inst.Operand(0)).(*MapValue)
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
@@ -290,7 +344,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)
@@ -298,10 +352,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, errorAt(inst, err.Error())
}
dstArray = dstArrayValue.(*LocalValue)
srcArrayValue, err := srcArray.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, errorAt(inst, err.Error())
}
srcArray = srcArrayValue.(*LocalValue)
}
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
return nil, nil, fr.errorAt(inst, "interp: slice dst element size does not match pointer type")
}
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
return nil, nil, fr.errorAt(inst, "interp: slice src element size does not match pointer type")
}
if dstArray.Type() != srcArray.Type() {
return nil, nil, fr.errorAt(inst, "interp: slice element types don't match")
}
length := dstLen.Value().SExtValue()
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
length = srcLength
}
if length < 0 {
return nil, nil, fr.errorAt(inst, "interp: trying to copy a slice with negative length?")
}
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, errorAt(inst, err.Error())
}
dstArray = dstArrayValue.(*LocalValue)
// src++
srcArrayValue, err := srcArray.GetElementPtr([]uint32{1})
if err != nil {
return nil, nil, errorAt(inst, err.Error())
}
srcArray = srcArrayValue.(*LocalValue)
}
case callee.Name() == "runtime.stringToBytes":
// convert a string to a []byte
bufPtr := fr.getLocal(inst.Operand(0))
@@ -313,7 +427,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)
@@ -321,7 +435,7 @@ 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
@@ -330,16 +444,16 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
panic("interp: expected typecode to be a ptrtoint")
return nil, nil, fr.errorAt(inst, "interp: expected typecode to be a ptrtoint")
}
typecode = typecode.Operand(0)
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
panic("interp: expected method set in runtime.interfaceImplements to be a constant gep")
return nil, nil, fr.errorAt(inst, "interp: expected method set in runtime.interfaceImplements to be a constant gep")
}
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
panic("interp: expected method set to be a constant gep")
return nil, nil, fr.errorAt(inst, "interp: expected method set to be a constant gep")
}
methodSet = methodSet.Operand(0).Initializer()
@@ -368,6 +482,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
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.
@@ -407,7 +525,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
@@ -430,7 +551,7 @@ 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 {
return nil, nil, err
}
@@ -440,7 +561,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
@@ -450,7 +571,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, "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())}
}
@@ -463,7 +584,7 @@ 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, "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())}
}
@@ -476,17 +597,17 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// conditional branch (if/then/else)
cond := fr.getLocal(inst.Operand(0)).Value()
if cond.Type() != fr.Mod.Context().Int1Type() {
panic("expected an i1 in a branch instruction")
return nil, nil, fr.errorAt(inst, "expected an i1 in a branch instruction")
}
thenBB := inst.Operand(1)
elseBB := inst.Operand(2)
if !cond.IsAInstruction().IsNil() {
return nil, nil, errors.New("interp: branch on a non-constant")
return nil, nil, fr.errorAt(inst, "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, errors.New("interp: branch on a non-const-propagated constant expression")
return nil, nil, fr.errorAt(inst, "interp: branch on a non-const-propagated constant expression")
}
switch cond {
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
@@ -494,7 +615,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
return nil, []llvm.Value{elseBB}, nil // else
default:
panic("branch was not true or false")
return nil, nil, fr.errorAt(inst, "branch was not true or false")
}
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
// unconditional branch (goto)
@@ -502,10 +623,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)
}
}
@@ -519,6 +641,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")
}
}
+25 -16
View File
@@ -7,7 +7,6 @@ package interp
// methods.
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
@@ -22,9 +21,17 @@ type Eval 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:")
}
@@ -32,7 +39,7 @@ 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{}{},
}
@@ -56,7 +63,7 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
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)
}
@@ -66,13 +73,17 @@ 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]
fn := call.CalledValue()
call.EraseFromParentAsInstruction()
_, err := e.Function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
if err == ErrUnreachable {
evalPkg := evalPackage{
Eval: e,
packagePath: pkgName,
}
_, err := evalPkg.function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, "")
if err == errUnreachable {
break
}
if err != nil {
@@ -83,16 +94,14 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
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]
+102
View File
@@ -0,0 +1,102 @@
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",
} {
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
}
+55 -20
View File
@@ -1,11 +1,28 @@
package interp
import (
"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,26 +40,36 @@ 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 {
switch fn.Name() {
case "runtime.alloc":
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}
case "runtime.nanotime":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.nanotime":
// Fixed value at compile time.
return &sideEffectResult{severity: sideEffectNone}
case "runtime._panic":
return &sideEffectResult{severity: sideEffectLimited}
case "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone}
case "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime._panic":
return &sideEffectResult{severity: sideEffectLimited}, 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 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,
@@ -53,6 +80,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")
}
@@ -69,7 +97,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, "unknown instructions")
case llvm.Call:
child := inst.CalledValue()
if !child.IsAInlineAsm().IsNil() {
@@ -95,7 +123,10 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
}
continue
}
childSideEffects := e.hasSideEffects(child)
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
@@ -137,7 +168,7 @@ 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
@@ -152,6 +183,7 @@ func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llv
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() {
@@ -171,10 +203,13 @@ func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llv
// 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.
+93
View File
@@ -0,0 +1,93 @@
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},
}
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)
}
}
}
+60
View File
@@ -0,0 +1,60 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.v1 = internal global i64 0
@main.nonConst1 = global [4 x i64] zeroinitializer
@main.nonConst2 = global i64 0
declare void @runtime.printint64(i64) unnamed_addr
declare void @runtime.printnl() unnamed_addr
define void @runtime.initAll() unnamed_addr {
entry:
call void @runtime.init()
call void @main.init()
ret void
}
define void @main() unnamed_addr {
entry:
%0 = load i64, i64* @main.v1
call void @runtime.printint64(i64 %0)
call void @runtime.printnl()
ret void
}
define internal void @runtime.init() unnamed_addr {
entry:
ret void
}
define internal void @main.init() unnamed_addr {
entry:
store i64 3, i64* @main.v1
call void @"main.init#1"()
; test the following pattern:
; func someValue() int // extern function
; var nonConst1 = [4]int{someValue(), 0, 0, 0}
%value1 = call i64 @someValue()
%gep1 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
store i64 %value1, i64* %gep1
; Test that the global really is marked dirty:
; var nonConst2 = nonConst1[0]
%gep2 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
%value2 = load i64, i64* %gep2
store i64 %value2, i64* @main.nonConst2
ret void
}
define internal void @"main.init#1"() unnamed_addr {
entry:
call void @runtime.printint64(i64 5)
call void @runtime.printnl()
ret void
}
declare i64 @someValue()
+29
View File
@@ -0,0 +1,29 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.nonConst1 = local_unnamed_addr global [4 x i64] zeroinitializer
@main.nonConst2 = local_unnamed_addr global i64 0
declare void @runtime.printint64(i64) unnamed_addr
declare void @runtime.printnl() unnamed_addr
define void @runtime.initAll() unnamed_addr {
entry:
call void @runtime.printint64(i64 5)
call void @runtime.printnl()
%value1 = call i64 @someValue()
store i64 %value1, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
%value2 = load i64, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
store i64 %value2, i64* @main.nonConst2
ret void
}
define void @main() unnamed_addr {
entry:
call void @runtime.printint64(i64 3)
call void @runtime.printnl()
ret void
}
declare i64 @someValue() local_unnamed_addr
+42
View File
@@ -0,0 +1,42 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@intToPtrResult = global i8 0
@ptrToIntResult = global i8 0
define void @runtime.initAll() {
call void @main.init()
ret void
}
define internal void @main.init() {
call void @testIntToPtr()
call void @testPtrToInt()
ret void
}
define internal void @testIntToPtr() {
%nil = icmp eq i8* inttoptr (i64 1024 to i8*), null
br i1 %nil, label %a, label %b
a:
; should not be reached
store i8 1, i8* @intToPtrResult
ret void
b:
; should be reached
store i8 2, i8* @intToPtrResult
ret void
}
define internal void @testPtrToInt() {
%zero = icmp eq i64 ptrtoint (i8* @ptrToIntResult to i64), 0
br i1 %zero, label %a, label %b
a:
; should not be reached
store i8 1, i8* @ptrToIntResult
ret void
b:
; should be reached
store i8 2, i8* @ptrToIntResult
ret void
}
+9
View File
@@ -0,0 +1,9 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@intToPtrResult = local_unnamed_addr global i8 2
@ptrToIntResult = local_unnamed_addr global i8 2
define void @runtime.initAll() local_unnamed_addr {
ret void
}
+76
View File
@@ -0,0 +1,76 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv6m-none-eabi"
%runtime._string = type { i8*, i32 }
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
@main.m = global %runtime.hashmap* null
@main.binaryMap = global %runtime.hashmap* null
@main.stringMap = global %runtime.hashmap* null
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
declare %runtime.hashmap* @runtime.hashmapMake(i8, i8, i32, i8* %context, i8* %parentHandle)
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8* %context, i8* %parentHandle)
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8* %context, i8* %parentHandle)
declare void @llvm.lifetime.end.p0i8(i64, i8*)
declare void @llvm.lifetime.start.p0i8(i64, i8*)
define void @runtime.initAll() unnamed_addr {
entry:
call void @main.init(i8* undef, i8* null)
ret void
}
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
; Test that hashmap optimizations generally work (even with lifetimes).
%hashmap.key = alloca i8
%hashmap.value = alloca %runtime._string
%0 = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 8, i32 1, i8* undef, i8* null)
%hashmap.value.bitcast = bitcast %runtime._string* %hashmap.value to i8*
call void @llvm.lifetime.start.p0i8(i64 8, i8* %hashmap.value.bitcast)
store %runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string* %hashmap.value
call void @llvm.lifetime.start.p0i8(i64 1, i8* %hashmap.key)
store i8 1, i8* %hashmap.key
call void @runtime.hashmapBinarySet(%runtime.hashmap* %0, i8* %hashmap.key, i8* %hashmap.value.bitcast, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 1, i8* %hashmap.key)
call void @llvm.lifetime.end.p0i8(i64 8, i8* %hashmap.value.bitcast)
store %runtime.hashmap* %0, %runtime.hashmap** @main.m
; Other tests, that can be done in a separate function.
call void @main.testNonConstantBinarySet()
call void @main.testNonConstantStringSet()
ret void
}
; Test that a map loaded from a global can still be used for mapassign
; operations (with binary keys).
define internal void @main.testNonConstantBinarySet() {
%hashmap.key = alloca i8
%hashmap.value = alloca i8
; Create hashmap from global. This breaks the normal hashmapBinarySet
; optimization, to test the fallback.
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 1, i32 1, i8* undef, i8* null)
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.binaryMap
%map = load %runtime.hashmap*, %runtime.hashmap** @main.binaryMap
; Do the binary set to the newly loaded map.
store i8 1, i8* %hashmap.key
store i8 2, i8* %hashmap.value
call void @runtime.hashmapBinarySet(%runtime.hashmap* %map, i8* %hashmap.key, i8* %hashmap.value, i8* undef, i8* null)
ret void
}
; Test that a map loaded from a global can still be used for mapassign
; operations (with string keys).
define internal void @main.testNonConstantStringSet() {
%hashmap.value = alloca i8
; Create hashmap from global. This breaks the normal hashmapStringSet
; optimization, to test the fallback.
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 8, i8 1, i32 1, i8* undef, i8* null)
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.stringMap
%map = load %runtime.hashmap*, %runtime.hashmap** @main.stringMap
; Do the string set to the newly loaded map.
store i8 2, i8* %hashmap.value
call void @runtime.hashmapStringSet(%runtime.hashmap* %map, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* %hashmap.value, i8* undef, i8* null)
ret void
}
+28
View File
@@ -0,0 +1,28 @@
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv6m-none-eabi"
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
%runtime._string = type { i8*, i32 }
@main.m = local_unnamed_addr global %runtime.hashmap* @"main$map"
@main.binaryMap = local_unnamed_addr global %runtime.hashmap* @"main$map.4"
@main.stringMap = local_unnamed_addr global %runtime.hashmap* @"main$map.6"
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
@"main$mapbucket" = internal unnamed_addr global { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, [8 x i8] c"\01\00\00\00\00\00\00\00", [8 x %runtime._string] [%runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer] }
@"main$map" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }, { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }* @"main$mapbucket", i32 0, i32 0, i32 0), i32 1, i8 1, i8 8, i8 0 }
@"main$alloca.2" = internal global i8 1
@"main$alloca.3" = internal global i8 2
@"main$map.4" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 1, i8 1, i8 0 }
@"main$alloca.5" = internal global i8 2
@"main$map.6" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 8, i8 1, i8 0 }
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8*, i8*) local_unnamed_addr
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8*, i8*) local_unnamed_addr
define void @runtime.initAll() unnamed_addr {
entry:
call void @runtime.hashmapBinarySet(%runtime.hashmap* @"main$map.4", i8* @"main$alloca.2", i8* @"main$alloca.3", i8* undef, i8* null)
call void @runtime.hashmapStringSet(%runtime.hashmap* @"main$map.6", i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* @"main$alloca.5", i8* undef, i8* null)
ret void
}
+61
View File
@@ -0,0 +1,61 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
define i64 @returnsConst() {
ret i64 0
}
define i64 @returnsArg(i64 %arg) {
ret i64 %arg
}
declare i64 @externalCall()
define i64 @externalCallOnly() {
%result = call i64 @externalCall()
ret i64 0
}
define i64 @externalCallAndReturn() {
%result = call i64 @externalCall()
ret i64 %result
}
define i64 @externalCallBranch() {
%result = call i64 @externalCall()
%zero = icmp eq i64 %result, 0
br i1 %zero, label %if.then, label %if.done
if.then:
ret i64 2
if.done:
ret i64 4
}
@cleanGlobalInt = global i64 5
define i64 @readCleanGlobal() {
%global = load i64, i64* @cleanGlobalInt
ret i64 %global
}
@dirtyGlobalInt = global i64 5
define i64 @readDirtyGlobal() {
%global = load i64, i64* @dirtyGlobalInt
ret i64 %global
}
declare i64* @getDirtyPointer()
define void @storeToPointer() {
%ptr = call i64* @getDirtyPointer()
store i64 3, i64* %ptr
ret void
}
@functionPointer = global i64()* null
define i64 @callFunctionPointer() {
%fp = load i64()*, i64()** @functionPointer
%result = call i64 %fp()
ret i64 %result
}
+86
View File
@@ -0,0 +1,86 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.uint8SliceSrc.buf = internal global [2 x i8] c"\03d"
@main.uint8SliceSrc = internal unnamed_addr global { i8*, i64, i64 } { i8* getelementptr inbounds ([2 x i8], [2 x i8]* @main.uint8SliceSrc.buf, i32 0, i32 0), i64 2, i64 2 }
@main.uint8SliceDst = internal unnamed_addr global { i8*, i64, i64 } zeroinitializer
@main.int16SliceSrc.buf = internal global [3 x i16] [i16 5, i16 123, i16 1024]
@main.int16SliceSrc = internal unnamed_addr global { i16*, i64, i64 } { i16* getelementptr inbounds ([3 x i16], [3 x i16]* @main.int16SliceSrc.buf, i32 0, i32 0), i64 3, i64 3 }
@main.int16SliceDst = internal unnamed_addr global { i16*, i64, i64 } zeroinitializer
declare i64 @runtime.sliceCopy(i8* %dst, i8* %src, i64 %dstLen, i64 %srcLen, i64 %elemSize) unnamed_addr
declare i8* @runtime.alloc(i64) unnamed_addr
declare void @runtime.printuint8(i8)
declare void @runtime.printint16(i16)
define void @runtime.initAll() unnamed_addr {
entry:
call void @main.init()
ret void
}
define void @main() unnamed_addr {
entry:
; print(uintSliceSrc[0])
%uint8SliceSrc.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc, i64 0, i32 0)
%uint8SliceSrc.val = load i8, i8* %uint8SliceSrc.buf
call void @runtime.printuint8(i8 %uint8SliceSrc.val)
; print(uintSliceDst[0])
%uint8SliceDst.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceDst, i64 0, i32 0)
%uint8SliceDst.val = load i8, i8* %uint8SliceDst.buf
call void @runtime.printuint8(i8 %uint8SliceDst.val)
; print(int16SliceSrc[0])
%int16SliceSrc.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc, i64 0, i32 0)
%int16SliceSrc.val = load i16, i16* %int16SliceSrc.buf
call void @runtime.printint16(i16 %int16SliceSrc.val)
; print(int16SliceDst[0])
%int16SliceDst.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceDst, i64 0, i32 0)
%int16SliceDst.val = load i16, i16* %int16SliceDst.buf
call void @runtime.printint16(i16 %int16SliceDst.val)
ret void
}
define internal void @main.init() unnamed_addr {
entry:
; equivalent of:
; uint8SliceDst = make([]uint8, len(uint8SliceSrc))
%uint8SliceSrc = load { i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc
%uint8SliceSrc.len = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 1
%uint8SliceDst.buf = call i8* @runtime.alloc(i64 %uint8SliceSrc.len)
%0 = insertvalue { i8*, i64, i64 } undef, i8* %uint8SliceDst.buf, 0
%1 = insertvalue { i8*, i64, i64 } %0, i64 %uint8SliceSrc.len, 1
%2 = insertvalue { i8*, i64, i64 } %1, i64 %uint8SliceSrc.len, 2
store { i8*, i64, i64 } %2, { i8*, i64, i64 }* @main.uint8SliceDst
; equivalent of:
; copy(uint8SliceDst, uint8SliceSrc)
%uint8SliceSrc.buf = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 0
%copy.n = call i64 @runtime.sliceCopy(i8* %uint8SliceDst.buf, i8* %uint8SliceSrc.buf, i64 %uint8SliceSrc.len, i64 %uint8SliceSrc.len, i64 1)
; equivalent of:
; int16SliceDst = make([]int16, len(int16SliceSrc))
%int16SliceSrc = load { i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc
%int16SliceSrc.len = extractvalue { i16*, i64, i64 } %int16SliceSrc, 1
%int16SliceSrc.len.bytes = mul i64 %int16SliceSrc.len, 2
%int16SliceDst.buf.raw = call i8* @runtime.alloc(i64 %int16SliceSrc.len.bytes)
%int16SliceDst.buf = bitcast i8* %int16SliceDst.buf.raw to i16*
%3 = insertvalue { i16*, i64, i64 } undef, i16* %int16SliceDst.buf, 0
%4 = insertvalue { i16*, i64, i64 } %3, i64 %int16SliceSrc.len, 1
%5 = insertvalue { i16*, i64, i64 } %4, i64 %int16SliceSrc.len, 2
store { i16*, i64, i64 } %5, { i16*, i64, i64 }* @main.int16SliceDst
; equivalent of:
; copy(int16SliceDst, int16SliceSrc)
%int16SliceSrc.buf = extractvalue { i16*, i64, i64 } %int16SliceSrc, 0
%int16SliceSrc.buf.i8ptr = bitcast i16* %int16SliceSrc.buf to i8*
%int16SliceDst.buf.i8ptr = bitcast i16* %int16SliceDst.buf to i8*
%copy.n2 = call i64 @runtime.sliceCopy(i8* %int16SliceDst.buf.i8ptr, i8* %int16SliceSrc.buf.i8ptr, i64 %int16SliceSrc.len, i64 %int16SliceSrc.len, i64 2)
ret void
}

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