Compare commits

...

273 Commits

Author SHA1 Message Date
BCG 490f000f8a Merge remote-tracking branch 'ardnew/feature/usb-common' into feature/usb-common 2021-11-06 21:49:41 -04:00
Ayke van Laethem 7caf0732fa transform: add debug info in interface lowering pass
This is fake debug info. It doesn't point to a source location because
there is no source location. However, it helps to correctly attribute
code size usage to particular packages.

I've also updated builder/sizes.go with some debugging helpers.
2021-11-06 10:50:55 +01:00
Ayke van Laethem edcece33ca transform: refactor interrupt lowering
Instead of doing everything in the interrupt lowering pass, generate
some more code in gen-device to declare interrupt handler functions and
do some work in the compiler so that interrupt lowering becomes a lot
simpler.

This has several benefits:

  - Overall code is smaller, in particular the interrupt lowering pass.
  - The code should be a bit less "magical" and instead a bit easier to
    read. In particular, instead of having a magic
    runtime.callInterruptHandler (that is fully written by the interrupt
    lowering pass), the runtime calls a generated function like
    device/sifive.InterruptHandler where this switch already exists in
    code.
  - Debug information is improved. This can be helpful during actual
    debugging but is also useful for other uses of DWARF debug
    information.

For an example on debug information improvement, this is what a
backtrace might look like before this commit:

    Breakpoint 1, 0x00000b46 in UART0_IRQHandler ()
    (gdb) bt
    #0  0x00000b46 in UART0_IRQHandler ()
    #1  <signal handler called>
    [..etc]

Notice that the debugger doesn't see the source code location where it
has stopped.

After this commit, breaking at the same line might look like this:

    Breakpoint 1, (*machine.UART).handleInterrupt (arg1=..., uart=<optimized out>) at /home/ayke/src/github.com/tinygo-org/tinygo/src/machine/machine_nrf.go:200
    200			uart.Receive(byte(nrf.UART0.RXD.Get()))
    (gdb) bt
    #0  (*machine.UART).handleInterrupt (arg1=..., uart=<optimized out>) at /home/ayke/src/github.com/tinygo-org/tinygo/src/machine/machine_nrf.go:200
    #1  UART0_IRQHandler () at /home/ayke/src/github.com/tinygo-org/tinygo/src/device/nrf/nrf51.go:176
    #2  <signal handler called>
    [..etc]

By now, the debugger sees an actual source location for UART0_IRQHandler
(in the generated file) and an inlined function.
2021-11-06 09:40:15 +01:00
jaap aarts 30bbdd5aeb Make the frequency selection more flexible on stm32f103 2021-11-05 18:11:43 +01:00
jaap aarts 03383760d6 Fix SPI on stm32f103 2021-11-05 18:11:43 +01:00
Ayke van Laethem fce403b7a0 targets: match LLVM triple to the one Clang uses
The target triples have to match mostly to be able to link LLVM modules.
Linking LLVM modules is already possible (the triples already match),
but testing becomes much easier when they match exactly.

For macOS, I picked "macosx10.12.0". That's an old and unsupported
version, but I had to pick _something_. Clang by default uses
"macos10.4.0", which is much older.
2021-11-05 09:42:00 +01:00
Ayke van Laethem fb33f3813d runtime: only initialize os.runtime_args when needed
This generally means that code size is reduced, especially when the os
package is not imported.

Specifically:

  - On Linux (which currently statically links musl), it avoids calling
    malloc, which avoids including the musl C heap for small programs
    saving around 1.6kB.
  - On WASI, it avoids initializing the args slice when the os package
    is not used. This reduces binary size by around 1kB.
2021-11-05 08:50:36 +01:00
Ayke van Laethem 670fcf59d8 linux: reduce binary size in the common case
This commit changes the runtime.putchar implementation to directly call
the `write` system call. This reduces the binary size by around 2.7kB.
2021-11-05 08:50:36 +01:00
Ayke van Laethem cceb655874 cgo: run CGo parser for all CGo fragments in a file
Previously, libclang was run on each fragment (import "C") separately.
However, in regular Go it's possible for later fragments to refer to
types in earlier fragments so they must have been parsed as one.

This commit changes the behavior to run only one C parser invocation for
each Go file.
2021-11-04 22:26:33 +01:00
Damian Gryski 13891d428f os: add File.WriteString and File.WriteAt
WriteString just does the simple and and converts the passed string
to a byte-slice.  This can be made zero-copy later with unsafe, if needed.

WriteAt returns ErrNotImplemented, to match Seek() and ReadAt().

Fixes #2157
2021-11-04 21:47:57 +01:00
Ayke van Laethem 6c9bb96bca wasm: update wasi-libc dependency
The latest version allows overriding the default CFLAGS. By default,
they're `-O2 -DNDEBUG`, thus not including DWARF debug information. This
commit changes this to include the `-g` flag.

Apart from an improved debug experience, this lets -size=full attribute
code to wasi-libc.

Before:

    $ tinygo build -o test.wasm -size=full ./testdata/alias.go
       code  rodata    data     bss |   flash     ram | package
    ------------------------------- | --------------- | -------
       1780       0     188  130733 |    1968  130921 | (unknown)
         84       0       0       0 |      84       0 | internal/task
        281       0       0       0 |     281       0 | main
       2374       0       4     147 |    2378     151 | runtime
    ------------------------------- | --------------- | -------
       4519       0     192  130880 |    4711  131072 | total

After:

    $ tinygo build -o test.wasm -size=full ./testdata/alias.go
       code  rodata    data     bss |   flash     ram | package
    ------------------------------- | --------------- | -------
         40       0     188  130733 |     228  130921 | (unknown)
       1740       0       0       0 |    1740       0 | C wasi-libc
         84       0       0       0 |      84       0 | internal/task
        281       0       0       0 |     281       0 | main
       2374       0       4     147 |    2378     151 | runtime
    ------------------------------- | --------------- | -------
       4519       0     192  130880 |    4711  131072 | total

The main difference here is the `(unknown)` code, which turns out to be
mostly wasi-libc in this trivial example.
2021-11-04 21:10:42 +01:00
Ayke van Laethem 403d93560b builder: build static binaries using musl on Linux
This commit adds support for musl-libc and uses it by default on Linux.
The main benefit of it is that binaries are always statically linked
instead of depending on the host libc, even when using CGo.

Advantages:
  - The resulting binaries are always statically linked.
  - No need for any tools on the host OS, like a compiler, linker, or
    libc in a release build of TinyGo.
  - This also simplifies cross compilation as no cross compiler is
    needed (it's all built into the TinyGo release build).

Disadvantages:
  - Binary size increases by 5-6 kilobytes if -no-debug is used. Binary
    size increases by a much larger margin when debugging symbols are
    included (the default behavior) because musl is built with debugging
    symbols enabled.
  - Musl does things a bit differently than glibc, and some CGo code
    might rely on the glibc behavior.
  - The first build takes a bit longer because musl needs to be built.

As an additional bonus, time is now obtained from the system in a way
that fixes the Y2038 problem because musl has been a bit more agressive
in switching to 64-bit time_t.
2021-11-04 17:15:38 +01:00
Ayke van Laethem b344d65781 builder: reduce number of open files
MacOS X 10.14 has a soft limit of 256 open files by default, at least on
CircleCI. So don't keep object files open while writing the ar file to
reduce the number of open files at once.

Context: the musl libc has more than 256 object files in the .a file.
This resulted in the error "too many open files" on MacOS X 10.14 when
running in CircleCI.
2021-11-04 17:15:38 +01:00
Ayke van Laethem c638f03b3c main: add -p flag to set parallelism
This is very useful for debugging.
2021-11-04 17:15:38 +01:00
Ayke van Laethem 79bdd3f79a picolibc: add include directory to build artefact
This is really just a preparatory commit for musl support. The idea is
to store not just the archive file (.a) but also an include directory.
This is optional for picolibc but required for musl, so the main purpose
of this commit is the refactor needed for this change.
2021-11-04 17:15:38 +01:00
Ayke van Laethem 39ff13fd1a wasm: specify wasi-libc in code, not in the JSON target file
This brings a bit more consistency to libc configuration. It seems
better to me to set the header flags all in the same place, instead of
some in Go code and some in JSON target files (depending on the target).
2021-11-04 17:15:38 +01:00
Ayke van Laethem de6f831983 ci: switch to GitHub Actions for Windows builds
GitHub Actions is faster and much better integrated into GitHub than
Azure Pipelines, and is in general easier to use. Therefore, switch to
GitHub Actions for our Windows builds and tests.
2021-11-04 13:54:21 +01:00
Ayke van Laethem fb6571e405 builder: add support for -size= flag for WebAssembly 2021-11-04 12:34:23 +01:00
Ayke van Laethem 29206cf0a4 targets: add CPU property everywhere
This is for consistency with Clang, which always adds a CPU flag even if
it's not specified in CFLAGS.

This commit also adds some tests to make sure the Clang target-cpu
matches the CPU property in the JSON files.

This does have an effect on the generated binaries. The effect is very
small though: on average just 0.2% increase in binary size, apparently
because Cortex-M3 and Cortex-M4 are compiled a bit differently. However,
when rebased on top of https://github.com/tinygo-org/tinygo/pull/2218
(minsize), the difference drops to -0.1% (a slight decrease on average).
2021-11-03 23:03:44 +01:00
Yurii Soldak c2165f74d8 nano-33-ble: SoftDevice s140v7 support 2021-11-03 20:54:40 +01:00
Ayke van Laethem 02ef64f012 stacksize: hardcode some more frame sizes for __aeabi_* functions
These functions are defined in compiler-rt in assembly and therefore
don't have stack size information. However, they're often called so
these missing functions often inhibit stack size calculation.

Example, before:

    $ tinygo build -o test.elf -target=cortex-m-qemu -print-stacks ./testdata/float.go
    function                         stack usage (in bytes)
    Reset_Handler                    unknown, __aeabi_memclr does not have stack frame information
    runtime.run$1                    unknown, __aeabi_dcmpgt does not have stack frame information

After:

    $ tinygo build -o test.elf -target=cortex-m-qemu -print-stacks ./testdata/float.go
    function                         stack usage (in bytes)
    Reset_Handler                    260
    runtime.run$1                    224
2021-11-03 18:42:16 +01:00
Ayke van Laethem 5792f3a1cf builder: improve accuracy of the -size=full flag
This commit improves accuracy of the -size=full flag in a big way.
Instead of relying on symbol names to figure out by which package
symbols belong, it will instead mostly use DWARF debug information
(specifically, debug line tables and debug information for global
variables) relying on symbols only for some specific things. This is
much more accurate: it also accounts for inlined functions.

For example, here is how it looked previously when compiling a personal
project:

     code  rodata    data     bss |   flash     ram | package
     1902     333       0       0 |    2235       0 | (bootstrap)
       46     256       0       0 |     302       0 | github
        0     454       0       0 |     454       0 | handleHardFault$string
      154      24       4       4 |     182       8 | internal/task
     2498      83       5    2054 |    2586    2059 | machine
        0      16      24     130 |      40     154 | machine$alloc
     1664      32      12       8 |    1708      20 | main
        0       0       0     200 |       0     200 | main$alloc
     2476      79       0      36 |    2555      36 | runtime
      576       0       0       0 |     576       0 | tinygo
     9316    1277      45    2432 |   10638    2477 | (sum)
    11208       -      48    6548 |   11256    6596 | (all)

And here is how it looks now:

     code  rodata    data     bss |   flash     ram | package
  ------------------------------- | --------------- | -------
     1509       0      12      23 |    1521      35 | (unknown)
      660       0       0       0 |     660       0 | C compiler-rt
       58       0       0       0 |      58       0 | C picolibc
        0       0       0    4096 |       0    4096 | C stack
      174       0       0       0 |     174       0 | device/arm
        6       0       0       0 |       6       0 | device/sam
      598     256       0       0 |     854       0 | github.com/aykevl/ledsgo
      320      24       0       4 |     344       4 | internal/task
     1414      99      24    2181 |    1537    2205 | machine
      726     352      12     208 |    1090     220 | main
     3002     542       0      36 |    3544      36 | runtime
      848       0       0       0 |     848       0 | runtime/volatile
       70       0       0       0 |      70       0 | time
      550       0       0       0 |     550       0 | tinygo.org/x/drivers/ws2812
  ------------------------------- | --------------- | -------
     9935    1273      48    6548 |   11256    6596 | total

There are some notable differences:

  * Odd packages like main$alloc and handleHardFault$string are gone,
    instead their code is put in the correct package.
  * C libraries and the stack are now included in the list, they were
    previously part of the (bootstrap) pseudo-package.
  * Unknown bytes are slightly reduced. It should be possible to reduce
    it significantly more in the future: most of it is now caused by
    interface invoke wrappers.
  * Inlined functions are now correctly attributed. For example, the
    runtime/volatile package is normally entirely inlined.
  * There is no difference between (sum) and (all) anymore. A better
    code size algorithm now counts the code/data sizes correctly.
  * And last (but not least) there is a stylistic change: the table now
    looks more like a table. Especially the summary should be clearer
    now.

Future goals:

  * Improve debug information so that the (unknown) pseudo-package is
    reduced in size or even eliminated altogether.
  * Add support for other file formats, most importantly WebAssembly.
  * Perhaps provide a way to expand this report per file, or in a
    machine-readable format like JSON or CSV.
2021-11-03 16:28:04 +01:00
Ayke van Laethem f63c389f1a compiler: change symbol name for string and packed data constants
This new symbol name format (package name + "$string" or "$pack" suffix)
is easier to parse for analysis.
2021-11-03 16:28:04 +01:00
Ayke van Laethem 7c24925aa7 compiler: add minsize attribute for -Oz
This matches the behavior of Clang, which uses optsize for -Os and adds
minsize for -Oz.

The code size change is all over the map, but using a hacked together
size comparison tool I've found that there is a slight reduction in
binary size overall (-1.6% with the tinygo smoke tests and -0.8% for the
drivers smoke test).
2021-11-03 13:40:13 +01:00
Ayke van Laethem d7b7583e83 compiler: refactor when the optsize attribute is set
This commit has a few related changes:

  * It sets the optsize attribute immediately in the compiler instead of
    adding it to each function afterwards in a loop. This seems to me
    like the more appropriate way to do it.
  * It centralizes setting the optsize attribute in the transform
    package, to make later changes easier.
  * It sets the optsize in a few more places: to runtime.initAll and to
    WebAssembly i64 wrappers.

This commit does not affect the binary size of any of the smoke tests,
so should be risk-free.
2021-11-03 13:40:13 +01:00
Ayke van Laethem 1869efe954 interp: use object layout information for LLVM types
This commit will use the memory layout information for heap allocations
added in the previous commit to determine LLVM types, instead of
guessing their types based on the content. This fixes a bug in which
recursive data structures (such as doubly linked lists) would result in
a compiler stack overflow due to infinite recursion.

Not all heap allocations have a memory layout yet, but this can be
incrementally fixed in the future. So far, this commit should fix
(almost?) all cases of this stack overflow issue.
2021-11-02 22:16:15 +01:00
Ayke van Laethem 54dd75f7b3 interp: simplify pointer arithmetic in getLLVMValue
Instead of doing lots of complicated calculations to get the shortest
GEP, I'll just cast it to i8*, do the GEP, and optionally cast to the
requested type.

This currently produces ugly constant expressions, but once LLVM
switches to opaque pointer types all of this shouldn't matter anymore.
2021-11-02 22:16:15 +01:00
Ayke van Laethem 27cbb53538 interp: support const getelementptr with non-zero first offset
This is uncommon, but it does happen if the source pointer is a bitcast
of a global. For example, if a struct is cast to an i8*, it's possible
to index beyond what would appear to be the size of the pointer (i8*).
2021-11-02 22:16:15 +01:00
Ayke van Laethem 0704794def compiler: add object layout information to heap allocations
This commit adds object layout information to new heap allocations. It
is not yet used anywhere: the next commit will make use of it.

Object layout information will eventually be used for a (mostly) precise
garbage collector. This is what the data is made for. However, it is
also useful in the interp package which can work better if it knows the
memory layout and thus the approximate LLVM type of heap-allocated
objects.
2021-11-02 22:16:15 +01:00
Ayke van Laethem f24a93c51d compiler, runtime: add layout parameter to runtime.alloc
This layout parameter is currently always nil and ignored, but will
eventually contain a pointer to a memory layout.

This commit also adds module verification to the transform tests, as I
found out that it didn't (and therefore didn't initially catch all
bugs).
2021-11-02 22:16:15 +01:00
Ayke van Laethem c454568688 loader: fix true path detection on Windows
This is necessary to display error messages on Windows. For example,
this command invocation is not correct (esp32 doesn't define
machine.LED, you need esp32-coreboard-v2 for example):

    tinygo run -target=esp32 examples/blinky1

It results in the following hard-to-read error message:

    # examples/blinky1
    ..\..\..\..\..\AppData\Local\tinygo\goroot-go1.16-24cb853b66a5367bf6d65bc08b2cb665c75bd9971f0be8f8b73f69d1a33e04a1-syscall\src\examples\blinky1\blinky1.go:11:17: LED not declared by package machine

With this commit, this error message becomes much easier to read:

    # examples/blinky1
    C:\Users\Ayke\go\src\github.com\tinygo-org\tinygo\src\examples\blinky1\blinky1.go:11:17: LED not declared by package machine
2021-10-31 19:10:26 +01:00
Nia Waldvogel d46bf2e5e0 transform (interface): fix merge error from #2202 2021-10-31 17:35:58 +01:00
Ayke van Laethem 9e1b4de999 compiler: add support for the go keyword on interface methods
This is a feature that was long missing, but because of the previous
refactor, it is now trivial to implement.
2021-10-31 14:17:25 +01:00
Ayke van Laethem a4afc3b4b0 compiler: simplify interface lowering
This commit simplifies the IR a little bit: instead of calling
pseudo-functions runtime.interfaceImplements and
runtime.interfaceMethod, real declared functions are being called that
are then defined in the interface lowering pass. This should simplify
the interaction between various transformation passes. It also reduces
the number of lines of code, which is generally a good thing.
2021-10-31 14:17:25 +01:00
Ayke van Laethem 90076f9401 all: drop support for LLVM 10 2021-10-31 10:44:17 +01:00
Ayke van Laethem afd49e7cdd compiler: add support for recursive function types
This adds support for a construct like this:

    type foo func(fn foo)

Unfortunately, LLVM cannot create function pointers that look like this.
LLVM only supports named types for structs (not for pointers) and thus
can't add a pointer to a function type of the same type to a parameter
of that function type.

The fix is simple: cast all function pointers to a void function, in
LLVM IR:

    void ()*

Raw function pointers are cast to this type before storing, and cast
back to the regular function type before calling. This means that
function parameters will never refer to its own type because raw
function types are fixed at that one type.

Somehow, this does have an effect on binary size in some cases. The
effect is small and goes both ways. On top of that, there is work
underway in LLVM which would make all pointer types opaque (without a
pointee type). This would make this whole commit useless and therefore
should fix any size increases that might happen.
https://llvm.org/docs/OpaquePointers.html
2021-10-30 15:55:20 +02:00
Ayke van Laethem 4199be9780 ci: increase timeout to 20 minutes 2021-10-28 17:44:51 +02:00
Ayke van Laethem 86f1e6aec4 compiler: properly implement div and rem operations
The division and remainder operations were lowered directly to LLVM IR.
This is wrong however because the Go specification defines exactly what
happens on a divide by zero or signed integer overflow and LLVM IR
itself treats those cases as undefined behavior. Therefore, this commit
implements divide by zero and signed integer overflow according to the
Go specification.

This does have an impact on the generated code, but it is surprisingly
small. I've used the drivers repo to test the code before and after, and
to my surprise most driver smoke tests are not changed at all. Those
that are, have only a small increase in code size. At the same time,
this change makes TinyGo more compliant to the Go specification.
2021-10-28 15:55:02 +02:00
Ayke van Laethem f99c600ad8 transform: work around renamed return type after merging LLVM modules
This fix is very similar to
https://github.com/tinygo-org/tinygo/pull/1768, but now for the return
type. It fixes the issue in
https://github.com/tinygo-org/tinygo/issues/1887.

Like #1768, I'm not sure how to test this as it is very specific to
certain renames that LLVM does and that don't seem very reproducable.
2021-10-28 09:20:08 +02:00
Ayke van Laethem 15d3f5f609 machine: support Pin.Get() function when the pin is configured as output
To my surprise, this is supported on all the devices I could test so
therefore it makes sense to change the API to allow this.
2021-10-28 07:22:19 +02:00
Yurii Soldak c8719f8d14 docker: add picolibc-include directory 2021-10-27 20:49:06 +02:00
Dmitriy Zakharkin e848f47ad4 Fix gen-device-svd to handle 64-bit 2021-10-27 14:52:27 +02:00
Ast-x64 3fe7ab19f6 bump go.bug.st/serial to version 1.1.3
Bump version to 1.1.3 in order to support riscv64 within tinygo.
See https://github.com/bugst/go-serial/pull/100 for more information.
2021-10-27 14:43:38 +02:00
Ayke van Laethem b5d61760f7 transform: remove some dead code
This showed up in the linter and it makes sense to just remove it.
2021-10-26 18:11:58 +02:00
Ayke van Laethem 14bb90c3c0 cgo: add support for stdio in picolibc and wasi-libc
This adds support for stdio in picolibc and fixes wasm_exec.js so that
it can also support C puts. With this, C stdout works on all supported
platforms.
2021-10-26 17:08:30 +02:00
Ayke van Laethem 1645f45c1a esp32c3: use tasks scheduler by default
Now that the tasks scheduler has been implemented for 32-bit RISC-V, it
can be used for the ESP32-C3.
2021-10-26 11:24:41 +02:00
Ayke van Laethem 38b9c55ae6 sam: move I2S0 to machine file
There is no need to put these in the board files as the I2S is the same
on all Microchip SAM D21 chips. This simplifies the code and avoids some
special *_baremetal.go files.

This change does not change the resulting binaries.
2021-10-25 14:49:02 +02:00
Ayke van Laethem 497c74e4a9 sam: simplify SPI peripheral declaration
This has practically no effect on the resulting binaries, the only
difference I could find was for the flash/console/spi driver example.
I'm not sure how to test that one, but I think it's very unlikely that
code will have changed in any meaningful way (apart from reordering some
globals).
2021-10-25 14:49:02 +02:00
Ayke van Laethem ae864bdf0c sam: simplify I2C peripheral declarations
This commit changes the I2C declarations so that the objects are
instantiated in each chip file (e.g. machine_atsamd21e18.go) and used to
define I2C0 (and similar) in the board file (e.g. board_qtpy.go). This
should make it easier to define new board files, and reduces the need
for separate *_baremetal.go files.

I have tested this the following way:

  - With the LIS3DH driver example on the Circuit Playground Express and
    the PyBadge.
  - With the LSM6DS3 driver example on the Arduino Nano 33 IoT.

They both still work fine.
2021-10-25 14:49:02 +02:00
Ayke van Laethem e50885a6f2 sam: simplify definition of SERCOM UART peripherals
Instead of defining them separately for each board, define them once in
the chip definition and later simply use &sercomUART1 etc. to refer to
them. This is simpler and less error-prone.

I found two bugs while working on this:

  - The P1AM-100 board mixed SERCOM 5 and SERCOM 3. It looks like SERCOM
    5 was intended, based on the used pins.
  - The Adafruit Matrix Portal appears to have configured the wrong
    interrupt.

Unfortunately, I can't test these fixes. However, they make it clear
that such a change is important to avoid bugs.

I tested this commit on the PyBadge and the Circuit Playground Express.
2021-10-25 14:49:02 +02:00
Ayke van Laethem 478dd3a28d compiler: add nounwind attribute
This attribute is also set by Clang when it compiles C source files
(unless -fexceptions is set). The advantage is that no unwind tables are
emitted on Linux (and perhaps other systems). It also avoids
__aeabi_unwind_cpp_pr0 on ARM when using the musl libc.
2021-10-25 13:39:54 +02:00
Rouven Broszeit 112b369636 Call __wasm_call_ctors() in wasi init function 2021-10-25 13:12:23 +02:00
Dmitriy 43efe94041 add support for CPU interrupts for ESP32-C3 2021-10-23 03:31:37 +02:00
Ayke van Laethem b5b2600b7b fe310: add support for bit banging drivers
This is necessary to add support for WS2812.
2021-10-21 08:14:34 +02:00
Ayke van Laethem 3f89fa0bee fe310: increase CPU frequency from 16MHz to 320MHz
This chip can run so much faster! Let's update the default frequency.

Also, change the UART implementation to be more fexible regarding the
clock frequency.
2021-10-21 07:13:57 +02:00
Yurii Soldak a5d905f19b rp2040: i2c SetBaudRate spelling 2021-10-21 00:29:16 +02:00
Yurii Soldak d1f1f267a3 rp2040: i2c baud rate handling improvements 2021-10-21 00:29:16 +02:00
sago35 d21ffc63b9 board: add M5Stack Core2 2021-10-20 20:28:47 +02:00
Damian Gryski 90f4b0a266 Makefile: add more TEST_PACKAGES that currently pass 2021-10-16 01:49:25 +02:00
Damian Gryski a88530b785 src/testing: stub B.ReportAllocs()
This allows test packages that use this feature in their benchmarks
to build and run (if not the benchmarks themselves).
2021-10-16 01:49:25 +02:00
Damian Gryski 18aaed63b9 src/runtime: add another set of invalid unicode runes to encodeUTF8() 2021-10-16 01:37:48 +02:00
Damian Gryski a413d5dfe9 rutime/gc_leaking: ensure heapptr is aligned on wasm 2021-10-14 01:56:05 +02:00
Ayke van Laethem 4d5ec6c57b main: use emulator exit code instead of parsing test output
This commit changes `tinygo test` to always look at the exit code of the
running test, instead of looking for a "PASS" string at the end of the
output. This is possible now that the binaries running under
qemu-system-arm or qemu-system-riscv32 will signal the correct exit code
when they exit.

As a side effect, this also makes it possible to avoid the "PASS" line
between successful tests. Before:

    $ tinygo test container/heap container/list
    PASS
    ok  	container/heap	0.001s
    PASS
    ok  	container/list	0.001s

After:

    $ tinygo test container/heap container/list
    ok  	container/heap	0.001s
    ok  	container/list	0.001s

The new behavior is more in line with upstream Go:

    go test container/heap container/list
    ok  	container/heap	0.004s
    ok  	container/list	0.004s
2021-10-06 09:04:06 +02:00
Ayke van Laethem 98f84a497d qemu: signal correct exit code to QEMU
There were a few issues that were causing qemu-system-arm and
qemu-system-riscv to give the wrong exit codes. They are in fact capable
of exiting with 0 or 1 signalled from the running application, but this
functionality wasn't used. This commit changes this in the following
ways:

  * It fixes SemiHosting codes, which were incorrectly written in
    decimal while they should have been written in hexadecimal (oops!).
  * It modifies all the baremetal main functions (aka reset handlers) to
    exit with `exit(0)` instead of `abort()`.
  * It changes `syscall.Exit` to call `exit(code)` instead of `abort()`
    on baremetal targets.
  * It adds these new exit functions where necessary, implemented in a
    way that signals the correct exit status if running under QEMU.

All in all, this means that `tinygo test` doesn't have to look at the
output of a test to determine the outcome. It can simply look at the
exit code.
2021-10-06 09:04:06 +02:00
sago35 00c73d62ad rp2040: add CPUFrequency() 2021-10-05 07:17:36 +02:00
Ayke van Laethem dbfaaf7c13 main: implement tinygo lldb subcommand
LLDB mostly works on most platforms, but it is still lacking in some
features. For example, it doesn't seem to support RISC-V yet (coming in
LLVM 12), it only partially supports AVR (no stacktraces), and it
doesn't seem to support the Ctrl-C keyboard command when running a
binary for another platform (e.g. with GOOS=arm64). However, it does
mostly work, even on baremetal systems.
2021-10-05 06:26:21 +02:00
Ayke van Laethem 878b62bbe8 riscv: switch to tasks-based scheduler
This is only supported for RV32 at the moment. RV64 can be added at a
later time.
2021-10-05 05:52:03 +02:00
Ayke van Laethem 5d8f25a622 hifive1-qemu: increase memory to 64K
Somehow this is accepted by QEMU. I'm doing this so that tests for
-target=hifive1-qemu still work with the RISC-V tasks scheduler (with a
stack size of 2048 bytes).
2021-10-05 05:52:03 +02:00
Ayke van Laethem c7413837aa riscv: align the heap to 16 bytes
This may be expected by the ABI. In particular, it means that stacks
allocated on the heap will be 16-byte aligned.
2021-10-05 05:52:03 +02:00
Federico G. Schwindt b1ec8eb2e0 os: implement Getwd 2021-10-05 00:14:09 +02:00
deadprogram 98fbf7ff25 build: double timeout period without output for make test
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-10-04 23:51:25 +02:00
Ayke van Laethem af00e218a8 riscv: implement 32-bit atomic operations
This is necessary to support the ESP32-C3, which lacks the A (atomic)
extension and thus requires these 32-bit atomic operations.
With this commit, flashing ./testdata/atomic.go to the ESP32-C3 works
correctly and produces the expected output on the serial console.
2021-10-04 21:27:00 +02:00
Ayke van Laethem b31d241388 riscv: use MSTATUS.MIE bit instead of MIE to disable interrupts
This should behave the same but is compatible with the ESP32-C3 which
lacks the MIE CSR (but does have the MSTATUS CSR).
2021-10-04 21:27:00 +02:00
Ayke van Laethem a6246e60f3 main: remove -target flag for LLVM targets
It is better to use environment variables (GOOS and GOARCH) for
consistency instead of providing two slightly incompatible ways. This
-target flag should only be used to specify a .json file (either
directly or in the TinyGo targets directory). Previously it was possible
to specify the LLVM target as well but that was never really fully
supported.

So:

  - To specify a different OS/arch like you would in regular Go, use
    GOOS and GOARCH.
  - To specify a microcontroller chip or board, use the -target flag.

Also remove the old `os.Setenv` which might have had a purpose long ago
but doesn't have a purpose now.
2021-10-04 18:22:55 +02:00
Ayke van Laethem 0a80da46b1 main: test other architectures by specifying a different GOARCH
... instead of setting a special -target= value. This is more robust and
makes sure that the test actually tests different arcitectures as they
would be compiled by TinyGo. As an example, the bug of the bugfix in the
previous commit ("arm: use armv7 instead of thumbv7") would have been
caught if this change was applied earlier.

I've decided to put GOOS/GOARCH in compileopts.Options, as it makes
sense to me to treat them the same way as command line parameters.
2021-10-04 18:22:55 +02:00
Ayke van Laethem 36f1517e8d arm: use armv7 instead of thumbv7
At the moment, thumbv7 is crashing. I'm not exactly sure why, but it
appears that there is an unknown instruction in __aeabi_uldivmod
(probably from libgcc).

I've fixed this by switching to armv7, which is also somewhat modern.
Maybe we can switch back to Thumb2 (aka thumbv7) once we start using
musl and compiler-rt. In the meantime, this does fix a miscompilation
(illegal instruction).
2021-10-04 18:22:55 +02:00
learnforpractice 04040453b4 fix export math functions issue 2021-10-03 16:28:34 +02:00
Ayke van Laethem 2b453db4da esp32c3: add support for GDB debugging
You can now debug the ESP32-C3 from the TinyGo command line, like this:

    tinygo flash -target=esp32c3 examples/serial
    tinygo gdb -target=esp32c3 examples/serial

It's important to flash before running `tinygo gdb`, because loading a
new firmware from GDB has not yet been implemented.

Probably the easiest way to connect to the ESP32-C3 is by using the
built-in JTAG connection. See:
https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-guides/jtag-debugging/configure-builtin-jtag.html

You will need to make sure that the `openocd` command in your $PATH is
the one from Espressif. Otherwise GDB will hang. You can debug this by
supplying the -ocd-output flag:

    $ tinygo gdb -target=esp32c3 -ocd-output examples/serial
    Open On-Chip Debugger 0.10.0
    openocd: Licensed under GNU GPL v2
    openocd: For bug reports, read
    openocd: 	http://openocd.org/doc/doxygen/bugs.html
    openocd: embedded:startup.tcl:60: Error: Can't find interface/esp_usb_jtag.cfg
    openocd: in procedure 'script'
    openocd: at file "embedded:startup.tcl", line 60

Make sure to configure OpenOCD correctly, until you get the correct
version (that includes the string "esp32"):

    $ openocd --version
    Open On-Chip Debugger  v0.10.0-esp32-20210721 (2021-07-21-13:33)
    Licensed under GNU GPL v2
    For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html

If you are on Linux, you may also get the following error:

    $ tinygo gdb -target=esp32c3 -ocd-output examples/serial
    Open On-Chip Debugger  v0.10.0-esp32-20210721 (2021-07-21-13:33)
    openocd: Licensed under GNU GPL v2
    openocd: For bug reports, read
    openocd: 	http://openocd.org/doc/doxygen/bugs.html
    openocd: Info : only one transport option; autoselect 'jtag'
    openocd: adapter speed: 40000 kHz
    openocd:
    openocd: Warn : Transport "jtag" was already selected
    openocd: Info : Listening on port 6666 for tcl connections
    openocd: Info : Listening on port 4444 for telnet connections
    openocd: Error: libusb_open() failed with LIBUSB_ERROR_ACCESS
    openocd: Error: esp_usb_jtag: could not find or open device!

The error LIBUSB_ERROR_ACCESS means that there is a permission error.
You can fix this by creating the following file:

    $ cat /etc/udev/rules.d/50-esp.rules
    # ESP32-C3
    SUBSYSTEMS=="usb", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="1001", MODE="0666"

For more details, see:
https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-guides/jtag-debugging/index.html
2021-10-02 09:24:13 +02:00
Ayke van Laethem 29d3e10dc9 ci: fix wasmtime.dev expiry issue by upgrading some packages
Hopefully this will fix the CI breakage after curl and wget refuse to
download anything from wasmtime.dev (which is signed by Let's Encrypt).

- wget needs and updated libgnutls30
- curl needs and updated libssl1.0.2
2021-10-01 17:45:54 +02:00
sago35 06c60b4ecf esp32: remove extra configuration 2021-09-29 14:51:04 +02:00
Ayke van Laethem 5c0a337c4f cgo: implement rudimentary C array decaying
This is just a first step. It's not complete, but it gets some real
world C code to parse.

This signature, from the ESP-IDF:

    esp_err_t esp_wifi_get_mac(wifi_interface_t ifx, uint8_t mac[6]);

Was previously converted to something like this (pseudocode):

    C.esp_err_t esp_wifi_get_mac(ifx C.wifi_interface_t, mac [6]uint8)

But this is not correct. C array parameters will decay. The array is
passed by reference instead of by value. Instead, this would be the
correct signature:

    C.esp_err_t esp_wifi_get_mac(ifx C.wifi_interface_t, mac *uint8)

So that it can be called like this (using CGo):

    var mac [6]byte
    errCode := C.esp_wifi_get_mac(C.ESP_IF_WIFI_AP, &mac[0])

This stores the result in the 6-element array mac.
2021-09-29 13:38:33 +02:00
Ayke van Laethem 04e8f44370 os: don't try to read executable path on baremetal
Baremetal systems usually pretend to be GOOS=linux, so an extra build
tag is needed here.
2021-09-28 20:28:15 +02:00
Ayke van Laethem e02727679f builder, cgo: support function definitions in CGo headers
For example, the following did not work before but does work with this
change:

    // int add(int a, int b) {
    //   return a + b;
    // }
    import "C"

    func main() {
        println("add:", C.add(3, 5))
    }

Even better, the functions in the header are compiled together with the
rest of the Go code and so they can be optimized together! Currently,
inlining is not yet allowed but const-propagation across functions
works. This should be improved in the future.
2021-09-28 18:44:11 +02:00
Ayke van Laethem 138add2b96 cgo: fix line/column reporting in syntax error messages 2021-09-28 18:44:11 +02:00
Ayke van Laethem bf9dab36f7 build: normalize target triples to match Clang
This commit changes a target triple like "armv6m-none-eabi" to
"armv6m-unknown-unknow-eabi". The reason is that while the former is
correctly parsed in Clang (due to normalization), it wasn't parsed
correctly in LLVM meaning that the environment wasn't set to EABI.

This change normalizes all target triples and uses the EABI environment
(-eabi in the triple) for Cortex-M targets.

This change also drops the `--target=` flag in the target JSON files,
the flag is now added implicitly in `(*compileopts.Config).CFlags()`.
This removes some duplication in target JSON files.

Unfortunately, this change also increases code size for Cortex-M
targets. It looks like LLVM now emits calls like __aeabi_memmove instead
of memmove, which pull in slightly more code (they basically just call
the regular C functions) and the calls themself don't seem to be as
efficient as they could be. Perhaps this is a LLVM bug that will be
fixed in the future, as this is a very common occurrence.
2021-09-28 18:44:11 +02:00
Ayke van Laethem 6234bf9a88 all: use -opt flag for optimization level in CFlags (-Os, etc)
This brings some consistency to the CFlags and fixes the issue that on
some platforms (Linux, MacOS), no optimization level was set and
therefore C files in packages were not optimized at all.
2021-09-28 18:44:11 +02:00
Damian Gryski 32899d1cc3 testing: add a stub for t.Parallel() 2021-09-27 20:02:02 +02:00
Damian Gryski 0dfb336563 add support for -test.short flag 2021-09-27 20:02:02 +02:00
Damian Gryski d9ad500cf7 src/reflect: fix type.Size() to account for struct padding
Fixes #2141
2021-09-27 17:04:16 +02:00
Ayke van Laethem 49dd2ce393 all: fix staticcheck warnings
This is a loose collection of small fixes flagged by staticcheck:

  - dead code
  - regexp expressions not using backticks (`foobar` / "foobar")
  - redundant types of slice and map initializers
  - misc other fixes

Not all of these seem very useful to me, but in particular dead code is
nice to fix. I've fixed them all just so that if there are problems,
they aren't hidden in the noise of less useful issues.
2021-09-27 15:47:12 +02:00
sago35 7df8e8db42 feather-stm32f405, feather-rp2040: add I2C pin names 2021-09-27 12:37:26 +02:00
Damian Gryski 25011c5078 src/internal/bytealg: fix indexing error in Compare()
Fixes #2139
2021-09-26 11:07:19 +02:00
sago35 54867f901a version: update TinyGo version to 0.21.0-dev 2021-09-23 21:08:44 +02:00
Nia Waldvogel 1573826005 transform (coroutines): move any misplaced entry-block allocas to the start of the entry block before lowering 2021-09-21 20:08:30 +02:00
Nia Waldvogel ecd8c2d902 transform (coroutines): fix memory corruption for tail calls that reference stack allocations
This change fixes a bug in which `alloca` memory lifetimes would not extend past the suspend of an asynchronous tail call.
This would typically manifest as memory corruption, and could happen with or without normal suspending calls within the function.
2021-09-21 20:08:30 +02:00
Ayke van Laethem a116fd0dc6 main: release version 0.20.0 2021-09-21 18:05:21 +02:00
Ayke van Laethem a590d791bd builder: simplify running of jobs
Instead of keeping a slice of jobs to run, let the runJobs function
determine which jobs should be run by investigating all dependencies.
This has two benefits:

  - The code is somewhat cleaner, as no 'jobs' slice needs to be
    maintained while constructing the dependency graph.
  - Eventually, some jobs might not be required by any dependency.
    While it's possible to avoid adding them to the slice, the simpler
    solution is to build a new slice from the dependencies which will
    only include required dependencies by design.
2021-09-17 22:22:27 +02:00
Ayke van Laethem cb147b9475 esp32c3: add support for this chip
This change adds support for the ESP32-C3, a new chip from Espressif. It
is a RISC-V core so porting was comparatively easy.

Most peripherals are shared with the (original) ESP32 chip, but with
subtle differences. Also, the SVD file I've used gives some
peripherals/registers a different name which makes sharing code harder.
Eventually, when an official SVD file for the ESP32 is released, I
expect that a lot of code can be shared between the two chips.

More information: https://www.espressif.com/en/products/socs/esp32-c3

TODO:
  - stack scheduler
  - interrupts
  - most peripherals (SPI, I2C, PWM, etc)
2021-09-16 20:13:04 +02:00
Ayke van Laethem c830f878c6 stm32: add support for PortMask* functions for WS2812 support
This also requires support in the tinygo.org/x/drivers/ws2812 package,
which I've already partially written.
2021-09-16 18:29:14 +02:00
Ayke van Laethem c7c874a487 loader: fix panic in CGo files with syntax errors
If all of the Go files presented to the compiler have syntax errors,
cgo.Process gets an empty files slice and will panic:

    panic: runtime error: index out of range [0] with length 0

    goroutine 1 [running]:
    github.com/tinygo-org/tinygo/cgo.Process({0x0, 0x4e8e36, 0x0}, {0xc000024104, 0x18}, 0xc000090fc0, {0xc000899780, 0x7, 0xc00018ce68})
    	/home/ayke/src/github.com/tinygo-org/tinygo/cgo/cgo.go:186 +0x22ee
    github.com/tinygo-org/tinygo/loader.(*Package).parseFiles(0xc0001ccf00)
    	/home/ayke/src/github.com/tinygo-org/tinygo/loader/loader.go:400 +0x51e

This is simple to work around: just don't try to run CGo when there are
no files to process. It just means there are bugs to fix before CGo can
properly run.

(This is perhaps not the nicest solution but certainly the simplest).
2021-09-15 19:04:26 +02:00
Ayke van Laethem c528a168ee os: add SEEK_SET, SEEK_CUR, and SEEK_END
These are deprecated but still used by some code.
2021-09-15 18:58:17 +02:00
Ayke van Laethem 88b9c27dbf unix: check for mmap error and act accordingly
At startup, a large chunk of virtual memory is used up by the heap. This
works fine in emulation (qemu-arm), but doesn't work so well on an
actual Raspberry Pi. Therefore, this commit reduces the requested amount
until a heap size is found that works on the system.

This can certainly be improved, but for now it's an important fix
because it allows TinyGo built binaries to actually run on a Raspberry
Pi with just 1GB RAM.
2021-09-15 17:06:21 +02:00
Ayke van Laethem 37ee4bea40 arm: switch to Thumb instruction set on ARM
This reduces binary size substantially, for two reasons:

  - It switches to a much more architecture ARMv4 vs ARMv7.
  - It switches to Thumb2, which is a lot denser than regular ARM.

Practically all modern and not-so-modern ARM chips support Thumb2, so
this seems like a safe change to me.

The size in numbers:

  - Code size for testdata/stdlib.go is reduced by about 35%.
  - Binary size for testdata/stdlib.go (when compiling with -no-debug to
    strip debug information) is reduced by about 16%.
2021-09-15 15:28:10 +02:00
ardnew ca4f251050 relax restriction on configuration with duplicate settings 2021-09-13 09:29:20 +02:00
ardnew 1e92e5f6c6 teensy40: enable hardware UART reconfiguration, fix receive watermark interrupt 2021-09-13 09:29:20 +02:00
Damian Gryski 3eb9dca695 machine: fix copy-paste error for atsamd21/51 calibTrim block
Fixes #2097
2021-09-10 18:07:17 +02:00
sago35 53794cf339 target: fix pid for mdbt50qrx-uf2 2021-09-09 18:40:03 +02:00
Damian Gryski da6c14481f runtime: fix a suspicious bitwise operation
The `0 << nxp.SIM_CLKDIV1_OUTDIV1_Pos` term was duplicated.
No effect other than triggering a static analysis check.
2021-09-09 17:51:46 +02:00
sago35 fb5516604d targets: add DefaultUART to adafruit boards 2021-09-09 15:58:35 +02:00
Ayke van Laethem e9f1ed701a cgo: don't normalize CGo tests anymore
Normalization was required because previously we supported Go 1.13 and
Go 1.14 at the same time. Now we've dropped support for both so this
normalization is not necessary anymore.

CGo support remains the same. It's just the test outputs that aren't
normalized anymore.
2021-09-09 13:28:50 +02:00
Ayke van Laethem 6315db21f7 compiler: avoid zero-sized alloca in channel operations
This works around a bug in LLVM
(https://bugs.llvm.org/show_bug.cgi?id=49916) but seems like a good
change in general.
2021-09-09 11:24:52 +02:00
ardnew 910cc457ad Merge remote-tracking branch 'origin/dev' into feature/usb-common 2021-09-08 23:46:21 -05:00
Damian Gryski 485a9284e7 builder: add missing error check for ioutil.TempFile() 2021-09-08 15:21:31 +02:00
BCG 602d3d7c78 board: add Raytac MDBT50Q-RX Dongle with TinyUF2 2021-09-08 12:40:49 +02:00
Ayke van Laethem 409688e67a compiler: fix equally named structs in different scopes
For example, in this code:

    type kv struct {
           v float32
    }

    func foo(a *kv) {
           type kv struct {
                   v byte
           }
    }

Both 'kv' types would be given the same LLVM type, even though they are
different types! This is fixed by only creating a LLVM type once per Go
type (types.Type).

As an added bonus, this change gives a performance improvement of about
0.4%. Not that much, but certainly not nothing for such a small change.
2021-09-08 10:02:57 +02:00
Damian Gryski d348db4a0d tinygo: add a flag for creating cpu profiles 2021-09-08 01:09:10 +02:00
Damian Gryski 95ab7cb8d1 Makefile: add smoke test with gc=leaking to test dead asm code 2021-09-07 08:00:11 +02:00
Damian Gryski 32de906f6d internal/task, runtime: add subsections_via_symbols to assembly files on darwin
This allows the assembly routines in these files to be stripped as dead
code if they're not referenced.  This solves the link issues on MacOS
when the `leaking` garbage collector or the `coroutines` scheduler
are selected.

Fixes #2081
2021-09-07 08:00:11 +02:00
Ron Evans eaab05fc43 Revert "Minor changes to support go 1.17"
This reverts commit 2d224ae049.
2021-09-06 12:39:40 +02:00
sago35 4d1945b467 nrf52840: fix ram size 2021-09-05 23:54:26 +02:00
Mike Mogenson fd9422d218 fix GBA ROM header
Populate the GBA ROM header so that emulators and physical Game Boy
Advance consoles recognize the ROM as a valid game.

Note: The reserve space at the end of the header was hand-tuned. Why
this magic value?
2021-09-05 19:59:26 +02:00
sago35 bbbe7d43ce machine/arduino_mkrwifi1010: fix pin definition of NINA_RESETN 2021-09-05 14:10:25 +02:00
Damian Gryski b3f1dacbb9 interp: remove unused gepOperands slice 2021-09-05 12:00:07 +02:00
Damian Gryski 5fa1e7163a src/runtime: reset heapptr to heapStart after preinit()
heapptr is assinged to heapStart (which is 0) when it's declared, but preinit()
may have moved the heap somewhere else.  Set heapptr to the proper value
of heapStart when we initialize the heap properly.

This allows the leaking allocator to work on unix.
2021-09-04 12:13:53 +02:00
deadprogram 4b1f92600f docker: use go 1.17 for docker dev build
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-09-02 11:18:52 +02:00
sago35 ca39bc9f35 machine/feather-rp2040: add pin name definition for feather 2021-09-01 20:55:35 +02:00
sago35 f985f2c376 targets: add openocd configuration for rp2040 2021-09-01 19:37:23 +02:00
Federico G. Schwindt f0936ffccb Implement os.Executable
For now this is a stub for everything but linux, which is a slightly
modified copy of the official implementation.

Should address #1778.
2021-09-01 19:01:35 +02:00
Yurii Soldak 97d48e5c02 board/nano-rp2040: define NINA_SPI and fix wifinina pins 2021-09-01 17:24:19 +02:00
Patricio Whittingslow a7c53cce06 machine/rp2040: add PWM implementation (#2015)
machine/rp2040: add PWM implementation
2021-09-01 16:58:13 +02:00
Federico G. Schwindt 2d224ae049 Minor changes to support go 1.17 2021-08-31 16:04:42 +02:00
Olivier Fauchon 6c6fea5387 BlackMagic (BMP) ARM JTAG/SWD debugger:
- Flashing and debugging with BMP can be done with -programmer=bmp
- New getBMPPorts() function was added to properly detect BMP USB serial ports.
2021-08-31 10:03:40 +02:00
sago35 98bd947817 machine/arduino_mkrwifi1010: add board definition for Arduino MKR WiFi 1010 2021-08-30 15:45:47 +02:00
Ayke van Laethem 255f35671d compiler: add support for new language features of Go 1.17 2021-08-30 09:18:58 +02:00
Ayke van Laethem 8e88e560a1 all: add support for Go 1.17 2021-08-30 09:18:58 +02:00
Ayke van Laethem d45497691f reflect: add StructField.IsExported method
This field was introduced in Go 1.17 and is used by the encoding/json
package (starting with Go 1.17).
2021-08-30 09:18:58 +02:00
Ayke van Laethem ad73986070 goenv: improve Go version detection
First look at the VERSION file, only then look at
src/runtime/internal/sys/zversion.go. This makes it possible to
correctly detect the Go version for release candidates.
2021-08-30 09:18:58 +02:00
deadprogram 931f87f96a docker: golang default images now based on bullseye
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-08-18 20:28:36 +02:00
deadprogram 192a32f8d9 docker: use autoremove to tr to cleanup broken packages
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-08-18 20:19:33 +02:00
deadprogram 972f4254eb docker: add GH actions build on fix-docker-llvm-build branch to sort out build issues
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-08-18 20:18:16 +02:00
deadprogram 7d83e2ee5c docker: apt clean before apt get of llvm to avoid broken packages
Signed-off-by: deadprogram <ron@hybridgroup.com>
2021-08-18 20:01:50 +02:00
Ayke van Laethem 0f2f73be53 compiler: fix max possible slice
This commit improves make([]T, len) to be closer to upstream Go. The
difference is unlikely to have much real-world effect, but previously
certain make([]T, len) expressions would not result in a slice out of
bounds error in TinyGo while they would have done such a thing in Go
proper. In practice, available RAM is likely to be a bigger limiting
factor.
2021-08-17 08:16:27 +02:00
Ayke van Laethem a2cc5715ba compiler: add *ssa.MakeSlice bounds tests
There are some bugs in it. This commit adds the tests, so that the next
commit can show what changed.
2021-08-17 08:16:27 +02:00
Ayke van Laethem 59d53182bb all: use new testing features of Go 1.14 and 1.15
This simplifies the tests a bit.
2021-08-16 21:19:26 +02:00
Ayke van Laethem 25c7bfd404 ci: drop support for Go 1.13 and 1.14
They aren't supported anymore in CI, and because untested code is broken
code, let's remove support for these Go versions altogether.
2021-08-16 21:19:26 +02:00
Ayke van Laethem 04f520040e testing: add support for the -test.v flag
This flag is passed automatically with the (new) -v flag for TinyGo. For
example, this prints all the test outputs:

    $ tinygo test -v crypto/md5
    === RUN   TestGolden
    --- PASS: TestGolden
    === RUN   TestGoldenMarshal
    --- PASS: TestGoldenMarshal
    === RUN   TestLarge
    --- PASS: TestLarge
    === RUN   TestBlockGeneric
    --- PASS: TestBlockGeneric
    === RUN   TestLargeHashes
    --- PASS: TestLargeHashes
    PASS
    ok  	crypto/md5	0.002s

This prints just a summary:

    $ tinygo test crypto/md5
    PASS
    ok  	crypto/md5	0.002s

(The superfluous 'PASS' message may be removed in the future).

This is especially useful when testing a large number of packages:

    $ tinygo test crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512
    PASS
    ok  	crypto/md5	0.002s
    PASS
    ok  	crypto/sha1	0.043s
    PASS
    ok  	crypto/sha256	0.002s
    PASS
    ok  	crypto/sha512	0.003s

At the moment, the -test.v flag is not supplied to binaries running in
emulation. I intend to fix this after
https://github.com/tinygo-org/tinygo/pull/2038 lands by refactoring
runPackageTest, Run, and runTestWithConfig in the main package which all
do something similar.
2021-08-13 08:53:40 +02:00
Ayke van Laethem f57e9622fd baremetal,wasm: support command line params and environment variables
This is mainly useful to be able to run `tinygo test`, for example:

    tinygo test -target=cortex-m-qemu -v math

This is not currently supported, but will be in the future.
2021-08-12 21:19:24 +02:00
Ayke van Laethem c25a7cc747 testing: test testing package using tinygo test 2021-08-12 13:23:41 +02:00
Ayke van Laethem 5e5ce98d42 compiler: add aliases for many hashing packages
This commit adds support for the following packages:

  - crypto/md5
  - crypto/sha1
  - crypto/sha256
  - crypto/sha512

They would normally need assembly implementations, but with these
aliases they already work everywhere.
2021-08-10 20:08:27 +02:00
Ayke van Laethem a3c4421f39 compiler: move math aliases from the runtime to the compiler
This makes them more flexible, especially with Go 1.17 making the
situation more complicated (see
https://github.com/golang/go/commit/1d20a362d0ca4898d77865e314ef6f73582daef0).
It also makes it possible to do the same for many other functions, such
as assembly implementations of cryptographich functions which are
similarly dependent on the architecture.
2021-08-10 20:08:27 +02:00
Ayke van Laethem 58565b42cc compiler: move LLVM math builtin support into the compiler
This simplifies src/runtime/math.go, which I eventually want to remove
entirely by moving the given functionality into the compiler.
2021-08-10 20:08:27 +02:00
Ayke van Laethem ca7c849da3 386: bump minimum requirement to the Pentium 4
Previously we used the i386 target, probably with all optional features
disabled. However, the Pentium 4 has been released a _long_ time ago and
it seems reasonable to me to take that as a minimum requirement.

Upstream Go now also seems to move in this direction:
https://github.com/golang/go/issues/40255

The main motivation for this is that there were floating point issues
when running the tests for the math package:

    GOARCH=386 tinygo test math

I haven't investigated what's the issue, but I strongly suspect it's
caused by the weird x87 80-bit floating point format. This could perhaps
be fixed in a different way (by setting the FPU precision to 64 bits)
but I figured that just setting the minimum requirement to the Pentium 4
would probably be fine. If needed, we can respect the GO386 environment
variable to support these very old CPUs.

To support this newer CPU, I had to make sure that the stack is aligned
to 16 bytes everywhere. This was not yet always the case.
2021-08-10 20:08:27 +02:00
Ayke van Laethem 6c1301688b math: fix math.Max and math.Min
The math package failed the package tests on arm64 and wasm:

    GOARCH=arm64 tinygo test math

Apparently the builtins llvm.maximum.f64 and llvm.minimum.f64 have
slightly different behavior on arm64 and wasm compared to what Go
expects.
2021-08-10 20:08:27 +02:00
Ayke van Laethem d05103668f crypto/rand: switch to arc4random_buf
This doesn't have the potential blocking issue of the getentropy call
(which calls WASI random_get when using wasi-libc) and should therefore
be a lot faster.

For context, this is what the random_get documentation says:

> Write high-quality random data into a buffer. This function blocks
> when the implementation is unable to immediately provide sufficient
> high-quality random data. This function may execute slowly, so when
> large mounts of random data are required, it's advisable to use this
> function to seed a pseudo-random number generator, rather than to
> provide the random data directly.
2021-08-09 15:20:39 +02:00
Patricio Whittingslow 4f7b23c2b7 machine/rp2040: add I2C support (#2013)
machine/rp2040: add i2c support
2021-08-06 17:22:50 +02:00
Dan Kegel cfae2d4f9a Makefile: add src/testing to FMT_PATHS 2021-08-06 08:19:15 +02:00
Dan Kegel 55789fd2c2 src/testing/benchmark.go: add subset implementation of Benchmark
Partially fixes #1808

Allows the following to succeed:

curl "https://golang.org/test/fibo.go?m=text" > fibo.go
tinygo build -o fibo fibo.go
./fibo -bench
2021-08-06 08:19:15 +02:00
Ayke van Laethem 478c592b13 wasm: add support for the crypto/rand package
This is done via wasi-libc and the WASI interface, for ease of
maintenance (only one implementation for both WASI and JS/browsers).
2021-08-05 19:01:14 +02:00
Ayke van Laethem ab47cea055 transform: improve GC stack slot pass to work around a bug
Bug 1790 ("musttail call must precede a ret with an optional bitcast")
is caused by the GC stack slot pass inserting a store instruction
between a musttail call and a return instruction. This is not allowed in
LLVM IR.

One solution would be to remove the musttail. That would probably work,
but 1) the go-llvm API doesn't support this and 2) this might have
unforeseen consequences. What I've done in this commit is to move the
store instruction to a position earlier in the basic block, just after
the last access to the GC stack slot alloca.

Thanks to @fgsch for a very small repro, which I've used as a regression
test.
2021-08-04 20:06:59 +02:00
ardnew 044fc1f7a0 Merge branch 'dev' into feature/usb-common 2021-08-02 13:39:59 -05:00
ardnew f029a039ea Merge remote-tracking branch 'origin/dev' into dev 2021-08-02 13:37:21 -05:00
soypat 98e70c9b19 machine/rp2040: add SPI support
spi working with loopback

SPI working

apply @deadprogram's suggestions

consolidate SPI board pin naming

fix up SPI configuration

add feather-rp2040 SPI pins

add arduino connect SPI pins

add SPI handle variables
2021-07-31 22:11:08 +02:00
Ayke van Laethem 7434e5a2c7 main: strip debug information at link time instead of at compile time
Stripping debug information at link time also allows relocation
compression (aka linker relaxations). Keeping debug information at
compile time and optionally stripping it at link time has some
advantages:

  * Automatic stack sizes on Cortex-M rely on the presence of debug
    information.
  * Some parts of the compiler now rely on the presence of debug
    information for proper diagnostics.
  * It works better with the cache: there is no distinction between
    debug and no-debug builds.
  * It makes it easier (or possible at all) to enable debug information
    in the wasi-libc library without big downsides.
2021-07-31 18:33:52 +02:00
Ayke van Laethem 65c1978965 wasm: align heap to 16 bytes
This commit fixes two things:

  * It changes the alignment to 16 bytes (from 4), to match max_align_t
    in C.
  * It manually aligns heapStart on WebAssembly, to work around a bug in
    wasm-ld with --stack-first (see https://reviews.llvm.org/D106499).
2021-07-30 08:38:51 +02:00
Federico G. Schwindt e834d78871 Fix undefined symbols error
Currently TinyGo does not process SFiles (assembly files), which
are needed by math/big. Add math_big_pure_go to the build tags to
unbreak it.
2021-07-27 14:14:39 +02:00
Ayke van Laethem 03481789b0 runtime: fix time base for time.Now()
This function previously returned the atomic time, that isn't affected
by system time changes but also has a time base at some arbitrary time
in the past. This makes sense for baremetal platforms (which typically
don't know the wall time) but it gives surprising results on Linux and
macOS: time.Now() usually returns a time somewhere near the start of
1970.

This commit fixes this by obtaining both time values: the monotonic time
and the wall clock time. This is also how the Go runtime implements the
time.now function.
2021-07-20 22:19:13 +02:00
sago35 73cf187552 machine/feather-nrf52: fix pin definition of uart 2021-07-20 17:45:41 +02:00
Ayke van Laethem b40703e986 wasm: override dlmalloc heap implementation from wasi-libc
These two heaps conflict with each other, so that if any function uses
the dlmalloc heap implementation it will eventually result in memory
corruption.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    tinygo test -c math

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

Use a global variable for this purpose instead to avoid the heap
allocation. This is safe as waitForEvent may only be called outside of
interrupts.
2021-05-30 20:56:01 +02:00
sago35 22eeed2da1 qtpy: add pin for neopixels 2021-05-30 11:01:19 +02:00
Kenneth Bell cfbc9be9bf rp2040: git ignore generated device files 2021-05-29 19:56:50 +02:00
ardnew e9ddbdf4a8 implemented USB HID composite keyboard support 2021-05-28 18:15:02 -05:00
ardnew 43d5eee1e6 begin isolating target-specific USB code 2021-05-28 18:14:59 -05:00
ardnew 0ff290cecc prevent heap alloc in control complete 2021-05-28 18:14:46 -05:00
ardnew eb705f0d4f rename package machine/usb2 to machine/usb 2021-05-28 18:14:30 -05:00
ardnew 733da1226d USB CDC-ACM UART Rx/Tx functioning for baseline target (Teensy 4.0/4.1) 2021-05-28 18:14:15 -05:00
ardnew 84e61c183a functioning ACM device registration with host 2021-05-28 18:12:17 -05:00
ardnew 1c2463f0b0 cleanup unused code for baseline 2021-05-28 18:12:13 -05:00
ardnew df57c029a7 begin USB refactor with package machine/usb2 2021-05-28 18:11:58 -05:00
Rajiv Kanchan 722a3a5c94 add rp2040, pico
adds preliminary support (just enough to run blinky1) for the Raspberry Pi Pico board along with the rp2040 mcu.
2021-05-28 18:29:04 +02:00
deadprogram ed2db8a26d Revert "scheduler: task.Data made 64bit to avoid overflow"
This reverts commit fd8938c634.
2021-05-28 07:38:59 +02:00
Kenneth Bell fd8938c634 scheduler: task.Data made 64bit to avoid overflow 2021-05-28 00:02:46 +02:00
Kenneth Bell fa3dd41a4f stm32: Use TIM for runtime clock 2021-05-28 00:02:46 +02:00
Kenneth Bell 003c96edc0 stm32f103 (bluepill): add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell ac54302301 stm32f7: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell c43a41165a stm32l4: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell 3145c2747e stm32l0: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell 2c4b507d34 stm32l5: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell ee167f15de stm32: add pwm for f4 series 2021-05-28 00:02:46 +02:00
Yurii Soldak 422ebeeec7 Do not disable interrupts on abort
Allows panic messages to be printed fully into serial console
2021-05-27 09:28:08 +02:00
Ayke van Laethem c93ddb630b compiler: skip context parameter when starting regular goroutine
Do not store the context parameter (which is used for closures and
function pointers) in the goroutine start parameter bundle for direct
functions that don't need a context parameter. This avoids storing the
(undef) context parameter and thus makes the IR to start a new goroutine
simpler in most cases.

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

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

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

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

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

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

These two meanings conflict, and resulted in workarounds like:

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

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

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

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

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

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

My eventual goal is the following:

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

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

This change does have an effect on code size and memory consumption.
There is often a small reduction (-8 bytes) in RAM consumption and an
increase in flash consumption.
2021-05-13 16:43:37 +02:00
585 changed files with 25784 additions and 5221 deletions
+22 -41
View File
@@ -17,18 +17,15 @@ commands:
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
sudo apt-get update
sudo apt-get install \
sudo apt-get install --no-install-recommends \
llvm-<<parameters.llvm>>-dev \
clang-<<parameters.llvm>> \
libclang-<<parameters.llvm>>-dev \
lld-<<parameters.llvm>> \
gcc-arm-linux-gnueabihf \
gcc-aarch64-linux-gnu \
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-8-dev
install-node:
steps:
- run:
@@ -117,7 +114,10 @@ commands:
key: wasi-libc-sysroot-systemclang-v3
paths:
- lib/wasi-libc/sysroot
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform .
- run:
name: "Test TinyGo"
command: go test -v -timeout=20m -tags=llvm<<parameters.llvm>> ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
no_output_timeout: 20m
- run: make gen-device -j4
- run: make smoketest XTENSA=0
- run: make tinygo-test
@@ -136,16 +136,12 @@ commands:
name: "Install apt dependencies"
command: |
sudo apt-get update
sudo apt-get install \
gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
sudo apt-get install --no-install-recommends \
libgnutls30 libssl1.0.2 \
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
- install-node
- install-wasmtime
- install-xtensa-toolchain:
@@ -157,7 +153,7 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-11-linux-v3-assert
- llvm-build-11-linux-v4-assert
- run:
name: "Build LLVM"
command: |
@@ -176,7 +172,7 @@ commands:
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-11-linux-v3-assert
key: llvm-build-11-linux-v4-assert
paths:
llvm-build
- run: make ASSERT=1
@@ -184,6 +180,7 @@ commands:
- run:
name: "Test TinyGo"
command: make ASSERT=1 test
no_output_timeout: 20m
environment:
# Note: -p=2 limits parallelism to two jobs at a time, which is
# necessary to keep memory consumption down and avoid OOM (for a
@@ -204,16 +201,12 @@ commands:
name: "Install apt dependencies"
command: |
sudo apt-get update
sudo apt-get install \
gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
sudo apt-get install --no-install-recommends \
libgnutls30 libssl1.0.2 \
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
- install-node
- install-wasmtime
- install-xtensa-toolchain:
@@ -225,7 +218,7 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-11-linux-v3-noassert
- llvm-build-11-linux-v4-noassert
- run:
name: "Build LLVM"
command: |
@@ -244,13 +237,14 @@ commands:
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-11-linux-v3-noassert
key: llvm-build-11-linux-v4-noassert
paths:
llvm-build
- build-wasi-libc
- run:
name: "Test TinyGo"
command: make test
no_output_timeout: 20m
- run:
name: "Install fpm"
command: |
@@ -286,8 +280,8 @@ commands:
- run:
name: "Install dependencies"
command: |
curl https://dl.google.com/go/go1.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.16.darwin-amd64.tar.gz
curl https://dl.google.com/go/go1.17.darwin-amd64.tar.gz -o go1.17.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.17.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- install-xtensa-toolchain:
@@ -311,7 +305,7 @@ commands:
- llvm-project/llvm/include
- restore_cache:
keys:
- llvm-build-11-macos-v4
- llvm-build-11-macos-v5
- run:
name: "Build LLVM"
command: |
@@ -327,7 +321,7 @@ commands:
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-11-macos-v4
key: llvm-build-11-macos-v5
paths:
llvm-build
- restore_cache:
@@ -343,6 +337,7 @@ commands:
- run:
name: "Test TinyGo"
command: make test
no_output_timeout: 20m
- run:
name: "Build TinyGo release"
command: |
@@ -365,18 +360,6 @@ commands:
- /go/pkg/mod
jobs:
test-llvm10-go113:
docker:
- image: circleci/golang:1.13-buster
steps:
- test-linux:
llvm: "10"
test-llvm10-go114:
docker:
- image: circleci/golang:1.14-buster
steps:
- test-linux:
llvm: "10"
test-llvm11-go115:
docker:
- image: circleci/golang:1.15-buster
@@ -391,12 +374,12 @@ jobs:
llvm: "11"
assert-test-linux:
docker:
- image: circleci/golang:1.14-stretch
- image: circleci/golang:1.17-stretch
steps:
- assert-test-linux
build-linux:
docker:
- image: circleci/golang:1.14-stretch
- image: circleci/golang:1.17-stretch
steps:
- build-linux
build-macos:
@@ -410,8 +393,6 @@ jobs:
workflows:
test-all:
jobs:
- test-llvm10-go113
- test-llvm10-go114
- test-llvm11-go115
- test-llvm11-go116
- build-linux
+3
View File
@@ -0,0 +1,3 @@
build/
llvm-*/
@@ -0,0 +1,45 @@
name: CI for tinygo-dev docker container
on:
push:
branches: [ dev, fix-docker-llvm-build ]
jobs:
push_to_registry:
name: Push Docker image to GHCR/Docker Hub
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v2
with:
submodules: recursive
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: |
tinygo/tinygo-dev
ghcr.io/${{ github.repository }}/tinygo-dev
tags: |
type=sha,format=long
type=raw,value=latest
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Log in to Github Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+85
View File
@@ -0,0 +1,85 @@
name: Windows
on: push
jobs:
build-windows:
runs-on: windows-2016
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: '1.17'
- name: Install QEMU
shell: bash
run: |
choco install qemu --version=2020.06.12
echo "C:\Program Files\QEMU" >> $GITHUB_PATH
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Cache LLVM source
uses: actions/cache@v2
id: cache-llvm-source
with:
key: llvm-source-11-windows-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/lld/include
llvm-project/llvm/include
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache LLVM build
uses: actions/cache@v2
id: cache-llvm-build
with:
key: llvm-build-11-windows-v2
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
shell: bash
run: |
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies
choco install ninja
# build!
make llvm-build
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Cache wasi-libc sysroot
uses: actions/cache@v2
id: cache-wasi-libc
with:
key: wasi-libc-sysroot-v1
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- name: Test TinyGo
shell: bash
run: make test
- name: Build TinyGo release tarball
run: make build/release -j4
- name: Make release artifact
shell: bash
working-directory: build/release
run: 7z -tzip a release.zip tinygo
- name: Publish release artifact
# Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39
# We can essentially pick one of these:
# - have a dobule-zipped artifact when downloaded from the UI
# - have a very slow artifact upload
# We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v2
with:
name: release-double-zipped
path: build/release/release.zip
- name: Smoke tests
shell: bash
run: make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
+2
View File
@@ -16,6 +16,8 @@ src/device/stm32/*.go
src/device/stm32/*.s
src/device/kendryte/*.go
src/device/kendryte/*.s
src/device/rp/*.go
src/device/rp/*.s
vendor
llvm-build
llvm-project
+3
View File
@@ -23,3 +23,6 @@
[submodule "lib/stm32-svd"]
path = lib/stm32-svd
url = https://github.com/tinygo-org/stm32-svd
[submodule "lib/musl"]
path = lib/musl
url = git://git.musl-libc.org/musl
+1 -1
View File
@@ -23,7 +23,7 @@ different guide:
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.13+)
* Go (1.15+)
* Standard build tools (gcc/clang)
* git
* CMake
+136
View File
@@ -1,3 +1,139 @@
0.20.0
---
* **command line**
- add support for Go 1.17
- improve Go version detection
- add support for the Black Magic Probe (BMP)
- add a flag for creating cpu profiles
* **compiler**
- `builder:` list libraries at the end of the linker command
- `builder:` strip debug information at link time instead of at compile time
- `builder:` add missing error check for `ioutil.TempFile()`
- `builder:` simplify running of jobs
- `compiler:` move LLVM math builtin support into the compiler
- `compiler:` move math aliases from the runtime to the compiler
- `compiler:` add aliases for many hashing packages
- `compiler:` add `*ssa.MakeSlice` bounds tests
- `compiler:` fix max possible slice
- `compiler:` add support for new language features of Go 1.17
- `compiler:` fix equally named structs in different scopes
- `compiler:` avoid zero-sized alloca in channel operations
- `interp:` don't ignore array indices for untyped objects
- `interp:` keep reverted package initializers in order
- `interp:` fix bug in compiler-time/run-time package initializers
- `loader:` fix panic in CGo files with syntax errors
- `transform:` improve GC stack slot pass to work around a bug
* **standard library**
- `crypto/rand`: switch to `arc4random_buf`
- `math:` fix `math.Max` and `math.Min`
- `math/big`: fix undefined symbols error
- `net:` add MAC address implementation
- `os:` implement `os.Executable`
- `os:` add `SEEK_SET`, `SEEK_CUR`, and `SEEK_END`
- `reflect:` add StructField.IsExported method
- `runtime:` reset heapptr to heapStart after preinit()
- `runtime:` add `subsections_via_symbols` to assembly files on darwin
- `testing:` add subset implementation of Benchmark
- `testing:` test testing package using `tinygo test`
- `testing:` add support for the `-test.v` flag
* **targets**
- `386:` bump minimum requirement to the Pentium 4
- `arm:` switch to Thumb instruction set on ARM
- `atsamd:` fix copy-paste error for atsamd21/51 calibTrim block
- `baremetal`,`wasm`: support command line params and environment variables
- `cortexm:` fix stack overflow because of unaligned stacks
- `esp32c3:` add support for the ESP32-C3 from Espressif
- `nrf52840:` fix ram size
- `nxpmk66f18:` fix a suspicious bitwise operation
- `rp2040:` add SPI support
- `rp2040:` add I2C support
- `rp2040:` add PWM implementation
- `rp2040:` add openocd configuration
- `stm32:` add support for PortMask* functions for WS2812 support
- `unix:` fix time base for time.Now()
- `unix:` check for mmap error and act accordingly
- `wasm:` override dlmalloc heap implementation from wasi-libc
- `wasm:` align heap to 16 bytes
- `wasm:` add support for the crypto/rand package
* **boards**
- add `DefaultUART` to adafruit boards
- `arduino-mkrwifi1010:` add board definition for Arduino MKR WiFi 1010
- `arduino-mkrwifi1010:` fix pin definition of `NINA_RESETN`
- `feather-nrf52:` fix pin definition of uart
- `feather-rp2040:` add pin name definition
- `gameboy-advance:` fix ROM header
- `mdbt50qrx-uf2:` add Raytac MDBT50Q-RX Dongle with TinyUF2
- `nano-rp2040:` define `NINA_SPI` and fix wifinina pins
- `teensy40:` enable hardware UART reconfiguration, fix receive watermark interrupt
0.19.0
---
* **command line**
- don't consider compile-only tests as failing
- add -test flag for `tinygo list`
- escape commands while printing them with the -x flag
- make flash-command portable and safer to use
- use `extended-remote` instead of `remote` in GDB
- detect specific serial port IDs based on USB vid/pid
- add a flag to the command line to select the serial implementation
* **compiler**
- `cgo`: improve constant parser
- `compiler`: support chained interrupt handlers
- `compiler`: add support for running a builtin in a goroutine
- `compiler`: do not emit nil checks for loading closure variables
- `compiler`: skip context parameter when starting regular goroutine
- `compiler`: refactor method names
- `compiler`: add function and global section pragmas
- `compiler`: implement `syscall.rawSyscallNoError` in inline assembly
- `interp`: ignore inline assembly in markExternal
- `interp`: fix a bug in pointer cast workaround
- `loader`: fix testing a main package
* **standard library**
- `crypto/rand`: replace this package with a TinyGo version
- `machine`: make USBCDC global a pointer
- `machine`: make UART objects pointer receivers
- `machine`: define Serial as the default output
- `net`: add initial support for net.IP
- `net`: add more net compatibility
- `os`: add stub for os.ReadDir
- `os`: add FileMode constants from Go 1.16
- `os`: add stubs required for net/http
- `os`: implement process related functions
- `reflect`: implement AppendSlice
- `reflect`: add stubs required for net/http
- `runtime`: make task.Data a 64-bit integer to avoid overflow
- `runtime`: expose memory stats
- `sync`: implement NewCond
- `syscall`: fix int type in libc version
* **targets**
- `cortexm`: do not disable interrupts on abort
- `cortexm`: bump default stack size to 2048 bytes
- `nrf`: avoid heap allocation in waitForEvent
- `nrf`: don't trigger a heap allocation in SPI.Transfer
- `nrf52840`: add support for flashing with the BOSSA tool
- `rp2040`: add support for GPIO input
- `rp2040`: add basic support for ADC
- `rp2040`: gpio and adc pin definitions
- `rp2040`: implement UART
- `rp2040`: patch elf to checksum 2nd stage boot
- `stm32`: add PWM for most chips
- `stm32`: add support for pin interrupts
- `stm32f103`: add support for PinInputPullup / PinInputPulldown
- `wasi`: remove wasm build tag
* **boards**
- `feather-rp2040`: add support for this board
- `feather-nrf52840-sense`: add board definition for this board
- `pca10059`: support flashing from Windows
- `nano-rp2040`: add this board
- `nano-33-ble`: add support for this board
- `pico`: add the Raspberry Pi Pico board with the new RP2040 chip
- `qtpy`: add pin for neopixels
- all: add definition for ws2812 for supported boards
0.18.0
---
+4 -4
View File
@@ -1,8 +1,8 @@
# TinyGo base stage installs the most recent Go 1.15.x, LLVM 11 and the TinyGo compiler itself.
FROM golang:1.15 AS tinygo-base
# TinyGo base stage installs the most recent Go 1.17.x, LLVM 11 and the TinyGo compiler itself.
FROM golang:1.17 AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-11 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git
@@ -15,7 +15,7 @@ RUN cd /tinygo/ && \
git submodule sync && \
git submodule update --init --recursive --force
COPY ./lib/picolibc-include/* /tinygo/lib/picolibc-include/
COPY ./lib/picolibc-stdio.c /tinygo/lib/picolibc-stdio.c
RUN cd /tinygo/ && \
go install /tinygo/
+101 -29
View File
@@ -10,9 +10,9 @@ LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
# Try to autodetect LLVM build tools.
detect = $(shell command -v $(1) 2> /dev/null && echo $(1))
CLANG ?= $(word 1,$(abspath $(call detect,llvm-build/bin/clang))$(call detect,clang-11)$(call detect,clang-10)$(call detect,clang))
LLVM_AR ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-ar))$(call detect,llvm-ar-11)$(call detect,llvm-ar-10)$(call detect,llvm-ar))
LLVM_NM ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-nm))$(call detect,llvm-nm-11)$(call detect,llvm-nm-10)$(call detect,llvm-nm))
CLANG ?= $(word 1,$(abspath $(call detect,llvm-build/bin/clang))$(call detect,clang-11)$(call detect,clang))
LLVM_AR ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-ar))$(call detect,llvm-ar-11)$(call detect,llvm-ar))
LLVM_NM ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-nm))$(call detect,llvm-nm-11)$(call detect,llvm-nm))
# Go binary and GOROOT to select
GO ?= go
@@ -36,7 +36,7 @@ else
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
@@ -71,7 +71,7 @@ else
endif
# Libraries that should be linked in for the statically linked Clang.
CLANG_LIB_NAMES = clangAnalysis clangARCMigrate clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangStaticAnalyzerCheckers clangStaticAnalyzerCore clangStaticAnalyzerFrontend clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
# Libraries that should be linked in for the statically linked LLD.
@@ -100,14 +100,14 @@ endif
clean:
@rm -rf build
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/testing src/internal/reflectlite transform
fmt:
@gofmt -l -w $(FMT_PATHS)
fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp
ifneq ($(STM32), 0)
gen-device: gen-device-stm32
endif
@@ -124,6 +124,7 @@ build/gen-device-svd: ./tools/gen-device-svd/*.go
gen-device-esp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/
GO111MODULE=off $(GO) fmt ./src/device/esp
gen-device-nrf: build/gen-device-svd
@@ -150,6 +151,9 @@ gen-device-stm32: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/
GO111MODULE=off $(GO) fmt ./src/device/stm32
gen-device-rp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/
GO111MODULE=off $(GO) fmt ./src/device/rp
# Get LLVM sources.
$(LLVM_PROJECTDIR)/llvm:
@@ -159,7 +163,7 @@ llvm-source: $(LLVM_PROJECTDIR)/llvm
# Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION)
# Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
@@ -171,7 +175,7 @@ $(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
cd lib/wasi-libc && make -j4 WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
cd lib/wasi-libc && make -j4 WASM_CFLAGS="-O2 -g -DNDEBUG" WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
# Build the Go compiler.
@@ -180,23 +184,37 @@ tinygo:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" .
test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -timeout=20m -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
TEST_PACKAGES = \
compress/bzip2 \
container/heap \
container/list \
container/ring \
crypto/des \
crypto/dsa \
crypto/md5 \
crypto/rc4 \
crypto/sha1 \
crypto/sha256 \
crypto/sha512 \
encoding \
encoding/ascii85 \
encoding/base32 \
encoding/hex \
hash \
hash/adler32 \
hash/fnv \
hash/crc64 \
html \
index/suffixarray \
math \
math/cmplx \
testing \
testing/iotest \
text/scanner \
text/scanner \
unicode \
unicode/utf8 \
# Test known-working standard library packages.
@@ -227,6 +245,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/memstats
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@@ -238,17 +258,17 @@ smoketest:
$(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
$(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
$(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
$(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
$(TINYGO) build -size short -o test.wasm -tags=pca10040 examples/blinky2
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=pca10056 examples/blinky2
$(TINYGO) build -size short -o test.wasm -tags=pca10056 examples/blinky2
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=circuitplay_express examples/blinky1
$(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1
@$(MD5SUM) test.wasm
# test all targets/boards
$(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1
@@ -331,9 +351,11 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840-sense examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=qtpy examples/blinky1
$(TINYGO) build -size short -o test.hex -target=qtpy examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1
@$(MD5SUM) test.hex
@@ -349,6 +371,18 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/caninterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mkrwifi1010 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/blinky1
@$(MD5SUM) test.hex
# test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex
@@ -379,6 +413,8 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/pwm
@$(MD5SUM) test.hex
endif
ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
@@ -405,24 +441,34 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/serial
@$(MD5SUM) test.bin
endif
$(TINYGO) build -size short -o test.bin -target=esp32c3 examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main
# test various compiler flags
$(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
@$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex
GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo
ifneq ($(OS),Windows_NT)
$(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial
endif
wasmtest:
@@ -433,12 +479,16 @@ build/release: tinygo gen-device wasi-libc
@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/musl/arch
@mkdir -p build/release/tinygo/lib/musl/crt
@mkdir -p build/release/tinygo/lib/musl/src
@mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libm
@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
@mkdir -p build/release/tinygo/pkg/armv6m-unknown-unknown-eabi
@mkdir -p build/release/tinygo/pkg/armv7m-unknown-unknown-eabi
@mkdir -p build/release/tinygo/pkg/armv7em-unknown-unknown-eabi
@echo copying source files
@cp -p build/tinygo$(EXE) build/release/tinygo/bin
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
@@ -447,22 +497,44 @@ build/release: tinygo gen-device wasi-libc
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
@cp -rp lib/musl/arch/aarch64 build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt
@cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl
@cp -rp lib/musl/include build/release/tinygo/lib/musl
@cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
@cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc-include build/release/tinygo/lib
@cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm
@cp -rp lib/picolibc-stdio.c build/release/tinygo/lib
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
@cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv6m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv6m-unknown-unknown-eabi/compiler-rt compiler-rt
./build/tinygo build-library -target=armv7m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7m-unknown-unknown-eabi/compiler-rt compiler-rt
./build/tinygo build-library -target=armv7em-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7em-unknown-unknown-eabi/compiler-rt compiler-rt
./build/tinygo build-library -target=armv6m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv6m-unknown-unknown-eabi/picolibc picolibc
./build/tinygo build-library -target=armv7m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7m-unknown-unknown-eabi/picolibc picolibc
./build/tinygo build-library -target=armv7em-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7em-unknown-unknown-eabi/picolibc picolibc
release: build/release
tar -czf build/release.tar.gz -C build/release tinygo
+11 -2
View File
@@ -43,7 +43,7 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 62 microcontroller boards are currently supported:
The following 71 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)
@@ -52,6 +52,8 @@ The following 62 microcontroller boards are currently supported:
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
@@ -67,8 +69,12 @@ The following 62 microcontroller boards are currently supported:
* [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010)
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble)
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
* [BBC micro:bit](https://microbit.org/)
@@ -78,6 +84,7 @@ The following 62 microcontroller boards are currently supported:
* [ESP32](https://www.espressif.com/en/products/socs/esp32)
* [ESP8266](https://www.espressif.com/en/products/socs/esp8266)
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [M5Stack Core2](https://shop.m5stack.com/products/m5stack-core2-esp32-iot-development-kit)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
@@ -95,6 +102,8 @@ The following 62 microcontroller boards are currently supported:
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/)
* [Raytac MDBT50Q-RX Dongle (with TinyUF2 bootloader)](https://www.adafruit.com/product/5199)
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
-88
View File
@@ -1,88 +0,0 @@
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
# be built anyway.
trigger:
- release
- dev
jobs:
- job: Build
timeoutInMinutes: 240 # 4h
pool:
vmImage: 'VS2017-Win2016'
steps:
- task: GoTool@0
inputs:
version: '1.16'
- checkout: self
fetchDepth: 1
- task: Cache@2
displayName: Cache LLVM source
inputs:
key: llvm-source-11-windows-v1
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-11-windows-v4
path: llvm-build
- task: Bash@3
displayName: Build LLVM
inputs:
targetType: inline
script: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
choco install ninja
# hack ninja to use fewer jobs
echo -e 'C:\\ProgramData\\Chocolatey\\bin\\ninja -j4 %*' > /usr/bin/ninja.bat
# build!
make llvm-build
fi
- task: Bash@3
displayName: Install QEMU
inputs:
targetType: inline
script: choco install qemu --version=2020.06.12
- task: CacheBeta@0
displayName: Cache wasi-libc sysroot
inputs:
key: wasi-libc-sysroot-v4
path: lib/wasi-libc/sysroot
- task: Bash@3
displayName: Build wasi-libc
inputs:
targetType: inline
script: PATH=/usr/bin:$PATH make wasi-libc
- task: Bash@3
displayName: Test TinyGo
inputs:
targetType: inline
script: |
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make test
- task: Bash@3
displayName: Build TinyGo release tarball
inputs:
targetType: inline
script: |
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make build/release -j4
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
displayName: Publish zip as artifact
artifact: tinygo
- task: Bash@3
displayName: Smoke tests
inputs:
targetType: inline
script: |
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
+25 -25
View File
@@ -5,6 +5,7 @@ import (
"debug/elf"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"path/filepath"
@@ -17,17 +18,12 @@ import (
// given as a parameter. It is equivalent to the following command:
//
// ar -rcs <archivePath> <objs...>
func makeArchive(archivePath string, objs []string) error {
func makeArchive(arfile *os.File, 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()
err := arwriter.WriteGlobalHeader()
if err != nil {
return &os.PathError{"write ar header", archivePath, err}
return &os.PathError{Op: "write ar header", Path: arfile.Name(), Err: err}
}
// Open all object files and read the symbols for the symbol table.
@@ -35,21 +31,17 @@ func makeArchive(archivePath string, objs []string) error {
name string // symbol name
fileIndex int // index into objfiles
}{}
objfiles := make([]struct {
file *os.File
archiveOffset int32
}, len(objs))
archiveOffsets := make([]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
return fmt.Errorf("failed to open file %s: %w", objpath, err)
}
symbols, err := dbg.Symbols()
if err != nil {
@@ -61,9 +53,8 @@ func makeArchive(archivePath string, objs []string) error {
// Don't include local symbols (STB_LOCAL).
continue
}
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC {
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT {
// Not a function.
// TODO: perhaps globals variables should also be included?
continue
}
// Include in archive.
@@ -72,6 +63,10 @@ func makeArchive(archivePath string, objs []string) error {
fileIndex int
}{symbol.Name, i})
}
// Close file, to avoid issues with too many open files (especially on
// MacOS X).
objfile.Close()
}
// Create the symbol table buffer.
@@ -125,7 +120,12 @@ func makeArchive(archivePath string, objs []string) error {
}
// Add all object files to the archive.
for i, objfile := range objfiles {
for i, objpath := range objs {
objfile, err := os.Open(objpath)
if err != nil {
return err
}
// 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)
@@ -133,17 +133,17 @@ func makeArchive(archivePath string, objs []string) error {
return err
}
if int64(int32(offset)) != offset {
return errors.New("large archives (4GB+) not supported: " + archivePath)
return errors.New("large archives (4GB+) not supported: " + arfile.Name())
}
objfiles[i].archiveOffset = int32(offset)
archiveOffsets[i] = int32(offset)
// Write the file header.
st, err := objfile.file.Stat()
st, err := objfile.Stat()
if err != nil {
return err
}
err = arwriter.WriteHeader(&ar.Header{
Name: filepath.Base(objfile.file.Name()),
Name: filepath.Base(objfile.Name()),
ModTime: time.Unix(0, 0),
Uid: 0,
Gid: 0,
@@ -155,22 +155,22 @@ func makeArchive(archivePath string, objs []string) error {
}
// Copy the file contents into the archive.
n, err := io.Copy(arwriter, objfile.file)
n, err := io.Copy(arwriter, objfile)
if err != nil {
return err
}
if n != st.Size() {
return errors.New("file modified during ar creation: " + archivePath)
return errors.New("file modified during ar creation: " + arfile.Name())
}
// File is not needed anymore.
objfile.file.Close()
objfile.Close()
}
// Create symbol indices.
indicesBuf := &bytes.Buffer{}
for _, sym := range symbolTable {
err = binary.Write(indicesBuf, binary.BigEndian, objfiles[sym.fileIndex].archiveOffset)
err = binary.Write(indicesBuf, binary.BigEndian, archiveOffsets[sym.fileIndex])
if err != nil {
return err
}
+225 -99
View File
@@ -12,13 +12,16 @@ import (
"errors"
"fmt"
"go/types"
"hash/crc32"
"io/ioutil"
"math/bits"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"github.com/tinygo-org/tinygo/cgo"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/goenv"
@@ -58,6 +61,7 @@ type BuildResult struct {
// implementation of an imported package changes.
type packageAction struct {
ImportPath string
CGoVersion int // cgo.Version
CompilerVersion int // compiler.Version
InterpVersion int // interp.Version
LLVMVersion string
@@ -84,6 +88,38 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
}
defer os.RemoveAll(dir)
// Check for a libc dependency.
// As a side effect, this also creates the headers for the given libc, if
// the libc needs them.
root := goenv.Get("TINYGOROOT")
var libcDependencies []*compileJob
switch config.Target.Libc {
case "musl":
job, err := Musl.load(config, dir)
if err != nil {
return err
}
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o")))
libcDependencies = append(libcDependencies, job)
case "picolibc":
libcJob, err := Picolibc.load(config, dir)
if err != nil {
return err
}
libcDependencies = append(libcDependencies, libcJob)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
libcDependencies = append(libcDependencies, dummyCompileJob(path))
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
optLevel, sizeLevel, _ := config.OptLevels()
compilerConfig := &compiler.Config{
Triple: config.Triple(),
CPU: config.CPU(),
@@ -92,13 +128,14 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
SizeLevel: sizeLevel,
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.Target.DefaultStackSize,
NeedsStackObjects: config.NeedsStackObjects(),
Debug: config.Debug(),
Debug: true,
LLVMFeatures: config.LLVMFeatures(),
}
@@ -122,11 +159,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err
}
// The slice of jobs that orchestrates most of the build.
// This is somewhat like an in-memory Makefile with each job being a
// Makefile target.
var jobs []*compileJob
// Create the *ssa.Program. This does not yet build the entire SSA of the
// program so it's pretty fast and doesn't need to be parallelized.
program := lprogram.LoadSSA()
@@ -136,7 +168,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
var packageJobs []*compileJob
packageBitcodePaths := make(map[string]string)
packageActionIDs := make(map[string]string)
optLevel, sizeLevel, _ := config.OptLevels()
for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition
@@ -150,6 +181,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
// the parameters for the build.
actionID := packageAction{
ImportPath: pkg.ImportPath,
CGoVersion: cgo.Version,
CompilerVersion: compiler.Version,
InterpVersion: interp.Version,
LLVMVersion: llvm.Version,
@@ -210,6 +242,50 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return errors.New("verification error after compiling package " + pkg.ImportPath)
}
// Load bitcode of CGo headers and join the modules together.
// This may seem vulnerable to cache problems, but this is not
// the case: the Go code that was just compiled already tracks
// all C files that are read and hashes them.
// These headers could be compiled in parallel but the benefit
// is so small that it's probably not worth parallelizing.
// Packages are compiled independently anyway.
for _, cgoHeader := range pkg.CGoHeaders {
// Store the header text in a temporary file.
f, err := ioutil.TempFile(dir, "cgosnippet-*.c")
if err != nil {
return err
}
_, err = f.Write([]byte(cgoHeader))
if err != nil {
return err
}
f.Close()
// Compile the code (if there is any) to bitcode.
flags := append([]string{"-c", "-emit-llvm", "-o", f.Name() + ".bc", f.Name()}, pkg.CFlags...)
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("clang", flags...)
}
err = runCCompiler(flags...)
if err != nil {
return &commandError{"failed to build CGo header", "", err}
}
// Load and link the bitcode.
// This makes it possible to optimize the functions defined
// in the header together with the Go code. In particular,
// this allows inlining. It also ensures there is only one
// file per package to cache.
headerMod, err := mod.Context().ParseBitcodeFile(f.Name() + ".bc")
if err != nil {
return fmt.Errorf("failed to load bitcode file: %w", err)
}
err = llvm.LinkModules(mod, headerMod)
if err != nil {
return fmt.Errorf("failed to link module: %w", err)
}
}
// Erase all globals that are part of the undefinedGlobals list.
// This list comes from the -ldflags="-X pkg.foo=val" option.
// Instead of setting the value directly in the AST (which would
@@ -244,16 +320,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return errors.New("verification error after interpreting " + pkgInit.Name())
}
if sizeLevel >= 2 {
// Set the "optsize" attribute to make slightly smaller
// binaries at the cost of some performance.
kind := llvm.AttributeKindID("optsize")
attr := mod.Context().CreateEnumAttribute(kind, 0)
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
fn.AddFunctionAttr(attr)
}
}
// Run function passes for each function in the module.
// These passes are intended to be run on each function right
// after they're created to reduce IR size (and maybe also for
@@ -309,7 +375,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return os.Rename(f.Name(), bitcodePath)
},
}
jobs = append(jobs, job)
packageJobs = append(packageJobs, job)
}
@@ -340,6 +405,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
llvmInitFn := mod.NamedFunction("runtime.initAll")
llvmInitFn.SetLinkage(llvm.InternalLinkage)
llvmInitFn.SetUnnamedAddr(true)
transform.AddStandardAttributes(llvmInitFn, config)
llvmInitFn.Param(0).SetName("context")
llvmInitFn.Param(1).SetName("parentHandle")
block := mod.Context().AddBasicBlock(llvmInitFn, "entry")
@@ -400,14 +466,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return nil
},
}
jobs = append(jobs, programJob)
// Check whether we only need to create an object file.
// If so, we don't need to link anything and will be finished quickly.
outext := filepath.Ext(outpath)
if outext == ".o" || outext == ".bc" || outext == ".ll" {
// Run jobs to produce the LLVM module.
err := runJobs(jobs)
err := runJobs(programJob, config.Options.Parallelism)
if err != nil {
return err
}
@@ -448,7 +513,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
},
}
jobs = append(jobs, outputObjectFileJob)
// Prepare link command.
linkerDependencies := []*compileJob{outputObjectFileJob}
@@ -459,39 +523,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
// Add compiler-rt dependency if needed. Usually this is a simple load from
// a cache.
if config.Target.RTLib == "compiler-rt" {
job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
job, err := CompilerRT.load(config, dir)
if err != nil {
return err
}
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
}
// Add libc dependency if needed.
root := goenv.Get("TINYGOROOT")
switch config.Target.Libc {
case "picolibc":
job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
// The library needs to be compiled (cache miss).
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
ldflags = append(ldflags, path)
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
// Add jobs to compile extra files. These files are in C or assembly and
// contain things like the interrupt vector table and low level operations
// such as stack switching.
@@ -505,7 +543,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err
},
}
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
}
@@ -524,7 +561,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err
},
}
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
}
}
@@ -535,9 +571,48 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
ldflags = append(ldflags, lprogram.LDFlags...)
}
// Add libc dependencies, if they exist.
linkerDependencies = append(linkerDependencies, libcDependencies...)
// Strip debug information with -no-debug.
if !config.Debug() {
for _, tag := range config.BuildTags() {
if tag == "baremetal" {
// Don't use -no-debug on baremetal targets. It makes no sense:
// the debug information isn't flashed to the device anyway.
return fmt.Errorf("stripping debug information is unnecessary for baremetal targets")
}
}
if config.Target.Linker == "wasm-ld" {
// Don't just strip debug information, also compress relocations
// while we're at it. Relocations can only be compressed when debug
// information is stripped.
ldflags = append(ldflags, "--strip-debug", "--compress-relocations")
} else if config.Target.Linker == "ld.lld" {
// ld.lld is also used on Linux.
ldflags = append(ldflags, "--strip-debug")
} else {
switch config.GOOS() {
case "linux":
// Either real linux or an embedded system (like AVR) that
// pretends to be Linux. It's a ELF linker wrapped by GCC in any
// case (not ld.lld - that case is handled above).
ldflags = append(ldflags, "-Wl,--strip-debug")
case "darwin":
// MacOS (darwin) doesn't have a linker flag to strip debug
// information. Apple expects you to use the strip command
// instead.
return errors.New("cannot remove debug information: MacOS doesn't suppor this linker flag")
default:
// Other OSes may have different flags.
return errors.New("cannot remove debug information: unknown OS: " + config.GOOS())
}
}
}
// Create a linker job, which links all object files together and does some
// extra stuff that can only be done after linking.
jobs = append(jobs, &compileJob{
linkJob := &compileJob{
description: "link",
dependencies: linkerDependencies,
run: func(job *compileJob) error {
@@ -547,8 +622,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
}
ldflags = append(ldflags, dependency.result)
}
if config.Options.PrintCommands {
fmt.Printf("%s %s\n", config.Target.Linker, strings.Join(ldflags, " "))
if config.Options.PrintCommands != nil {
config.Options.PrintCommands(config.Target.Linker, ldflags...)
}
err = link(config.Target.Linker, ldflags...)
if err != nil {
@@ -566,6 +641,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err
}
}
// Apply ELF patches
if config.AutomaticStackSize() {
// Modify the .tinygo_stacksizes section that contains a stack size
// for each goroutine.
@@ -574,23 +651,39 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return fmt.Errorf("could not modify stack sizes: %w", err)
}
}
if config.RP2040BootPatch() {
// Patch the second stage bootloader CRC into the .boot2 section
err = patchRP2040BootCRC(executable)
if err != nil {
return fmt.Errorf("could not patch RP2040 second stage boot loader: %w", err)
}
}
// Print code size if requested.
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable)
packagePathMap := make(map[string]string, len(lprogram.Packages))
for _, pkg := range lprogram.Sorted() {
packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
}
sizes, err := loadProgramSize(executable, packagePathMap)
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)
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code+sizes.ROData, sizes.Data, sizes.BSS, sizes.Flash(), sizes.RAM())
} else {
if !config.Debug() {
fmt.Println("warning: data incomplete, remove the -no-debug flag for more detail")
}
fmt.Printf(" code rodata data bss | flash ram | package\n")
fmt.Printf("------------------------------- | --------------- | -------\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)
fmt.Printf("------------------------------- | --------------- | -------\n")
fmt.Printf("%7d %7d %7d %7d | %7d %7d | total\n", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS, sizes.Code+sizes.ROData+sizes.Data, sizes.Data+sizes.BSS)
}
}
@@ -601,12 +694,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return nil
},
})
}
// Run all jobs to compile and link the program.
// Do this now (instead of after elf-to-hex and similar conversions) as it
// is simpler and cannot be parallelized.
err = runJobs(jobs)
err = runJobs(linkJob, config.Options.Parallelism)
if err != nil {
return err
}
@@ -631,7 +724,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
if err != nil {
return err
}
case "esp32", "esp8266":
case "esp32", "esp32c3", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
tmppath = filepath.Join(dir, "main"+outext)
@@ -639,6 +732,18 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
if err != nil {
return err
}
case "nrf-dfu":
// special format for nrfutil for Nordic chips
tmphexpath := filepath.Join(dir, "main.hex")
err := objcopy(executable, tmphexpath, "hex")
if err != nil {
return err
}
tmppath = filepath.Join(dir, "main"+outext)
err = makeDFUFirmwareImage(config.Options, tmphexpath, tmppath)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
}
@@ -686,7 +791,7 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
// stack-allocated values.
// Use -wasm-abi=generic to disable this behaviour.
if config.WasmAbi() == "js" {
err := transform.ExternalInt64AsPtr(mod)
err := transform.ExternalInt64AsPtr(mod, config)
if err != nil {
return err
}
@@ -908,30 +1013,7 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
// stack size information. Before this modification, all stack sizes in the
// section assume the default stack size (which is relatively big).
func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error {
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
if err != nil {
return err
}
defer fp.Close()
elfFile, err := elf.NewFile(fp)
if err != nil {
return err
}
section := elfFile.Section(".tinygo_stacksizes")
if section == nil {
return errors.New("could not find .tinygo_stacksizes section")
}
if section.Size != section.FileSize {
// Sanity check.
return fmt.Errorf("expected .tinygo_stacksizes to have identical size and file size, got %d and %d", section.Size, section.FileSize)
}
// Read all goroutine stack sizes.
data := make([]byte, section.Size)
_, err = fp.ReadAt(data, int64(section.Offset))
data, fileHeader, err := getElfSectionData(executable, ".tinygo_stacksizes")
if err != nil {
return err
}
@@ -953,15 +1035,19 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
if fn.stackSizeType == stacksize.Bounded {
stackSize := uint32(fn.stackSize)
// Adding 4 for the stack canary. Even though the size may be
// automatically determined, stack overflow checking is still
// important as the stack size cannot be determined for all
// goroutines.
stackSize += 4
// Add stack size used by interrupts.
switch elfFile.Machine {
switch fileHeader.Machine {
case elf.EM_ARM:
if stackSize%8 != 0 {
// If the stack isn't a multiple of 8, it means the leaf
// function with the biggest stack depth doesn't have an aligned
// stack. If the STKALIGN flag is set (which it is by default)
// the interrupt controller will forcibly align the stack before
// storing in-use registers. This will thus overwrite one word
// past the end of the stack (off-by-one).
stackSize += 4
}
// On Cortex-M (assumed here), this stack size is 8 words or 32
// bytes. This is only to store the registers that the interrupt
// may modify, the interrupt will switch to the interrupt stack
@@ -969,6 +1055,14 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
// Some background:
// https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching
stackSize += 32
// Adding 4 for the stack canary, and another 4 to keep the
// stack aligned. Even though the size may be automatically
// determined, stack overflow checking is still important as the
// stack size cannot be determined for all goroutines.
stackSize += 8
default:
return fmt.Errorf("unknown architecture: %s", fileHeader.Machine.String())
}
// Finally write the stack size to the binary.
@@ -976,13 +1070,7 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
}
}
// Write back the modified stack sizes.
_, err = fp.WriteAt(data, int64(section.Offset))
if err != nil {
return err
}
return nil
return replaceElfSection(executable, ".tinygo_stacksizes", data)
}
// printStacks prints the maximum stack depth for functions that are started as
@@ -1014,3 +1102,41 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS
}
}
}
// RP2040 second stage bootloader CRC32 calculation
//
// Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf
// Section: 2.8.1.3.1. Checksum
func patchRP2040BootCRC(executable string) error {
bytes, _, err := getElfSectionData(executable, ".boot2")
if err != nil {
return err
}
if len(bytes) != 256 {
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes")
}
// From the 'official' RP2040 checksum script:
//
// Our bootrom CRC32 is slightly bass-ackward but it's
// best to work around for now (FIXME)
// 100% worth it to save two Thumb instructions
revBytes := make([]byte, len(bytes))
for i := range bytes {
revBytes[i] = bits.Reverse8(bytes[i])
}
// crc32.Update does an initial negate and negates the
// result, so to meet RP2040 spec, pass 0x0 as initial
// hash and negate returned value.
//
// Note: checksum is over 252 bytes (256 - 4)
hash := bits.Reverse32(crc32.Update(0x0, crc32.IEEETable, revBytes[:252]) ^ 0xFFFFFFFF)
// Write the CRC to the end of the bootloader.
binary.LittleEndian.PutUint32(bytes[252:], hash)
// Update the .boot2 section to included the CRC
return replaceElfSection(executable, ".boot2", bytes)
}
-105
View File
@@ -1,105 +0,0 @@
package builder
import (
"io"
"os"
"path/filepath"
"time"
"github.com/tinygo-org/tinygo/goenv"
)
// Return the newest timestamp of all the file paths passed in. Used to check
// for stale caches.
func cacheTimestamp(paths []string) (time.Time, error) {
var timestamp time.Time
for _, path := range paths {
st, err := os.Stat(path)
if err != nil {
return time.Time{}, err
}
if timestamp.IsZero() {
timestamp = st.ModTime()
} else if timestamp.Before(st.ModTime()) {
timestamp = st.ModTime()
}
}
return timestamp, nil
}
// Try to load a given file from the cache. Return "", nil if no cached file can
// be found (or the file is stale), return the absolute path if there is a cache
// and return an error on I/O errors.
func cacheLoad(name string, sourceFiles []string) (string, error) {
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
cacheStat, err := os.Stat(cachepath)
if os.IsNotExist(err) {
return "", nil // does not exist
} else if err != nil {
return "", err // cannot stat cache file
}
sourceTimestamp, err := cacheTimestamp(sourceFiles)
if err != nil {
return "", err // cannot stat source files
}
if cacheStat.ModTime().After(sourceTimestamp) {
return cachepath, nil
} else {
os.Remove(cachepath)
// stale cache
return "", nil
}
}
// Store the file located at tmppath in the cache with the given name. The
// tmppath may or may not be gone afterwards.
func cacheStore(tmppath, name string, sourceFiles []string) (string, error) {
// get the last modified time
if len(sourceFiles) == 0 {
panic("cache: no source files")
}
// TODO: check the config key
dir := goenv.Get("GOCACHE")
err := os.MkdirAll(dir, 0777)
if err != nil {
return "", err
}
cachepath := filepath.Join(dir, name)
err = copyFile(tmppath, cachepath)
if err != nil {
return "", err
}
return cachepath, nil
}
// copyFile copies the given file from src to dst. It can copy over
// a possibly already existing file at the destination.
func copyFile(src, dst string) error {
inf, err := os.Open(src)
if err != nil {
return err
}
defer inf.Close()
outpath := dst + ".tmp"
outf, err := os.Create(outpath)
if err != nil {
return err
}
_, err = io.Copy(outf, inf)
if err != nil {
os.Remove(outpath)
return err
}
err = outf.Close()
if err != nil {
return err
}
return os.Rename(dst+".tmp", dst)
}
+146
View File
@@ -0,0 +1,146 @@
package builder
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm"
)
// Test whether the Clang generated "target-cpu" attribute matches the CPU
// property in TinyGo target files.
func TestClangAttributes(t *testing.T) {
var targetNames = []string{
// Please keep this list sorted!
"atmega328p",
"atmega1280",
"atmega1284p",
"atmega2560",
"attiny85",
"cortex-m0",
"cortex-m0plus",
"cortex-m3",
//"cortex-m33", // TODO: broken in LLVM 11, fixed in https://reviews.llvm.org/D90305
"cortex-m4",
"cortex-m7",
"esp32c3",
"fe310",
"gameboy-advance",
"k210",
"nintendoswitch",
"riscv-qemu",
"wasi",
"wasm",
}
if hasBuiltinTools {
// hasBuiltinTools is set when TinyGo is statically linked with LLVM,
// which also implies it was built with Xtensa support.
targetNames = append(targetNames, "esp32", "esp8266")
}
for _, targetName := range targetNames {
targetName := targetName
t.Run(targetName, func(t *testing.T) {
testClangAttributes(t, &compileopts.Options{Target: targetName})
})
}
for _, options := range []*compileopts.Options{
{GOOS: "linux", GOARCH: "386"},
{GOOS: "linux", GOARCH: "amd64"},
{GOOS: "linux", GOARCH: "arm"},
{GOOS: "linux", GOARCH: "arm64"},
{GOOS: "darwin", GOARCH: "amd64"},
{GOOS: "darwin", GOARCH: "arm64"},
} {
t.Run("GOOS="+options.GOOS+",GOARCH="+options.GOARCH, func(t *testing.T) {
testClangAttributes(t, options)
})
}
}
func testClangAttributes(t *testing.T, options *compileopts.Options) {
testDir := t.TempDir()
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
ctx := llvm.NewContext()
defer ctx.Dispose()
target, err := compileopts.LoadTarget(options)
if err != nil {
t.Fatalf("could not load target: %s", err)
}
config := compileopts.Config{
Options: options,
Target: target,
ClangHeaders: clangHeaderPath,
}
// Create a very simple C input file.
srcpath := filepath.Join(testDir, "test.c")
err = ioutil.WriteFile(srcpath, []byte("int add(int a, int b) { return a + b; }"), 0o666)
if err != nil {
t.Fatalf("could not write target file %s: %s", srcpath, err)
}
// Compile this file using Clang.
outpath := filepath.Join(testDir, "test.bc")
flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags()...)
if config.GOOS() == "darwin" {
// Silence some warnings that happen when testing GOOS=darwin on
// something other than MacOS.
flags = append(flags, "-Wno-missing-sysroot", "-Wno-incompatible-sysroot")
}
err = runCCompiler(flags...)
if err != nil {
t.Fatalf("failed to compile %s: %s", srcpath, err)
}
// Read the resulting LLVM bitcode.
mod, err := ctx.ParseBitcodeFile(outpath)
if err != nil {
t.Fatalf("could not parse bitcode file %s: %s", outpath, err)
}
defer mod.Dispose()
// Check whether the LLVM target matches.
if mod.Target() != config.Triple() {
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
}
// Check the "target-cpu" string attribute of the add function.
add := mod.NamedFunction("add")
var cpu string
cpuAttr := add.GetStringAttributeAtIndex(-1, "target-cpu")
if !cpuAttr.IsNil() {
cpu = cpuAttr.GetStringValue()
}
if cpu != config.CPU() {
t.Errorf("target has CPU %#v but Clang makes it CPU %#v", config.CPU(), cpu)
}
}
// This TestMain is necessary because TinyGo may also be invoked to run certain
// LLVM tools in a separate process. Not capturing these invocations would lead
// to recursive tests.
func TestMain(m *testing.M) {
if len(os.Args) >= 2 {
switch os.Args[1] {
case "clang", "ld.lld", "wasm-ld":
// Invoke a specific tool.
err := RunTool(os.Args[1], os.Args[2:]...)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
os.Exit(0)
}
}
// Run normal tests.
os.Exit(m.Run())
}
+5 -3
View File
@@ -157,10 +157,12 @@ var aeabiBuiltins = []string{
//
// For more information, see: https://compiler-rt.llvm.org/
var CompilerRT = Library{
name: "compiler-rt",
cflags: func() []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} },
name: "compiler-rt",
cflags: func(target, headerPath string) []string {
return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"}
},
sourceDir: "lib/compiler-rt/lib/builtins",
sources: func(target string) []string {
librarySources: func(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
builtins = append(builtins, aeabiBuiltins...)
+7 -3
View File
@@ -56,7 +56,7 @@ import (
// depfile but without invalidating its name. For this reason, the depfile is
// written on each new compilation (even when it seems unnecessary). However, it
// could in rare cases lead to a stale file fetched from the cache.
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands bool) (string, error) {
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) {
// Hash input file.
fileHash, err := hashFile(abspath)
if err != nil {
@@ -128,8 +128,8 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
// flags (for the assembler) is a compiler error.
flags = append(flags, "-Qunused-arguments")
}
if printCommands {
fmt.Printf("clang %s\n", strings.Join(flags, " "))
if printCommands != nil {
printCommands("clang", flags...)
}
err = runCCompiler(flags...)
if err != nil {
@@ -155,6 +155,10 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
// Write dependencies file.
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
if err != nil {
return "", err
}
buf, err = json.MarshalIndent(dependencySlice, "", "\t")
if err != nil {
panic(err) // shouldn't happen
+25 -11
View File
@@ -21,6 +21,7 @@ func init() {
commands["clang"] = []string{"clang-" + llvmMajor}
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"}
// Add the path to a Homebrew-installed LLVM for ease of use (no need to
// manually set $PATH).
if runtime.GOOS == "darwin" {
@@ -28,6 +29,7 @@ func init() {
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
commands["lldb"] = append(commands["lldb"], prefix+"lldb")
}
// 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
@@ -36,6 +38,7 @@ func init() {
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")
commands["lldb"] = append(commands["lldb"], "C:\\Program Files\\LLVM\\bin\\lldb.exe")
}
// Add the path to LLVM installed from ports.
if runtime.GOOS == "freebsd" {
@@ -43,23 +46,34 @@ func init() {
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
commands["lldb"] = append(commands["lldb"], prefix+"lldb")
}
}
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()
// LookupCommand looks up the executable name for a given LLVM tool such as
// clang or wasm-ld. It returns the (relative) command that can be used to
// invoke the tool or an error if it could not be found.
func LookupCommand(name string) (string, error) {
for _, cmdName := range commands[name] {
_, err := exec.LookPath(cmdName)
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
if errors.Unwrap(err) == exec.ErrNotFound {
continue
}
return err
return cmdName, err
}
return nil
return cmdName, nil
}
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
return "", errors.New("%#v: none of these commands were found in your $PATH: " + strings.Join(commands[name], " "))
}
func execCommand(name string, args ...string) error {
name, err := LookupCommand(name)
if err != nil {
return err
}
cmd := exec.Command(name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
+3 -3
View File
@@ -13,7 +13,7 @@ import (
// 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)
spec, err := compileopts.LoadTarget(options)
if err != nil {
return nil, err
}
@@ -33,8 +33,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 || minor < 13 || minor > 16 {
return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor)
if major != 1 || minor < 15 || minor > 17 {
return nil, fmt.Errorf("requires go version 1.15 through 1.17, got go%d.%d", major, minor)
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
+57
View File
@@ -0,0 +1,57 @@
package builder
import (
"debug/elf"
"fmt"
"os"
)
func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileHeader, error) {
elfFile, err := elf.Open(executable)
if err != nil {
return nil, elf.FileHeader{}, err
}
defer elfFile.Close()
section := elfFile.Section(sectionName)
if section == nil {
return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName)
}
data, err := section.Data()
return data, elfFile.FileHeader, err
}
func replaceElfSection(executable string, sectionName string, data []byte) error {
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
if err != nil {
return err
}
defer fp.Close()
elfFile, err := elf.Open(executable)
if err != nil {
return err
}
defer elfFile.Close()
section := elfFile.Section(sectionName)
if section == nil {
return fmt.Errorf("could not find %s section", sectionName)
}
// Implicitly check for compressed sections
if section.Size != section.FileSize {
return fmt.Errorf("expected section %s to have identical size and file size, got %d and %d", sectionName, section.Size, section.FileSize)
}
// Only permit complete replacement of section
if section.Size != uint64(len(data)) {
return fmt.Errorf("expected section %s to have size %d, was actually %d", sectionName, len(data), section.Size)
}
// Write the replacement section data
_, err = fp.WriteAt(data, int64(section.Offset))
return err
}
+19 -6
View File
@@ -78,11 +78,21 @@ func makeESPFirmareImage(infile, outfile, format string) error {
// An added benefit is that we don't need to check for errors all the time.
outf := &bytes.Buffer{}
// Chip IDs. Source:
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22
chip_id := map[string]uint16{
"esp32": 0x0000,
"esp32c3": 0x0005,
}[format]
// Image header.
switch format {
case "esp32":
case "esp32", "esp32c3":
// Header format:
// https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
// Note: not adding a SHA256 hash as the binary is modified by
// esptool.py while flashing and therefore the hash won't be valid
// anymore.
binary.Write(outf, binary.LittleEndian, struct {
magic uint8
segment_count uint8
@@ -91,15 +101,18 @@ func makeESPFirmareImage(infile, outfile, format string) error {
entry_addr uint32
wp_pin uint8
spi_pin_drv [3]uint8
reserved [11]uint8
chip_id uint16
min_chip_rev uint8
reserved [8]uint8
hash_appended bool
}{
magic: 0xE9,
segment_count: byte(len(segments)),
spi_mode: 0, // irrelevant, replaced by esptool when flashing
spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
entry_addr: uint32(inf.Entry),
wp_pin: 0xEE, // disable WP pin
chip_id: chip_id,
hash_appended: true, // add a SHA256 hash
})
case "esp8266":
@@ -142,7 +155,7 @@ func makeESPFirmareImage(infile, outfile, format string) error {
outf.Write(make([]byte, 15-outf.Len()%16))
outf.WriteByte(checksum)
if format == "esp32" {
if format != "esp8266" {
// SHA256 hash (to protect against image corruption, not for security).
hash := sha256.Sum256(outf.Bytes())
outf.Write(hash[:])
+34 -8
View File
@@ -65,19 +65,45 @@ func (job *compileJob) readyToRun() bool {
return true
}
// runJobs runs all the jobs indicated in the jobs slice and returns the error
// of the first job that fails to run.
// It runs all jobs in the order of the slice, as long as all dependencies have
// already run. Therefore, if some jobs are preferred to run before others, they
// should be ordered as such in this slice.
func runJobs(jobs []*compileJob) error {
// runJobs runs the indicated job and all its dependencies. For every job, all
// the dependencies are run first. It returns the error of the first job that
// fails.
// It runs all jobs in the order of the dependencies slice, depth-first.
// Therefore, if some jobs are preferred to run before others, they should be
// ordered as such in the job dependencies.
func runJobs(job *compileJob, parallelism int) error {
if parallelism == 0 {
// Have a default, if the parallelism isn't set. This is useful for
// tests.
parallelism = runtime.NumCPU()
}
if parallelism < 1 {
return fmt.Errorf("-p flag must be at least 1, provided -p=%d", parallelism)
}
// Create a slice of jobs to run, where all dependencies are run in order.
jobs := []*compileJob{}
addedJobs := map[*compileJob]struct{}{}
var addJobs func(*compileJob)
addJobs = func(job *compileJob) {
if _, ok := addedJobs[job]; ok {
return
}
for _, dep := range job.dependencies {
addJobs(dep)
}
jobs = append(jobs, job)
addedJobs[job] = struct{}{}
}
addJobs(job)
// Create channels to communicate with the workers.
doneChan := make(chan *compileJob)
workerChan := make(chan *compileJob)
defer close(workerChan)
// Start a number of workers.
for i := 0; i < runtime.NumCPU(); i++ {
for i := 0; i < parallelism; i++ {
if jobRunnerDebug {
fmt.Println("## starting worker", i)
}
@@ -92,7 +118,7 @@ func runJobs(jobs []*compileJob) error {
for {
// If there are free workers, try starting a new job (if one is
// available). If it succeeds, try again to fill the entire worker pool.
if numRunningJobs < runtime.NumCPU() {
if numRunningJobs < parallelism {
jobToRun := nextJob(jobs)
if jobToRun != nil {
// Start job.
+113 -51
View File
@@ -1,10 +1,12 @@
package builder
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
@@ -14,72 +16,87 @@ type Library struct {
// The library name, such as compiler-rt or picolibc.
name string
cflags func() []string
// makeHeaders creates a header include dir for the library
makeHeaders func(target, includeDir string) error
// cflags returns the C flags specific to this library
cflags func(target, headerPath string) []string
// The source directory, relative to TINYGOROOT.
sourceDir string
// The source files, relative to sourceDir.
sources func(target string) []string
}
librarySources func(target string) []string
// fullPath returns the full path to the source directory.
func (l *Library) fullPath() string {
return filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir)
}
// sourcePaths returns a slice with the full paths to the source files.
func (l *Library) sourcePaths(target string) []string {
sources := l.sources(target)
paths := make([]string, len(sources))
for i, name := range sources {
paths[i] = filepath.Join(l.fullPath(), name)
}
return paths
// The source code for the crt1.o file, relative to sourceDir.
crt1Source string
}
// Load the library archive, possibly generating and caching it if needed.
// The resulting file is stored in the provided tmpdir, which is expected to be
// removed after the Load call.
func (l *Library) Load(target, tmpdir string) (path string, err error) {
job, err := l.load(target, "", tmpdir)
// The resulting directory may be stored in the provided tmpdir, which is
// expected to be removed after the Load call.
func (l *Library) Load(config *compileopts.Config, tmpdir string) (dir string, err error) {
job, err := l.load(config, tmpdir)
if err != nil {
return "", err
}
jobs := append([]*compileJob{job}, job.dependencies...)
err = runJobs(jobs)
return job.result, err
err = runJobs(job, config.Options.Parallelism)
return filepath.Dir(job.result), err
}
// load returns a compile job to build this library file for the given target
// and CPU. It may return a dummy compileJob if the library build is already
// cached. The path is stored as job.result but is only valid if the job and
// job.dependencies have been run.
// cached. The path is stored as job.result but is only valid after the job has
// been run.
// The provided tmpdir will be used to store intermediary files and possibly the
// output archive file, it is expected to be removed after use.
func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error) {
// Try to load a precompiled library.
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
if _, err := os.Stat(precompiledPath); err == nil {
// As a side effect, this call creates the library header files if they didn't
// exist yet.
func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, err error) {
outdir, precompiled := config.LibcPath(l.name)
archiveFilePath := filepath.Join(outdir, "lib.a")
if precompiled {
// Found a precompiled library for this OS/architecture. Return the path
// directly.
return dummyCompileJob(precompiledPath), nil
}
var outfile string
if cpu != "" {
outfile = l.name + "-" + target + "-" + cpu + ".a"
} else {
outfile = l.name + "-" + target + ".a"
return dummyCompileJob(archiveFilePath), nil
}
// Try to fetch this library from the cache.
if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil {
// Cache hit.
return dummyCompileJob(path), nil
if _, err := os.Stat(archiveFilePath); err == nil {
return dummyCompileJob(archiveFilePath), nil
}
// Cache miss, build it now.
// Create the destination directory where the components of this library
// (lib.a file, include directory) are placed.
outname := filepath.Base(outdir)
err = os.MkdirAll(filepath.Join(goenv.Get("GOCACHE"), outname), 0o777)
if err != nil {
// Could not create directory (and not because it already exists).
return nil, err
}
// Make headers if needed.
headerPath := filepath.Join(outdir, "include")
target := config.Triple()
if l.makeHeaders != nil {
if _, err = os.Stat(headerPath); err != nil {
temporaryHeaderPath, err := ioutil.TempDir(outdir, "include.tmp*")
if err != nil {
return nil, err
}
defer os.RemoveAll(temporaryHeaderPath)
err = l.makeHeaders(target, temporaryHeaderPath)
if err != nil {
return nil, err
}
err = os.Rename(temporaryHeaderPath, headerPath)
if err != nil {
return nil, err
}
}
}
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
err = os.Mkdir(dir, 0777)
@@ -91,9 +108,16 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err 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.
args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
cpu := config.CPU()
if cpu != "" {
args = append(args, "-mcpu="+cpu)
// X86 has deprecated the -mcpu flag, so we need to use -march instead.
// However, ARM has not done this.
if strings.HasPrefix(target, "i386") || strings.HasPrefix(target, "x86_64") {
args = append(args, "-march="+cpu)
} else {
args = append(args, "-mcpu="+cpu)
}
}
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
@@ -108,27 +132,39 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
// Create job to put all the object files in a single archive. This archive
// file is the (static) library file.
var objs []string
arpath := filepath.Join(dir, l.name+".a")
job = &compileJob{
description: "ar " + l.name + ".a",
result: arpath,
description: "ar " + l.name + "/lib.a",
result: filepath.Join(goenv.Get("GOCACHE"), outname, "lib.a"),
run: func(*compileJob) error {
// Create an archive of all object files.
err := makeArchive(arpath, objs)
f, err := ioutil.TempFile(outdir, "libc.a.tmp*")
if err != nil {
return err
}
err = makeArchive(f, objs)
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
// Store this archive in the cache.
_, err = cacheStore(arpath, outfile, l.sourcePaths(target))
return err
return os.Rename(f.Name(), archiveFilePath)
},
}
// Create jobs to compile all sources. These jobs are depended upon by the
// archive job above, so must be run first.
for _, srcpath := range l.sourcePaths(target) {
srcpath := srcpath // avoid concurrency issues by redefining inside the loop
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
for _, path := range l.librarySources(target) {
// Strip leading "../" parts off the path.
cleanpath := path
for strings.HasPrefix(cleanpath, "../") {
cleanpath = cleanpath[3:]
}
srcpath := filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir, path)
objpath := filepath.Join(dir, cleanpath+".o")
os.MkdirAll(filepath.Dir(objpath), 0o777)
objs = append(objs, objpath)
job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath,
@@ -145,5 +181,31 @@ func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error)
})
}
// Create crt1.o job, if needed.
// Add this as a (fake) dependency to the ar file so it gets compiled.
// (It could be done in parallel with creating the ar file, but it probably
// won't make much of a difference in speed).
if l.crt1Source != "" {
srcpath := filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir, l.crt1Source)
job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath,
run: func(*compileJob) error {
var compileArgs []string
compileArgs = append(compileArgs, args...)
tmpfile, err := ioutil.TempFile(outdir, "crt1.o.tmp*")
if err != nil {
return err
}
tmpfile.Close()
compileArgs = append(compileArgs, "-o", tmpfile.Name(), srcpath)
err = runCCompiler(compileArgs...)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o"))
},
})
}
return job, nil
}
+162
View File
@@ -0,0 +1,162 @@
package builder
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
var Musl = Library{
name: "musl",
makeHeaders: func(target, includeDir string) error {
bits := filepath.Join(includeDir, "bits")
err := os.Mkdir(bits, 0777)
if err != nil {
return err
}
arch := compileopts.MuslArchitecture(target)
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl")
// Create the file alltypes.h.
f, err := os.Create(filepath.Join(bits, "alltypes.h"))
if err != nil {
return err
}
infiles := []string{
filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"),
filepath.Join(muslDir, "include", "alltypes.h.in"),
}
for _, infile := range infiles {
data, err := ioutil.ReadFile(infile)
if err != nil {
return err
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "TYPEDEF ") {
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
value := matches[1]
name := matches[2]
line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name)
}
if strings.HasPrefix(line, "STRUCT ") {
matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line)
name := matches[1]
value := matches[2]
line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name)
}
f.WriteString(line + "\n")
}
}
f.Close()
// Create the file syscall.h.
f, err = os.Create(filepath.Join(bits, "syscall.h"))
if err != nil {
return err
}
data, err := ioutil.ReadFile(filepath.Join(muslDir, "arch", arch, "bits", "syscall.h.in"))
if err != nil {
return err
}
_, err = f.Write(bytes.ReplaceAll(data, []byte("__NR_"), []byte("SYS_")))
if err != nil {
return err
}
f.Close()
return nil
},
cflags: func(target, headerPath string) []string {
arch := compileopts.MuslArchitecture(target)
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl")
return []string{
"-std=c99", // same as in musl
"-D_XOPEN_SOURCE=700", // same as in musl
// Musl triggers some warnings and we don't want to show any
// warnings while compiling (only errors or silence), so disable
// specific warnings that are triggered in musl.
"-Werror",
"-Wno-logical-op-parentheses",
"-Wno-bitwise-op-parentheses",
"-Wno-shift-op-parentheses",
"-Wno-ignored-attributes",
"-Wno-string-plus-int",
"-Qunused-arguments",
// Select include dirs. Don't include standard library includes
// (that would introduce host dependencies and other complications),
// but do include all the include directories expected by musl.
"-nostdlibinc",
"-I" + muslDir + "/arch/" + arch,
"-I" + muslDir + "/arch/generic",
"-I" + muslDir + "/src/include",
"-I" + muslDir + "/src/internal",
"-I" + headerPath,
"-I" + muslDir + "/include",
}
},
sourceDir: "lib/musl/src",
librarySources: func(target string) []string {
arch := compileopts.MuslArchitecture(target)
globs := []string{
"env/*.c",
"errno/*.c",
"exit/*.c",
"internal/defsysinfo.c",
"internal/libc.c",
"internal/syscall_ret.c",
"internal/vdso.c",
"malloc/*.c",
"mman/*.c",
"signal/*.c",
"stdio/*.c",
"string/*.c",
"thread/" + arch + "/*.s",
"thread/" + arch + "/*.c",
"thread/*.c",
"time/*.c",
"unistd/*.c",
}
var sources []string
seenSources := map[string]struct{}{}
basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
for _, pattern := range globs {
matches, err := filepath.Glob(basepath + pattern)
if err != nil {
// From the documentation:
// > Glob ignores file system errors such as I/O errors reading
// > directories. The only possible returned error is
// > ErrBadPattern, when pattern is malformed.
// So the only possible error is when the (statically defined)
// pattern is wrong. In other words, a programming bug.
panic("could not glob source dirs: " + err.Error())
}
for _, match := range matches {
relpath, err := filepath.Rel(basepath, match)
if err != nil {
// Not sure if this is even possible.
panic(err)
}
// Make sure architecture specific files override generic files.
id := strings.ReplaceAll(relpath, "/"+arch+"/", "/")
if _, ok := seenSources[id]; ok {
// Already seen this file, skipping this (generic) file.
continue
}
seenSources[id] = struct{}{}
sources = append(sources, relpath)
}
}
return sources
},
crt1Source: "../crt/crt1.c", // lib/musl/crt/crt1.c
}
+27
View File
@@ -0,0 +1,27 @@
package builder
import (
"fmt"
"io/ioutil"
"os/exec"
"github.com/tinygo-org/tinygo/compileopts"
)
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
if options.PrintCommands != nil {
options.PrintCommands(cmdLine[0], cmdLine[1:]...)
}
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
cmd.Stdout = ioutil.Discard
err := cmd.Run()
if err != nil {
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
}
return nil
}
+121 -3
View File
@@ -1,6 +1,7 @@
package builder
import (
"os"
"path/filepath"
"github.com/tinygo-org/tinygo/goenv"
@@ -10,17 +11,134 @@ import (
// based on newlib.
var Picolibc = Library{
name: "picolibc",
cflags: func() []string {
makeHeaders: func(target, includeDir string) error {
f, err := os.Create(filepath.Join(includeDir, "picolibc.h"))
if err != nil {
return err
}
return f.Close()
},
cflags: func(target, headerPath string) []string {
picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc")
return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"}
return []string{
"-Werror",
"-Wall",
"-std=gnu11",
"-D_COMPILING_NEWLIB",
"-DHAVE_ALIAS_ATTRIBUTE",
"-DTINY_STDIO",
"-nostdlibinc",
"-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include",
"-I" + picolibcDir + "/tinystdio",
"-I" + headerPath,
}
},
sourceDir: "lib/picolibc/newlib/libc",
sources: func(target string) []string {
librarySources: func(target string) []string {
return picolibcSources
},
}
var picolibcSources = []string{
"../../../picolibc-stdio.c",
"tinystdio/asprintf.c",
"tinystdio/atod_engine.c",
"tinystdio/atod_ryu.c",
"tinystdio/atof_engine.c",
"tinystdio/atof_ryu.c",
//"tinystdio/atold_engine.c", // have_long_double and not long_double_equals_double
"tinystdio/clearerr.c",
"tinystdio/compare_exchange.c",
"tinystdio/dtoa_data.c",
"tinystdio/dtoa_engine.c",
"tinystdio/dtoa_ryu.c",
"tinystdio/ecvtbuf.c",
"tinystdio/ecvt.c",
"tinystdio/ecvt_data.c",
"tinystdio/ecvtfbuf.c",
"tinystdio/ecvtf.c",
"tinystdio/ecvtf_data.c",
"tinystdio/exchange.c",
//"tinystdio/fclose.c", // posix-io
"tinystdio/fcvtbuf.c",
"tinystdio/fcvt.c",
"tinystdio/fcvtfbuf.c",
"tinystdio/fcvtf.c",
"tinystdio/fdevopen.c",
//"tinystdio/fdopen.c", // posix-io
"tinystdio/feof.c",
"tinystdio/ferror.c",
"tinystdio/fflush.c",
"tinystdio/fgetc.c",
"tinystdio/fgets.c",
"tinystdio/fileno.c",
"tinystdio/filestrget.c",
"tinystdio/filestrputalloc.c",
"tinystdio/filestrput.c",
//"tinystdio/fopen.c", // posix-io
"tinystdio/fprintf.c",
"tinystdio/fputc.c",
"tinystdio/fputs.c",
"tinystdio/fread.c",
"tinystdio/fscanf.c",
"tinystdio/fseek.c",
"tinystdio/ftell.c",
"tinystdio/ftoa_data.c",
"tinystdio/ftoa_engine.c",
"tinystdio/ftoa_ryu.c",
"tinystdio/fwrite.c",
"tinystdio/gcvtbuf.c",
"tinystdio/gcvt.c",
"tinystdio/gcvtfbuf.c",
"tinystdio/gcvtf.c",
"tinystdio/getchar.c",
"tinystdio/gets.c",
"tinystdio/matchcaseprefix.c",
"tinystdio/perror.c",
//"tinystdio/posixiob.c", // posix-io
//"tinystdio/posixio.c", // posix-io
"tinystdio/printf.c",
"tinystdio/putchar.c",
"tinystdio/puts.c",
"tinystdio/ryu_divpow2.c",
"tinystdio/ryu_log10.c",
"tinystdio/ryu_log2pow5.c",
"tinystdio/ryu_pow5bits.c",
"tinystdio/ryu_table.c",
"tinystdio/ryu_umul128.c",
"tinystdio/scanf.c",
"tinystdio/setbuf.c",
"tinystdio/setvbuf.c",
//"tinystdio/sflags.c", // posix-io
"tinystdio/snprintf.c",
"tinystdio/snprintfd.c",
"tinystdio/snprintff.c",
"tinystdio/sprintf.c",
"tinystdio/sprintfd.c",
"tinystdio/sprintff.c",
"tinystdio/sscanf.c",
"tinystdio/strfromd.c",
"tinystdio/strfromf.c",
"tinystdio/strtod.c",
"tinystdio/strtod_l.c",
"tinystdio/strtof.c",
//"tinystdio/strtold.c", // have_long_double and not long_double_equals_double
//"tinystdio/strtold_l.c", // have_long_double and not long_double_equals_double
"tinystdio/ungetc.c",
"tinystdio/vasprintf.c",
"tinystdio/vfiprintf.c",
"tinystdio/vfiscanf.c",
"tinystdio/vfprintf.c",
"tinystdio/vfprintff.c",
"tinystdio/vfscanf.c",
"tinystdio/vfscanff.c",
"tinystdio/vprintf.c",
"tinystdio/vscanf.c",
"tinystdio/vsnprintf.c",
"tinystdio/vsprintf.c",
"tinystdio/vsscanf.c",
"string/bcmp.c",
"string/bcopy.c",
"string/bzero.c",
+524 -109
View File
@@ -1,16 +1,30 @@
package builder
import (
"bytes"
"debug/dwarf"
"debug/elf"
"encoding/binary"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/aykevl/go-wasm"
"github.com/tinygo-org/tinygo/goenv"
)
// Set to true to print extra debug logs.
const sizesDebug = false
// programSize contains size statistics per package of a compiled program.
type programSize struct {
Packages map[string]*packageSize
Sum *packageSize
Packages map[string]packageSize
Code uint64
ROData uint64
Data uint64
BSS uint64
}
@@ -26,6 +40,16 @@ func (ps *programSize) sortedPackageNames() []string {
return names
}
// Flash usage in regular microcontrollers.
func (ps *programSize) Flash() uint64 {
return ps.Code + ps.ROData + ps.Data
}
// Static RAM usage in regular microcontrollers.
func (ps *programSize) RAM() uint64 {
return ps.Data + ps.BSS
}
// packageSize contains the size of a package, calculated from the linked object
// file.
type packageSize struct {
@@ -45,129 +69,520 @@ func (ps *packageSize) RAM() uint64 {
return ps.Data + ps.BSS
}
type symbolList []elf.Symbol
func (l symbolList) Len() int {
return len(l)
// A mapping of a single chunk of code or data to a file path.
type addressLine struct {
Address uint64
Length uint64 // length of this chunk
File string // file path as stored in DWARF
IsVariable bool // true if this is a variable (or constant), false if it is code
}
func (l symbolList) Less(i, j int) bool {
bind_i := elf.ST_BIND(l[i].Info)
bind_j := elf.ST_BIND(l[j].Info)
if l[i].Value == l[j].Value && bind_i != elf.STB_WEAK && bind_j == elf.STB_WEAK {
// sort weak symbols after non-weak symbols
return true
// Sections defined in the input file. This struct defines them in a
// filetype-agnostic way but roughly follow the ELF types (.text, .data, .bss,
// etc).
type memorySection struct {
Type memoryType
Address uint64
Size uint64
}
type memoryType int
const (
memoryCode memoryType = iota + 1
memoryData
memoryROData
memoryBSS
memoryStack
)
// Regular expressions to match particular symbol names. These are not stored as
// DWARF variables because they have no mapping to source code global variables.
var (
// Various globals that aren't a variable but nonetheless need to be stored
// somewhere:
// alloc: heap allocations during init interpretation
// pack: data created when storing a constant in an interface for example
// string: buffer behind strings
packageSymbolRegexp = regexp.MustCompile(`\$(alloc|pack|string)(\.[0-9]+)?$`)
// Reflect sidetables. Created by the reflect lowering pass.
// See src/reflect/sidetables.go.
reflectDataRegexp = regexp.MustCompile(`^reflect\.[a-zA-Z]+Sidetable$`)
)
// readProgramSizeFromDWARF reads the source location for each line of code and
// each variable in the program, as far as this is stored in the DWARF debug
// information.
func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset uint64) ([]addressLine, error) {
r := data.Reader()
var lines []*dwarf.LineFile
var addresses []addressLine
for {
e, err := r.Next()
if err != nil {
return nil, err
}
if e == nil {
break
}
switch e.Tag {
case dwarf.TagCompileUnit:
// Found a compile unit.
// We can read the .debug_line section using it, which contains a
// mapping for most instructions to their file/line/column - even
// for inlined functions!
lr, err := data.LineReader(e)
if err != nil {
return nil, err
}
lines = lr.Files()
var lineEntry = dwarf.LineEntry{
EndSequence: true,
}
// Line tables are organized as sequences of line entries until an
// end sequence. A single line table can contain multiple such
// sequences. The last line entry is an EndSequence to indicate the
// end.
for {
// Read the next .debug_line entry.
prevLineEntry := lineEntry
err := lr.Next(&lineEntry)
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
if prevLineEntry.EndSequence && lineEntry.Address == 0 {
// Tombstone value. This symbol has been removed, for
// example by the --gc-sections linker flag. It is still
// here in the debug information because the linker can't
// just remove this reference.
// Read until the next EndSequence so that this sequence is
// skipped.
// For more details, see (among others):
// https://reviews.llvm.org/D84825
for {
err := lr.Next(&lineEntry)
if err != nil {
return nil, err
}
if lineEntry.EndSequence {
break
}
}
}
if !prevLineEntry.EndSequence {
// The chunk describes the code from prevLineEntry to
// lineEntry.
line := addressLine{
Address: prevLineEntry.Address + codeOffset,
Length: lineEntry.Address - prevLineEntry.Address,
File: prevLineEntry.File.Name,
}
if line.Length != 0 {
addresses = append(addresses, line)
}
}
}
case dwarf.TagVariable:
// Global variable (or constant). Most of these are not actually
// stored in the binary, because they have been optimized out. Only
// the ones with a location are still present.
r.SkipChildren()
file := e.AttrField(dwarf.AttrDeclFile)
location := e.AttrField(dwarf.AttrLocation)
globalType := e.AttrField(dwarf.AttrType)
if file == nil || location == nil || globalType == nil {
// Doesn't contain the requested information.
continue
}
// Try to parse the location. While this could in theory be a very
// complex expression, usually it's just a DW_OP_addr opcode
// followed by an address.
locationCode := location.Val.([]uint8)
if locationCode[0] != 3 { // DW_OP_addr
continue
}
var addr uint64
switch len(locationCode) {
case 1 + 2:
addr = uint64(binary.LittleEndian.Uint16(locationCode[1:]))
case 1 + 4:
addr = uint64(binary.LittleEndian.Uint32(locationCode[1:]))
case 1 + 8:
addr = binary.LittleEndian.Uint64(locationCode[1:])
default:
continue // unknown address
}
// Parse the type of the global variable, which (importantly)
// contains the variable size. We're not interested in the type,
// only in the size.
typ, err := data.Type(globalType.Val.(dwarf.Offset))
if err != nil {
return nil, err
}
addresses = append(addresses, addressLine{
Address: addr,
Length: uint64(typ.Size()),
File: lines[file.Val.(int64)].Name,
IsVariable: true,
})
default:
r.SkipChildren()
}
}
return l[i].Value < l[j].Value
}
func (l symbolList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
return addresses, nil
}
// 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 the file doesn't contain DWARF debug information, the returned program
// size will still have valid summaries but won't have complete size information
// per package.
func loadProgramSize(path string, packagePathMap map[string]string) (*programSize, error) {
// Open the binary file.
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
defer f.Close()
var sumCode uint64
var sumData uint64
var sumBSS uint64
for _, section := range file.Sections {
if section.Flags&elf.SHF_ALLOC == 0 {
continue
}
if section.Type != elf.SHT_PROGBITS && section.Type != elf.SHT_NOBITS {
continue
}
if section.Name == ".stack" {
// HACK: this works around a bug in ld.lld from LLVM 10. The linker
// marks sections with no input symbols (such as is the case for the
// .stack section) as SHT_PROGBITS instead of SHT_NOBITS. While it
// doesn't affect the generated binaries (.hex and .bin), it does
// affect the reported size.
// https://bugs.llvm.org/show_bug.cgi?id=45336
// https://reviews.llvm.org/D76981
// It has been merged in master, but it has not (yet) been
// backported to the LLVM 10 release branch.
sumBSS += section.Size
} else if section.Type == elf.SHT_NOBITS {
sumBSS += section.Size
} else if section.Flags&elf.SHF_EXECINSTR != 0 {
sumCode += section.Size
} else if section.Flags&elf.SHF_WRITE != 0 {
sumData += section.Size
}
}
// This stores all chunks of addresses found in the binary.
var addresses []addressLine
allSymbols, err := file.Symbols()
if err != nil {
return nil, err
}
symbols := make([]elf.Symbol, 0, len(allSymbols))
for _, symbol := range allSymbols {
symType := elf.ST_TYPE(symbol.Info)
if symbol.Size == 0 {
continue
}
if symType != elf.STT_FUNC && symType != elf.STT_OBJECT && symType != elf.STT_NOTYPE {
continue
}
if symbol.Section >= elf.SectionIndex(len(file.Sections)) {
continue
}
section := file.Sections[symbol.Section]
if section.Flags&elf.SHF_ALLOC == 0 {
continue
}
symbols = append(symbols, symbol)
}
sort.Sort(symbolList(symbols))
sizes := map[string]*packageSize{}
var lastSymbolValue uint64
for _, symbol := range symbols {
symType := elf.ST_TYPE(symbol.Info)
//bind := elf.ST_BIND(symbol.Info)
section := file.Sections[symbol.Section]
pkgName := "(bootstrap)"
symName := strings.TrimLeft(symbol.Name, "(*")
dot := strings.IndexByte(symName, '.')
if dot > 0 {
pkgName = symName[:dot]
}
pkgSize := sizes[pkgName]
if pkgSize == nil {
pkgSize = &packageSize{}
sizes[pkgName] = pkgSize
}
if lastSymbolValue != symbol.Value || lastSymbolValue == 0 {
if symType == elf.STT_FUNC {
pkgSize.Code += symbol.Size
} else if section.Flags&elf.SHF_WRITE != 0 {
if section.Type == elf.SHT_NOBITS {
pkgSize.BSS += symbol.Size
} else {
pkgSize.Data += symbol.Size
}
} else {
pkgSize.ROData += symbol.Size
// Load the binary file, which could be in a number of file formats.
var sections []memorySection
if file, err := elf.NewFile(f); err == nil {
// Read DWARF information. The error is intentionally ignored.
data, _ := file.DWARF()
if data != nil {
addresses, err = readProgramSizeFromDWARF(data, 0)
if err != nil {
// However, _do_ report an error here. Something must have gone
// wrong while trying to parse DWARF data.
return nil, err
}
}
lastSymbolValue = symbol.Value
// Read the ELF symbols for some more chunks of location information.
// Some globals (such as strings) aren't stored in the DWARF debug
// information and therefore need to be obtained in a different way.
allSymbols, err := file.Symbols()
if err != nil {
return nil, err
}
for _, symbol := range allSymbols {
symType := elf.ST_TYPE(symbol.Info)
if symbol.Size == 0 {
continue
}
if symType != elf.STT_FUNC && symType != elf.STT_OBJECT && symType != elf.STT_NOTYPE {
continue
}
section := file.Sections[symbol.Section]
if section.Flags&elf.SHF_ALLOC == 0 {
continue
}
if packageSymbolRegexp.MatchString(symbol.Name) || reflectDataRegexp.MatchString(symbol.Name) {
addresses = append(addresses, addressLine{
Address: symbol.Value,
Length: symbol.Size,
File: symbol.Name,
IsVariable: true,
})
}
}
// Load allocated sections.
for _, section := range file.Sections {
if section.Flags&elf.SHF_ALLOC == 0 {
continue
}
if section.Type == elf.SHT_NOBITS {
if section.Name == ".stack" {
// TinyGo emits stack sections on microcontroller using the
// ".stack" name.
// This is a bit ugly, but I don't think there is a way to
// mark the stack section in a linker script.
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Type: memoryStack,
})
} else {
// Regular .bss section.
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Type: memoryBSS,
})
}
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_EXECINSTR != 0 {
// .text
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Type: memoryCode,
})
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_WRITE != 0 {
// .data
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Type: memoryData,
})
} else if section.Type == elf.SHT_PROGBITS {
// .rodata
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Type: memoryROData,
})
}
}
} else if file, err := wasm.Parse(f); err == nil {
// File is in WebAssembly format.
// Put code at a very high address, so that it won't conflict with the
// data in the memory section.
const codeOffset = 0x8000_0000_0000_0000
// Read DWARF information. The error is intentionally ignored.
data, err := file.DWARF()
if data != nil {
addresses, err = readProgramSizeFromDWARF(data, codeOffset)
if err != nil {
// However, _do_ report an error here. Something must have gone
// wrong while trying to parse DWARF data.
return nil, err
}
}
var linearMemorySize uint64
for _, section := range file.Sections {
switch section := section.(type) {
case *wasm.SectionCode:
sections = append(sections, memorySection{
Address: codeOffset,
Size: uint64(section.Size()),
Type: memoryCode,
})
case *wasm.SectionMemory:
// This value is used when processing *wasm.SectionData (which
// always comes after *wasm.SectionMemory).
linearMemorySize = uint64(section.Entries[0].Limits.Initial) * 64 * 1024
case *wasm.SectionData:
// Data sections contain initial values for linear memory.
// First load the list of data sections, and sort them by
// address for easier processing.
var dataSections []memorySection
for _, entry := range section.Entries {
address, err := wasm.Eval(bytes.NewBuffer(entry.Offset))
if err != nil {
return nil, fmt.Errorf("could not parse data section address: %w", err)
}
dataSections = append(dataSections, memorySection{
Address: uint64(address[0].(int32)),
Size: uint64(len(entry.Data)),
Type: memoryData,
})
}
sort.Slice(dataSections, func(i, j int) bool {
return dataSections[i].Address < dataSections[j].Address
})
// And now add all data sections for linear memory.
// Parts that are in the slice of data sections are added as
// memoryData, and parts that are not are added as memoryBSS.
addr := uint64(0)
for _, section := range dataSections {
if addr < section.Address {
sections = append(sections, memorySection{
Address: addr,
Size: section.Address - addr,
Type: memoryBSS,
})
}
if addr > section.Address {
// This might be allowed, I'm not sure.
// It certainly doesn't make a lot of sense.
return nil, fmt.Errorf("overlapping data section")
}
// addr == section.Address
sections = append(sections, section)
addr = section.Address + section.Size
}
if addr < linearMemorySize {
sections = append(sections, memorySection{
Address: addr,
Size: linearMemorySize - addr,
Type: memoryBSS,
})
}
}
}
} else {
return nil, fmt.Errorf("could not parse file: %w", err)
}
sum := &packageSize{}
// Sort the slice of address chunks by address, so that we can iterate
// through it to calculate section sizes.
sort.Slice(addresses, func(i, j int) bool {
if addresses[i].Address == addresses[j].Address {
// Very rarely, there might be duplicate addresses.
// If that happens, sort the largest chunks first.
return addresses[i].Length > addresses[j].Length
}
return addresses[i].Address < addresses[j].Address
})
// Now finally determine the binary/RAM size usage per package by going
// through each allocated section.
sizes := make(map[string]packageSize)
for _, section := range sections {
switch section.Type {
case memoryCode:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
if isVariable {
field.ROData += size
} else {
field.Code += size
}
sizes[path] = field
}, packagePathMap)
case memoryROData:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.ROData += size
sizes[path] = field
}, packagePathMap)
case memoryData:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.Data += size
sizes[path] = field
}, packagePathMap)
case memoryBSS:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.BSS += size
sizes[path] = field
}, packagePathMap)
case memoryStack:
// We store the C stack as a pseudo-package.
sizes["C stack"] = packageSize{
BSS: section.Size,
}
}
}
// ...and summarize the results.
program := &programSize{
Packages: sizes,
}
for _, pkg := range sizes {
sum.Code += pkg.Code
sum.ROData += pkg.ROData
sum.Data += pkg.Data
sum.BSS += pkg.BSS
program.Code += pkg.Code
program.ROData += pkg.ROData
program.Data += pkg.Data
program.BSS += pkg.BSS
}
return &programSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
return program, nil
}
// readSection determines for each byte in this section to which package it
// belongs. It reports this usage through the addSize callback.
func readSection(section memorySection, addresses []addressLine, addSize func(string, uint64, bool), packagePathMap map[string]string) {
// The addr variable tracks at which address we are while going through this
// section. We start at the beginning.
addr := section.Address
sectionEnd := section.Address + section.Size
for _, line := range addresses {
if line.Address < section.Address || line.Address+line.Length >= sectionEnd {
// Check that this line is entirely within the section.
// Don't bother dealing with line entries that cross sections (that
// seems rather unlikely anyway).
continue
}
if addr < line.Address {
// There is a gap: there is a space between the current and the
// previous line entry.
addSize("(unknown)", line.Address-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %4d: unknown (gap)\n", addr, line.Address, line.Address-addr)
}
}
if addr > line.Address+line.Length {
// The current line is already covered by a previous line entry.
// Simply skip it.
continue
}
// At this point, addr falls within the current line (probably at the
// start).
length := line.Length
if addr > line.Address {
// There is some overlap: the previous line entry already covered
// part of this line entry. So reduce the length to add to the
// remaining bit of the line entry.
length = line.Length - (addr - line.Address)
}
// Finally, mark this chunk of memory as used by the given package.
addSize(findPackagePath(line.File, packagePathMap), length, line.IsVariable)
addr = line.Address + line.Length
}
if addr < sectionEnd {
// There is a gap at the end of the section.
addSize("(unknown)", sectionEnd-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %4d: unknown (end)\n", addr, sectionEnd, sectionEnd-addr)
}
}
}
// findPackagePath returns the Go package (or a pseudo package) for the given
// path. It uses some heuristics, for example for some C libraries.
func findPackagePath(path string, packagePathMap map[string]string) string {
// Check whether this path is part of one of the compiled packages.
packagePath, ok := packagePathMap[filepath.Dir(path)]
if !ok {
if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) {
// Emit C libraries (in the lib subdirectory of TinyGo) as a single
// package, with a "C" prefix. For example: "C compiler-rt" for the
// compiler runtime library from LLVM.
packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1]
} else if packageSymbolRegexp.MatchString(path) {
// Parse symbol names like main$alloc or runtime$string.
packagePath = path[:strings.LastIndex(path, "$")]
} else if reflectDataRegexp.MatchString(path) {
// Parse symbol names like reflect.structTypesSidetable.
packagePath = "Go reflect data"
} else if path == "<Go interface assert>" {
// Interface type assert, generated by the interface lowering pass.
packagePath = "Go interface assert"
} else if path == "<Go interface method>" {
// Interface method wrapper (switch over all concrete types),
// generated by the interface lowering pass.
packagePath = "Go interface method"
} else if path == "<stdin>" {
// This can happen when the source code (in Go) doesn't have a
// source file and uses "-" as the location. Somewhere this is
// converted to "<stdin>".
// Convert this back to the "-" string. Eventually, this should be
// fixed in the compiler.
packagePath = "-"
} else {
// This is some other path. Not sure what it is, so just emit its directory.
packagePath = filepath.Dir(path) // fallback
}
}
return packagePath
}
+3 -3
View File
@@ -24,7 +24,7 @@ func runCCompiler(flags ...string) error {
}
// Compile this with an external invocation of the Clang compiler.
return execCommand(commands["clang"], flags...)
return execCommand("clang", flags...)
}
// link invokes a linker with the given name and flags.
@@ -38,8 +38,8 @@ func link(linker string, flags ...string) error {
}
// Fall back to external command.
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
if _, ok := commands[linker]; ok {
return execCommand(linker, flags...)
}
cmd := exec.Command(linker, flags...)
+3 -1
View File
@@ -141,11 +141,13 @@ func split(input []byte, limit int) [][]byte {
var block []byte
output := make([][]byte, 0, len(input)/limit+1)
for len(input) >= limit {
// add all blocks
block, input = input[:limit], input[limit:]
output = append(output, block)
}
if len(input) > 0 {
output = append(output, input[:len(input)])
// add remaining block (that isn't full sized)
output = append(output, input)
}
return output
}
+167 -147
View File
@@ -25,12 +25,19 @@ import (
"golang.org/x/tools/go/ast/astutil"
)
// Version of the cgo package. It must be incremented whenever the cgo package
// is changed in a way that affects the output so that cached package builds
// will be invalidated.
// This version is independent of the TinyGo version number.
const Version = 1 // last change: run libclang once per Go file
// cgoPackage holds all CGo-related information of a package.
type cgoPackage struct {
generated *ast.File
generatedPos token.Pos
errors []error
dir string
currentDir string // current working directory
packageDir string // full path to the package to process
fset *token.FileSet
tokenFiles map[string]*token.File
missingSymbols map[string]struct{}
@@ -124,17 +131,17 @@ var cgoAliases = map[string]string{
// builtinAliases are handled specially because they only exist on the Go side
// of CGo, not on the CGo side (they're prefixed with "_Cgo_" there).
var builtinAliases = map[string]struct{}{
"char": struct{}{},
"schar": struct{}{},
"uchar": struct{}{},
"short": struct{}{},
"ushort": struct{}{},
"int": struct{}{},
"uint": struct{}{},
"long": struct{}{},
"ulong": struct{}{},
"longlong": struct{}{},
"ulonglong": struct{}{},
"char": {},
"schar": {},
"uchar": {},
"short": {},
"ushort": {},
"int": {},
"uint": {},
"long": {},
"ulong": {},
"longlong": {},
"ulonglong": {},
}
// cgoTypes lists some C types with ambiguous sizes that must be retrieved
@@ -158,12 +165,13 @@ typedef unsigned long long _Cgo_ulonglong;
// Process extracts `import "C"` statements from the AST, parses the comment
// with libclang, and modifies the AST to use this information. It returns a
// newly created *ast.File that should be added to the list of to-be-parsed
// files, the CFLAGS and LDFLAGS found in #cgo lines, and a map of file hashes
// of the accessed C header files. If there is one or more error, it returns
// these in the []error slice but still modifies the AST.
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []string, map[string][]byte, []error) {
// files, the CGo header snippets that should be compiled (for inline
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
// hashes of the accessed C header files. If there is one or more error, it
// returns these in the []error slice but still modifies the AST.
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{
dir: dir,
currentDir: dir,
fset: fset,
tokenFiles: map[string]*token.File{},
missingSymbols: map[string]struct{}{},
@@ -184,14 +192,14 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
// Find the absolute path for this package.
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
if err != nil {
return nil, nil, nil, nil, []error{
return nil, nil, nil, nil, nil, []error{
scanner.Error{
Pos: fset.Position(files[0].Pos()),
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
},
}
}
packagePath = filepath.Dir(packagePath)
p.packageDir = filepath.Dir(packagePath)
// Construct a new in-memory AST for CGo declarations of this package.
unsafeImport := &ast.ImportSpec{
@@ -224,7 +232,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
Specs: []ast.Spec{
&ast.ValueSpec{
Names: []*ast.Ident{
&ast.Ident{
{
Name: "_",
Obj: &ast.Object{
Kind: ast.Var,
@@ -255,9 +263,10 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
p.missingSymbols["_Cgo_"+name] = struct{}{}
}
// Find `import "C"` statements in the file.
var statements []*ast.GenDecl
for _, f := range files {
// Find `import "C"` C fragments in the file.
cgoHeaders := make([]string, len(files)) // combined CGo header fragment for each file
for i, f := range files {
var cgoHeader string
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
genDecl, ok := decl.(*ast.GenDecl)
@@ -281,105 +290,40 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
continue
}
// Found a CGo statement.
statements = append(statements, genDecl)
// Remove this import declaration.
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
i--
}
// Print the AST, for debugging.
//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
}
makePathsAbsolute(flags, packagePath)
p.cflags = append(p.cflags, flags...)
case "LDFLAGS":
flags, err := shlex.Split(value)
if err != nil {
// TODO: find the exact location where the error happened.
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
continue
}
if err := checkLinkerFlags(name, flags); err != nil {
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
continue
}
makePathsAbsolute(flags, packagePath)
p.ldflags = append(p.ldflags, flags...)
default:
startPos := strings.LastIndex(line[4:colon], name) + 4
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
continue
}
if genDecl.Doc == nil {
continue
}
// Iterate through all parts of the CGo header. Note that every //
// line is a new comment.
position := fset.Position(genDecl.Doc.Pos())
fragment := fmt.Sprintf("# %d %#v\n", position.Line, position.Filename)
for _, comment := range genDecl.Doc.List {
// Find all #cgo lines, extract and use their contents, and
// replace the lines with spaces (to preserve locations).
c := p.parseCGoPreprocessorLines(comment.Text, comment.Slash)
// Change the comment (which is still in Go syntax, with // and
// /* */ ) to a regular string by replacing the start/end
// markers of comments with spaces.
// It is similar to the Text() method but differs in that it
// doesn't strip anything and tries to keep all offsets correct
// by adding spaces and newlines where necessary.
if c[1] == '/' { /* comment */
c = " " + c[2:]
} else { // comment
c = " " + c[2:len(c)-2]
}
fragment += c + "\n"
}
cgoHeader += fragment
}
cgoHeaders[i] = cgoHeader
}
// Define CFlags that will be used while parsing the package.
@@ -389,16 +333,9 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...)
cflagsForCGo = append(cflagsForCGo, p.cflags...)
// Process all CGo imports.
for _, genDecl := range statements {
cgoComment := genDecl.Doc.Text()
pos := genDecl.Pos()
if genDecl.Doc != nil {
pos = genDecl.Doc.Pos()
}
position := fset.PositionFor(pos, true)
p.parseFragment(cgoComment+cgoTypes, cflagsForCGo, position.Filename, position.Line)
// Process CGo imports for each file.
for i, f := range files {
p.parseFragment(cgoHeaders[i]+cgoTypes, cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()))
}
// Declare functions found by libclang.
@@ -433,18 +370,18 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
// Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated)
return p.generated, p.cflags, p.ldflags, p.visitedFiles, p.errors
return p.generated, cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
}
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
// relative flags into absolute flags, if they are relative. This is necessary
// because the C compiler is usually not invoked from the package path.
func makePathsAbsolute(args []string, packagePath string) {
func (p *cgoPackage) makePathsAbsolute(args []string) {
nextIsPath := false
for i, arg := range args {
if nextIsPath {
if !filepath.IsAbs(arg) {
args[i] = filepath.Join(packagePath, arg)
args[i] = filepath.Join(p.packageDir, arg)
}
}
if arg == "-I" || arg == "-L" {
@@ -454,12 +391,95 @@ func makePathsAbsolute(args []string, packagePath string) {
if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
path := arg[2:]
if !filepath.IsAbs(path) {
args[i] = arg[:2] + filepath.Join(packagePath, path)
args[i] = arg[:2] + filepath.Join(p.packageDir, path)
}
}
}
}
// parseCGoPreprocessorLines reads #cgo pseudo-preprocessor lines in the source
// text (import "C" fragment), stores their information such as CFLAGS, and
// returns the same text but with those #cgo lines replaced by spaces (to keep
// position offsets the same).
func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) string {
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(text, "#cgo ")
if lineStart < 0 {
break
}
lineLen := strings.IndexByte(text[lineStart:], '\n')
if lineLen < 0 {
lineLen = len(text) - lineStart
}
lineEnd := lineStart + lineLen
line := text[lineStart:lineEnd]
spaces := make([]byte, len(line))
for i := range spaces {
spaces[i] = ' '
}
text = text[:lineStart] + string(spaces) + text[lineEnd:]
// Get the text before the colon in the #cgo directive.
colon := strings.IndexByte(line, ':')
if colon < 0 {
p.addErrorAfter(pos, 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(pos, text[:lineStart+colon-1], "invalid #cgo line")
continue
}
if len(fields) > 1 {
p.addErrorAfter(pos, 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(pos, text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
continue
}
if err := checkCompilerFlags(name, flags); err != nil {
p.addErrorAfter(pos, text[:lineStart+colon+1], err.Error())
continue
}
p.makePathsAbsolute(flags)
p.cflags = append(p.cflags, flags...)
case "LDFLAGS":
flags, err := shlex.Split(value)
if err != nil {
// TODO: find the exact location where the error happened.
p.addErrorAfter(pos, text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
continue
}
if err := checkLinkerFlags(name, flags); err != nil {
p.addErrorAfter(pos, text[:lineStart+colon+1], err.Error())
continue
}
p.makePathsAbsolute(flags)
p.ldflags = append(p.ldflags, flags...)
default:
startPos := strings.LastIndex(line[4:colon], name) + 4
p.addErrorAfter(pos, text[:lineStart+startPos], "invalid #cgo line: "+name)
continue
}
}
return text
}
// addFuncDecls adds the C function declarations found by libclang in the
// comment above the `import "C"` statement.
func (p *cgoPackage) addFuncDecls() {
@@ -494,7 +514,7 @@ func (p *cgoPackage) addFuncDecls() {
if fn.variadic {
decl.Doc = &ast.CommentGroup{
List: []*ast.Comment{
&ast.Comment{
{
Slash: fn.pos,
Text: "//go:variadic",
},
@@ -505,7 +525,7 @@ func (p *cgoPackage) addFuncDecls() {
for i, arg := range fn.args {
args[i] = &ast.Field{
Names: []*ast.Ident{
&ast.Ident{
{
NamePos: fn.pos,
Name: arg.name,
Obj: &ast.Object{
@@ -553,7 +573,7 @@ func (p *cgoPackage) addFuncPtrDecls() {
Name: "C." + name + "$funcaddr",
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
Names: []*ast.Ident{{
NamePos: fn.pos,
Name: "C." + name + "$funcaddr",
Obj: obj,
@@ -603,7 +623,7 @@ func (p *cgoPackage) addConstDecls() {
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
Names: []*ast.Ident{{
NamePos: constVal.pos,
Name: "C." + name,
Obj: obj,
@@ -644,7 +664,7 @@ func (p *cgoPackage) addVarDecls() {
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{&ast.Ident{
Names: []*ast.Ident{{
NamePos: global.pos,
Name: "C." + name,
Obj: obj,
@@ -845,9 +865,9 @@ func (p *cgoPackage) makeUnionField(typ *elaboratedTypeInfo) *ast.StructType {
Struct: typ.typeExpr.Struct,
Fields: &ast.FieldList{
Opening: typ.typeExpr.Fields.Opening,
List: []*ast.Field{&ast.Field{
List: []*ast.Field{{
Names: []*ast.Ident{
&ast.Ident{
{
NamePos: typ.typeExpr.Fields.Opening,
Name: "$union",
},
@@ -928,9 +948,9 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
Recv: &ast.FieldList{
Opening: pos,
List: []*ast.Field{
&ast.Field{
{
Names: []*ast.Ident{
&ast.Ident{
{
NamePos: pos,
Name: "union",
},
@@ -959,7 +979,7 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
{
Type: &ast.StarExpr{
Star: pos,
X: field.Type,
@@ -1038,9 +1058,9 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
Recv: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
{
Names: []*ast.Ident{
&ast.Ident{
{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
@@ -1074,7 +1094,7 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
{
Type: bitfield.field.Type,
},
},
@@ -1191,9 +1211,9 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
Recv: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
{
Names: []*ast.Ident{
&ast.Ident{
{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
@@ -1224,9 +1244,9 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
Params: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
{
Names: []*ast.Ident{
&ast.Ident{
{
NamePos: bitfield.pos,
Name: "value",
Obj: nil,
+3 -12
View File
@@ -12,7 +12,6 @@ import (
"go/types"
"io/ioutil"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
@@ -25,19 +24,11 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
// platforms and Go versions.
func normalizeResult(result string) string {
actual := strings.ReplaceAll(result, "\r\n", "\n")
// Make sure all functions are wrapped, even those that would otherwise be
// single-line functions. This is necessary because Go 1.14 changed the way
// such functions are wrapped and it's important to have consistent test
// results.
re := regexp.MustCompile(`func \((.+)\)( .*?) +{ (.+) }`)
actual = re.ReplaceAllString(actual, "func ($1)$2 {\n\t$3\n}")
return actual
}
func TestCGo(t *testing.T) {
var cflags = []string{"--target=armv6m-none-eabi"}
var cflags = []string{"--target=armv6m-unknown-unknown-eabi"}
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
name := name // avoid a race condition
@@ -65,7 +56,7 @@ func TestCGo(t *testing.T) {
}
// Process the AST with CGo.
cgoAST, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
// Check the AST for type errors.
var typecheckErrors []error
@@ -105,7 +96,7 @@ func TestCGo(t *testing.T) {
if err != nil {
t.Errorf("could not write out CGo AST: %v", err)
}
actual := normalizeResult(string(buf.Bytes()))
actual := normalizeResult(buf.String())
// Read the file with the expected output, to compare against.
outfile := filepath.Join("testdata", name+".out.go")
+164 -74
View File
@@ -11,105 +11,184 @@ import (
"strings"
)
var (
prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error)
precedences = map[token.Token]int{
token.ADD: precedenceAdd,
token.SUB: precedenceAdd,
token.MUL: precedenceMul,
token.QUO: precedenceMul,
token.REM: precedenceMul,
}
)
const (
precedenceLowest = iota + 1
precedenceAdd
precedenceMul
precedencePrefix
)
func init() {
// This must be done in an init function to avoid an initialization order
// failure.
prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){
token.IDENT: parseIdent,
token.INT: parseBasicLit,
token.FLOAT: parseBasicLit,
token.STRING: parseBasicLit,
token.CHAR: parseBasicLit,
token.LPAREN: parseParenExpr,
token.SUB: parseUnaryExpr,
}
}
// parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value)
expr, err := parseConstExpr(t)
if t.token != token.EOF {
expr, err := parseConstExpr(t, precedenceLowest)
t.Next()
if t.curToken != token.EOF {
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: "unexpected token " + t.token.String(),
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + ", expected end of expression",
}
}
return expr, err
}
// parseConstExpr parses a stream of C tokens to a Go expression.
func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
switch t.token {
case token.LPAREN:
lparen := t.pos
t.Next()
x, err := parseConstExpr(t)
if err != nil {
return nil, err
}
if t.token != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.pos,
}
t.Next()
return expr, nil
case token.INT, token.FLOAT, token.STRING, token.CHAR:
expr := &ast.BasicLit{
ValuePos: t.pos,
Kind: t.token,
Value: t.value,
}
t.Next()
return expr, nil
case token.IDENT:
expr := &ast.Ident{
NamePos: t.pos,
Name: "C." + t.value,
}
t.Next()
return expr, nil
case token.EOF:
func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
if t.curToken == token.EOF {
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Pos: t.fset.Position(t.curPos),
Msg: "empty constant",
}
default:
}
prefix := prefixParseFns[t.curToken]
if prefix == nil {
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s", t.token),
Pos: t.fset.Position(t.curPos),
Msg: fmt.Sprintf("unexpected token %s", t.curToken),
}
}
leftExpr, err := prefix(t)
for t.peekToken != token.EOF && precedence < precedences[t.peekToken] {
switch t.peekToken {
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
t.Next()
leftExpr, err = parseBinaryExpr(t, leftExpr)
}
}
return leftExpr, err
}
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
return &ast.Ident{
NamePos: t.curPos,
Name: "C." + t.curValue,
}, nil
}
func parseBasicLit(t *tokenizer) (ast.Expr, *scanner.Error) {
return &ast.BasicLit{
ValuePos: t.curPos,
Kind: t.curToken,
Value: t.curValue,
}, nil
}
func parseParenExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
lparen := t.curPos
t.Next()
x, err := parseConstExpr(t, precedenceLowest)
if err != nil {
return nil, err
}
t.Next()
if t.curToken != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.curPos,
}
return expr, nil
}
func parseBinaryExpr(t *tokenizer, left ast.Expr) (ast.Expr, *scanner.Error) {
expression := &ast.BinaryExpr{
X: left,
Op: t.curToken,
OpPos: t.curPos,
}
precedence := precedences[t.curToken]
t.Next()
right, err := parseConstExpr(t, precedence)
expression.Y = right
return expression, err
}
func parseUnaryExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
expression := &ast.UnaryExpr{
OpPos: t.curPos,
Op: t.curToken,
}
t.Next()
x, err := parseConstExpr(t, precedencePrefix)
expression.X = x
return expression, err
}
// unexpectedToken returns an error of the form "unexpected token FOO, expected
// BAR".
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
return &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
Pos: t.fset.Position(t.curPos),
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.curToken, expected),
}
}
// tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct {
pos token.Pos
fset *token.FileSet
token token.Token
value string
buf string
curPos, peekPos token.Pos
fset *token.FileSet
curToken, peekToken token.Token
curValue, peekValue string
buf string
}
// newTokenizer initializes a new tokenizer, positioned at the first token in
// the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
t := &tokenizer{
pos: start,
fset: fset,
buf: buf,
token: token.ILLEGAL,
peekPos: start,
fset: fset,
buf: buf,
peekToken: token.ILLEGAL,
}
t.Next() // Parse the first token.
// Parse the first two tokens (cur and peek).
t.Next()
t.Next()
return t
}
// Next consumes the next token in the stream. There is no return value, read
// the next token from the pos, token and value properties.
func (t *tokenizer) Next() {
t.pos += token.Pos(len(t.value))
// The previous peek is now the current token.
t.curPos = t.peekPos
t.curToken = t.peekToken
t.curValue = t.peekValue
// Parse the next peek token.
t.peekPos += token.Pos(len(t.curValue))
for {
if len(t.buf) == 0 {
t.token = token.EOF
t.peekToken = token.EOF
return
}
c := t.buf[0]
@@ -118,17 +197,28 @@ func (t *tokenizer) Next() {
// Skip whitespace.
// Based on this source, not sure whether it represents C whitespace:
// https://en.cppreference.com/w/cpp/string/byte/isspace
t.pos++
t.peekPos++
t.buf = t.buf[1:]
case c == '(' || c == ')':
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%':
// Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators.
switch c {
case '(':
t.token = token.LPAREN
t.peekToken = token.LPAREN
case ')':
t.token = token.RPAREN
t.peekToken = token.RPAREN
case '+':
t.peekToken = token.ADD
case '-':
t.peekToken = token.SUB
case '*':
t.peekToken = token.MUL
case '/':
t.peekToken = token.QUO
case '%':
t.peekToken = token.REM
}
t.value = t.buf[:1]
t.peekValue = t.buf[:1]
t.buf = t.buf[1:]
return
case c >= '0' && c <= '9':
@@ -146,17 +236,17 @@ func (t *tokenizer) Next() {
break
}
}
t.value = t.buf[:tokenLen]
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
if hasDot {
// Integer constants are more complicated than this but this is
// a close approximation.
// https://en.cppreference.com/w/cpp/language/integer_literal
t.token = token.FLOAT
t.value = strings.TrimRight(t.value, "f")
t.peekToken = token.FLOAT
t.peekValue = strings.TrimRight(t.peekValue, "f")
} else {
t.token = token.INT
t.value = strings.TrimRight(t.value, "uUlL")
t.peekToken = token.INT
t.peekValue = strings.TrimRight(t.peekValue, "uUlL")
}
return
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
@@ -170,9 +260,9 @@ func (t *tokenizer) Next() {
break
}
}
t.value = t.buf[:tokenLen]
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
t.token = token.IDENT
t.peekToken = token.IDENT
return
case c == '"':
// String constant. Find the first '"' character that is not
@@ -188,8 +278,8 @@ func (t *tokenizer) Next() {
escape = c == '\\'
}
}
t.token = token.STRING
t.value = t.buf[:tokenLen]
t.peekToken = token.STRING
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
case c == '\'':
@@ -206,12 +296,12 @@ func (t *tokenizer) Next() {
escape = c == '\\'
}
}
t.token = token.CHAR
t.value = t.buf[:tokenLen]
t.peekToken = token.CHAR
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
default:
t.token = token.ILLEGAL
t.peekToken = token.ILLEGAL
return
}
}
+19 -2
View File
@@ -18,7 +18,7 @@ func TestParseConst(t *testing.T) {
{`(5)`, `(5)`},
{`(((5)))`, `(5)`},
{`)`, `error: 1:1: unexpected token )`},
{`5)`, `error: 1:2: unexpected token )`},
{`5)`, `error: 1:2: unexpected token ), expected end of expression`},
{" \t)", `error: 1:4: unexpected token )`},
{`5.8f`, `5.8`},
{`foo`, `C.foo`},
@@ -30,7 +30,24 @@ func TestParseConst(t *testing.T) {
{`'a'`, `'a'`},
{`0b10`, `0b10`},
{`0x1234_5678`, `0x1234_5678`},
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
{`5 5`, `error: 1:3: unexpected token INT, expected end of expression`}, // test for a bugfix
// Binary operators.
{`5+5`, `5 + 5`},
{`5-5`, `5 - 5`},
{`5*5`, `5 * 5`},
{`5/5`, `5 / 5`},
{`5%5`, `5 % 5`},
{`(5/5)`, `(5 / 5)`},
{`1 - 2`, `1 - 2`},
{`1 - 2 + 3`, `1 - 2 + 3`},
{`1 - 2 * 3`, `1 - 2*3`},
{`(1 - 2) * 3`, `(1 - 2) * 3`},
{`1 * 2 - 3`, `1*2 - 3`},
{`1 * (2 - 3)`, `1 * (2 - 3)`},
// Unary operators.
{`-5`, `-5`},
{`-5-2`, `-5 - 2`},
{`5 - - 2`, `5 - -2`},
} {
fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0)
+48 -12
View File
@@ -16,7 +16,7 @@ import (
)
/*
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
#include <clang-c/Index.h> // if this fails, install libclang-11-dev
#include <stdlib.h>
#include <stdint.h>
@@ -72,17 +72,14 @@ var diagnosticSeverity = [...]string{
C.CXDiagnostic_Fatal: "fatal",
}
func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) {
func (p *cgoPackage) parseFragment(fragment string, cflags []string, filename string) {
index := C.clang_createIndex(0, 0)
defer C.clang_disposeIndex(index)
// pretend to be a .c file
filenameC := C.CString(posFilename + "!cgo.c")
filenameC := C.CString(filename + "!cgo.c")
defer C.free(unsafe.Pointer(filenameC))
// fix up error locations
fragment = fmt.Sprintf("# %d %#v\n", posLine+1, posFilename) + fragment
fragmentC := C.CString(fragment)
defer C.free(unsafe.Pointer(fragmentC))
@@ -196,14 +193,14 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
}
fn.args = append(fn.args, paramInfo{
name: argName,
typeExpr: p.makeASTType(argType, pos),
typeExpr: p.makeDecayingASTType(argType, pos),
})
}
resultType := C.tinygo_clang_getCursorResultType(c)
if resultType.kind != C.CXType_Void {
fn.results = &ast.FieldList{
List: []*ast.Field{
&ast.Field{
{
Type: p.makeASTType(resultType, pos),
},
},
@@ -380,7 +377,7 @@ func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
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)
relpath, err := filepath.Rel(p.currentDir, position.Filename)
if err == nil {
position.Filename = relpath
}
@@ -391,6 +388,41 @@ func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
})
}
// makeDecayingASTType does the same as makeASTType but takes care of decaying
// types (arrays in function parameters, etc). It is otherwise identical to
// makeASTType.
func (p *cgoPackage) makeDecayingASTType(typ C.CXType, pos token.Pos) ast.Expr {
// Strip typedefs, if any.
underlyingType := typ
if underlyingType.kind == C.CXType_Typedef {
c := C.tinygo_clang_getTypeDeclaration(typ)
underlyingType = C.tinygo_clang_getTypedefDeclUnderlyingType(c)
// TODO: support a chain of typedefs. At the moment, it seems to get
// stuck in an endless loop when trying to get to the most underlying
// type.
}
// Check for decaying type. An example would be an array type in a
// parameter. This declaration:
// void foo(char buf[6]);
// is the same as this one:
// void foo(char *buf);
// But this one:
// void bar(char buf[6][4]);
// equals this:
// void bar(char *buf[4]);
// so not all array dimensions should be stripped, just the first one.
// TODO: there are more kinds of decaying types.
if underlyingType.kind == C.CXType_ConstantArray {
// Apply type decaying.
pointeeType := C.clang_getElementType(underlyingType)
return &ast.StarExpr{
Star: pos,
X: p.makeASTType(pointeeType, pos),
}
}
return p.makeASTType(typ, pos)
}
// makeASTType return the ast.Expr for the given libclang type. In other words,
// it converts a libclang type to a type in the Go AST.
func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
@@ -775,7 +807,7 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
}
*inBitfield = false
field.Names = []*ast.Ident{
&ast.Ident{
{
NamePos: pos,
Name: name,
Obj: &ast.Object{
@@ -796,8 +828,12 @@ func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientDat
pos := p.getCursorPosition(c)
value := C.tinygo_clang_getEnumConstantDeclValue(c)
p.constants[name] = constantInfo{
expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)},
pos: pos,
expr: &ast.BasicLit{
ValuePos: pos,
Kind: token.INT,
Value: strconv.FormatInt(int64(value), 10),
},
pos: pos,
}
return C.CXChildVisit_Continue
}
-1
View File
@@ -1,5 +1,4 @@
// +build !byollvm
// +build !llvm10
package cgo
-14
View File
@@ -1,14 +0,0 @@
// +build !byollvm
// +build llvm10
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
*/
import "C"
+1 -1
View File
@@ -3,7 +3,7 @@
// are slightly different from the ones defined in libclang.go, but they
// should be ABI compatible.
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
#include <clang-c/Index.h> // if this fails, install libclang-11-dev
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
return clang_getTranslationUnitCursor(tu);
+8
View File
@@ -14,6 +14,12 @@ typedef someType noType; // undefined type
#define SOME_CONST_2 6) // const not used (so no error)
#define SOME_CONST_3 1234 // const too large for byte
*/
//
//
// #define SOME_CONST_4 8) // after some empty lines
import "C"
// #warning another warning
import "C"
// Make sure that errors for the following lines won't change with future
@@ -30,4 +36,6 @@ var (
_ = C.SOME_CONST_1
_ byte = C.SOME_CONST_3
_ = C.SOME_CONST_4
)
+4 -1
View File
@@ -1,13 +1,16 @@
// CGo errors:
// testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:13:23: unexpected token )
// testdata/errors.go:22:5: warning: another warning
// testdata/errors.go:13:23: unexpected token ), expected end of expression
// testdata/errors.go:19:26: unexpected token ), expected end of expression
// Type checking errors after CGo processing:
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as uint8 value in variable declaration (overflows)
// testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
// testdata/errors.go:112: undeclared name: C.SOME_CONST_4
package main
+4
View File
@@ -111,6 +111,10 @@ void variadic2(int x, int y, ...);
*/
import "C"
// // Test that we can refer from this CGo fragment to the fragment above.
// typedef myint myint2;
import "C"
var (
// Simple typedefs.
_ C.myint
+5 -15
View File
@@ -67,9 +67,7 @@ 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) 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
}
@@ -105,15 +103,9 @@ type C.struct_type1 struct {
}
type C.struct_type2 struct{ _type C.int }
func (union *C.union_1) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_s() *C.short {
return (*C.short)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) }
type C.union_1 struct{ $union uint64 }
@@ -138,9 +130,7 @@ func (union *C.union_3) unionfield_thing() *C.union3_t {
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_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))
}
+114 -10
View File
@@ -5,6 +5,7 @@ package compileopts
import (
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
@@ -21,7 +22,7 @@ type Config struct {
TestConfig TestConfig
}
// Triple returns the LLVM target triple, like armv6m-none-eabi.
// Triple returns the LLVM target triple, like armv6m-unknown-unknown-eabi.
func (c *Config) Triple() string {
return c.Target.Triple
}
@@ -55,7 +56,7 @@ func (c *Config) GOARCH() string {
// BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string {
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
@@ -89,7 +90,7 @@ func (c *Config) NeedsStackObjects() bool {
switch c.GC() {
case "conservative", "extalloc":
for _, tag := range c.BuildTags() {
if tag == "wasm" {
if tag == "tinygo.wasm" {
return true
}
}
@@ -113,6 +114,18 @@ func (c *Config) Scheduler() string {
return "coroutines"
}
// Serial returns the serial implementation for this build configuration: uart,
// usb (meaning USB-CDC), or none.
func (c *Config) Serial() string {
if c.Options.Serial != "" {
return c.Options.Serial
}
if c.Target.Serial != "" {
return c.Target.Serial
}
return "none"
}
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline.
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
@@ -176,6 +189,48 @@ func (c *Config) AutomaticStackSize() bool {
return false
}
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
// calculates and patches in the checksum for the 2nd stage bootloader.
func (c *Config) RP2040BootPatch() bool {
if c.Target.RP2040BootPatch != nil {
return *c.Target.RP2040BootPatch
}
return false
}
// MuslArchitecture returns the architecture name as used in musl libc. It is
// usually the same as the first part of the LLVM triple, but not always.
func MuslArchitecture(triple string) string {
arch := strings.Split(triple, "-")[0]
if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
arch = "arm"
}
return arch
}
// LibcPath returns the path to the libc directory. The libc path will be either
// a precompiled libc shipped with a TinyGo build, or a libc path in the cache
// directory (which might not yet be built).
func (c *Config) LibcPath(name string) (path string, precompiled bool) {
// Try to load a precompiled library.
precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", c.Triple(), name)
if _, err := os.Stat(precompiledDir); err == nil {
// Found a precompiled library for this OS/architecture. Return the path
// directly.
return precompiledDir, true
}
// No precompiled library found. Determine the path name that will be used
// in the build cache.
var outname string
if c.CPU() != "" {
outname = name + "-" + c.Triple() + "-" + c.CPU()
} else {
outname = name + "-" + c.Triple()
}
return filepath.Join(goenv.Get("GOCACHE"), outname), false
}
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing.
func (c *Config) CFlags() []string {
@@ -183,13 +238,53 @@ func (c *Config) CFlags() []string {
for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
}
if c.Target.Libc == "picolibc" {
switch c.Target.Libc {
case "picolibc":
root := goenv.Get("TINYGOROOT")
cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
path, _ := c.LibcPath("picolibc")
cflags = append(cflags,
"--sysroot="+path,
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "include"),
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "tinystdio"),
)
case "musl":
root := goenv.Get("TINYGOROOT")
path, _ := c.LibcPath("musl")
arch := MuslArchitecture(c.Triple())
cflags = append(cflags,
"--sysroot="+path,
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "musl", "arch", arch),
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "musl", "include"),
)
case "wasi-libc":
root := goenv.Get("TINYGOROOT")
cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot")
case "":
// No libc specified, nothing to add.
default:
// Incorrect configuration. This could be handled in a better way, but
// usually this will be found by developers (not by TinyGo users).
panic("unknown libc: " + c.Target.Libc)
}
if c.Debug() {
cflags = append(cflags, "-g")
// Always emit debug information. It is optionally stripped at link time.
cflags = append(cflags, "-g")
// Use the same optimization level as TinyGo.
cflags = append(cflags, "-O"+c.Options.Opt)
// Set the LLVM target triple.
cflags = append(cflags, "--target="+c.Triple())
// Set the -mcpu (or similar) flag.
if c.Target.CPU != "" {
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
// x86 prefers the -march flag (-mcpu is deprecated there).
cflags = append(cflags, "-march="+c.Target.CPU)
} else if strings.HasPrefix(c.Triple(), "avr") {
// AVR MCUs use -mmcu instead of -mcpu.
cflags = append(cflags, "-mmcu="+c.Target.CPU)
} else {
// The rest just uses -mcpu.
cflags = append(cflags, "-mcpu="+c.Target.CPU)
}
}
return cflags
}
@@ -229,8 +324,9 @@ 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.
// Debug returns whether debug (DWARF) information should be retained by the
// linker. By default, debug information is retained but it can be removed with
// the -no-debug flag.
func (c *Config) Debug() bool {
return c.Options.Debug
}
@@ -254,6 +350,11 @@ func (c *Config) BinaryFormat(ext string) string {
// More information:
// https://github.com/Microsoft/uf2
return "uf2"
case ".zip":
if c.Target.BinaryFormat != "" {
return c.Target.BinaryFormat
}
return "zip"
default:
// Use the ELF format for unrecognized file formats.
return "elf"
@@ -271,6 +372,9 @@ func (c *Config) Programmer() (method, openocdInterface string) {
case "openocd", "msd", "command":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface
case "bmp":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, ""
default:
// The -programmer flag specifies something else, assume it specifies
// the OpenOCD interface name.
+17 -2
View File
@@ -9,23 +9,29 @@ import (
var (
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
validSerialOptions = []string{"none", "uart", "usb"}
validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
)
// Options contains extra options to give to the compiler. These options are
// usually passed from the command line.
// usually passed from the command line, but can also be passed in environment
// variables for example.
type Options struct {
GOOS string // environment variable
GOARCH string // environment variable
Target string
Opt string
GC string
PanicStrategy string
Scheduler string
Serial string
PrintIR bool
DumpSSA bool
VerifyIR bool
PrintCommands bool
PrintCommands func(cmd string, args ...string)
Parallelism int // -p flag
Debug bool
PrintSizes string
PrintAllocs *regexp.Regexp // regexp string
@@ -59,6 +65,15 @@ func (o *Options) Verify() error {
}
}
if o.Serial != "" {
valid := isInArray(validSerialOptions, o.Serial)
if !valid {
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
o.Serial,
strings.Join(validSerialOptions, ", "))
}
}
if o.PrintSizes != "" {
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
if !valid {
+51 -63
View File
@@ -31,6 +31,7 @@ type TargetSpec struct {
BuildTags []string `json:"build-tags"`
GC string `json:"gc"`
Scheduler string `json:"scheduler"`
Serial string `json:"serial"` // which serial output to use (uart, usb, none)
Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"`
@@ -40,10 +41,12 @@ type TargetSpec struct {
LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"`
ExtraFiles []string `json:"extra-files"`
RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
FlashCommand string `json:"flash-command"`
GDB []string `json:"gdb"`
PortReset string `json:"flash-1200-bps-reset"`
SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"`
@@ -158,75 +161,54 @@ func (spec *TargetSpec) resolveInherits() error {
}
// Load a target specification.
func LoadTarget(target string) (*TargetSpec, error) {
if target == "" {
func LoadTarget(options *Options) (*TargetSpec, error) {
if options.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]
"arm": "armv7",
}[options.GOARCH]
if llvmarch == "" {
llvmarch = goarch
llvmarch = options.GOARCH
}
target = llvmarch + "--" + llvmos
if goarch == "arm" {
llvmos := options.GOOS
if llvmos == "darwin" {
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer
// to iOS!
llvmos = "macosx10.12.0"
if llvmarch == "aarch64" {
// Looks like Apple prefers to call this architecture ARM64
// instead of AArch64.
llvmarch = "arm64"
}
}
// Target triples (which actually have four components, but are called
// triples for historical reasons) have the form:
// arch-vendor-os-environment
target := llvmarch + "-unknown-" + llvmos
if options.GOARCH == "arm" {
target += "-gnueabihf"
}
return defaultTarget(goos, goarch, target)
return defaultTarget(options.GOOS, options.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.
err := spec.loadFromGivenStr(options.Target)
if err != nil {
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",
"i686": "386",
"x86_64": "amd64",
"aarch64": "arm64",
"armv7": "arm",
}[tripleSplit[0]]
if goarch == "" {
goarch = tripleSplit[0]
}
return defaultTarget(goos, goarch, strings.Join(tripleSplit, "-"))
}
// 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
}
// WindowsBuildNotSupportedErr is being thrown, when goos is windows and no target has been specified.
@@ -246,13 +228,27 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
Scheduler: "tasks",
Linker: "cc",
DefaultStackSize: 1024 * 64, // 64kB
CFlags: []string{"--target=" + triple},
GDB: []string{"gdb"},
PortReset: "false",
}
switch goarch {
case "386":
spec.CPU = "pentium4"
case "amd64":
spec.CPU = "x86-64"
case "arm":
spec.CPU = "generic"
case "arm64":
spec.CPU = "generic"
}
if goos == "darwin" {
spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
} else if goos == "linux" {
spec.Linker = "ld.lld"
spec.RTLib = "compiler-rt"
spec.Libc = "musl"
spec.LDFlags = append(spec.LDFlags, "--gc-sections")
} else {
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
}
@@ -264,18 +260,10 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
// Some educated guesses as to how to invoke helper programs.
spec.GDB = []string{"gdb-multiarch"}
if goarch == "arm" && goos == "linux" {
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
spec.Linker = "arm-linux-gnueabihf-gcc"
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
spec.Emulator = []string{"qemu-arm"}
}
if goarch == "arm64" && goos == "linux" {
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu")
spec.Linker = "aarch64-linux-gnu-gcc"
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
}
if goarch == "386" && runtime.GOARCH == "amd64" {
spec.CFlags = append(spec.CFlags, "-m32")
spec.LDFlags = append(spec.LDFlags, "-m32")
spec.Emulator = []string{"qemu-aarch64"}
}
}
return &spec, nil
+4 -3
View File
@@ -1,22 +1,23 @@
package compileopts
import (
"os"
"reflect"
"testing"
)
func TestLoadTarget(t *testing.T) {
_, err := LoadTarget("arduino")
_, err := LoadTarget(&Options{Target: "arduino"})
if err != nil {
t.Error("LoadTarget test failed:", err)
}
_, err = LoadTarget("notexist")
_, err = LoadTarget(&Options{Target: "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" {
if !os.IsNotExist(err) {
t.Error("LoadTarget failed for wrong reason:", err)
}
}
+102
View File
@@ -0,0 +1,102 @@
package compiler
// This file defines alias functions for functions that are normally defined in
// Go assembly.
//
// The Go toolchain defines many performance critical functions in assembly
// instead of plain Go. This is a problem for TinyGo as it currently (as of
// august 2021) is not able to compile these assembly files and even if it
// could, it would not be able to make use of them for many targets that are
// supported by TinyGo (baremetal RISC-V, AVR, etc). Therefore, many of these
// functions are aliased to their generic Go implementation.
// This results in slower than possible implementations, but at least they are
// usable.
import "tinygo.org/x/go-llvm"
var stdlibAliases = map[string]string{
// crypto packages
"crypto/md5.block": "crypto/md5.blockGeneric",
"crypto/sha1.block": "crypto/sha1.blockGeneric",
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
"crypto/sha256.block": "crypto/sha256.blockGeneric",
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
// math package
"math.Asin": "math.asin",
"math.Asinh": "math.asinh",
"math.Acos": "math.acos",
"math.Acosh": "math.acosh",
"math.Atan": "math.atan",
"math.Atanh": "math.atanh",
"math.Atan2": "math.atan2",
"math.Cbrt": "math.cbrt",
"math.Ceil": "math.ceil",
"math.archCeil": "math.ceil",
"math.Cos": "math.cos",
"math.Cosh": "math.cosh",
"math.Erf": "math.erf",
"math.Erfc": "math.erfc",
"math.Exp": "math.exp",
"math.archExp": "math.exp",
"math.Expm1": "math.expm1",
"math.Exp2": "math.exp2",
"math.archExp2": "math.exp2",
"math.Floor": "math.floor",
"math.archFloor": "math.floor",
"math.Frexp": "math.frexp",
"math.Hypot": "math.hypot",
"math.archHypot": "math.hypot",
"math.Ldexp": "math.ldexp",
"math.Log": "math.log",
"math.archLog": "math.log",
"math.Log1p": "math.log1p",
"math.Log10": "math.log10",
"math.Log2": "math.log2",
"math.Max": "math.max",
"math.archMax": "math.max",
"math.Min": "math.min",
"math.archMin": "math.min",
"math.Mod": "math.mod",
"math.Modf": "math.modf",
"math.archModf": "math.modf",
"math.Pow": "math.pow",
"math.Remainder": "math.remainder",
"math.Sin": "math.sin",
"math.Sinh": "math.sinh",
"math.Sqrt": "math.sqrt",
"math.archSqrt": "math.sqrt",
"math.Tan": "math.tan",
"math.Tanh": "math.tanh",
"math.Trunc": "math.trunc",
"math.archTrunc": "math.trunc",
}
// createAlias implements the function (in the builder) as a call to the alias
// function.
func (b *builder) createAlias(alias llvm.Value) {
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
b.llvmFn.SetUnnamedAddr(true)
if b.Debug {
if b.fn.Syntax() != nil {
// Create debug info file if present.
b.difunc = b.attachDebugInfo(b.fn)
}
pos := b.program.Fset.Position(b.fn.Pos())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
entryBlock := llvm.AddBasicBlock(b.llvmFn, "entry")
b.SetInsertPointAtEnd(entryBlock)
if b.llvmFn.Type() != alias.Type() {
b.addError(b.fn.Pos(), "alias function should have the same type as aliasee "+alias.Name())
b.CreateUnreachable()
return
}
result := b.CreateCall(alias, b.llvmFn.Params(), "")
if result.Type().TypeKind() == llvm.VoidTypeKind {
b.CreateRetVoid()
} else {
b.CreateRet(result)
}
}
+66 -2
View File
@@ -101,6 +101,55 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
}
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
// conversions. This conversion was added in Go 1.17. For details, see:
// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) {
// From the spec:
// > If the length of the slice is less than the length of the array, a
// > run-time panic occurs.
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
}
// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
// function must panic if the ptr/len parameters are invalid.
func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, lenType *types.Basic) {
// From the documentation of unsafe.Slice:
// > At run time, if len is negative, or if ptr is nil and len is not
// > zero, a run-time panic occurs.
// However, in practice, it is also necessary to check that the length is
// not too big that a GEP wouldn't be possible without wrapping the pointer.
// These two checks (non-negative and not too big) can be merged into one
// using an unsiged greater than.
// Make sure the len value is at least as big as a uintptr.
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
if lenType.Info()&types.IsUnsigned != 0 {
len = b.CreateZExt(len, b.uintptrType, "")
} else {
len = b.CreateSExt(len, b.uintptrType, "")
}
}
// Determine the maximum slice size, and therefore the maximum value of the
// len parameter.
maxSize := b.maxSliceSize(ptr.Type().ElementType())
maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false)
// Do the check. By using unsigned greater than for the length check, signed
// negative values are also checked (which are very large numbers when
// interpreted as signed values).
zero := llvm.ConstInt(len.Type(), 0, false)
lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "")
ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "")
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
assert = b.CreateOr(assert, lenOutOfBounds, "")
b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic")
}
// createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake.
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
@@ -115,11 +164,9 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
if bufSizeType.Info()&types.IsUnsigned != 0 {
// Unsigned, so zero-extend to uint type.
bufSizeType = types.Typ[types.Uint]
bufSize = b.CreateZExt(bufSize, b.intType, "")
} else {
// Signed, so sign-extend to int type.
bufSizeType = types.Typ[types.Int]
bufSize = b.CreateSExt(bufSize, b.intType, "")
}
}
@@ -162,6 +209,10 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
case *ssa.Alloc:
// An alloc is never nil.
return
case *ssa.FreeVar:
// A free variable is allocated in a parent function and is thus never
// nil.
return
case *ssa.IndexAddr:
// This pointer is the result of an index operation into a slice or
// array. Such slices/arrays are already bounds checked so the pointer
@@ -202,6 +253,19 @@ func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
}
// createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic
// will be emitted. This follows the Go specification which says that a divide
// by zero must cause a run time panic.
func (b *builder) createDivideByZeroCheck(y llvm.Value) {
if b.info.nobounds {
return
}
// isZero = y == 0
isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "")
b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic")
}
// createRuntimeAssert is a common function to create a new branch on an assert
// bool, calling an assert func if the assert value is true (1).
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
+2 -3
View File
@@ -38,11 +38,10 @@ func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
old := b.getValue(call.Args[1])
newVal := b.getValue(call.Args[2])
if strings.HasSuffix(name, "64") {
arch := strings.Split(b.Triple, "-")[0]
if strings.HasPrefix(arch, "arm") && strings.HasSuffix(arch, "m") {
if strings.HasPrefix(b.Triple, "thumb") {
// Work around a bug in LLVM, at least LLVM 11:
// https://reviews.llvm.org/D95891
// Check for armv6m, armv7, armv7em, and perhaps others.
// Check for thumbv6m, thumbv7, thumbv7em, and perhaps others.
// See also: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
compareAndSwap := b.mod.NamedFunction("__sync_val_compare_and_swap_8")
if compareAndSwap.IsNil() {
+2 -2
View File
@@ -63,10 +63,10 @@ func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType
case llvm.StructTypeKind:
fieldInfos := c.flattenAggregateType(t, name, goType)
if len(fieldInfos) <= maxFieldsPerParam {
// managed to expand this parameter
return fieldInfos
} else {
// failed to lower
}
// failed to expand this parameter: too many fields
}
// TODO: split small arrays
return []paramInfo{
+28 -9
View File
@@ -32,8 +32,15 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// store value-to-send
valueType := b.getLLVMType(instr.X.Type())
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
}
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
@@ -46,7 +53,9 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if !isZeroSize {
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
}
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
@@ -56,7 +65,14 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
ch := b.getValue(unop.X)
// Allocate memory to receive into.
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
}
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
@@ -64,9 +80,14 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
// Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
received := b.CreateLoad(valueAlloca, "chan.received")
var received llvm.Value
if isZeroSize {
received = llvm.ConstNull(valueType)
} else {
received = b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
@@ -116,7 +137,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// determine the receive buffer size and alignment.
recvbufSize := uint64(0)
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
@@ -133,7 +153,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align
}
hasReceives = true
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
@@ -150,7 +169,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.i8ptrType)
if hasReceives {
if recvbufSize != 0 {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
+230 -104
View File
@@ -23,7 +23,7 @@ import (
// Version of the compiler pacakge. Must be incremented each time the compiler
// package changes in a way that affects the generated LLVM module.
// This version is independent of the TinyGo version number.
const Version = 9 // last change: implement reflect.New()
const Version = 24 // last change: add layout param to runtime.alloc calls
func init() {
llvm.InitializeAllTargets()
@@ -33,9 +33,6 @@ func init() {
llvm.InitializeAllAsmPrinters()
}
// The TinyGo import path.
const tinygoPath = "github.com/tinygo-org/tinygo"
// Config is the configuration for the compiler. Most settings should be copied
// directly from compileopts.Config, it recreated here to decouple the compiler
// package a bit and because it makes caching easier.
@@ -51,6 +48,7 @@ type Config struct {
GOARCH string
CodeModel string
RelocationModel string
SizeLevel int
// Various compiler options that determine how code is generated.
Scheduler string
@@ -74,15 +72,18 @@ type compilerContext struct {
cu llvm.Metadata
difiles map[string]llvm.Metadata
ditypes map[types.Type]llvm.Metadata
llvmTypes map[types.Type]llvm.Type
machine llvm.TargetMachine
targetData llvm.TargetData
intType llvm.Type
i8ptrType llvm.Type // for convenience
rawVoidFuncType llvm.Type // for convenience
funcPtrAddrSpace int
uintptrType llvm.Type
program *ssa.Program
diagnostics []error
astComments map[string]*ast.CommentGroup
pkg *types.Package
runtimePkg *types.Package
}
@@ -94,6 +95,7 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
DumpSSA: dumpSSA,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
llvmTypes: make(map[types.Type]llvm.Type),
machine: machine,
targetData: machine.CreateTargetData(),
astComments: map[string]*ast.CommentGroup{},
@@ -122,6 +124,7 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false)
dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType)
c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace()
c.rawVoidFuncType = dummyFunc.Type()
dummyFunc.EraseFromParentAsFunction()
return c
@@ -139,7 +142,6 @@ type builder struct {
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
currentBlock *ssa.BasicBlock
phis []phiNode
taskHandle llvm.Value
deferPtr llvm.Value
difunc llvm.Metadata
dilocals map[*types.Var]llvm.Metadata
@@ -231,10 +233,6 @@ func Sizes(machine llvm.TargetMachine) types.Sizes {
targetData := machine.CreateTargetData()
defer targetData.Dispose()
intPtrType := targetData.IntPtrType()
if intPtrType.IntTypeWidth()/8 <= 32 {
}
var intWidth int
if targetData.PointerSize() <= 4 {
// 8, 16, 32 bits targets
@@ -249,13 +247,14 @@ func Sizes(machine llvm.TargetMachine) types.Sizes {
return &stdSizes{
IntSize: int64(intWidth / 8),
PtrSize: int64(targetData.PointerSize()),
MaxAlign: int64(targetData.PrefTypeAlignment(intPtrType)),
MaxAlign: int64(targetData.PrefTypeAlignment(targetData.IntPtrType())),
}
}
// CompilePackage compiles a single package to a LLVM module.
func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) {
c := newCompilerContext(moduleName, machine, config, dumpSSA)
c.pkg = pkg.Pkg
c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg
c.program = ssaPkg.Prog
@@ -289,14 +288,14 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
if c.Debug {
c.mod.AddNamedMetadataOperand("llvm.module.flags",
c.ctx.MDNode([]llvm.Metadata{
llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(), // Error on mismatch
llvm.ConstInt(c.ctx.Int32Type(), 2, false).ConstantAsMetadata(), // Warning on mismatch
c.ctx.MDString("Debug Info Version"),
llvm.ConstInt(c.ctx.Int32Type(), 3, false).ConstantAsMetadata(), // DWARF version
}),
)
c.mod.AddNamedMetadataOperand("llvm.module.flags",
c.ctx.MDNode([]llvm.Metadata{
llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(),
llvm.ConstInt(c.ctx.Int32Type(), 7, false).ConstantAsMetadata(), // Max on mismatch
c.ctx.MDString("Dwarf Version"),
llvm.ConstInt(c.ctx.Int32Type(), 4, false).ConstantAsMetadata(),
}),
@@ -315,10 +314,23 @@ func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
return c.getLLVMType(typ)
}
// getLLVMType creates and returns a LLVM type for a Go type. In the case of
// named struct types (or Go types implemented as named LLVM structs such as
// strings) it also creates it first if necessary.
// getLLVMType returns a LLVM type for a Go type. It doesn't recreate already
// created types. This is somewhat important for performance, but especially
// important for named struct types (which should only be created once).
func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
// Try to load the LLVM type from the cache.
if t, ok := c.llvmTypes[goType]; ok {
return t
}
// Not already created, so adding this type to the cache.
llvmType := c.makeLLVMType(goType)
c.llvmTypes[goType] = llvmType
return llvmType
}
// makeLLVMType creates a LLVM type for a Go type. Don't call this, use
// getLLVMType instead.
func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
switch typ := goType.(type) {
case *types.Array:
elemType := c.getLLVMType(typ.Elem())
@@ -367,12 +379,10 @@ func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
// LLVM. This is because it is otherwise impossible to create
// self-referencing types such as linked lists.
llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name()
llvmType := c.mod.GetTypeByName(llvmName)
if llvmType.IsNil() {
llvmType = c.ctx.StructCreateNamed(llvmName)
underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
}
llvmType := c.ctx.StructCreateNamed(llvmName)
c.llvmTypes[goType] = llvmType // avoid infinite recursion
underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
return llvmType
}
return c.getLLVMType(typ.Underlying())
@@ -439,7 +449,7 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata {
AlignInBits: uint32(c.targetData.ABITypeAlignment(llvmType)) * 8,
ElementType: c.getDIType(typ.Elem()),
Subscripts: []llvm.DISubrange{
llvm.DISubrange{
{
Lo: 0,
Count: typ.Len(),
},
@@ -715,11 +725,11 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
// Create the function definition.
b := newBuilder(c, irbuilder, member)
if member.Blocks == nil {
continue // external function
}
// Create the function definition.
b := newBuilder(c, irbuilder, member)
b.createFunction()
case *ssa.Type:
if types.IsInterface(member.Type()) {
@@ -758,10 +768,36 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
case *ssa.Global:
// Global variable.
info := c.getGlobalInfo(member)
global := c.getGlobal(member)
if !info.extern {
global := c.getGlobal(member)
global.SetInitializer(llvm.ConstNull(global.Type().ElementType()))
global.SetVisibility(llvm.HiddenVisibility)
if info.section != "" {
global.SetSection(info.section)
}
}
}
}
// Add forwarding functions for functions that would otherwise be
// implemented in assembly.
for _, name := range members {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
if member.Blocks != nil {
continue // external function
}
info := c.getFunctionInfo(member)
if aliasName, ok := stdlibAliases[info.linkName]; ok {
alias := c.mod.NamedFunction(aliasName)
if alias.IsNil() {
// Shouldn't happen, but perhaps best to just ignore.
// The error will be a link error, if there is an error.
continue
}
b := newBuilder(c, irbuilder, member)
b.createAlias(alias)
}
}
}
@@ -783,10 +819,14 @@ func (b *builder) createFunction() {
b.addError(b.fn.Pos(), errValue)
return
}
b.addStandardDefinedAttributes(b.llvmFn)
if !b.info.exported {
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
b.llvmFn.SetUnnamedAddr(true)
}
if b.info.section != "" {
b.llvmFn.SetSection(b.info.section)
}
if b.info.exported && strings.HasPrefix(b.Triple, "wasm") {
// Set the exported name. This is necessary for WebAssembly because
// otherwise the function is not exported.
@@ -806,6 +846,17 @@ func (b *builder) createFunction() {
b.llvmFn.AddFunctionAttr(noinline)
}
if b.info.interrupt {
// Mark this function as an interrupt.
// This is necessary on MCUs that don't push caller saved registers when
// entering an interrupt, such as on AVR.
if strings.HasPrefix(b.Triple, "avr") {
b.llvmFn.AddFunctionAttr(b.ctx.CreateStringAttribute("signal", ""))
} else {
b.addError(b.fn.Pos(), "//go:interrupt not supported on this architecture")
}
}
// Add debug info, if needed.
if b.Debug {
if b.fn.Synthetic == "package initializer" {
@@ -994,6 +1045,8 @@ func getPos(val posser) token.Pos {
switch val := val.(type) {
case *ssa.MakeInterface:
return getPos(val.X)
case *ssa.MakeClosure:
return val.Fn.(*ssa.Function).Pos()
case *ssa.Return:
syntax := val.Parent().Syntax()
if syntax != nil {
@@ -1048,52 +1101,8 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
case *ssa.Defer:
b.createDefer(instr)
case *ssa.Go:
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.getValue(param))
}
// Start a new goroutine.
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
// Goroutine call is regular function call. No context is necessary.
context = llvm.Undef(b.i8ptrType)
case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue := b.getValue(value)
context = b.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
params = append(params, context) // context parameter
b.createGoInstruction(b.getFunction(callee), params, "", callee.Pos())
} else if !instr.Call.IsInvoke() {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
funcPtr, context := b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context) // context parameter
switch b.Scheduler {
case "none", "coroutines":
// There are no additional parameters needed for the goroutine start operation.
case "tasks":
// Add the function pointer as a parameter to start the goroutine.
params = append(params, funcPtr)
default:
panic("unknown scheduler type")
}
b.createGoInstruction(funcPtr, params, b.fn.RelString(nil), instr.Pos())
} else {
b.addError(instr.Pos(), "todo: go on interface call")
}
b.createGo(instr)
case *ssa.If:
cond := b.getValue(instr.Cond)
block := instr.Block()
@@ -1306,6 +1315,38 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
case "ssa:wrapnilchk":
// TODO: do an actual nil check?
return argValues[0], nil
// Builtins from the unsafe package.
case "Add": // unsafe.Add
// This is basically just a GEP operation.
// Note: the pointer is always of type *i8.
ptr := argValues[0]
len := argValues[1]
return b.CreateGEP(ptr, []llvm.Value{len}, ""), nil
case "Slice": // unsafe.Slice
// This creates a slice from a pointer and a length.
// Note that the exception mentioned in the documentation (if the
// pointer and length are nil, the slice is also nil) is trivially
// already the case.
ptr := argValues[0]
len := argValues[1]
slice := llvm.Undef(b.ctx.StructType([]llvm.Type{
ptr.Type(),
b.uintptrType,
b.uintptrType,
}, false))
b.createUnsafeSliceCheck(ptr, len, argTypes[1].Underlying().(*types.Basic))
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
// Too small, zero-extend len.
len = b.CreateZExt(len, b.uintptrType, "")
} else if len.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
// Too big, truncate len.
len = b.CreateTrunc(len, b.uintptrType, "")
}
slice = b.CreateInsertValue(slice, ptr, 0, "")
slice = b.CreateInsertValue(slice, len, 1, "")
slice = b.CreateInsertValue(slice, len, 2, "")
return slice, nil
default:
return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName)
}
@@ -1317,9 +1358,9 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
//
// This is also where compiler intrinsics are implemented.
func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) {
if instr.IsInvoke() {
fnCast, args := b.getInvokeCall(instr)
return b.createCall(fnCast, args, ""), nil
var params []llvm.Value
for _, param := range instr.Args {
params = append(params, b.getValue(param))
}
// Try to call the function directly for trivially static calls.
@@ -1334,6 +1375,11 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.createMemoryCopyCall(fn, instr.Args)
case name == "runtime.memzero":
return b.createMemoryZeroCall(instr.Args)
case name == "math.Ceil" || name == "math.Floor" || name == "math.Sqrt" || name == "math.Trunc":
result, ok := b.createMathOp(instr)
if ok {
return result, nil
}
case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
return b.createInlineAsm(instr.Args)
case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
@@ -1346,6 +1392,8 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.emitCSROperation(instr)
case strings.HasPrefix(name, "syscall.Syscall"):
return b.createSyscall(instr)
case strings.HasPrefix(name, "syscall.rawSyscallNoError"):
return b.createRawSyscallNoError(instr)
case strings.HasPrefix(name, "runtime/volatile.Load"):
return b.createVolatileLoad(instr)
case strings.HasPrefix(name, "runtime/volatile.Store"):
@@ -1383,12 +1431,20 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
} else if call, ok := instr.Value.(*ssa.Builtin); ok {
// Builtin function (append, close, delete, etc.).)
var argTypes []types.Type
var argValues []llvm.Value
for _, arg := range instr.Args {
argTypes = append(argTypes, arg.Type())
argValues = append(argValues, b.getValue(arg))
}
return b.createBuiltin(argTypes, argValues, call.Name(), instr.Pos())
return b.createBuiltin(argTypes, params, call.Name(), instr.Pos())
} else if instr.IsInvoke() {
// Interface method call (aka invoke call).
itf := b.getValue(instr.Value) // interface value (runtime._interface)
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
value := b.CreateExtractValue(itf, 1, "invoke.func.value") // receiver
// Prefix the params with receiver value and suffix with typecode.
params = append([]llvm.Value{value}, params...)
params = append(params, typecode)
callee = b.getInvokeFunction(instr)
context = llvm.Undef(b.i8ptrType)
} else {
// Function pointer.
value := b.getValue(instr.Value)
@@ -1398,11 +1454,6 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
b.createNilCheck(instr.Value, callee, "fpcall")
}
var params []llvm.Value
for _, param := range instr.Args {
params = append(params, b.getValue(param))
}
if !exported {
// This function takes a context parameter.
// Add it to the end of the parameter list.
@@ -1420,7 +1471,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
func (b *builder) getValue(expr ssa.Value) llvm.Value {
switch expr := expr.(type) {
case *ssa.Const:
return b.createConst(b.info.linkName, expr)
return b.createConst(expr)
case *ssa.Function:
if b.getFunctionInfo(expr).exported {
b.addError(expr.Pos(), "cannot use an exported function as value: "+expr.String())
@@ -1445,6 +1496,28 @@ func (b *builder) getValue(expr ssa.Value) llvm.Value {
}
}
// maxSliceSize determines the maximum size a slice of the given element type
// can be.
func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 {
// Calculate ^uintptr(0), which is the max value that fits in uintptr.
maxPointerValue := llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)).ZExtValue()
// Calculate (^uint(0))/2, which is the max value that fits in an int.
maxIntegerValue := llvm.ConstNot(llvm.ConstInt(c.intType, 0, false)).ZExtValue() / 2
// Determine the maximum allowed size for a slice. The biggest possible
// pointer (starting from 0) would be maxPointerValue*sizeof(elementType) so
// divide by the element type to get the real maximum size.
maxSize := maxPointerValue / c.targetData.TypeAllocSize(elementType)
// len(slice) is an int. Make sure the length remains small enough to fit in
// an int.
if maxSize > maxIntegerValue {
maxSize = maxIntegerValue
}
return maxSize
}
// createExpr translates a Go SSA expression to LLVM IR. This can be zero, one,
// or multiple LLVM IR instructions and/or runtime calls.
func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
@@ -1465,7 +1538,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size))
}
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment)
layoutValue := b.createObjectLayout(typ, expr.Pos())
buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, layoutValue}, expr.Comment)
buf = b.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
return buf, nil
} else {
@@ -1658,10 +1732,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
elemSize := b.targetData.TypeAllocSize(llvmElemType)
elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false)
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in
// uintptr if uintptr were signed.
maxSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false))
if elemSize > maxSize.ZExtValue() {
maxSize := b.maxSliceSize(llvmElemType)
if elemSize > maxSize {
// This seems to be checked by the typechecker already, but let's
// check it again just to be sure.
return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize))
@@ -1670,7 +1742,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// Bounds checking.
lenType := expr.Len.Type().Underlying().(*types.Basic)
capType := expr.Cap.Type().Underlying().(*types.Basic)
b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
maxSizeValue := llvm.ConstInt(b.uintptrType, maxSize, false)
b.createSliceBoundsCheck(maxSizeValue, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
// Allocate the backing array.
sliceCapCast, err := b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
@@ -1678,7 +1751,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
return llvm.Value{}, err
}
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize}, "makeslice.buf")
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize, layoutValue}, "makeslice.buf")
slicePtr = b.CreateBitCast(slicePtr, llvm.PointerType(llvmElemType, 0), "makeslice.array")
// Extend or truncate if necessary. This is safe as we've already done
@@ -1913,6 +1987,17 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
default:
return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String())
}
case *ssa.SliceToArrayPointer:
// Conversion from a slice to an array pointer, as the name clearly
// says. This requires a runtime check to make sure the slice is at
// least as big as the array.
slice := b.getValue(expr.X)
sliceLen := b.CreateExtractValue(slice, 1, "")
arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len()
b.createSliceToArrayPointerCheck(sliceLen, arrayLen)
ptr := b.CreateExtractValue(slice, 0, "")
ptr = b.CreateBitCast(ptr, b.getLLVMType(expr.Type()), "")
return ptr, nil
case *ssa.TypeAssert:
return b.createTypeAssert(expr), nil
case *ssa.UnOp:
@@ -1941,17 +2026,58 @@ func (b *builder) createBinOp(op token.Token, typ, ytyp types.Type, x, y llvm.Va
return b.CreateSub(x, y, ""), nil
case token.MUL: // *
return b.CreateMul(x, y, ""), nil
case token.QUO: // /
case token.QUO, token.REM: // /, %
// Check for a divide by zero. If y is zero, the Go
// specification says that a runtime error must be triggered.
b.createDivideByZeroCheck(y)
if signed {
return b.CreateSDiv(x, y, ""), nil
// Deal with signed division overflow.
// The LLVM LangRef says:
//
// Overflow also leads to undefined behavior; this is a
// rare case, but can occur, for example, by doing a
// 32-bit division of -2147483648 by -1.
//
// The Go specification however says this about division:
//
// The one exception to this rule is that if the dividend
// x is the most negative value for the int type of x, the
// quotient q = x / -1 is equal to x (and r = 0) due to
// two's-complement integer overflow.
//
// In other words, in the special case that the lowest
// possible signed integer is divided by -1, the result of
// the division is the same as x (the dividend).
// This is implemented by checking for this condition and
// changing y to 1 if it occurs, for example for 32-bit
// ints:
//
// if x == -2147483648 && y == -1 {
// y = 1
// }
//
// Dividing x by 1 obviously returns x, therefore satisfying
// the Go specification without a branch.
llvmType := x.Type()
minusOne := llvm.ConstSub(llvm.ConstInt(llvmType, 0, false), llvm.ConstInt(llvmType, 1, false))
lowestInteger := llvm.ConstInt(x.Type(), 1<<(llvmType.IntTypeWidth()-1), false)
yIsMinusOne := b.CreateICmp(llvm.IntEQ, y, minusOne, "")
xIsLowestInteger := b.CreateICmp(llvm.IntEQ, x, lowestInteger, "")
hasOverflow := b.CreateAnd(yIsMinusOne, xIsLowestInteger, "")
y = b.CreateSelect(hasOverflow, llvm.ConstInt(llvmType, 1, true), y, "")
if op == token.QUO {
return b.CreateSDiv(x, y, ""), nil
} else {
return b.CreateSRem(x, y, ""), nil
}
} else {
return b.CreateUDiv(x, y, ""), nil
}
case token.REM: // %
if signed {
return b.CreateSRem(x, y, ""), nil
} else {
return b.CreateURem(x, y, ""), nil
if op == token.QUO {
return b.CreateUDiv(x, y, ""), nil
} else {
return b.CreateURem(x, y, ""), nil
}
}
case token.AND: // &
return b.CreateAnd(x, y, ""), nil
@@ -2298,7 +2424,7 @@ func (b *builder) createBinOp(op token.Token, typ, ytyp types.Type, x, y llvm.Va
}
// createConst creates a LLVM constant value from a Go constant.
func (b *builder) createConst(prefix string, expr *ssa.Const) llvm.Value {
func (b *builder) createConst(expr *ssa.Const) llvm.Value {
switch typ := expr.Type().Underlying().(type) {
case *types.Basic:
llvmType := b.getLLVMType(typ)
@@ -2314,7 +2440,7 @@ func (b *builder) createConst(prefix string, expr *ssa.Const) llvm.Value {
strLen := llvm.ConstInt(b.uintptrType, uint64(len(str)), false)
var strPtr llvm.Value
if str != "" {
objname := prefix + "$string"
objname := b.pkg.Path() + "$string"
global := llvm.AddGlobal(b.mod, llvm.ArrayType(b.ctx.Int8Type(), len(str)), objname)
global.SetInitializer(b.ctx.ConstString(str, false))
global.SetLinkage(llvm.InternalLinkage)
@@ -2344,15 +2470,15 @@ func (b *builder) createConst(prefix string, expr *ssa.Const) llvm.Value {
n, _ := constant.Float64Val(expr.Value)
return llvm.ConstFloat(llvmType, n)
} else if typ.Kind() == types.Complex64 {
r := b.createConst(prefix, ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float32]))
i := b.createConst(prefix, ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float32]))
r := b.createConst(ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float32]))
i := b.createConst(ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float32]))
cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.FloatType(), b.ctx.FloatType()}, false))
cplx = b.CreateInsertValue(cplx, r, 0, "")
cplx = b.CreateInsertValue(cplx, i, 1, "")
return cplx
} else if typ.Kind() == types.Complex128 {
r := b.createConst(prefix, ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float64]))
i := b.createConst(prefix, ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float64]))
r := b.createConst(ssa.NewConst(constant.Real(expr.Value), types.Typ[types.Float64]))
i := b.createConst(ssa.NewConst(constant.Imag(expr.Value), types.Typ[types.Float64]))
cplx := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.DoubleType(), b.ctx.DoubleType()}, false))
cplx = b.CreateInsertValue(cplx, r, 0, "")
cplx = b.CreateInsertValue(cplx, i, 1, "")
+74 -37
View File
@@ -9,6 +9,7 @@ import (
"testing"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/loader"
"tinygo.org/x/go-llvm"
)
@@ -16,9 +17,16 @@ import (
// Pass -update to go test to update the output of the test files.
var flagUpdate = flag.Bool("update", false, "update tests based on test output")
type testCase struct {
file string
target string
}
// Basic tests for the compiler. Build some Go files and compare the output with
// the expected LLVM IR for regression testing.
func TestCompiler(t *testing.T) {
t.Parallel()
// Check LLVM version.
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if err != nil {
@@ -32,43 +40,68 @@ func TestCompiler(t *testing.T) {
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
}
target, err := compileopts.LoadTarget("wasm")
if err != nil {
t.Fatal("failed to load target:", err)
}
config := &compileopts.Config{
Options: &compileopts.Options{},
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
tests := []testCase{
{"basic.go", ""},
{"pointer.go", ""},
{"slice.go", ""},
{"string.go", ""},
{"float.go", ""},
{"interface.go", ""},
{"func.go", ""},
{"pragma.go", ""},
{"goroutine.go", "wasm"},
{"goroutine.go", "cortex-m-qemu"},
{"channel.go", ""},
{"intrinsics.go", "cortex-m-qemu"},
{"intrinsics.go", "wasm"},
{"gc.go", ""},
}
tests := []string{
"basic.go",
"pointer.go",
"slice.go",
"string.go",
"float.go",
"interface.go",
"func.go",
_, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
if err != nil {
t.Fatal("could not read Go version:", err)
}
if minor >= 17 {
tests = append(tests, testCase{"go1.17.go", ""})
}
for _, testCase := range tests {
t.Run(testCase, func(t *testing.T) {
for _, tc := range tests {
name := tc.file
targetString := "wasm"
if tc.target != "" {
targetString = tc.target
name = tc.file + "-" + tc.target
}
t.Run(name, func(t *testing.T) {
options := &compileopts.Options{
Target: targetString,
}
target, err := compileopts.LoadTarget(options)
if err != nil {
t.Fatal("failed to load target:", err)
}
config := &compileopts.Config{
Options: options,
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
}
// Load entire program AST into memory.
lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, config.ClangHeaders, types.Config{
lprogram, err := loader.Load(config, []string{"./testdata/" + tc.file}, config.ClangHeaders, types.Config{
Sizes: Sizes(machine),
})
if err != nil {
@@ -76,13 +109,13 @@ func TestCompiler(t *testing.T) {
}
err = lprogram.Parse()
if err != nil {
t.Fatalf("could not parse test case %s: %s", testCase, err)
t.Fatalf("could not parse test case %s: %s", tc.file, err)
}
// Compile AST to IR.
program := lprogram.LoadSSA()
pkg := lprogram.MainPkg()
mod, errs := CompilePackage(testCase, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
mod, errs := CompilePackage(tc.file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
if errs != nil {
for _, err := range errs {
t.Error(err)
@@ -105,18 +138,22 @@ func TestCompiler(t *testing.T) {
}
funcPasses.FinalizeFunc()
outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll"
outFilePrefix := tc.file[:len(tc.file)-3]
if tc.target != "" {
outFilePrefix += "-" + tc.target
}
outPath := "./testdata/" + outFilePrefix + ".ll"
// Update test if needed. Do not check the result.
if *flagUpdate {
err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666)
err := ioutil.WriteFile(outPath, []byte(mod.String()), 0666)
if err != nil {
t.Error("failed to write updated output file:", err)
}
return
}
expected, err := ioutil.ReadFile(outfile)
expected, err := ioutil.ReadFile(outPath)
if err != nil {
t.Fatal("failed to read golden file:", err)
}
+6 -5
View File
@@ -217,7 +217,8 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// This may be hit a variable number of times, so use a heap allocation.
size := b.targetData.TypeAllocSize(deferFrameType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
nilPtr := llvm.ConstNull(b.i8ptrType)
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
}
if b.NeedsStackObjects {
@@ -331,10 +332,10 @@ func (b *builder) createRunDefers() {
//Pass context
forwardParams = append(forwardParams, context)
} else {
// Isolate the typecode.
typecode := forwardParams[0]
forwardParams = forwardParams[1:]
fnPtr = b.getInvokePtr(callback, typecode)
// Move typecode from the start to the end of the list of
// parameters.
forwardParams = append(forwardParams[1:], forwardParams[0])
fnPtr = b.getInvokeFunction(callback)
// Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms
+1 -11
View File
@@ -3,7 +3,6 @@ package compiler
// This file contains some utility functions related to error handling.
import (
"go/scanner"
"go/token"
"go/types"
"path/filepath"
@@ -20,20 +19,11 @@ func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
}
}
// addError adds a new compiler diagnostic with the given location and message.
func (c *compilerContext) addError(pos token.Pos, msg string) {
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
}
// errorAt returns an error value at the location of the instruction.
// The location information may not be complete as it depends on debug
// information in the IR.
func errorAt(inst llvm.Value, msg string) scanner.Error {
return scanner.Error{
Pos: getPosition(inst),
Msg: msg,
}
}
// getPosition returns the position information for the given value, as far as
// it is available.
func getPosition(val llvm.Value) token.Position {
+16 -4
View File
@@ -23,7 +23,7 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
switch c.FuncImplementation {
case "doubleword":
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
funcValueScalar = llvm.ConstBitCast(funcPtr, c.rawVoidFuncType)
case "switch":
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
@@ -80,8 +80,21 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
context = b.CreateExtractValue(funcValue, 0, "")
switch b.FuncImplementation {
case "doubleword":
funcPtr = b.CreateExtractValue(funcValue, 1, "")
bitcast := b.CreateExtractValue(funcValue, 1, "")
if !bitcast.IsAConstantExpr().IsNil() && bitcast.Opcode() == llvm.BitCast {
funcPtr = bitcast.Operand(0)
return
}
llvmSig := b.getRawFuncType(sig)
funcPtr = b.CreateBitCast(bitcast, llvmSig, "")
case "switch":
if !funcValue.IsAConstant().IsNil() {
// If this is a constant func value, the underlying function is
// known and can be returned directly.
funcValueWithSignatureGlobal := llvm.ConstExtractValue(funcValue, []uint32{1}).Operand(0)
funcPtr = llvm.ConstExtractValue(funcValueWithSignatureGlobal.Initializer(), []uint32{0}).Operand(0)
return
}
llvmSig := b.getRawFuncType(sig)
sigGlobal := b.getFuncSignatureID(sig)
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
@@ -96,8 +109,7 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
switch c.FuncImplementation {
case "doubleword":
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, c.rawVoidFuncType}, false)
case "switch":
return c.getLLVMRuntimeType("funcValue")
default:
+122 -16
View File
@@ -5,25 +5,115 @@ package compiler
import (
"go/token"
"go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createGoInstruction starts a new goroutine with the provided function pointer
// and parameters.
// In general, you should pass all regular parameters plus the context parameter.
// There is one exception: the task-based scheduler needs to have the function
// pointer passed in as a parameter too in addition to the context.
//
// Because a go statement doesn't return anything, return undef.
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
// createGo emits code to start a new goroutine.
func (b *builder) createGo(instr *ssa.Go) {
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.getValue(param))
}
var prefix string
var funcPtr llvm.Value
hasContext := false
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
// Goroutine call is regular function call. No context is necessary.
if b.Scheduler == "coroutines" {
// The context parameter is assumed to be always present in the
// coroutines scheduler.
context = llvm.Undef(b.i8ptrType)
}
case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue := b.getValue(value)
context = b.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
if !context.IsNil() {
params = append(params, context) // context parameter
hasContext = true
}
funcPtr = b.getFunction(callee)
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
// We cheat. None of the builtins do any long or blocking operation, so
// we might as well run these builtins right away without the program
// noticing the difference.
// Possible exceptions:
// - copy: this is a possibly long operation, but not a blocking
// operation. Semantically it makes no difference to run it right
// away (not in a goroutine). However, in practice it makes no sense
// to run copy in a goroutine as there is no way to (safely) know
// when it is finished.
// - panic: the error message would appear in the parent goroutine.
// But because `go panic("err")` would halt the program anyway
// (there is no recover), panicking right away would give the same
// behavior as creating a goroutine, switching the scheduler to that
// goroutine, and panicking there. So this optimization seems
// correct.
// - recover: because it runs in a new goroutine, it is never a
// deferred function. Thus this is a no-op.
if builtin.Name() == "recover" {
// This is a no-op, even in a deferred function:
// go recover()
return
}
var argTypes []types.Type
var argValues []llvm.Value
for _, arg := range instr.Call.Args {
argTypes = append(argTypes, arg.Type())
argValues = append(argValues, b.getValue(arg))
}
b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
return
} else if instr.Call.IsInvoke() {
// This is a method call on an interface value.
itf := b.getValue(instr.Call.Value)
itfTypeCode := b.CreateExtractValue(itf, 0, "")
itfValue := b.CreateExtractValue(itf, 1, "")
funcPtr = b.getInvokeFunction(&instr.Call)
params = append([]llvm.Value{itfValue}, params...) // start with receiver
params = append(params, itfTypeCode) // end with typecode
} else {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context) // context parameter
hasContext = true
switch b.Scheduler {
case "none", "coroutines":
// There are no additional parameters needed for the goroutine start operation.
case "tasks":
// Add the function pointer as a parameter to start the goroutine.
params = append(params, funcPtr)
default:
panic("unknown scheduler type")
}
prefix = b.fn.RelString(nil)
}
paramBundle := b.emitPointerPack(params)
var callee, stackSize llvm.Value
switch b.Scheduler {
case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
callee = b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos())
if b.AutomaticStackSize {
// The stack size is not known until after linking. Call a dummy
// function that will be replaced with a load from a special ELF
@@ -35,7 +125,7 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
// The stack size is fixed at compile time. By emitting it here as a
// constant, it can be optimized.
if b.Scheduler == "tasks" && b.DefaultStackSize == 0 {
b.addError(pos, "default stack size for goroutines is not set")
b.addError(instr.Pos(), "default stack size for goroutines is not set")
}
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
}
@@ -49,7 +139,6 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
}
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
// createGoroutineStartWrapper creates a wrapper for the task-based
@@ -70,7 +159,12 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
// allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a
// return value.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
//
// The hasContext parameter indicates whether the context parameter (the second
// to last parameter of the function) is used for this wrapper. If hasContext is
// false, the parameter bundle is assumed to have no context parameter and undef
// is passed instead.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
var wrapper llvm.Value
builder := c.ctx.NewBuilder()
@@ -87,6 +181,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
@@ -117,8 +212,15 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes()
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
params = append(params, llvm.Undef(c.i8ptrType))
paramTypes = paramTypes[:len(paramTypes)-1] // strip parentHandle parameter
if !hasContext {
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
}
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
if !hasContext {
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
}
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy parentHandle parameter
// Create the call.
builder.CreateCall(fn, params, "")
@@ -144,6 +246,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
@@ -180,8 +283,11 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Get the function pointer.
fnPtr := params[len(params)-1]
// Ignore the last param, which isn't used anymore.
// TODO: avoid this extra "parent handle" parameter in most functions.
// The last parameter in the packed object has somewhat of a dual role.
// Inside the parameter bundle it's the function pointer, stored right
// after the context pointer. But in the IR call instruction, it's the
// parentHandle function that's always undef outside of the coroutines
// scheduler. Thus, make the parameter undef here.
params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call.
+2 -2
View File
@@ -72,7 +72,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
args := []llvm.Value{}
constraints := []string{}
hasOutput := false
asmString = regexp.MustCompile("\\{\\}").ReplaceAllStringFunc(asmString, func(s string) string {
asmString = regexp.MustCompile(`\{\}`).ReplaceAllStringFunc(asmString, func(s string) string {
hasOutput = true
return "$0"
})
@@ -80,7 +80,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
constraints = append(constraints, "=&r")
registerNumbers[""] = 0
}
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
asmString = regexp.MustCompile(`\{[a-zA-Z]+\}`).ReplaceAllStringFunc(asmString, func(s string) string {
// TODO: skip strings like {r4} etc. that look like ARM push/pop
// instructions.
name := s[1 : len(s)-1]
+81 -37
View File
@@ -47,6 +47,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
var length int64
var methodSet llvm.Value
var ptrTo llvm.Value
var typeAssert llvm.Value
switch typ := typ.(type) {
case *types.Named:
references = c.getTypeCode(typ.Underlying())
@@ -69,6 +70,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}
if _, ok := typ.Underlying().(*types.Interface); !ok {
methodSet = c.getTypeMethodSet(typ)
} else {
typeAssert = c.getInterfaceImplementsFunc(typ)
typeAssert = llvm.ConstPtrToInt(typeAssert, c.uintptrType)
}
if _, ok := typ.Underlying().(*types.Pointer); !ok {
ptrTo = c.getTypeCode(types.NewPointer(typ))
@@ -87,6 +91,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
if !ptrTo.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
}
if !typeAssert.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, typeAssert, []uint32{4})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetGlobalConstant(true)
@@ -193,7 +200,11 @@ func getTypeCodeName(t types.Type) string {
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ {
methods[i] = t.Method(i).Name() + ":" + getTypeCodeName(t.Method(i).Type())
name := t.Method(i).Name()
if !token.IsExported(name) {
name = t.Method(i).Pkg().Path() + "." + name
}
methods[i] = name + ":" + getTypeCodeName(t.Method(i).Type())
}
return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map:
@@ -306,15 +317,33 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
// getMethodSignatureName returns a unique name (that can be used as the name of
// a global) for the given method.
func (c *compilerContext) getMethodSignatureName(method *types.Func) string {
signature := methodSignature(method)
var globalName string
if token.IsExported(method.Name()) {
globalName = "reflect/methods." + signature
} else {
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
}
return globalName
}
// getMethodSignature returns a global variable which is a reference to an
// external *i8 indicating the indicating the signature of this method. It is
// used during the interface lowering pass.
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
signature := methodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
globalName := c.getMethodSignatureName(method)
signatureGlobal := c.mod.NamedGlobal(globalName)
if signatureGlobal.IsNil() {
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
// TODO: put something useful in these globals, such as the method
// signature. Useful to one day implement reflect.Value.Method(n).
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false))
signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
signatureGlobal.SetGlobalConstant(true)
signatureGlobal.SetAlignment(1)
}
return signatureGlobal
}
@@ -334,15 +363,16 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk := llvm.Value{}
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type.
// This pseudo call will be lowered in the interface lowering pass to a
// real call which checks whether the provided typecode is any of the
// concrete types that implements this interface.
// This is a call to an interface type assert function.
// The interface lowering pass will define this function by filling it
// with a type switch over all concrete types that implement this
// interface, and returning whether it's one of the matched types.
// This is very different from how interface asserts are implemented in
// the main Go compiler, where the runtime checks whether the type
// implements each method of the interface. See:
// https://research.swtch.com/interfaces
methodSet := b.getInterfaceMethodSet(expr.AssertedType)
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
fn := b.getInterfaceImplementsFunc(expr.AssertedType)
commaOk = b.CreateCall(fn, []llvm.Value{actualTypeNum}, "")
} else {
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
@@ -409,39 +439,52 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
}
}
// getInvokePtr creates an interface function pointer lookup for the specified invoke instruction, using a specified typecode.
func (b *builder) getInvokePtr(instr *ssa.CallCommon, typecode llvm.Value) llvm.Value {
llvmFnType := b.getRawFuncType(instr.Method.Type().(*types.Signature))
values := []llvm.Value{
typecode,
b.getInterfaceMethodSet(instr.Value.Type()),
b.getMethodSignature(instr.Method),
// getMethodsString returns a string to be used in the "tinygo-methods" string
// attribute for interface functions.
func (c *compilerContext) getMethodsString(itf *types.Interface) string {
methods := make([]string, itf.NumMethods())
for i := range methods {
methods[i] = c.getMethodSignatureName(itf.Method(i))
}
fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func")
return b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
return strings.Join(methods, "; ")
}
// getInvokeCall creates and returns the function pointer and parameters of an
// interface call.
func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf := b.getValue(instr.Value) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.typecode")
fnCast := b.getInvokePtr(instr, typecode)
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
args := []llvm.Value{receiverValue}
for _, arg := range instr.Args {
args = append(args, b.getValue(arg))
// getInterfaceImplementsfunc returns a declared function that works as a type
// switch. The interface lowering pass will define this function.
func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value {
fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert"
llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.uintptrType}, false)
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn)
methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
}
// Add the context parameter. An interface call never takes a context but we
// have to supply the parameter anyway.
args = append(args, llvm.Undef(b.i8ptrType))
// Add the parent goroutine handle.
args = append(args, llvm.Undef(b.i8ptrType))
return llvmFn
}
return fnCast, args
// getInvokeFunction returns the thunk to call the given interface method. The
// thunk is declared, not defined: it will be defined by the interface lowering
// pass.
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
fnName := getTypeCodeName(instr.Value.Type().Underlying()) + "." + instr.Method.Name() + "$invoke"
llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
sig := instr.Method.Type().(*types.Signature)
var paramTuple []*types.Var
for i := 0; i < sig.Params().Len(); i++ {
paramTuple = append(paramTuple, sig.Params().At(i))
}
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.Uintptr]))
llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)).ElementType()
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn)
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
}
return llvmFn
}
// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be
@@ -478,6 +521,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
c.addStandardAttributes(wrapper)
wrapper.LastParam().SetName("parentHandle")
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
+6 -6
View File
@@ -36,22 +36,22 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Fall back to a generic error.
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
}
funcRawPtr, funcContext := b.decodeFuncValue(funcValue, nil)
funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType)
// Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass.
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
}
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
global.SetVisibility(llvm.HiddenVisibility)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
initializer := llvm.ConstNull(globalLLVMType)
initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{1, 0})
initializer = llvm.ConstInsertValue(initializer, funcContext, []uint32{0})
initializer = llvm.ConstInsertValue(initializer, funcPtr, []uint32{1})
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{2, 0})
global.SetInitializer(initializer)
// Add debug info to the interrupt global.
+54
View File
@@ -48,3 +48,57 @@ func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil
}
var mathToLLVMMapping = map[string]string{
"math.Sqrt": "llvm.sqrt.f64",
"math.Floor": "llvm.floor.f64",
"math.Ceil": "llvm.ceil.f64",
"math.Trunc": "llvm.trunc.f64",
}
// createMathOp tries to lower the given call as a LLVM math intrinsic, if
// possible. It returns the call result if possible, and a boolean whether it
// succeeded. If it doesn't succeed, the architecture doesn't support the given
// intrinsic.
func (b *builder) createMathOp(call *ssa.CallCommon) (llvm.Value, bool) {
// Check whether this intrinsic is supported on the given GOARCH.
// If it is unsupported, this can have two reasons:
//
// 1. LLVM can expand the intrinsic inline (using float instructions), but
// the result doesn't pass the tests of the math package.
// 2. LLVM cannot expand the intrinsic inline, will therefore lower it as a
// libm function call, but the libm function call also fails the math
// package tests.
//
// Whatever the implementation, it must pass the tests in the math package
// so unfortunately only the below intrinsic+architecture combinations are
// supported.
name := call.StaticCallee().RelString(nil)
switch name {
case "math.Ceil", "math.Floor", "math.Trunc":
if b.GOARCH != "wasm" && b.GOARCH != "arm64" {
return llvm.Value{}, false
}
case "math.Sqrt":
if b.GOARCH != "wasm" && b.GOARCH != "amd64" && b.GOARCH != "386" {
return llvm.Value{}, false
}
default:
return llvm.Value{}, false // only the above functions are supported.
}
llvmFn := b.mod.NamedFunction(mathToLLVMMapping[name])
if llvmFn.IsNil() {
// The intrinsic doesn't exist yet, so declare it.
// At the moment, all supported intrinsics have the form "double
// foo(double %x)" so we can hardcode the signature here.
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
llvmFn = llvm.AddFunction(b.mod, mathToLLVMMapping[name], llvmType)
}
// Create a call to the intrinsic.
args := make([]llvm.Value, len(call.Args))
for i, arg := range call.Args {
args[i] = b.getValue(arg)
}
return b.CreateCall(llvmFn, args, ""), true
}
+202 -16
View File
@@ -1,6 +1,11 @@
package compiler
import (
"fmt"
"go/token"
"go/types"
"math/big"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
@@ -8,21 +13,6 @@ import (
// This file contains helper functions for LLVM that are not exposed in the Go
// bindings.
// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
if value.IsNil() {
return nil
}
var uses []llvm.Value
use := value.FirstUse()
for !use.IsNil() {
uses = append(uses, use.User())
use = use.NextUse()
}
return uses
}
// createTemporaryAlloca creates a new alloca in the entry block and adds
// lifetime start information in the IR signalling that the alloca won't be used
// before this point.
@@ -44,7 +34,7 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.NeedsStackObjects, values)
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.pkg.Path(), b.NeedsStackObjects, values)
}
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
@@ -67,3 +57,199 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
global.SetInitializer(value)
return global
}
// createObjectLayout returns a LLVM value (of type i8*) that describes where
// there are pointers in the type t. If all the data fits in a word, it is
// returned as a word. Otherwise it will store the data in a global.
//
// The value contains two pieces of information: the length of the object and
// which words contain a pointer (indicated by setting the given bit to 1). For
// arrays, only the element is stored. This works because the GC knows the
// object size and can therefore know how this value is repeated in the object.
func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value {
// Use the element type for arrays. This works even for nested arrays.
for {
kind := t.TypeKind()
if kind == llvm.ArrayTypeKind {
t = t.ElementType()
continue
}
if kind == llvm.StructTypeKind {
fields := t.StructElementTypes()
if len(fields) == 1 {
t = fields[0]
continue
}
}
break
}
// Do a few checks to see whether we need to generate any object layout
// information at all.
objectSizeBytes := c.targetData.TypeAllocSize(t)
pointerSize := c.targetData.TypeAllocSize(c.i8ptrType)
pointerAlignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
if objectSizeBytes < pointerSize {
// Too small to contain a pointer.
layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
}
bitmap := c.getPointerBitmap(t, pos)
if bitmap.BitLen() == 0 {
// There are no pointers in this type, so we can simplify the layout.
// TODO: this can be done in many other cases, e.g. when allocating an
// array (like [4][]byte, which repeats a slice 4 times).
layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
}
if objectSizeBytes%uint64(pointerAlignment) != 0 {
// This shouldn't happen except for packed structs, which aren't
// currently used.
c.addError(pos, "internal error: unexpected object size for object with pointer field")
return llvm.ConstNull(c.i8ptrType)
}
objectSizeWords := objectSizeBytes / uint64(pointerAlignment)
pointerBits := pointerSize * 8
var sizeFieldBits uint64
switch pointerBits {
case 16:
sizeFieldBits = 4
case 32:
sizeFieldBits = 5
case 64:
sizeFieldBits = 6
default:
panic("unknown pointer size")
}
layoutFieldBits := pointerBits - 1 - sizeFieldBits
// Try to emit the value as an inline integer. This is possible in most
// cases.
if objectSizeWords < layoutFieldBits {
// If it can be stored directly in the pointer value, do so.
// The runtime knows that if the least significant bit of the pointer is
// set, the pointer contains the value itself.
layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
}
// Unfortunately, the object layout is too big to fit in a pointer-sized
// integer. Store it in a global instead.
// Try first whether the global already exists. All objects with a
// particular name have the same type, so this is possible.
globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap)
global := c.mod.NamedGlobal(globalName)
if !global.IsNil() {
return llvm.ConstBitCast(global, c.i8ptrType)
}
// Create the global initializer.
bitmapBytes := make([]byte, int(objectSizeWords+7)/8)
copy(bitmapBytes, bitmap.Bytes())
var bitmapByteValues []llvm.Value
for _, b := range bitmapBytes {
bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false))
}
initializer := c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, objectSizeWords, false),
llvm.ConstArray(c.ctx.Int8Type(), bitmapByteValues),
}, false)
global = llvm.AddGlobal(c.mod, initializer.Type(), globalName)
global.SetInitializer(initializer)
global.SetUnnamedAddr(true)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
if c.targetData.PrefTypeAlignment(c.uintptrType) < 2 {
// AVR doesn't have alignment by default.
global.SetAlignment(2)
}
if c.Debug && pos != token.NoPos {
// Creating a fake global so that the value can be inspected in GDB.
// For example, the layout for strings.stringFinder (as of Go version
// 1.15) has the following type according to GDB:
// type = struct {
// uintptr numBits;
// uint8 data[33];
// }
// ...that's sort of a mixed C/Go type, but it is readable. More
// importantly, these object layout globals can be read and printed by
// GDB which may be useful for debugging.
position := c.program.Fset.Position(pos)
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[position.Filename], llvm.DIGlobalVariableExpression{
Name: globalName,
File: c.getDIFile(position.Filename),
Line: position.Line,
Type: c.getDIType(types.NewStruct([]*types.Var{
types.NewVar(pos, nil, "numBits", types.Typ[types.Uintptr]),
types.NewVar(pos, nil, "data", types.NewArray(types.Typ[types.Byte], int64(len(bitmapByteValues)))),
}, nil)),
LocalToUnit: false,
Expr: c.dibuilder.CreateExpression(nil),
})
global.AddMetadata(0, diglobal)
}
return llvm.ConstBitCast(global, c.i8ptrType)
}
// getPointerBitmap scans the given LLVM type for pointers and sets bits in a
// bigint at the word offset that contains a pointer. This scan is recursive.
func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0)
case llvm.PointerTypeKind:
return big.NewInt(1)
case llvm.StructTypeKind:
ptrs := big.NewInt(0)
if typ.StructName() == "runtime.funcValue" {
// Hack: the type runtime.funcValue contains an 'id' field which is
// of type uintptr, but before the LowerFuncValues pass it actually
// contains a pointer (ptrtoint) to a global. This trips up the
// interp package. Therefore, make the id field a pointer for now.
typ = c.ctx.StructType([]llvm.Type{c.i8ptrType, c.i8ptrType}, false)
}
for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, pos)
if subptrs.BitLen() == 0 {
continue
}
offset := c.targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
// This error will let the compilation fail, but by continuing
// the error can still easily be shown.
c.addError(pos, "internal error: allocated struct contains unaligned pointer")
continue
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
case llvm.ArrayTypeKind:
subtyp := typ.ElementType()
subptrs := c.getPointerBitmap(subtyp, pos)
ptrs := big.NewInt(0)
if subptrs.BitLen() == 0 {
return ptrs
}
elementSize := c.targetData.TypeAllocSize(subtyp)
if elementSize%uint64(alignment) != 0 {
// This error will let the compilation fail (but continues so that
// other errors can be shown).
c.addError(pos, "internal error: allocated array contains unaligned pointer")
return ptrs
}
for i := 0; i < typ.ArrayLength(); i++ {
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
default:
// Should not happen.
panic("unknown LLVM type")
}
}
+4 -4
View File
@@ -12,7 +12,7 @@ import (
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
// If the values are all constants, they are be stored in a constant global and deduplicated.
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bool, values []llvm.Value) llvm.Value {
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, prefix string, needsStackObjects bool, values []llvm.Value) llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
@@ -83,12 +83,11 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bo
if constant {
// The data is known at compile time, so store it in a constant global.
// The global address is marked as unnamed, which allows LLVM to merge duplicates.
funcName := builder.GetInsertBlock().Parent().Name()
global := llvm.AddGlobal(mod, packedType, funcName+"$pack")
global := llvm.AddGlobal(mod, packedType, prefix+"$pack")
global.SetInitializer(ctx.ConstStruct(values, false))
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetLinkage(llvm.PrivateLinkage)
global.SetLinkage(llvm.InternalLinkage)
return llvm.ConstBitCast(global, i8ptrType)
}
@@ -97,6 +96,7 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bo
alloc := mod.NamedFunction("runtime.alloc")
packedHeapAlloc := builder.CreateCall(alloc, []llvm.Value{
sizeValue,
llvm.ConstNull(i8ptrType),
llvm.Undef(i8ptrType), // unused context parameter
llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "")
+57
View File
@@ -24,7 +24,9 @@ type functionInfo struct {
module string // go:wasm-module
importName string // go:linkname, go:export - The name the developer assigns
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
section string // go:section - object file section name
exported bool // go:export, CGo
interrupt bool // go:interrupt
nobounds bool // go:nobounds
variadic bool // go:variadic (CGo only)
inline inlineType // go:inline
@@ -107,6 +109,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
llvmFn.AddFunctionAttr(attr)
}
}
c.addStandardDeclaredAttributes(llvmFn)
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, info := range paramInfos {
@@ -249,6 +252,10 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
importName = parts[1]
info.exported = true
case "//go:interrupt":
if hasUnsafeImport(f.Pkg.Pkg) {
info.interrupt = true
}
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
@@ -270,6 +277,10 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2]
}
case "//go:section":
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
info.section = parts[1]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
@@ -318,6 +329,47 @@ func getParams(sig *types.Signature) []*types.Var {
return params
}
// addStandardDeclaredAttributes adds attributes that are set for any function,
// whether declared or defined.
func (c *compilerContext) addStandardDeclaredAttributes(llvmFn llvm.Value) {
if c.SizeLevel >= 1 {
// Set the "optsize" attribute to make slightly smaller binaries at the
// cost of minimal performance loss (-Os in Clang).
kind := llvm.AttributeKindID("optsize")
attr := c.ctx.CreateEnumAttribute(kind, 0)
llvmFn.AddFunctionAttr(attr)
}
if c.SizeLevel >= 2 {
// Set the "minsize" attribute to reduce code size even further,
// regardless of performance loss (-Oz in Clang).
kind := llvm.AttributeKindID("minsize")
attr := c.ctx.CreateEnumAttribute(kind, 0)
llvmFn.AddFunctionAttr(attr)
}
}
// addStandardDefinedAttributes adds the set of attributes that are added to
// every function defined by TinyGo (even thunks/wrappers), possibly depending
// on the architecture. It does not set attributes only set for declared
// functions, use addStandardDeclaredAttributes for this.
func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) {
// TinyGo does not currently raise exceptions, so set the 'nounwind' flag.
// This behavior matches Clang when compiling C source files.
// It reduces binary size on Linux a little bit on non-x86_64 targets by
// eliminating exception tables for these functions.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0))
if strings.Split(c.Triple, "-")[0] == "x86_64" {
// Required by the ABI.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 0))
}
}
// addStandardAttribute adds all attributes added to defined functions.
func (c *compilerContext) addStandardAttributes(llvmFn llvm.Value) {
c.addStandardDeclaredAttributes(llvmFn)
c.addStandardDefinedAttributes(llvmFn)
}
// 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).
@@ -325,6 +377,7 @@ type globalInfo struct {
linkName string // go:extern
extern bool // go:extern
align int // go:align
section string // go:section
}
// loadASTComments loads comments on globals from the AST, for use later in the
@@ -438,6 +491,10 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
if err == nil {
info.align = align
}
case "//go:section":
if len(parts) == 2 {
info.section = parts[1]
}
}
}
}
+30 -8
View File
@@ -10,11 +10,11 @@ import (
"tinygo.org/x/go-llvm"
)
// createSyscall emits an inline system call instruction, depending on the
// target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// createRawSyscall creates a system call with the provided system call number
// and returns the result as a single integer (the system call result). The
// result is not further interpreted.
func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
num := b.getValue(call.Args[0])
var syscallResult llvm.Value
switch {
case b.GOARCH == "amd64":
if b.GOOS == "darwin" {
@@ -57,7 +57,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "")
return b.CreateCall(target, args, ""), nil
case b.GOARCH == "386" && b.GOOS == "linux":
// Sources:
// syscall(2) man page
@@ -83,7 +83,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
}
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "")
return b.CreateCall(target, args, ""), nil
case b.GOARCH == "arm" && b.GOOS == "linux":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
@@ -115,7 +115,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
}
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "")
return b.CreateCall(target, args, ""), nil
case b.GOARCH == "arm64" && b.GOOS == "linux":
// Source: syscall(2) man page.
args := []llvm.Value{}
@@ -147,10 +147,19 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "")
return b.CreateCall(target, args, ""), nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
}
}
// createSyscall emits instructions for the syscall.Syscall* family of
// functions, depending on the target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
switch b.GOOS {
case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno
@@ -190,3 +199,16 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
}
}
// createRawSyscallNoError emits instructions for the Linux-specific
// syscall.rawSyscallNoError function.
func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) {
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "")
return retval, nil
}
+31
View File
@@ -10,6 +10,22 @@ func equalInt(x, y int) bool {
return x == y
}
func divInt(x, y int) int {
return x / y
}
func divUint(x, y uint) uint {
return x / y
}
func remInt(x, y int) int {
return x % y
}
func remUint(x, y uint) uint {
return x % y
}
func floatEQ(x, y float32) bool {
return x == y
}
@@ -55,3 +71,18 @@ func complexMul(x, y complex64) complex64 {
}
// TODO: complexDiv (requires runtime call)
// A type 'kv' also exists in function foo. Test that these two types don't
// conflict with each other.
type kv struct {
v float32
}
func foo(a *kv) {
// Define a new 'kv' type.
type kv struct {
v byte
}
// Use this type.
func(b *kv) {}(nil)
}
+118 -16
View File
@@ -1,74 +1,159 @@
; ModuleID = 'basic.go'
source_filename = "basic.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
target triple = "wasm32-unknown-wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
%main.kv = type { float }
%main.kv.0 = type { i8 }
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
define hidden i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = add i32 %x, %y
ret i32 %0
}
define hidden i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp eq i32 %x, %y
ret i1 %0
}
define hidden i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32 @main.divInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next
divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0
unreachable
divbyzero.next: ; preds = %entry
%1 = icmp eq i32 %y, -1
%2 = icmp eq i32 %x, -2147483648
%3 = and i1 %1, %2
%4 = select i1 %3, i32 1, i32 %y
%5 = sdiv i32 %x, %4
ret i32 %5
}
declare void @runtime.divideByZeroPanic(i8*, i8*)
; Function Attrs: nounwind
define hidden i32 @main.divUint(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next
divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0
unreachable
divbyzero.next: ; preds = %entry
%1 = udiv i32 %x, %y
ret i32 %1
}
; Function Attrs: nounwind
define hidden i32 @main.remInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next
divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0
unreachable
divbyzero.next: ; preds = %entry
%1 = icmp eq i32 %y, -1
%2 = icmp eq i32 %x, -2147483648
%3 = and i1 %1, %2
%4 = select i1 %3, i32 1, i32 %y
%5 = srem i32 %x, %4
ret i32 %5
}
; Function Attrs: nounwind
define hidden i32 @main.remUint(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next
divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0
unreachable
divbyzero.next: ; preds = %entry
%1 = urem i32 %x, %y
ret i32 %1
}
; Function Attrs: nounwind
define hidden i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = fcmp oeq float %x, %y
ret i1 %0
}
define hidden i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = fcmp une float %x, %y
ret i1 %0
}
define hidden i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = fcmp olt float %x, %y
ret i1 %0
}
define hidden i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = fcmp ole float %x, %y
ret i1 %0
}
define hidden i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = fcmp ogt float %x, %y
ret i1 %0
}
define hidden i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = fcmp oge float %x, %y
ret i1 %0
}
define hidden float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret float %x.r
}
define hidden float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret float %x.i
}
define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = fadd float %x.r, %y.r
%1 = fadd float %x.i, %y.i
@@ -77,7 +162,8 @@ entry:
ret { float, float } %3
}
define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = fsub float %x.r, %y.r
%1 = fsub float %x.i, %y.i
@@ -86,7 +172,8 @@ entry:
ret { float, float } %3
}
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = fmul float %x.r, %y.r
%1 = fmul float %x.i, %y.i
@@ -98,3 +185,18 @@ entry:
%7 = insertvalue { float, float } %6, float %5, 1
ret { float, float } %7
}
; Function Attrs: nounwind
define hidden void @main.foo(%main.kv* dereferenceable_or_null(4) %a, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @"main.foo$1"(%main.kv.0* null, i8* undef, i8* undef)
ret void
}
; Function Attrs: nounwind
define hidden void @"main.foo$1"(%main.kv.0* dereferenceable_or_null(1) %b, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
attributes #0 = { nounwind }
+25
View File
@@ -0,0 +1,25 @@
package main
func chanIntSend(ch chan int) {
ch <- 3
}
func chanIntRecv(ch chan int) {
<-ch
}
func chanZeroSend(ch chan struct{}) {
ch <- struct{}{}
}
func chanZeroRecv(ch chan struct{}) {
<-ch
}
func selectZeroRecv(ch1 chan int, ch2 chan struct{}) {
select {
case ch1 <- 1:
case <-ch2:
default:
}
}
+121
View File
@@ -0,0 +1,121 @@
; ModuleID = 'channel.go'
source_filename = "channel.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i8* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.chanIntSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.value = alloca i32, align 4
%chan.value.bitcast = bitcast i32* %chan.value to i8*
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
store i32 3, i32* %chan.value, align 4
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @runtime.chanSend(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null) #0
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
ret void
}
; Function Attrs: argmemonly nounwind willreturn
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
declare void @runtime.chanSend(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
; Function Attrs: argmemonly nounwind willreturn
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1
; Function Attrs: nounwind
define hidden void @main.chanIntRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.value = alloca i32, align 4
%chan.value.bitcast = bitcast i32* %chan.value to i8*
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null) #0
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
declare i1 @runtime.chanRecv(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.chanZeroSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @runtime.chanSend(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null) #0
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
; Function Attrs: nounwind
define hidden void @main.chanZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null) #0
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
; Function Attrs: nounwind
define hidden void @main.selectZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch1, %runtime.channel* dereferenceable_or_null(32) %ch2, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
%select.send.value = alloca i32, align 4
store i32 1, i32* %select.send.value, align 4
%select.states.alloca.bitcast = bitcast [2 x %runtime.chanSelectState]* %select.states.alloca to i8*
call void @llvm.lifetime.start.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
%.repack = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 0
store %runtime.channel* %ch1, %runtime.channel** %.repack, align 8
%.repack1 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 1
%0 = bitcast i8** %.repack1 to i32**
store i32* %select.send.value, i32** %0, align 4
%.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 0
store %runtime.channel* %ch2, %runtime.channel** %.repack3, align 8
%.repack4 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 1
store i8* null, i8** %.repack4, align 4
%select.states = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0
%select.result = call { i32, i1 } @runtime.tryChanSelect(i8* undef, %runtime.chanSelectState* nonnull %select.states, i32 2, i32 2, i8* undef, i8* null) #0
call void @llvm.lifetime.end.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
%1 = extractvalue { i32, i1 } %select.result, 0
%2 = icmp eq i32 %1, 0
br i1 %2, label %select.done, label %select.next
select.done: ; preds = %select.body, %select.next, %entry
ret void
select.next: ; preds = %entry
%3 = icmp eq i32 %1, 1
br i1 %3, label %select.body, label %select.done
select.body: ; preds = %select.next
br label %select.done
}
declare { i32, i1 } @runtime.tryChanSelect(i8*, %runtime.chanSelectState*, i32, i32, i8*, i8*)
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind willreturn }
+22 -11
View File
@@ -1,16 +1,18 @@
; ModuleID = 'float.go'
source_filename = "float.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
target triple = "wasm32-unknown-wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
define hidden i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
@@ -21,22 +23,26 @@ entry:
ret i32 %0
}
define hidden float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret float 0x41F0000000000000
}
define hidden i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret i32 -1
}
define hidden { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 }
}
define hidden i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = uitofp i32 %v to float
%withinmax = fcmp ole float %0, 0x41EFFFFFC0000000
@@ -45,7 +51,8 @@ entry:
ret i32 %1
}
define hidden float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
@@ -57,7 +64,8 @@ entry:
ret float %1
}
define hidden i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 2.550000e+02
@@ -68,7 +76,8 @@ entry:
ret i8 %0
}
define hidden i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%abovemin = fcmp oge float %v, -1.280000e+02
%belowmax = fcmp ole float %v, 1.270000e+02
@@ -80,3 +89,5 @@ entry:
%0 = select i1 %inbounds, i8 %normal, i8 %remapped
ret i8 %0
}
attributes #0 = { nounwind }
+15 -9
View File
@@ -1,33 +1,35 @@
; ModuleID = 'func.go'
source_filename = "func.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
target triple = "wasm32-unknown-wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.someFunc$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @main.someFunc to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
define hidden void @main.foo(i8* %callback.context, i32 %callback.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden void @main.foo(i8* %callback.context, i32 %callback.funcptr, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i32 @runtime.getFuncPtr(i8* %callback.context, i32 %callback.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null)
%0 = call i32 @runtime.getFuncPtr(i8* %callback.context, i32 %callback.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null) #0
%1 = icmp eq i32 %0, 0
br i1 %1, label %fpcall.throw, label %fpcall.next
fpcall.throw: ; preds = %entry
call void @runtime.nilPanic(i8* undef, i8* null)
call void @runtime.nilPanic(i8* undef, i8* null) #0
unreachable
fpcall.next: ; preds = %entry
%2 = inttoptr i32 %0 to void (i32, i8*, i8*)*
call void %2(i32 3, i8* %callback.context, i8* undef)
call void %2(i32 3, i8* %callback.context, i8* undef) #0
ret void
}
@@ -35,13 +37,17 @@ declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i
declare void @runtime.nilPanic(i8*, i8*)
define hidden void @main.bar(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden void @main.bar(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @main.foo(i8* undef, i32 ptrtoint (%runtime.funcValueWithSignature* @"main.someFunc$withSignature" to i32), i8* undef, i8* undef)
ret void
}
define hidden void @main.someFunc(i32 %arg0, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden void @main.someFunc(i32 %arg0, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
attributes #0 = { nounwind }
+72
View File
@@ -0,0 +1,72 @@
package main
var (
scalar1 *byte
scalar2 *int32
scalar3 *int64
scalar4 *float32
array1 *[3]byte
array2 *[71]byte
array3 *[3]*byte
struct1 *struct{}
struct2 *struct {
x int
y int
}
struct3 *struct {
x *byte
y [60]uintptr
z *byte
}
slice1 []byte
slice2 []*int
slice3 [][]byte
)
func newScalar() {
scalar1 = new(byte)
scalar2 = new(int32)
scalar3 = new(int64)
scalar4 = new(float32)
}
func newArray() {
array1 = new([3]byte)
array2 = new([71]byte)
array3 = new([3]*byte)
}
func newStruct() {
struct1 = new(struct{})
struct2 = new(struct {
x int
y int
})
struct3 = new(struct {
x *byte
y [60]uintptr
z *byte
})
}
func newFuncValue() *func() {
// On some platforms that use runtime.funcValue ("switch" style) function
// values, a func value is allocated as having two pointer words while the
// struct looks like {unsafe.Pointer; uintptr}. This is so that the interp
// package won't get confused, see getPointerBitmap in compiler/llvm.go for
// details.
return new(func())
}
func makeSlice() {
slice1 = make([]byte, 5)
slice2 = make([]*int, 5)
slice3 = make([][]byte, 5)
}
func makeInterface(v complex128) interface{} {
return v // always stored in an allocation
}
+113
View File
@@ -0,0 +1,113 @@
; ModuleID = 'gc.go'
source_filename = "gc.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
%runtime.interfaceMethodInfo = type { i8*, i32 }
%runtime.funcValue = type { i8*, i32 }
%runtime._interface = type { i32, i8* }
@main.scalar1 = hidden global i8* null, align 4
@main.scalar2 = hidden global i32* null, align 4
@main.scalar3 = hidden global i64* null, align 4
@main.scalar4 = hidden global float* null, align 4
@main.array1 = hidden global [3 x i8]* null, align 4
@main.array2 = hidden global [71 x i8]* null, align 4
@main.array3 = hidden global [3 x i8*]* null, align 4
@main.struct1 = hidden global {}* null, align 4
@main.struct2 = hidden global { i32, i32 }* null, align 4
@main.struct3 = hidden global { i8*, [60 x i32], i8* }* null, align 4
@main.slice1 = hidden global { i8*, i32, i32 } zeroinitializer, align 8
@main.slice2 = hidden global { i32**, i32, i32 } zeroinitializer, align 8
@main.slice3 = hidden global { { i8*, i32, i32 }*, i32, i32 } zeroinitializer, align 8
@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c" \00\00\00\00\00\00\01" }
@"reflect/types.type:basic:complex128" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:complex128", i32 0 }
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:complex128", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.newScalar(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%new = call i8* @runtime.alloc(i32 1, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new, i8** @main.scalar1, align 4
%new1 = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new1, i8** bitcast (i32** @main.scalar2 to i8**), align 4
%new2 = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new2, i8** bitcast (i64** @main.scalar3 to i8**), align 4
%new3 = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new3, i8** bitcast (float** @main.scalar4 to i8**), align 4
ret void
}
; Function Attrs: nounwind
define hidden void @main.newArray(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%new = call i8* @runtime.alloc(i32 3, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new, i8** bitcast ([3 x i8]** @main.array1 to i8**), align 4
%new1 = call i8* @runtime.alloc(i32 71, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new1, i8** bitcast ([71 x i8]** @main.array2 to i8**), align 4
%new2 = call i8* @runtime.alloc(i32 12, i8* nonnull inttoptr (i32 67 to i8*), i8* undef, i8* null) #0
store i8* %new2, i8** bitcast ([3 x i8*]** @main.array3 to i8**), align 4
ret void
}
; Function Attrs: nounwind
define hidden void @main.newStruct(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%new = call i8* @runtime.alloc(i32 0, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new, i8** bitcast ({}** @main.struct1 to i8**), align 4
%new1 = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %new1, i8** bitcast ({ i32, i32 }** @main.struct2 to i8**), align 4
%new2 = call i8* @runtime.alloc(i32 248, i8* bitcast ({ i32, [8 x i8] }* @"runtime/gc.layout:62-2000000000000001" to i8*), i8* undef, i8* null) #0
store i8* %new2, i8** bitcast ({ i8*, [60 x i32], i8* }** @main.struct3 to i8**), align 4
ret void
}
; Function Attrs: nounwind
define hidden %runtime.funcValue* @main.newFuncValue(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%new = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 197 to i8*), i8* undef, i8* null) #0
%0 = bitcast i8* %new to %runtime.funcValue*
ret %runtime.funcValue* %0
}
; Function Attrs: nounwind
define hidden void @main.makeSlice(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%makeslice = call i8* @runtime.alloc(i32 5, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
store i8* %makeslice, i8** getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 0), align 8
store i32 5, i32* getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 1), align 4
store i32 5, i32* getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 2), align 8
%makeslice1 = call i8* @runtime.alloc(i32 20, i8* nonnull inttoptr (i32 67 to i8*), i8* undef, i8* null) #0
store i8* %makeslice1, i8** bitcast ({ i32**, i32, i32 }* @main.slice2 to i8**), align 8
store i32 5, i32* getelementptr inbounds ({ i32**, i32, i32 }, { i32**, i32, i32 }* @main.slice2, i32 0, i32 1), align 4
store i32 5, i32* getelementptr inbounds ({ i32**, i32, i32 }, { i32**, i32, i32 }* @main.slice2, i32 0, i32 2), align 8
%makeslice3 = call i8* @runtime.alloc(i32 60, i8* nonnull inttoptr (i32 71 to i8*), i8* undef, i8* null) #0
store i8* %makeslice3, i8** bitcast ({ { i8*, i32, i32 }*, i32, i32 }* @main.slice3 to i8**), align 8
store i32 5, i32* getelementptr inbounds ({ { i8*, i32, i32 }*, i32, i32 }, { { i8*, i32, i32 }*, i32, i32 }* @main.slice3, i32 0, i32 1), align 4
store i32 5, i32* getelementptr inbounds ({ { i8*, i32, i32 }*, i32, i32 }, { { i8*, i32, i32 }*, i32, i32 }* @main.slice3, i32 0, i32 2), align 8
ret void
}
; Function Attrs: nounwind
define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i8* @runtime.alloc(i32 16, i8* null, i8* undef, i8* null) #0
%.repack = bitcast i8* %0 to double*
store double %v.r, double* %.repack, align 8
%.repack1 = getelementptr inbounds i8, i8* %0, i32 8
%1 = bitcast i8* %.repack1 to double*
store double %v.i, double* %1, align 8
%2 = insertvalue %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:complex128" to i32), i8* undef }, i8* %0, 1
ret %runtime._interface %2
}
attributes #0 = { nounwind }
+41
View File
@@ -0,0 +1,41 @@
package main
// Test changes to the language introduced in Go 1.17.
// For details, see: https://tip.golang.org/doc/go1.17#language
// These tests should be merged into the regular slice tests once Go 1.17 is the
// minimun Go version for TinyGo.
import "unsafe"
func Add32(p unsafe.Pointer, len int) unsafe.Pointer {
return unsafe.Add(p, len)
}
func Add64(p unsafe.Pointer, len int64) unsafe.Pointer {
return unsafe.Add(p, len)
}
func SliceToArray(s []int) *[4]int {
return (*[4]int)(s)
}
func SliceToArrayConst() *[4]int {
s := make([]int, 6)
return (*[4]int)(s)
}
func SliceInt(ptr *int, len int) []int {
return unsafe.Slice(ptr, len)
}
func SliceUint16(ptr *byte, len uint16) []byte {
return unsafe.Slice(ptr, len)
}
func SliceUint64(ptr *int, len uint64) []int {
return unsafe.Slice(ptr, len)
}
func SliceInt64(ptr *int, len int64) []int {
return unsafe.Slice(ptr, len)
}
+147
View File
@@ -0,0 +1,147 @@
; ModuleID = 'go1.17.go'
source_filename = "go1.17.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden i8* @main.Add32(i8* %p, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = getelementptr i8, i8* %p, i32 %len
ret i8* %0
}
; Function Attrs: nounwind
define hidden i8* @main.Add64(i8* %p, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = trunc i64 %len to i32
%1 = getelementptr i8, i8* %p, i32 %0
ret i8* %1
}
; Function Attrs: nounwind
define hidden [4 x i32]* @main.SliceToArray(i32* %s.data, i32 %s.len, i32 %s.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp ult i32 %s.len, 4
br i1 %0, label %slicetoarray.throw, label %slicetoarray.next
slicetoarray.throw: ; preds = %entry
call void @runtime.sliceToArrayPointerPanic(i8* undef, i8* null) #0
unreachable
slicetoarray.next: ; preds = %entry
%1 = bitcast i32* %s.data to [4 x i32]*
ret [4 x i32]* %1
}
declare void @runtime.sliceToArrayPointerPanic(i8*, i8*)
; Function Attrs: nounwind
define hidden [4 x i32]* @main.SliceToArrayConst(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%makeslice = call i8* @runtime.alloc(i32 24, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
br i1 false, label %slicetoarray.throw, label %slicetoarray.next
slicetoarray.throw: ; preds = %entry
unreachable
slicetoarray.next: ; preds = %entry
%0 = bitcast i8* %makeslice to [4 x i32]*
ret [4 x i32]* %0
}
; Function Attrs: nounwind
define hidden { i32*, i32, i32 } @main.SliceInt(i32* dereferenceable_or_null(4) %ptr, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp ugt i32 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i32 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null) #0
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%6 = insertvalue { i32*, i32, i32 } %5, i32 %len, 1
%7 = insertvalue { i32*, i32, i32 } %6, i32 %len, 2
ret { i32*, i32, i32 } %7
}
declare void @runtime.unsafeSlicePanic(i8*, i8*)
; Function Attrs: nounwind
define hidden { i8*, i32, i32 } @main.SliceUint16(i8* dereferenceable_or_null(1) %ptr, i16 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp eq i8* %ptr, null
%1 = icmp ne i16 %len, 0
%2 = and i1 %0, %1
br i1 %2, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null) #0
unreachable
unsafe.Slice.next: ; preds = %entry
%3 = zext i16 %len to i32
%4 = insertvalue { i8*, i32, i32 } undef, i8* %ptr, 0
%5 = insertvalue { i8*, i32, i32 } %4, i32 %3, 1
%6 = insertvalue { i8*, i32, i32 } %5, i32 %3, 2
ret { i8*, i32, i32 } %6
}
; Function Attrs: nounwind
define hidden { i32*, i32, i32 } @main.SliceUint64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp ugt i64 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i64 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null) #0
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = trunc i64 %len to i32
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
ret { i32*, i32, i32 } %8
}
; Function Attrs: nounwind
define hidden { i32*, i32, i32 } @main.SliceInt64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = icmp ugt i64 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i64 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null) #0
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = trunc i64 %len to i32
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
ret { i32*, i32, i32 } %8
}
attributes #0 = { nounwind }
+209
View File
@@ -0,0 +1,209 @@
; ModuleID = 'goroutine.go'
source_filename = "goroutine.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv7m-unknown-unknown-eabi"
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i32, i32* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
@"main$string" = internal unnamed_addr constant [4 x i8] c"test", align 1
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* undef, i8* undef) #0
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null) #0
ret void
}
declare void @main.regularFunction(i32, i8*, i8*)
; Function Attrs: nounwind
define linkonce_odr void @"main.regularFunction$gowrapper"(i8* %0) unnamed_addr #1 {
entry:
%unpack.int = ptrtoint i8* %0 to i32
call void @main.regularFunction(i32 %unpack.int, i8* undef, i8* undef) #0
ret void
}
declare i32 @"internal/task.getGoroutineStackSize"(i32, i8*, i8*)
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef) #0
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #2 {
entry:
%unpack.int = ptrtoint i8* %0 to i32
call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, i8* undef, i8* undef)
ret void
}
; Function Attrs: nounwind
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%n = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%0 = bitcast i8* %n to i32*
store i32 3, i32* %0, align 4
%1 = call i8* @runtime.alloc(i32 8, i8* null, i8* undef, i8* null) #0
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %n, i8** %4, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef) #0
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* nonnull %1, i32 %stacksize, i8* undef, i8* null) #0
%5 = load i32, i32* %0, align 4
call void @runtime.printint32(i32 %5, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%unpack.ptr = bitcast i8* %context to i32*
store i32 7, i32* %unpack.ptr, align 4
ret void
}
; Function Attrs: nounwind
define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #3 {
entry:
%1 = bitcast i8* %0 to i32*
%2 = load i32, i32* %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
call void @"main.closureFunctionGoroutine$1"(i32 %2, i8* %5, i8* undef)
ret void
}
declare void @runtime.printint32(i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.funcGoroutine(i8* %fn.context, void ()* %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i8* @runtime.alloc(i32 12, i8* null, i8* undef, i8* null) #0
%1 = bitcast i8* %0 to i32*
store i32 5, i32* %1, align 4
%2 = getelementptr inbounds i8, i8* %0, i32 4
%3 = bitcast i8* %2 to i8**
store i8* %fn.context, i8** %3, align 4
%4 = getelementptr inbounds i8, i8* %0, i32 8
%5 = bitcast i8* %4 to void ()**
store void ()* %fn.funcptr, void ()** %5, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* undef, i8* undef) #0
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* nonnull %0, i32 %stacksize, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define linkonce_odr void @main.funcGoroutine.gowrapper(i8* %0) unnamed_addr #4 {
entry:
%1 = bitcast i8* %0 to i32*
%2 = load i32, i32* %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
%6 = getelementptr inbounds i8, i8* %0, i32 8
%7 = bitcast i8* %6 to void (i32, i8*, i8*)**
%8 = load void (i32, i8*, i8*)*, void (i32, i8*, i8*)** %7, align 4
call void %8(i32 %2, i8* %5, i8* undef) #0
ret void
}
; Function Attrs: nounwind
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null) #0
ret void
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null) #0
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i8* @runtime.alloc(i32 16, i8* null, i8* undef, i8* null) #0
%1 = bitcast i8* %0 to i8**
store i8* %itf.value, i8** %1, align 4
%2 = getelementptr inbounds i8, i8* %0, i32 4
%.repack = bitcast i8* %2 to i8**
store i8* getelementptr inbounds ([4 x i8], [4 x i8]* @"main$string", i32 0, i32 0), i8** %.repack, align 4
%.repack1 = getelementptr inbounds i8, i8* %0, i32 8
%3 = bitcast i8* %.repack1 to i32*
store i32 4, i32* %3, align 4
%4 = getelementptr inbounds i8, i8* %0, i32 12
%5 = bitcast i8* %4 to i32*
store i32 %itf.typecode, i32* %5, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), i8* undef, i8* undef) #0
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), i8* nonnull %0, i32 %stacksize, i8* undef, i8* null) #0
ret void
}
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(i8*, i8*, i32, i32, i8*, i8*) #5
; Function Attrs: nounwind
define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(i8* %0) unnamed_addr #6 {
entry:
%1 = bitcast i8* %0 to i8**
%2 = load i8*, i8** %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
%6 = getelementptr inbounds i8, i8* %0, i32 8
%7 = bitcast i8* %6 to i32*
%8 = load i32, i32* %7, align 4
%9 = getelementptr inbounds i8, i8* %0, i32 12
%10 = bitcast i8* %9 to i32*
%11 = load i32, i32* %10, align 4
call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(i8* %2, i8* %5, i32 %8, i32 %11, i8* undef, i8* undef) #0
ret void
}
attributes #0 = { nounwind }
attributes #1 = { nounwind "tinygo-gowrapper"="main.regularFunction" }
attributes #2 = { nounwind "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" }
attributes #3 = { nounwind "tinygo-gowrapper"="main.closureFunctionGoroutine$1" }
attributes #4 = { nounwind "tinygo-gowrapper" }
attributes #5 = { "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
attributes #6 = { nounwind "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
+141
View File
@@ -0,0 +1,141 @@
; ModuleID = 'goroutine.go'
source_filename = "goroutine.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i8* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
@"main$pack" = internal unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"main$pack.1" = internal unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.closureFunctionGoroutine$1$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
@"main$string" = internal unnamed_addr constant [4 x i8] c"test", align 1
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @main.regularFunction to i32), i8* bitcast ({ i32, i8* }* @"main$pack" to i8*), i32 undef, i8* undef, i8* null) #0
ret void
}
declare void @main.regularFunction(i32, i8*, i8*)
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.inlineFunctionGoroutine$1" to i32), i8* bitcast ({ i32, i8* }* @"main$pack.1" to i8*), i32 undef, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%n = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%0 = bitcast i8* %n to i32*
store i32 3, i32* %0, align 4
%1 = call i8* @runtime.alloc(i32 8, i8* null, i8* undef, i8* null) #0
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %n, i8** %4, align 4
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* nonnull %1, i32 undef, i8* undef, i8* null) #0
%5 = load i32, i32* %0, align 4
call void @runtime.printint32(i32 %5, i8* undef, i8* null) #0
ret void
}
; Function Attrs: nounwind
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%unpack.ptr = bitcast i8* %context to i32*
store i32 7, i32* %unpack.ptr, align 4
ret void
}
declare void @runtime.printint32(i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.funcGoroutine(i8* %fn.context, i32 %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i32 @runtime.getFuncPtr(i8* %fn.context, i32 %fn.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null) #0
%1 = call i8* @runtime.alloc(i32 8, i8* null, i8* undef, i8* null) #0
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %fn.context, i8** %4, align 4
call void @"internal/task.start"(i32 %0, i8* nonnull %1, i32 undef, i8* undef, i8* null) #0
ret void
}
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null) #0
ret void
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null) #0
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i8* @runtime.alloc(i32 16, i8* null, i8* undef, i8* null) #0
%1 = bitcast i8* %0 to i8**
store i8* %itf.value, i8** %1, align 4
%2 = getelementptr inbounds i8, i8* %0, i32 4
%.repack = bitcast i8* %2 to i8**
store i8* getelementptr inbounds ([4 x i8], [4 x i8]* @"main$string", i32 0, i32 0), i8** %.repack, align 4
%.repack1 = getelementptr inbounds i8, i8* %0, i32 8
%3 = bitcast i8* %.repack1 to i32*
store i32 4, i32* %3, align 4
%4 = getelementptr inbounds i8, i8* %0, i32 12
%5 = bitcast i8* %4 to i32*
store i32 %itf.typecode, i32* %5, align 4
call void @"internal/task.start"(i32 ptrtoint (void (i8*, i8*, i32, i32, i8*, i8*)* @"interface:{Print:func:{basic:string}{}}.Print$invoke" to i32), i8* nonnull %0, i32 undef, i8* undef, i8* null) #0
ret void
}
declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(i8*, i8*, i32, i32, i8*, i8*) #1
attributes #0 = { nounwind }
attributes #1 = { "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
+51
View File
@@ -0,0 +1,51 @@
package main
func regularFunctionGoroutine() {
go regularFunction(5)
}
func inlineFunctionGoroutine() {
go func(x int) {
}(5)
}
func closureFunctionGoroutine() {
n := 3
go func(x int) {
n = 7
}(5)
print(n) // note: this is racy (but good enough for this test)
}
func funcGoroutine(fn func(x int)) {
go fn(5)
}
func recoverBuiltinGoroutine() {
// This is a no-op.
go recover()
}
func copyBuiltinGoroutine(dst, src []byte) {
// This is not run in a goroutine. While this copy operation can indeed take
// some time (if there is a lot of data to copy), there is no race-free way
// to make use of the result so it's unlikely applications will make use of
// it. And doing it this way should be just within the Go specification.
go copy(dst, src)
}
func closeBuiltinGoroutine(ch chan int) {
// This builtin is executed directly, not in a goroutine.
// The observed behavior is the same.
go close(ch)
}
func regularFunction(x int)
type simpleInterface interface {
Print(string)
}
func startInterfaceMethod(itf simpleInterface) {
go itf.Print("test")
}
+9
View File
@@ -49,6 +49,15 @@ func isStringer(itf interface{}) bool {
return ok
}
type fooInterface interface {
String() string
foo(int) byte
}
func callFooMethod(itf fooInterface) uint8 {
return itf.foo(3)
}
func callErrorMethod(itf error) string {
return itf.Error()
}
+57 -34
View File
@@ -1,58 +1,67 @@
; ModuleID = 'interface.go'
source_filename = "interface.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
target triple = "wasm32-unknown-wasi"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID* }
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
%runtime.interfaceMethodInfo = type { i8*, i32 }
%runtime._interface = type { i32, i8* }
%runtime._string = type { i8*, i32 }
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int" }
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error" }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }
@"func Error() string" = external constant i8
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }
@"func String() string" = external constant i8
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func String() string"]
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int", i32 0 }
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error", i32 ptrtoint (i1 (i32)* @"interface:{Error:func:{}{basic:string}}.$typeassert" to i32) }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", i32 ptrtoint (i1 (i32)* @"interface:{Error:func:{}{basic:string}}.$typeassert" to i32) }
@"reflect/methods.Error() string" = linkonce_odr constant i8 0, align 1
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", i32 ptrtoint (i1 (i32)* @"interface:{String:func:{}{basic:string}}.$typeassert" to i32) }
@"reflect/methods.String() string" = linkonce_odr constant i8 0, align 1
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.String() string"]
@"reflect/types.typeid:basic:int" = external constant i8
@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
define hidden %runtime._interface @main.simpleType(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden %runtime._interface @main.simpleType(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* null }
}
define hidden %runtime._interface @main.pointerType(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden %runtime._interface @main.pointerType(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:basic:int" to i32), i8* null }
}
define hidden %runtime._interface @main.interfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden %runtime._interface @main.interfaceType(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:named:error" to i32), i8* null }
}
define hidden %runtime._interface @main.anonymousInterfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32) #1
; Function Attrs: nounwind
define hidden %runtime._interface @main.anonymousInterfaceType(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), i8* null }
}
define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
declare i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32) #2
; Function Attrs: nounwind
define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.typeid:basic:int", i8* undef, i8* null)
%typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.typeid:basic:int", i8* undef, i8* null) #0
br i1 %typecode, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
@@ -64,9 +73,10 @@ typeassert.next: ; preds = %typeassert.ok, %ent
declare i1 @runtime.typeAssert(i32, i8* dereferenceable_or_null(1), i8*, i8*)
define hidden i1 @main.isError(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.isError(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* undef, i8* null)
%0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #0
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
@@ -76,11 +86,10 @@ typeassert.next: ; preds = %typeassert.ok, %ent
ret i1 %0
}
declare i1 @runtime.interfaceImplements(i32, i8** dereferenceable_or_null(4), i8*, i8*)
define hidden i1 @main.isStringer(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.isStringer(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"reflect/types.interface:interface{String() string}$interface", i32 0, i32 0), i8* undef, i8* null)
%0 = call i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #0
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.ok: ; preds = %entry
@@ -90,12 +99,26 @@ typeassert.next: ; preds = %typeassert.ok, %ent
ret i1 %0
}
define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i8 @main.callFooMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"func Error() string", i8* undef, i8* null)
%invoke.func.cast = inttoptr i32 %invoke.func to %runtime._string (i8*, i8*, i8*)*
%0 = call %runtime._string %invoke.func.cast(i8* %itf.value, i8* undef, i8* undef)
%0 = call i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(i8* %itf.value, i32 3, i32 %itf.typecode, i8* undef, i8* undef) #0
ret i8 %0
}
declare i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(i8*, i32, i32, i8*, i8*) #3
; Function Attrs: nounwind
define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(i8* %itf.value, i32 %itf.typecode, i8* undef, i8* undef) #0
ret %runtime._string %0
}
declare i32 @runtime.interfaceMethod(i32, i8** dereferenceable_or_null(4), i8* dereferenceable_or_null(1), i8*, i8*)
declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(i8*, i32, i8*, i8*) #4
attributes #0 = { nounwind }
attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" }
attributes #2 = { "tinygo-methods"="reflect/methods.String() string" }
attributes #3 = { "tinygo-invoke"="main.$methods.foo(int) byte" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) byte" }
attributes #4 = { "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" }
+32
View File
@@ -0,0 +1,32 @@
; ModuleID = 'intrinsics.go'
source_filename = "intrinsics.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv7m-unknown-unknown-eabi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call double @math.Sqrt(double %x, i8* undef, i8* undef) #0
ret double %0
}
declare double @math.Sqrt(double, i8*, i8*)
; Function Attrs: nounwind
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call double @math.Trunc(double %x, i8* undef, i8* undef) #0
ret double %0
}
declare double @math.Trunc(double, i8*, i8*)
attributes #0 = { nounwind }
+35
View File
@@ -0,0 +1,35 @@
; ModuleID = 'intrinsics.go'
source_filename = "intrinsics.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call double @llvm.sqrt.f64(double %x)
ret double %0
}
; Function Attrs: nounwind readnone speculatable willreturn
declare double @llvm.sqrt.f64(double) #1
; Function Attrs: nounwind
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call double @llvm.trunc.f64(double %x)
ret double %0
}
; Function Attrs: nounwind readnone speculatable willreturn
declare double @llvm.trunc.f64(double) #1
attributes #0 = { nounwind }
attributes #1 = { nounwind readnone speculatable willreturn }
+14
View File
@@ -0,0 +1,14 @@
package main
// Test how intrinsics are lowered: either as regular calls to the math
// functions or as LLVM builtins (such as llvm.sqrt.f64).
import "math"
func mySqrt(x float64) float64 {
return math.Sqrt(x)
}
func myTrunc(x float64) float64 {
return math.Trunc(x)
}
+20 -10
View File
@@ -1,51 +1,61 @@
; ModuleID = 'pointer.go'
source_filename = "pointer.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
target triple = "wasm32-unknown-wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
define hidden [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret [0 x i32] zeroinitializer
}
define hidden i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = bitcast i8* %x to i32*
ret i32* %0
}
define hidden i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = bitcast i32* %x to i8*
ret i8* %0
}
define hidden i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret i8* %x
}
define hidden i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = getelementptr inbounds i8, i8* %ptr, i32 10
ret i8* %0
}
define hidden i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = getelementptr inbounds i8, i8* %ptr, i32 %offset
ret i8* %0
}
define hidden i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = getelementptr i32, i32* %ptr, i32 %offset
ret i32* %0
}
attributes #0 = { nounwind }
+66
View File
@@ -0,0 +1,66 @@
package main
import _ "unsafe"
// Creates an external global with name extern_global.
//go:extern extern_global
var externGlobal [0]byte
// Creates a
//go:align 32
var alignedGlobal [4]uint32
// Test conflicting pragmas (the last one counts).
//go:align 64
//go:align 16
var alignedGlobal16 [4]uint32
// Test exported functions.
//export extern_func
func externFunc() {
}
// Define a function in a different package using go:linkname.
//go:linkname withLinkageName1 somepkg.someFunction1
func withLinkageName1() {
}
// Import a function from a different package using go:linkname.
//go:linkname withLinkageName2 somepkg.someFunction2
func withLinkageName2()
// Function has an 'inline hint', similar to the inline keyword in C.
//go:inline
func inlineFunc() {
}
// Function should never be inlined, equivalent to GCC
// __attribute__((noinline)).
//go:noinline
func noinlineFunc() {
}
// This function should have the specified section.
//go:section .special_function_section
func functionInSection() {
}
//export exportedFunctionInSection
//go:section .special_function_section
func exportedFunctionInSection() {
}
// This function should not: it's only a declaration and not a definition.
//go:section .special_function_section
func undefinedFunctionNotInSection()
//go:section .special_global_section
var globalInSection uint32
//go:section .special_global_section
//go:extern undefinedGlobalNotInSection
var undefinedGlobalNotInSection uint32
//go:align 1024
//go:section .global_section
var multipleGlobalPragmas uint32
+65
View File
@@ -0,0 +1,65 @@
; ModuleID = 'pragma.go'
source_filename = "pragma.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-wasi"
@extern_global = external global [0 x i8], align 1
@main.alignedGlobal = hidden global [4 x i32] zeroinitializer, align 32
@main.alignedGlobal16 = hidden global [4 x i32] zeroinitializer, align 16
@main.globalInSection = hidden global i32 0, section ".special_global_section", align 4
@undefinedGlobalNotInSection = external global i32, align 4
@main.multipleGlobalPragmas = hidden global i32 0, section ".global_section", align 1024
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
; Function Attrs: nounwind
define void @extern_func() #1 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @somepkg.someFunction1(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
declare void @somepkg.someFunction2(i8*, i8*)
; Function Attrs: inlinehint nounwind
define hidden void @main.inlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #2 {
entry:
ret void
}
; Function Attrs: noinline nounwind
define hidden void @main.noinlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #3 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.functionInSection(i8* %context, i8* %parentHandle) unnamed_addr #0 section ".special_function_section" {
entry:
ret void
}
; Function Attrs: nounwind
define void @exportedFunctionInSection() #4 section ".special_function_section" {
entry:
ret void
}
declare void @main.undefinedFunctionNotInSection(i8*, i8*)
attributes #0 = { nounwind }
attributes #1 = { nounwind "wasm-export-name"="extern_func" }
attributes #2 = { inlinehint nounwind }
attributes #3 = { noinline nounwind }
attributes #4 = { nounwind "wasm-export-name"="exportedFunctionInSection" }
+18
View File
@@ -23,3 +23,21 @@ func sliceAppendSlice(ints, added []int) []int {
func sliceCopy(dst, src []int) int {
return copy(dst, src)
}
// Test bounds checking in *ssa.MakeSlice instruction.
func makeByteSlice(len int) []byte {
return make([]byte, len)
}
func makeInt16Slice(len int) []int16 {
return make([]int16, len)
}
func makeArraySlice(len int) [][3]byte {
return make([][3]byte, len) // slice with element size of 3
}
func makeInt32Slice(len int) []int32 {
return make([]int32, len)
}
+103 -14
View File
@@ -1,32 +1,36 @@
; ModuleID = 'slice.go'
source_filename = "slice.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
target triple = "wasm32-unknown-wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
define hidden i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret i32 %ints.len
}
define hidden i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret i32 %ints.cap
}
define hidden i32 @main.sliceElement(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32 @main.sliceElement(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%.not = icmp ult i32 %index, %ints.len
br i1 %.not, label %lookup.next, label %lookup.throw
lookup.throw: ; preds = %entry
call void @runtime.lookupPanic(i8* undef, i8* null)
call void @runtime.lookupPanic(i8* undef, i8* null) #0
unreachable
lookup.next: ; preds = %entry
@@ -37,9 +41,10 @@ lookup.next: ; preds = %entry
declare void @runtime.lookupPanic(i8*, i8*)
define hidden { i32*, i32, i32 } @main.sliceAppendValues(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden { i32*, i32, i32 } @main.sliceAppendValues(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%varargs = call i8* @runtime.alloc(i32 12, i8* undef, i8* null)
%varargs = call i8* @runtime.alloc(i32 12, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%0 = bitcast i8* %varargs to i32*
store i32 1, i32* %0, align 4
%1 = getelementptr inbounds i8, i8* %varargs, i32 4
@@ -49,7 +54,7 @@ entry:
%4 = bitcast i8* %3 to i32*
store i32 3, i32* %4, align 4
%append.srcPtr = bitcast i32* %ints.data to i8*
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, i8* undef, i8* null)
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, i8* undef, i8* null) #0
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
%append.newBuf = bitcast i8* %append.newPtr to i32*
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
@@ -62,11 +67,12 @@ entry:
declare { i8*, i32, i32 } @runtime.sliceAppend(i8*, i8* nocapture readonly, i32, i32, i32, i32, i8*, i8*)
define hidden { i32*, i32, i32 } @main.sliceAppendSlice(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32* %added.data, i32 %added.len, i32 %added.cap, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden { i32*, i32, i32 } @main.sliceAppendSlice(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32* %added.data, i32 %added.len, i32 %added.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%append.srcPtr = bitcast i32* %ints.data to i8*
%append.srcPtr1 = bitcast i32* %added.data to i8*
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* %append.srcPtr1, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, i8* undef, i8* null)
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* %append.srcPtr1, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, i8* undef, i8* null) #0
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
%append.newBuf = bitcast i8* %append.newPtr to i32*
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
@@ -77,12 +83,95 @@ entry:
ret { i32*, i32, i32 } %2
}
define hidden i32 @main.sliceCopy(i32* %dst.data, i32 %dst.len, i32 %dst.cap, i32* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32 @main.sliceCopy(i32* %dst.data, i32 %dst.len, i32 %dst.cap, i32* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%copy.dstPtr = bitcast i32* %dst.data to i8*
%copy.srcPtr = bitcast i32* %src.data to i8*
%copy.n = call i32 @runtime.sliceCopy(i8* %copy.dstPtr, i8* %copy.srcPtr, i32 %dst.len, i32 %src.len, i32 4, i8* undef, i8* null)
%copy.n = call i32 @runtime.sliceCopy(i8* %copy.dstPtr, i8* %copy.srcPtr, i32 %dst.len, i32 %src.len, i32 4, i8* undef, i8* null) #0
ret i32 %copy.n
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
; Function Attrs: nounwind
define hidden { i8*, i32, i32 } @main.makeByteSlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%slice.maxcap = icmp slt i32 %len, 0
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null) #0
unreachable
slice.next: ; preds = %entry
%makeslice.buf = call i8* @runtime.alloc(i32 %len, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%0 = insertvalue { i8*, i32, i32 } undef, i8* %makeslice.buf, 0
%1 = insertvalue { i8*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i8*, i32, i32 } %1, i32 %len, 2
ret { i8*, i32, i32 } %2
}
declare void @runtime.slicePanic(i8*, i8*)
; Function Attrs: nounwind
define hidden { i16*, i32, i32 } @main.makeInt16Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%slice.maxcap = icmp slt i32 %len, 0
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null) #0
unreachable
slice.next: ; preds = %entry
%makeslice.cap = shl i32 %len, 1
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%makeslice.array = bitcast i8* %makeslice.buf to i16*
%0 = insertvalue { i16*, i32, i32 } undef, i16* %makeslice.array, 0
%1 = insertvalue { i16*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i16*, i32, i32 } %1, i32 %len, 2
ret { i16*, i32, i32 } %2
}
; Function Attrs: nounwind
define hidden { [3 x i8]*, i32, i32 } @main.makeArraySlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%slice.maxcap = icmp ugt i32 %len, 1431655765
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null) #0
unreachable
slice.next: ; preds = %entry
%makeslice.cap = mul i32 %len, 3
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%makeslice.array = bitcast i8* %makeslice.buf to [3 x i8]*
%0 = insertvalue { [3 x i8]*, i32, i32 } undef, [3 x i8]* %makeslice.array, 0
%1 = insertvalue { [3 x i8]*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { [3 x i8]*, i32, i32 } %1, i32 %len, 2
ret { [3 x i8]*, i32, i32 } %2
}
; Function Attrs: nounwind
define hidden { i32*, i32, i32 } @main.makeInt32Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%slice.maxcap = icmp ugt i32 %len, 1073741823
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null) #0
unreachable
slice.next: ; preds = %entry
%makeslice.cap = shl i32 %len, 2
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* nonnull inttoptr (i32 3 to i8*), i8* undef, i8* null) #0
%makeslice.array = bitcast i8* %makeslice.buf to i32*
%0 = insertvalue { i32*, i32, i32 } undef, i32* %makeslice.array, 0
%1 = insertvalue { i32*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i32*, i32, i32 } %1, i32 %len, 2
ret { i32*, i32, i32 } %2
}
attributes #0 = { nounwind }
+26 -16
View File
@@ -1,41 +1,46 @@
; ModuleID = 'string.go'
source_filename = "string.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
target triple = "wasm32-unknown-wasi"
%runtime._string = type { i8*, i32 }
@"main.someString$string" = internal unnamed_addr constant [3 x i8] c"foo", align 1
@"main$string" = internal unnamed_addr constant [3 x i8] c"foo", align 1
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret void
}
define hidden %runtime._string @main.someString(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden %runtime._string @main.someString(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret %runtime._string { i8* getelementptr inbounds ([3 x i8], [3 x i8]* @"main.someString$string", i32 0, i32 0), i32 3 }
ret %runtime._string { i8* getelementptr inbounds ([3 x i8], [3 x i8]* @"main$string", i32 0, i32 0), i32 3 }
}
define hidden %runtime._string @main.zeroLengthString(i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden %runtime._string @main.zeroLengthString(i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret %runtime._string zeroinitializer
}
define hidden i32 @main.stringLen(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i32 @main.stringLen(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
ret i32 %s.len
}
define hidden i8 @main.stringIndex(i8* %s.data, i32 %s.len, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i8 @main.stringIndex(i8* %s.data, i32 %s.len, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%.not = icmp ult i32 %index, %s.len
br i1 %.not, label %lookup.next, label %lookup.throw
lookup.throw: ; preds = %entry
call void @runtime.lookupPanic(i8* undef, i8* null)
call void @runtime.lookupPanic(i8* undef, i8* null) #0
unreachable
lookup.next: ; preds = %entry
@@ -46,26 +51,31 @@ lookup.next: ; preds = %entry
declare void @runtime.lookupPanic(i8*, i8*)
define hidden i1 @main.stringCompareEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.stringCompareEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null) #0
ret i1 %0
}
declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*)
define hidden i1 @main.stringCompareUnequal(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.stringCompareUnequal(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null) #0
%1 = xor i1 %0, true
ret i1 %1
}
define hidden i1 @main.stringCompareLarger(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
; Function Attrs: nounwind
define hidden i1 @main.stringCompareLarger(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr #0 {
entry:
%0 = call i1 @runtime.stringLess(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
%0 = call i1 @runtime.stringLess(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null) #0
%1 = xor i1 %0, true
ret i1 %1
}
declare i1 @runtime.stringLess(i8*, i32, i8*, i32, i8*, i8*)
attributes #0 = { nounwind }
+5 -4
View File
@@ -1,16 +1,17 @@
module github.com/tinygo-org/tinygo
go 1.13
go 1.15
require (
github.com/aykevl/go-wasm v0.0.2-0.20211030161413-11881cb9032d // indirect
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693
github.com/chromedp/chromedp v0.6.4
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
github.com/mattn/go-colorable v0.1.8
go.bug.st/serial v1.1.2
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
go.bug.st/serial v1.1.3
golang.org/x/sys v0.0.0-20210510120138-977fb7262007
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c
)
+23 -12
View File
@@ -1,3 +1,5 @@
github.com/aykevl/go-wasm v0.0.2-0.20211030161413-11881cb9032d h1:JeuI5/546naK5hpOIX+Lq5xE8rvt7uwiTp6iL+pLQgk=
github.com/aykevl/go-wasm v0.0.2-0.20211030161413-11881cb9032d/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw=
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693 h1:11eq/RkpaotwdF6b1TRMcdgQUPNmyFEJOB7zLvh0O/Y=
@@ -6,8 +8,8 @@ github.com/chromedp/chromedp v0.6.4 h1:Gx7ZkRyrSVmbbDDja/ieNgNGJIvElroPOyeqYQGVD
github.com/chromedp/chromedp v0.6.4/go.mod h1:vodUdJf5dF/b8n0UBJv6NeM/QK28RjP3j+eM7fq4+84=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
@@ -30,31 +32,40 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
go.bug.st/serial v1.1.2 h1:6xDpbta8KJ+VLRTeM8ghhxXRMLE/Lr8h9iDKwydarAY=
go.bug.st/serial v1.1.2/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY=
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.bug.st/serial v1.1.3 h1:YEBxJa9pKS9Wdg46B/jiaKbvvbUrjhZZZITfJHEJhaE=
go.bug.st/serial v1.1.3/go.mod h1:8TT7u/SwwNIpJ8QaG4s+HTjFt9ReXs2cdOU7ZEk50Dk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9 h1:nvvuMxmx1q0gfRki3T0hjG8EwAcVCs91oWAXvyt4zhI=
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs=
+5 -5
View File
@@ -12,7 +12,7 @@ import (
// Version of TinyGo.
// Update this value before release of new version of software.
const Version = "0.18.0"
const Version = "0.21.0-dev"
// GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
@@ -48,7 +48,10 @@ func GetGorootVersion(goroot string) (major, minor int, err error) {
// 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(
if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`")
@@ -59,9 +62,6 @@ func GorootVersionString(goroot string) (string, error) {
return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else {
return "", err
}
+1
View File
@@ -57,6 +57,7 @@ func (r *runner) errorAt(inst instruction, err error) *Error {
pos := getPosition(inst.llvmInst)
return &Error{
ImportPath: r.pkgName,
Inst: inst.llvmInst,
Pos: pos,
Err: err,
Traceback: []ErrorLine{{pos, inst.llvmInst}},
+34 -3
View File
@@ -15,7 +15,7 @@ import (
// package is changed in a way that affects the output so that cached package
// builds will be invalidated.
// This version is independent of the TinyGo version number.
const Version = 1
const Version = 2 // last change: fix GEP on untyped pointers
// Enable extra checks, which should be disabled by default.
// This may help track down bugs by adding a few more sanity checks.
@@ -110,17 +110,26 @@ func Run(mod llvm.Module, debug bool) error {
fmt.Fprintln(os.Stderr, "call:", fn.Name())
}
_, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
call.EraseFromParentAsInstruction()
if callErr != nil {
if isRecoverableError(callErr.Err) {
if r.debug {
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
}
// Remove instructions that were created as part of interpreting
// the package.
mem.revert()
// Create a call to the package initializer (which was
// previously deleted).
i8undef := llvm.Undef(r.i8ptrType)
r.builder.CreateCall(fn, []llvm.Value{i8undef, i8undef}, "")
// Make sure that any globals touched by the package
// initializer, won't be accessed by later package initializers.
r.markExternalLoad(fn)
continue
}
return callErr
}
call.EraseFromParentAsInstruction()
for index, obj := range mem.objects {
r.objects[index] = obj
}
@@ -129,7 +138,7 @@ func Run(mod llvm.Module, debug bool) error {
// Update all global variables in the LLVM module.
mem := memoryView{r: r}
for _, obj := range r.objects {
for i, obj := range r.objects {
if obj.llvmGlobal.IsNil() {
continue
}
@@ -159,6 +168,12 @@ func Run(mod llvm.Module, debug bool) error {
name := obj.llvmGlobal.Name()
obj.llvmGlobal.EraseFromParentAsGlobal()
newGlobal.SetName(name)
// Update interp-internal references.
delete(r.globals, obj.llvmGlobal)
obj.llvmGlobal = newGlobal
r.globals[newGlobal] = i
r.objects[i] = obj
continue
}
if err != nil {
@@ -264,3 +279,19 @@ func (r *runner) getFunction(llvmFn llvm.Value) *function {
r.functionCache[llvmFn] = fn
return fn
}
// markExternalLoad marks the given llvmValue as being loaded externally. This
// is primarily used to mark package initializers that could not be run at
// compile time. As an example, a package initialize might store to a global
// variable. Another package initializer might read from the same global
// variable. By marking this function as being run at runtime, that load
// instruction will need to be run at runtime instead of at compile time.
func (r *runner) markExternalLoad(llvmValue llvm.Value) {
mem := memoryView{r: r}
mem.markExternalLoad(llvmValue)
for index, obj := range mem.objects {
if obj.marked > r.objects[index].marked {
r.objects[index].marked = obj.marked
}
}
}
+2 -12
View File
@@ -3,7 +3,6 @@ package interp
import (
"io/ioutil"
"os"
"regexp"
"strings"
"testing"
@@ -17,6 +16,8 @@ func TestInterp(t *testing.T) {
"slice-copy",
"consteval",
"interface",
"revert",
"alloc",
} {
name := name // make tc local to this closure
t.Run(name, func(t *testing.T) {
@@ -87,8 +88,6 @@ func runTest(t *testing.T, pathPrefix string) {
}
}
var alignRegexp = regexp.MustCompile(", align [0-9]+$")
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
// equal. That means, only relevant lines are compared (excluding comments
// etc.).
@@ -100,15 +99,6 @@ func fuzzyEqualIR(s1, s2 string) bool {
}
for i, line1 := range lines1 {
line2 := lines2[i]
match1 := alignRegexp.MatchString(line1)
match2 := alignRegexp.MatchString(line2)
if match1 != match2 {
// Only one of the lines has the align keyword. Remove it.
// This is a change to make the test work in both LLVM 10 and LLVM
// 11 (LLVM 11 appears to automatically add alignment everywhere).
line1 = alignRegexp.ReplaceAllString(line1, "")
line2 = alignRegexp.ReplaceAllString(line2, "")
}
if line1 != line2 {
return false
}
+30 -26
View File
@@ -234,11 +234,15 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// Get the requested memory size to be allocated.
size := operands[1].Uint()
// Get the object layout, if it is available.
llvmLayoutType := r.getLLVMTypeFromLayout(operands[2])
// Create the object.
alloc := object{
globalName: r.pkgName + "$alloc",
buffer: newRawValue(uint32(size)),
size: uint32(size),
globalName: r.pkgName + "$alloc",
llvmLayoutType: llvmLayoutType,
buffer: newRawValue(uint32(size)),
size: uint32(size),
}
index := len(r.objects)
r.objects = append(r.objects, alloc)
@@ -378,7 +382,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
} else {
locals[inst.localIndex] = literalValue{uint8(0)}
}
case callFn.name == "runtime.interfaceImplements":
case strings.HasSuffix(callFn.name, ".$typeassert"):
if r.debug {
fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:])
}
@@ -393,11 +397,9 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
return nil, mem, r.errorAt(inst, err)
}
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
interfaceMethodSetPtr, err := operands[2].asPointer(r)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
interfaceMethodSet := mem.get(interfaceMethodSetPtr.index()).llvmGlobal.Initializer()
llvmFn := inst.llvmInst.CalledValue()
methodSetAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-methods")
methodSetString := methodSetAttr.GetStringValue()
// Make a set of all the methods on the concrete type, for
// easier checking in the next step.
@@ -412,8 +414,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// of defined methods calculated above. This is the interface
// assert itself.
assertOk := uint8(1) // i1 true
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
for _, name := range strings.Split(methodSetString, "; ") {
if _, ok := concreteTypeMethods[name]; !ok {
// There is a method on the interface that is not
// implemented by the type. The assertion will fail.
@@ -423,20 +424,20 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
}
// If assertOk is still 1, the assertion succeeded.
locals[inst.localIndex] = literalValue{assertOk}
case callFn.name == "runtime.interfaceMethod":
// This builtin returns the function (which may be a thunk) to
// invoke a method on an interface. It does not call the method.
case strings.HasSuffix(callFn.name, "$invoke"):
// This thunk is the interface method dispatcher: it is called
// with all regular parameters and a type code. It will then
// call the concrete method for it.
if r.debug {
fmt.Fprintln(os.Stderr, indent+"interface method:", operands[1:])
fmt.Fprintln(os.Stderr, indent+"invoke method:", operands[1:])
}
// Load the first param, which is the type code (ptrtoint of the
// type code global).
typecodeIDPtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem)
// Load the type code of the interface value.
typecodeIDBitCast, err := operands[len(operands)-3].toLLVMValue(inst.llvmInst.Operand(len(operands)-4).Type(), &mem)
if err != nil {
return nil, mem, r.errorAt(inst, err)
}
typecodeID := typecodeIDPtrToInt.Operand(0).Initializer()
typecodeID := typecodeIDBitCast.Operand(0).Initializer()
// Load the method set, which is part of the typecodeID object.
methodSet := llvm.ConstExtractValue(typecodeID, []uint32{2}).Operand(0).Initializer()
@@ -444,7 +445,10 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// We don't need to load the interface method set.
// Load the signature of the to-be-called function.
signature := inst.llvmInst.Operand(2)
llvmFn := inst.llvmInst.CalledValue()
invokeAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
invokeName := invokeAttr.GetStringValue()
signature := r.mod.NamedGlobal(invokeName)
// Iterate through all methods, looking for the one method that
// should be returned.
@@ -457,9 +461,13 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
}
}
if method.IsNil() {
return nil, mem, r.errorAt(inst, errors.New("could not find method: "+signature.Name()))
return nil, mem, r.errorAt(inst, errors.New("could not find method: "+invokeName))
}
locals[inst.localIndex] = r.getValue(method)
// Change the to-be-called function to the underlying method to
// be called and fall through to the default case.
callFn = r.getFunction(method)
fallthrough
default:
if len(callFn.blocks) == 0 {
// Call to a function declaration without a definition
@@ -590,18 +598,14 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// GetElementPtr does pointer arithmetic, changing the offset of the
// pointer into the underlying object.
var offset uint64
var gepOperands []uint64
for i := 2; i < len(operands); i += 2 {
index := operands[i].Uint()
elementSize := operands[i+1].Uint()
if int64(elementSize) < 0 {
// This is a struct field.
// The field number is encoded by flipping all the bits.
gepOperands = append(gepOperands, ^elementSize)
offset += index
} else {
// This is a normal GEP, probably an array index.
gepOperands = append(gepOperands, index)
offset += elementSize * index
}
}
+168 -78
View File
@@ -18,6 +18,7 @@ import (
"encoding/binary"
"errors"
"math"
"math/big"
"strconv"
"strings"
@@ -27,17 +28,20 @@ import (
// An object is a memory buffer that may be an already existing global or a
// global created with runtime.alloc or the alloca instruction. If llvmGlobal is
// set, that's the global for this object, otherwise it needs to be created (if
// it is still reachable when the package initializer returns).
// it is still reachable when the package initializer returns). The
// llvmLayoutType is not necessarily a complete type: it may need to be
// repeated (for example, for a slice value).
//
// Objects are copied in a memory view when they are stored to, to provide the
// ability to roll back interpreting a function.
type object struct {
llvmGlobal llvm.Value
llvmType llvm.Type // must match llvmGlobal.Type() if both are set, may be unset if llvmGlobal is set
globalName string // name, if not yet created (not guaranteed to be the final name)
buffer value // buffer with value as given by interp, nil if external
size uint32 // must match buffer.len(), if available
marked uint8 // 0 means unmarked, 1 means external read, 2 means external write
llvmGlobal llvm.Value
llvmType llvm.Type // must match llvmGlobal.Type() if both are set, may be unset if llvmGlobal is set
llvmLayoutType llvm.Type // LLVM type based on runtime.alloc layout parameter, if available
globalName string // name, if not yet created (not guaranteed to be the final name)
buffer value // buffer with value as given by interp, nil if external
size uint32 // must match buffer.len(), if available
marked uint8 // 0 means unmarked, 1 means external read, 2 means external write
}
// clone() returns a cloned version of this object, for when an object needs to
@@ -178,6 +182,9 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) {
default:
panic("interp: unknown constant expression")
}
} else if !llvmValue.IsAInlineAsm().IsNil() {
// Inline assembly can modify globals but only exported globals. Let's
// hope the author knows what they're doing.
} else {
llvmType := llvmValue.Type()
switch llvmType.TypeKind() {
@@ -519,6 +526,18 @@ func (v pointerValue) llvmValue(mem *memoryView) llvm.Value {
// bitcast. The llvm.Type parameter is optional, if omitted the pointer type may
// be different than expected.
func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, error) {
// If a particular LLVM type is requested, cast to it.
if !llvmType.IsNil() && llvmType.TypeKind() != llvm.PointerTypeKind {
// The LLVM value has (or should have) the same bytes once compiled, but
// does not have the right LLVM type. This can happen for example when
// storing to a struct with a single pointer field: this pointer may
// then become the value even though the pointer should be wrapped in a
// struct.
// This can be worked around by simply converting to a raw value,
// rawValue knows how to create such structs.
return v.asRawValue(mem.r).toLLVMValue(llvmType, mem)
}
// Obtain the llvmValue, creating it if it doesn't exist yet.
llvmValue := v.llvmValue(mem)
if llvmValue.IsNil() {
@@ -526,7 +545,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
// runtime.alloc.
// First allocate a new global for this object.
obj := mem.get(v.index())
if obj.llvmType.IsNil() {
if obj.llvmType.IsNil() && obj.llvmLayoutType.IsNil() {
// Create an initializer without knowing the global type.
// This is probably the result of a runtime.alloc call.
initializer, err := obj.buffer.asRawValue(mem.r).rawLLVMValue(mem)
@@ -540,7 +559,23 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
obj.llvmGlobal = llvmValue
mem.put(v.index(), obj)
} else {
globalType := obj.llvmType.ElementType()
// The global type is known, or at least its structure.
var globalType llvm.Type
if !obj.llvmType.IsNil() {
// The exact type is known.
globalType = obj.llvmType.ElementType()
} else { // !obj.llvmLayoutType.IsNil()
// The exact type isn't known, but the object layout is known.
globalType = obj.llvmLayoutType
// The layout may not span the full size of the global because
// of repetition. One example would be make([]string, 5) which
// would be 10 words in size but the layout would only be two
// words (for the string type).
typeSize := mem.r.targetData.TypeAllocSize(globalType)
if typeSize != uint64(obj.size) {
globalType = llvm.ArrayType(globalType, int(uint64(obj.size)/typeSize))
}
}
if checks && mem.r.targetData.TypeAllocSize(globalType) != uint64(obj.size) {
panic("size of the globalType isn't the same as the object size")
}
@@ -559,6 +594,11 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
return llvm.Value{}, errors.New("interp: allocated value does not match allocated type")
}
llvmValue.SetInitializer(initializer)
if obj.llvmType.IsNil() {
// The exact type isn't known (only the layout), so use the
// alignment that would normally be expected from runtime.alloc.
llvmValue.SetAlignment(mem.r.maxAlign)
}
}
// It should be included in r.globals because otherwise markExternal
@@ -568,76 +608,24 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
llvmValue.SetLinkage(llvm.InternalLinkage)
}
if llvmType.IsNil() {
return llvmValue, nil
}
if llvmType.TypeKind() != llvm.PointerTypeKind {
// The LLVM value has (or should have) the same bytes once compiled, but
// does not have the right LLVM type. This can happen for example when
// storing to a struct with a single pointer field: this pointer may
// then become the value even though the pointer should be wrapped in a
// struct.
// This can be worked around by simply converting to a raw value,
// rawValue knows how to create such structs.
if v.offset() != 0 {
return llvm.Value{}, errors.New("interp: offset set without known pointer type")
if v.offset() != 0 {
// If there is an offset, make sure to use a GEP to index into the
// pointer.
// Cast to an i8* first (if needed) for easy indexing.
if llvmValue.Type() != mem.r.i8ptrType {
llvmValue = llvm.ConstBitCast(llvmValue, mem.r.i8ptrType)
}
return v.asRawValue(mem.r).toLLVMValue(llvmType, mem)
llvmValue = llvm.ConstInBoundsGEP(llvmValue, []llvm.Value{
llvm.ConstInt(llvmValue.Type().Context().Int32Type(), uint64(v.offset()), false),
})
}
requestedType := llvmType
objectElementType := llvmValue.Type()
if requestedType == objectElementType {
if v.offset() != 0 {
// This should never happen, if offset is non-zero, the types
// shouldn't match.
return llvm.Value{}, errors.New("interp: offset set while there is no way to convert the type")
}
return llvmValue, nil
// If a particular LLVM pointer type is requested, cast to it.
if !llvmType.IsNil() && llvmType != llvmValue.Type() {
llvmValue = llvm.ConstBitCast(llvmValue, llvmType)
}
if v.offset() == 0 {
// Offset is zero, so we can just bitcast to get a correct pointer.
return llvm.ConstBitCast(llvmValue, llvmType), nil
}
// We need to make a constant GEP for pointer arithmetic.
int32Type := llvmType.Context().Int32Type()
indices := []llvm.Value{llvm.ConstInt(int32Type, 0, false)}
requestedType = requestedType.ElementType()
objectElementType = objectElementType.ElementType()
offset := int64(v.offset())
for offset > 0 {
switch objectElementType.TypeKind() {
case llvm.ArrayTypeKind:
elementType := objectElementType.ElementType()
elementSize := mem.r.targetData.TypeAllocSize(elementType)
elementIndex := uint64(offset) / elementSize
indices = append(indices, llvm.ConstInt(int32Type, elementIndex, false))
offset -= int64(elementIndex * elementSize)
objectElementType = elementType
case llvm.StructTypeKind:
element := mem.r.targetData.ElementContainingOffset(objectElementType, uint64(offset))
indices = append(indices, llvm.ConstInt(int32Type, uint64(element), false))
offset -= int64(mem.r.targetData.ElementOffset(objectElementType, element))
objectElementType = objectElementType.StructElementTypes()[element]
default:
return llvm.Value{}, errors.New("interp: pointer index with something other than a struct or array?")
}
}
if offset < 0 {
return llvm.Value{}, errors.New("interp: offset has somehow gone negative, this should be impossible")
}
// Finally do the gep, using the above computed indices.
// If it still doesn't match te requested type, it's possible to bitcast (as
// the bits of the pointer are now correct, just not the type).
gep := llvm.ConstInBoundsGEP(llvmValue, indices)
if gep.Type() != llvmType {
return llvm.ConstBitCast(gep, llvmType), nil
}
return gep, nil
return llvmValue, nil
}
// rawValue is a raw memory buffer that can store either pointers or regular
@@ -971,12 +959,9 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
case llvm.GetElementPtr:
ptr := llvmValue.Operand(0)
index := llvmValue.Operand(1)
if checks && index.IsAConstantInt().IsNil() || index.ZExtValue() != 0 {
panic("expected first index of const gep to be i32 0")
}
numOperands := llvmValue.OperandsCount()
elementType := ptr.Type().ElementType()
totalOffset := uint64(0)
totalOffset := r.targetData.TypeAllocSize(elementType) * index.ZExtValue()
for i := 2; i < numOperands; i++ {
indexValue := llvmValue.Operand(i)
if checks && indexValue.IsAConstantInt().IsNil() {
@@ -1195,3 +1180,108 @@ func (r *runner) getValue(llvmValue llvm.Value) value {
panic("unknown value")
}
}
// readObjectLayout reads the object layout as it is stored by the compiler. It
// returns the size in the number of words and the bitmap.
func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) {
pointerSize := layoutValue.len(r)
if checks && uint64(pointerSize) != r.targetData.TypeAllocSize(r.i8ptrType) {
panic("inconsistent pointer size")
}
// The object layout can be stored in a global variable, directly as an
// integer value, or can be nil.
ptr, err := layoutValue.asPointer(r)
if err == errIntegerAsPointer {
// It's an integer, which means it's a small object or unknown.
layout := layoutValue.Uint()
if layout == 0 {
// Nil pointer, which means the layout is unknown.
return 0, nil
}
if layout%2 != 1 {
// Sanity check: the least significant bit must be set. This is how
// the runtime can separate pointers from integers.
panic("unexpected layout")
}
// Determine format of bitfields in the integer.
pointerBits := uint64(pointerSize * 8)
var sizeFieldBits uint64
switch pointerBits {
case 16:
sizeFieldBits = 4
case 32:
sizeFieldBits = 5
case 64:
sizeFieldBits = 6
default:
panic("unknown pointer size")
}
// Extract fields.
objectSizeWords := (layout >> 1) & (1<<sizeFieldBits - 1)
bitmap := new(big.Int).SetUint64(layout >> (1 + sizeFieldBits))
return objectSizeWords, bitmap
}
// Read the object size in words and the bitmap from the global.
buf := r.objects[ptr.index()].buffer.(rawValue)
objectSizeWords := rawValue{buf: buf.buf[:r.pointerSize]}.Uint()
rawByteValues := buf.buf[r.pointerSize:]
rawBytes := make([]byte, len(rawByteValues))
for i, v := range rawByteValues {
if uint64(byte(v)) != v {
panic("found pointer in data array?") // sanity check
}
rawBytes[i] = byte(v)
}
bitmap := new(big.Int).SetBytes(rawBytes)
return objectSizeWords, bitmap
}
// getLLVMTypeFromLayout returns the 'layout type', which is an approximation of
// the real type. Pointers are in the correct location but the actual object may
// have some additional repetition, for example in the buffer of a slice.
func (r *runner) getLLVMTypeFromLayout(layoutValue value) llvm.Type {
objectSizeWords, bitmap := r.readObjectLayout(layoutValue)
if bitmap == nil {
// No information available.
return llvm.Type{}
}
if bitmap.BitLen() == 0 {
// There are no pointers in this object, so treat this as a raw byte
// buffer. This is important because objects without pointers may have
// lower alignment.
return r.mod.Context().Int8Type()
}
// Create the LLVM type.
pointerSize := layoutValue.len(r)
pointerAlignment := r.targetData.PrefTypeAlignment(r.i8ptrType)
var fields []llvm.Type
for i := 0; i < int(objectSizeWords); {
if bitmap.Bit(i) != 0 {
// Pointer field.
fields = append(fields, r.i8ptrType)
i += int(pointerSize / uint32(pointerAlignment))
} else {
// Byte/word field.
fields = append(fields, r.mod.Context().IntType(pointerAlignment*8))
i += 1
}
}
var llvmLayoutType llvm.Type
if len(fields) == 1 {
llvmLayoutType = fields[0]
} else {
llvmLayoutType = r.mod.Context().StructType(fields, false)
}
objectSizeBytes := objectSizeWords * uint64(pointerAlignment)
if checks && r.targetData.TypeAllocSize(llvmLayoutType) != objectSizeBytes {
panic("unexpected size") // sanity check
}
return llvmLayoutType
}
+53
View File
@@ -0,0 +1,53 @@
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c" \00\00\00\00\00\00\01" }
@pointerFree12 = global i8* null
@pointerFree7 = global i8* null
@pointerFree3 = global i8* null
@pointerFree0 = global i8* null
@layout1 = global i8* null
@layout2 = global i8* null
@layout3 = global i8* null
@layout4 = global i8* null
@bigobj1 = global i8* null
declare i8* @runtime.alloc(i32, i8*) unnamed_addr
define void @runtime.initAll() unnamed_addr {
call void @main.init()
ret void
}
define internal void @main.init() unnamed_addr {
; Object that's word-aligned.
%pointerFree12 = call i8* @runtime.alloc(i32 12, i8* inttoptr (i32 3 to i8*))
store i8* %pointerFree12, i8** @pointerFree12
; Object larger than a word but not word-aligned.
%pointerFree7 = call i8* @runtime.alloc(i32 7, i8* inttoptr (i32 3 to i8*))
store i8* %pointerFree7, i8** @pointerFree7
; Object smaller than a word (and of course not word-aligned).
%pointerFree3 = call i8* @runtime.alloc(i32 3, i8* inttoptr (i32 3 to i8*))
store i8* %pointerFree3, i8** @pointerFree3
; Zero-sized object.
%pointerFree0 = call i8* @runtime.alloc(i32 0, i8* inttoptr (i32 3 to i8*))
store i8* %pointerFree0, i8** @pointerFree0
; Object made out of 3 pointers.
%layout1 = call i8* @runtime.alloc(i32 12, i8* inttoptr (i32 67 to i8*))
store i8* %layout1, i8** @layout1
; Array (or slice) of 5 slices.
%layout2 = call i8* @runtime.alloc(i32 60, i8* inttoptr (i32 71 to i8*))
store i8* %layout2, i8** @layout2
; Oddly shaped object, using all bits in the layout integer.
%layout3 = call i8* @runtime.alloc(i32 104, i8* inttoptr (i32 2467830261 to i8*))
store i8* %layout3, i8** @layout3
; ...repeated.
%layout4 = call i8* @runtime.alloc(i32 312, i8* inttoptr (i32 2467830261 to i8*))
store i8* %layout4, i8** @layout4
; Large object that needs to be stored in a separate global.
%bigobj1 = call i8* @runtime.alloc(i32 248, i8* bitcast ({ i32, [8 x i8] }* @"runtime/gc.layout:62-2000000000000001" to i8*))
store i8* %bigobj1, i8** @bigobj1
ret void
}
+25
View File
@@ -0,0 +1,25 @@
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
@pointerFree12 = local_unnamed_addr global i8* getelementptr inbounds ([12 x i8], [12 x i8]* @"main$alloc", i32 0, i32 0)
@pointerFree7 = local_unnamed_addr global i8* getelementptr inbounds ([7 x i8], [7 x i8]* @"main$alloc.1", i32 0, i32 0)
@pointerFree3 = local_unnamed_addr global i8* getelementptr inbounds ([3 x i8], [3 x i8]* @"main$alloc.2", i32 0, i32 0)
@pointerFree0 = local_unnamed_addr global i8* getelementptr inbounds ([0 x i8], [0 x i8]* @"main$alloc.3", i32 0, i32 0)
@layout1 = local_unnamed_addr global i8* bitcast ([3 x i8*]* @"main$alloc.4" to i8*)
@layout2 = local_unnamed_addr global i8* bitcast ([5 x { i8*, i32, i32 }]* @"main$alloc.5" to i8*)
@layout3 = local_unnamed_addr global i8* bitcast ({ i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* }* @"main$alloc.6" to i8*)
@layout4 = local_unnamed_addr global i8* bitcast ([3 x { i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* }]* @"main$alloc.7" to i8*)
@bigobj1 = local_unnamed_addr global i8* bitcast ({ i8*, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i8* }* @"main$alloc.8" to i8*)
@"main$alloc" = internal global [12 x i8] zeroinitializer, align 4
@"main$alloc.1" = internal global [7 x i8] zeroinitializer, align 4
@"main$alloc.2" = internal global [3 x i8] zeroinitializer, align 4
@"main$alloc.3" = internal global [0 x i8] zeroinitializer, align 4
@"main$alloc.4" = internal global [3 x i8*] zeroinitializer, align 4
@"main$alloc.5" = internal global [5 x { i8*, i32, i32 }] zeroinitializer, align 4
@"main$alloc.6" = internal global { i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* } zeroinitializer, align 4
@"main$alloc.7" = internal global [3 x { i8*, i8*, i8*, i32, i32, i8*, i8*, i32, i32, i32, i32, i32, i32, i8*, i8*, i32, i32, i32, i8*, i8*, i32, i32, i8*, i32, i32, i8* }] zeroinitializer, align 4
@"main$alloc.8" = internal global { i8*, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i8* } zeroinitializer, align 4
define void @runtime.initAll() unnamed_addr {
ret void
}
+12
View File
@@ -66,6 +66,9 @@ entry:
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
store i16 7, i16* @main.exposedValue2
; Test that inline assembly is ignored.
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
; Test switch statement.
%switch1 = call i64 @testSwitch(i64 1) ; 1 returns 6
%switch2 = call i64 @testSwitch(i64 9) ; 9 returns the default value -1
@@ -102,6 +105,15 @@ entry:
ret void
}
; Inline assembly should be ignored in the interp package. While it is possible
; to modify other globals that way, usually that's not the case and there is no
; real way to check.
define void @hasInlineAsm() {
entry:
call void asm sideeffect "", ""()
ret void
}
define i64 @testSwitch(i64 %val) {
entry:
; Test switch statement.

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