Compare commits

...

221 Commits

Author SHA1 Message Date
Ayke van Laethem a9ba6ebad9 main: version 0.13.1
This release fixes a few bugs introduced in the previous 0.13.0 release.
2020-04-21 17:02:43 +02:00
Ayke van Laethem 565ff99c31 gba: always use ARM mode instead of Thumb mode
This results in bigger code size, but it works around a bug in the
linker.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    x = p[3]

It has two instructions:

    x_ptr = &p[3]
    x = *x_ptr

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

With this:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    tinygo gdb -target=hifive1b examples/echo

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

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

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

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

This commit zips the release tarball itself so it can be downloaded as
an artifact.
2020-01-05 12:43:21 +01:00
BCG 5f77447e1a Adding code use 12-bit resolution for ADC and to clear previous ADC INPUTCTRL before subsequent read 2020-01-05 12:34:47 +01:00
Ayke van Laethem 27fafb7ab5 runtime: fix atsamd51 volatile usage
It was still using the (long removed) //go:volatile pragma for volatile
variables, thus it was only accidentally working.
2020-01-05 12:08:33 +01:00
Ayke van Laethem d1cc3c109d compiler: add globaldce pass to start of optimization pipeline
This reduces code size in a few cases when tested against the drivers
smoketests (although there was one minor increase) without significantly
increasing compile time. In fact, in my testing compile time appears to
be going down a little bit (around 1%, within the noise).
2020-01-05 11:37:26 +01:00
308 changed files with 13567 additions and 5980 deletions
+77 -34
View File
@@ -14,18 +14,16 @@ commands:
- run:
name: "Install apt dependencies"
command: |
echo 'deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
sudo apt-get update
sudo apt-get install \
llvm<<parameters.llvm>>-dev \
clang<<parameters.llvm>> \
libclang<<parameters.llvm>>-dev \
lld<<parameters.llvm>> \
llvm-<<parameters.llvm>>-dev \
clang-<<parameters.llvm>> \
libclang-<<parameters.llvm>>-dev \
lld-<<parameters.llvm>> \
gcc-arm-linux-gnueabihf \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \
qemu-user \
gcc-avr \
@@ -43,28 +41,51 @@ commands:
steps:
- restore_cache:
keys:
- llvm-source-9-v0
- llvm-source-10-v0
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-9-v0
key: llvm-source-10-v0
paths:
- llvm-project
build-wasi-libc:
steps:
- restore_cache:
keys:
- wasi-libc-sysroot-v2
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-v2
paths:
- lib/wasi-libc/sysroot
test-linux:
parameters:
llvm:
type: string
steps:
- checkout
- submodules
- apt-dependencies:
llvm: "-9"
llvm: "<<parameters.llvm>>"
- install-node
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- llvm-source-linux
- run: go install .
- run: go test -v ./cgo ./compileopts ./interp ./transform .
- run: go install -tags=llvm<<parameters.llvm>> .
- restore_cache:
keys:
- wasi-libc-sysroot-systemclang-v1
- run: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-systemclang-v1
paths:
- lib/wasi-libc/sysroot
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./interp ./transform .
- run: make gen-device -j4
- run: make smoketest
- save_cache:
@@ -99,7 +120,7 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-9-linux-v0-assert
- llvm-build-10-linux-v0-assert
- run:
name: "Build LLVM"
command: |
@@ -117,10 +138,11 @@ commands:
make ASSERT=1 llvm-build
fi
- save_cache:
key: llvm-build-9-linux-v0-assert
key: llvm-build-10-linux-v0-assert
paths:
llvm-build
- run: make ASSERT=1
- build-wasi-libc
- run:
name: "Test TinyGo"
command: make ASSERT=1 test
@@ -157,7 +179,7 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-9-linux-v0
- llvm-build-10-linux-v0
- run:
name: "Build LLVM"
command: |
@@ -175,9 +197,10 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-9-linux-v0
key: llvm-build-10-linux-v0
paths:
llvm-build
- build-wasi-libc
- run:
name: "Test TinyGo"
command: make test
@@ -209,8 +232,8 @@ commands:
- run:
name: "Install dependencies"
command: |
curl https://dl.google.com/go/go1.13.darwin-amd64.tar.gz -o go1.13.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.13.darwin-amd64.tar.gz
curl https://dl.google.com/go/go1.14.darwin-amd64.tar.gz -o go1.14.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- restore_cache:
@@ -219,17 +242,17 @@ commands:
- go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-9-macos-v0
- llvm-source-10-macos-v0
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-9-macos-v0
key: llvm-source-10-macos-v0
paths:
- llvm-project
- restore_cache:
keys:
- llvm-build-9-macos-v0
- llvm-build-10-macos-v0
- run:
name: "Build LLVM"
command: |
@@ -241,9 +264,19 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-9-macos-v0
key: llvm-build-10-macos-v0
paths:
llvm-build
- restore_cache:
keys:
- wasi-libc-sysroot-macos-v1
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-macos-v1
paths:
- lib/wasi-libc/sysroot
- run:
name: "Test TinyGo"
command: make test
@@ -278,27 +311,36 @@ commands:
jobs:
test-llvm9-go111:
docker:
- image: circleci/golang:1.11-stretch
- image: circleci/golang:1.11-buster
steps:
- test-linux
test-llvm9-go112:
- test-linux:
llvm: "9"
test-llvm10-go112:
docker:
- image: circleci/golang:1.12-stretch
- image: circleci/golang:1.12-buster
steps:
- test-linux
test-llvm9-go113:
- test-linux:
llvm: "10"
test-llvm10-go113:
docker:
- image: circleci/golang:1.13-stretch
- image: circleci/golang:1.13-buster
steps:
- test-linux
- test-linux:
llvm: "10"
test-llvm10-go114:
docker:
- image: circleci/golang:1.14-buster
steps:
- test-linux:
llvm: "10"
assert-test-linux:
docker:
- image: circleci/golang:1.13-stretch
- image: circleci/golang:1.14-stretch
steps:
- assert-test-linux
build-linux:
docker:
- image: circleci/golang:1.13-stretch
- image: circleci/golang:1.14-stretch
steps:
- build-linux
build-macos:
@@ -314,8 +356,9 @@ workflows:
test-all:
jobs:
- test-llvm9-go111
- test-llvm9-go112
- test-llvm9-go113
- test-llvm10-go112
- test-llvm10-go113
- test-llvm10-go114
- build-linux
- build-macos
- assert-test-linux
+7 -1
View File
@@ -9,8 +9,14 @@
url = https://github.com/avr-rust/avr-mcu.git
[submodule "lib/cmsis-svd"]
path = lib/cmsis-svd
url = https://github.com/tinygo-org/cmsis-svd
url = https://github.com/posborne/cmsis-svd
[submodule "lib/compiler-rt"]
path = lib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt.git
branch = release_80
[submodule "lib/wasi-libc"]
path = lib/wasi-libc
url = https://github.com/CraneStation/wasi-libc
[submodule "lib/picolibc"]
path = lib/picolibc
url = https://github.com/keith-packard/picolibc.git
+158
View File
@@ -1,3 +1,161 @@
0.13.1
---
* **standard library**
- `runtime`: do not put scheduler and GC code in the same section
- `runtime`: copy stack scan assembly for GBA
* **boards**
- `gameboy-advance`: always use ARM mode instead of Thumb mode
0.13.0
---
* **command line**
- use `gdb-multiarch` for debugging Cortex-M chips
- support `tinygo run` with simavr
- support LLVM 10
- support Go 1.14
- retry 3 times when attempting to do a 1200-baud reset
* **compiler**
- mark the `abort` function as noreturn
- fix deferred calls to exported functions
- add debug info for local variables
- check for channel size limit
- refactor coroutine lowering
- add `dereferenceable_or_null` attribute to pointer parameters
- do not perform nil checking when indexing slices and on `unsafe.Pointer`
- remove `runtime.isnil` hack
- use LLVM builtins for runtime `memcpy`/`memmove`/`memzero` functions
- implement spec-compliant shifts on negative/overflow
- support anonymous type asserts
- track pointer result of string concatenation for GC
- track PHI nodes for GC
- add debug info to goroutine start wrappers
- optimize comparing interface values against nil
- fix miscompilation when deferring an interface call
- builder: include picolibc for most baremetal targets
- builder: run tools (clang, lld) as separate processes
- builder: use `-fshort-enums` consistently
- interp: add support for constant type asserts
- interp: better support for interface operations
- interp: include backtrace with error
- transform: do not track const globals for GC
- transform: replace panics with source locations
- transform: fix error in interface lowering pass
- transform: make coroutine lowering deterministic
- transform: fix miscompilation in func lowering
* **cgo**
- make `-I` and `-L` paths absolute
* **standard library**
- `machine`: set the USB VID and PID to the manufacturer values
- `machine`: correct USB CDC composite descriptors
- `machine`: move `errors.New` calls to globals
- `runtime`: support operations on nil maps
- `runtime`: fix copy builtin return value on AVR
- `runtime`: refactor goroutines
- `runtime`: support `-scheduler=none` on most platforms
- `runtime`: run package initialization in the main goroutine
- `runtime`: export `malloc` / `free` for use from C
- `runtime`: add garbage collector that uses an external allocator
- `runtime`: scan callee-saved registers while marking the stack
- `runtime`: remove recursion from conservative GC
- `runtime`: fix blocking select on nil channel
- `runtime/volatile`: include `ReplaceBits` method
- `sync`: implement trivial `sync.Map`
* **targets**
- `arm`: use `-fomit-frame-pointer`
- `atmega1284`: support this chip for testing purposes
- `atsamd51`: make QSPI available on all boards
- `atsamd51`: add support for ADC1
- `atsamd51`: use new interrupt registration in UART code
- `attiny`: clean up pin definitions
- `avr`: use the correct RAM start address
- `avr`: pass the correct `-mmcu` flag to the linker
- `avr`: add support for tasks scheduler (disabled by default)
- `avr`: fix linker problem with overlapping program/data areas
- `nrf`: fix typo in pin configuration options
- `nrf`: add lib/nrfx/mdk to include dirs
- `nrf52840`: implement USB-CDC
- `riscv`: implement VirtIO target and add RISC-V integration test
- `riscv`: add I2C support for the HiFive1 rev B board
- `stm32`: refactor GPIO pin handling
- `stm32`: refactor UART code
- `stm32f4`: add SPI
- `wasm`: support Go 1.14 (breaking previous versions)
- `wasm`: support `syscall/js.CopyBytesToJS`
- `wasm`: sync polyfills from Go 1.14.
* **boards**
- `arduino-mega2560`: add the Arduino Mega 2560
- `clue-alpha`: add the Adafruit CLUE Alpha
- `gameboy-advance`: enable debugging with GDB
- `particle-argon`: add the Particle Argon board
- `particle-boron`: add the Particle Boron board
- `particle-xenon`: add the Particle Xenon board
- `reelboard`: add `reelboard-s140v7` SoftDevice target
0.12.0
---
* **command line**
- add initial FreeBSD support
- remove getting a serial port in gdb subcommand
- add support for debugging through JLinkGDBServer
- fix CGo when cross compiling
- remove default port check for Digispark as micronucleus communicates directly using HID
- differentiate between various serial/USB error messages
* **builder**
- improve detection of Clang headers
* **compiler**
- fix assertion on empty interface
- don't crash when encountering `types.Invalid`
- revise defer to use heap allocations when running a variable number of times
- improve error messages for failed imports
- improve "function redeclared" error
- add globaldce pass to start of optimization pipeline
- add support for debugging globals
- implement RISC-V CSR operations as intrinsics
- add support for CGO_ENABLED environment variable
- do not emit debug info for extern globals (bugfix)
- add support for interrupts
- implement maps for arbitrary keys
- interp: error location for "unknown GEP" error
- wasm-abi: create temporary allocas in the entry block
* **cgo**
- add support for symbols in `#define`
- fix a bug in number tokenization
* **standard library**
- `machine`: avoid bytes package in USB logic
- `runtime`: fix external address declarations
- `runtime`: provide implementation for `internal/bytealg.IndexByte`
* **targets**
- `atsamd51`: fix volatile usage
- `atsamd51`: fix ADC, updating to 12-bits precision
- `atsamd51`: refactor SPI pin configuration to only look at pin numbers
- `atsamd51`: switch UART to use new pin configuration
- `atsamd51`: fix obvious bug in I2C code
- `atsamd51`: use only the necessary UART interrupts
- `atsamd51`: refactor I2C pin handling to auto-detect pin mode
- `avr`: use a garbage collector
- `fe310`: use CLINT peripheral for timekeeping
- `fe310`: add support for PLIC interrupts
- `fe310`: implement UART receive interrupts
- `riscv`: support sleeping in QEMU
- `riscv`: add bare-bones interrupt support
- `riscv`: print exception PC and code
- `wasm`: implement memcpy and memset
- `wasm`: include wasi-libc
- `wasm`: use wasi ABI for basic startup/stdout
* **boards**
- `arduino`: make avrdude command line compatible with Windows
- `arduino-nano`: add this board
- `arduino-nano33`: fix UART1 and UART2
- `circuitplay-bluefruit`: add this board
- `digispark`: add clock speed and pin mappings
- `gameboy-advance`: include compiler-rt in build
- `gameboy-advance`: implement interrupt handler
- `hifive1b`: add support for gdb subcommand
- `pyportal`: add this board
- `pyportal`: remove manual SPI pin mapping as now handled by default
0.11.0
---
* **command line**
+1
View File
@@ -15,3 +15,4 @@ Ayke van Laethem <aykevanlaethem@gmail.com>
Daniel Esteban <conejo@conejo.me>
Loon, LLC.
Ron Evans <ron@hybridgroup.com>
Jaden Weiss <jaden@jadendw.dev>
+11 -8
View File
@@ -1,10 +1,10 @@
# TinyGo base stage installs Go 1.13, LLVM 9 and the TinyGo compiler itself.
FROM golang:1.13 AS tinygo-base
# TinyGo base stage installs Go 1.14, LLVM 10 and the TinyGo compiler itself.
FROM golang:1.14 AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-9-dev libclang-9-dev git
apt-get install -y llvm-10-dev libclang-10-dev git
COPY . /tinygo
@@ -12,8 +12,11 @@ COPY . /tinygo
# after copying the tinygo directory in the previous step.
RUN cd /tinygo/ && \
rm -rf ./lib/* && \
git submodule sync && \
git submodule update --init --recursive --force
COPY ./lib/picolibc-include/* /tinygo/lib/picolibc-include/
RUN cd /tinygo/ && \
go install /tinygo/
@@ -25,9 +28,9 @@ COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y libllvm9 lld-9
apt-get install -y libllvm10 lld-10
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
FROM tinygo-base AS tinygo-avr
@@ -58,7 +61,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils make clang-9 && \
apt-get install -y apt-utils make clang-10 && \
make gen-device-nrf && make gen-device-stm32
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
@@ -70,7 +73,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils make clang-9 binutils-avr gcc-avr avr-libc && \
apt-get install -y apt-utils make clang-10 binutils-avr gcc-avr avr-libc && \
make gen-device
CMD ["tinygo"]
+2 -2
View File
@@ -1,7 +1,7 @@
Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
Copyright (c) 2018-2020 TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library.
Copyright (c) 2009-2019 The Go Authors. All rights reserved.
Copyright (c) 2009-2020 The Go Authors. All rights reserved.
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
+81 -16
View File
@@ -4,8 +4,30 @@ all: tinygo
# Default build and source directories, as created by `make llvm-build`.
LLVM_BUILDDIR ?= llvm-build
CLANG_SRC ?= llvm-project/clang
LLD_SRC ?= llvm-project/lld
LLVM_PROJECTDIR ?= llvm-project
CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
# Try to autodetect LLVM build tools.
ifneq (, $(shell command -v llvm-build/bin/clang 2> /dev/null))
CLANG ?= $(abspath llvm-build/bin/clang)
else
CLANG ?= clang-10
endif
ifneq (, $(shell command -v llvm-build/bin/llvm-ar 2> /dev/null))
LLVM_AR ?= $(abspath llvm-build/bin/llvm-ar)
else ifneq (, $(shell command -v llvm-ar-10 2> /dev/null))
LLVM_AR ?= llvm-ar-10
else
LLVM_AR ?= llvm-ar
endif
ifneq (, $(shell command -v llvm-build/bin/llvm-nm 2> /dev/null))
LLVM_NM ?= $(abspath llvm-build/bin/llvm-nm)
else ifneq (, $(shell command -v llvm-nm-10 2> /dev/null))
LLVM_NM ?= llvm-nm-10
else
LLVM_NM ?= llvm-nm
endif
# Go binary and GOROOT to select
GO ?= go
@@ -18,7 +40,7 @@ MD5SUM = md5sum
TINYGO ?= tinygo
# Use CCACHE for LLVM if possible
ifneq (, $(shell which ccache))
ifneq (, $(shell command -v ccache 2> /dev/null))
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=ON'
endif
@@ -31,7 +53,7 @@ endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
ifeq ($(OS),Windows_NT)
EXE = .exe
@@ -81,8 +103,8 @@ LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -
# For static linking.
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++11
CGO_LDFLAGS+=$(LIBCLANG_PATH) -std=c++11 -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
CGO_CXXFLAGS=-std=c++14
CGO_LDFLAGS+=$(LIBCLANG_PATH) -std=c++14 -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
endif
@@ -116,7 +138,7 @@ gen-device-sam: build/gen-device-svd
GO111MODULE=off $(GO) fmt ./src/device/sam
gen-device-sifive: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
GO111MODULE=off $(GO) fmt ./src/device/sifive
gen-device-stm32: build/gen-device-svd
@@ -125,26 +147,33 @@ gen-device-stm32: build/gen-device-svd
# Get LLVM sources.
llvm-project/README.md:
git clone -b release/9.x https://github.com/llvm/llvm-project
llvm-source: llvm-project/README.md
$(LLVM_PROJECTDIR)/README.md:
git clone -b release/10.x https://github.com/llvm/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/README.md
# Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
# Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
cd $(LLVM_BUILDDIR); ninja
# Build wasi-libc sysroot
.PHONY: wasi-libc
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
cd lib/wasi-libc && make -j4 WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
# Build the Go compiler.
tinygo:
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
test:
test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform .
tinygo-test:
@@ -185,6 +214,8 @@ smoketest:
# test simulated boards on play.tinygo.org
$(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=hifive1-qemu examples/serial
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
@@ -226,8 +257,12 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=clue_alpha examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
@$(MD5SUM) test.gba
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
@@ -238,6 +273,14 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=metro-m4-airlift examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pyportal examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-argon examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-boron examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
@@ -246,23 +289,35 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1
@$(MD5SUM) test.hex
ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
@$(MD5SUM) test.hex
endif
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
release: tinygo gen-device
release: tinygo gen-device wasi-libc
@mkdir -p build/release/tinygo/bin
@mkdir -p build/release/tinygo/lib/clang/include
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
@mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
@mkdir -p build/release/tinygo/lib/wasi-libc
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
@@ -275,9 +330,19 @@ release: tinygo gen-device
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
@cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc
@cp -rp lib/picolibc-include build/release/tinygo/lib
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
@cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets
./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a compiler-rt
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc
tar -czf build/release.tar.gz -C build/release tinygo
+10 -2
View File
@@ -43,16 +43,21 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 22 microcontroller boards are currently supported:
The following 32 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit CLUE Alpha](https://www.adafruit.com/product/4500)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [BBC micro:bit](https://microbit.org/)
@@ -62,6 +67,9 @@ The following 22 microcontroller boards are currently supported:
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
* [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/)
* [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/)
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
* [PineTime DevKit](https://www.pine64.org/pinetime/)
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
@@ -128,4 +136,4 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/9.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/10.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
+21 -12
View File
@@ -10,11 +10,14 @@ jobs:
pool:
vmImage: 'VS2017-Win2016'
steps:
- task: GoTool@0
inputs:
version: '1.14.1'
- checkout: self
- task: CacheBeta@0
displayName: Cache LLVM source
inputs:
key: llvm-source-9-windows-v0
key: llvm-source-10-windows-v0
path: llvm-project
- task: Bash@3
displayName: Download LLVM source
@@ -24,7 +27,7 @@ jobs:
- task: CacheBeta@0
displayName: Cache LLVM build
inputs:
key: llvm-build-9-windows-v0
key: llvm-build-10-windows-v0
path: llvm-build
- task: Bash@3
displayName: Build LLVM
@@ -34,10 +37,6 @@ jobs:
if [ ! -f llvm-build/lib/liblldELF.a ]
then
choco install ninja
# LLVM 9 cannot be built with MinGW 8.
# For details: https://reviews.llvm.org/D70266
choco uninstall mingw
choco install mingw --version=7.3.0
make llvm-build
fi
- task: Bash@3
@@ -45,12 +44,22 @@ jobs:
inputs:
targetType: inline
script: choco install qemu
- task: CacheBeta@0
displayName: Cache wasi-libc sysroot
inputs:
key: wasi-libc-sysroot-v2
path: lib/wasi-libc/sysroot
- task: Bash@3
displayName: Build wasi-libc
inputs:
targetType: inline
script: PATH=/usr/bin:$PATH make wasi-libc
- task: Bash@3
displayName: Test TinyGo
inputs:
targetType: inline
script: |
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make test
- task: Bash@3
@@ -58,17 +67,17 @@ jobs:
inputs:
targetType: inline
script: |
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make release -j4
- publish: $(System.DefaultWorkingDirectory)/build/release.tar.gz
displayName: Publish tarball as artifact
artifact: tinygo.windows-amd64.tar.gz
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
displayName: Publish zip as artifact
artifact: tinygo
- task: Bash@3
displayName: Smoke tests
inputs:
targetType: inline
script: |
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make smoketest TINYGO=build/tinygo AVR=0
+58 -43
View File
@@ -16,6 +16,8 @@ import (
"github.com/tinygo-org/tinygo/compiler"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
// Build performs a single package to executable Go build. It takes in a package
@@ -25,34 +27,34 @@ import (
// The error value may be of type *MultiError. Callers will likely want to check
// for this case and print such errors individually.
func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error {
c, err := compiler.NewCompiler(pkgName, config)
// Compile Go code to IR.
machine, err := compiler.NewTargetMachine(config)
if err != nil {
return err
}
// Compile Go code to IR.
errs := c.Compile(pkgName)
if len(errs) != 0 {
mod, extraFiles, errs := compiler.Compile(pkgName, machine, config)
if errs != nil {
return newMultiError(errs)
}
if config.Options.PrintIR {
fmt.Println("; Generated LLVM IR:")
fmt.Println(c.IR())
fmt.Println(mod.String())
}
if err := c.Verify(); err != nil {
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after IR construction")
}
err = interp.Run(c.Module(), config.DumpSSA())
err = interp.Run(mod, config.DumpSSA())
if err != nil {
return err
}
if err := c.Verify(); err != nil {
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after interpreting runtime.initAll")
}
if config.GOOS() != "darwin" {
c.ApplyFunctionSections() // -ffunction-sections
transform.ApplyFunctionSections(mod) // -ffunction-sections
}
// Browsers cannot handle external functions that have type i64 because it
@@ -61,7 +63,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// stack-allocated values.
// Use -wasm-abi=generic to disable this behaviour.
if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") {
err := c.ExternalInt64AsPtr()
err := transform.ExternalInt64AsPtr(mod)
if err != nil {
return err
}
@@ -71,23 +73,23 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// exactly.
errs = nil
switch config.Options.Opt {
case "none:", "0":
errs = c.Optimize(0, 0, 0) // -O0
case "none", "0":
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
case "1":
errs = c.Optimize(1, 0, 0) // -O1
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
case "2":
errs = c.Optimize(2, 0, 225) // -O2
errs = transform.Optimize(mod, config, 2, 0, 225) // -O2
case "s":
errs = c.Optimize(2, 1, 225) // -Os
errs = transform.Optimize(mod, config, 2, 1, 225) // -Os
case "z":
errs = c.Optimize(2, 2, 5) // -Oz, default
errs = transform.Optimize(mod, config, 2, 2, 5) // -Oz, default
default:
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
}
if len(errs) > 0 {
return newMultiError(errs)
}
if err := c.Verify(); err != nil {
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification failure after LLVM optimization passes")
}
@@ -97,8 +99,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// pointers are flash and which are in RAM so that pointers can have a
// correct address space parameter (address space 1 is for flash).
if strings.HasPrefix(config.Triple(), "avr") {
c.NonConstGlobals()
if err := c.Verify(); err != nil {
transform.NonConstGlobals(mod)
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after making all globals non-constant on AVR")
}
}
@@ -107,11 +109,17 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
outext := filepath.Ext(outpath)
switch outext {
case ".o":
return c.EmitObject(outpath)
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666)
case ".bc":
return c.EmitBitcode(outpath)
data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes()
return ioutil.WriteFile(outpath, data, 0666)
case ".ll":
return c.EmitText(outpath)
data := []byte(mod.String())
return ioutil.WriteFile(outpath, data, 0666)
default:
// Act as a compiler driver.
@@ -124,29 +132,39 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// Write the object file.
objfile := filepath.Join(dir, "main.o")
err = c.EmitObject(objfile)
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
// Load builtins library from the cache, possibly compiling it on the
// fly.
var librt string
if config.Target.RTLib == "compiler-rt" {
librt, err = loadBuiltins(config.Triple())
if err != nil {
return err
}
err = ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
if err != nil {
return err
}
// Prepare link command.
executable := filepath.Join(dir, "main")
tmppath := executable // final file
ldflags := append(config.LDFlags(), "-o", executable, objfile)
// Load builtins library from the cache, possibly compiling it on the
// fly.
if config.Target.RTLib == "compiler-rt" {
librt, err := CompilerRT.Load(config.Triple())
if err != nil {
return err
}
ldflags = append(ldflags, librt)
}
// Add libc.
if config.Target.Libc == "picolibc" {
libc, err := Picolibc.Load(config.Triple())
if err != nil {
return err
}
ldflags = append(ldflags, libc)
}
// Compile extra files.
root := goenv.Get("TINYGOROOT")
for i, path := range config.ExtraFiles() {
@@ -160,16 +178,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
}
// Compile C files in packages.
for i, pkg := range c.Packages() {
for _, file := range pkg.CFiles {
path := filepath.Join(pkg.Package.Dir, file)
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, path)...)
if err != nil {
return &commandError{"failed to build", path, err}
}
ldflags = append(ldflags, outpath)
for i, file := range extraFiles {
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+filepath.Base(file)+".o")
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
if err != nil {
return &commandError{"failed to build", file, err}
}
ldflags = append(ldflags, outpath)
}
// Link the object files together.
@@ -207,7 +222,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
} else if outext == ".uf2" {
// Get UF2 from the .elf file.
tmppath = filepath.Join(dir, "main"+outext)
err := convertELFFileToUF2File(executable, tmppath)
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
if err != nil {
return err
}
+15 -105
View File
@@ -1,12 +1,7 @@
package builder
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
// These are the GENERIC_SOURCES according to CMakeList.txt.
@@ -156,105 +151,20 @@ var aeabiBuiltins = []string{
"arm/aeabi_uldivmod.S",
}
func builtinFiles(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if strings.HasPrefix(target, "arm") {
builtins = append(builtins, aeabiBuiltins...)
}
return builtins
}
// builtinsDir returns the directory where the sources for compiler-rt are kept.
func builtinsDir() string {
return filepath.Join(goenv.Get("TINYGOROOT"), "lib", "compiler-rt", "lib", "builtins")
}
// Get the builtins archive, possibly generating it as needed.
func loadBuiltins(target string) (path string, err error) {
// Try to load a precompiled compiler-rt library.
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, "compiler-rt.a")
if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled compiler-rt for this OS/architecture. Return the
// path directly.
return precompiledPath, nil
}
outfile := "librt-" + target + ".a"
builtinsDir := builtinsDir()
builtins := builtinFiles(target)
srcs := make([]string, len(builtins))
for i, name := range builtins {
srcs[i] = filepath.Join(builtinsDir, name)
}
if path, err := cacheLoad(outfile, commands["clang"][0], srcs); path != "" || err != nil {
return path, err
}
var cachepath string
err = CompileBuiltins(target, func(path string) error {
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
cachepath = path
return err
})
return cachepath, err
}
// CompileBuiltins compiles builtins from compiler-rt into a static library.
// When it succeeds, it will call the callback with the resulting path. The path
// will be removed after callback returns. If callback returns an error, this is
// passed through to the return value of this function.
func CompileBuiltins(target string, callback func(path string) error) error {
builtinsDir := builtinsDir()
builtins := builtinFiles(target)
srcs := make([]string, len(builtins))
for i, name := range builtins {
srcs[i] = filepath.Join(builtinsDir, name)
}
dirPrefix := "tinygo-builtins"
remapDir := filepath.Join(os.TempDir(), dirPrefix)
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
if err != nil {
return err
}
defer os.RemoveAll(dir)
// Compile all builtins.
// TODO: use builtins optimized for a given target if available.
objs := make([]string, 0, len(builtins))
for _, name := range builtins {
objname := name
if strings.LastIndexByte(objname, '/') >= 0 {
objname = objname[strings.LastIndexByte(objname, '/'):]
// CompilerRT is a library with symbols required by programs compiled with LLVM.
// These symbols are for operations that cannot be emitted with a single
// instruction or a short sequence of instructions for that target.
//
// For more information, see: https://compiler-rt.llvm.org/
var CompilerRT = Library{
name: "compiler-rt",
cflags: func() []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} },
sourceDir: "lib/compiler-rt/lib/builtins",
sources: func(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
builtins = append(builtins, aeabiBuiltins...)
}
objpath := filepath.Join(dir, objname+".o")
objs = append(objs, objpath)
srcpath := filepath.Join(builtinsDir, name)
// Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the
// archive itself, which varies each run.
args := []string{"-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target=" + target, "-fdebug-prefix-map=" + dir + "=" + remapDir}
if strings.HasPrefix(target, "riscv32-") {
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
}
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
}
// Put all the object files in a single archive. This archive file will be
// used to statically link compiler-rt.
arpath := filepath.Join(dir, "librt.a")
err = makeArchive(arpath, objs)
if err != nil {
return err
}
// Give the caller the resulting file. The callback must copy the file,
// because after it returns the temporary directory will be removed.
return callback(arpath)
return builtins
},
}
+24 -18
View File
@@ -48,6 +48,7 @@
#include "llvm/Support/Host.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/TargetRegistry.h"
@@ -70,12 +71,12 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
bool Success = true;
// Parse the arguments.
std::unique_ptr<OptTable> OptTbl(createDriverOptTable());
const OptTable &OptTbl = getDriverOptTable();
const unsigned IncludedFlagsBitmask = options::CC1AsOption;
unsigned MissingArgIndex, MissingArgCount;
InputArgList Args = OptTbl->ParseArgs(Argv, MissingArgIndex, MissingArgCount,
IncludedFlagsBitmask);
InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount,
IncludedFlagsBitmask);
// Check for missing argument error.
if (MissingArgCount) {
@@ -88,7 +89,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
auto ArgString = A->getAsString(Args);
std::string Nearest;
if (OptTbl->findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
if (OptTbl.findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
Diags.Report(diag::err_drv_unknown_argument) << ArgString;
else
Diags.Report(diag::err_drv_unknown_argument_with_suggestion)
@@ -181,6 +182,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.RelaxAll = Args.hasArg(OPT_mrelax_all);
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn);
Opts.RelocationModel = Args.getLastArgValue(OPT_mrelocation_model, "pic");
Opts.TargetABI = Args.getLastArgValue(OPT_target_abi);
Opts.IncrementalLinkerCompatible =
@@ -208,8 +210,8 @@ getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
sys::RemoveFileOnSignal(Path);
std::error_code EC;
auto Out = llvm::make_unique<raw_fd_ostream>(
Path, EC, (Binary ? sys::fs::F_None : sys::fs::F_Text));
auto Out = std::make_unique<raw_fd_ostream>(
Path, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_Text));
if (EC) {
Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message();
return nullptr;
@@ -245,7 +247,9 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(Opts.Triple));
assert(MRI && "Unable to create target register info!");
std::unique_ptr<MCAsmInfo> MAI(TheTarget->createMCAsmInfo(*MRI, Opts.Triple));
MCTargetOptions MCOptions;
std::unique_ptr<MCAsmInfo> MAI(
TheTarget->createMCAsmInfo(*MRI, Opts.Triple, MCOptions));
assert(MAI && "Unable to create target asm info!");
// Ensure MCAsmInfo initialization occurs before any use, otherwise sections
@@ -269,7 +273,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
std::unique_ptr<MCObjectFileInfo> MOFI(new MCObjectFileInfo());
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr);
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr, &MCOptions);
bool PIC = false;
if (Opts.RelocationModel == "static") {
@@ -326,7 +330,8 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
raw_pwrite_stream *Out = FDOS.get();
std::unique_ptr<buffer_ostream> BOS;
MCTargetOptions MCOptions;
MCOptions.MCNoWarn = Opts.NoWarn;
MCOptions.MCFatalWarnings = Opts.FatalWarnings;
MCOptions.ABIName = Opts.TargetABI;
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
@@ -340,7 +345,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
std::unique_ptr<MCAsmBackend> MAB(
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
auto FOut = llvm::make_unique<formatted_raw_ostream>(*Out);
auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
Str.reset(TheTarget->createAsmStreamer(
Ctx, std::move(FOut), /*asmverbose*/ true,
/*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB),
@@ -351,7 +356,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
assert(Opts.OutputType == AssemblerInvocation::FT_Obj &&
"Invalid file type!");
if (!FDOS->supportsSeeking()) {
BOS = make_unique<buffer_ostream>(*FDOS);
BOS = std::make_unique<buffer_ostream>(*FDOS);
Out = BOS.get();
}
@@ -436,7 +441,7 @@ static void LLVMErrorHandler(void *UserData, const std::string &Message,
Diags.Report(diag::err_fe_error_backend) << Message;
// We cannot recover from llvm errors.
exit(1);
sys::Process::Exit(1);
}
int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
@@ -464,11 +469,11 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
return 1;
if (Asm.ShowHelp) {
std::unique_ptr<OptTable> Opts(driver::createDriverOptTable());
Opts->PrintHelp(llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler",
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
/*ShowAllAliases=*/false);
getDriverOptTable().PrintHelp(
llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler",
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
/*ShowAllAliases=*/false);
return 0;
}
@@ -485,7 +490,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
// FIXME: Remove this, one day.
if (!Asm.LLVMArgs.empty()) {
unsigned NumArgs = Asm.LLVMArgs.size();
auto Args = llvm::make_unique<const char*[]>(NumArgs + 2);
auto Args = std::make_unique<const char*[]>(NumArgs + 2);
Args[0] = "clang (LLVM option parsing)";
for (unsigned i = 0; i != NumArgs; ++i)
Args[i + 1] = Asm.LLVMArgs[i].c_str();
@@ -499,6 +504,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
// If any timers were active but haven't been destroyed yet, print their
// results now.
TimerGroup::printAll(errs());
TimerGroup::clearAll();
return !!Failed;
}
+2 -2
View File
@@ -11,8 +11,6 @@
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/ArrayRef.h"
/// Helper class for representing a single invocation of the assembler.
struct AssemblerInvocation {
/// @name Target Options
@@ -82,6 +80,7 @@ struct AssemblerInvocation {
unsigned RelaxAll : 1;
unsigned NoExecStack : 1;
unsigned FatalWarnings : 1;
unsigned NoWarn : 1;
unsigned IncrementalLinkerCompatible : 1;
unsigned EmbedBitcode : 1;
@@ -107,6 +106,7 @@ public:
RelaxAll = 0;
NoExecStack = 0;
FatalWarnings = 0;
NoWarn = 0;
IncrementalLinkerCompatible = 0;
DwarfVersion = 0;
EmbedBitcode = 0;
+3 -3
View File
@@ -52,8 +52,7 @@ bool tinygo_clang_driver(int argc, char **argv) {
std::unique_ptr<clang::CompilerInstance> Clang(new clang::CompilerInstance());
bool success = clang::CompilerInvocation::CreateFromArgs(
Clang->getInvocation(),
const_cast<const char **>(CCArgs.data()),
const_cast<const char **>(CCArgs.data()) + CCArgs.size(),
CCArgs,
Diags);
if (!success) {
return false;
@@ -74,7 +73,8 @@ bool tinygo_clang_driver(int argc, char **argv) {
} else if (strcmp(*CCArgs.data(), "-cc1as") == 0) {
// This is the assembler frontend. Parse the arguments.
AssemblerInvocation Asm;
if (!AssemblerInvocation::CreateFromArgs(Asm, llvm::ArrayRef<const char*>(CCArgs).slice(1), Diags))
ArrayRef<const char *> Argv = llvm::ArrayRef<const char*>(CCArgs);
if (!AssemblerInvocation::CreateFromArgs(Asm, Argv.slice(1), Diags))
return false;
// Execute the invocation, unless there were parsing errors.
+17 -13
View File
@@ -6,25 +6,28 @@ import (
"os/exec"
"runtime"
"strings"
"tinygo.org/x/go-llvm"
)
// Commands lists command alternatives for various operating systems. These
// commands may have a slightly different name across operating systems and
// distributions or may not even exist in $PATH, in which case absolute paths
// may be used.
var commands = map[string][]string{
"clang": {"clang-9"},
"ld.lld": {"ld.lld-9", "ld.lld"},
"wasm-ld": {"wasm-ld-9", "wasm-ld"},
}
var commands = map[string][]string{}
func init() {
// Add the path to a Homebrew-installed LLVM 9 for ease of use (no need to
llvmMajor := strings.Split(llvm.Version, ".")[0]
commands["clang"] = []string{"clang-" + llvmMajor}
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
// Add the path to a Homebrew-installed LLVM for ease of use (no need to
// manually set $PATH).
if runtime.GOOS == "darwin" {
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm@9/bin/clang-9")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm@9/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm@9/bin/wasm-ld")
prefix := "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
}
// Add the path for when LLVM was installed with the installer from
// llvm.org, which by default doesn't add LLVM to the $PATH environment
@@ -34,11 +37,12 @@ func init() {
commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
}
// Add the path to the llvm90 installed from ports
// Add the path to LLVM installed from ports.
if runtime.GOOS == "freebsd" {
commands["clang"] = append(commands["clang"], "/usr/local/llvm90/bin/clang-9")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/llvm90/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/llvm90/bin/wasm-ld")
prefix := "/usr/local/llvm" + llvmMajor + "/bin/"
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
}
}
-60
View File
@@ -1,60 +0,0 @@
// +build byollvm
package builder
import (
"errors"
"os"
"os/exec"
"unsafe"
"github.com/tinygo-org/tinygo/goenv"
)
/*
#cgo CXXFLAGS: -fno-rtti
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_clang_driver(int argc, char **argv);
*/
import "C"
// runCCompiler invokes a C compiler with the given arguments.
//
// This version invokes the built-in Clang when trying to run the Clang compiler.
func runCCompiler(command string, flags ...string) error {
switch command {
case "clang":
// Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
return errors.New("could not locate Clang headers")
}
flags = append(flags, "-I"+headerPath)
flags = append([]string{"tinygo:" + command}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_clang_driver(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to compile using built-in clang")
}
return nil
default:
// Running some other compiler. Maybe it has been defined in the
// commands map (unlikely).
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
// Alternatively, run the compiler directly.
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
}
-24
View File
@@ -1,24 +0,0 @@
// +build !byollvm
package builder
// This file provides a way to a C compiler as an external command. See also:
// clang-external.go
import (
"os"
"os/exec"
)
// runCCompiler invokes a C compiler with the given arguments.
//
// This version always runs the compiler as an external command.
func runCCompiler(command string, flags ...string) error {
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
+2 -2
View File
@@ -25,8 +25,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 != 11 && minor != 12 && minor != 13) {
return nil, fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor)
if major != 1 || minor < 11 || minor > 14 {
return nil, fmt.Errorf("requires go version 1.11, 1.12, 1.13, or 1.14, got go%d.%d", major, minor)
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{
+102
View File
@@ -0,0 +1,102 @@
package builder
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
// Library is a container for information about a single C library, such as a
// compiler runtime or libc.
type Library struct {
// The library name, such as compiler-rt or picolibc.
name string
cflags func() []string
// The source directory, relative to TINYGOROOT.
sourceDir string
// The source files, relative to sourceDir.
sources func(target string) []string
}
// fullPath returns the full path to the source directory.
func (l *Library) fullPath() string {
return filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir)
}
// sourcePaths returns a slice with the full paths to the source files.
func (l *Library) sourcePaths(target string) []string {
sources := l.sources(target)
paths := make([]string, len(sources))
for i, name := range sources {
paths[i] = filepath.Join(l.fullPath(), name)
}
return paths
}
// Load the library archive, possibly generating and caching it if needed.
func (l *Library) Load(target string) (path string, err error) {
// Try to load a precompiled library.
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled library for this OS/architecture. Return the path
// directly.
return precompiledPath, nil
}
outfile := l.name + "-" + target + ".a"
// Try to fetch this library from the cache.
if path, err := cacheLoad(outfile, commands["clang"][0], l.sourcePaths(target)); path != "" || err != nil {
// Cache hit.
return path, err
}
// Cache miss, build it now.
dirPrefix := "tinygo-" + l.name
remapDir := filepath.Join(os.TempDir(), dirPrefix)
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
if err != nil {
return "", err
}
defer os.RemoveAll(dir)
// Precalculate the flags to the compiler invocation.
args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
args = append(args, "-fshort-enums", "-fomit-frame-pointer")
}
if strings.HasPrefix(target, "riscv32-") {
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
}
// Compile all sources.
var objs []string
for _, srcpath := range l.sourcePaths(target) {
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
objs = append(objs, objpath)
// Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the
// archive itself, which varies each run.
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
if err != nil {
return "", &commandError{"failed to build", srcpath, err}
}
}
// Put all the object files in a single archive. This archive file will be
// used to statically link this library.
arpath := filepath.Join(dir, l.name+".a")
err = makeArchive(arpath, objs)
if err != nil {
return "", err
}
// Store this archive in the cache.
return cacheStore(arpath, outfile, commands["clang"][0], l.sourcePaths(target))
}
-71
View File
@@ -1,71 +0,0 @@
// +build byollvm
package builder
// This file provides a Link() function that uses the bundled lld if possible.
import (
"errors"
"os"
"os/exec"
"unsafe"
"github.com/tinygo-org/tinygo/goenv"
)
/*
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_link_elf(int argc, char **argv);
bool tinygo_link_wasm(int argc, char **argv);
*/
import "C"
// link invokes a linker with the given name and flags.
//
// This version uses the built-in linker when trying to use lld.
func link(linker string, flags ...string) error {
switch linker {
case "ld.lld":
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_link_elf(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to link using built-in ld.lld")
}
return nil
case "wasm-ld":
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
defer C.free(buf)
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_link_wasm(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to link using built-in wasm-ld")
}
return nil
default:
// Fall back to external command.
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
}
-27
View File
@@ -1,27 +0,0 @@
// +build !byollvm
package builder
// This file provides a Link() function that always runs an external command. It
// is provided for when tinygo is built without linking to liblld.
import (
"os"
"os/exec"
"github.com/tinygo-org/tinygo/goenv"
)
// link invokes a linker with the given name and arguments.
//
// This version always runs the linker as an external command.
func link(linker string, flags ...string) error {
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
+2 -2
View File
@@ -8,12 +8,12 @@ extern "C" {
bool tinygo_link_elf(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::elf::link(args, false);
return lld::elf::link(args, false, llvm::outs(), llvm::errs());
}
bool tinygo_link_wasm(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::wasm::link(args, false);
return lld::wasm::link(args, false, llvm::outs(), llvm::errs());
}
} // external "C"
+127
View File
@@ -0,0 +1,127 @@
package builder
import (
"path/filepath"
"github.com/tinygo-org/tinygo/goenv"
)
// Picolibc is a C library for bare metal embedded devices. It was originally
// based on newlib.
var Picolibc = Library{
name: "picolibc",
cflags: func() []string {
picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc")
return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "--sysroot=" + picolibcDir, "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"}
},
sourceDir: "lib/picolibc/newlib/libc",
sources: func(target string) []string {
return picolibcSources
},
}
var picolibcSources = []string{
"string/bcmp.c",
"string/bcopy.c",
"string/bzero.c",
"string/explicit_bzero.c",
"string/ffsl.c",
"string/ffsll.c",
"string/fls.c",
"string/flsl.c",
"string/flsll.c",
"string/gnu_basename.c",
"string/index.c",
"string/memccpy.c",
"string/memchr.c",
"string/memcmp.c",
"string/memcpy.c",
"string/memmem.c",
"string/memmove.c",
"string/mempcpy.c",
"string/memrchr.c",
"string/memset.c",
"string/rawmemchr.c",
"string/rindex.c",
"string/stpcpy.c",
"string/stpncpy.c",
"string/strcasecmp.c",
"string/strcasecmp_l.c",
"string/strcasestr.c",
"string/strcat.c",
"string/strchr.c",
"string/strchrnul.c",
"string/strcmp.c",
"string/strcoll.c",
"string/strcoll_l.c",
"string/strcpy.c",
"string/strcspn.c",
"string/strdup.c",
"string/strerror.c",
"string/strerror_r.c",
"string/strlcat.c",
"string/strlcpy.c",
"string/strlen.c",
"string/strlwr.c",
"string/strncasecmp.c",
"string/strncasecmp_l.c",
"string/strncat.c",
"string/strncmp.c",
"string/strncpy.c",
"string/strndup.c",
"string/strnlen.c",
"string/strnstr.c",
"string/strpbrk.c",
"string/strrchr.c",
"string/strsep.c",
"string/strsignal.c",
"string/strspn.c",
"string/strstr.c",
"string/strtok.c",
"string/strtok_r.c",
"string/strupr.c",
"string/strverscmp.c",
"string/strxfrm.c",
"string/strxfrm_l.c",
"string/swab.c",
"string/timingsafe_bcmp.c",
"string/timingsafe_memcmp.c",
"string/u_strerr.c",
"string/wcpcpy.c",
"string/wcpncpy.c",
"string/wcscasecmp.c",
"string/wcscasecmp_l.c",
"string/wcscat.c",
"string/wcschr.c",
"string/wcscmp.c",
"string/wcscoll.c",
"string/wcscoll_l.c",
"string/wcscpy.c",
"string/wcscspn.c",
"string/wcsdup.c",
"string/wcslcat.c",
"string/wcslcpy.c",
"string/wcslen.c",
"string/wcsncasecmp.c",
"string/wcsncasecmp_l.c",
"string/wcsncat.c",
"string/wcsncmp.c",
"string/wcsncpy.c",
"string/wcsnlen.c",
"string/wcspbrk.c",
"string/wcsrchr.c",
"string/wcsspn.c",
"string/wcsstr.c",
"string/wcstok.c",
"string/wcswidth.c",
"string/wcsxfrm.c",
"string/wcsxfrm_l.c",
"string/wcwidth.c",
"string/wmemchr.c",
"string/wmemcmp.c",
"string/wmemcpy.c",
"string/wmemmove.c",
"string/wmempcpy.c",
"string/wmemset.c",
"string/xpg_strerror_r.c",
}
+12 -1
View File
@@ -84,7 +84,18 @@ func loadProgramSize(path string) (*programSize, error) {
if section.Type != elf.SHT_PROGBITS && section.Type != elf.SHT_NOBITS {
continue
}
if section.Type == elf.SHT_NOBITS {
if section.Name == ".stack" {
// HACK: this works around a bug in ld.lld from LLVM 10. The linker
// marks sections with no input symbols (such as is the case for the
// .stack section) as SHT_PROGBITS instead of SHT_NOBITS. While it
// doesn't affect the generated binaries (.hex and .bin), it does
// affect the reported size.
// https://bugs.llvm.org/show_bug.cgi?id=45336
// https://reviews.llvm.org/D76981
// It has been merged in master, but it has not (yet) been
// backported to the LLVM 10 release branch.
sumBSS += section.Size
} else if section.Type == elf.SHT_NOBITS {
sumBSS += section.Size
} else if section.Flags&elf.SHF_EXECINSTR != 0 {
sumCode += section.Size
+54
View File
@@ -0,0 +1,54 @@
// +build byollvm
package builder
import (
"errors"
"unsafe"
)
/*
#cgo CXXFLAGS: -fno-rtti
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_clang_driver(int argc, char **argv);
bool tinygo_link_elf(int argc, char **argv);
bool tinygo_link_wasm(int argc, char **argv);
*/
import "C"
const hasBuiltinTools = true
// RunTool runs the given tool (such as clang).
//
// This version actually runs the tools because TinyGo was compiled while
// linking statically with LLVM (with the byollvm build tag).
func RunTool(tool string, args ...string) error {
args = append([]string{"tinygo:" + tool}, args...)
var cflag *C.char
buf := C.calloc(C.size_t(len(args)), C.size_t(unsafe.Sizeof(cflag)))
defer C.free(buf)
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(args):len(args)]
for i, flag := range args {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
var ok C.bool
switch tool {
case "clang":
ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf))
case "ld.lld":
ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf))
case "wasm-ld":
ok = C.tinygo_link_wasm(C.int(len(args)), (**C.char)(buf))
default:
return errors.New("unknown tool: " + tool)
}
if !ok {
return errors.New("failed to run tool: " + tool)
}
return nil
}
+15
View File
@@ -0,0 +1,15 @@
// +build !byollvm
package builder
import "errors"
const hasBuiltinTools = false
// RunTool runs the given tool (such as clang).
//
// This version doesn't actually run the tool: TinyGo has not been compiled by
// statically linking to LLVM.
func RunTool(tool string, args ...string) error {
return errors.New("cannot run tool: " + tool)
}
+59
View File
@@ -0,0 +1,59 @@
package builder
import (
"errors"
"os"
"os/exec"
"github.com/tinygo-org/tinygo/goenv"
)
// runCCompiler invokes a C compiler with the given arguments.
func runCCompiler(command string, flags ...string) error {
if hasBuiltinTools && command == "clang" {
// Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
return errors.New("could not locate Clang headers")
}
flags = append(flags, "-I"+headerPath)
cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// Running some other compiler. Maybe it has been defined in the
// commands map (unlikely).
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
// Alternatively, run the compiler directly.
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// link invokes a linker with the given name and flags.
func link(linker string, flags ...string) error {
if hasBuiltinTools && (linker == "ld.lld" || linker == "wasm-ld") {
// Run command with internal linker.
cmd := exec.Command(os.Args[0], append([]string{linker}, flags...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// Fall back to external command.
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
+30 -9
View File
@@ -11,26 +11,33 @@ import (
"bytes"
"encoding/binary"
"io/ioutil"
"strconv"
)
// convertELFFileToUF2File converts an ELF file to a UF2 file.
func convertELFFileToUF2File(infile, outfile string) error {
func convertELFFileToUF2File(infile, outfile string, uf2FamilyID string) error {
// Read the .text segment.
targetAddress, data, err := extractROM(infile)
if err != nil {
return err
}
output, _ := convertBinToUF2(data, uint32(targetAddress))
output, _, err := convertBinToUF2(data, uint32(targetAddress), uf2FamilyID)
if err != nil {
return err
}
return ioutil.WriteFile(outfile, output, 0644)
}
// convertBinToUF2 converts the binary bytes in input to UF2 formatted data.
func convertBinToUF2(input []byte, targetAddr uint32) ([]byte, int) {
func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byte, int, error) {
blocks := split(input, 256)
output := make([]byte, 0)
bl := newUF2Block(targetAddr)
bl, err := newUF2Block(targetAddr, uf2FamilyID)
if err != nil {
return nil, 0, err
}
bl.SetNumBlocks(len(blocks))
for i := 0; i < len(blocks); i++ {
@@ -41,7 +48,7 @@ func convertBinToUF2(input []byte, targetAddr uint32) ([]byte, int) {
bl.IncrementAddress(bl.payloadSize)
}
return output, len(blocks)
return output, len(blocks), nil
}
const (
@@ -65,18 +72,32 @@ type uf2Block struct {
}
// newUF2Block returns a new uf2Block struct that has been correctly populated
func newUF2Block(targetAddr uint32) *uf2Block {
func newUF2Block(targetAddr uint32, uf2FamilyID string) (*uf2Block, error) {
var flags uint32
var familyID uint32
if uf2FamilyID != "" {
flags |= flagFamilyIDPresent
v, err := strconv.ParseUint(uf2FamilyID, 0, 32)
if err != nil {
return nil, err
}
familyID = uint32(v)
}
return &uf2Block{magicStart0: uf2MagicStart0,
magicStart1: uf2MagicStart1,
magicEnd: uf2MagicEnd,
targetAddr: targetAddr,
flags: 0x0,
familyID: 0x0,
flags: flags,
familyID: familyID,
payloadSize: 256,
data: make([]byte, 476),
}
}, nil
}
const (
flagFamilyIDPresent = 0x00002000
)
// Bytes converts the uf2Block to a slice of bytes that can be written to file.
func (b *uf2Block) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, 512))
+44
View File
@@ -14,7 +14,9 @@ package cgo
import (
"fmt"
"go/ast"
"go/scanner"
"go/token"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -168,11 +170,28 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
enums: map[string]enumInfo{},
}
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
// Note that it is only disabled for memcpy (etc) calls made from Go, which
// have better alternatives anyway.
cflags = append(cflags, "-D_FORTIFY_SOURCE=0")
// Add a new location for the following file.
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
generatedTokenPos.SetLines([]int{0})
p.generatedPos = generatedTokenPos.Pos(0)
// Find the absolute path for this package.
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
if err != nil {
return nil, []error{
scanner.Error{
Pos: fset.Position(files[0].Pos()),
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
},
}
}
packagePath = filepath.Dir(packagePath)
// Construct a new in-memory AST for CGo declarations of this package.
unsafeImport := &ast.ImportSpec{
Path: &ast.BasicLit{
@@ -338,6 +357,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
continue
}
makePathsAbsolute(flags, packagePath)
cflags = append(cflags, flags...)
default:
startPos := strings.LastIndex(line[4:colon], name) + 4
@@ -395,6 +415,30 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
return p.generated, p.errors
}
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
// relative flags into absolute flags, if they are relative. This is necessary
// because the C compiler is usually not invoked from the package path.
func makePathsAbsolute(args []string, packagePath string) {
nextIsPath := false
for i, arg := range args {
if nextIsPath {
if !filepath.IsAbs(arg) {
args[i] = filepath.Join(packagePath, arg)
}
}
if arg == "-I" || arg == "-L" {
nextIsPath = true
continue
}
if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
path := arg[2:]
if !filepath.IsAbs(path) {
args[i] = arg[:2] + filepath.Join(packagePath, path)
}
}
}
}
// addFuncDecls adds the C function declarations found by libclang in the
// comment above the `import "C"` statement.
func (p *cgoPackage) addFuncDecls() {
+17 -1
View File
@@ -11,6 +11,7 @@ import (
"go/types"
"io/ioutil"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
@@ -19,6 +20,21 @@ import (
// Pass -update to go test to update the output of the test files.
var flagUpdate = flag.Bool("update", false, "Update images based on test output.")
// normalizeResult normalizes Go source code that comes out of tests across
// platforms and Go versions.
func normalizeResult(result string) string {
actual := strings.Replace(result, "\r\n", "\n", -1)
// Make sure all functions are wrapped, even those that would otherwise be
// single-line functions. This is necessary because Go 1.14 changed the way
// such functions are wrapped and it's important to have consistent test
// results.
re := regexp.MustCompile(`func \((.+)\)( .*?) +{ (.+) }`)
actual = re.ReplaceAllString(actual, "func ($1)$2 {\n\t$3\n}")
return actual
}
func TestCGo(t *testing.T) {
var cflags = []string{"--target=armv6m-none-eabi"}
@@ -74,7 +90,7 @@ func TestCGo(t *testing.T) {
if err != nil {
t.Errorf("could not write out CGo AST: %v", err)
}
actual := strings.Replace(string(buf.Bytes()), "\r\n", "\n", -1)
actual := normalizeResult(string(buf.Bytes()))
// Read the file with the expected output, to compare against.
outfile := filepath.Join("testdata", name+".out.go")
+1 -1
View File
@@ -15,7 +15,7 @@ import (
)
/*
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
#include <stdlib.h>
#include <stdint.h>
+7 -6
View File
@@ -1,13 +1,14 @@
// +build !byollvm
// +build !llvm9
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-9/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@9/include
#cgo freebsd CFLAGS: -I/usr/local/llvm90/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-9/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm90/lib -lclang
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
*/
import "C"
+14
View File
@@ -0,0 +1,14 @@
// +build !byollvm
// +build llvm9
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-9/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@9/include
#cgo freebsd CFLAGS: -I/usr/local/llvm9/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-9/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm9/lib -lclang
*/
import "C"
+1 -1
View File
@@ -3,7 +3,7 @@
// are slightly different from the ones defined in libclang.go, but they
// should be ABI compatible.
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
return clang_getTranslationUnitCursor(tu);
+4
View File
@@ -9,6 +9,9 @@ package main
#cgo CFLAGS: -DFOO
#cgo CFLAGS: -Iinclude
#include "foo.h"
#if defined(FOO)
#define BAR 3
#else
@@ -23,4 +26,5 @@ import "C"
var (
_ = C.BAR
_ = C.FOO_H
)
+1
View File
@@ -9,6 +9,7 @@ import "unsafe"
var _ unsafe.Pointer
const C.BAR = 3
const C.FOO_H = 1
type C.int16_t = int16
type C.int32_t = int32
+1
View File
@@ -0,0 +1 @@
#define FOO_H 1
+39 -13
View File
@@ -62,12 +62,18 @@ type C.union3_t = C.union_1
type C.union_nested_t = C.union_3
type C.unionarray_t = struct{ arr [10]C.uchar }
func (s *C.struct_4) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
func (s *C.struct_4) set_bitfield_a(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 }
func (s *C.struct_4) bitfield_a() C.uchar {
return s.__bitfield_1 & 0x1f
}
func (s *C.struct_4) set_bitfield_a(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0
}
func (s *C.struct_4) bitfield_b() C.uchar {
return s.__bitfield_1 >> 5 & 0x1
}
func (s *C.struct_4) set_bitfield_b(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 }
func (s *C.struct_4) set_bitfield_b(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5
}
func (s *C.struct_4) bitfield_c() C.uchar {
return s.__bitfield_1 >> 6
}
@@ -94,25 +100,45 @@ type C.struct_type1 struct {
}
type C.struct_type2 struct{ _type C.int }
func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_s() *C.short {
return (*C.short)(unsafe.Pointer(&union.$union))
}
type C.union_1 struct{ $union uint64 }
func (union *C.union_2) unionfield_area() *C.point2d_t { return (*C.point2d_t)(unsafe.Pointer(&union.$union)) }
func (union *C.union_2) unionfield_solid() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) }
func (union *C.union_2) unionfield_area() *C.point2d_t {
return (*C.point2d_t)(unsafe.Pointer(&union.$union))
}
func (union *C.union_2) unionfield_solid() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
}
type C.union_2 struct{ $union [3]uint32 }
func (union *C.union_3) unionfield_point() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) }
func (union *C.union_3) unionfield_array() *C.unionarray_t { return (*C.unionarray_t)(unsafe.Pointer(&union.$union)) }
func (union *C.union_3) unionfield_thing() *C.union3_t { return (*C.union3_t)(unsafe.Pointer(&union.$union)) }
func (union *C.union_3) unionfield_point() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
}
func (union *C.union_3) unionfield_array() *C.unionarray_t {
return (*C.unionarray_t)(unsafe.Pointer(&union.$union))
}
func (union *C.union_3) unionfield_thing() *C.union3_t {
return (*C.union3_t)(unsafe.Pointer(&union.$union))
}
type C.union_3 struct{ $union [2]uint64 }
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
func (union *C.union_union2d) unionfield_d() *[2]float64 { return (*[2]float64)(unsafe.Pointer(&union.$union)) }
func (union *C.union_union2d) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C.union_union2d) unionfield_d() *[2]float64 {
return (*[2]float64)(unsafe.Pointer(&union.$union))
}
type C.union_union2d struct{ $union [2]uint64 }
type C.enum_option C.int
+67 -10
View File
@@ -5,6 +5,7 @@ package compileopts
import (
"errors"
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"
@@ -21,6 +22,29 @@ type Config struct {
TestConfig TestConfig
}
// FuncValueImplementation is an enum for the particular implementations of Go
// func values.
type FuncValueImplementation int
// These constants describe the various possible implementations of Go func
// values.
const (
FuncValueNone FuncValueImplementation = iota
// A func value is implemented as a pair of pointers:
// {context, function pointer}
// where the context may be a pointer to a heap-allocated struct containing
// the free variables, or it may be undef if the function being pointed to
// doesn't need a context. The function pointer is a regular function
// pointer.
FuncValueDoubleword
// As funcValueDoubleword, but with the function pointer replaced by a
// unique ID per function signature. Function values are called by using a
// switch statement and choosing which function to call.
FuncValueSwitch
)
// Triple returns the LLVM target triple, like armv6m-none-eabi.
func (c *Config) Triple() string {
return c.Target.Triple
@@ -65,8 +89,14 @@ func (c *Config) BuildTags() []string {
return tags
}
// CgoEnabled returns true if (and only if) CGo is enabled. It is true by
// default and false if CGO_ENABLED is set to "0".
func (c *Config) CgoEnabled() bool {
return goenv.Get("CGO_ENABLED") == "1"
}
// GC returns the garbage collection strategy in use on this platform. Valid
// values are "none", "leaking", and "conservative".
// values are "none", "leaking", "extalloc", and "conservative".
func (c *Config) GC() string {
if c.Options.GC != "" {
return c.Options.GC
@@ -74,22 +104,29 @@ func (c *Config) GC() string {
if c.Target.GC != "" {
return c.Target.GC
}
return "conservative"
for _, tag := range c.Target.BuildTags {
if tag == "baremetal" || tag == "wasm" {
return "conservative"
}
}
return "extalloc"
}
// NeedsStackObjects returns true if the compiler should insert stack objects
// that can be traced by the garbage collector.
func (c *Config) NeedsStackObjects() bool {
if c.GC() != "conservative" {
switch c.GC() {
case "conservative", "extalloc":
for _, tag := range c.BuildTags() {
if tag == "baremetal" {
return false
}
}
return true
default:
return false
}
for _, tag := range c.BuildTags() {
if tag == "baremetal" {
return false
}
}
return true
}
// Scheduler returns the scheduler implementation. Valid values are "coroutines"
@@ -105,6 +142,21 @@ func (c *Config) Scheduler() string {
return "coroutines"
}
// FuncImplementation picks an appropriate func value implementation for the
// target.
func (c *Config) FuncImplementation() FuncValueImplementation {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
switch c.Scheduler() {
case "none", "coroutines":
return FuncValueSwitch
case "tasks":
return FuncValueDoubleword
default:
panic("unknown scheduler type")
}
}
// PanicStrategy returns the panic strategy selected for this target. Valid
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
// instruction).
@@ -119,6 +171,11 @@ func (c *Config) CFlags() []string {
for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.Replace(flag, "{root}", goenv.Get("TINYGOROOT"), -1))
}
if c.Target.Libc == "picolibc" {
root := goenv.Get("TINYGOROOT")
cflags = append(cflags, "--sysroot="+filepath.Join(root, "lib", "picolibc", "newlib", "libc"))
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
}
return cflags
}
+25 -1
View File
@@ -32,6 +32,7 @@ type TargetSpec struct {
Compiler string `json:"compiler"`
Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"`
CFlags []string `json:"cflags"`
LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"`
@@ -43,9 +44,11 @@ type TargetSpec struct {
FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"`
UF2FamilyID string `json:"uf2-family-id"`
OpenOCDInterface string `json:"openocd-interface"`
OpenOCDTarget string `json:"openocd-target"`
OpenOCDTransport string `json:"openocd-transport"`
JLinkDevice string `json:"jlink-device"`
}
// copyProperties copies all properties that are set in spec2 into itself.
@@ -82,6 +85,9 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
if spec2.RTLib != "" {
spec.RTLib = spec2.RTLib
}
if spec2.Libc != "" {
spec.Libc = spec2.Libc
}
spec.CFlags = append(spec.CFlags, spec2.CFlags...)
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
if spec2.LinkerScript != "" {
@@ -109,6 +115,9 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
if spec2.FlashFilename != "" {
spec.FlashFilename = spec2.FlashFilename
}
if spec2.UF2FamilyID != "" {
spec.UF2FamilyID = spec2.UF2FamilyID
}
if spec2.OpenOCDInterface != "" {
spec.OpenOCDInterface = spec2.OpenOCDInterface
}
@@ -118,6 +127,9 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
if spec2.OpenOCDTransport != "" {
spec.OpenOCDTransport = spec2.OpenOCDTransport
}
if spec2.JLinkDevice != "" {
spec.JLinkDevice = spec2.JLinkDevice
}
}
// load reads a target specification from the JSON in the given io.Reader. It
@@ -221,6 +233,14 @@ func LoadTarget(target string) (*TargetSpec, error) {
if len(tripleSplit) < 3 {
return nil, errors.New("expected a full LLVM target or a custom target in -target flag")
}
if tripleSplit[0] == "arm" {
// LLVM and Clang have a different idea of what "arm" means, so
// upgrade to a slightly more modern ARM. In fact, when you pass
// --target=arm--linux-gnueabihf to Clang, it will convert that
// internally to armv7-unknown-linux-gnueabihf. Changing the
// architecture to armv7 will keep things consistent.
tripleSplit[0] = "armv7"
}
goos := tripleSplit[2]
if strings.HasPrefix(goos, "darwin") {
goos = "darwin"
@@ -229,11 +249,12 @@ func LoadTarget(target string) (*TargetSpec, error) {
"i386": "386",
"x86_64": "amd64",
"aarch64": "arm64",
"armv7": "arm",
}[tripleSplit[0]]
if goarch == "" {
goarch = tripleSplit[0]
}
return defaultTarget(goos, goarch, target)
return defaultTarget(goos, goarch, strings.Join(tripleSplit, "-"))
}
}
@@ -247,6 +268,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
BuildTags: []string{goos, goarch},
Compiler: "clang",
Linker: "cc",
CFlags: []string{"--target=" + triple},
GDB: "gdb",
PortReset: "false",
FlashMethod: "native",
@@ -259,11 +281,13 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs.
if goarch == "arm" && goos == "linux" {
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
spec.Linker = "arm-linux-gnueabihf-gcc"
spec.GDB = "arm-linux-gnueabihf-gdb"
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
}
if goarch == "arm64" && goos == "linux" {
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu")
spec.Linker = "aarch64-linux-gnu-gcc"
spec.GDB = "aarch64-linux-gnu-gdb"
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
+143 -80
View File
@@ -4,16 +4,19 @@ package compiler
// required by the Go programming language.
import (
"fmt"
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// emitLookupBoundsCheck emits a bounds check before doing a lookup into a
// createLookupBoundsCheck emits a bounds check before doing a lookup into a
// slice. This is required by the Go language spec: an index out of bounds must
// cause a panic.
func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
if frame.fn.IsNoBounds() {
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
if b.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -23,41 +26,29 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
// correctly extend that type.
if indexType.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 {
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
index = b.CreateZExt(index, arrayLen.Type(), "")
} else {
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
index = b.CreateSExt(index, arrayLen.Type(), "")
}
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
// The index is bigger than the array length type, so extend it.
arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "")
arrayLen = b.CreateZExt(arrayLen, index.Type(), "")
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now do the bounds check: index >= arrayLen
outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("lookupPanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "")
b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic")
}
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
// createSliceBoundsCheck emits a bounds check before a slicing operation to make
// sure it is within bounds.
//
// This function is both used for slicing a slice (low and high have their
// normal meaning) and for creating a new slice, where 'capacity' means the
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases.
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if frame.fn.IsNoBounds() {
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if b.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -75,92 +66,164 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
capacityType = max.Type()
}
if capacityType != capacity.Type() {
capacity = c.builder.CreateZExt(capacity, capacityType, "")
capacity = b.CreateZExt(capacity, capacityType, "")
}
// Extend low and high to be the same size as capacity.
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if lowType.Info()&types.IsUnsigned != 0 {
low = c.builder.CreateZExt(low, capacityType, "")
low = b.CreateZExt(low, capacityType, "")
} else {
low = c.builder.CreateSExt(low, capacityType, "")
low = b.CreateSExt(low, capacityType, "")
}
}
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if highType.Info()&types.IsUnsigned != 0 {
high = c.builder.CreateZExt(high, capacityType, "")
high = b.CreateZExt(high, capacityType, "")
} else {
high = c.builder.CreateSExt(high, capacityType, "")
high = b.CreateSExt(high, capacityType, "")
}
}
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if maxType.Info()&types.IsUnsigned != 0 {
max = c.builder.CreateZExt(max, capacityType, "")
max = b.CreateZExt(max, capacityType, "")
} else {
max = c.builder.CreateSExt(max, capacityType, "")
max = b.CreateSExt(max, capacityType, "")
}
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now do the bounds check: low > high || high > capacity
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("slicePanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := b.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
}
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
// has no effect in well-behaved programs, but makes sure no uncaught nil
// createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake.
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
if b.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
}
// Check whether the bufSize parameter must be cast to a wider integer for
// comparison.
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
if bufSizeType.Info()&types.IsUnsigned != 0 {
// Unsigned, so zero-extend to uint type.
bufSizeType = types.Typ[types.Uint]
bufSize = b.CreateZExt(bufSize, b.intType, "")
} else {
// Signed, so sign-extend to int type.
bufSizeType = types.Typ[types.Int]
bufSize = b.CreateSExt(bufSize, b.intType, "")
}
}
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in an
// uintptr if uintptrs were signed.
maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false))
if elementSize > maxBufSize.ZExtValue() {
b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize))
return
}
// Avoid divide-by-zero.
if elementSize == 0 {
elementSize = 1
}
// Make the maxBufSize actually the maximum allowed value (in number of
// elements in the channel buffer).
maxBufSize = llvm.ConstUDiv(maxBufSize, llvm.ConstInt(b.uintptrType, elementSize, false))
// Make sure maxBufSize has the same type as bufSize.
if maxBufSize.Type() != bufSize.Type() {
maxBufSize = llvm.ConstZExt(maxBufSize, bufSize.Type())
}
// Do the check for a too large (or negative) buffer size.
bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "")
b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic")
}
// createNilCheck checks whether the given pointer is nil, and panics if it is.
// It has no effect in well-behaved programs, but makes sure no uncaught nil
// pointer dereferences exist in valid Go code.
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix string) {
// Check whether we need to emit this check at all.
if !ptr.IsAGlobalValue().IsNil() {
return
}
// Check whether this is a nil pointer.
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
switch inst := inst.(type) {
case *ssa.IndexAddr:
// This pointer is the result of an index operation into a slice or
// array. Such slices/arrays are already bounds checked so the pointer
// must be a valid (non-nil) pointer. No nil checking is necessary.
return
case *ssa.Convert:
// This is a pointer that comes from a conversion from unsafe.Pointer.
// Don't do nil checking because this is unsafe code and the code should
// know what it is doing.
// Note: all *ssa.Convert instructions that result in a pointer must
// come from unsafe.Pointer. Testing here for unsafe.Pointer to be sure.
if inst.X.Type() == types.Typ[types.UnsafePointer] {
return
}
}
// Compare against nil.
var isnil llvm.Value
if ptr.Type().PointerAddressSpace() == 0 {
// Do the nil check using the isnil builtin, which marks the parameter
// as nocapture.
// The reason it has to go through a builtin, is that a regular icmp
// instruction may capture the pointer in LLVM semantics, see
// https://reviews.llvm.org/D60047 for details. Pointer capturing
// unfortunately breaks escape analysis, so we use this trick to let the
// functionattr pass know that this pointer doesn't really escape.
ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "")
isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "")
} else {
// Do the nil check using a regular icmp. This can happen with function
// pointers on AVR, which don't benefit from escape analysis anyway.
nilptr := llvm.ConstPointerNull(ptr.Type())
isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
}
c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
// We previously used a hack to make sure this wouldn't break escape
// analysis, but this is not necessary anymore since
// https://reviews.llvm.org/D60047 has been merged.
nilptr := llvm.ConstPointerNull(ptr.Type())
isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("nilPanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
// Emit the nil check in IR.
b.createRuntimeAssert(isnil, blockPrefix, "nilPanic")
}
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
// This function assumes that the shift value is signed.
func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
if b.fn.IsNoBounds() {
// Function disabled bounds checking - skip shift check.
return
}
// isNegative = shift < 0
isNegative := b.CreateICmp(llvm.IntSLT, shift, llvm.ConstInt(shift.Type(), 0, false), "")
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
}
// createRuntimeAssert is a common function to create a new branch on an assert
// bool, calling an assert func if the assert value is true (1).
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
// Check whether we can resolve this check at compile time.
if !assert.IsAConstantInt().IsNil() {
val := assert.ZExtValue()
if val == 0 {
// Everything is constant so the check does not have to be emitted
// in IR. This avoids emitting some redundant IR.
return
}
}
faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block.
b.CreateCondBr(assert, faultBlock, nextBlock)
// Fail: the assert triggered so panic.
b.SetInsertPointAtEnd(faultBlock)
b.createRuntimeCall(assertFunc, nil, "")
b.CreateUnreachable()
// Ok: assert didn't trigger so continue normally.
b.SetInsertPointAtEnd(nextBlock)
}
+167 -67
View File
@@ -1,8 +1,9 @@
package compiler
import (
"fmt"
"golang.org/x/tools/go/ssa"
"go/types"
"strconv"
"tinygo.org/x/go-llvm"
)
@@ -11,62 +12,80 @@ import (
// The maximum number of arguments that can be expanded from a single struct. If
// a struct contains more fields, it is passed as a struct without expanding.
const MaxFieldsPerParam = 3
const maxFieldsPerParam = 3
// Shortcut: create a call to runtime.<fnName> with the given arguments.
func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
runtimePkg := c.ir.Program.ImportedPackage("runtime")
member := runtimePkg.Members[fnName]
if member == nil {
panic("trying to call runtime." + fnName)
}
fn := c.ir.GetFunction(member.(*ssa.Function))
if fn.LLVMFn.IsNil() {
panic(fmt.Errorf("function %s does not appear in LLVM IR", fnName))
}
if !fn.IsExported() {
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
}
return c.createCall(fn.LLVMFn, args, name)
// paramInfo contains some information collected about a function parameter,
// useful while declaring or defining a function.
type paramInfo struct {
llvmType llvm.Type
name string // name, possibly with suffixes for e.g. struct fields
flags paramFlags
}
// Create a call to the given function with the arguments possibly expanded.
func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
// paramFlags identifies parameter attributes for flags. Most importantly, it
// determines which parameters are dereferenceable_or_null and which aren't.
type paramFlags uint8
const (
// Parameter may have the deferenceable_or_null attribute. This attribute
// cannot be applied to unsafe.Pointer and to the data pointer of slices.
paramIsDeferenceableOrNull = 1 << iota
)
// createCall creates a new call to runtime.<fnName> with the given arguments.
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
fullName := "runtime." + fnName
fn := b.mod.NamedFunction(fullName)
if fn.IsNil() {
panic("trying to call non-existent function: " + fullName)
}
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
return b.createCall(fn, args, name)
}
// createCall creates a call to the given function with the arguments possibly
// expanded.
func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
expanded := make([]llvm.Value, 0, len(args))
for _, arg := range args {
fragments := c.expandFormalParam(arg)
fragments := b.expandFormalParam(arg)
expanded = append(expanded, fragments...)
}
return c.builder.CreateCall(fn, expanded, name)
return b.CreateCall(fn, expanded, name)
}
// Expand an argument type to a list that can be used in a function call
// parameter list.
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := c.flattenAggregateType(t)
if len(fields) <= MaxFieldsPerParam {
return fields
fieldInfos := flattenAggregateType(t, name, goType)
if len(fieldInfos) <= maxFieldsPerParam {
return fieldInfos
} else {
// failed to lower
return []llvm.Type{t}
}
default:
// TODO: split small arrays
return []llvm.Type{t}
}
// TODO: split small arrays
return []paramInfo{
{
llvmType: t,
name: name,
flags: getTypeFlags(goType),
},
}
}
// Expand an argument type to a list of offsets from the start of the object.
// Used together with expandFormalParam to get the offset of each value from the
// start of the non-expanded value.
func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
// expandFormalParamOffsets returns a list of offsets from the start of an
// object of type t after it would have been split up by expandFormalParam. This
// is useful for debug information, where it is necessary to know the offset
// from the start of the combined object.
func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := c.flattenAggregateTypeOffsets(t)
if len(fields) <= MaxFieldsPerParam {
fields := b.flattenAggregateTypeOffsets(t)
if len(fields) <= maxFieldsPerParam {
return fields
} else {
// failed to lower
@@ -78,14 +97,17 @@ func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
}
}
// Equivalent of expandFormalParamType for parameter values.
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
// expandFormalParam splits a formal param value into pieces, so it can be
// passed directly as part of a function call. For example, it splits up small
// structs into individual fields. It is the equivalent of expandFormalParamType
// for parameter values.
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() {
case llvm.StructTypeKind:
fieldTypes := c.flattenAggregateType(v.Type())
if len(fieldTypes) <= MaxFieldsPerParam {
fields := c.flattenAggregate(v)
if len(fields) != len(fieldTypes) {
fieldInfos := flattenAggregateType(v.Type(), "", nil)
if len(fieldInfos) <= maxFieldsPerParam {
fields := b.flattenAggregate(v)
if len(fields) != len(fieldInfos) {
panic("type and value param lowering don't match")
}
return fields
@@ -101,24 +123,98 @@ func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
// Try to flatten a struct type to a list of types. Returns a 1-element slice
// with the passed in type if this is not possible.
func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type {
func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
typeFlags := getTypeFlags(goType)
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := make([]llvm.Type, 0, t.StructElementTypesCount())
for _, subfield := range t.StructElementTypes() {
subfields := c.flattenAggregateType(subfield)
fields = append(fields, subfields...)
paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
for i, subfield := range t.StructElementTypes() {
suffix := strconv.Itoa(i)
if goType != nil {
// Try to come up with a good suffix for this struct field,
// depending on which Go type it's based on.
switch goType := goType.Underlying().(type) {
case *types.Interface:
suffix = []string{"typecode", "value"}[i]
case *types.Slice:
suffix = []string{"data", "len", "cap"}[i]
case *types.Struct:
suffix = goType.Field(i).Name()
case *types.Basic:
switch goType.Kind() {
case types.Complex64, types.Complex128:
suffix = []string{"r", "i"}[i]
case types.String:
suffix = []string{"data", "len"}[i]
}
case *types.Signature:
suffix = []string{"context", "funcptr"}[i]
}
}
subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
for i := range subInfos {
subInfos[i].flags |= typeFlags
}
paramInfos = append(paramInfos, subInfos...)
}
return fields
return paramInfos
default:
return []llvm.Type{t}
return []paramInfo{
{
llvmType: t,
name: name,
flags: typeFlags,
},
}
}
}
// Return the offsets from the start of the object if this object type were
// flattened like in flattenAggregate. Used together with flattenAggregate to
// know the start indices of each value in the non-flattened object.
func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
// getTypeFlags returns the type flags for a given type. It will not recurse
// into sub-types (such as in structs).
func getTypeFlags(t types.Type) paramFlags {
if t == nil {
return 0
}
switch t.Underlying().(type) {
case *types.Pointer:
// Pointers in Go must either point to an object or be nil.
return paramIsDeferenceableOrNull
case *types.Chan, *types.Map:
// Channels and maps are implemented as pointers pointing to some
// object, and follow the same rules as *types.Pointer.
return paramIsDeferenceableOrNull
default:
return 0
}
}
// extractSubfield extracts a field from a struct, or returns null if this is
// not a struct and thus no subfield can be obtained.
func extractSubfield(t types.Type, field int) types.Type {
if t == nil {
return nil
}
switch t := t.Underlying().(type) {
case *types.Struct:
return t.Field(field).Type()
case *types.Interface, *types.Slice, *types.Basic, *types.Signature:
// These Go types are (sometimes) implemented as LLVM structs but can't
// really be split further up in Go (with the possible exception of
// complex numbers).
return nil
default:
// This should be unreachable.
panic("cannot split subfield: " + t.String())
}
}
// flattenAggregateTypeOffset returns the offsets from the start of an object of
// type t if this object were flattened like in flattenAggregate. Used together
// with flattenAggregate to know the start indices of each value in the
// non-flattened object.
//
// Note: this is an implementation detail, use expandFormalParamOffsets instead.
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := make([]uint64, 0, t.StructElementTypesCount())
@@ -136,15 +232,15 @@ func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
}
}
// Break down a struct into its elementary types for argument passing. The value
// equivalent of flattenAggregateType
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
// flattenAggregate breaks down a struct into its elementary values for argument
// passing. It is the value equivalent of flattenAggregateType
func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() {
case llvm.StructTypeKind:
fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
for i := range v.Type().StructElementTypes() {
subfield := c.builder.CreateExtractValue(v, i, "")
subfields := c.flattenAggregate(subfield)
subfield := b.CreateExtractValue(v, i, "")
subfields := b.flattenAggregate(subfield)
fields = append(fields, subfields...)
}
return fields
@@ -153,25 +249,29 @@ func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
}
}
// Collapse a list of fields into its original value.
func (c *Compiler) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
param, remaining := c.collapseFormalParamInternal(t, fields)
// collapseFormalParam combines an aggregate object back into the original
// value. This is used to join multiple LLVM parameters into a single Go value
// in the function entry block.
func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
param, remaining := b.collapseFormalParamInternal(t, fields)
if len(remaining) != 0 {
panic("failed to expand back all fields")
}
return param
}
// Returns (value, remainingFields). Used by collapseFormalParam.
func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
// collapseFormalParamInternal is an implementation detail of
// collapseFormalParam: it works by recursing until there are no fields left.
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
switch t.TypeKind() {
case llvm.StructTypeKind:
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
flattened := flattenAggregateType(t, "", nil)
if len(flattened) <= maxFieldsPerParam {
value := llvm.ConstNull(t)
for i, subtyp := range t.StructElementTypes() {
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
fields = remaining
value = c.builder.CreateInsertValue(value, structField, i, "")
value = b.CreateInsertValue(value, structField, i, "")
}
return value, fields
} else {
+91 -85
View File
@@ -11,73 +11,79 @@ import (
"tinygo.org/x/go-llvm"
)
func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value {
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false)
bufSize := c.getValue(frame, expr.Size)
return c.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().(*types.Chan).Elem()))
elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
bufSize := b.getValue(expr.Size)
b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
bufSize = b.CreateZExt(bufSize, b.uintptrType, "")
} else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
bufSize = b.CreateTrunc(bufSize, b.uintptrType, "")
}
return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
}
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
// channel send operation during goroutine lowering.
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
ch := c.getValue(frame, instr.Chan)
chanValue := c.getValue(frame, instr.X)
// createChanSend emits a pseudo chan send operation. It is lowered to the
// actual channel send operation during goroutine lowering.
func (b *builder) createChanSend(instr *ssa.Send) {
ch := b.getValue(instr.Chan)
chanValue := b.getValue(instr.X)
// store value-to-send
valueType := c.getLLVMType(instr.X.Type())
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
c.builder.CreateStore(chanValue, valueAlloca)
valueType := b.getLLVMType(instr.X.Type())
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
// Do the send.
c.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
// End the lifetime of the alloca.
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
// actual channel receive operation during goroutine lowering.
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
ch := c.getValue(frame, unop.X)
func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
valueType := b.getLLVMType(unop.X.Type().(*types.Chan).Elem())
ch := b.getValue(unop.X)
// Allocate memory to receive into.
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
// Do the receive.
commaOk := c.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received")
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
received := b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, received, 0, "")
tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
return tuple
} else {
return received
}
}
// emitChanClose closes the given channel.
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
ch := c.getValue(frame, param)
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
// createChanClose closes the given channel.
func (b *builder) createChanClose(param ssa.Value) {
ch := b.getValue(param)
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
}
// emitSelect emits all IR necessary for a select statements. That's a
// createSelect emits all IR necessary for a select statements. That's a
// non-trivial amount of code because select is very complex to implement.
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := c.getLLVMType(expr.Type())
llvmType := b.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
c.createRuntimeCall("deadlock", nil, "")
b.createRuntimeCall("deadlock", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:
@@ -85,7 +91,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
// default:
// }
retval := llvm.Undef(llvmType)
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "")
return retval // {-1, false}
}
}
@@ -103,30 +109,30 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := c.getValue(frame, state.Chan)
ch := b.getValue(state.Chan)
selectState := llvm.ConstNull(chanSelectStateType)
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
selectState = b.CreateInsertValue(selectState, ch, 0, "")
switch state.Dir {
case types.RecvOnly:
// Make sure the receive buffer is big enough and has the correct alignment.
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
llvmType := b.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
recvbufSize = size
}
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align
}
hasReceives = true
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
sendValue := c.getValue(frame, state.Send)
alloca := llvmutil.CreateEntryBlockAlloca(c.builder, sendValue.Type(), "select.send.value")
c.builder.CreateStore(sendValue, alloca)
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
sendValue := b.getValue(state.Send)
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
b.CreateStore(sendValue, alloca)
ptr := b.CreateBitCast(alloca, b.i8ptrType, "")
selectState = b.CreateInsertValue(selectState, ptr, 1, "")
default:
panic("unreachable")
}
@@ -134,74 +140,74 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
}
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(c.i8ptrType)
recvbuf := llvm.Undef(b.i8ptrType)
if hasReceives {
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := c.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
recvbuf = b.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.recvbuf")
}
// Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
statesAlloca, statesI8, statesSize := c.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
for i, state := range selectStates {
// Set each slice element to the appropriate channel.
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
gep := b.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
}, "")
c.builder.CreateStore(state, gep)
b.CreateStore(state, gep)
}
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
statesPtr := b.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.states")
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
// Do the select in the runtime.
var results llvm.Value
if expr.Blocking {
// Stack-allocate operation structures.
// If these were simply created as a slice, they would heap-allocate.
chBlockAllocaType := llvm.ArrayType(c.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockAllocaPtr, chBlockSize := c.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := c.builder.CreateGEP(chBlockAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockAllocaPtr, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := b.CreateGEP(chBlockAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.block")
results = c.createRuntimeCall("chanSelect", []llvm.Value{
results = b.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
}, "select.result")
// Terminate the lifetime of the operation structures.
c.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
} else {
results = c.createRuntimeCall("tryChanSelect", []llvm.Value{
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
}, "select.result")
}
// Terminate the lifetime of the states alloca.
c.emitLifetimeEnd(statesI8, statesSize)
b.emitLifetimeEnd(statesI8, statesSize)
// The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract
// instruction will treat a *ssa.Select specially and load it there inline.
// Store the receive alloca in a sidetable until we hit this extract
// instruction.
if frame.selectRecvBuf == nil {
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
if b.selectRecvBuf == nil {
b.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
}
frame.selectRecvBuf[expr] = recvbuf
b.selectRecvBuf[expr] = recvbuf
return results
}
@@ -210,28 +216,28 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
// when extracting a value from a select statement (*ssa.Select). Because
// *ssa.Select cannot load all values in advance, it does this later in the
// *ssa.Extract expression.
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
if expr.Index == 0 {
// index
value := c.getValue(frame, expr.Tuple)
index := c.builder.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
index = c.builder.CreateSExt(index, c.intType, "")
value := b.getValue(expr.Tuple)
index := b.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() {
index = b.CreateSExt(index, b.intType, "")
}
return index
} else if expr.Index == 1 {
// comma-ok
value := c.getValue(frame, expr.Tuple)
return c.builder.CreateExtractValue(value, expr.Index, "")
value := b.getValue(expr.Tuple)
return b.CreateExtractValue(value, expr.Index, "")
} else {
// Select statements are (index, ok, ...) where ... is a number of
// received values, depending on how many receive statements there
// are. They are all combined into one alloca (because only one
// receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it.
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
return c.builder.CreateLoad(ptr, "")
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0)
ptr := b.CreateBitCast(recvbuf, typ, "")
return b.CreateLoad(ptr, "")
}
}
+988 -1076
View File
File diff suppressed because it is too large Load Diff
+120 -112
View File
@@ -23,16 +23,16 @@ import (
// deferInitFunc sets up this function for future deferred calls. It must be
// called from within the entry block when this function contains deferred
// calls.
func (c *Compiler) deferInitFunc(frame *Frame) {
func (b *builder) deferInitFunc() {
// Some setup.
frame.deferFuncs = make(map[*ir.Function]int)
frame.deferInvokeFuncs = make(map[string]int)
frame.deferClosureFuncs = make(map[*ir.Function]int)
b.deferFuncs = make(map[*ir.Function]int)
b.deferInvokeFuncs = make(map[string]int)
b.deferClosureFuncs = make(map[*ir.Function]int)
// Create defer list pointer.
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
b.deferPtr = b.CreateAlloca(deferType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr)
}
// isInLoop checks if there is a path from a basic block to itself.
@@ -69,53 +69,54 @@ func isInLoop(start *ssa.BasicBlock) bool {
return false
}
// emitDefer emits a single defer instruction, to be run when this function
// createDefer emits a single defer instruction, to be run when this function
// returns.
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
func (b *builder) createDefer(instr *ssa.Defer) {
// The pointer to the previous defer struct, which we will replace to
// make a linked list.
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
next := b.CreateLoad(b.deferPtr, "defer.next")
var values []llvm.Value
valueTypes := []llvm.Type{c.uintptrType, next.Type()}
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
if instr.Call.IsInvoke() {
// Method call on an interface.
// Get callback type number.
methodName := instr.Call.Method.FullName()
if _, ok := frame.deferInvokeFuncs[methodName]; !ok {
frame.deferInvokeFuncs[methodName] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, &instr.Call)
if _, ok := b.deferInvokeFuncs[methodName]; !ok {
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
}
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferInvokeFuncs[methodName]), false)
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by the call parameters).
itf := c.getValue(frame, instr.Call.Value) // interface
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, receiverValue}
valueTypes = append(valueTypes, c.i8ptrType)
itf := b.getValue(instr.Call.Value) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, typecode, receiverValue}
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
for _, arg := range instr.Call.Args {
val := c.getValue(frame, arg)
val := b.getValue(arg)
values = append(values, val)
valueTypes = append(valueTypes, val.Type())
}
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call.
fn := c.ir.GetFunction(callee)
fn := b.ir.GetFunction(callee)
if _, ok := frame.deferFuncs[fn]; !ok {
frame.deferFuncs[fn] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, fn)
if _, ok := b.deferFuncs[fn]; !ok {
b.deferFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, fn)
}
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferFuncs[fn]), false)
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := c.getValue(frame, param)
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -127,23 +128,23 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
// pointer.
// TODO: ignore this closure entirely and put pointers to the free
// variables directly in the defer struct, avoiding a memory allocation.
closure := c.getValue(frame, instr.Call.Value)
context := c.builder.CreateExtractValue(closure, 0, "")
closure := b.getValue(instr.Call.Value)
context := b.CreateExtractValue(closure, 0, "")
// Get the callback number.
fn := c.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
if _, ok := frame.deferClosureFuncs[fn]; !ok {
frame.deferClosureFuncs[fn] = len(frame.allDeferFuncs)
frame.allDeferFuncs = append(frame.allDeferFuncs, makeClosure)
fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
if _, ok := b.deferClosureFuncs[fn]; !ok {
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
}
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferClosureFuncs[fn]), false)
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferClosureFuncs[fn]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the
// context pointer).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := c.getValue(frame, param)
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -151,41 +152,41 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
valueTypes = append(valueTypes, context.Type())
} else {
c.addError(instr.Pos(), "todo: defer on uncommon function call type")
b.addError(instr.Pos(), "todo: defer on uncommon function call type")
return
}
// Make a struct out of the collected values to put in the defer frame.
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFrame := llvm.ConstNull(deferFrameType)
for i, value := range values {
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
deferFrame = b.CreateInsertValue(deferFrame, value, i, "")
}
// Put this struct in an allocation.
var alloca llvm.Value
if !isInLoop(instr.Block()) {
// This can safely use a stack allocation.
alloca = llvmutil.CreateEntryBlockAlloca(c.builder, deferFrameType, "defer.alloca")
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferFrameType, "defer.alloca")
} else {
// This may be hit a variable number of times, so use a heap allocation.
size := c.targetData.TypeAllocSize(deferFrameType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
allocCall := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
alloca = c.builder.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
size := b.targetData.TypeAllocSize(deferFrameType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
}
if c.NeedsStackObjects() {
c.trackPointer(alloca)
if b.NeedsStackObjects() {
b.trackPointer(alloca)
}
c.builder.CreateStore(deferFrame, alloca)
b.CreateStore(deferFrame, alloca)
// Push it on top of the linked list by replacing deferPtr.
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
c.builder.CreateStore(allocaCast, frame.deferPtr)
allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
b.CreateStore(allocaCast, b.deferPtr)
}
// emitRunDefers emits code to run all deferred functions.
func (c *Compiler) emitRunDefers(frame *Frame) {
// createRunDefers emits code to run all deferred functions.
func (b *builder) createRunDefers() {
// Add a loop like the following:
// for stack != nil {
// _stack := stack
@@ -202,44 +203,44 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// }
// Create loop.
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
c.builder.CreateBr(loophead)
loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead")
loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop")
unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default")
end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end")
b.CreateBr(loophead)
// Create loop head:
// for stack != nil {
c.builder.SetInsertPointAtEnd(loophead)
deferData := c.builder.CreateLoad(frame.deferPtr, "")
stackIsNil := c.builder.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
c.builder.CreateCondBr(stackIsNil, end, loop)
b.SetInsertPointAtEnd(loophead)
deferData := b.CreateLoad(b.deferPtr, "")
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
b.CreateCondBr(stackIsNil, end, loop)
// Create loop body:
// _stack := stack
// stack = stack.next
// switch stack.callback {
c.builder.SetInsertPointAtEnd(loop)
nextStackGEP := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
b.SetInsertPointAtEnd(loop)
nextStackGEP := b.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
}, "stack.next.gep")
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
c.builder.CreateStore(nextStack, frame.deferPtr)
gep := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
nextStack := b.CreateLoad(nextStackGEP, "stack.next")
b.CreateStore(nextStack, b.deferPtr)
gep := b.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field
}, "callback.gep")
callback := c.builder.CreateLoad(gep, "callback")
sw := c.builder.CreateSwitch(callback, unreachable, len(frame.allDeferFuncs))
callback := b.CreateLoad(gep, "callback")
sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs))
for i, callback := range frame.allDeferFuncs {
for i, callback := range b.allDeferFuncs {
// Create switch case, for example:
// case 0:
// // run first deferred call
block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
c.builder.SetInsertPointAtEnd(block)
block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
case *ssa.CallCommon:
// Call on an interface value.
@@ -248,105 +249,112 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
}
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType}
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0), b.uintptrType, b.i8ptrType}
for _, arg := range callback.Args {
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
}
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct (including receiver).
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Isolate the typecode.
typecode, forwardParams := forwardParams[0], forwardParams[1:]
// Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
fnPtr, _ := c.getInvokeCall(frame, callback)
c.createCall(fnPtr, forwardParams, "")
fnPtr := b.getInvokePtr(callback, typecode)
b.createCall(fnPtr, forwardParams, "")
case *ir.Function:
// Direct call.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
for _, param := range callback.Params {
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
}
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range callback.Params {
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Plain TinyGo functions add some extra parameters to implement async functionality and function recievers.
// These parameters should not be supplied when calling into an external C/ASM function.
if !callback.IsExported() {
// Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
}
// Call real function.
c.createCall(callback.LLVMFn, forwardParams, "")
b.createCall(callback.LLVMFn, forwardParams, "")
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
fn := b.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
}
valueTypes = append(valueTypes, c.i8ptrType) // closure
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
valueTypes = append(valueTypes, b.i8ptrType) // closure
deferFrameType := b.ctx.StructType(valueTypes, false)
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := c.builder.CreateLoad(gep, "param")
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
// Call deferred function.
c.createCall(fn.LLVMFn, forwardParams, "")
b.createCall(fn.LLVMFn, forwardParams, "")
default:
panic("unknown deferred function type")
}
// Branch back to the start of the loop.
c.builder.CreateBr(loophead)
b.CreateBr(loophead)
}
// Create default unreachable block:
// default:
// unreachable
// }
c.builder.SetInsertPointAtEnd(unreachable)
c.builder.CreateUnreachable()
b.SetInsertPointAtEnd(unreachable)
b.CreateUnreachable()
// End of loop.
c.builder.SetInsertPointAtEnd(end)
b.SetInsertPointAtEnd(end)
}
+5 -2
View File
@@ -1,5 +1,7 @@
package compiler
// This file contains some utility functions related to error handling.
import (
"go/scanner"
"go/token"
@@ -9,7 +11,8 @@ import (
"tinygo.org/x/go-llvm"
)
func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
// makeError makes it easy to create an error from a token.Pos with a message.
func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
return types.Error{
Fset: c.ir.Program.Fset,
Pos: pos,
@@ -17,7 +20,7 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
}
}
func (c *Compiler) addError(pos token.Pos, msg string) {
func (c *compilerContext) addError(pos token.Pos, msg string) {
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
}
+41 -64
View File
@@ -6,53 +6,26 @@ package compiler
import (
"go/types"
"github.com/tinygo-org/tinygo/compileopts"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
type funcValueImplementation int
const (
funcValueNone funcValueImplementation = iota
// A func value is implemented as a pair of pointers:
// {context, function pointer}
// where the context may be a pointer to a heap-allocated struct containing
// the free variables, or it may be undef if the function being pointed to
// doesn't need a context. The function pointer is a regular function
// pointer.
funcValueDoubleword
// As funcValueDoubleword, but with the function pointer replaced by a
// unique ID per function signature. Function values are called by using a
// switch statement and choosing which function to call.
funcValueSwitch
)
// funcImplementation picks an appropriate func value implementation for the
// target.
func (c *Compiler) funcImplementation() funcValueImplementation {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
switch c.Scheduler() {
case "coroutines":
return funcValueSwitch
case "tasks":
return funcValueDoubleword
default:
panic("unknown scheduler type")
}
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
return b.compilerContext.createFuncValue(b.Builder, funcPtr, context, sig)
}
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
var funcValueScalar llvm.Value
switch c.funcImplementation() {
case funcValueDoubleword:
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
case funcValueSwitch:
case compileopts.FuncValueSwitch:
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
@@ -73,35 +46,35 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
}
funcValueType := c.getFuncType(sig)
funcValue := llvm.Undef(funcValueType)
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
funcValue = builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
return funcValue
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation.
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
return c.builder.CreateExtractValue(funcValue, 1, "")
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
return b.CreateExtractValue(funcValue, 1, "")
}
// extractFuncContext extracts the context pointer from this function value. It
// is a cheap operation.
func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
return c.builder.CreateExtractValue(funcValue, 0, "")
func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
return b.CreateExtractValue(funcValue, 0, "")
}
// decodeFuncValue extracts the context and the function pointer from this func
// value. This may be an expensive operation.
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = c.builder.CreateExtractValue(funcValue, 0, "")
switch c.funcImplementation() {
case funcValueDoubleword:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch:
llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getTypeCode(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = b.CreateExtractValue(funcValue, 0, "")
switch b.FuncImplementation() {
case compileopts.FuncValueDoubleword:
funcPtr = b.CreateExtractValue(funcValue, 1, "")
case compileopts.FuncValueSwitch:
llvmSig := b.getRawFuncType(sig)
sigGlobal := b.getTypeCode(sig)
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
default:
panic("unimplemented func value variant")
}
@@ -109,12 +82,12 @@ func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (
}
// getFuncType returns the type of a func value given a signature.
func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
switch c.funcImplementation() {
case funcValueDoubleword:
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case funcValueSwitch:
case compileopts.FuncValueSwitch:
return c.getLLVMRuntimeType("funcValue")
default:
panic("unimplemented func value variant")
@@ -122,7 +95,7 @@ func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
}
// getRawFuncType returns a LLVM function pointer type for a given signature.
func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// Get the return type.
var returnType llvm.Type
switch typ.Results().Len() {
@@ -152,11 +125,15 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
// The receiver is not an interface, but a i8* type.
recv = c.i8ptrType
}
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
for _, info := range expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
}
for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(typ.Params().At(i).Type())
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
for _, info := range expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
}
// All functions take these parameters at the end.
paramTypes = append(paramTypes, c.i8ptrType) // context
@@ -168,24 +145,24 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
// parseMakeClosure makes a function value (with context) from the given
// closure expression.
func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.Value, error) {
func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
if len(expr.Bindings) == 0 {
panic("unexpected: MakeClosure without bound variables")
}
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
f := b.ir.GetFunction(expr.Fn.(*ssa.Function))
// Collect all bound variables.
boundVars := make([]llvm.Value, len(expr.Bindings))
for i, binding := range expr.Bindings {
// The context stores the bound variables.
llvmBoundVar := c.getValue(frame, binding)
llvmBoundVar := b.getValue(binding)
boundVars[i] = llvmBoundVar
}
// Store the bound variables in a single object, allocating it on the heap
// if necessary.
context := c.emitPointerPack(boundVars)
context := b.emitPointerPack(boundVars)
// Create the closure.
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
return b.createFuncValue(f.LLVMFn, context, f.Signature), nil
}
+23 -17
View File
@@ -12,53 +12,59 @@ import (
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
// one of the expressions that need this.
func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) {
// There are uses of this expression, Make sure the pointers
// are tracked during GC.
switch expr := expr.(type) {
case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap:
// These values are always of pointer type in IR.
c.trackPointer(value)
b.trackPointer(value)
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
if !value.IsNil() {
c.trackValue(value)
b.trackValue(value)
}
case *ssa.Select:
if alloca, ok := frame.selectRecvBuf[expr]; ok {
if alloca, ok := b.selectRecvBuf[expr]; ok {
if alloca.IsAUndefValue().IsNil() {
c.trackPointer(alloca)
b.trackPointer(alloca)
}
}
case *ssa.UnOp:
switch expr.Op {
case token.MUL:
// Pointer dereference.
c.trackValue(value)
b.trackValue(value)
case token.ARROW:
// Channel receive operator.
// It's not necessary to look at commaOk here, because in that
// case it's just an aggregate and trackValue will extract the
// pointer in there (if there is one).
c.trackValue(value)
b.trackValue(value)
}
case *ssa.BinOp:
switch expr.Op {
case token.ADD:
// String concatenation.
b.trackValue(value)
}
}
}
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
// individual pointers
func (c *Compiler) trackValue(value llvm.Value) {
func (b *builder) trackValue(value llvm.Value) {
typ := value.Type()
switch typ.TypeKind() {
case llvm.PointerTypeKind:
c.trackPointer(value)
b.trackPointer(value)
case llvm.StructTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.StructElementTypesCount()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
}
case llvm.ArrayTypeKind:
if !typeHasPointers(typ) {
@@ -66,19 +72,19 @@ func (c *Compiler) trackValue(value llvm.Value) {
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
}
}
}
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
// first if needed. The input value must be of LLVM pointer type.
func (c *Compiler) trackPointer(value llvm.Value) {
if value.Type() != c.i8ptrType {
value = c.builder.CreateBitCast(value, c.i8ptrType, "")
func (b *builder) trackPointer(value llvm.Value) {
if value.Type() != b.i8ptrType {
value = b.CreateBitCast(value, b.i8ptrType, "")
}
c.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
b.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
}
// typeHasPointers returns whether this type is a pointer or contains pointers.
File diff suppressed because it is too large Load Diff
+73 -36
View File
@@ -3,34 +3,32 @@ package compiler
// This file implements the 'go' keyword to start a new goroutine. See
// goroutine-lowering.go for more details.
import "tinygo.org/x/go-llvm"
import (
"go/token"
// emitStartGoroutine starts a new goroutine with the provided function pointer
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
// createGoInstruction starts a new goroutine with the provided function pointer
// and parameters.
// In general, you should pass all regular parameters plus the context parameter.
// There is one exception: the task-based scheduler needs to have the function
// pointer passed in as a parameter too in addition to the context.
//
// Because a go statement doesn't return anything, return undef.
func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
switch c.Scheduler() {
case "tasks":
paramBundle := c.emitPointerPack(params)
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
calleeValue := c.createGoroutineStartWrapper(funcPtr)
c.createRuntimeCall("startGoroutine", []llvm.Value{calleeValue, paramBundle}, "")
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
paramBundle := b.emitPointerPack(params)
var callee llvm.Value
switch b.Scheduler() {
case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
case "coroutines":
// We roundtrip through runtime.makeGoroutine as a signal (to find these
// calls) and to break any optimizations LLVM will try to do: they are
// invalid if we called this as a regular function to be updated later.
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
c.createCall(calleeValue, append(params, llvm.ConstPointerNull(c.i8ptrType)), "")
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
default:
panic("unreachable")
}
b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
@@ -52,36 +50,57 @@ func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) l
// allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a
// return value.
func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
var wrapper llvm.Value
builder := c.ctx.NewBuilder()
defer builder.Dispose()
if !fn.IsAFunction().IsNil() {
// See whether this wrapper has already been created. If so, return it.
name := fn.Name()
wrapper = c.mod.NamedFunction(name + "$gowrapper")
if !wrapper.IsNil() {
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
}
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
wrapper.SetLinkage(llvm.PrivateLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(entry)
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
Flags: 0, // ?
})
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
Name: "<goroutine wrapper>",
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: diFuncType,
LocalToUnit: true,
IsDefinition: true,
ScopeLine: 0,
Flags: llvm.FlagPrototyped,
Optimized: true,
})
wrapper.SetSubprogram(difunc)
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
// Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes()
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-1])
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
params = append(params, llvm.Undef(c.i8ptrType))
// Create the call.
c.builder.CreateCall(fn, params, "")
builder.CreateCall(fn, params, "")
} else {
// For a function pointer like this:
@@ -101,22 +120,40 @@ func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
// With a bit of luck, identical wrapper functions like these can be
// merged into one.
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, ".gowrapper", wrapperType)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(entry)
builder.SetInsertPointAtEnd(entry)
if c.Debug() {
pos := c.ir.Program.Fset.Position(pos)
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
File: c.getDIFile(pos.Filename),
Parameters: nil, // do not show parameters in debugger
Flags: 0, // ?
})
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
Name: "<goroutine wrapper>",
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: diFuncType,
LocalToUnit: true,
IsDefinition: true,
ScopeLine: 0,
Flags: llvm.FlagPrototyped,
Optimized: true,
})
wrapper.SetSubprogram(difunc)
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
// Get the list of parameters, with the extra parameters at the end.
paramTypes := fn.Type().ElementType().ParamTypes()
paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes)
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
// Get the function pointer.
fnPtr := params[len(params)-1]
@@ -126,13 +163,13 @@ func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call.
c.builder.CreateCall(fnPtr, params, "")
builder.CreateCall(fnPtr, params, "")
}
// Finish the function. Every basic block must end in a terminator, and
// because goroutines never return a value we can simply return void.
c.builder.CreateRetVoid()
builder.CreateRetVoid()
// Return a ptrtoint of the wrapper, not the function itself.
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
return builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
+63 -18
View File
@@ -18,8 +18,8 @@ import (
// func ReadRegister(name string) uintptr
//
// The register name must be a constant, for example "sp".
func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
func (b *builder) createReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
var asm string
switch name {
@@ -31,7 +31,7 @@ func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value,
panic("unknown architecture")
}
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
return b.CreateCall(target, nil, ""), nil
}
// This is a compiler builtin, which emits a piece of inline assembly with no
@@ -41,12 +41,12 @@ func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value,
// func Asm(asm string)
//
// The provided assembly must be a constant.
func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
// Magic function: insert inline assembly instead of calling it.
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false)
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{}, false)
asm := constant.StringVal(args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
return b.CreateCall(target, nil, ""), nil
}
// This is a compiler builtin, which allows assembly to be called in a flexible
@@ -63,7 +63,7 @@ func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
// "value": 1
// "result": &dest,
// })
func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) {
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
registers := map[string]llvm.Value{}
registerMap := instr.Args[1].(*ssa.MakeMap)
@@ -73,17 +73,17 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.Block() {
return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block")
return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block")
}
key := constant.StringVal(r.Key.(*ssa.Const).Value)
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X)
registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X)
case *ssa.Call:
if r.Common() == instr {
break
}
default:
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
}
}
// TODO: handle dollar signs in asm string
@@ -98,7 +98,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
name := s[1 : len(s)-1]
if _, ok := registers[name]; !ok {
if err == nil {
err = c.makeError(instr.Pos(), "unknown register name: "+name)
err = b.makeError(instr.Pos(), "unknown register name: "+name)
}
return s
}
@@ -112,7 +112,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
case llvm.PointerTypeKind:
constraints = append(constraints, "*m")
default:
err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
err = b.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
return s
}
}
@@ -121,9 +121,9 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
if err != nil {
return llvm.Value{}, err
}
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
fnType := llvm.FunctionType(b.ctx.VoidType(), argTypes, false)
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
return c.builder.CreateCall(target, args, ""), nil
return b.CreateCall(target, args, ""), nil
}
// This is a compiler builtin which emits an inline SVCall instruction. It can
@@ -137,7 +137,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
//
// The num parameter must be a constant. All other parameters may be any scalar
// value supported by LLVM inline assembly.
func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error) {
func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -150,7 +150,7 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
}
llvmValue := c.getValue(frame, arg)
llvmValue := b.getValue(arg)
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
@@ -158,7 +158,52 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
// clobbered. r0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{r1},~{r2},~{r3}"
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
return c.builder.CreateCall(target, llvmArgs, ""), nil
return b.CreateCall(target, llvmArgs, ""), nil
}
// This is a compiler builtin which emits CSR instructions. It can be one of:
//
// func (csr CSR) Get() uintptr
// func (csr CSR) Set(uintptr)
// func (csr CSR) SetBits(uintptr) uintptr
// func (csr CSR) ClearBits(uintptr) uintptr
//
// The csr parameter (method receiver) must be a constant. Other parameter can
// be any value.
func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) {
csrConst, ok := call.Args[0].(*ssa.Const)
if !ok {
return llvm.Value{}, b.makeError(call.Pos(), "CSR must be constant")
}
csr := csrConst.Uint64()
switch name := call.StaticCallee().Name(); name {
case "Get":
// Note that this instruction may have side effects, and thus must be
// marked as such.
fnType := llvm.FunctionType(b.uintptrType, nil, false)
asm := fmt.Sprintf("csrr $0, %d", csr)
target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0)
return b.CreateCall(target, nil, ""), nil
case "Set":
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrw %d, $0", csr)
target := llvm.InlineAsm(fnType, asm, "r", true, false, 0)
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
case "SetBits":
// Note: it may be possible to optimize this to csrrsi in many cases.
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrrs $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
case "ClearBits":
// Note: it may be possible to optimize this to csrrci in many cases.
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrrc $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name)
}
}
+101 -101
View File
@@ -16,35 +16,35 @@ import (
"tinygo.org/x/go-llvm"
)
// parseMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
// createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
// It tries to put the type in the interface value, but if that's not possible,
// it will do an allocation of the right size and put that in the interface
// value field.
//
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := c.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := c.getTypeCode(typ)
itfMethodSetGlobal := c.getTypeMethodSet(typ)
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
// An interface value is a {typecode, value} tuple named runtime._interface.
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := b.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := b.getTypeCode(typ)
itfMethodSetGlobal := b.getTypeMethodSet(typ)
itfConcreteTypeGlobal := b.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := c.getLLVMRuntimeType("typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
typeInInterface := b.getLLVMRuntimeType("typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(b.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
itfConcreteTypeGlobal.SetGlobalConstant(true)
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
}
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
itfTypeCode := b.CreatePtrToInt(itfConcreteTypeGlobal, b.uintptrType, "")
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = b.CreateInsertValue(itf, itfValue, 1, "")
return itf
}
// getTypeCode returns a reference to a type code.
// It returns a pointer to an external global which should be replaced with the
// real type in the interface lowering pass.
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
globalName := "reflect/types.type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
@@ -91,7 +91,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
// makeStructTypeFields creates a new global that stores all type information
// related to this struct type, and returns the resulting global. This global is
// actually an array of all the fields in the structs.
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
// The global is an array of runtime.structField structs.
runtimeStructField := c.getLLVMRuntimeType("structField")
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
@@ -228,7 +228,7 @@ func getTypeCodeName(t types.Type) string {
// getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass.
func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$methodset")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
@@ -271,8 +271,13 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
// getInterfaceMethodSet returns a global variable with the method set of the
// given named interface type. This method set is used by the interface lowering
// pass.
func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$interface")
func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
name := typ.String()
if _, ok := typ.(*types.Named); !ok {
// Anonymous interface.
name = "reflect/types.interface:" + name
}
global := c.mod.NamedGlobal(name + "$interface")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
// method set already exist, return it
@@ -287,7 +292,7 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
}
value := llvm.ConstArray(c.i8ptrType, methods)
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.PrivateLinkage)
@@ -297,7 +302,7 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
// getMethodSignature returns a global variable which is a reference to an
// external *i8 indicating the indicating the signature of this method. It is
// used during the interface lowering pass.
func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
signature := ir.MethodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() {
@@ -307,18 +312,18 @@ func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
return signatureGlobal
}
// parseTypeAssert will emit the code for a typeassert, used in if statements
// createTypeAssert will emit the code for a typeassert, used in if statements
// and in type switches (Go SSA does not have type switches, only if/else
// chains). Note that even though the Go SSA does not contain type switches,
// LLVM will recognize the pattern and make it a real switch in many cases.
//
// Type asserts on concrete types are trivial: just compare type numbers. Type
// asserts on interfaces are more difficult, see the comments in the function.
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
itf := c.getValue(frame, expr.X)
assertedType := c.getLLVMType(expr.AssertedType)
func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
itf := b.getValue(expr.X)
assertedType := b.getLLVMType(expr.AssertedType)
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type")
commaOk := llvm.Value{}
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type.
@@ -329,15 +334,15 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
// the main Go compiler, where the runtime checks whether the type
// implements each method of the interface. See:
// https://research.swtch.com/interfaces
methodSet := c.getInterfaceMethodSet(expr.AssertedType.(*types.Named))
commaOk = c.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
methodSet := b.getInterfaceMethodSet(expr.AssertedType)
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
} else {
// Type assert on concrete type.
// Call runtime.typeAssert, which will be lowered to a simple icmp or
// const false in the interface lowering pass.
assertedTypeCodeGlobal := c.getTypeCode(expr.AssertedType)
commaOk = c.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType)
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
}
// Add 2 new basic blocks (that should get optimized away): one for the
@@ -351,15 +356,15 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
// typeassert should return a zero value, not an incorrectly casted
// value.
prevBlock := c.builder.GetInsertBlock()
okBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.ok")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
c.builder.CreateCondBr(commaOk, okBlock, nextBlock)
prevBlock := b.GetInsertBlock()
okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok")
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was
// successful.
c.builder.SetInsertPointAtEnd(okBlock)
b.SetInsertPointAtEnd(okBlock)
var valueOk llvm.Value
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type. Easy: just return the same
@@ -368,74 +373,70 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
} else {
// Type assert on concrete type. Extract the underlying type from
// the interface (but only after checking it matches).
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
valueOk = b.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
}
c.builder.CreateBr(nextBlock)
b.CreateBr(nextBlock)
// Continue after the if statement.
c.builder.SetInsertPointAtEnd(nextBlock)
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
b.SetInsertPointAtEnd(nextBlock)
phi := b.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
if expr.CommaOk {
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple
tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
return tuple
} else {
// This is kind of dirty as the branch above becomes mostly useless,
// but hopefully this gets optimized away.
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
return phi
}
}
// getInvokeCall creates and returns the function pointer and parameters of an
// interface call. It can be used in a call or defer instruction.
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf := c.getValue(frame, instr.Value) // interface
llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature))
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
// getInvokePtr creates an interface function pointer lookup for the specified invoke instruction, using a specified typecode.
func (b *builder) getInvokePtr(instr *ssa.CallCommon, typecode llvm.Value) llvm.Value {
llvmFnType := b.getRawFuncType(instr.Method.Type().(*types.Signature))
values := []llvm.Value{
typecode,
c.getInterfaceMethodSet(instr.Value.Type().(*types.Named)),
c.getMethodSignature(instr.Method),
b.getInterfaceMethodSet(instr.Value.Type()),
b.getMethodSignature(instr.Method),
}
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
fnCast := c.builder.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func")
return b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
}
// getInvokeCall creates and returns the function pointer and parameters of an
// interface call.
func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf := b.getValue(instr.Value) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.typecode")
fnCast := b.getInvokePtr(instr, typecode)
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
args := []llvm.Value{receiverValue}
for _, arg := range instr.Args {
args = append(args, c.getValue(frame, arg))
args = append(args, b.getValue(arg))
}
// Add the context parameter. An interface call never takes a context but we
// have to supply the parameter anyway.
args = append(args, llvm.Undef(c.i8ptrType))
args = append(args, llvm.Undef(b.i8ptrType))
// Add the parent goroutine handle.
args = append(args, llvm.Undef(c.i8ptrType))
args = append(args, llvm.Undef(b.i8ptrType))
return fnCast, args
}
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
// createInterfaceInvokeWrapper. The former is called during IR construction
// itself and the latter is called when finishing up the IR.
type interfaceInvokeWrapper struct {
fn *ir.Function
wrapper llvm.Value
receiverType llvm.Type
}
// Wrap an interface method function pointer. The wrapper takes in a pointer to
// the underlying value, dereferences it, and calls the real method. This
// wrapper is only needed when the interface value actually doesn't fit in a
// pointer and a pointer to the value must be created.
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be
// invoked from an interface. The wrapper takes in a pointer to the underlying
// value, dereferences or unpacks it if necessary, and calls the real method.
// If the method to wrap has a pointer receiver, no wrapping is necessary and
// the function is returned directly.
func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
wrapperName := f.LinkName() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() {
@@ -445,7 +446,10 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
// Get the expanded receiver type.
receiverType := c.getLLVMType(f.Params[0].Type())
expandedReceiverType := c.expandFormalParamType(receiverType)
var expandedReceiverType []llvm.Type
for _, info := range expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
}
// Does this method even need any wrapping?
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {
@@ -464,41 +468,37 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
if f.LLVMFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle")
}
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
fn: f,
wrapper: wrapper,
receiverType: receiverType,
})
return wrapper
}
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
// see that function for details.
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
wrapper := state.wrapper
fn := state.fn
receiverType := state.receiverType
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
// Create a new builder just to create this wrapper.
b := builder{
compilerContext: c,
Builder: c.ctx.NewBuilder(),
}
defer b.Builder.Dispose()
// add debug info if needed
if c.Debug() {
pos := c.ir.Program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
pos := c.ir.Program.Fset.Position(f.Pos())
difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
// set up IR builder
block := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(block)
block := b.ctx.AddBasicBlock(wrapper, "entry")
b.SetInsertPointAtEnd(block)
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
c.builder.CreateCall(fn.LLVMFn, params, "")
c.builder.CreateRetVoid()
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(f.LLVMFn, params, "")
b.CreateRetVoid()
} else {
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
c.builder.CreateRet(ret)
ret := b.CreateCall(f.LLVMFn, params, "ret")
b.CreateRet(ret)
}
return wrapper
}
+92
View File
@@ -0,0 +1,92 @@
package compiler
import (
"strconv"
"strings"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
// will be lowered to a real interrupt during interrupt lowering.
//
// This two-stage approach allows unused interrupts to be optimized away if
// necessary.
func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, error) {
// Get the interrupt number, which must be a compile-time constant.
id, ok := instr.Args[0].(*ssa.Const)
if !ok {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt ID is not a constant")
}
// Get the func value, which also must be a compile time constant.
// Note that bound functions are allowed if the function has a pointer
// receiver and is a global. This is rather strict but still allows for
// idiomatic Go code.
funcValue := b.getValue(instr.Args[1])
if funcValue.IsAConstant().IsNil() {
// Try to determine the cause of the non-constantness for a nice error
// message.
switch instr.Args[1].(type) {
case *ssa.MakeClosure:
// This may also be a bound method.
return llvm.Value{}, b.makeError(instr.Pos(), "closures are not supported in interrupt.New")
}
// Fall back to a generic error.
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
}
// Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass.
globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
}
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
global.SetLinkage(llvm.PrivateLinkage)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
initializer := llvm.ConstNull(globalLLVMType)
initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{1, 0})
global.SetInitializer(initializer)
// Add debug info to the interrupt global.
if b.Debug() {
pos := b.ir.Program.Fset.Position(instr.Pos())
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
LinkageName: globalName,
File: b.getDIFile(pos.Filename),
Line: pos.Line,
Type: b.getDIType(globalType),
Expr: b.dibuilder.CreateExpression(nil),
LocalToUnit: false,
})
global.AddMetadata(0, diglobal)
}
// Create the runtime/interrupt.Interrupt type. It is a struct with a single
// member of type int.
num := llvm.ConstPtrToInt(global, b.intType)
interrupt := llvm.ConstNamedStruct(b.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
// Add dummy "use" call for AVR, because interrupts may be used even though
// they are never referenced again. This is unlike Cortex-M or the RISC-V
// PLIC where each interrupt must be enabled using the interrupt number, and
// thus keeps the Interrupt object alive.
// This call is removed during interrupt lowering.
if strings.HasPrefix(b.Triple(), "avr") {
useFn := b.mod.NamedFunction("runtime/interrupt.use")
if useFn.IsNil() {
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType)
}
b.CreateCall(useFn, []llvm.Value{interrupt}, "")
}
return interrupt, nil
}
+50
View File
@@ -0,0 +1,50 @@
package compiler
// This file contains helper functions to create calls to LLVM intrinsics.
import (
"strconv"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createMemoryCopyCall creates a call to a builtin LLVM memcpy or memmove
// function, declaring this function if needed. These calls are treated
// specially by optimization passes possibly resulting in better generated code,
// and will otherwise be lowered to regular libc memcpy/memmove calls.
func (b *builder) createMemoryCopyCall(fn *ssa.Function, args []ssa.Value) (llvm.Value, error) {
fnName := "llvm." + fn.Name() + ".p0i8.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.i8ptrType, b.uintptrType, b.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
}
var params []llvm.Value
for _, param := range args {
params = append(params, b.getValue(param))
}
params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false))
b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil
}
// createMemoryZeroCall creates calls to llvm.memset.* to zero a block of
// memory, declaring the function if needed. These calls will be lowered to
// regular libc memset calls if they aren't optimized out in a different way.
func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
fnName := "llvm.memset.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.ctx.Int8Type(), b.uintptrType, b.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
}
params := []llvm.Value{
b.getValue(args[0]),
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
b.getValue(args[1]),
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
}
b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil
}
+41 -19
View File
@@ -1,7 +1,8 @@
package compiler
// This file implements a set of sanity checks for the IR that is generated.
// It can catch some mistakes that LLVM's verifier cannot.
// Package ircheck implements a checker for LLVM IR, that goes a bit further
// than the regular LLVM IR verifier. Note that it checks different things, so
// this is not a replacement for the LLVM verifier but does catch things that
// the LLVM verifier doesn't catch.
package ircheck
import (
"errors"
@@ -10,7 +11,11 @@ import (
"tinygo.org/x/go-llvm"
)
func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
type checker struct {
ctx llvm.Context
}
func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// prevent infinite recursion for self-referential types
if _, ok := checked[t]; ok {
return nil
@@ -81,7 +86,7 @@ func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specia
return nil
}
func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// check type
if err := c.checkType(v.Type(), types, specials); err != nil {
return fmt.Errorf("failed to verify type of value: %s", err.Error())
@@ -95,12 +100,30 @@ func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specia
return nil
}
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// check value properties
if err := c.checkValue(inst, types, specials); err != nil {
return errorAt(inst, err.Error())
}
// The alloca instruction can be present in every basic block. However,
// allocas in basic blocks other than the entry basic block have a number of
// problems:
// * They are hard to optimize, leading to potential missed optimizations.
// * They may cause stack overflows in loops that would otherwise be
// innocent.
// * They cause extra code to be generated, because it requires the use of
// a frame pointer.
// * Perhaps most importantly, the coroutine lowering pass of LLVM (as of
// LLVM 9) cannot deal with these allocas:
// https://llvm.org/docs/Coroutines.html
// Therefore, alloca instructions should be limited to the entry block.
if !inst.IsAAllocaInst().IsNil() {
if inst.InstructionParent() != inst.InstructionParent().Parent().EntryBasicBlock() {
return errorAt(inst, "internal error: non-static alloca")
}
}
// check operands
for i := 0; i < inst.OperandsCount(); i++ {
if err := c.checkValue(inst.Operand(i), types, specials); err != nil {
@@ -111,7 +134,7 @@ func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{
return nil
}
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
// check basic block value and type
var errs []error
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
@@ -128,7 +151,7 @@ func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struc
return errs
}
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
// check function value and type
var errs []error
if err := c.checkValue(fn, types, specials); err != nil {
@@ -143,26 +166,25 @@ func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, sp
return errs
}
func (c *Compiler) checkModule() []error {
// Module checks the given module and returns a slice of error, if there are
// any.
func Module(mod llvm.Module) []error {
// check for any context mismatches
var errs []error
switch {
case c.mod.Context() == c.ctx:
// this is correct
case c.mod.Context() == llvm.GlobalContext():
c := checker{
ctx: mod.Context(),
}
if c.ctx == llvm.GlobalContext() {
// somewhere we accidentally used the global context instead of a real context
errs = append(errs, errors.New("module uses global context"))
default:
// we used some other context by accident
errs = append(errs, fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
}
types := map[llvm.Type]struct{}{}
specials := map[llvm.TypeKind]llvm.Type{}
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
errs = append(errs, c.checkFunction(fn, types, specials)...)
}
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
for g := mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
if err := c.checkValue(g, types, specials); err != nil {
errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error()))
}
+48
View File
@@ -0,0 +1,48 @@
package ircheck
import (
"go/scanner"
"go/token"
"path/filepath"
"tinygo.org/x/go-llvm"
)
// errorAt returns an error value at the location of the instruction.
// The location information may not be complete as it depends on debug
// information in the IR.
func errorAt(inst llvm.Value, msg string) scanner.Error {
return scanner.Error{
Pos: getPosition(inst),
Msg: msg,
}
}
// getPosition returns the position information for the given value, as far as
// it is available.
func getPosition(val llvm.Value) token.Position {
if !val.IsAInstruction().IsNil() {
loc := val.InstructionDebugLoc()
if loc.IsNil() {
return token.Position{}
}
file := loc.LocationScope().ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.LocationLine()),
Column: int(loc.LocationColumn()),
}
} else if !val.IsAFunction().IsNil() {
loc := val.Subprogram()
if loc.IsNil() {
return token.Position{}
}
file := loc.ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.SubprogramLine()),
}
} else {
return token.Position{}
}
}
+10 -10
View File
@@ -24,39 +24,39 @@ func getUses(value llvm.Value) []llvm.Value {
}
// createTemporaryAlloca creates a new alloca in the entry block and adds
// lifetime start infromation in the IR signalling that the alloca won't be used
// lifetime start information in the IR signalling that the alloca won't be used
// before this point.
//
// This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it.
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
return llvmutil.CreateTemporaryAlloca(c.builder, c.mod, t, name)
func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
}
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
// llvm.lifetime.end intrinsic. It is commonly used together with
// createTemporaryAlloca.
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
llvmutil.EmitLifetimeEnd(c.builder, c.mod, ptr, size)
func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
llvmutil.EmitLifetimeEnd(b.Builder, b.mod, ptr, size)
}
// emitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(c.builder, c.mod, c.Config, values)
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.Config, values)
}
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes)
func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
return llvmutil.EmitPointerUnpack(b.Builder, b.mod, ptr, valueTypes)
}
// makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global.
// Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself.
func (c *Compiler) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
globalType := llvm.ArrayType(elementType, len(buf))
global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType)
+22
View File
@@ -12,6 +12,7 @@ import (
// EmitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
// If the values are all constants, they are be stored in a constant global and deduplicated.
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.Config, values []llvm.Value) llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
@@ -42,6 +43,27 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
// it in an alloca first for bitcasting (store+bitcast+load).
packedAlloc, _, _ = CreateTemporaryAlloca(builder, mod, packedType, "")
} else {
// Check if the values are all constants.
constant := true
for _, v := range values {
if !v.IsConstant() {
constant = false
break
}
}
if constant {
// The data is known at compile time, so store it in a constant global.
// The global address is marked as unnamed, which allows LLVM to merge duplicates.
funcName := builder.GetInsertBlock().Parent().Name()
global := llvm.AddGlobal(mod, packedType, funcName+"$pack")
global.SetInitializer(ctx.ConstStruct(values, false))
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstBitCast(global, i8ptrType)
}
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(uintptrType, size, false)
alloc := mod.NamedFunction("runtime.alloc")
+108 -56
View File
@@ -6,115 +6,167 @@ import (
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType := c.getLLVMType(valueType)
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
// initializing an appropriately sized object.
func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
mapType := expr.Type().Underlying().(*types.Map)
keyType := mapType.Key().Underlying()
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
var llvmKeyType llvm.Type
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// String keys.
llvmKeyType = b.getLLVMType(keyType)
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = b.getLLVMType(keyType)
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = b.getLLVMRuntimeType("_interface")
}
keySize := b.targetData.TypeAllocSize(llvmKeyType)
valueSize := b.targetData.TypeAllocSize(llvmValueType)
llvmKeySize := llvm.ConstInt(b.ctx.Int8Type(), keySize, false)
llvmValueSize := llvm.ConstInt(b.ctx.Int8Type(), valueSize, false)
sizeHint := llvm.ConstInt(b.uintptrType, 8, false)
if expr.Reserve != nil {
sizeHint = b.getValue(expr.Reserve)
var err error
sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos())
if err != nil {
return llvm.Value{}, err
}
}
hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "")
return hashmap, nil
}
// createMapLookup returns the value in a map. It calls a runtime function
// depending on the map key type to load the map value and its comma-ok value.
func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType := b.getLLVMType(valueType)
// Allocate the memory for the resulting type. Do not zero this memory: it
// will be zeroed by the hashmap get implementation if the key is not
// present in the map.
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value")
mapValueAlloca, mapValuePtr, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
// We need the map size (with type uintptr) to pass to the hashmap*Get
// functions. This is necessary because those *Get functions are valid on
// nil maps, and they'll need to zero the value pointer by that number of
// bytes.
mapValueSize := mapValueAllocaSize
if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType)
}
// Do the lookup. How it is done depends on the key type.
var commaOkValue llvm.Value
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
params := []llvm.Value{m, key, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
// Store the key in an alloca, in the entry block to avoid dynamic stack
// growth.
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, mapKeyAlloca)
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca)
// Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} else {
// Not trivially comparable using memcmp.
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// Not trivially comparable using memcmp. Make it an interface instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface now.
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
}
// Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap.
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
mapValue := b.CreateLoad(mapValueAlloca, "")
b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize)
if commaOk {
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOkValue, 1, "")
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, mapValue, 0, "")
tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "")
return tuple, nil
} else {
return mapValue, nil
}
}
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value")
c.builder.CreateStore(value, valueAlloca)
// createMapUpdate updates a map key to a given value, by creating an
// appropriate runtime call.
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
b.CreateStore(value, valueAlloca)
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, valuePtr}
c.createRuntimeCall("hashmapStringSet", params, "")
b.createRuntimeCall("hashmapStringSet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr, valuePtr}
c.createRuntimeCall("hashmapBinarySet", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
} else {
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// Key is not trivially comparable, so compare it as an interface instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first.
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey, valuePtr}
b.createRuntimeCall("hashmapInterfaceSet", params, "")
}
c.emitLifetimeEnd(valuePtr, valueSize)
b.emitLifetimeEnd(valuePtr, valueSize)
}
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
// createMapDelete deletes a key from a map by calling the appropriate runtime
// function. It is the implementation of the Go delete() builtin.
func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key}
c.createRuntimeCall("hashmapStringDelete", params, "")
b.createRuntimeCall("hashmapStringDelete", params, "")
return nil
} else if hashmapIsBinaryKey(keyType) {
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr}
c.createRuntimeCall("hashmapBinaryDelete", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
return nil
} else {
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// Key is not trivially comparable, so compare it as an interface
// instead.
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first.
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey}
b.createRuntimeCall("hashmapInterfaceDelete", params, "")
return nil
}
}
// Get FNV-1a hash of this string.
//
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
func hashmapHash(data []byte) uint32 {
var result uint32 = 2166136261 // FNV offset basis
for _, c := range data {
result ^= uint32(c)
result *= 16777619 // FNV prime
}
return result
}
// Get the topmost 8 bits of the hash, without using a special value (like 0).
func hashmapTopHash(hash uint32) uint8 {
tophash := uint8(hash >> 24)
if tophash < 1 {
// 0 means empty slot, so make it bigger.
tophash += 1
}
return tophash
}
// Returns true if this key type does not contain strings, interfaces etc., so
// can be compared with runtime.memequal.
func hashmapIsBinaryKey(keyType types.Type) bool {
-153
View File
@@ -1,153 +0,0 @@
package compiler
import (
"errors"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
// Run the LLVM optimizer over the module.
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []error {
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.SetSizeLevel(sizeLevel)
if inlinerThreshold != 0 {
builder.UseInlinerWithThreshold(inlinerThreshold)
}
builder.AddCoroutinePassesToExtensionPoints()
if c.PanicStrategy() == "trap" {
transform.ReplacePanicsWithTrap(c.mod) // -panic=trap
}
// run a check of all of our code
if c.VerifyIR() {
errs := c.checkModule()
if errs != nil {
return errs
}
}
// Run function passes for each function.
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
defer funcPasses.Dispose()
builder.PopulateFunc(funcPasses)
funcPasses.InitializeFunc()
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
if optLevel > 0 {
// Run some preparatory passes for the Go optimizer.
goPasses := llvm.NewPassManager()
defer goPasses.Dispose()
goPasses.AddGlobalOptimizerPass()
goPasses.AddConstantPropagationPass()
goPasses.AddAggressiveDCEPass()
goPasses.AddFunctionAttrsPass()
goPasses.Run(c.mod)
// Run Go-specific optimization passes.
transform.OptimizeMaps(c.mod)
transform.OptimizeStringToBytes(c.mod)
transform.OptimizeAllocs(c.mod)
transform.LowerInterfaces(c.mod)
if c.funcImplementation() == funcValueSwitch {
transform.LowerFuncValues(c.mod)
}
// After interfaces are lowered, there are many more opportunities for
// interprocedural optimizations. To get them to work, function
// attributes have to be updated first.
goPasses.Run(c.mod)
// Run TinyGo-specific interprocedural optimizations.
transform.OptimizeAllocs(c.mod)
transform.OptimizeStringToBytes(c.mod)
// Lower runtime.isnil calls to regular nil comparisons.
isnil := c.mod.NamedFunction("runtime.isnil")
if !isnil.IsNil() {
for _, use := range getUses(isnil) {
c.builder.SetInsertPointBefore(use)
ptr := use.Operand(0)
if !ptr.IsABitCastInst().IsNil() {
ptr = ptr.Operand(0)
}
nilptr := llvm.ConstPointerNull(ptr.Type())
icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
use.ReplaceAllUsesWith(icmp)
use.EraseFromParentAsInstruction()
}
}
err := c.LowerGoroutines()
if err != nil {
return []error{err}
}
} else {
// Must be run at any optimization level.
transform.LowerInterfaces(c.mod)
if c.funcImplementation() == funcValueSwitch {
transform.LowerFuncValues(c.mod)
}
err := c.LowerGoroutines()
if err != nil {
return []error{err}
}
}
if c.VerifyIR() {
if errs := c.checkModule(); errs != nil {
return errs
}
}
if err := c.Verify(); err != nil {
return []error{errors.New("optimizations caused a verification failure")}
}
if sizeLevel >= 2 {
// Set the "optsize" attribute to make slightly smaller binaries at the
// cost of some performance.
kind := llvm.AttributeKindID("optsize")
attr := c.ctx.CreateEnumAttribute(kind, 0)
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
fn.AddFunctionAttr(attr)
}
}
// After TinyGo-specific transforms have finished, undo exporting these functions.
for _, name := range c.getFunctionsUsedInTransforms() {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
fn.SetLinkage(llvm.InternalLinkage)
}
// Run function passes again, because without it, llvm.coro.size.i32()
// doesn't get lowered.
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
// Run module passes.
modPasses := llvm.NewPassManager()
defer modPasses.Dispose()
builder.Populate(modPasses)
modPasses.Run(c.mod)
hasGCPass := transform.AddGlobalsBitmap(c.mod)
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
if hasGCPass {
if err := c.Verify(); err != nil {
return []error{errors.New("GC pass caused a verification failure")}
}
}
return nil
}
+4 -4
View File
@@ -10,13 +10,13 @@ import (
// The original license can be found here:
// https://golang.org/LICENSE
type StdSizes struct {
type stdSizes struct {
IntSize int64
PtrSize int64
MaxAlign int64
}
func (s *StdSizes) Alignof(T types.Type) int64 {
func (s *stdSizes) Alignof(T types.Type) int64 {
// For arrays and structs, alignment is defined in terms
// of alignment of the elements and fields, respectively.
switch t := T.Underlying().(type) {
@@ -61,7 +61,7 @@ func (s *StdSizes) Alignof(T types.Type) int64 {
return a
}
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
func (s *stdSizes) Offsetsof(fields []*types.Var) []int64 {
offsets := make([]int64, len(fields))
var o int64
for i, f := range fields {
@@ -89,7 +89,7 @@ var basicSizes = [...]byte{
types.Complex128: 16,
}
func (s *StdSizes) Sizeof(T types.Type) int64 {
func (s *stdSizes) Sizeof(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Basic:
k := t.Kind()
+36 -6
View File
@@ -26,7 +26,7 @@ type globalInfo struct {
// loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals.
func (c *Compiler) loadASTComments(lprogram *loader.Program) {
func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
@@ -56,25 +56,55 @@ func (c *Compiler) loadASTComments(lprogram *loader.Program) {
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
// the LLVM IR if it has not been added already.
func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
info := c.getGlobalInfo(g)
llvmGlobal := c.mod.NamedGlobal(info.linkName)
if llvmGlobal.IsNil() {
llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem())
typ := g.Type().(*types.Pointer).Elem()
llvmType := c.getLLVMType(typ)
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
if !info.extern {
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
llvmGlobal.SetLinkage(llvm.InternalLinkage)
}
if info.align > c.targetData.ABITypeAlignment(llvmType) {
llvmGlobal.SetAlignment(info.align)
// Set alignment from the //go:align comment.
var alignInBits uint32
if info.align < 0 || info.align&(info.align-1) != 0 {
// Check for power-of-two (or 0).
// See: https://stackoverflow.com/a/108360
c.addError(g.Pos(), "global variable alignment must be a positive power of two")
} else {
// Set the alignment only when it is a power of two.
alignInBits = uint32(info.align) ^ uint32(info.align-1)
if info.align > c.targetData.ABITypeAlignment(llvmType) {
llvmGlobal.SetAlignment(info.align)
}
}
if c.Debug() && !info.extern {
// Add debug info.
// TODO: this should be done for every global in the program, not just
// the ones that are referenced from some code.
pos := c.ir.Program.Fset.Position(g.Pos())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: g.RelString(nil),
LinkageName: info.linkName,
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: c.getDIType(typ),
LocalToUnit: false,
Expr: c.dibuilder.CreateExpression(nil),
AlignInBits: alignInBits,
})
llvmGlobal.AddMetadata(0, diglobal)
}
}
return llvmGlobal
}
// getGlobalInfo returns some information about a specific global.
func (c *Compiler) getGlobalInfo(g *ssa.Global) globalInfo {
func (c *compilerContext) getGlobalInfo(g *ssa.Global) globalInfo {
info := globalInfo{}
if strings.HasPrefix(g.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
+45 -45
View File
@@ -10,14 +10,14 @@ import (
"tinygo.org/x/go-llvm"
)
// emitSyscall emits an inline system call instruction, depending on the target
// OS/arch.
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
num := c.getValue(frame, call.Args[0])
// createSyscall emits an inline system call instruction, depending on the
// target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
num := b.getValue(call.Args[0])
var syscallResult llvm.Value
switch {
case c.GOARCH() == "amd64":
if c.GOOS() == "darwin" {
case b.GOARCH() == "amd64":
if b.GOOS() == "darwin" {
// Darwin adds this magic number to system call numbers:
//
// > Syscall classes for 64-bit system call entry.
@@ -28,13 +28,13 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
// > All system classes enter the kernel via the syscall instruction.
//
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
num = b.CreateOr(num, llvm.ConstInt(b.uintptrType, 0x2000000, false), "")
}
// Sources:
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
args := []llvm.Value{num}
argTypes := []llvm.Type{c.uintptrType}
argTypes := []llvm.Type{b.uintptrType}
// Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
constraints := "={rax},0"
@@ -50,21 +50,21 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{r12}",
"{r13}",
}[i]
llvmValue := c.getValue(frame, arg)
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH() == "386" && c.GOOS() == "linux":
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "386" && b.GOOS() == "linux":
// Sources:
// syscall(2) man page
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
args := []llvm.Value{num}
argTypes := []llvm.Type{c.uintptrType}
argTypes := []llvm.Type{b.uintptrType}
// Constraints will look something like:
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
constraints := "={eax},0"
@@ -77,14 +77,14 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{edi}",
"{ebp}",
}[i]
llvmValue := c.getValue(frame, arg)
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH() == "arm" && c.GOOS() == "linux":
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "arm" && b.GOOS() == "linux":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
args := []llvm.Value{}
@@ -102,21 +102,21 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{r5}",
"{r6}",
}[i]
llvmValue := c.getValue(frame, arg)
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
argTypes = append(argTypes, c.uintptrType)
argTypes = append(argTypes, b.uintptrType)
constraints += ",{r7}" // syscall number
for i := len(call.Args) - 1; i < 4; i++ {
// r0-r3 get clobbered after the syscall returns
constraints += ",~{r" + strconv.Itoa(i) + "}"
}
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH() == "arm64" && c.GOOS() == "linux":
syscallResult = b.CreateCall(target, args, "")
case b.GOARCH() == "arm64" && b.GOOS() == "linux":
// Source: syscall(2) man page.
args := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -132,12 +132,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
"{x4}",
"{x5}",
}[i]
llvmValue := c.getValue(frame, arg)
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
argTypes = append(argTypes, c.uintptrType)
argTypes = append(argTypes, b.uintptrType)
constraints += ",{x8}" // syscall number
for i := len(call.Args) - 1; i < 8; i++ {
// x0-x7 may get clobbered during the syscall following the aarch64
@@ -145,13 +145,13 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
constraints += ",~{x" + strconv.Itoa(i) + "}"
}
constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = c.builder.CreateCall(target, args, "")
syscallResult = b.CreateCall(target, args, "")
default:
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
}
switch c.GOOS() {
switch b.GOOS() {
case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno
// Pseudocode:
@@ -160,15 +160,15 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
// err = -syscallResult
// }
// return syscallResult, 0, err
zero := llvm.ConstInt(c.uintptrType, 0, false)
inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
zero := llvm.ConstInt(b.uintptrType, 0, false)
inrange1 := b.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "")
inrange2 := b.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(b.uintptrType, 0xfffffffffffff000, true), "") // -4096
hasError := b.CreateAnd(inrange1, inrange2, "")
errResult := b.CreateSelect(hasError, b.CreateSub(zero, syscallResult, ""), zero, "syscallError")
retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, zero, 1, "")
retval = b.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
case "darwin":
// Return values: r0, r1 uintptr, err Errno
@@ -178,15 +178,15 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
// err = syscallResult
// }
// return syscallResult, 0, err
zero := llvm.ConstInt(c.uintptrType, 0, false)
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
zero := llvm.ConstInt(b.uintptrType, 0, false)
hasError := b.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "")
errResult := b.CreateSelect(hasError, syscallResult, zero, "syscallError")
retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, zero, 1, "")
retval = b.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
default:
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
}
}
+13 -9
View File
@@ -8,19 +8,23 @@ import (
"tinygo.org/x/go-llvm"
)
func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
c.emitNilCheck(frame, addr, "deref")
val := c.builder.CreateLoad(addr, "")
// createVolatileLoad is the implementation of the intrinsic function
// runtime/volatile.LoadT().
func (b *builder) createVolatileLoad(instr *ssa.CallCommon) (llvm.Value, error) {
addr := b.getValue(instr.Args[0])
b.createNilCheck(instr.Args[0], addr, "deref")
val := b.CreateLoad(addr, "")
val.SetVolatile(true)
return val, nil
}
func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
addr := c.getValue(frame, instr.Args[0])
val := c.getValue(frame, instr.Args[1])
c.emitNilCheck(frame, addr, "deref")
store := c.builder.CreateStore(val, addr)
// createVolatileStore is the implementation of the intrinsic function
// runtime/volatile.StoreT().
func (b *builder) createVolatileStore(instr *ssa.CallCommon) (llvm.Value, error) {
addr := b.getValue(instr.Args[0])
val := b.getValue(instr.Args[1])
b.createNilCheck(instr.Args[0], addr, "deref")
store := b.CreateStore(val, addr)
store.SetVolatile(true)
return llvm.Value{}, nil
}
+4 -5
View File
@@ -4,11 +4,10 @@ go 1.11
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/creack/goselect v0.1.0 // indirect
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae
go.bug.st/serial v1.0.0
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
google.golang.org/appengine v1.4.0 // indirect
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a
)
+31 -6
View File
@@ -1,23 +1,42 @@
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 h1:mACY1anK6HNCZtm/DK2Rf2ZPHggVqeB0+7rY9Gl6wyI=
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
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-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
@@ -32,3 +51,9 @@ tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEy
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae h1:s8J5EyxCkHxXB08UI3gk9W9IS/ekizRvSX+PfZxnAB0=
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566 h1:a4y30bTf7U0zDA75v2PTL+XQ2OzJetj19gK8XwQpUNY=
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d h1:mtgZh/e8a3wxneQFuLXoQYO//1mvlki02yZ1JCwMKp4=
tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a h1:Ugje2Lxuv8CFncHzs5W+hWfJvPsM+W4K0zRvzFbLvoE=
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
+8
View File
@@ -18,6 +18,7 @@ var Keys = []string{
"GOROOT",
"GOPATH",
"GOCACHE",
"CGO_ENABLED",
"TINYGOROOT",
}
@@ -57,6 +58,13 @@ func Get(name string) string {
panic("could not find cache dir: " + err.Error())
}
return filepath.Join(dir, "tinygo")
case "CGO_ENABLED":
val := os.Getenv("CGO_ENABLED")
if val == "1" || val == "0" {
return val
}
// Default to enabling CGo.
return "1"
case "TINYGOROOT":
return sourceDir()
default:
+19 -30
View File
@@ -13,55 +13,44 @@ import (
// errUnreachable is returned when an unreachable instruction is executed. This
// error should not be visible outside of the interp package.
var errUnreachable = errors.New("interp: unreachable executed")
// Unsupported is the specific error that is returned when an unsupported
// instruction is hit while trying to interpret all initializers.
type Unsupported struct {
ImportPath string
Inst llvm.Value
Pos token.Position
}
func (e Unsupported) Error() string {
// TODO: how to return the actual instruction string?
// It looks like LLVM provides no function for that...
return scanner.Error{
Pos: e.Pos,
Msg: "interp: unsupported instruction",
}.Error()
}
var errUnreachable = &Error{Err: errors.New("interp: unreachable executed")}
// unsupportedInstructionError returns a new "unsupported instruction" error for
// the given instruction. It includes source location information, when
// available.
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Unsupported {
return &Unsupported{
ImportPath: e.packagePath,
Inst: inst,
Pos: getPosition(inst),
}
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Error {
return e.errorAt(inst, errors.New("interp: unsupported instruction"))
}
// ErrorLine is one line in a traceback. The position may be missing.
type ErrorLine struct {
Pos token.Position
Inst llvm.Value
}
// Error encapsulates compile-time interpretation errors with an associated
// import path. The errors may not have a precise location attached.
type Error struct {
ImportPath string
Errs []scanner.Error
Inst llvm.Value
Pos token.Position
Err error
Traceback []ErrorLine
}
// Error returns the string of the first error in the list of errors.
func (e Error) Error() string {
return e.Errs[0].Error()
func (e *Error) Error() string {
return e.Pos.String() + ": " + e.Err.Error()
}
// errorAt returns an error value for the currently interpreted package at the
// location of the instruction. The location information may not be complete as
// it depends on debug information in the IR.
func (e *evalPackage) errorAt(inst llvm.Value, msg string) Error {
return Error{
func (e *evalPackage) errorAt(inst llvm.Value, err error) *Error {
return &Error{
ImportPath: e.packagePath,
Errs: []scanner.Error{errorAt(inst, msg)},
Pos: getPosition(inst),
Err: err,
}
}
+81 -24
View File
@@ -4,6 +4,7 @@ package interp
// functions.
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
@@ -21,7 +22,7 @@ type frame struct {
// Most of it works at compile time. Some calls get translated into calls to be
// executed at runtime: calls to functions with side effects, external calls,
// and operations on the result of such instructions.
func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (retval Value, outgoing []llvm.Value, err error) {
func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (retval Value, outgoing []llvm.Value, err *Error) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if fr.Debug {
print(indent)
@@ -97,7 +98,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
value = operand.Load()
}
if value.Type() != inst.Type() {
return nil, nil, fr.errorAt(inst, "interp: load: type does not match")
return nil, nil, fr.errorAt(inst, errors.New("interp: load: type does not match"))
}
fr.locals[inst] = fr.getValue(value)
case !inst.IsAStoreInst().IsNil():
@@ -121,13 +122,16 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// Not a constant operation.
// This should be detected by the scanner, but isn't at the
// moment.
return nil, nil, fr.errorAt(inst, "todo: non-const gep")
return nil, nil, fr.errorAt(inst, errors.New("todo: non-const gep"))
}
indices[i] = uint32(operand.Value().ZExtValue())
}
result := value.GetElementPtr(indices)
result, err := value.GetElementPtr(indices)
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
if result.Type() != inst.Type() {
return nil, nil, fr.errorAt(inst, "interp: gep: type does not match")
return nil, nil, fr.errorAt(inst, errors.New("interp: gep: type does not match"))
}
fr.locals[inst] = result
@@ -182,7 +186,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
}
// It is not possible in Go to bitcast a map value to a pointer.
return nil, nil, fr.errorAt(inst, "unimplemented: bitcast of map")
return nil, nil, fr.errorAt(inst, errors.New("unimplemented: bitcast of map"))
}
value := fr.getLocal(operand).(*LocalValue)
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
@@ -264,7 +268,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
if elementCount == 1 {
fr.locals[resultInst] = result
} else {
fr.locals[resultInst] = result.GetElementPtr([]uint32{0, 0})
result, err := result.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
fr.locals[resultInst] = result
}
case callee.Name() == "runtime.hashmapMake":
// create a map
@@ -365,32 +373,49 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// a bitcast of the original array instead of the GEP,
// which breaks our assumptions.
// Re-add this GEP, in the hope that it it is then of the correct type...
dstArray = dstArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
srcArray = srcArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
dstArrayValue, err := dstArray.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
dstArray = dstArrayValue.(*LocalValue)
srcArrayValue, err := srcArray.GetElementPtr([]uint32{0, 0})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
srcArray = srcArrayValue.(*LocalValue)
}
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
return nil, nil, fr.errorAt(inst, "interp: slice dst element size does not match pointer type")
return nil, nil, fr.errorAt(inst, errors.New("interp: slice dst element size does not match pointer type"))
}
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
return nil, nil, fr.errorAt(inst, "interp: slice src element size does not match pointer type")
return nil, nil, fr.errorAt(inst, errors.New("interp: slice src element size does not match pointer type"))
}
if dstArray.Type() != srcArray.Type() {
return nil, nil, fr.errorAt(inst, "interp: slice element types don't match")
return nil, nil, fr.errorAt(inst, errors.New("interp: slice element types don't match"))
}
length := dstLen.Value().SExtValue()
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
length = srcLength
}
if length < 0 {
return nil, nil, fr.errorAt(inst, "interp: trying to copy a slice with negative length?")
return nil, nil, fr.errorAt(inst, errors.New("interp: trying to copy a slice with negative length?"))
}
for i := int64(0); i < length; i++ {
var err error
// *dst = *src
dstArray.Store(srcArray.Load())
// dst++
dstArray = dstArray.GetElementPtr([]uint32{1}).(*LocalValue)
dstArrayValue, err := dstArray.GetElementPtr([]uint32{1})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
dstArray = dstArrayValue.(*LocalValue)
// src++
srcArray = srcArray.GetElementPtr([]uint32{1}).(*LocalValue)
srcArrayValue, err := srcArray.GetElementPtr([]uint32{1})
if err != nil {
return nil, nil, fr.errorAt(inst, err)
}
srcArray = srcArrayValue.(*LocalValue)
}
case callee.Name() == "runtime.stringToBytes":
// convert a string to a []byte
@@ -416,20 +441,35 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
fr.locals[inst] = &LocalValue{fr.Eval, ret}
case callee.Name() == "runtime.typeAssert":
actualTypeInt := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
assertedType := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
if actualTypeInt.IsAConstantExpr().IsNil() || actualTypeInt.Opcode() != llvm.PtrToInt {
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode in runtime.typeAssert to be a ptrtoint"))
}
actualType := actualTypeInt.Operand(0)
if actualType.IsAConstant().IsNil() || assertedType.IsAConstant().IsNil() {
return nil, nil, fr.errorAt(inst, errors.New("interp: unimplemented: type assert with non-constant interface value"))
}
assertOk := uint64(0)
if llvm.ConstExtractValue(actualType.Initializer(), []uint32{0}) == assertedType {
assertOk = 1
}
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), assertOk, false)}
case callee.Name() == "runtime.interfaceImplements":
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
return nil, nil, fr.errorAt(inst, "interp: expected typecode to be a ptrtoint")
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode to be a ptrtoint"))
}
typecode = typecode.Operand(0)
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
return nil, nil, fr.errorAt(inst, "interp: expected method set in runtime.interfaceImplements to be a constant gep")
return nil, nil, fr.errorAt(inst, errors.New("interp: expected method set in runtime.interfaceImplements to be a constant gep"))
}
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
return nil, nil, fr.errorAt(inst, "interp: expected method set to be a constant gep")
return nil, nil, fr.errorAt(inst, errors.New("interp: expected method set to be a constant gep"))
}
methodSet = methodSet.Operand(0).Initializer()
@@ -529,6 +569,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// interpret anyway and hope for the best.
ret, err = fr.function(callee, params, indent+" ")
if err != nil {
// Record this function call in the backtrace.
err.Traceback = append(err.Traceback, ErrorLine{
Pos: getPosition(inst),
Inst: inst,
})
return nil, nil, err
}
}
@@ -547,7 +592,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = fr.getValue(newValue)
} else {
if len(indices) != 1 {
return nil, nil, fr.errorAt(inst, "interp: cannot handle extractvalue with not exactly 1 index")
return nil, nil, fr.errorAt(inst, errors.New("interp: cannot handle extractvalue with not exactly 1 index"))
}
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateExtractValue(agg.Underlying, int(indices[0]), inst.Name())}
}
@@ -560,10 +605,22 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = &LocalValue{fr.Eval, newValue}
} else {
if len(indices) != 1 {
return nil, nil, fr.errorAt(inst, "interp: cannot handle insertvalue with not exactly 1 index")
return nil, nil, fr.errorAt(inst, errors.New("interp: cannot handle insertvalue with not exactly 1 index"))
}
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateInsertValue(agg.Underlying, val.Value(), int(indices[0]), inst.Name())}
}
case !inst.IsASelectInst().IsNil():
// var result T
// if cond {
// result = x
// } else {
// result = y
// }
// return result
cond := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
x := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
y := fr.getLocal(inst.Operand(2)).(*LocalValue).Underlying
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSelect(cond, x, y, "")}
case !inst.IsAReturnInst().IsNil() && inst.OperandsCount() == 0:
return nil, nil, nil // ret void
@@ -573,17 +630,17 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
// conditional branch (if/then/else)
cond := fr.getLocal(inst.Operand(0)).Value()
if cond.Type() != fr.Mod.Context().Int1Type() {
return nil, nil, fr.errorAt(inst, "expected an i1 in a branch instruction")
return nil, nil, fr.errorAt(inst, errors.New("expected an i1 in a branch instruction"))
}
thenBB := inst.Operand(1)
elseBB := inst.Operand(2)
if !cond.IsAInstruction().IsNil() {
return nil, nil, fr.errorAt(inst, "interp: branch on a non-constant")
return nil, nil, fr.errorAt(inst, errors.New("interp: branch on a non-constant"))
}
if !cond.IsAConstantExpr().IsNil() {
// This may happen when the instruction builder could not
// const-fold some instructions.
return nil, nil, fr.errorAt(inst, "interp: branch on a non-const-propagated constant expression")
return nil, nil, fr.errorAt(inst, errors.New("interp: branch on a non-const-propagated constant expression"))
}
switch cond {
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
@@ -591,7 +648,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
return nil, []llvm.Value{elseBB}, nil // else
default:
return nil, nil, fr.errorAt(inst, "branch was not true or false")
return nil, nil, fr.errorAt(inst, errors.New("branch was not true or false"))
}
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
// unconditional branch (goto)
+1 -1
View File
@@ -97,7 +97,7 @@ func Run(mod llvm.Module, debug bool) error {
// function interprets the given function. The params are the function params
// and the indent is the string indentation to use when dumping all interpreted
// instructions.
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, error) {
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, *Error) {
fr := frame{
evalPackage: e,
fn: fn,
+1
View File
@@ -15,6 +15,7 @@ func TestInterp(t *testing.T) {
"slice-copy",
"consteval",
"map",
"interface",
} {
name := name // make tc local to this closure
t.Run(name, func(t *testing.T) {
+11 -2
View File
@@ -1,6 +1,7 @@
package interp
import (
"errors"
"strings"
"tinygo.org/x/go-llvm"
@@ -40,7 +41,7 @@ type sideEffectResult struct {
// hasSideEffects scans this function and all descendants, recursively. It
// returns whether this function has side effects and if it does, which globals
// it mentions anywhere in this function or any called functions.
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error) {
name := fn.Name()
switch {
case name == "runtime.alloc":
@@ -51,6 +52,8 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime._panic":
return &sideEffectResult{severity: sideEffectLimited}, nil
case name == "runtime.typeAssert":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.sliceCopy":
@@ -97,7 +100,7 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
switch inst.InstructionOpcode() {
case llvm.IndirectBr, llvm.Invoke:
// Not emitted by the compiler.
return nil, e.errorAt(inst, "unknown instructions")
return nil, e.errorAt(inst, errors.New("unknown instructions"))
case llvm.Call:
child := inst.CalledValue()
if !child.IsAInlineAsm().IsNil() {
@@ -118,6 +121,12 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
if child.IsDeclaration() {
// External function call. Assume only limited side effects
// (no affected globals, etc.).
switch child.Name() {
case "runtime.typeAssert":
continue // implemented in interp
case "runtime.interfaceImplements":
continue // implemented in interp
}
if e.hasLocalSideEffects(dirtyLocals, inst) {
result.updateSeverity(sideEffectLimited)
}
+2
View File
@@ -23,6 +23,8 @@ var scanTestTable = []struct {
{"callFunctionPointer", sideEffectAll, []string{"functionPointer"}},
{"getDirtyPointer", sideEffectLimited, nil},
{"storeToPointer", sideEffectLimited, nil},
{"callTypeAssert", sideEffectNone, nil},
{"callInterfaceImplements", sideEffectNone, nil},
}
func TestScan(t *testing.T) {
+28
View File
@@ -0,0 +1,28 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i64 }
%runtime.interfaceMethodInfo = type { i8*, i64 }
%runtime.typeInInterface = type { %runtime.typecodeID*, %runtime.interfaceMethodInfo* }
@main.v1 = global i1 0
@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0 }
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
@"typeInInterface:reflect/types.type:named:main.foo" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:named:main.foo", %runtime.interfaceMethodInfo* null }
declare i1 @runtime.typeAssert(i64, %runtime.typecodeID*, i8*, i8*)
define void @runtime.initAll() unnamed_addr {
entry:
call void @main.init()
ret void
}
define internal void @main.init() unnamed_addr {
entry:
; Test type asserts.
%typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:named:main.foo" to i64), %runtime.typecodeID* @"reflect/types.type:named:main.foo", i8* undef, i8* null)
store i1 %typecode, i1* @main.v1
ret void
}
+9
View File
@@ -0,0 +1,9 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.v1 = local_unnamed_addr global i1 true
define void @runtime.initAll() unnamed_addr {
entry:
ret void
}
+17
View File
@@ -1,6 +1,11 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
%runtime.typecodeID = type { %runtime.typecodeID*, i64 }
declare i1 @runtime.typeAssert(i64, %runtime.typecodeID*, i8*, i8*)
declare i1 @runtime.interfaceImplements(i64, i8**)
define i64 @returnsConst() {
ret i64 0
}
@@ -59,3 +64,15 @@ define i64 @callFunctionPointer() {
%result = call i64 %fp()
ret i64 %result
}
define i1 @callTypeAssert() {
; Note: parameters are not realistic.
%ok = call i1 @runtime.typeAssert(i64 0, %runtime.typecodeID* null, i8* undef, i8* null)
ret i1 %ok
}
define i1 @callInterfaceImplements() {
; Note: parameters are not realistic.
%ok = call i1 @runtime.interfaceImplements(i64 0, i8** null)
ret i1 %ok
}
+5 -1
View File
@@ -24,7 +24,11 @@ func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
}
buf := make([]byte, strLen.ZExtValue())
for i := range buf {
c := strPtr.GetElementPtr([]uint32{uint32(i)}).Load()
gep, err := strPtr.GetElementPtr([]uint32{uint32(i)})
if err != nil {
panic(err) // TODO
}
c := gep.Load()
buf[i] = byte(c.ZExtValue())
}
return buf
+14 -13
View File
@@ -3,6 +3,7 @@ package interp
// This file provides a litte bit of abstraction around LLVM values.
import (
"errors"
"strconv"
"tinygo.org/x/go-llvm"
@@ -11,13 +12,13 @@ import (
// A Value is a LLVM value with some extra methods attached for easier
// interpretation.
type Value interface {
Value() llvm.Value // returns a LLVM value
Type() llvm.Type // equal to Value().Type()
IsConstant() bool // returns true if this value is a constant value
Load() llvm.Value // dereference a pointer
Store(llvm.Value) // store to a pointer
GetElementPtr([]uint32) Value // returns an interior pointer
String() string // string representation, for debugging
Value() llvm.Value // returns a LLVM value
Type() llvm.Type // equal to Value().Type()
IsConstant() bool // returns true if this value is a constant value
Load() llvm.Value // dereference a pointer
Store(llvm.Value) // store to a pointer
GetElementPtr([]uint32) (Value, error) // returns an interior pointer
String() string // string representation, for debugging
}
// A type that simply wraps a LLVM constant value.
@@ -97,21 +98,21 @@ func (v *LocalValue) Store(value llvm.Value) {
}
// GetElementPtr returns a GEP when the underlying value is of pointer type.
func (v *LocalValue) GetElementPtr(indices []uint32) Value {
func (v *LocalValue) GetElementPtr(indices []uint32) (Value, error) {
if !v.Underlying.IsAGlobalVariable().IsNil() {
int32Type := v.Underlying.Type().Context().Int32Type()
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
return &LocalValue{v.Eval, gep}
return &LocalValue{v.Eval, gep}, nil
}
if !v.Underlying.IsAConstantExpr().IsNil() {
switch v.Underlying.Opcode() {
case llvm.GetElementPtr, llvm.IntToPtr, llvm.BitCast:
int32Type := v.Underlying.Type().Context().Int32Type()
llvmIndices := getLLVMIndices(int32Type, indices)
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}, nil
}
}
panic("interp: unknown GEP")
return nil, errors.New("interp: unknown GEP")
}
// stripPointerCasts removes all const bitcasts from pointer values, if there
@@ -309,8 +310,8 @@ func (v *MapValue) Store(value llvm.Value) {
// GetElementPtr panics: maps are of reference type so their (interior)
// addresses cannot be calculated.
func (v *MapValue) GetElementPtr(indices []uint32) Value {
panic("interp: GEP on a map")
func (v *MapValue) GetElementPtr(indices []uint32) (Value, error) {
return nil, errors.New("interp: GEP on a map")
}
// PutString does a map assign operation, assuming that the map is of type
+7 -28
View File
@@ -27,14 +27,13 @@ type Program struct {
// Function or method.
type Function struct {
*ssa.Function
LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export, go:interrupt
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
interrupt bool // go:interrupt
inline InlineType // go:inline
LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export
exported bool // go:export
nobounds bool // go:nobounds
flag bool // used by dead code elimination
inline InlineType // go:inline
}
// Interface type that is at some point used in a type assert (to check whether
@@ -243,18 +242,6 @@ func (f *Function) parsePragmas() {
f.inline = InlineHint
case "//go:noinline":
f.inline = InlineNone
case "//go:interrupt":
if len(parts) != 2 {
continue
}
name := parts[1]
if strings.HasSuffix(name, "_vect") {
// AVR vector naming
name = "__vector_" + name[:len(name)-5]
}
f.linkName = name
f.exported = true
f.interrupt = true
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
@@ -288,14 +275,6 @@ func (f *Function) IsExported() bool {
return f.exported || f.CName() != ""
}
// Return true for functions annotated with //go:interrupt. The function name is
// already customized in LinkName() to hook up in the interrupt vector.
//
// On some platforms (like AVR), interrupts need a special compiler flag.
func (f *Function) IsInterrupt() bool {
return f.interrupt
}
// Return the inline directive of this function.
func (f *Function) Inline() InlineType {
return f.inline
+14 -3
View File
@@ -1,6 +1,7 @@
package ir
import (
"errors"
"go/types"
"golang.org/x/tools/go/ssa"
@@ -58,7 +59,7 @@ func signature(sig *types.Signature) string {
// Simple pass that removes dead code. This pass makes later analysis passes
// more useful.
func (p *Program) SimpleDCE() {
func (p *Program) SimpleDCE() error {
// Unmark all functions.
for _, f := range p.Functions {
f.flag = false
@@ -66,13 +67,21 @@ func (p *Program) SimpleDCE() {
// Initial set of live functions. Include main.main, *.init and runtime.*
// functions.
main := p.mainPkg.Members["main"].(*ssa.Function)
main, ok := p.mainPkg.Members["main"].(*ssa.Function)
if !ok {
if p.mainPkg.Members["main"] == nil {
return errors.New("function main is undeclared in the main package")
} else {
return errors.New("cannot declare main - must be func")
}
}
runtimePkg := p.Program.ImportedPackage("runtime")
mathPkg := p.Program.ImportedPackage("math")
taskPkg := p.Program.ImportedPackage("internal/task")
p.GetFunction(main).flag = true
worklist := []*ssa.Function{main}
for _, f := range p.Functions {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || (f.Pkg == mathPkg && f.Pkg != nil) {
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
if f.flag {
continue
}
@@ -135,4 +144,6 @@ func (p *Program) SimpleDCE() {
}
}
p.Functions = livefunctions
return nil
}
Submodule
+1
Submodule lib/picolibc added at 80528c684b
View File
Submodule
+1
Submodule lib/wasi-libc added at a280fead2a
+1 -1
View File
@@ -423,7 +423,7 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
if len(p.CgoFiles) != 0 {
cflags := append(p.CFlags, "-I"+p.Package.Dir)
if p.ClangHeaders != "" {
cflags = append(cflags, "-I"+p.ClangHeaders)
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders)
}
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags)
if errs != nil {
+166 -79
View File
@@ -23,8 +23,9 @@ import (
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/interp"
"github.com/tinygo-org/tinygo/loader"
"tinygo.org/x/go-llvm"
serial "go.bug.st/serial.v1"
"go.bug.st/serial"
)
// commandError is an error type to wrap os/exec.Command errors. This provides
@@ -50,6 +51,17 @@ func moveFile(src, dst string) error {
}
// Failed to move, probably a different filesystem.
// Do a copy + remove.
err = copyFile(src, dst)
if err != nil {
return err
}
return os.Remove(src)
}
// copyFile copies the given file from src to dst. It copies first to a .tmp
// file which is then moved 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
@@ -145,14 +157,6 @@ func Test(pkgName string, options *compileopts.Options) error {
// Flash builds and flashes the built binary to the given serial port.
func Flash(pkgName, port string, options *compileopts.Options) error {
if port == "" {
var err error
port, err = getDefaultPort()
if err != nil {
return err
}
}
config, err := builder.NewConfig(options)
if err != nil {
return err
@@ -192,6 +196,14 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return builder.Build(pkgName, fileExt, config, func(tmppath string) error {
// do we need port reset to put MCU into bootloader mode?
if config.Target.PortReset == "true" {
if port == "" {
var err error
port, err = getDefaultPort()
if err != nil {
return err
}
}
err := touchSerialPortAt1200bps(port)
if err != nil {
return &commandError{"failed to reset port", tmppath, err}
@@ -207,6 +219,15 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
flashCmd := config.Target.FlashCommand
fileToken := "{" + fileExt[1:] + "}"
flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1)
if port == "" && strings.Contains(flashCmd, "{port}") {
var err error
port, err = getDefaultPort()
if err != nil {
return err
}
}
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
// Execute the command.
@@ -288,17 +309,23 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
gdbInterface, openocdInterface := config.Programmer()
switch gdbInterface {
case "msd", "command", "":
if openocdInterface != "" && config.Target.OpenOCDTarget != "" {
gdbInterface = "openocd"
}
if len(config.Target.Emulator) != 0 {
// Assume QEMU as an emulator.
gdbInterface = "qemu"
if config.Target.Emulator[0] == "mgba" {
gdbInterface = "mgba"
} else {
gdbInterface = "qemu"
}
} else if openocdInterface != "" && config.Target.OpenOCDTarget != "" {
gdbInterface = "openocd"
} else if config.Target.JLinkDevice != "" {
gdbInterface = "jlink"
}
}
// Run the GDB server, if necessary.
var gdbCommands []string
var daemon *exec.Cmd
switch gdbInterface {
case "native":
// Run GDB directly.
@@ -310,7 +337,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
if err != nil {
return err
}
daemon := exec.Command("openocd", args...)
daemon = exec.Command("openocd", args...)
if ocdOutput {
// Make it clear which output is from the daemon.
w := &ColorWriter{
@@ -321,40 +348,58 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
daemon.Stdout = w
daemon.Stderr = w
}
// Make sure the daemon doesn't receive Ctrl-C that is intended for
// GDB (to break the currently executing program).
setCommandAsDaemon(daemon)
// Start now, and kill it on exit.
daemon.Start()
defer func() {
daemon.Process.Signal(os.Interrupt)
// Maybe we should send a .Kill() after x seconds?
daemon.Wait()
}()
case "jlink":
gdbCommands = append(gdbCommands, "target remote :2331", "load", "monitor reset halt")
// We need a separate debugging daemon for on-chip debugging.
daemon = exec.Command("JLinkGDBServer", "-device", config.Target.JLinkDevice)
if ocdOutput {
// Make it clear which output is from the daemon.
w := &ColorWriter{
Out: os.Stderr,
Prefix: "jlink: ",
Color: TermColorYellow,
}
daemon.Stdout = w
daemon.Stderr = w
}
case "qemu":
gdbCommands = append(gdbCommands, "target remote :1234")
// Run in an emulator.
args := append(config.Target.Emulator[1:], tmppath, "-s", "-S")
daemon := exec.Command(config.Target.Emulator[0], args...)
daemon = exec.Command(config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
case "mgba":
gdbCommands = append(gdbCommands, "target remote :2345")
// Run in an emulator.
args := append(config.Target.Emulator[1:], tmppath, "-g")
daemon = exec.Command(config.Target.Emulator[0], args...)
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
case "msd":
return errors.New("gdb is not supported for drag-and-drop programmable devices")
default:
return fmt.Errorf("gdb is not supported with interface %#v", gdbInterface)
}
if daemon != nil {
// Make sure the daemon doesn't receive Ctrl-C that is intended for
// GDB (to break the currently executing program).
setCommandAsDaemon(daemon)
// Start now, and kill it on exit.
daemon.Start()
err = daemon.Start()
if err != nil {
return &commandError{"failed to run", daemon.Path, err}
}
defer func() {
daemon.Process.Signal(os.Interrupt)
// Maybe we should send a .Kill() after x seconds?
daemon.Wait()
}()
case "msd":
return errors.New("gdb is not supported for drag-and-drop programmable devices")
default:
return fmt.Errorf("gdb is not supported with interface %#v", gdbInterface)
}
// Ignore Ctrl-C, it must be passed on to GDB.
@@ -428,16 +473,22 @@ func Run(pkgName string, options *compileopts.Options) error {
})
}
func touchSerialPortAt1200bps(port string) error {
// Open port
p, err := serial.Open(port, &serial.Mode{BaudRate: 1200})
if err != nil {
return fmt.Errorf("opening port: %s", err)
}
defer p.Close()
func touchSerialPortAt1200bps(port string) (err error) {
retryCount := 3
for i := 0; i < retryCount; i++ {
// Open port
p, e := serial.Open(port, &serial.Mode{BaudRate: 1200})
if e != nil {
time.Sleep(1 * time.Second)
err = e
continue
}
defer p.Close()
p.SetDTR(false)
return nil
p.SetDTR(false)
return nil
}
return fmt.Errorf("opening port: %s", err)
}
func flashUF2UsingMSD(volume, tmppath string) error {
@@ -542,7 +593,6 @@ func parseSize(s string) (int64, error) {
}
// getDefaultPort returns the default serial port depending on the operating system.
// Currently only supports macOS and Linux.
func getDefaultPort() (port string, err error) {
var portPath string
switch runtime.GOOS {
@@ -564,7 +614,7 @@ func getDefaultPort() (port string, err error) {
}
if out.String() == "No Instance(s) Available." {
return "", errors.New("unable to locate a USB device to be flashed")
return "", errors.New("no serial ports available")
}
for _, line := range strings.Split(out.String(), "\n") {
@@ -575,7 +625,7 @@ func getDefaultPort() (port string, err error) {
}
}
}
return "", errors.New("unable to locate a USB device to be flashed")
return "", errors.New("unable to locate a serial port")
default:
return "", errors.New("unable to search for a default USB device to be flashed on this OS")
}
@@ -585,7 +635,7 @@ func getDefaultPort() (port string, err error) {
return "", err
}
if d == nil {
return "", errors.New("unable to locate a USB device to be flashed")
return "", errors.New("unable to locate a serial port")
}
return d[0], nil
@@ -608,38 +658,49 @@ func usage() {
flag.PrintDefaults()
}
// printCompilerError prints compiler errors using the provided logger function
// (similar to fmt.Println).
//
// There is one exception: interp errors may print to stderr unconditionally due
// to limitations in the LLVM bindings.
func printCompilerError(logln func(...interface{}), err error) {
switch err := err.(type) {
case types.Error, scanner.Error:
logln(err)
case *interp.Error:
logln("#", err.ImportPath)
logln(err.Error())
if !err.Inst.IsNil() {
err.Inst.Dump()
logln()
}
if len(err.Traceback) > 0 {
logln("\ntraceback:")
for _, line := range err.Traceback {
logln(line.Pos.String() + ":")
line.Inst.Dump()
logln()
}
}
case loader.Errors:
logln("#", err.Pkg.ImportPath)
for _, err := range err.Errs {
logln(err)
}
case *builder.MultiError:
for _, err := range err.Errs {
logln(err)
}
default:
logln("error:", err)
}
}
func handleCompilerError(err error) {
if err != nil {
switch err := err.(type) {
case *interp.Unsupported:
// hit an unknown/unsupported instruction
fmt.Fprintln(os.Stderr, "#", err.ImportPath)
msg := "unsupported instruction during init evaluation:"
if err.Pos.String() != "" {
msg = err.Pos.String() + " " + msg
}
fmt.Fprintln(os.Stderr, msg)
err.Inst.Dump()
fmt.Fprintln(os.Stderr)
case types.Error, scanner.Error:
fmt.Fprintln(os.Stderr, err)
case interp.Error:
fmt.Fprintln(os.Stderr, "#", err.ImportPath)
for _, err := range err.Errs {
fmt.Fprintln(os.Stderr, err)
}
case loader.Errors:
fmt.Fprintln(os.Stderr, "#", err.Pkg.ImportPath)
for _, err := range err.Errs {
fmt.Fprintln(os.Stderr, err)
}
case *builder.MultiError:
for _, err := range err.Errs {
fmt.Fprintln(os.Stderr, err)
}
default:
fmt.Fprintln(os.Stderr, "error:", err)
}
printCompilerError(func(args ...interface{}) {
fmt.Fprintln(os.Stderr, args...)
}, err)
os.Exit(1)
}
}
@@ -647,7 +708,7 @@ func handleCompilerError(err error) {
func main() {
outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
scheduler := flag.String("scheduler", "", "which scheduler to use (coroutines, tasks)")
printIR := flag.Bool("printir", false, "print LLVM IR")
@@ -672,6 +733,18 @@ func main() {
}
command := os.Args[1]
// Early command processing, before commands are interpreted by the Go flag
// library.
switch command {
case "clang", "ld.lld", "wasm-ld":
err := builder.RunTool(command, os.Args[2:]...)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
os.Exit(0)
}
flag.CommandLine.Parse(os.Args[2:])
options := &compileopts.Options{
Target: *target,
@@ -732,7 +805,7 @@ func main() {
}
err := Build(pkgName, *outpath, options)
handleCompilerError(err)
case "build-builtins":
case "build-library":
// Note: this command is only meant to be used while making a release!
if *outpath == "" {
fmt.Fprintln(os.Stderr, "No output filename supplied (-o).")
@@ -742,10 +815,24 @@ func main() {
if *target == "" {
fmt.Fprintln(os.Stderr, "No target (-target).")
}
err := builder.CompileBuiltins(*target, func(path string) error {
return moveFile(path, *outpath)
})
if flag.NArg() != 1 {
fmt.Fprintf(os.Stderr, "Build-library only accepts exactly one library name as argument, %d given\n", flag.NArg())
usage()
os.Exit(1)
}
var lib *builder.Library
switch name := flag.Arg(0); name {
case "compiler-rt":
lib = &builder.CompilerRT
case "picolibc":
lib = &builder.Picolibc
default:
fmt.Fprintf(os.Stderr, "Unknown library: %s\n", name)
os.Exit(1)
}
path, err := lib.Load(*target)
handleCompilerError(err)
copyFile(path, *outpath)
case "flash", "gdb":
if *outpath != "" {
fmt.Fprintln(os.Stderr, "Output cannot be specified with the flash command.")
@@ -822,7 +909,7 @@ func main() {
if s, err := builder.GorootVersionString(goenv.Get("GOROOT")); err == nil {
goversion = s
}
fmt.Printf("tinygo version %s %s/%s (using go version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion)
fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version)
case "env":
if flag.NArg() == 0 {
// Show all environment variables.
+50 -28
View File
@@ -6,18 +6,21 @@ package main
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"testing"
"time"
"github.com/tinygo-org/tinygo/builder"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/loader"
"github.com/tinygo-org/tinygo/goenv"
)
const TESTDATA = "testdata"
@@ -55,6 +58,14 @@ func TestCompiler(t *testing.T) {
runPlatTests("cortex-m-qemu", matches, t)
})
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
// Note: running only on Windows and macOS because Linux (as of 2020)
// usually has an outdated QEMU version that doesn't support RISC-V yet.
t.Run("EmulatedRISCV", func(t *testing.T) {
runPlatTests("riscv-qemu", matches, t)
})
}
if runtime.GOOS == "linux" {
t.Run("ARMLinux", func(t *testing.T) {
runPlatTests("arm--linux-gnueabihf", matches, t)
@@ -62,9 +73,20 @@ func TestCompiler(t *testing.T) {
t.Run("ARM64Linux", func(t *testing.T) {
runPlatTests("aarch64--linux-gnu", matches, t)
})
t.Run("WebAssembly", func(t *testing.T) {
runPlatTests("wasm", matches, t)
})
goVersion, err := builder.GorootVersionString(goenv.Get("GOROOT"))
if err != nil {
t.Error("could not get Go version:", err)
return
}
minorVersion := strings.Split(goVersion, ".")[1]
if minorVersion != "13" {
// WebAssembly tests fail on Go 1.13, so skip them there. Versions
// below that are also not supported but still seem to pass, so
// include them in the tests for now.
t.Run("WebAssembly", func(t *testing.T) {
runPlatTests("wasm", matches, t)
})
}
}
}
@@ -72,22 +94,7 @@ func runPlatTests(target string, matches []string, t *testing.T) {
t.Parallel()
for _, path := range matches {
switch {
case target == "wasm":
// testdata/gc.go is known not to work on WebAssembly
if path == filepath.Join("testdata", "gc.go") {
continue
}
case target == "":
// run all tests on host
case target == "cortex-m-qemu":
// all tests are supported
default:
// cross-compilation of cgo is not yet supported
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
continue
}
}
path := path // redefine to avoid race condition
t.Run(filepath.Base(path), func(t *testing.T) {
t.Parallel()
@@ -146,13 +153,7 @@ func runTest(path, target string, t *testing.T) {
binary := filepath.Join(tmpdir, "test")
err = runBuild("./"+path, binary, config)
if err != nil {
if errLoader, ok := err.(loader.Errors); ok {
for _, err := range errLoader.Errs {
t.Log("failed to build:", err)
}
} else {
t.Log("failed to build:", err)
}
printCompilerError(t.Log, err)
t.Fail()
return
}
@@ -183,7 +184,7 @@ func runTest(path, target string, t *testing.T) {
}
go func() {
// Terminate the process if it runs too long.
timer := time.NewTimer(1 * time.Second)
timer := time.NewTimer(10 * time.Second)
select {
case <-runComplete:
timer.Stop()
@@ -232,3 +233,24 @@ func runTest(path, target string, t *testing.T) {
t.Fail()
}
}
// 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 := builder.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())
}
+3 -1
View File
@@ -35,6 +35,8 @@ import (
"unsafe"
)
var errCycleCountTooLarge = errors.New("requested cycle count is too large, overflows 24 bit counter")
// Run the given assembly code. The code will be marked as having side effects,
// as it doesn't produce output and thus would normally be eliminated by the
// optimizer.
@@ -232,7 +234,7 @@ func SetupSystemTimer(cyclecount uint32) error {
}
if cyclecount&SYST_RVR_RELOAD_Msk != cyclecount {
// The cycle refresh register is only 24 bits wide. The user-specified value will overflow.
return errors.New("requested cycle count is too large, overflows 24 bit counter")
return errCycleCountTooLarge
}
// set refresh count
+277
View File
@@ -0,0 +1,277 @@
package riscv
// This file lists constants for CSR operations and defines methods on CSRs that
// are implemented as compiler intrinsics.
// CSR constants are used for use in CSR (Control and Status Register) compiler
// intrinsics.
type CSR int16
// Get returns the value of the given CSR.
func (csr CSR) Get() uintptr
// Set stores a new value in the given CSR.
func (csr CSR) Set(uintptr)
// SetBits atomically sets the given bits in this ISR and returns the old value.
func (csr CSR) SetBits(uintptr) uintptr
// ClearBits atomically clears the given bits in this ISR and returns the old
// value.
func (csr CSR) ClearBits(uintptr) uintptr
// CSR values defined in the RISC-V privileged specification. Not all values may
// be available on any given chip.
//
// Source: https://github.com/riscv/riscv-isa-manual/blob/riscv-priv-1.10/src/priv-csrs.tex
const (
// User Trap Setup
USTATUS CSR = 0x000 // User status register.
UIE CSR = 0x004 // User interrupt-enable register.
UTVEC CSR = 0x005 // User trap handler base address.
// User Trap Handling
USCRATCH CSR = 0x040 // Scratch register for user trap handlers.
UEPC CSR = 0x041 // User exception program counter.
UCAUSE CSR = 0x042 // User trap cause.
UTVAL CSR = 0x043 // User bad address or instruction.
UIP CSR = 0x044 // User interrupt pending.
// User Floating-Point CSRs
FFLAGS CSR = 0x001 // Floating-Point Accrued Exceptions.
FRM CSR = 0x002 // Floating-Point Dynamic Rounding Mode.
FCSR CSR = 0x003 // Floating-Point Control and Status
// User Counter/Timers
CYCLE CSR = 0xC00 // Cycle counter for RDCYCLE instruction.
TIME CSR = 0xC01 // Timer for RDTIME instruction.
INSTRET CSR = 0xC02 // Instructions-retired counter for RDINSTRET instruction.
HPMCOUNTER3 CSR = 0xC03 // Performance-monitoring counter 3.
HPMCOUNTER4 CSR = 0xC04 // Performance-monitoring counter 4.
HPMCOUNTER5 CSR = 0xC05 // Performance-monitoring counter 5.
HPMCOUNTER6 CSR = 0xC06 // Performance-monitoring counter 6.
HPMCOUNTER7 CSR = 0xC07 // Performance-monitoring counter 7.
HPMCOUNTER8 CSR = 0xC08 // Performance-monitoring counter 8.
HPMCOUNTER9 CSR = 0xC09 // Performance-monitoring counter 9.
HPMCOUNTER10 CSR = 0xC0A // Performance-monitoring counter 10.
HPMCOUNTER11 CSR = 0xC0B // Performance-monitoring counter 11.
HPMCOUNTER12 CSR = 0xC0C // Performance-monitoring counter 12.
HPMCOUNTER13 CSR = 0xC0D // Performance-monitoring counter 13.
HPMCOUNTER14 CSR = 0xC0E // Performance-monitoring counter 14.
HPMCOUNTER15 CSR = 0xC0F // Performance-monitoring counter 15.
HPMCOUNTER16 CSR = 0xC10 // Performance-monitoring counter 16.
HPMCOUNTER17 CSR = 0xC11 // Performance-monitoring counter 17.
HPMCOUNTER18 CSR = 0xC12 // Performance-monitoring counter 18.
HPMCOUNTER19 CSR = 0xC13 // Performance-monitoring counter 19.
HPMCOUNTER20 CSR = 0xC14 // Performance-monitoring counter 20.
HPMCOUNTER21 CSR = 0xC15 // Performance-monitoring counter 21.
HPMCOUNTER22 CSR = 0xC16 // Performance-monitoring counter 22.
HPMCOUNTER23 CSR = 0xC17 // Performance-monitoring counter 23.
HPMCOUNTER24 CSR = 0xC18 // Performance-monitoring counter 24.
HPMCOUNTER25 CSR = 0xC19 // Performance-monitoring counter 25.
HPMCOUNTER26 CSR = 0xC1A // Performance-monitoring counter 26.
HPMCOUNTER27 CSR = 0xC1B // Performance-monitoring counter 27.
HPMCOUNTER28 CSR = 0xC1C // Performance-monitoring counter 28.
HPMCOUNTER29 CSR = 0xC1D // Performance-monitoring counter 29.
HPMCOUNTER30 CSR = 0xC1E // Performance-monitoring counter 30.
HPMCOUNTER31 CSR = 0xC1F // Performance-monitoring counter 31.
CYCLEH CSR = 0xC80 // Upper 32 bits of CYCLE, RV32I only.
TIMEH CSR = 0xC81 // Upper 32 bits of TIME, RV32I only.
INSTRETH CSR = 0xC82 // Upper 32 bits of INSTRET, RV32I only.
HPMCOUNTER3H CSR = 0xC83 // Upper 32 bits of HPMCOUNTER3, RV32I only.
HPMCOUNTER4H CSR = 0xC84 // Upper 32 bits of HPMCOUNTER4, RV32I only.
HPMCOUNTER5H CSR = 0xC85 // Upper 32 bits of HPMCOUNTER5, RV32I only.
HPMCOUNTER6H CSR = 0xC86 // Upper 32 bits of HPMCOUNTER6, RV32I only.
HPMCOUNTER7H CSR = 0xC87 // Upper 32 bits of HPMCOUNTER7, RV32I only.
HPMCOUNTER8H CSR = 0xC88 // Upper 32 bits of HPMCOUNTER8, RV32I only.
HPMCOUNTER9H CSR = 0xC89 // Upper 32 bits of HPMCOUNTER9, RV32I only.
HPMCOUNTER10H CSR = 0xC8A // Upper 32 bits of HPMCOUNTER10, RV32I only.
HPMCOUNTER11H CSR = 0xC8B // Upper 32 bits of HPMCOUNTER11, RV32I only.
HPMCOUNTER12H CSR = 0xC8C // Upper 32 bits of HPMCOUNTER12, RV32I only.
HPMCOUNTER13H CSR = 0xC8D // Upper 32 bits of HPMCOUNTER13, RV32I only.
HPMCOUNTER14H CSR = 0xC8E // Upper 32 bits of HPMCOUNTER14, RV32I only.
HPMCOUNTER15H CSR = 0xC8F // Upper 32 bits of HPMCOUNTER15, RV32I only.
HPMCOUNTER16H CSR = 0xC90 // Upper 32 bits of HPMCOUNTER16, RV32I only.
HPMCOUNTER17H CSR = 0xC91 // Upper 32 bits of HPMCOUNTER17, RV32I only.
HPMCOUNTER18H CSR = 0xC92 // Upper 32 bits of HPMCOUNTER18, RV32I only.
HPMCOUNTER19H CSR = 0xC93 // Upper 32 bits of HPMCOUNTER19, RV32I only.
HPMCOUNTER20H CSR = 0xC94 // Upper 32 bits of HPMCOUNTER20, RV32I only.
HPMCOUNTER21H CSR = 0xC95 // Upper 32 bits of HPMCOUNTER21, RV32I only.
HPMCOUNTER22H CSR = 0xC96 // Upper 32 bits of HPMCOUNTER22, RV32I only.
HPMCOUNTER23H CSR = 0xC97 // Upper 32 bits of HPMCOUNTER23, RV32I only.
HPMCOUNTER24H CSR = 0xC98 // Upper 32 bits of HPMCOUNTER24, RV32I only.
HPMCOUNTER25H CSR = 0xC99 // Upper 32 bits of HPMCOUNTER25, RV32I only.
HPMCOUNTER26H CSR = 0xC9A // Upper 32 bits of HPMCOUNTER26, RV32I only.
HPMCOUNTER27H CSR = 0xC9B // Upper 32 bits of HPMCOUNTER27, RV32I only.
HPMCOUNTER28H CSR = 0xC9C // Upper 32 bits of HPMCOUNTER28, RV32I only.
HPMCOUNTER29H CSR = 0xC9D // Upper 32 bits of HPMCOUNTER29, RV32I only.
HPMCOUNTER30H CSR = 0xC9E // Upper 32 bits of HPMCOUNTER30, RV32I only.
HPMCOUNTER31H CSR = 0xC9F // Upper 32 bits of HPMCOUNTER31, RV32I only.
// Supervisor Trap Setup
SSTATUS CSR = 0x100 // Supervisor status register.
SEDELEG CSR = 0x102 // Supervisor exception delegation register.
SIDELEG CSR = 0x103 // Supervisor interrupt delegation register.
SIE CSR = 0x104 // Supervisor interrupt-enable register.
STVEC CSR = 0x105 // Supervisor trap handler base address.
SCOUNTEREN CSR = 0x106 // Supervisor counter enable.
// Supervisor Trap Handling
SSCRATCH CSR = 0x140 // Scratch register for supervisor trap handlers.
SEPC CSR = 0x141 // Supervisor exception program counter.
SCAUSE CSR = 0x142 // Supervisor trap cause.
STVAL CSR = 0x143 // Supervisor bad address or instruction.
SIP CSR = 0x144 // Supervisor interrupt pending.
// Supervisor Protection and Translation
SATP CSR = 0x180 // Supervisor address translation and protection.
// Machine Information Registers
MVENDORID CSR = 0xF11 // Vendor ID.
MARCHID CSR = 0xF12 // Architecture ID.
MIMPID CSR = 0xF13 // Implementation ID.
MHARTID CSR = 0xF14 // Hardware thread ID.
// Machine Trap Setup
MSTATUS CSR = 0x300 // Machine status register.
MISA CSR = 0x301 // ISA and extensions
MEDELEG CSR = 0x302 // Machine exception delegation register.
MIDELEG CSR = 0x303 // Machine interrupt delegation register.
MIE CSR = 0x304 // Machine interrupt-enable register.
MTVEC CSR = 0x305 // Machine trap-handler base address.
MCOUNTEREN CSR = 0x306 // Machine counter enable.
// Machine Trap Handling
MSCRATCH CSR = 0x340 // Scratch register for machine trap handlers.
MEPC CSR = 0x341 // Machine exception program counter.
MCAUSE CSR = 0x342 // Machine trap cause.
MTVAL CSR = 0x343 // Machine bad address or instruction.
MIP CSR = 0x344 // Machine interrupt pending.
// Machine Protection and Translation
PMPCFG0 CSR = 0x3A0 // Physical memory protection configuration.
PMPCFG1 CSR = 0x3A1 // Physical memory protection configuration, RV32 only.
PMPCFG2 CSR = 0x3A2 // Physical memory protection configuration.
PMPCFG3 CSR = 0x3A3 // Physical memory protection configuration, RV32 only.
PMPADDR0 CSR = 0x3B0 // Physical memory protection address register 0.
PMPADDR1 CSR = 0x3B1 // Physical memory protection address register 1.
PMPADDR2 CSR = 0x3B2 // Physical memory protection address register 2.
PMPADDR3 CSR = 0x3B3 // Physical memory protection address register 3.
PMPADDR4 CSR = 0x3B4 // Physical memory protection address register 4.
PMPADDR5 CSR = 0x3B5 // Physical memory protection address register 5.
PMPADDR6 CSR = 0x3B6 // Physical memory protection address register 6.
PMPADDR7 CSR = 0x3B7 // Physical memory protection address register 7.
PMPADDR8 CSR = 0x3B8 // Physical memory protection address register 8.
PMPADDR9 CSR = 0x3B9 // Physical memory protection address register 9.
PMPADDR10 CSR = 0x3BA // Physical memory protection address register 10.
PMPADDR11 CSR = 0x3BB // Physical memory protection address register 11.
PMPADDR12 CSR = 0x3BC // Physical memory protection address register 12.
PMPADDR13 CSR = 0x3BD // Physical memory protection address register 13.
PMPADDR14 CSR = 0x3BE // Physical memory protection address register 14.
PMPADDR15 CSR = 0x3BF // Physical memory protection address register 15.
// Machine Counter/Timers
mcycle CSR = 0xB00 // Machine cycle counter.
minstret CSR = 0xB02 // Machine instructions-retired counter.
MHPMCOUNTER3 CSR = 0xB03 // Machine performance-monitoring counter 3.
MHPMCOUNTER4 CSR = 0xB04 // Machine performance-monitoring counter 4.
MHPMCOUNTER5 CSR = 0xB05 // Machine performance-monitoring counter 5.
MHPMCOUNTER6 CSR = 0xB06 // Machine performance-monitoring counter 6.
MHPMCOUNTER7 CSR = 0xB07 // Machine performance-monitoring counter 7.
MHPMCOUNTER8 CSR = 0xB08 // Machine performance-monitoring counter 8.
MHPMCOUNTER9 CSR = 0xB09 // Machine performance-monitoring counter 9.
MHPMCOUNTER10 CSR = 0xB0A // Machine performance-monitoring counter 10.
MHPMCOUNTER11 CSR = 0xB0B // Machine performance-monitoring counter 11.
MHPMCOUNTER12 CSR = 0xB0C // Machine performance-monitoring counter 12.
MHPMCOUNTER13 CSR = 0xB0D // Machine performance-monitoring counter 13.
MHPMCOUNTER14 CSR = 0xB0E // Machine performance-monitoring counter 14.
MHPMCOUNTER15 CSR = 0xB0F // Machine performance-monitoring counter 15.
MHPMCOUNTER16 CSR = 0xB10 // Machine performance-monitoring counter 16.
MHPMCOUNTER17 CSR = 0xB11 // Machine performance-monitoring counter 17.
MHPMCOUNTER18 CSR = 0xB12 // Machine performance-monitoring counter 18.
MHPMCOUNTER19 CSR = 0xB13 // Machine performance-monitoring counter 19.
MHPMCOUNTER20 CSR = 0xB14 // Machine performance-monitoring counter 20.
MHPMCOUNTER21 CSR = 0xB15 // Machine performance-monitoring counter 21.
MHPMCOUNTER22 CSR = 0xB16 // Machine performance-monitoring counter 22.
MHPMCOUNTER23 CSR = 0xB17 // Machine performance-monitoring counter 23.
MHPMCOUNTER24 CSR = 0xB18 // Machine performance-monitoring counter 24.
MHPMCOUNTER25 CSR = 0xB19 // Machine performance-monitoring counter 25.
MHPMCOUNTER26 CSR = 0xB1A // Machine performance-monitoring counter 26.
MHPMCOUNTER27 CSR = 0xB1B // Machine performance-monitoring counter 27.
MHPMCOUNTER28 CSR = 0xB1C // Machine performance-monitoring counter 28.
MHPMCOUNTER29 CSR = 0xB1D // Machine performance-monitoring counter 29.
MHPMCOUNTER30 CSR = 0xB1E // Machine performance-monitoring counter 30.
MHPMCOUNTER31 CSR = 0xB1F // Machine performance-monitoring counter 31.
MCYCLEH CSR = 0xB80 // Upper 32 bits of MCYCLE, RV32I only.
MINSTRETH CSR = 0xB82 // Upper 32 bits of MINSTRET, RV32I only.
MHPMCOUNTER3H CSR = 0xB83 // Upper 32 bits of MHPMCOUNTER3, RV32I only.
MHPMCOUNTER4H CSR = 0xB84 // Upper 32 bits of MHPMCOUNTER4, RV32I only.
MHPMCOUNTER5H CSR = 0xB85 // Upper 32 bits of MHPMCOUNTER5, RV32I only.
MHPMCOUNTER6H CSR = 0xB86 // Upper 32 bits of MHPMCOUNTER6, RV32I only.
MHPMCOUNTER7H CSR = 0xB87 // Upper 32 bits of MHPMCOUNTER7, RV32I only.
MHPMCOUNTER8H CSR = 0xB88 // Upper 32 bits of MHPMCOUNTER8, RV32I only.
MHPMCOUNTER9H CSR = 0xB89 // Upper 32 bits of MHPMCOUNTER9, RV32I only.
MHPMCOUNTER10H CSR = 0xB8A // Upper 32 bits of MHPMCOUNTER10, RV32I only.
MHPMCOUNTER11H CSR = 0xB8B // Upper 32 bits of MHPMCOUNTER11, RV32I only.
MHPMCOUNTER12H CSR = 0xB8C // Upper 32 bits of MHPMCOUNTER12, RV32I only.
MHPMCOUNTER13H CSR = 0xB8D // Upper 32 bits of MHPMCOUNTER13, RV32I only.
MHPMCOUNTER14H CSR = 0xB8E // Upper 32 bits of MHPMCOUNTER14, RV32I only.
MHPMCOUNTER15H CSR = 0xB8F // Upper 32 bits of MHPMCOUNTER15, RV32I only.
MHPMCOUNTER16H CSR = 0xB90 // Upper 32 bits of MHPMCOUNTER16, RV32I only.
MHPMCOUNTER17H CSR = 0xB91 // Upper 32 bits of MHPMCOUNTER17, RV32I only.
MHPMCOUNTER18H CSR = 0xB92 // Upper 32 bits of MHPMCOUNTER18, RV32I only.
MHPMCOUNTER19H CSR = 0xB93 // Upper 32 bits of MHPMCOUNTER19, RV32I only.
MHPMCOUNTER20H CSR = 0xB94 // Upper 32 bits of MHPMCOUNTER20, RV32I only.
MHPMCOUNTER21H CSR = 0xB95 // Upper 32 bits of MHPMCOUNTER21, RV32I only.
MHPMCOUNTER22H CSR = 0xB96 // Upper 32 bits of MHPMCOUNTER22, RV32I only.
MHPMCOUNTER23H CSR = 0xB97 // Upper 32 bits of MHPMCOUNTER23, RV32I only.
MHPMCOUNTER24H CSR = 0xB98 // Upper 32 bits of MHPMCOUNTER24, RV32I only.
MHPMCOUNTER25H CSR = 0xB99 // Upper 32 bits of MHPMCOUNTER25, RV32I only.
MHPMCOUNTER26H CSR = 0xB9A // Upper 32 bits of MHPMCOUNTER26, RV32I only.
MHPMCOUNTER27H CSR = 0xB9B // Upper 32 bits of MHPMCOUNTER27, RV32I only.
MHPMCOUNTER28H CSR = 0xB9C // Upper 32 bits of MHPMCOUNTER28, RV32I only.
MHPMCOUNTER29H CSR = 0xB9D // Upper 32 bits of MHPMCOUNTER29, RV32I only.
MHPMCOUNTER30H CSR = 0xB9E // Upper 32 bits of MHPMCOUNTER30, RV32I only.
MHPMCOUNTER31H CSR = 0xB9F // Upper 32 bits of MHPMCOUNTER31, RV32I only.
// Machine Counter Setup
MHPMEVENT4 CSR = 0x324 // Machine performance-monitoring event selector 4.
MHPMEVENT5 CSR = 0x325 // Machine performance-monitoring event selector 5.
MHPMEVENT6 CSR = 0x326 // Machine performance-monitoring event selector 6.
MHPMEVENT7 CSR = 0x327 // Machine performance-monitoring event selector 7.
MHPMEVENT8 CSR = 0x328 // Machine performance-monitoring event selector 8.
MHPMEVENT9 CSR = 0x329 // Machine performance-monitoring event selector 9.
MHPMEVENT10 CSR = 0x32A // Machine performance-monitoring event selector 10.
MHPMEVENT11 CSR = 0x32B // Machine performance-monitoring event selector 11.
MHPMEVENT12 CSR = 0x32C // Machine performance-monitoring event selector 12.
MHPMEVENT13 CSR = 0x32D // Machine performance-monitoring event selector 13.
MHPMEVENT14 CSR = 0x32E // Machine performance-monitoring event selector 14.
MHPMEVENT15 CSR = 0x32F // Machine performance-monitoring event selector 15.
MHPMEVENT16 CSR = 0x330 // Machine performance-monitoring event selector 16.
MHPMEVENT17 CSR = 0x331 // Machine performance-monitoring event selector 17.
MHPMEVENT18 CSR = 0x332 // Machine performance-monitoring event selector 18.
MHPMEVENT19 CSR = 0x333 // Machine performance-monitoring event selector 19.
MHPMEVENT20 CSR = 0x334 // Machine performance-monitoring event selector 20.
MHPMEVENT21 CSR = 0x335 // Machine performance-monitoring event selector 21.
MHPMEVENT22 CSR = 0x336 // Machine performance-monitoring event selector 22.
MHPMEVENT23 CSR = 0x337 // Machine performance-monitoring event selector 23.
MHPMEVENT24 CSR = 0x338 // Machine performance-monitoring event selector 24.
MHPMEVENT25 CSR = 0x339 // Machine performance-monitoring event selector 25.
MHPMEVENT26 CSR = 0x33A // Machine performance-monitoring event selector 26.
MHPMEVENT27 CSR = 0x33B // Machine performance-monitoring event selector 27.
MHPMEVENT28 CSR = 0x33C // Machine performance-monitoring event selector 28.
MHPMEVENT29 CSR = 0x33D // Machine performance-monitoring event selector 29.
MHPMEVENT30 CSR = 0x33E // Machine performance-monitoring event selector 30.
MHPMEVENT31 CSR = 0x33F // Machine performance-monitoring event selector 31.
// Debug/Trace Registers (shared with Debug Mode)
TSELECT CSR = 0x7A0 // Debug/Trace trigger register select.
TDATA1 CSR = 0x7A1 // First Debug/Trace trigger data register.
TDATA2 CSR = 0x7A2 // Second Debug/Trace trigger data register.
TDATA3 CSR = 0x7A3 // Third Debug/Trace trigger data register.
// Debug Mode Registers
DCSR CSR = 0x7B0 // Debug control and status register.
DPC CSR = 0x7B1 // Debug PC.
DSCRATCH CSR = 0x7B2 // Debug scratch register.
)
+48
View File
@@ -5,8 +5,56 @@
_start:
// Load the stack pointer.
la sp, _stack_top
// Load the globals pointer. The program will load pointers relative to this
// register, so it must be set to the right value on startup.
// See: https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register
la gp, __global_pointer$
// Jump to runtime.main
call main
.section .text.handleInterruptASM
.global handleInterruptASM
.type handleInterruptASM,@function
handleInterruptASM:
// Save and restore all registers, because the hardware only saves/restores
// the pc.
// Note: we have to do this in assembly because the "interrupt"="machine"
// attribute is broken in LLVM: https://bugs.llvm.org/show_bug.cgi?id=42984
addi sp, sp, -64
sw ra, 60(sp)
sw t0, 56(sp)
sw t1, 52(sp)
sw t2, 48(sp)
sw a0, 44(sp)
sw a1, 40(sp)
sw a2, 36(sp)
sw a3, 32(sp)
sw a4, 28(sp)
sw a5, 24(sp)
sw a6, 20(sp)
sw a7, 16(sp)
sw t3, 12(sp)
sw t4, 8(sp)
sw t5, 4(sp)
sw t6, 0(sp)
call handleInterrupt
lw t6, 0(sp)
lw t5, 4(sp)
lw t4, 8(sp)
lw t3, 12(sp)
lw a7, 16(sp)
lw a6, 20(sp)
lw a5, 24(sp)
lw a4, 28(sp)
lw a3, 32(sp)
lw a2, 36(sp)
lw a1, 40(sp)
lw a0, 44(sp)
lw t2, 48(sp)
lw t1, 52(sp)
lw t0, 56(sp)
lw ra, 60(sp)
addi sp, sp, 64
mret
+57
View File
@@ -0,0 +1,57 @@
// Hand created file. DO NOT DELETE.
// STM32FXXX (except stm32f1xx) bitfield definitions that are not
// auto-generated by gen-device-svd.go
// These apply to the stm32 families that use the MODER, OTYPE amd PUPDR
// registers for managing GPIO functionality.
// Add in other families that use the same settings, e.g. stm32f0xx, etc
// +build stm32,!stm32f103xx
package stm32
// AltFunc represents the alternate function peripherals that can be mapped to
// the GPIO ports. Since these differ by what is supported on the stm32 family
// they are defined in the more specific files
type AltFunc uint8
// Family-wide common post-reset AltFunc. This represents
// normal GPIO operation of the pins
const AF0_SYSTEM AltFunc = 0
const (
// Register values for the chip
// GPIOx_MODER
GPIOModeInput = 0
GPIOModeOutputGeneral = 1
GPIOModeOutputAltFunc = 2
GPIOModeAnalog = 3
// GPIOx_OTYPER
GPIOOutputTypePushPull = 0
GPIOOutputTypeOpenDrain = 1
// GPIOx_OSPEEDR
GPIOSpeedLow = 0
GPIOSpeedMid = 1
GPIOSpeedHigh = 2 // Note: this is also low speed on stm32f0, see RM0091
GPIOSpeedVeryHigh = 3
// GPIOx_PUPDR
GPIOPUPDRFloating = 0
GPIOPUPDRPullUp = 1
GPIOPUPDRPullDown = 2
)
// SPI prescaler values fPCLK / X
const (
SPI_PCLK_2 = 0
SPI_PCLK_4 = 1
SPI_PCLK_8 = 2
SPI_PCLK_16 = 3
SPI_PCLK_32 = 4
SPI_PCLK_64 = 5
SPI_PCLK_128 = 6
SPI_PCLK_256 = 7
)
@@ -0,0 +1,26 @@
// These are the supported alternate function numberings on the stm32f407
// +build stm32,stm32f407
// Alternate function settings on the stm32f4xx series
package stm32
const (
// Alternative peripheral pin functions
// AF0_SYSTEM is defined im the common bitfields package
AF1_TIM1_2 AltFunc = 1
AF2_TIM3_4_5 = 2
AF3_TIM8_9_10_11 = 3
AF4_I2C1_2_3 = 4
AF5_SPI1_SPI2 = 5
AF6_SPI3 = 6
AF7_USART1_2_3 = 7
AF8_USART4_5_6 = 8
AF9_CAN1_CAN2_TIM12_13_14 = 9
AF10_OTG_FS_OTG_HS = 10
AF11_ETH = 11
AF12_FSMC_SDIO_OTG_HS_1 = 12
AF13_DCMI = 13
AF14 = 14
AF15_EVENTOUT = 15
)
+1 -1
View File
@@ -17,7 +17,7 @@ func main() {
var led_state bool
//go:export SysTick_Handler
//export SysTick_Handler
func timer_isr() {
if led_state {
machine.LED.Low()
+1 -1
View File
@@ -2,7 +2,7 @@
The examples here show two different ways of using WebAssembly with TinyGo:
1. Defining and exporting functions via the `//go:export <name>` directive. See
1. Defining and exporting functions via the `//export <name>` directive. See
[the export folder](./export) for an example of this. Additionally, the Wasm
module (which has a default value of `env`) can be specified using
`//go:wasm-module <module>`.
+2 -2
View File
@@ -8,12 +8,12 @@ import (
func main() {
}
//go:export add
//export add
func add(a, b int) int {
return a + b
}
//go:export update
//export update
func update() {
document := js.Global().Get("document")
aStr := document.Call("getElementById", "a").Get("value").String()
+98
View File
@@ -0,0 +1,98 @@
package task
const asserts = false
// Queue is a FIFO container of tasks.
// The zero value is an empty queue.
type Queue struct {
head, tail *Task
}
// Push a task onto the queue.
func (q *Queue) Push(t *Task) {
if asserts && t.Next != nil {
panic("runtime: pushing a task to a queue with a non-nil Next pointer")
}
if q.tail != nil {
q.tail.Next = t
}
q.tail = t
t.Next = nil
if q.head == nil {
q.head = t
}
}
// Pop a task off of the queue.
func (q *Queue) Pop() *Task {
t := q.head
if t == nil {
return nil
}
q.head = t.Next
if q.tail == t {
q.tail = nil
}
t.Next = nil
return t
}
// Append pops the contents of another queue and pushes them onto the end of this queue.
func (q *Queue) Append(other *Queue) {
if q.head == nil {
q.head = other.head
} else {
q.tail.Next = other.head
}
q.tail = other.tail
other.head, other.tail = nil, nil
}
// Stack is a LIFO container of tasks.
// The zero value is an empty stack.
// This is slightly cheaper than a queue, so it can be preferable when strict ordering is not necessary.
type Stack struct {
top *Task
}
// Push a task onto the stack.
func (s *Stack) Push(t *Task) {
if asserts && t.Next != nil {
panic("runtime: pushing a task to a stack with a non-nil Next pointer")
}
s.top, t.Next = t, s.top
}
// Pop a task off of the stack.
func (s *Stack) Pop() *Task {
t := s.top
if t != nil {
s.top = t.Next
}
t.Next = nil
return t
}
// tail follows the chain of tasks.
// If t is nil, returns nil.
// Otherwise, returns the task in the chain where the Next field is nil.
func (t *Task) tail() *Task {
if t == nil {
return nil
}
for t.Next != nil {
t = t.Next
}
return t
}
// Queue moves the contents of the stack into a queue.
// Elements can be popped from the queue in the same order that they would be popped from the stack.
func (s *Stack) Queue() Queue {
head := s.top
s.top = nil
return Queue{
head: head,
tail: head.tail(),
}
}
+20
View File
@@ -0,0 +1,20 @@
package task
import (
"unsafe"
)
// Task is a state of goroutine for scheduling purposes.
type Task struct {
// Next is a field which can be used to make a linked list of tasks.
Next *Task
// Ptr is a field which can be used for storing a pointer.
Ptr unsafe.Pointer
// Data is a field which can be used for storing state information.
Data uint
// state is the underlying running state of the task.
state state
}
+103
View File
@@ -0,0 +1,103 @@
// +build scheduler.coroutines
package task
import (
"unsafe"
)
// rawState is an underlying coroutine state exposed by llvm.coro.
// This matches *i8 in LLVM.
type rawState uint8
//export llvm.coro.resume
func (s *rawState) resume()
type state struct{ *rawState }
//export llvm.coro.noop
func noopState() *rawState
// Resume the task until it pauses or completes.
func (t *Task) Resume() {
t.state.resume()
}
// setState is used by the compiler to set the state of the function at the beginning of a function call.
// Returns the state of the caller.
func (t *Task) setState(s *rawState) *rawState {
caller := t.state
t.state = state{s}
return caller.rawState
}
// returnTo is used by the compiler to return to the state of the caller.
func (t *Task) returnTo(parent *rawState) {
t.state = state{parent}
t.returnCurrent()
}
// returnCurrent is used by the compiler to return to the state of the caller in a case where the state is not replaced.
func (t *Task) returnCurrent() {
scheduleTask(t)
}
//go:linkname scheduleTask runtime.runqueuePushBack
func scheduleTask(*Task)
// setReturnPtr is used by the compiler to store the return buffer into the task.
// This buffer is where the return value of a function that is about to be called will be stored.
func (t *Task) setReturnPtr(buf unsafe.Pointer) {
t.Ptr = buf
}
// getReturnPtr is used by the compiler to get the return buffer stored into the task.
// This is called at the beginning of an async function, and the return is stored into this buffer immediately before resuming the caller.
func (t *Task) getReturnPtr() unsafe.Pointer {
return t.Ptr
}
// createTask returns a new task struct initialized with a no-op state.
func createTask() *Task {
return &Task{
state: state{noopState()},
}
}
// start invokes a function in a new goroutine. Calls to this are inserted by the compiler.
// The created goroutine starts running immediately.
// This is implemented inside the compiler.
func start(fn uintptr, args unsafe.Pointer)
// Current returns the current active task.
// This is implemented inside the compiler.
func Current() *Task
// Pause suspends the current running task.
// This is implemented inside the compiler.
func Pause()
type taskHolder interface {
setState(*rawState) *rawState
returnTo(*rawState)
returnCurrent()
setReturnPtr(unsafe.Pointer)
getReturnPtr() unsafe.Pointer
}
// If there are no direct references to the task methods, they will not be discovered by the compiler, and this will trigger a compiler error.
// Instantiating this interface forces discovery of these methods.
var _ = taskHolder((*Task)(nil))
func fake() {
// Hack to ensure intrinsics are discovered.
Current()
go func() {}()
Pause()
}
// OnSystemStack returns whether the caller is running on the system stack.
func OnSystemStack() bool {
// This scheduler does not do any stack switching.
return true
}
+35
View File
@@ -0,0 +1,35 @@
// +build scheduler.none
package task
import "unsafe"
//go:linkname runtimePanic runtime.runtimePanic
func runtimePanic(str string)
func Pause() {
runtimePanic("scheduler is disabled")
}
func Current() *Task {
runtimePanic("scheduler is disabled")
return nil
}
//go:noinline
func start(fn uintptr, args unsafe.Pointer) {
// The compiler will error if this is reachable.
runtimePanic("scheduler is disabled")
}
type state struct{}
func (t *Task) Resume() {
runtimePanic("scheduler is disabled")
}
// OnSystemStack returns whether the caller is running on the system stack.
func OnSystemStack() bool {
// This scheduler does not do any stack switching.
return true
}
+80
View File
@@ -0,0 +1,80 @@
// +build scheduler.tasks
package task
import "unsafe"
//go:linkname runtimePanic runtime.runtimePanic
func runtimePanic(str string)
// Stack canary, to detect a stack overflow. The number is a random number
// generated by random.org. The bit fiddling dance is necessary because
// otherwise Go wouldn't allow the cast to a smaller integer size.
const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0)))
// state is a structure which holds a reference to the state of the task.
// When the task is suspended, the registers are stored onto the stack and the stack pointer is stored into sp.
type state struct {
// sp is the stack pointer of the saved state.
// When the task is inactive, the saved registers are stored at the top of the stack.
sp uintptr
// canaryPtr points to the top word of the stack (the lowest address).
// This is used to detect stack overflows.
// When initializing the goroutine, the stackCanary constant is stored there.
// If the stack overflowed, the word will likely no longer equal stackCanary.
canaryPtr *uintptr
}
// currentTask is the current running task, or nil if currently in the scheduler.
var currentTask *Task
// Current returns the current active task.
func Current() *Task {
return currentTask
}
// Pause suspends the current task and returns to the scheduler.
// This function may only be called when running on a goroutine stack, not when running on the system stack or in an interrupt.
func Pause() {
// Check whether the canary (the lowest address of the stack) is still
// valid. If it is not, a stack overflow has occured.
if *currentTask.state.canaryPtr != stackCanary {
runtimePanic("goroutine stack overflow")
}
currentTask.state.pause()
}
// Resume the task until it pauses or completes.
// This may only be called from the scheduler.
func (t *Task) Resume() {
currentTask = t
t.state.resume()
currentTask = nil
}
// initialize the state and prepare to call the specified function with the specified argument bundle.
func (s *state) initialize(fn uintptr, args unsafe.Pointer) {
// Create a stack.
stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0)))
// Invoke architecture-specific initialization.
s.archInit(stack, fn, args)
}
//go:linkname runqueuePushBack runtime.runqueuePushBack
func runqueuePushBack(*Task)
// start creates and starts a new goroutine with the given function and arguments.
// The new goroutine is scheduled to run later.
func start(fn uintptr, args unsafe.Pointer) {
t := &Task{}
t.state.initialize(fn, args)
runqueuePushBack(t)
}
// OnSystemStack returns whether the caller is running on the system stack.
func OnSystemStack() bool {
// If there is not an active goroutine, then this must be running on the system stack.
return Current() == nil
}

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