Compare commits

...

198 Commits

Author SHA1 Message Date
Nia Waldvogel 1573826005 transform (coroutines): move any misplaced entry-block allocas to the start of the entry block before lowering 2021-09-21 20:08:30 +02:00
Nia Waldvogel ecd8c2d902 transform (coroutines): fix memory corruption for tail calls that reference stack allocations
This change fixes a bug in which `alloca` memory lifetimes would not extend past the suspend of an asynchronous tail call.
This would typically manifest as memory corruption, and could happen with or without normal suspending calls within the function.
2021-09-21 20:08:30 +02:00
Ayke van Laethem a116fd0dc6 main: release version 0.20.0 2021-09-21 18:05:21 +02:00
Ayke van Laethem a590d791bd builder: simplify running of jobs
Instead of keeping a slice of jobs to run, let the runJobs function
determine which jobs should be run by investigating all dependencies.
This has two benefits:

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

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

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

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

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

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

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

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

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

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

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

The size in numbers:

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

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

    type kv struct {
           v float32
    }

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

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

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

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

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

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

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

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

This prints just a summary:

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

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

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

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

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

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

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

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

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

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

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

    GOARCH=386 tinygo test math

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

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

    GOARCH=arm64 tinygo test math

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

For context, this is what the random_get documentation says:

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

Allows the following to succeed:

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

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

Thanks to @fgsch for a very small repro, which I've used as a regression
test.
2021-08-04 20:06:59 +02:00
soypat 98e70c9b19 machine/rp2040: add SPI support
spi working with loopback

SPI working

apply @deadprogram's suggestions

consolidate SPI board pin naming

fix up SPI configuration

add feather-rp2040 SPI pins

add arduino connect SPI pins

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    tinygo test -c math

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

Use a global variable for this purpose instead to avoid the heap
allocation. This is safe as waitForEvent may only be called outside of
interrupts.
2021-05-30 20:56:01 +02:00
sago35 22eeed2da1 qtpy: add pin for neopixels 2021-05-30 11:01:19 +02:00
Kenneth Bell cfbc9be9bf rp2040: git ignore generated device files 2021-05-29 19:56:50 +02:00
Rajiv Kanchan 722a3a5c94 add rp2040, pico
adds preliminary support (just enough to run blinky1) for the Raspberry Pi Pico board along with the rp2040 mcu.
2021-05-28 18:29:04 +02:00
deadprogram ed2db8a26d Revert "scheduler: task.Data made 64bit to avoid overflow"
This reverts commit fd8938c634.
2021-05-28 07:38:59 +02:00
Kenneth Bell fd8938c634 scheduler: task.Data made 64bit to avoid overflow 2021-05-28 00:02:46 +02:00
Kenneth Bell fa3dd41a4f stm32: Use TIM for runtime clock 2021-05-28 00:02:46 +02:00
Kenneth Bell 003c96edc0 stm32f103 (bluepill): add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell ac54302301 stm32f7: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell c43a41165a stm32l4: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell 3145c2747e stm32l0: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell 2c4b507d34 stm32l5: add pwm 2021-05-28 00:02:46 +02:00
Kenneth Bell ee167f15de stm32: add pwm for f4 series 2021-05-28 00:02:46 +02:00
Yurii Soldak 422ebeeec7 Do not disable interrupts on abort
Allows panic messages to be printed fully into serial console
2021-05-27 09:28:08 +02:00
Ayke van Laethem c93ddb630b compiler: skip context parameter when starting regular goroutine
Do not store the context parameter (which is used for closures and
function pointers) in the goroutine start parameter bundle for direct
functions that don't need a context parameter. This avoids storing the
(undef) context parameter and thus makes the IR to start a new goroutine
simpler in most cases.

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

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

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

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

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

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

These two meanings conflict, and resulted in workarounds like:

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

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

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

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

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

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

My eventual goal is the following:

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

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

This change does have an effect on code size and memory consumption.
There is often a small reduction (-8 bytes) in RAM consumption and an
increase in flash consumption.
2021-05-13 16:43:37 +02:00
sago35 1646326164 Update README.md 2021-05-12 12:22:08 +02:00
Ayke van Laethem e44cb7e519 main: version 0.18.0 2021-05-12 07:23:49 +02:00
Ayke van Laethem d1e96fd0a8 wasm: scan globals conservatively
Make the GC globals scan phase conservative instead of precise on
WebAssembly. This reduces code size at the risk of introducing some
false positives.

This is a stopgap measure to mitigate an issue with the precise scanning
of globals that doesn't track all pointers. It works for regular globals
but globals created in the interp package don't always have a type and
therefore may be missed by the AddGlobalsBitmap pass.
The same issue is present on Linux and macOS, but is not as noticeable
there.
2021-05-11 18:15:36 +02:00
Ayke van Laethem ff5d0c9886 compiler: only check for default stack size with tasks scheduler
With -scheduler=none, there are no goroutines so there is no stack size
anywhere. Therefore, don't check whether the default stack size is set.
2021-05-11 00:17:03 +02:00
Yurii Soldak d7ec9ddcd2 Discover USB ports only
This will ignore f.ex. bluetooth
2021-05-10 13:27:59 +02:00
sago35 e7c6bd3730 atsame5x: add support for CAN 2021-05-10 12:27:10 +02:00
Ayke van Laethem 9f5066aa6f runtime: use the tasks scheduler instead of coroutines
This results in smaller and likely more efficient code. It does require
some architecture specific code for each architecture, but I've kept the
amount of code as small as possible.
2021-05-09 17:40:13 +02:00
Ayke van Laethem 3b24fedf92 compiler: use wasm for tests
The next commit will change the implementation of func values on Linux
as a result of switching to a task-based scheduler. To keep the
compiler/testdata/func.go test working as expected, switch to
WebAssembly tests.
2021-05-09 17:40:13 +02:00
Ayke van Laethem 8cd2a462b9 runtime: remove the asyncScheduler constant
There is no reason to specialize this per chip as it is only ever used
for JavaScript. Not only that, it is causing confusion and is yet
another quirk to learn when porting the runtime to a new
microcontroller.
2021-05-08 23:08:12 +02:00
Ayke van Laethem 25b045d0a7 runtime: improve timers on nrf, and samd chips
This commit improves the timers on various microcontrollers to better
deal with counter wraparound. The result is a reduction in RAM size of
around 12 bytes and a small effect (sometimes positive, sometimes
negative) on flash consumption. But perhaps more importantly: getting
the current time is now interrupt-safe (it previously could result in a
race condition) and the timer will now be correct when the timer isn't
retrieved for a long duration. Before this commit, a call to `time.Now`
more than 8 minutes after the previous call could result in an incorrect
time.

For more details, see:
https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
2021-05-08 20:59:40 +02:00
Ayke van Laethem 78acbdf0d9 main: match go test output
This commit makes the output of `tinygo test` similar to that of `go
test`. It changes the following things in the process:

  * Running multiple tests in a single command is now possible. They
    aren't paralellized yet.
  * Packages with no test files won't crash TinyGo, instead it logs it
    in the same way the Go toolchain does.
2021-05-06 20:04:16 +02:00
Federico G. Schwindt 617e2791ef Add -llvm-features parameter
With this is possible to enable e.g., SIMD in WASM using -llvm-features
+simd128.  Multiple features can be specified separated by comma,
e.g., -llvm-features +simd128,+tail-call

With help from @deadprogram and @aykevl.
2021-05-06 18:07:14 +02:00
Kenneth Bell a0908ff55b compiler: openocd commands in tinygo command line 2021-05-06 15:09:41 +02:00
Ayke van Laethem 2f1f8fb075 machine: move PinMode to central location
It is always implemented exactly the same way (as an uint8) so there is
no reason to implement it in each target separately.

This also makes it easier to add some documentation to it.
2021-05-06 13:59:12 +02:00
sago35 6f61b83ad5 atsamd21: remove special handling for SPI-24mhz 2021-05-06 10:32:24 +02:00
Dan Kegel 8dfefb46d1 wasi: do not crash if argc is 0
Instead, leave args at its default value (which provides a fake argv[0] as it has for a long time).

linux and mac do not seem affected.

Fixes #1862 (tinygo apps after v0.17.0-113-g7b761fa crash if run without argv[0])
2021-05-05 19:16:28 +02:00
Ayke van Laethem 959442dc82 unix: use conservative GC by default
This commit does two things:

 1. It makes it possible to grow the heap on Linux and MacOS by
    allocating 1GB of virtual memory on startup and then slowly using it
    as necessary, when running out of available heap space.
 2. It switches the default GC to be the conservative GC (previously
    extalloc). This is good for consistency with other platforms that
    all use this same GC.

This makes the extalloc GC unused by default.
2021-05-05 17:20:15 +02:00
Ayke van Laethem c1aa152a63 unix: avoid possible heap allocation with -opt=0
This heap allocation would normally be optimized away, but with -opt=0
perhaps not. This is a problem if the conservative GC is used, because
the conservative GC needs to be initialized before use.
2021-05-05 17:20:15 +02:00
sago35 dd3d8a363a qtpy: fix i2c setting 2021-05-05 10:12:08 +02:00
sago35 bb509ec91d atsamd51, atsamd21: fix ADC.Get() value at 8bit and 10bit 2021-05-05 06:51:09 +02:00
Ayke van Laethem 944f022060 interp: support extractvalue/insertvalue with multiple operands
TinyGo doesn't emit these instructions, but they can occur as a result
of optimizations.
2021-05-04 21:21:56 +02:00
Ayke van Laethem cd517a30af transform: split interface and reflect lowering
These two passes are related, but can definitely work independently.
Which is what this change does: it splits the two passes. This should
make it easier to change these two new passes in the future.

This change now also enables slightly better testing by testing these
two passes independently. In particular, the reflect lowering pass got
some actual tests: it was barely unit-tested before.

I have verified that this doesn't really change code size, at least not
on the microbit target. Two tests do change, but in a very minor way
(and in opposite direction).
2021-05-03 20:10:49 +02:00
Olivier Fauchon 52d8655eec Patch Cleanup 2021-05-03 18:16:46 +02:00
Olivier Fauchon f5786941e5 Fix bad I2C0/I2C1 declaration 2021-05-03 18:16:46 +02:00
Ayke van Laethem 1f73941c43 ci: bump Xcode version to use macOS 10.14
The CircleCI macOS builds are failing, probably due to the old macOS
version that's used. This version (10.13 High Sierra) isn't supported
anymore on Homebrew so it seems best to me to simply bump the version.

I picked Xcode 11.1.0 because 10.3.0 is somehow triggering an error
while trying to install QEMU (the Python install fails).

Because of this newer Xcode version, I had to add an extra flag
(-isysroot) to the default command line for MacOS. The reason is that
this newer Xcode version no longer stores header files in /usr/local, an
SDK must be specified manually. With this change, the default SDK is
used.
2021-05-02 23:55:10 +02:00
Raqbit abeab51d00 Add Arduino Nano w/ New Bootloader target
Since 2018, Arduino Nanos and clones are sold with a new bootloader, which
requires programming at 115200 baud instead of the 57600 baud required
by the old one.
2021-05-01 17:09:46 +02:00
sago35 9ef75f17bf atsamd51, atsame5x: unify samd51 and same5x 2021-04-29 09:20:44 +02:00
Ayke van Laethem c3992bd77b compiler: improve position information
In many cases, position information is not stored in Go SSA instructions
because they don't exit directly in the source code. This includes
implicit type conversions, implicit returns at the end of a function,
the creation of a (hidden) slice when calling a variadic function, and
many other cases. I'm not sure where this information is supposed to
come from, but this patch takes the value (usually) from the value the
instruction refers to. This seems to work well for these implicit
conversions.

I've also added a few extra tests to the heap-to-stack transform pass,
of which one requires this improved position information.
2021-04-26 16:15:57 +02:00
Ayke van Laethem f79e66ac2e cortexm: disable FPU on Cortex-M4
On some boards the FPU is already enabled on startup, probably as part
of the bootloader. On other chips it was enabled as part of the runtime
startup code. In all these cases, enabling the FPU is currently
unsupported: the automatic stack sizing of goroutines assumes that the
processor won't need to reserve space for FPU registers. Enabling the
FPU therefore can lead to a stack overflow.

This commit either removes the code that enables the FPU, or simply
disables it in startup code. A future change should fully enable the FPU
so that operations on float32 can be performed by the FPU instead of in
software, greatly speeding up such code.
2021-04-24 18:41:40 +02:00
Ayke van Laethem 4eac212695 gen-device: add extra constants and rename them to be Go style
- Add some extra fields: FPUPresent, CPU and NVICPrioBits which may
    come in handy at a later time (and are easy to add).
  - Rename DEVICE to Device, to match Go style.

This is in preparation to the next commit, which requires the FPUPresent
flag.
2021-04-24 18:41:40 +02:00
471 changed files with 16486 additions and 5866 deletions
+21 -35
View File
@@ -157,7 +157,7 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-11-linux-v3-assert
- llvm-build-11-linux-v4-assert
- run:
name: "Build LLVM"
command: |
@@ -176,7 +176,7 @@ commands:
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-11-linux-v3-assert
key: llvm-build-11-linux-v4-assert
paths:
llvm-build
- run: make ASSERT=1
@@ -225,7 +225,7 @@ commands:
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-11-linux-v3-noassert
- llvm-build-11-linux-v4-noassert
- run:
name: "Build LLVM"
command: |
@@ -244,7 +244,7 @@ commands:
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-11-linux-v3-noassert
key: llvm-build-11-linux-v4-noassert
paths:
llvm-build
- build-wasi-libc
@@ -286,24 +286,24 @@ commands:
- run:
name: "Install dependencies"
command: |
curl https://dl.google.com/go/go1.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.16.darwin-amd64.tar.gz
curl https://dl.google.com/go/go1.17.darwin-amd64.tar.gz -o go1.17.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.17.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- install-xtensa-toolchain:
variant: "macos"
- restore_cache:
keys:
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v2-{{ checksum "go.mod" }}
- go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v3-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-11-macos-v2
- llvm-source-11-macos-v3
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-11-macos-v2
key: llvm-source-11-macos-v3
paths:
- llvm-project/clang/lib/Headers
- llvm-project/clang/include
@@ -311,7 +311,7 @@ commands:
- llvm-project/llvm/include
- restore_cache:
keys:
- llvm-build-11-macos-v3
- llvm-build-11-macos-v5
- run:
name: "Build LLVM"
command: |
@@ -327,17 +327,17 @@ commands:
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
fi
- save_cache:
key: llvm-build-11-macos-v3
key: llvm-build-11-macos-v5
paths:
llvm-build
- restore_cache:
keys:
- wasi-libc-sysroot-macos-v3
- wasi-libc-sysroot-macos-v4
- run:
name: "Build wasi-libc"
command: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-macos-v3
key: wasi-libc-sysroot-macos-v4
paths:
- lib/wasi-libc/sysroot
- run:
@@ -359,30 +359,18 @@ commands:
tinygo version
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- /go/pkg/mod
jobs:
test-llvm10-go113:
docker:
- image: circleci/golang:1.13-buster
steps:
- test-linux:
llvm: "10"
test-llvm10-go114:
docker:
- image: circleci/golang:1.14-buster
steps:
- test-linux:
llvm: "10"
test-llvm11-go115:
test-llvm10-go115:
docker:
- image: circleci/golang:1.15-buster
steps:
- test-linux:
llvm: "11"
llvm: "10"
test-llvm11-go116:
docker:
- image: circleci/golang:1.16-buster
@@ -391,17 +379,17 @@ jobs:
llvm: "11"
assert-test-linux:
docker:
- image: circleci/golang:1.14-stretch
- image: circleci/golang:1.17-stretch
steps:
- assert-test-linux
build-linux:
docker:
- image: circleci/golang:1.14-stretch
- image: circleci/golang:1.17-stretch
steps:
- build-linux
build-macos:
macos:
xcode: "10.1.0"
xcode: "11.1.0" # macOS 10.14
steps:
- build-macos
@@ -410,9 +398,7 @@ jobs:
workflows:
test-all:
jobs:
- test-llvm10-go113
- test-llvm10-go114
- test-llvm11-go115
- test-llvm10-go115
- test-llvm11-go116
- build-linux
- build-macos
+3
View File
@@ -0,0 +1,3 @@
build/
llvm-*/
@@ -0,0 +1,45 @@
name: CI for tinygo-dev docker container
on:
push:
branches: [ dev, fix-docker-llvm-build ]
jobs:
push_to_registry:
name: Push Docker image to GHCR/Docker Hub
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v2
with:
submodules: recursive
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: |
tinygo/tinygo-dev
ghcr.io/${{ github.repository }}/tinygo-dev
tags: |
type=sha,format=long
type=raw,value=latest
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Log in to Github Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+2
View File
@@ -16,6 +16,8 @@ src/device/stm32/*.go
src/device/stm32/*.s
src/device/kendryte/*.go
src/device/kendryte/*.s
src/device/rp/*.go
src/device/rp/*.s
vendor
llvm-build
llvm-project
+1 -1
View File
@@ -23,7 +23,7 @@ different guide:
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.13+)
* Go (1.15+)
* Standard build tools (gcc/clang)
* git
* CMake
+258
View File
@@ -1,3 +1,261 @@
0.20.0
---
* **command line**
- add support for Go 1.17
- improve Go version detection
- add support for the Black Magic Probe (BMP)
- add a flag for creating cpu profiles
* **compiler**
- `builder:` list libraries at the end of the linker command
- `builder:` strip debug information at link time instead of at compile time
- `builder:` add missing error check for `ioutil.TempFile()`
- `builder:` simplify running of jobs
- `compiler:` move LLVM math builtin support into the compiler
- `compiler:` move math aliases from the runtime to the compiler
- `compiler:` add aliases for many hashing packages
- `compiler:` add `*ssa.MakeSlice` bounds tests
- `compiler:` fix max possible slice
- `compiler:` add support for new language features of Go 1.17
- `compiler:` fix equally named structs in different scopes
- `compiler:` avoid zero-sized alloca in channel operations
- `interp:` don't ignore array indices for untyped objects
- `interp:` keep reverted package initializers in order
- `interp:` fix bug in compiler-time/run-time package initializers
- `loader:` fix panic in CGo files with syntax errors
- `transform:` improve GC stack slot pass to work around a bug
* **standard library**
- `crypto/rand`: switch to `arc4random_buf`
- `math:` fix `math.Max` and `math.Min`
- `math/big`: fix undefined symbols error
- `net:` add MAC address implementation
- `os:` implement `os.Executable`
- `os:` add `SEEK_SET`, `SEEK_CUR`, and `SEEK_END`
- `reflect:` add StructField.IsExported method
- `runtime:` reset heapptr to heapStart after preinit()
- `runtime:` add `subsections_via_symbols` to assembly files on darwin
- `testing:` add subset implementation of Benchmark
- `testing:` test testing package using `tinygo test`
- `testing:` add support for the `-test.v` flag
* **targets**
- `386:` bump minimum requirement to the Pentium 4
- `arm:` switch to Thumb instruction set on ARM
- `atsamd:` fix copy-paste error for atsamd21/51 calibTrim block
- `baremetal`,`wasm`: support command line params and environment variables
- `cortexm:` fix stack overflow because of unaligned stacks
- `esp32c3:` add support for the ESP32-C3 from Espressif
- `nrf52840:` fix ram size
- `nxpmk66f18:` fix a suspicious bitwise operation
- `rp2040:` add SPI support
- `rp2040:` add I2C support
- `rp2040:` add PWM implementation
- `rp2040:` add openocd configuration
- `stm32:` add support for PortMask* functions for WS2812 support
- `unix:` fix time base for time.Now()
- `unix:` check for mmap error and act accordingly
- `wasm:` override dlmalloc heap implementation from wasi-libc
- `wasm:` align heap to 16 bytes
- `wasm:` add support for the crypto/rand package
* **boards**
- add `DefaultUART` to adafruit boards
- `arduino-mkrwifi1010:` add board definition for Arduino MKR WiFi 1010
- `arduino-mkrwifi1010:` fix pin definition of `NINA_RESETN`
- `feather-nrf52:` fix pin definition of uart
- `feather-rp2040:` add pin name definition
- `gameboy-advance:` fix ROM header
- `mdbt50qrx-uf2:` add Raytac MDBT50Q-RX Dongle with TinyUF2
- `nano-rp2040:` define `NINA_SPI` and fix wifinina pins
- `teensy40:` enable hardware UART reconfiguration, fix receive watermark interrupt
0.19.0
---
* **command line**
- don't consider compile-only tests as failing
- add -test flag for `tinygo list`
- escape commands while printing them with the -x flag
- make flash-command portable and safer to use
- use `extended-remote` instead of `remote` in GDB
- detect specific serial port IDs based on USB vid/pid
- add a flag to the command line to select the serial implementation
* **compiler**
- `cgo`: improve constant parser
- `compiler`: support chained interrupt handlers
- `compiler`: add support for running a builtin in a goroutine
- `compiler`: do not emit nil checks for loading closure variables
- `compiler`: skip context parameter when starting regular goroutine
- `compiler`: refactor method names
- `compiler`: add function and global section pragmas
- `compiler`: implement `syscall.rawSyscallNoError` in inline assembly
- `interp`: ignore inline assembly in markExternal
- `interp`: fix a bug in pointer cast workaround
- `loader`: fix testing a main package
* **standard library**
- `crypto/rand`: replace this package with a TinyGo version
- `machine`: make USBCDC global a pointer
- `machine`: make UART objects pointer receivers
- `machine`: define Serial as the default output
- `net`: add initial support for net.IP
- `net`: add more net compatibility
- `os`: add stub for os.ReadDir
- `os`: add FileMode constants from Go 1.16
- `os`: add stubs required for net/http
- `os`: implement process related functions
- `reflect`: implement AppendSlice
- `reflect`: add stubs required for net/http
- `runtime`: make task.Data a 64-bit integer to avoid overflow
- `runtime`: expose memory stats
- `sync`: implement NewCond
- `syscall`: fix int type in libc version
* **targets**
- `cortexm`: do not disable interrupts on abort
- `cortexm`: bump default stack size to 2048 bytes
- `nrf`: avoid heap allocation in waitForEvent
- `nrf`: don't trigger a heap allocation in SPI.Transfer
- `nrf52840`: add support for flashing with the BOSSA tool
- `rp2040`: add support for GPIO input
- `rp2040`: add basic support for ADC
- `rp2040`: gpio and adc pin definitions
- `rp2040`: implement UART
- `rp2040`: patch elf to checksum 2nd stage boot
- `stm32`: add PWM for most chips
- `stm32`: add support for pin interrupts
- `stm32f103`: add support for PinInputPullup / PinInputPulldown
- `wasi`: remove wasm build tag
* **boards**
- `feather-rp2040`: add support for this board
- `feather-nrf52840-sense`: add board definition for this board
- `pca10059`: support flashing from Windows
- `nano-rp2040`: add this board
- `nano-33-ble`: add support for this board
- `pico`: add the Raspberry Pi Pico board with the new RP2040 chip
- `qtpy`: add pin for neopixels
- all: add definition for ws2812 for supported boards
0.18.0
---
* **command line**
- drop support for Go 1.11 and 1.12
- throw an error when no target is specified on Windows
- improve error messages in `getDefaultPort()`, support for multiple ports
- remove `-cflags` and `-ldflags` flags
- implement `-ldflags="-X ..."`
- add `-print-allocs` flag that lets you print all heap allocations
- openocd commands in tinygo command line
- add `-llvm-features` parameter
- match `go test` output
- discover USB ports only, this will ignore f.ex. bluetooth
- use physicmal path instead of cached GOROOT in function getGoroot
- add goroot for snap installs
* **compiler**
- `builder`: add support for `-opt=0`
- `builder`, `compiler`: compile and cache packages in parallel
- `builder`: run interp per package
- `builder`: cache C and assembly file outputs
- `builder`: add support for `-x` flag to print commands
- `builder`: add optsize attribute while building the package
- `builder`: run function passes per package
- `builder`: hard code Clang compiler
- `compiler`: do not use `llvm.GlobalContext()`
- `compiler`: remove SimpleDCE pass
- `compiler`: do not emit nil checks for `*ssa.Alloc` instructions
- `compiler`: merge `runtime.typecodeID` and runtime.typeInInterface
- `compiler`: do not check for impossible type asserts
- `compiler`: fix use of global context: `llvm.Int32Type()`
- `compiler`: add interface IR test
- `compiler`: fix lack of method name in interface matching
- `compiler`: fix "fragment covers entire variable" bug
- `compiler`: optimize string literals and globals
- `compiler`: decouple func lowering from interface type codes
- `compiler`: add function attributes to some runtime calls
- `compiler`: improve position information in error messages
- `cgo`: add support for CFLAGS in .c files
- `interp`: support GEP on fixed (MMIO) addresses
- `interp`: handle `(reflect.Type).Elem()`
- `interp`: add support for runtime.interfaceMethod
- `interp`: make toLLVMValue return an error instead of panicking
- `interp`: add support for switch statement
- `interp`: fix phi instruction
- `interp`: remove map support
- `interp`: support extractvalue/insertvalue with multiple operands
- `transform`: optimize string comparisons against ""
- `transform`: optimize `reflect.Type` `Implements()` method
- `transform`: fix bug in interface lowering when signatures are renamed
- `transform`: don't rely on struct name of `runtime.typecodeID`
- `transform`: use IPSCCP pass instead of the constant propagation pass
- `transform`: fix func lowering assertion failure
- `transform`: do not lower zero-sized alloc to alloca
- `transform`: split interface and reflect lowering
* **standard library**
- `runtime`: add dummy debug package
- `machine`: fix data shift/mask in newUSBSetup
- `machine`: make `machine.I2C0` and similar objects pointers
- `machine`: unify usbcdc code
- `machine`: refactor PWM support
- `machine`: avoid heap allocations in USB code
- `reflect`: let `reflect.Type` be of interface type
- `reflect`: implement a number of stub functions
- `reflect`: check for access in the `Interface` method call
- `reflect`: fix `AssignableTo` and `Implements` methods
- `reflect`: implement `Value.CanAddr`
- `reflect`: implement `Sizeof` and `Alignof` for func values
- `reflect`: implement `New` function
- `runtime`: implement command line arguments in hosted environments
- `runtime`: implement environment variables for Linux
- `runtime`: improve timers on nrf, and samd chips
* **targets**
- all: use -Qunused-arguments only for assembly files
- `atmega1280`: add PWM support
- `attiny`: remove dummy UART
- `atsamd21`: improve SPI
- `atsamd51`: fix PWM support in atsamd51p20
- `atsamd5x`: improve SPI
- `atsamd51`, `atsame5x`: unify samd51 and same5x
- `atsamd51`, `atsamd21`: fix `ADC.Get()` value at 8bit and 10bit
- `atsame5x`: add support for CAN
- `avr`: remove I2C stubs from attiny support
- `cortexm`: check for `arm-none-eabi-gdb` and `gdb-multiarch` commands
- `cortexm`: add `__isr_vector` symbol
- `cortexm`: disable FPU on Cortex-M4
- `cortexm`: clean up Cortex-M target files
- `fe310`: fix SPI read
- `gameboy-advance`: Fix RGBA color interpretation
- `nrf52833`: add PWM support
- `stm32l0`: use unified UART logic
- `stm32`: move f103 (bluepill) to common i2c code
- `stm32`: separate altfunc selection for UART Tx/Rx
- `stm32`: i2c implementation for F7, L5 and L4 MCUs
- `stm32`: make SPI CLK fast to fix data issue
- `stm32`: support SPI on L4 series
- `unix`: avoid possible heap allocation with `-opt=0`
- `unix`: use conservative GC by default
- `unix`: use the tasks scheduler instead of coroutines
- `wasi`: upgrade WASI version to wasi_snapshot_preview1
- `wasi`: darwin: support basic file io based on libc
- `wasm`: only export explicitly exported functions
- `wasm`: use WASI ABI for exit function
- `wasm`: scan globals conservatively
* **boards**
- `arduino-mega1280`: add support for the Arduino Mega 1280
- `arduino-nano-new`: Add Arduino Nano w/ New Bootloader target
- `atsame54-xpro`: add initial support this board
- `feather-m4-can`: add initial support for this board
- `grandcentral-m4`: add board support for Adafruit Grand Central M4 (SAMD51)
- `lgt92`: update to new UART structure
- `microbit`: remove LED constant
- `microbit-v2`: add support for S113 SoftDevice
- `nucleol432`: add support for this board
- `nucleo-l031k6`: add this board
- `pca10059`: initial support for this board
- `qtpy`: fix msd-volume-name
- `qtpy`: fix i2c setting
- `teensy40`: move txBuffer allocation to UART declaration
- `teensy40`: add UART0 as alias for UART1
0.17.0
---
+3 -3
View File
@@ -1,8 +1,8 @@
# TinyGo base stage installs the most recent Go 1.15.x, LLVM 11 and the TinyGo compiler itself.
FROM golang:1.15 AS tinygo-base
# TinyGo base stage installs the most recent Go 1.17.x, LLVM 11 and the TinyGo compiler itself.
FROM golang:1.17 AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" >> /etc/apt/sources.list && \
echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-11 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git
+63 -22
View File
@@ -36,7 +36,7 @@ else
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
@@ -71,7 +71,7 @@ else
endif
# Libraries that should be linked in for the statically linked Clang.
CLANG_LIB_NAMES = clangAnalysis clangARCMigrate clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangStaticAnalyzerCheckers clangStaticAnalyzerCore clangStaticAnalyzerFrontend clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
# Libraries that should be linked in for the statically linked LLD.
@@ -100,14 +100,14 @@ endif
clean:
@rm -rf build
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/testing src/internal/reflectlite transform
fmt:
@gofmt -l -w $(FMT_PATHS)
fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp
ifneq ($(STM32), 0)
gen-device: gen-device-stm32
endif
@@ -124,6 +124,7 @@ build/gen-device-svd: ./tools/gen-device-svd/*.go
gen-device-esp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/
GO111MODULE=off $(GO) fmt ./src/device/esp
gen-device-nrf: build/gen-device-svd
@@ -150,6 +151,9 @@ gen-device-stm32: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/
GO111MODULE=off $(GO) fmt ./src/device/stm32
gen-device-rp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/
GO111MODULE=off $(GO) fmt ./src/device/rp
# Get LLVM sources.
$(LLVM_PROJECTDIR)/llvm:
@@ -159,7 +163,7 @@ llvm-source: $(LLVM_PROJECTDIR)/llvm
# Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION)
# Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
@@ -182,25 +186,33 @@ tinygo:
test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
TEST_PACKAGES = \
container/heap \
container/list \
container/ring \
crypto/des \
crypto/md5 \
crypto/sha1 \
crypto/sha256 \
crypto/sha512 \
encoding \
encoding/ascii85 \
encoding/base32 \
encoding/hex \
hash/adler32 \
hash/fnv \
hash/crc64 \
math \
math/cmplx \
testing \
text/scanner \
unicode/utf8 \
# Test known-working standard library packages.
# TODO: do this in one command, parallelize, and only show failing tests (no
# implied -v flag).
# TODO: parallelize, and only show failing tests (no implied -v flag).
.PHONY: tinygo-test
tinygo-test:
$(TINYGO) test container/heap
$(TINYGO) test container/list
$(TINYGO) test container/ring
$(TINYGO) test crypto/des
$(TINYGO) test encoding/ascii85
$(TINYGO) test encoding/base32
$(TINYGO) test encoding/hex
$(TINYGO) test hash/adler32
$(TINYGO) test hash/fnv
$(TINYGO) test hash/crc64
$(TINYGO) test math
$(TINYGO) test math/cmplx
$(TINYGO) test text/scanner
$(TINYGO) test unicode/utf8
$(TINYGO) test $(TEST_PACKAGES)
.PHONY: smoketest
smoketest:
@@ -224,6 +236,8 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/memstats
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@@ -328,9 +342,11 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840-sense examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=qtpy examples/blinky1
$(TINYGO) build -size short -o test.hex -target=qtpy examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1
@$(MD5SUM) test.hex
@@ -340,8 +356,24 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/can
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/caninterrupt
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-mkrwifi1010 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/blinky1
@$(MD5SUM) test.hex
# test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex
@@ -372,6 +404,8 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/pwm
@$(MD5SUM) test.hex
endif
ifneq ($(AVR), 0)
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
@@ -399,6 +433,8 @@ ifneq ($(XTENSA), 0)
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
@$(MD5SUM) test.bin
endif
$(TINYGO) build -size short -o test.bin -target=esp32c3 examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial
@@ -412,10 +448,15 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
@$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex
ifneq ($(OS),Windows_NT)
$(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial
endif
wasmtest:
+15 -2
View File
@@ -43,7 +43,7 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 57 microcontroller boards are currently supported:
The following 70 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
@@ -52,7 +52,10 @@ The following 57 microcontroller boards are currently supported:
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
@@ -63,10 +66,15 @@ The following 57 microcontroller boards are currently supported:
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit QT Py](https://www.adafruit.com/product/4600)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010)
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble)
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
* [BBC micro:bit](https://microbit.org/)
@@ -84,6 +92,7 @@ The following 57 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)
* [Nordic Semiconductor pca10059](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
* [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/)
@@ -92,12 +101,16 @@ The following 57 microcontroller boards are currently supported:
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/)
* [Raytac MDBT50Q-RX Dongle (with TinyUF2 bootloader)](https://www.adafruit.com/product/5199)
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
* [ST Micro "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html)
* [ST Micro "Nucleo" L031K6](https://www.st.com/ja/evaluation-tools/nucleo-l031k6.html)
* [ST Micro "Nucleo" L432KC](https://www.st.com/ja/evaluation-tools/nucleo-l432kc.html)
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
+2 -2
View File
@@ -12,7 +12,7 @@ jobs:
steps:
- task: GoTool@0
inputs:
version: '1.16'
version: '1.17'
- checkout: self
fetchDepth: 1
- task: Cache@2
@@ -28,7 +28,7 @@ jobs:
- task: CacheBeta@0
displayName: Cache LLVM build
inputs:
key: llvm-build-11-windows-v4
key: llvm-build-11-windows-v5
path: llvm-build
- task: Bash@3
displayName: Build LLVM
+154 -84
View File
@@ -12,7 +12,9 @@ import (
"errors"
"fmt"
"go/types"
"hash/crc32"
"io/ioutil"
"math/bits"
"os"
"path/filepath"
"runtime"
@@ -39,6 +41,11 @@ type BuildResult struct {
// The directory of the main package. This is useful for testing as the test
// binary must be run in the directory of the tested package.
MainDir string
// ImportPath is the import path of the main package. This is useful for
// correctly printing test results: the import path isn't always the same as
// the path listed on the command line.
ImportPath string
}
// packageAction is the struct that is serialized to JSON and hashed, to work as
@@ -93,7 +100,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.Target.DefaultStackSize,
NeedsStackObjects: config.NeedsStackObjects(),
Debug: config.Debug(),
Debug: true,
LLVMFeatures: config.LLVMFeatures(),
}
// Load the target machine, which is the LLVM object that contains all
@@ -116,11 +124,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err
}
// The slice of jobs that orchestrates most of the build.
// This is somewhat like an in-memory Makefile with each job being a
// Makefile target.
var jobs []*compileJob
// Create the *ssa.Program. This does not yet build the entire SSA of the
// program so it's pretty fast and doesn't need to be parallelized.
program := lprogram.LoadSSA()
@@ -303,7 +306,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return os.Rename(f.Name(), bitcodePath)
},
}
jobs = append(jobs, job)
packageJobs = append(packageJobs, job)
}
@@ -394,14 +396,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return nil
},
}
jobs = append(jobs, programJob)
// Check whether we only need to create an object file.
// If so, we don't need to link anything and will be finished quickly.
outext := filepath.Ext(outpath)
if outext == ".o" || outext == ".bc" || outext == ".ll" {
// Run jobs to produce the LLVM module.
err := runJobs(jobs)
err := runJobs(programJob)
if err != nil {
return err
}
@@ -442,7 +443,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
},
}
jobs = append(jobs, outputObjectFileJob)
// Prepare link command.
linkerDependencies := []*compileJob{outputObjectFileJob}
@@ -457,38 +457,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
if err != nil {
return err
}
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
}
// Add libc dependency if needed.
root := goenv.Get("TINYGOROOT")
switch config.Target.Libc {
case "picolibc":
job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
// The library needs to be compiled (cache miss).
jobs = append(jobs, job.dependencies...)
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
ldflags = append(ldflags, path)
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
// Add jobs to compile extra files. These files are in C or assembly and
// contain things like the interrupt vector table and low level operations
// such as stack switching.
root := goenv.Get("TINYGOROOT")
for _, path := range config.ExtraFiles() {
abspath := filepath.Join(root, path)
job := &compileJob{
@@ -499,7 +474,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err
},
}
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
}
@@ -518,7 +492,6 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err
},
}
jobs = append(jobs, job)
linkerDependencies = append(linkerDependencies, job)
}
}
@@ -529,9 +502,63 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
ldflags = append(ldflags, lprogram.LDFlags...)
}
// Add libc dependency if needed.
switch config.Target.Libc {
case "picolibc":
job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
if err != nil {
return err
}
linkerDependencies = append(linkerDependencies, job)
case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); os.IsNotExist(err) {
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
job := dummyCompileJob(path)
linkerDependencies = append(linkerDependencies, job)
case "":
// no library specified, so nothing to do
default:
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
// Strip debug information with -no-debug.
if !config.Debug() {
for _, tag := range config.BuildTags() {
if tag == "baremetal" {
// Don't use -no-debug on baremetal targets. It makes no sense:
// the debug information isn't flashed to the device anyway.
return fmt.Errorf("stripping debug information is unnecessary for baremetal targets")
}
}
if config.Target.Linker == "wasm-ld" {
// Don't just strip debug information, also compress relocations
// while we're at it. Relocations can only be compressed when debug
// information is stripped.
ldflags = append(ldflags, "--strip-debug", "--compress-relocations")
} else {
switch config.GOOS() {
case "linux":
// Either real linux or an embedded system (like AVR) that
// pretends to be Linux. It's a ELF linker wrapped by GCC in any
// case.
ldflags = append(ldflags, "-Wl,--strip-debug")
case "darwin":
// MacOS (darwin) doesn't have a linker flag to strip debug
// information. Apple expects you to use the strip command
// instead.
return errors.New("cannot remove debug information: MacOS doesn't suppor this linker flag")
default:
// Other OSes may have different flags.
return errors.New("cannot remove debug information: unknown OS: " + config.GOOS())
}
}
}
// Create a linker job, which links all object files together and does some
// extra stuff that can only be done after linking.
jobs = append(jobs, &compileJob{
linkJob := &compileJob{
description: "link",
dependencies: linkerDependencies,
run: func(job *compileJob) error {
@@ -541,8 +568,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
}
ldflags = append(ldflags, dependency.result)
}
if config.Options.PrintCommands {
fmt.Printf("%s %s\n", config.Target.Linker, strings.Join(ldflags, " "))
if config.Options.PrintCommands != nil {
config.Options.PrintCommands(config.Target.Linker, ldflags...)
}
err = link(config.Target.Linker, ldflags...)
if err != nil {
@@ -560,6 +587,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return err
}
}
// Apply ELF patches
if config.AutomaticStackSize() {
// Modify the .tinygo_stacksizes section that contains a stack size
// for each goroutine.
@@ -568,6 +597,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return fmt.Errorf("could not modify stack sizes: %w", err)
}
}
if config.RP2040BootPatch() {
// Patch the second stage bootloader CRC into the .boot2 section
err = patchRP2040BootCRC(executable)
if err != nil {
return fmt.Errorf("could not patch RP2040 second stage boot loader: %w", err)
}
}
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
sizes, err := loadProgramSize(executable)
@@ -595,12 +631,12 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
return nil
},
})
}
// Run all jobs to compile and link the program.
// Do this now (instead of after elf-to-hex and similar conversions) as it
// is simpler and cannot be parallelized.
err = runJobs(jobs)
err = runJobs(linkJob)
if err != nil {
return err
}
@@ -625,7 +661,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
if err != nil {
return err
}
case "esp32", "esp8266":
case "esp32", "esp32c3", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
tmppath = filepath.Join(dir, "main"+outext)
@@ -633,12 +669,25 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil
if err != nil {
return err
}
case "nrf-dfu":
// special format for nrfutil for Nordic chips
tmphexpath := filepath.Join(dir, "main.hex")
err := objcopy(executable, tmphexpath, "hex")
if err != nil {
return err
}
tmppath = filepath.Join(dir, "main"+outext)
err = makeDFUFirmwareImage(config.Options, tmphexpath, tmppath)
if err != nil {
return err
}
default:
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
}
return action(BuildResult{
Binary: tmppath,
MainDir: lprogram.MainPkg().Dir,
Binary: tmppath,
MainDir: lprogram.MainPkg().Dir,
ImportPath: lprogram.MainPkg().ImportPath,
})
}
@@ -901,30 +950,7 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
// stack size information. Before this modification, all stack sizes in the
// section assume the default stack size (which is relatively big).
func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error {
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
if err != nil {
return err
}
defer fp.Close()
elfFile, err := elf.NewFile(fp)
if err != nil {
return err
}
section := elfFile.Section(".tinygo_stacksizes")
if section == nil {
return errors.New("could not find .tinygo_stacksizes section")
}
if section.Size != section.FileSize {
// Sanity check.
return fmt.Errorf("expected .tinygo_stacksizes to have identical size and file size, got %d and %d", section.Size, section.FileSize)
}
// Read all goroutine stack sizes.
data := make([]byte, section.Size)
_, err = fp.ReadAt(data, int64(section.Offset))
data, fileHeader, err := getElfSectionData(executable, ".tinygo_stacksizes")
if err != nil {
return err
}
@@ -946,15 +972,19 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
if fn.stackSizeType == stacksize.Bounded {
stackSize := uint32(fn.stackSize)
// Adding 4 for the stack canary. Even though the size may be
// automatically determined, stack overflow checking is still
// important as the stack size cannot be determined for all
// goroutines.
stackSize += 4
// Add stack size used by interrupts.
switch elfFile.Machine {
switch fileHeader.Machine {
case elf.EM_ARM:
if stackSize%8 != 0 {
// If the stack isn't a multiple of 8, it means the leaf
// function with the biggest stack depth doesn't have an aligned
// stack. If the STKALIGN flag is set (which it is by default)
// the interrupt controller will forcibly align the stack before
// storing in-use registers. This will thus overwrite one word
// past the end of the stack (off-by-one).
stackSize += 4
}
// On Cortex-M (assumed here), this stack size is 8 words or 32
// bytes. This is only to store the registers that the interrupt
// may modify, the interrupt will switch to the interrupt stack
@@ -962,6 +992,14 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
// Some background:
// https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching
stackSize += 32
// Adding 4 for the stack canary, and another 4 to keep the
// stack aligned. Even though the size may be automatically
// determined, stack overflow checking is still important as the
// stack size cannot be determined for all goroutines.
stackSize += 8
default:
return fmt.Errorf("unknown architecture: %s", fileHeader.Machine.String())
}
// Finally write the stack size to the binary.
@@ -969,13 +1007,7 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map
}
}
// Write back the modified stack sizes.
_, err = fp.WriteAt(data, int64(section.Offset))
if err != nil {
return err
}
return nil
return replaceElfSection(executable, ".tinygo_stacksizes", data)
}
// printStacks prints the maximum stack depth for functions that are started as
@@ -1007,3 +1039,41 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS
}
}
}
// RP2040 second stage bootloader CRC32 calculation
//
// Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf
// Section: 2.8.1.3.1. Checksum
func patchRP2040BootCRC(executable string) error {
bytes, _, err := getElfSectionData(executable, ".boot2")
if err != nil {
return err
}
if len(bytes) != 256 {
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes")
}
// From the 'official' RP2040 checksum script:
//
// Our bootrom CRC32 is slightly bass-ackward but it's
// best to work around for now (FIXME)
// 100% worth it to save two Thumb instructions
revBytes := make([]byte, len(bytes))
for i := range bytes {
revBytes[i] = bits.Reverse8(bytes[i])
}
// crc32.Update does an initial negate and negates the
// result, so to meet RP2040 spec, pass 0x0 as initial
// hash and negate returned value.
//
// Note: checksum is over 252 bytes (256 - 4)
hash := bits.Reverse32(crc32.Update(0x0, crc32.IEEETable, revBytes[:252]) ^ 0xFFFFFFFF)
// Write the CRC to the end of the bootloader.
binary.LittleEndian.PutUint32(bytes[252:], hash)
// Update the .boot2 section to included the CRC
return replaceElfSection(executable, ".boot2", bytes)
}
+7 -3
View File
@@ -56,7 +56,7 @@ import (
// depfile but without invalidating its name. For this reason, the depfile is
// written on each new compilation (even when it seems unnecessary). However, it
// could in rare cases lead to a stale file fetched from the cache.
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands bool) (string, error) {
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) {
// Hash input file.
fileHash, err := hashFile(abspath)
if err != nil {
@@ -128,8 +128,8 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
// flags (for the assembler) is a compiler error.
flags = append(flags, "-Qunused-arguments")
}
if printCommands {
fmt.Printf("clang %s\n", strings.Join(flags, " "))
if printCommands != nil {
printCommands("clang", flags...)
}
err = runCCompiler(flags...)
if err != nil {
@@ -155,6 +155,10 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
// Write dependencies file.
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
if err != nil {
return "", err
}
buf, err = json.MarshalIndent(dependencySlice, "", "\t")
if err != nil {
panic(err) // shouldn't happen
+12 -2
View File
@@ -17,18 +17,28 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if err != nil {
return nil, err
}
if options.OpenOCDCommands != nil {
// Override the OpenOCDCommands from the target spec if specified on
// the command-line
spec.OpenOCDCommands = options.OpenOCDCommands
}
goroot := goenv.Get("GOROOT")
if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually")
}
major, minor, err := goenv.GetGorootVersion(goroot)
if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 || minor < 13 || minor > 16 {
return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor)
if major != 1 || minor < 15 || minor > 17 {
return nil, fmt.Errorf("requires go version 1.15 through 1.17, got go%d.%d", major, minor)
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{
Options: options,
Target: spec,
+57
View File
@@ -0,0 +1,57 @@
package builder
import (
"debug/elf"
"fmt"
"os"
)
func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileHeader, error) {
elfFile, err := elf.Open(executable)
if err != nil {
return nil, elf.FileHeader{}, err
}
defer elfFile.Close()
section := elfFile.Section(sectionName)
if section == nil {
return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName)
}
data, err := section.Data()
return data, elfFile.FileHeader, err
}
func replaceElfSection(executable string, sectionName string, data []byte) error {
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
if err != nil {
return err
}
defer fp.Close()
elfFile, err := elf.Open(executable)
if err != nil {
return err
}
defer elfFile.Close()
section := elfFile.Section(sectionName)
if section == nil {
return fmt.Errorf("could not find %s section", sectionName)
}
// Implicitly check for compressed sections
if section.Size != section.FileSize {
return fmt.Errorf("expected section %s to have identical size and file size, got %d and %d", sectionName, section.Size, section.FileSize)
}
// Only permit complete replacement of section
if section.Size != uint64(len(data)) {
return fmt.Errorf("expected section %s to have size %d, was actually %d", sectionName, len(data), section.Size)
}
// Write the replacement section data
_, err = fp.WriteAt(data, int64(section.Offset))
return err
}
+19 -6
View File
@@ -78,11 +78,21 @@ func makeESPFirmareImage(infile, outfile, format string) error {
// An added benefit is that we don't need to check for errors all the time.
outf := &bytes.Buffer{}
// Chip IDs. Source:
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22
chip_id := map[string]uint16{
"esp32": 0x0000,
"esp32c3": 0x0005,
}[format]
// Image header.
switch format {
case "esp32":
case "esp32", "esp32c3":
// Header format:
// https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
// Note: not adding a SHA256 hash as the binary is modified by
// esptool.py while flashing and therefore the hash won't be valid
// anymore.
binary.Write(outf, binary.LittleEndian, struct {
magic uint8
segment_count uint8
@@ -91,15 +101,18 @@ func makeESPFirmareImage(infile, outfile, format string) error {
entry_addr uint32
wp_pin uint8
spi_pin_drv [3]uint8
reserved [11]uint8
chip_id uint16
min_chip_rev uint8
reserved [8]uint8
hash_appended bool
}{
magic: 0xE9,
segment_count: byte(len(segments)),
spi_mode: 0, // irrelevant, replaced by esptool when flashing
spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
entry_addr: uint32(inf.Entry),
wp_pin: 0xEE, // disable WP pin
chip_id: chip_id,
hash_appended: true, // add a SHA256 hash
})
case "esp8266":
@@ -142,7 +155,7 @@ func makeESPFirmareImage(infile, outfile, format string) error {
outf.Write(make([]byte, 15-outf.Len()%16))
outf.WriteByte(checksum)
if format == "esp32" {
if format != "esp8266" {
// SHA256 hash (to protect against image corruption, not for security).
hash := sha256.Sum256(outf.Bytes())
outf.Write(hash[:])
+23 -6
View File
@@ -65,12 +65,29 @@ func (job *compileJob) readyToRun() bool {
return true
}
// runJobs runs all the jobs indicated in the jobs slice and returns the error
// of the first job that fails to run.
// It runs all jobs in the order of the slice, as long as all dependencies have
// already run. Therefore, if some jobs are preferred to run before others, they
// should be ordered as such in this slice.
func runJobs(jobs []*compileJob) error {
// runJobs runs the indicated job and all its dependencies. For every job, all
// the dependencies are run first. It returns the error of the first job that
// fails.
// It runs all jobs in the order of the dependencies slice, depth-first.
// Therefore, if some jobs are preferred to run before others, they should be
// ordered as such in the job dependencies.
func runJobs(job *compileJob) error {
// Create a slice of jobs to run, where all dependencies are run in order.
jobs := []*compileJob{}
addedJobs := map[*compileJob]struct{}{}
var addJobs func(*compileJob)
addJobs = func(job *compileJob) {
if _, ok := addedJobs[job]; ok {
return
}
for _, dep := range job.dependencies {
addJobs(dep)
}
jobs = append(jobs, job)
addedJobs[job] = struct{}{}
}
addJobs(job)
// Create channels to communicate with the workers.
doneChan := make(chan *compileJob)
workerChan := make(chan *compileJob)
+3 -4
View File
@@ -46,15 +46,14 @@ func (l *Library) Load(target, tmpdir string) (path string, err error) {
if err != nil {
return "", err
}
jobs := append([]*compileJob{job}, job.dependencies...)
err = runJobs(jobs)
err = runJobs(job)
return job.result, err
}
// load returns a compile job to build this library file for the given target
// and CPU. It may return a dummy compileJob if the library build is already
// cached. The path is stored as job.result but is only valid if the job and
// job.dependencies have been run.
// cached. The path is stored as job.result but is only valid after the job has
// been run.
// The provided tmpdir will be used to store intermediary files and possibly the
// output archive file, it is expected to be removed after use.
func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error) {
+27
View File
@@ -0,0 +1,27 @@
package builder
import (
"fmt"
"io/ioutil"
"os/exec"
"github.com/tinygo-org/tinygo/compileopts"
)
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
if options.PrintCommands != nil {
options.PrintCommands(cmdLine[0], cmdLine[1:]...)
}
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
cmd.Stdout = ioutil.Discard
err := cmd.Run()
if err != nil {
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
}
return nil
}
-9
View File
@@ -12,7 +12,6 @@ import (
"go/types"
"io/ioutil"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
@@ -25,14 +24,6 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
// platforms and Go versions.
func normalizeResult(result string) string {
actual := strings.ReplaceAll(result, "\r\n", "\n")
// Make sure all functions are wrapped, even those that would otherwise be
// single-line functions. This is necessary because Go 1.14 changed the way
// such functions are wrapped and it's important to have consistent test
// results.
re := regexp.MustCompile(`func \((.+)\)( .*?) +{ (.+) }`)
actual = re.ReplaceAllString(actual, "func ($1)$2 {\n\t$3\n}")
return actual
}
+164 -74
View File
@@ -11,105 +11,184 @@ import (
"strings"
)
var (
prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error)
precedences = map[token.Token]int{
token.ADD: precedenceAdd,
token.SUB: precedenceAdd,
token.MUL: precedenceMul,
token.QUO: precedenceMul,
token.REM: precedenceMul,
}
)
const (
precedenceLowest = iota + 1
precedenceAdd
precedenceMul
precedencePrefix
)
func init() {
// This must be done in an init function to avoid an initialization order
// failure.
prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){
token.IDENT: parseIdent,
token.INT: parseBasicLit,
token.FLOAT: parseBasicLit,
token.STRING: parseBasicLit,
token.CHAR: parseBasicLit,
token.LPAREN: parseParenExpr,
token.SUB: parseUnaryExpr,
}
}
// parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value)
expr, err := parseConstExpr(t)
if t.token != token.EOF {
expr, err := parseConstExpr(t, precedenceLowest)
t.Next()
if t.curToken != token.EOF {
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: "unexpected token " + t.token.String(),
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + ", expected end of expression",
}
}
return expr, err
}
// parseConstExpr parses a stream of C tokens to a Go expression.
func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
switch t.token {
case token.LPAREN:
lparen := t.pos
t.Next()
x, err := parseConstExpr(t)
if err != nil {
return nil, err
}
if t.token != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.pos,
}
t.Next()
return expr, nil
case token.INT, token.FLOAT, token.STRING, token.CHAR:
expr := &ast.BasicLit{
ValuePos: t.pos,
Kind: t.token,
Value: t.value,
}
t.Next()
return expr, nil
case token.IDENT:
expr := &ast.Ident{
NamePos: t.pos,
Name: "C." + t.value,
}
t.Next()
return expr, nil
case token.EOF:
func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
if t.curToken == token.EOF {
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Pos: t.fset.Position(t.curPos),
Msg: "empty constant",
}
default:
}
prefix := prefixParseFns[t.curToken]
if prefix == nil {
return nil, &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s", t.token),
Pos: t.fset.Position(t.curPos),
Msg: fmt.Sprintf("unexpected token %s", t.curToken),
}
}
leftExpr, err := prefix(t)
for t.peekToken != token.EOF && precedence < precedences[t.peekToken] {
switch t.peekToken {
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
t.Next()
leftExpr, err = parseBinaryExpr(t, leftExpr)
}
}
return leftExpr, err
}
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
return &ast.Ident{
NamePos: t.curPos,
Name: "C." + t.curValue,
}, nil
}
func parseBasicLit(t *tokenizer) (ast.Expr, *scanner.Error) {
return &ast.BasicLit{
ValuePos: t.curPos,
Kind: t.curToken,
Value: t.curValue,
}, nil
}
func parseParenExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
lparen := t.curPos
t.Next()
x, err := parseConstExpr(t, precedenceLowest)
if err != nil {
return nil, err
}
t.Next()
if t.curToken != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.curPos,
}
return expr, nil
}
func parseBinaryExpr(t *tokenizer, left ast.Expr) (ast.Expr, *scanner.Error) {
expression := &ast.BinaryExpr{
X: left,
Op: t.curToken,
OpPos: t.curPos,
}
precedence := precedences[t.curToken]
t.Next()
right, err := parseConstExpr(t, precedence)
expression.Y = right
return expression, err
}
func parseUnaryExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
expression := &ast.UnaryExpr{
OpPos: t.curPos,
Op: t.curToken,
}
t.Next()
x, err := parseConstExpr(t, precedencePrefix)
expression.X = x
return expression, err
}
// unexpectedToken returns an error of the form "unexpected token FOO, expected
// BAR".
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
return &scanner.Error{
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
Pos: t.fset.Position(t.curPos),
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.curToken, expected),
}
}
// tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct {
pos token.Pos
fset *token.FileSet
token token.Token
value string
buf string
curPos, peekPos token.Pos
fset *token.FileSet
curToken, peekToken token.Token
curValue, peekValue string
buf string
}
// newTokenizer initializes a new tokenizer, positioned at the first token in
// the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
t := &tokenizer{
pos: start,
fset: fset,
buf: buf,
token: token.ILLEGAL,
peekPos: start,
fset: fset,
buf: buf,
peekToken: token.ILLEGAL,
}
t.Next() // Parse the first token.
// Parse the first two tokens (cur and peek).
t.Next()
t.Next()
return t
}
// Next consumes the next token in the stream. There is no return value, read
// the next token from the pos, token and value properties.
func (t *tokenizer) Next() {
t.pos += token.Pos(len(t.value))
// The previous peek is now the current token.
t.curPos = t.peekPos
t.curToken = t.peekToken
t.curValue = t.peekValue
// Parse the next peek token.
t.peekPos += token.Pos(len(t.curValue))
for {
if len(t.buf) == 0 {
t.token = token.EOF
t.peekToken = token.EOF
return
}
c := t.buf[0]
@@ -118,17 +197,28 @@ func (t *tokenizer) Next() {
// Skip whitespace.
// Based on this source, not sure whether it represents C whitespace:
// https://en.cppreference.com/w/cpp/string/byte/isspace
t.pos++
t.peekPos++
t.buf = t.buf[1:]
case c == '(' || c == ')':
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%':
// Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators.
switch c {
case '(':
t.token = token.LPAREN
t.peekToken = token.LPAREN
case ')':
t.token = token.RPAREN
t.peekToken = token.RPAREN
case '+':
t.peekToken = token.ADD
case '-':
t.peekToken = token.SUB
case '*':
t.peekToken = token.MUL
case '/':
t.peekToken = token.QUO
case '%':
t.peekToken = token.REM
}
t.value = t.buf[:1]
t.peekValue = t.buf[:1]
t.buf = t.buf[1:]
return
case c >= '0' && c <= '9':
@@ -146,17 +236,17 @@ func (t *tokenizer) Next() {
break
}
}
t.value = t.buf[:tokenLen]
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
if hasDot {
// Integer constants are more complicated than this but this is
// a close approximation.
// https://en.cppreference.com/w/cpp/language/integer_literal
t.token = token.FLOAT
t.value = strings.TrimRight(t.value, "f")
t.peekToken = token.FLOAT
t.peekValue = strings.TrimRight(t.peekValue, "f")
} else {
t.token = token.INT
t.value = strings.TrimRight(t.value, "uUlL")
t.peekToken = token.INT
t.peekValue = strings.TrimRight(t.peekValue, "uUlL")
}
return
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
@@ -170,9 +260,9 @@ func (t *tokenizer) Next() {
break
}
}
t.value = t.buf[:tokenLen]
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
t.token = token.IDENT
t.peekToken = token.IDENT
return
case c == '"':
// String constant. Find the first '"' character that is not
@@ -188,8 +278,8 @@ func (t *tokenizer) Next() {
escape = c == '\\'
}
}
t.token = token.STRING
t.value = t.buf[:tokenLen]
t.peekToken = token.STRING
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
case c == '\'':
@@ -206,12 +296,12 @@ func (t *tokenizer) Next() {
escape = c == '\\'
}
}
t.token = token.CHAR
t.value = t.buf[:tokenLen]
t.peekToken = token.CHAR
t.peekValue = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
default:
t.token = token.ILLEGAL
t.peekToken = token.ILLEGAL
return
}
}
+19 -2
View File
@@ -18,7 +18,7 @@ func TestParseConst(t *testing.T) {
{`(5)`, `(5)`},
{`(((5)))`, `(5)`},
{`)`, `error: 1:1: unexpected token )`},
{`5)`, `error: 1:2: unexpected token )`},
{`5)`, `error: 1:2: unexpected token ), expected end of expression`},
{" \t)", `error: 1:4: unexpected token )`},
{`5.8f`, `5.8`},
{`foo`, `C.foo`},
@@ -30,7 +30,24 @@ func TestParseConst(t *testing.T) {
{`'a'`, `'a'`},
{`0b10`, `0b10`},
{`0x1234_5678`, `0x1234_5678`},
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
{`5 5`, `error: 1:3: unexpected token INT, expected end of expression`}, // test for a bugfix
// Binary operators.
{`5+5`, `5 + 5`},
{`5-5`, `5 - 5`},
{`5*5`, `5 * 5`},
{`5/5`, `5 / 5`},
{`5%5`, `5 % 5`},
{`(5/5)`, `(5 / 5)`},
{`1 - 2`, `1 - 2`},
{`1 - 2 + 3`, `1 - 2 + 3`},
{`1 - 2 * 3`, `1 - 2*3`},
{`(1 - 2) * 3`, `(1 - 2) * 3`},
{`1 * 2 - 3`, `1*2 - 3`},
{`1 * (2 - 3)`, `1 * (2 - 3)`},
// Unary operators.
{`-5`, `-5`},
{`-5-2`, `-5 - 2`},
{`5 - - 2`, `5 - -2`},
} {
fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0)
+1 -1
View File
@@ -1,7 +1,7 @@
// CGo errors:
// testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:13:23: unexpected token )
// testdata/errors.go:13:23: unexpected token ), expected end of expression
// Type checking errors after CGo processing:
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as uint8 value in variable declaration (overflows)
+5 -15
View File
@@ -67,9 +67,7 @@ type C.union3_t = C.union_1
type C.union_nested_t = C.union_3
type C.unionarray_t = struct{ arr [10]C.uchar }
func (s *C.struct_4) bitfield_a() C.uchar {
return s.__bitfield_1 & 0x1f
}
func (s *C.struct_4) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
func (s *C.struct_4) set_bitfield_a(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0
}
@@ -105,15 +103,9 @@ type C.struct_type1 struct {
}
type C.struct_type2 struct{ _type C.int }
func (union *C.union_1) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_s() *C.short {
return (*C.short)(unsafe.Pointer(&union.$union))
}
func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) }
func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) }
type C.union_1 struct{ $union uint64 }
@@ -138,9 +130,7 @@ func (union *C.union_3) unionfield_thing() *C.union3_t {
type C.union_3 struct{ $union [2]uint64 }
func (union *C.union_union2d) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
func (union *C.union_union2d) unionfield_d() *[2]float64 {
return (*[2]float64)(unsafe.Pointer(&union.$union))
}
+41 -13
View File
@@ -55,7 +55,7 @@ func (c *Config) GOARCH() string {
// BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string {
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
@@ -80,12 +80,7 @@ func (c *Config) GC() string {
if c.Target.GC != "" {
return c.Target.GC
}
for _, tag := range c.Target.BuildTags {
if tag == "baremetal" || tag == "wasm" {
return "conservative"
}
}
return "extalloc"
return "conservative"
}
// NeedsStackObjects returns true if the compiler should insert stack objects
@@ -94,7 +89,7 @@ func (c *Config) NeedsStackObjects() bool {
switch c.GC() {
case "conservative", "extalloc":
for _, tag := range c.BuildTags() {
if tag == "wasm" {
if tag == "tinygo.wasm" {
return true
}
}
@@ -118,6 +113,18 @@ func (c *Config) Scheduler() string {
return "coroutines"
}
// Serial returns the serial implementation for this build configuration: uart,
// usb (meaning USB-CDC), or none.
func (c *Config) Serial() string {
if c.Options.Serial != "" {
return c.Options.Serial
}
if c.Target.Serial != "" {
return c.Target.Serial
}
return "none"
}
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline.
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
@@ -181,6 +188,15 @@ func (c *Config) AutomaticStackSize() bool {
return false
}
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
// calculates and patches in the checksum for the 2nd stage bootloader.
func (c *Config) RP2040BootPatch() bool {
if c.Target.RP2040BootPatch != nil {
return *c.Target.RP2040BootPatch
}
return false
}
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing.
func (c *Config) CFlags() []string {
@@ -193,9 +209,8 @@ func (c *Config) CFlags() []string {
cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
}
if c.Debug() {
cflags = append(cflags, "-g")
}
// Always emit debug information. It is optionally stripped at link time.
cflags = append(cflags, "-g")
return cflags
}
@@ -234,8 +249,9 @@ func (c *Config) VerifyIR() bool {
return c.Options.VerifyIR
}
// Debug returns whether to add debug symbols to the IR, for debugging with GDB
// and similar.
// Debug returns whether debug (DWARF) information should be retained by the
// linker. By default, debug information is retained but it can be removed with
// the -no-debug flag.
func (c *Config) Debug() bool {
return c.Options.Debug
}
@@ -259,6 +275,11 @@ func (c *Config) BinaryFormat(ext string) string {
// More information:
// https://github.com/Microsoft/uf2
return "uf2"
case ".zip":
if c.Target.BinaryFormat != "" {
return c.Target.BinaryFormat
}
return "zip"
default:
// Use the ELF format for unrecognized file formats.
return "elf"
@@ -276,6 +297,9 @@ func (c *Config) Programmer() (method, openocdInterface string) {
case "openocd", "msd", "command":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface
case "bmp":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, ""
default:
// The -programmer flag specifies something else, assume it specifies
// the OpenOCD interface name.
@@ -342,6 +366,10 @@ func (c *Config) WasmAbi() string {
return c.Target.WasmAbi
}
func (c *Config) LLVMFeatures() string {
return c.Options.LLVMFeatures
}
type TestConfig struct {
CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc
+31 -18
View File
@@ -9,6 +9,7 @@ import (
var (
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
validSerialOptions = []string{"none", "uart", "usb"}
validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
@@ -17,24 +18,27 @@ var (
// Options contains extra options to give to the compiler. These options are
// usually passed from the command line.
type Options struct {
Target string
Opt string
GC string
PanicStrategy string
Scheduler string
PrintIR bool
DumpSSA bool
VerifyIR bool
PrintCommands bool
Debug bool
PrintSizes string
PrintAllocs *regexp.Regexp // regexp string
PrintStacks bool
Tags string
WasmAbi string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
TestConfig TestConfig
Programmer string
Target string
Opt string
GC string
PanicStrategy string
Scheduler string
Serial string
PrintIR bool
DumpSSA bool
VerifyIR bool
PrintCommands func(cmd string, args ...string)
Debug bool
PrintSizes string
PrintAllocs *regexp.Regexp // regexp string
PrintStacks bool
Tags string
WasmAbi string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
TestConfig TestConfig
Programmer string
OpenOCDCommands []string
LLVMFeatures string
}
// Verify performs a validation on the given options, raising an error if options are not valid.
@@ -57,6 +61,15 @@ func (o *Options) Verify() error {
}
}
if o.Serial != "" {
valid := isInArray(validSerialOptions, o.Serial)
if !valid {
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
o.Serial,
strings.Join(validSerialOptions, ", "))
}
}
if o.PrintSizes != "" {
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
if !valid {
+19 -8
View File
@@ -31,6 +31,7 @@ type TargetSpec struct {
BuildTags []string `json:"build-tags"`
GC string `json:"gc"`
Scheduler string `json:"scheduler"`
Serial string `json:"serial"` // which serial output to use (uart, usb, none)
Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"`
@@ -40,10 +41,12 @@ type TargetSpec struct {
LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"`
ExtraFiles []string `json:"extra-files"`
RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
FlashCommand string `json:"flash-command"`
GDB []string `json:"gdb"`
PortReset string `json:"flash-1200-bps-reset"`
SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"`
@@ -169,6 +172,7 @@ func LoadTarget(target string) (*TargetSpec, error) {
"386": "i386",
"amd64": "x86_64",
"arm64": "aarch64",
"arm": "thumbv7",
}[goarch]
if llvmarch == "" {
llvmarch = goarch
@@ -239,22 +243,29 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
// No target spec available. Use the default one, useful on most systems
// with a regular OS.
spec := TargetSpec{
Triple: triple,
GOOS: goos,
GOARCH: goarch,
BuildTags: []string{goos, goarch},
Linker: "cc",
CFlags: []string{"--target=" + triple},
GDB: []string{"gdb"},
PortReset: "false",
Triple: triple,
GOOS: goos,
GOARCH: goarch,
BuildTags: []string{goos, goarch},
Scheduler: "tasks",
Linker: "cc",
DefaultStackSize: 1024 * 64, // 64kB
CFlags: []string{"--target=" + triple},
GDB: []string{"gdb"},
PortReset: "false",
}
if goarch == "386" {
spec.CPU = "pentium4"
}
if goos == "darwin" {
spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
} else {
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
}
if goarch != "wasm" {
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+".S")
}
if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs.
+99
View File
@@ -0,0 +1,99 @@
package compiler
// This file defines alias functions for functions that are normally defined in
// Go assembly.
//
// The Go toolchain defines many performance critical functions in assembly
// instead of plain Go. This is a problem for TinyGo as it currently (as of
// august 2021) is not able to compile these assembly files and even if it
// could, it would not be able to make use of them for many targets that are
// supported by TinyGo (baremetal RISC-V, AVR, etc). Therefore, many of these
// functions are aliased to their generic Go implementation.
// This results in slower than possible implementations, but at least they are
// usable.
import "tinygo.org/x/go-llvm"
var stdlibAliases = map[string]string{
// crypto packages
"crypto/md5.block": "crypto/md5.blockGeneric",
"crypto/sha1.block": "crypto/sha1.blockGeneric",
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
"crypto/sha256.block": "crypto/sha256.blockGeneric",
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
// math package
"math.Asin": "math.asin",
"math.Asinh": "math.asinh",
"math.Acos": "math.acos",
"math.Acosh": "math.acosh",
"math.Atan": "math.atan",
"math.Atanh": "math.atanh",
"math.Atan2": "math.atan2",
"math.Cbrt": "math.cbrt",
"math.Ceil": "math.ceil",
"math.archCeil": "math.ceil",
"math.Cos": "math.cos",
"math.Cosh": "math.cosh",
"math.Erf": "math.erf",
"math.Erfc": "math.erfc",
"math.Exp": "math.exp",
"math.archExp": "math.exp",
"math.Expm1": "math.expm1",
"math.Exp2": "math.exp2",
"math.archExp2": "math.exp2",
"math.Floor": "math.floor",
"math.archFloor": "math.floor",
"math.Frexp": "math.frexp",
"math.Hypot": "math.hypot",
"math.archHypot": "math.hypot",
"math.Ldexp": "math.ldexp",
"math.Log": "math.log",
"math.archLog": "math.log",
"math.Log1p": "math.log1p",
"math.Log10": "math.log10",
"math.Log2": "math.log2",
"math.Max": "math.max",
"math.archMax": "math.max",
"math.Min": "math.min",
"math.archMin": "math.min",
"math.Mod": "math.mod",
"math.Modf": "math.modf",
"math.archModf": "math.modf",
"math.Pow": "math.pow",
"math.Remainder": "math.remainder",
"math.Sin": "math.sin",
"math.Sinh": "math.sinh",
"math.Sqrt": "math.sqrt",
"math.archSqrt": "math.sqrt",
"math.Tan": "math.tan",
"math.Tanh": "math.tanh",
"math.Trunc": "math.trunc",
"math.archTrunc": "math.trunc",
}
// createAlias implements the function (in the builder) as a call to the alias
// function.
func (b *builder) createAlias(alias llvm.Value) {
if b.Debug {
if b.fn.Syntax() != nil {
// Create debug info file if present.
b.difunc = b.attachDebugInfo(b.fn)
}
pos := b.program.Fset.Position(b.fn.Pos())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
entryBlock := llvm.AddBasicBlock(b.llvmFn, "entry")
b.SetInsertPointAtEnd(entryBlock)
if b.llvmFn.Type() != alias.Type() {
b.addError(b.fn.Pos(), "alias function should have the same type as aliasee "+alias.Name())
b.CreateUnreachable()
return
}
result := b.CreateCall(alias, b.llvmFn.Params(), "")
if result.Type().TypeKind() == llvm.VoidTypeKind {
b.CreateRetVoid()
} else {
b.CreateRet(result)
}
}
+53
View File
@@ -101,6 +101,55 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
}
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
// conversions. This conversion was added in Go 1.17. For details, see:
// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) {
// From the spec:
// > If the length of the slice is less than the length of the array, a
// > run-time panic occurs.
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
}
// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
// function must panic if the ptr/len parameters are invalid.
func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, lenType *types.Basic) {
// From the documentation of unsafe.Slice:
// > At run time, if len is negative, or if ptr is nil and len is not
// > zero, a run-time panic occurs.
// However, in practice, it is also necessary to check that the length is
// not too big that a GEP wouldn't be possible without wrapping the pointer.
// These two checks (non-negative and not too big) can be merged into one
// using an unsiged greater than.
// Make sure the len value is at least as big as a uintptr.
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
if lenType.Info()&types.IsUnsigned != 0 {
len = b.CreateZExt(len, b.uintptrType, "")
} else {
len = b.CreateSExt(len, b.uintptrType, "")
}
}
// Determine the maximum slice size, and therefore the maximum value of the
// len parameter.
maxSize := b.maxSliceSize(ptr.Type().ElementType())
maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false)
// Do the check. By using unsigned greater than for the length check, signed
// negative values are also checked (which are very large numbers when
// interpreted as signed values).
zero := llvm.ConstInt(len.Type(), 0, false)
lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "")
ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "")
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
assert = b.CreateOr(assert, lenOutOfBounds, "")
b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic")
}
// createChanBoundsCheck creates a bounds check before creating a new channel to
// check that the value is not too big for runtime.chanMake.
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
@@ -162,6 +211,10 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
case *ssa.Alloc:
// An alloc is never nil.
return
case *ssa.FreeVar:
// A free variable is allocated in a parent function and is thus never
// nil.
return
case *ssa.IndexAddr:
// This pointer is the result of an index operation into a slice or
// array. Such slices/arrays are already bounds checked so the pointer
+28 -9
View File
@@ -32,8 +32,15 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// store value-to-send
valueType := b.getLLVMType(instr.X.Type())
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
}
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
@@ -46,7 +53,9 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if !isZeroSize {
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
}
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
@@ -56,7 +65,14 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
ch := b.getValue(unop.X)
// Allocate memory to receive into.
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
}
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
@@ -64,9 +80,14 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
// Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
received := b.CreateLoad(valueAlloca, "chan.received")
var received llvm.Value
if isZeroSize {
received = llvm.ConstNull(valueType)
} else {
received = b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
@@ -116,7 +137,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// determine the receive buffer size and alignment.
recvbufSize := uint64(0)
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
@@ -133,7 +153,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align
}
hasReceives = true
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
@@ -150,7 +169,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.i8ptrType)
if hasReceives {
if recvbufSize != 0 {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
+190 -65
View File
@@ -23,7 +23,7 @@ import (
// Version of the compiler pacakge. Must be incremented each time the compiler
// package changes in a way that affects the generated LLVM module.
// This version is independent of the TinyGo version number.
const Version = 9 // last change: implement reflect.New()
const Version = 19 // last change: fix channel ops with zero values
func init() {
llvm.InitializeAllTargets()
@@ -59,6 +59,7 @@ type Config struct {
DefaultStackSize uint64
NeedsStackObjects bool
Debug bool // Whether to emit debug information in the LLVM module.
LLVMFeatures string
}
// compilerContext contains function-independent data that should still be
@@ -73,6 +74,7 @@ type compilerContext struct {
cu llvm.Metadata
difiles map[string]llvm.Metadata
ditypes map[types.Type]llvm.Metadata
llvmTypes map[types.Type]llvm.Type
machine llvm.TargetMachine
targetData llvm.TargetData
intType llvm.Type
@@ -93,6 +95,7 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
DumpSSA: dumpSSA,
difiles: make(map[string]llvm.Metadata),
ditypes: make(map[types.Type]llvm.Metadata),
llvmTypes: make(map[types.Type]llvm.Type),
machine: machine,
targetData: machine.CreateTargetData(),
astComments: map[string]*ast.CommentGroup{},
@@ -185,7 +188,12 @@ func NewTargetMachine(config *Config) (llvm.TargetMachine, error) {
if err != nil {
return llvm.TargetMachine{}, err
}
features := strings.Join(config.Features, ",")
feat := config.Features
if len(config.LLVMFeatures) > 0 {
feat = append(feat, config.LLVMFeatures)
}
features := strings.Join(feat, ",")
var codeModel llvm.CodeModel
var relocationModel llvm.RelocMode
@@ -309,10 +317,23 @@ func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type {
return c.getLLVMType(typ)
}
// getLLVMType creates and returns a LLVM type for a Go type. In the case of
// named struct types (or Go types implemented as named LLVM structs such as
// strings) it also creates it first if necessary.
// getLLVMType returns a LLVM type for a Go type. It doesn't recreate already
// created types. This is somewhat important for performance, but especially
// important for named struct types (which should only be created once).
func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
// Try to load the LLVM type from the cache.
if t, ok := c.llvmTypes[goType]; ok {
return t
}
// Not already created, so adding this type to the cache.
llvmType := c.makeLLVMType(goType)
c.llvmTypes[goType] = llvmType
return llvmType
}
// makeLLVMType creates a LLVM type for a Go type. Don't call this, use
// getLLVMType instead.
func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
switch typ := goType.(type) {
case *types.Array:
elemType := c.getLLVMType(typ.Elem())
@@ -361,12 +382,10 @@ func (c *compilerContext) getLLVMType(goType types.Type) llvm.Type {
// LLVM. This is because it is otherwise impossible to create
// self-referencing types such as linked lists.
llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name()
llvmType := c.mod.GetTypeByName(llvmName)
if llvmType.IsNil() {
llvmType = c.ctx.StructCreateNamed(llvmName)
underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
}
llvmType := c.ctx.StructCreateNamed(llvmName)
c.llvmTypes[goType] = llvmType // avoid infinite recursion
underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
return llvmType
}
return c.getLLVMType(typ.Underlying())
@@ -709,11 +728,11 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
// Create the function definition.
b := newBuilder(c, irbuilder, member)
if member.Blocks == nil {
continue // external function
}
// Create the function definition.
b := newBuilder(c, irbuilder, member)
b.createFunction()
case *ssa.Type:
if types.IsInterface(member.Type()) {
@@ -752,10 +771,36 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
case *ssa.Global:
// Global variable.
info := c.getGlobalInfo(member)
global := c.getGlobal(member)
if !info.extern {
global := c.getGlobal(member)
global.SetInitializer(llvm.ConstNull(global.Type().ElementType()))
global.SetVisibility(llvm.HiddenVisibility)
if info.section != "" {
global.SetSection(info.section)
}
}
}
}
// Add forwarding functions for functions that would otherwise be
// implemented in assembly.
for _, name := range members {
member := pkg.Members[name]
switch member := member.(type) {
case *ssa.Function:
if member.Blocks != nil {
continue // external function
}
info := c.getFunctionInfo(member)
if aliasName, ok := stdlibAliases[info.linkName]; ok {
alias := c.mod.NamedFunction(aliasName)
if alias.IsNil() {
// Shouldn't happen, but perhaps best to just ignore.
// The error will be a link error, if there is an error.
continue
}
b := newBuilder(c, irbuilder, member)
b.createAlias(alias)
}
}
}
@@ -781,6 +826,9 @@ func (b *builder) createFunction() {
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
b.llvmFn.SetUnnamedAddr(true)
}
if b.info.section != "" {
b.llvmFn.SetSection(b.info.section)
}
if b.info.exported && strings.HasPrefix(b.Triple, "wasm") {
// Set the exported name. This is necessary for WebAssembly because
// otherwise the function is not exported.
@@ -964,11 +1012,61 @@ func (b *builder) createFunction() {
}
}
// posser is an interface that's implemented by both ssa.Value and
// ssa.Instruction. It is implemented by everything that has a Pos() method,
// which is all that getPos() needs.
type posser interface {
Pos() token.Pos
}
// getPos returns position information for a ssa.Value or ssa.Instruction.
//
// Not all instructions have position information, especially when they're
// implicit (such as implicit casts or implicit returns at the end of a
// function). In these cases, it makes sense to try a bit harder to guess what
// the position really should be.
func getPos(val posser) token.Pos {
pos := val.Pos()
if pos != token.NoPos {
// Easy: position is known.
return pos
}
// No position information is known.
switch val := val.(type) {
case *ssa.MakeInterface:
return getPos(val.X)
case *ssa.MakeClosure:
return val.Fn.(*ssa.Function).Pos()
case *ssa.Return:
syntax := val.Parent().Syntax()
if syntax != nil {
// non-synthetic
return syntax.End()
}
return token.NoPos
case *ssa.FieldAddr:
return getPos(val.X)
case *ssa.IndexAddr:
return getPos(val.X)
case *ssa.Slice:
return getPos(val.X)
case *ssa.Store:
return getPos(val.Addr)
case *ssa.Extract:
return getPos(val.Tuple)
default:
// This is reachable, for example with *ssa.Const, *ssa.If, and
// *ssa.Jump. They might be implemented in some way in the future.
return token.NoPos
}
}
// createInstruction builds the LLVM IR equivalent instructions for the
// particular Go SSA instruction.
func (b *builder) createInstruction(instr ssa.Instruction) {
if b.Debug {
pos := b.program.Fset.Position(instr.Pos())
pos := b.program.Fset.Position(getPos(instr))
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
@@ -994,52 +1092,8 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
case *ssa.Defer:
b.createDefer(instr)
case *ssa.Go:
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.getValue(param))
}
// Start a new goroutine.
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
// Goroutine call is regular function call. No context is necessary.
context = llvm.Undef(b.i8ptrType)
case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue := b.getValue(value)
context = b.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
params = append(params, context) // context parameter
b.createGoInstruction(b.getFunction(callee), params, "", callee.Pos())
} else if !instr.Call.IsInvoke() {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
funcPtr, context := b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context) // context parameter
switch b.Scheduler {
case "none", "coroutines":
// There are no additional parameters needed for the goroutine start operation.
case "tasks":
// Add the function pointer as a parameter to start the goroutine.
params = append(params, funcPtr)
default:
panic("unknown scheduler type")
}
b.createGoInstruction(funcPtr, params, b.fn.RelString(nil), instr.Pos())
} else {
b.addError(instr.Pos(), "todo: go on interface call")
}
b.createGo(instr)
case *ssa.If:
cond := b.getValue(instr.Cond)
block := instr.Block()
@@ -1252,6 +1306,38 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
case "ssa:wrapnilchk":
// TODO: do an actual nil check?
return argValues[0], nil
// Builtins from the unsafe package.
case "Add": // unsafe.Add
// This is basically just a GEP operation.
// Note: the pointer is always of type *i8.
ptr := argValues[0]
len := argValues[1]
return b.CreateGEP(ptr, []llvm.Value{len}, ""), nil
case "Slice": // unsafe.Slice
// This creates a slice from a pointer and a length.
// Note that the exception mentioned in the documentation (if the
// pointer and length are nil, the slice is also nil) is trivially
// already the case.
ptr := argValues[0]
len := argValues[1]
slice := llvm.Undef(b.ctx.StructType([]llvm.Type{
ptr.Type(),
b.uintptrType,
b.uintptrType,
}, false))
b.createUnsafeSliceCheck(ptr, len, argTypes[1].Underlying().(*types.Basic))
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
// Too small, zero-extend len.
len = b.CreateZExt(len, b.uintptrType, "")
} else if len.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
// Too big, truncate len.
len = b.CreateTrunc(len, b.uintptrType, "")
}
slice = b.CreateInsertValue(slice, ptr, 0, "")
slice = b.CreateInsertValue(slice, len, 1, "")
slice = b.CreateInsertValue(slice, len, 2, "")
return slice, nil
default:
return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName)
}
@@ -1280,6 +1366,11 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.createMemoryCopyCall(fn, instr.Args)
case name == "runtime.memzero":
return b.createMemoryZeroCall(instr.Args)
case name == "math.Ceil" || name == "math.Floor" || name == "math.Sqrt" || name == "math.Trunc":
result, ok := b.createMathOp(instr)
if ok {
return result, nil
}
case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
return b.createInlineAsm(instr.Args)
case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/arm64.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
@@ -1292,6 +1383,8 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
return b.emitCSROperation(instr)
case strings.HasPrefix(name, "syscall.Syscall"):
return b.createSyscall(instr)
case strings.HasPrefix(name, "syscall.rawSyscallNoError"):
return b.createRawSyscallNoError(instr)
case strings.HasPrefix(name, "runtime/volatile.Load"):
return b.createVolatileLoad(instr)
case strings.HasPrefix(name, "runtime/volatile.Store"):
@@ -1391,6 +1484,28 @@ func (b *builder) getValue(expr ssa.Value) llvm.Value {
}
}
// maxSliceSize determines the maximum size a slice of the given element type
// can be.
func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 {
// Calculate ^uintptr(0), which is the max value that fits in uintptr.
maxPointerValue := llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)).ZExtValue()
// Calculate (^uint(0))/2, which is the max value that fits in an int.
maxIntegerValue := llvm.ConstNot(llvm.ConstInt(c.intType, 0, false)).ZExtValue() / 2
// Determine the maximum allowed size for a slice. The biggest possible
// pointer (starting from 0) would be maxPointerValue*sizeof(elementType) so
// divide by the element type to get the real maximum size.
maxSize := maxPointerValue / c.targetData.TypeAllocSize(elementType)
// len(slice) is an int. Make sure the length remains small enough to fit in
// an int.
if maxSize > maxIntegerValue {
maxSize = maxIntegerValue
}
return maxSize
}
// createExpr translates a Go SSA expression to LLVM IR. This can be zero, one,
// or multiple LLVM IR instructions and/or runtime calls.
func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
@@ -1604,10 +1719,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
elemSize := b.targetData.TypeAllocSize(llvmElemType)
elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false)
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in
// uintptr if uintptr were signed.
maxSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false))
if elemSize > maxSize.ZExtValue() {
maxSize := b.maxSliceSize(llvmElemType)
if elemSize > maxSize {
// This seems to be checked by the typechecker already, but let's
// check it again just to be sure.
return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize))
@@ -1616,7 +1729,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// Bounds checking.
lenType := expr.Len.Type().Underlying().(*types.Basic)
capType := expr.Cap.Type().Underlying().(*types.Basic)
b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
maxSizeValue := llvm.ConstInt(b.uintptrType, maxSize, false)
b.createSliceBoundsCheck(maxSizeValue, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
// Allocate the backing array.
sliceCapCast, err := b.createConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
@@ -1859,6 +1973,17 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
default:
return llvm.Value{}, b.makeError(expr.Pos(), "unknown slice type: "+typ.String())
}
case *ssa.SliceToArrayPointer:
// Conversion from a slice to an array pointer, as the name clearly
// says. This requires a runtime check to make sure the slice is at
// least as big as the array.
slice := b.getValue(expr.X)
sliceLen := b.CreateExtractValue(slice, 1, "")
arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len()
b.createSliceToArrayPointerCheck(sliceLen, arrayLen)
ptr := b.CreateExtractValue(slice, 0, "")
ptr = b.CreateBitCast(ptr, b.getLLVMType(expr.Type()), "")
return ptr, nil
case *ssa.TypeAssert:
return b.createTypeAssert(expr), nil
case *ssa.UnOp:
+70 -37
View File
@@ -9,6 +9,7 @@ import (
"testing"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/loader"
"tinygo.org/x/go-llvm"
)
@@ -16,9 +17,16 @@ import (
// Pass -update to go test to update the output of the test files.
var flagUpdate = flag.Bool("update", false, "update tests based on test output")
type testCase struct {
file string
target string
}
// Basic tests for the compiler. Build some Go files and compare the output with
// the expected LLVM IR for regression testing.
func TestCompiler(t *testing.T) {
t.Parallel()
// Check LLVM version.
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if err != nil {
@@ -32,43 +40,64 @@ func TestCompiler(t *testing.T) {
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
}
target, err := compileopts.LoadTarget("i686--linux")
if err != nil {
t.Fatal("failed to load target:", err)
}
config := &compileopts.Config{
Options: &compileopts.Options{},
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
tests := []testCase{
{"basic.go", ""},
{"pointer.go", ""},
{"slice.go", ""},
{"string.go", ""},
{"float.go", ""},
{"interface.go", ""},
{"func.go", ""},
{"pragma.go", ""},
{"goroutine.go", "wasm"},
{"goroutine.go", "cortex-m-qemu"},
{"channel.go", ""},
{"intrinsics.go", "cortex-m-qemu"},
{"intrinsics.go", "wasm"},
}
tests := []string{
"basic.go",
"pointer.go",
"slice.go",
"string.go",
"float.go",
"interface.go",
"func.go",
_, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
if err != nil {
t.Fatal("could not read Go version:", err)
}
if minor >= 17 {
tests = append(tests, testCase{"go1.17.go", ""})
}
for _, testCase := range tests {
t.Run(testCase, func(t *testing.T) {
for _, tc := range tests {
name := tc.file
targetString := "wasm"
if tc.target != "" {
targetString = tc.target
name = tc.file + "-" + tc.target
}
t.Run(name, func(t *testing.T) {
target, err := compileopts.LoadTarget(targetString)
if err != nil {
t.Fatal("failed to load target:", err)
}
config := &compileopts.Config{
Options: &compileopts.Options{},
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
FuncImplementation: config.FuncImplementation(),
AutomaticStackSize: config.AutomaticStackSize(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
}
// Load entire program AST into memory.
lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, config.ClangHeaders, types.Config{
lprogram, err := loader.Load(config, []string{"./testdata/" + tc.file}, config.ClangHeaders, types.Config{
Sizes: Sizes(machine),
})
if err != nil {
@@ -76,13 +105,13 @@ func TestCompiler(t *testing.T) {
}
err = lprogram.Parse()
if err != nil {
t.Fatalf("could not parse test case %s: %s", testCase, err)
t.Fatalf("could not parse test case %s: %s", tc.file, err)
}
// Compile AST to IR.
program := lprogram.LoadSSA()
pkg := lprogram.MainPkg()
mod, errs := CompilePackage(testCase, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
mod, errs := CompilePackage(tc.file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
if errs != nil {
for _, err := range errs {
t.Error(err)
@@ -105,18 +134,22 @@ func TestCompiler(t *testing.T) {
}
funcPasses.FinalizeFunc()
outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll"
outFilePrefix := tc.file[:len(tc.file)-3]
if tc.target != "" {
outFilePrefix += "-" + tc.target
}
outPath := "./testdata/" + outFilePrefix + ".ll"
// Update test if needed. Do not check the result.
if *flagUpdate {
err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666)
err := ioutil.WriteFile(outPath, []byte(mod.String()), 0666)
if err != nil {
t.Error("failed to write updated output file:", err)
}
return
}
expected, err := ioutil.ReadFile(outfile)
expected, err := ioutil.ReadFile(outPath)
if err != nil {
t.Fatal("failed to read golden file:", err)
}
+117 -15
View File
@@ -5,25 +5,110 @@ package compiler
import (
"go/token"
"go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createGoInstruction starts a new goroutine with the provided function pointer
// and parameters.
// In general, you should pass all regular parameters plus the context parameter.
// There is one exception: the task-based scheduler needs to have the function
// pointer passed in as a parameter too in addition to the context.
//
// Because a go statement doesn't return anything, return undef.
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
// createGo emits code to start a new goroutine.
func (b *builder) createGo(instr *ssa.Go) {
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.getValue(param))
}
var prefix string
var funcPtr llvm.Value
hasContext := false
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
// Goroutine call is regular function call. No context is necessary.
if b.Scheduler == "coroutines" {
// The context parameter is assumed to be always present in the
// coroutines scheduler.
context = llvm.Undef(b.i8ptrType)
}
case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions.
funcValue := b.getValue(value)
context = b.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
if !context.IsNil() {
params = append(params, context) // context parameter
hasContext = true
}
funcPtr = b.getFunction(callee)
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
// We cheat. None of the builtins do any long or blocking operation, so
// we might as well run these builtins right away without the program
// noticing the difference.
// Possible exceptions:
// - copy: this is a possibly long operation, but not a blocking
// operation. Semantically it makes no difference to run it right
// away (not in a goroutine). However, in practice it makes no sense
// to run copy in a goroutine as there is no way to (safely) know
// when it is finished.
// - panic: the error message would appear in the parent goroutine.
// But because `go panic("err")` would halt the program anyway
// (there is no recover), panicking right away would give the same
// behavior as creating a goroutine, switching the scheduler to that
// goroutine, and panicking there. So this optimization seems
// correct.
// - recover: because it runs in a new goroutine, it is never a
// deferred function. Thus this is a no-op.
if builtin.Name() == "recover" {
// This is a no-op, even in a deferred function:
// go recover()
return
}
var argTypes []types.Type
var argValues []llvm.Value
for _, arg := range instr.Call.Args {
argTypes = append(argTypes, arg.Type())
argValues = append(argValues, b.getValue(arg))
}
b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
return
} else if !instr.Call.IsInvoke() {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context) // context parameter
hasContext = true
switch b.Scheduler {
case "none", "coroutines":
// There are no additional parameters needed for the goroutine start operation.
case "tasks":
// Add the function pointer as a parameter to start the goroutine.
params = append(params, funcPtr)
default:
panic("unknown scheduler type")
}
prefix = b.fn.RelString(nil)
} else {
b.addError(instr.Pos(), "todo: go on interface call")
return
}
paramBundle := b.emitPointerPack(params)
var callee, stackSize llvm.Value
switch b.Scheduler {
case "none", "tasks":
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
callee = b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos())
if b.AutomaticStackSize {
// The stack size is not known until after linking. Call a dummy
// function that will be replaced with a load from a special ELF
@@ -34,6 +119,9 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
} else {
// The stack size is fixed at compile time. By emitting it here as a
// constant, it can be optimized.
if b.Scheduler == "tasks" && b.DefaultStackSize == 0 {
b.addError(instr.Pos(), "default stack size for goroutines is not set")
}
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
}
case "coroutines":
@@ -46,7 +134,6 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
}
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
// createGoroutineStartWrapper creates a wrapper for the task-based
@@ -67,7 +154,12 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
// allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a
// return value.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
//
// The hasContext parameter indicates whether the context parameter (the second
// to last parameter of the function) is used for this wrapper. If hasContext is
// false, the parameter bundle is assumed to have no context parameter and undef
// is passed instead.
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
var wrapper llvm.Value
builder := c.ctx.NewBuilder()
@@ -114,8 +206,15 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes()
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
params = append(params, llvm.Undef(c.i8ptrType))
paramTypes = paramTypes[:len(paramTypes)-1] // strip parentHandle parameter
if !hasContext {
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
}
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
if !hasContext {
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
}
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy parentHandle parameter
// Create the call.
builder.CreateCall(fn, params, "")
@@ -177,8 +276,11 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// Get the function pointer.
fnPtr := params[len(params)-1]
// Ignore the last param, which isn't used anymore.
// TODO: avoid this extra "parent handle" parameter in most functions.
// The last parameter in the packed object has somewhat of a dual role.
// Inside the parameter bundle it's the function pointer, stored right
// after the context pointer. But in the IR call instruction, it's the
// parentHandle function that's always undef outside of the coroutines
// scheduler. Thus, make the parameter undef here.
params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call.
+13 -2
View File
@@ -311,10 +311,21 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
// used during the interface lowering pass.
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
signature := methodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
var globalName string
if token.IsExported(method.Name()) {
globalName = "reflect/methods." + signature
} else {
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
}
signatureGlobal := c.mod.NamedGlobal(globalName)
if signatureGlobal.IsNil() {
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
// TODO: put something useful in these globals, such as the method
// signature. Useful to one day implement reflect.Value.Method(n).
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false))
signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
signatureGlobal.SetGlobalConstant(true)
signatureGlobal.SetAlignment(1)
}
return signatureGlobal
}
+1 -4
View File
@@ -41,10 +41,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// type are lowered in the interrupt lowering pass.
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
}
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
global.SetVisibility(llvm.HiddenVisibility)
global.SetGlobalConstant(true)
+54
View File
@@ -48,3 +48,57 @@ func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
b.CreateCall(llvmFn, params, "")
return llvm.Value{}, nil
}
var mathToLLVMMapping = map[string]string{
"math.Sqrt": "llvm.sqrt.f64",
"math.Floor": "llvm.floor.f64",
"math.Ceil": "llvm.ceil.f64",
"math.Trunc": "llvm.trunc.f64",
}
// createMathOp tries to lower the given call as a LLVM math intrinsic, if
// possible. It returns the call result if possible, and a boolean whether it
// succeeded. If it doesn't succeed, the architecture doesn't support the given
// intrinsic.
func (b *builder) createMathOp(call *ssa.CallCommon) (llvm.Value, bool) {
// Check whether this intrinsic is supported on the given GOARCH.
// If it is unsupported, this can have two reasons:
//
// 1. LLVM can expand the intrinsic inline (using float instructions), but
// the result doesn't pass the tests of the math package.
// 2. LLVM cannot expand the intrinsic inline, will therefore lower it as a
// libm function call, but the libm function call also fails the math
// package tests.
//
// Whatever the implementation, it must pass the tests in the math package
// so unfortunately only the below intrinsic+architecture combinations are
// supported.
name := call.StaticCallee().RelString(nil)
switch name {
case "math.Ceil", "math.Floor", "math.Trunc":
if b.GOARCH != "wasm" && b.GOARCH != "arm64" {
return llvm.Value{}, false
}
case "math.Sqrt":
if b.GOARCH != "wasm" && b.GOARCH != "amd64" && b.GOARCH != "386" {
return llvm.Value{}, false
}
default:
return llvm.Value{}, false // only the above functions are supported.
}
llvmFn := b.mod.NamedFunction(mathToLLVMMapping[name])
if llvmFn.IsNil() {
// The intrinsic doesn't exist yet, so declare it.
// At the moment, all supported intrinsics have the form "double
// foo(double %x)" so we can hardcode the signature here.
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
llvmFn = llvm.AddFunction(b.mod, mathToLLVMMapping[name], llvmType)
}
// Create a call to the intrinsic.
args := make([]llvm.Value, len(call.Args))
for i, arg := range call.Args {
args[i] = b.getValue(arg)
}
return b.CreateCall(llvmFn, args, ""), true
}
+10
View File
@@ -24,6 +24,7 @@ type functionInfo struct {
module string // go:wasm-module
importName string // go:linkname, go:export - The name the developer assigns
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
section string // go:section - object file section name
exported bool // go:export, CGo
nobounds bool // go:nobounds
variadic bool // go:variadic (CGo only)
@@ -270,6 +271,10 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2]
}
case "//go:section":
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
info.section = parts[1]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
@@ -325,6 +330,7 @@ type globalInfo struct {
linkName string // go:extern
extern bool // go:extern
align int // go:align
section string // go:section
}
// loadASTComments loads comments on globals from the AST, for use later in the
@@ -438,6 +444,10 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
if err == nil {
info.align = align
}
case "//go:section":
if len(parts) == 2 {
info.section = parts[1]
}
}
}
}
+30 -8
View File
@@ -10,11 +10,11 @@ import (
"tinygo.org/x/go-llvm"
)
// createSyscall emits an inline system call instruction, depending on the
// target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// createRawSyscall creates a system call with the provided system call number
// and returns the result as a single integer (the system call result). The
// result is not further interpreted.
func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
num := b.getValue(call.Args[0])
var syscallResult llvm.Value
switch {
case b.GOARCH == "amd64":
if b.GOOS == "darwin" {
@@ -57,7 +57,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "")
return b.CreateCall(target, args, ""), nil
case b.GOARCH == "386" && b.GOOS == "linux":
// Sources:
// syscall(2) man page
@@ -83,7 +83,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
}
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = b.CreateCall(target, args, "")
return b.CreateCall(target, args, ""), nil
case b.GOARCH == "arm" && b.GOOS == "linux":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
@@ -115,7 +115,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
}
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "")
return b.CreateCall(target, args, ""), nil
case b.GOARCH == "arm64" && b.GOOS == "linux":
// Source: syscall(2) man page.
args := []llvm.Value{}
@@ -147,10 +147,19 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
syscallResult = b.CreateCall(target, args, "")
return b.CreateCall(target, args, ""), nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
}
}
// createSyscall emits instructions for the syscall.Syscall* family of
// functions, depending on the target OS/arch.
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
switch b.GOOS {
case "linux", "freebsd":
// Return values: r0, r1 uintptr, err Errno
@@ -190,3 +199,16 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
}
}
// createRawSyscallNoError emits instructions for the Linux-specific
// syscall.rawSyscallNoError function.
func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) {
syscallResult, err := b.createRawSyscall(call)
if err != nil {
return syscallResult, err
}
retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false))
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "")
return retval, nil
}
+15
View File
@@ -55,3 +55,18 @@ func complexMul(x, y complex64) complex64 {
}
// TODO: complexDiv (requires runtime call)
// A type 'kv' also exists in function foo. Test that these two types don't
// conflict with each other.
type kv struct {
v float32
}
func foo(a *kv) {
// Define a new 'kv' type.
type kv struct {
v byte
}
// Use this type.
func(b *kv) {}(nil)
}
+16 -2
View File
@@ -1,7 +1,10 @@
; ModuleID = 'basic.go'
source_filename = "basic.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%main.kv = type { float }
%main.kv.0 = type { i8 }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@@ -98,3 +101,14 @@ entry:
%7 = insertvalue { float, float } %6, float %5, 1
ret { float, float } %7
}
define hidden void @main.foo(%main.kv* dereferenceable_or_null(4) %a, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @"main.foo$1"(%main.kv.0* null, i8* undef, i8* undef)
ret void
}
define hidden void @"main.foo$1"(%main.kv.0* dereferenceable_or_null(1) %b, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
+25
View File
@@ -0,0 +1,25 @@
package main
func chanIntSend(ch chan int) {
ch <- 3
}
func chanIntRecv(ch chan int) {
<-ch
}
func chanZeroSend(ch chan struct{}) {
ch <- struct{}{}
}
func chanZeroRecv(ch chan struct{}) {
<-ch
}
func selectZeroRecv(ch1 chan int, ch2 chan struct{}) {
select {
case ch1 <- 1:
case <-ch2:
default:
}
}
+114
View File
@@ -0,0 +1,114 @@
; ModuleID = 'channel.go'
source_filename = "channel.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i8* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.chanIntSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.value = alloca i32, align 4
%chan.value.bitcast = bitcast i32* %chan.value to i8*
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
store i32 3, i32* %chan.value, align 4
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @runtime.chanSend(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
ret void
}
; Function Attrs: argmemonly nounwind willreturn
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #0
declare void @runtime.chanSend(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
; Function Attrs: argmemonly nounwind willreturn
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #0
define hidden void @main.chanIntRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.value = alloca i32, align 4
%chan.value.bitcast = bitcast i32* %chan.value to i8*
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
declare i1 @runtime.chanRecv(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
define hidden void @main.chanZeroSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @runtime.chanSend(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
define hidden void @main.chanZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
define hidden void @main.selectZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch1, %runtime.channel* dereferenceable_or_null(32) %ch2, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
%select.send.value = alloca i32, align 4
store i32 1, i32* %select.send.value, align 4
%select.states.alloca.bitcast = bitcast [2 x %runtime.chanSelectState]* %select.states.alloca to i8*
call void @llvm.lifetime.start.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
%.repack = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 0
store %runtime.channel* %ch1, %runtime.channel** %.repack, align 8
%.repack1 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 1
%0 = bitcast i8** %.repack1 to i32**
store i32* %select.send.value, i32** %0, align 4
%.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 0
store %runtime.channel* %ch2, %runtime.channel** %.repack3, align 8
%.repack4 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 1
store i8* null, i8** %.repack4, align 4
%select.states = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0
%select.result = call { i32, i1 } @runtime.tryChanSelect(i8* undef, %runtime.chanSelectState* nonnull %select.states, i32 2, i32 2, i8* undef, i8* null)
call void @llvm.lifetime.end.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
%1 = extractvalue { i32, i1 } %select.result, 0
%2 = icmp eq i32 %1, 0
br i1 %2, label %select.done, label %select.next
select.done: ; preds = %select.body, %select.next, %entry
ret void
select.next: ; preds = %entry
%3 = icmp eq i32 %1, 1
br i1 %3, label %select.body, label %select.done
select.body: ; preds = %select.next
br label %select.done
}
declare { i32, i1 } @runtime.tryChanSelect(i8*, %runtime.chanSelectState*, i32, i32, i8*, i8*)
attributes #0 = { argmemonly nounwind willreturn }
+2 -2
View File
@@ -1,7 +1,7 @@
; ModuleID = 'float.go'
source_filename = "float.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
+2 -2
View File
@@ -1,7 +1,7 @@
; ModuleID = 'func.go'
source_filename = "func.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
+41
View File
@@ -0,0 +1,41 @@
package main
// Test changes to the language introduced in Go 1.17.
// For details, see: https://tip.golang.org/doc/go1.17#language
// These tests should be merged into the regular slice tests once Go 1.17 is the
// minimun Go version for TinyGo.
import "unsafe"
func Add32(p unsafe.Pointer, len int) unsafe.Pointer {
return unsafe.Add(p, len)
}
func Add64(p unsafe.Pointer, len int64) unsafe.Pointer {
return unsafe.Add(p, len)
}
func SliceToArray(s []int) *[4]int {
return (*[4]int)(s)
}
func SliceToArrayConst() *[4]int {
s := make([]int, 6)
return (*[4]int)(s)
}
func SliceInt(ptr *int, len int) []int {
return unsafe.Slice(ptr, len)
}
func SliceUint16(ptr *byte, len uint16) []byte {
return unsafe.Slice(ptr, len)
}
func SliceUint64(ptr *int, len uint64) []int {
return unsafe.Slice(ptr, len)
}
func SliceInt64(ptr *int, len int64) []int {
return unsafe.Slice(ptr, len)
}
+136
View File
@@ -0,0 +1,136 @@
; ModuleID = 'go1.17.go'
source_filename = "go1.17.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden i8* @main.Add32(i8* %p, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = getelementptr i8, i8* %p, i32 %len
ret i8* %0
}
define hidden i8* @main.Add64(i8* %p, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = trunc i64 %len to i32
%1 = getelementptr i8, i8* %p, i32 %0
ret i8* %1
}
define hidden [4 x i32]* @main.SliceToArray(i32* %s.data, i32 %s.len, i32 %s.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ult i32 %s.len, 4
br i1 %0, label %slicetoarray.throw, label %slicetoarray.next
slicetoarray.throw: ; preds = %entry
call void @runtime.sliceToArrayPointerPanic(i8* undef, i8* null)
unreachable
slicetoarray.next: ; preds = %entry
%1 = bitcast i32* %s.data to [4 x i32]*
ret [4 x i32]* %1
}
declare void @runtime.sliceToArrayPointerPanic(i8*, i8*)
define hidden [4 x i32]* @main.SliceToArrayConst(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%makeslice = call i8* @runtime.alloc(i32 24, i8* undef, i8* null)
br i1 false, label %slicetoarray.throw, label %slicetoarray.next
slicetoarray.throw: ; preds = %entry
unreachable
slicetoarray.next: ; preds = %entry
%0 = bitcast i8* %makeslice to [4 x i32]*
ret [4 x i32]* %0
}
define hidden { i32*, i32, i32 } @main.SliceInt(i32* dereferenceable_or_null(4) %ptr, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ugt i32 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i32 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%6 = insertvalue { i32*, i32, i32 } %5, i32 %len, 1
%7 = insertvalue { i32*, i32, i32 } %6, i32 %len, 2
ret { i32*, i32, i32 } %7
}
declare void @runtime.unsafeSlicePanic(i8*, i8*)
define hidden { i8*, i32, i32 } @main.SliceUint16(i8* dereferenceable_or_null(1) %ptr, i16 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp eq i8* %ptr, null
%1 = icmp ne i16 %len, 0
%2 = and i1 %0, %1
br i1 %2, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%3 = zext i16 %len to i32
%4 = insertvalue { i8*, i32, i32 } undef, i8* %ptr, 0
%5 = insertvalue { i8*, i32, i32 } %4, i32 %3, 1
%6 = insertvalue { i8*, i32, i32 } %5, i32 %3, 2
ret { i8*, i32, i32 } %6
}
define hidden { i32*, i32, i32 } @main.SliceUint64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ugt i64 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i64 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = trunc i64 %len to i32
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
ret { i32*, i32, i32 } %8
}
define hidden { i32*, i32, i32 } @main.SliceInt64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = icmp ugt i64 %len, 1073741823
%1 = icmp eq i32* %ptr, null
%2 = icmp ne i64 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
unsafe.Slice.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
unreachable
unsafe.Slice.next: ; preds = %entry
%5 = trunc i64 %len to i32
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
ret { i32*, i32, i32 } %8
}
+150
View File
@@ -0,0 +1,150 @@
; ModuleID = 'goroutine.go'
source_filename = "goroutine.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i32, i32* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null)
ret void
}
declare void @main.regularFunction(i32, i8*, i8*)
define linkonce_odr void @"main.regularFunction$gowrapper"(i8* %0) unnamed_addr #0 {
entry:
%unpack.int = ptrtoint i8* %0 to i32
call void @main.regularFunction(i32 %unpack.int, i8* undef, i8* undef)
ret void
}
declare i32 @"internal/task.getGoroutineStackSize"(i32, i8*, i8*)
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null)
ret void
}
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #1 {
entry:
%unpack.int = ptrtoint i8* %0 to i32
call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, i8* undef, i8* undef)
ret void
}
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%n = call i8* @runtime.alloc(i32 4, i8* undef, i8* null)
%0 = bitcast i8* %n to i32*
store i32 3, i32* %0, align 4
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %n, i8** %4, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* nonnull %1, i32 %stacksize, i8* undef, i8* null)
%5 = load i32, i32* %0, align 4
call void @runtime.printint32(i32 %5, i8* undef, i8* null)
ret void
}
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%unpack.ptr = bitcast i8* %context to i32*
store i32 7, i32* %unpack.ptr, align 4
ret void
}
define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #2 {
entry:
%1 = bitcast i8* %0 to i32*
%2 = load i32, i32* %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
call void @"main.closureFunctionGoroutine$1"(i32 %2, i8* %5, i8* undef)
ret void
}
declare void @runtime.printint32(i32, i8*, i8*)
define hidden void @main.funcGoroutine(i8* %fn.context, void (i32, i8*, i8*)* %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i8* @runtime.alloc(i32 12, i8* undef, i8* null)
%1 = bitcast i8* %0 to i32*
store i32 5, i32* %1, align 4
%2 = getelementptr inbounds i8, i8* %0, i32 4
%3 = bitcast i8* %2 to i8**
store i8* %fn.context, i8** %3, align 4
%4 = getelementptr inbounds i8, i8* %0, i32 8
%5 = bitcast i8* %4 to void (i32, i8*, i8*)**
store void (i32, i8*, i8*)* %fn.funcptr, void (i32, i8*, i8*)** %5, align 4
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* undef, i8* undef)
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* nonnull %0, i32 %stacksize, i8* undef, i8* null)
ret void
}
define linkonce_odr void @main.funcGoroutine.gowrapper(i8* %0) unnamed_addr #3 {
entry:
%1 = bitcast i8* %0 to i32*
%2 = load i32, i32* %1, align 4
%3 = getelementptr inbounds i8, i8* %0, i32 4
%4 = bitcast i8* %3 to i8**
%5 = load i8*, i8** %4, align 4
%6 = getelementptr inbounds i8, i8* %0, i32 8
%7 = bitcast i8* %6 to void (i32, i8*, i8*)**
%8 = load void (i32, i8*, i8*)*, void (i32, i8*, i8*)** %7, align 4
call void %8(i32 %2, i8* %5, i8* undef)
ret void
}
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null)
ret void
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
attributes #0 = { "tinygo-gowrapper"="main.regularFunction" }
attributes #1 = { "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" }
attributes #2 = { "tinygo-gowrapper"="main.closureFunctionGoroutine$1" }
attributes #3 = { "tinygo-gowrapper" }
+106
View File
@@ -0,0 +1,106 @@
; ModuleID = 'goroutine.go'
source_filename = "goroutine.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.funcValueWithSignature = type { i32, i8* }
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
%"internal/task.state" = type { i8* }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
@"main.regularFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"main.inlineFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
@"main.closureFunctionGoroutine$1$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @main.regularFunction to i32), i8* bitcast ({ i32, i8* }* @"main.regularFunctionGoroutine$pack" to i8*), i32 undef, i8* undef, i8* null)
ret void
}
declare void @main.regularFunction(i32, i8*, i8*)
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.inlineFunctionGoroutine$1" to i32), i8* bitcast ({ i32, i8* }* @"main.inlineFunctionGoroutine$pack" to i8*), i32 undef, i8* undef, i8* null)
ret void
}
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%n = call i8* @runtime.alloc(i32 4, i8* undef, i8* null)
%0 = bitcast i8* %n to i32*
store i32 3, i32* %0, align 4
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %n, i8** %4, align 4
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* nonnull %1, i32 undef, i8* undef, i8* null)
%5 = load i32, i32* %0, align 4
call void @runtime.printint32(i32 %5, i8* undef, i8* null)
ret void
}
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%unpack.ptr = bitcast i8* %context to i32*
store i32 7, i32* %unpack.ptr, align 4
ret void
}
declare void @runtime.printint32(i32, i8*, i8*)
define hidden void @main.funcGoroutine(i8* %fn.context, i32 %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call i32 @runtime.getFuncPtr(i8* %fn.context, i32 %fn.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null)
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
%2 = bitcast i8* %1 to i32*
store i32 5, i32* %2, align 4
%3 = getelementptr inbounds i8, i8* %1, i32 4
%4 = bitcast i8* %3 to i8**
store i8* %fn.context, i8** %4, align 4
call void @"internal/task.start"(i32 %0, i8* nonnull %1, i32 undef, i8* undef, i8* null)
ret void
}
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null)
ret void
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
ret void
}
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
+43
View File
@@ -0,0 +1,43 @@
package main
func regularFunctionGoroutine() {
go regularFunction(5)
}
func inlineFunctionGoroutine() {
go func(x int) {
}(5)
}
func closureFunctionGoroutine() {
n := 3
go func(x int) {
n = 7
}(5)
print(n) // note: this is racy (but good enough for this test)
}
func funcGoroutine(fn func(x int)) {
go fn(5)
}
func recoverBuiltinGoroutine() {
// This is a no-op.
go recover()
}
func copyBuiltinGoroutine(dst, src []byte) {
// This is not run in a goroutine. While this copy operation can indeed take
// some time (if there is a lot of data to copy), there is no race-free way
// to make use of the result so it's unlikely applications will make use of
// it. And doing it this way should be just within the Go specification.
go copy(dst, src)
}
func closeBuiltinGoroutine(ch chan int) {
// This builtin is executed directly, not in a goroutine.
// The observed behavior is the same.
go close(ch)
}
func regularFunction(x int)
+8 -8
View File
@@ -1,7 +1,7 @@
; ModuleID = 'interface.go'
source_filename = "interface.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID* }
%runtime.interfaceMethodInfo = type { i8*, i32 }
@@ -13,15 +13,15 @@ target triple = "i686--linux"
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error" }
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }
@"func Error() string" = external constant i8
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
@"reflect/methods.Error() string" = linkonce_odr constant i8 0, align 1
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }
@"func String() string" = external constant i8
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func String() string"]
@"reflect/methods.String() string" = linkonce_odr constant i8 0, align 1
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.String() string"]
@"reflect/types.typeid:basic:int" = external constant i8
@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"]
@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@@ -92,7 +92,7 @@ typeassert.next: ; preds = %typeassert.ok, %ent
define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"func Error() string", i8* undef, i8* null)
%invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Error() string", i8* undef, i8* null)
%invoke.func.cast = inttoptr i32 %invoke.func to %runtime._string (i8*, i8*, i8*)*
%0 = call %runtime._string %invoke.func.cast(i8* %itf.value, i8* undef, i8* undef)
ret %runtime._string %0
+27
View File
@@ -0,0 +1,27 @@
; ModuleID = 'intrinsics.go'
source_filename = "intrinsics.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "armv7m-none-eabi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @math.Sqrt(double %x, i8* undef, i8* undef)
ret double %0
}
declare double @math.Sqrt(double, i8*, i8*)
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @math.Trunc(double %x, i8* undef, i8* undef)
ret double %0
}
declare double @math.Trunc(double, i8*, i8*)
+31
View File
@@ -0,0 +1,31 @@
; ModuleID = 'intrinsics.go'
source_filename = "intrinsics.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @llvm.sqrt.f64(double %x)
ret double %0
}
; Function Attrs: nounwind readnone speculatable willreturn
declare double @llvm.sqrt.f64(double) #0
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%0 = call double @llvm.trunc.f64(double %x)
ret double %0
}
; Function Attrs: nounwind readnone speculatable willreturn
declare double @llvm.trunc.f64(double) #0
attributes #0 = { nounwind readnone speculatable willreturn }
+14
View File
@@ -0,0 +1,14 @@
package main
// Test how intrinsics are lowered: either as regular calls to the math
// functions or as LLVM builtins (such as llvm.sqrt.f64).
import "math"
func mySqrt(x float64) float64 {
return math.Sqrt(x)
}
func myTrunc(x float64) float64 {
return math.Trunc(x)
}
+2 -2
View File
@@ -1,7 +1,7 @@
; ModuleID = 'pointer.go'
source_filename = "pointer.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
+66
View File
@@ -0,0 +1,66 @@
package main
import _ "unsafe"
// Creates an external global with name extern_global.
//go:extern extern_global
var externGlobal [0]byte
// Creates a
//go:align 32
var alignedGlobal [4]uint32
// Test conflicting pragmas (the last one counts).
//go:align 64
//go:align 16
var alignedGlobal16 [4]uint32
// Test exported functions.
//export extern_func
func externFunc() {
}
// Define a function in a different package using go:linkname.
//go:linkname withLinkageName1 somepkg.someFunction1
func withLinkageName1() {
}
// Import a function from a different package using go:linkname.
//go:linkname withLinkageName2 somepkg.someFunction2
func withLinkageName2()
// Function has an 'inline hint', similar to the inline keyword in C.
//go:inline
func inlineFunc() {
}
// Function should never be inlined, equivalent to GCC
// __attribute__((noinline)).
//go:noinline
func noinlineFunc() {
}
// This function should have the specified section.
//go:section .special_function_section
func functionInSection() {
}
//export exportedFunctionInSection
//go:section .special_function_section
func exportedFunctionInSection() {
}
// This function should not: it's only a declaration and not a definition.
//go:section .special_function_section
func undefinedFunctionNotInSection()
//go:section .special_global_section
var globalInSection uint32
//go:section .special_global_section
//go:extern undefinedGlobalNotInSection
var undefinedGlobalNotInSection uint32
//go:align 1024
//go:section .global_section
var multipleGlobalPragmas uint32
+59
View File
@@ -0,0 +1,59 @@
; ModuleID = 'pragma.go'
source_filename = "pragma.go"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
@extern_global = external global [0 x i8], align 1
@main.alignedGlobal = hidden global [4 x i32] zeroinitializer, align 32
@main.alignedGlobal16 = hidden global [4 x i32] zeroinitializer, align 16
@main.globalInSection = hidden global i32 0, section ".special_global_section", align 4
@undefinedGlobalNotInSection = external global i32, align 4
@main.multipleGlobalPragmas = hidden global i32 0, section ".global_section", align 1024
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
define void @extern_func() #0 {
entry:
ret void
}
define hidden void @somepkg.someFunction1(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
ret void
}
declare void @somepkg.someFunction2(i8*, i8*)
; Function Attrs: inlinehint
define hidden void @main.inlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #1 {
entry:
ret void
}
; Function Attrs: noinline
define hidden void @main.noinlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #2 {
entry:
ret void
}
define hidden void @main.functionInSection(i8* %context, i8* %parentHandle) unnamed_addr section ".special_function_section" {
entry:
ret void
}
define void @exportedFunctionInSection() #3 section ".special_function_section" {
entry:
ret void
}
declare void @main.undefinedFunctionNotInSection(i8*, i8*)
attributes #0 = { "wasm-export-name"="extern_func" }
attributes #1 = { inlinehint }
attributes #2 = { noinline }
attributes #3 = { "wasm-export-name"="exportedFunctionInSection" }
+18
View File
@@ -23,3 +23,21 @@ func sliceAppendSlice(ints, added []int) []int {
func sliceCopy(dst, src []int) int {
return copy(dst, src)
}
// Test bounds checking in *ssa.MakeSlice instruction.
func makeByteSlice(len int) []byte {
return make([]byte, len)
}
func makeInt16Slice(len int) []int16 {
return make([]int16, len)
}
func makeArraySlice(len int) [][3]byte {
return make([][3]byte, len) // slice with element size of 3
}
func makeInt32Slice(len int) []int32 {
return make([]int32, len)
}
+78 -2
View File
@@ -1,7 +1,7 @@
; ModuleID = 'slice.go'
source_filename = "slice.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
@@ -86,3 +86,79 @@ entry:
}
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
define hidden { i8*, i32, i32 } @main.makeByteSlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp slt i32 %len, 0
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.buf = call i8* @runtime.alloc(i32 %len, i8* undef, i8* null)
%0 = insertvalue { i8*, i32, i32 } undef, i8* %makeslice.buf, 0
%1 = insertvalue { i8*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i8*, i32, i32 } %1, i32 %len, 2
ret { i8*, i32, i32 } %2
}
declare void @runtime.slicePanic(i8*, i8*)
define hidden { i16*, i32, i32 } @main.makeInt16Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp slt i32 %len, 0
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.cap = shl i32 %len, 1
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
%makeslice.array = bitcast i8* %makeslice.buf to i16*
%0 = insertvalue { i16*, i32, i32 } undef, i16* %makeslice.array, 0
%1 = insertvalue { i16*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i16*, i32, i32 } %1, i32 %len, 2
ret { i16*, i32, i32 } %2
}
define hidden { [3 x i8]*, i32, i32 } @main.makeArraySlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp ugt i32 %len, 1431655765
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.cap = mul i32 %len, 3
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
%makeslice.array = bitcast i8* %makeslice.buf to [3 x i8]*
%0 = insertvalue { [3 x i8]*, i32, i32 } undef, [3 x i8]* %makeslice.array, 0
%1 = insertvalue { [3 x i8]*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { [3 x i8]*, i32, i32 } %1, i32 %len, 2
ret { [3 x i8]*, i32, i32 } %2
}
define hidden { i32*, i32, i32 } @main.makeInt32Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
entry:
%slice.maxcap = icmp ugt i32 %len, 1073741823
br i1 %slice.maxcap, label %slice.throw, label %slice.next
slice.throw: ; preds = %entry
call void @runtime.slicePanic(i8* undef, i8* null)
unreachable
slice.next: ; preds = %entry
%makeslice.cap = shl i32 %len, 2
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
%makeslice.array = bitcast i8* %makeslice.buf to i32*
%0 = insertvalue { i32*, i32, i32 } undef, i32* %makeslice.array, 0
%1 = insertvalue { i32*, i32, i32 } %0, i32 %len, 1
%2 = insertvalue { i32*, i32, i32 } %1, i32 %len, 2
ret { i32*, i32, i32 } %2
}
+2 -2
View File
@@ -1,7 +1,7 @@
; ModuleID = 'string.go'
source_filename = "string.go"
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
target triple = "i686--linux"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32--wasi"
%runtime._string = type { i8*, i32 }
+3 -3
View File
@@ -1,6 +1,6 @@
module github.com/tinygo-org/tinygo
go 1.13
go 1.15
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
@@ -10,7 +10,7 @@ require (
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
github.com/mattn/go-colorable v0.1.8
go.bug.st/serial v1.1.2
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
golang.org/x/sys v0.0.0-20210510120138-977fb7262007
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c
)
+17 -6
View File
@@ -30,6 +30,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
go.bug.st/serial v1.1.2 h1:6xDpbta8KJ+VLRTeM8ghhxXRMLE/Lr8h9iDKwydarAY=
@@ -38,23 +40,32 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9 h1:nvvuMxmx1q0gfRki3T0hjG8EwAcVCs91oWAXvyt4zhI=
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs=
+5 -5
View File
@@ -12,7 +12,7 @@ import (
// Version of TinyGo.
// Update this value before release of new version of software.
const Version = "0.18.0-dev"
const Version = "0.20.0"
// GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
@@ -48,7 +48,10 @@ func GetGorootVersion(goroot string) (major, minor int, err error) {
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
// can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join(
if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`")
@@ -59,9 +62,6 @@ func GorootVersionString(goroot string) (string, error) {
return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else {
return "", err
}
+1
View File
@@ -57,6 +57,7 @@ func (r *runner) errorAt(inst instruction, err error) *Error {
pos := getPosition(inst.llvmInst)
return &Error{
ImportPath: r.pkgName,
Inst: inst.llvmInst,
Pos: pos,
Err: err,
Traceback: []ErrorLine{{pos, inst.llvmInst}},
+34 -3
View File
@@ -15,7 +15,7 @@ import (
// package is changed in a way that affects the output so that cached package
// builds will be invalidated.
// This version is independent of the TinyGo version number.
const Version = 1
const Version = 2 // last change: fix GEP on untyped pointers
// Enable extra checks, which should be disabled by default.
// This may help track down bugs by adding a few more sanity checks.
@@ -110,17 +110,26 @@ func Run(mod llvm.Module, debug bool) error {
fmt.Fprintln(os.Stderr, "call:", fn.Name())
}
_, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
call.EraseFromParentAsInstruction()
if callErr != nil {
if isRecoverableError(callErr.Err) {
if r.debug {
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
}
// Remove instructions that were created as part of interpreting
// the package.
mem.revert()
// Create a call to the package initializer (which was
// previously deleted).
i8undef := llvm.Undef(r.i8ptrType)
r.builder.CreateCall(fn, []llvm.Value{i8undef, i8undef}, "")
// Make sure that any globals touched by the package
// initializer, won't be accessed by later package initializers.
r.markExternalLoad(fn)
continue
}
return callErr
}
call.EraseFromParentAsInstruction()
for index, obj := range mem.objects {
r.objects[index] = obj
}
@@ -129,7 +138,7 @@ func Run(mod llvm.Module, debug bool) error {
// Update all global variables in the LLVM module.
mem := memoryView{r: r}
for _, obj := range r.objects {
for i, obj := range r.objects {
if obj.llvmGlobal.IsNil() {
continue
}
@@ -159,6 +168,12 @@ func Run(mod llvm.Module, debug bool) error {
name := obj.llvmGlobal.Name()
obj.llvmGlobal.EraseFromParentAsGlobal()
newGlobal.SetName(name)
// Update interp-internal references.
delete(r.globals, obj.llvmGlobal)
obj.llvmGlobal = newGlobal
r.globals[newGlobal] = i
r.objects[i] = obj
continue
}
if err != nil {
@@ -264,3 +279,19 @@ func (r *runner) getFunction(llvmFn llvm.Value) *function {
r.functionCache[llvmFn] = fn
return fn
}
// markExternalLoad marks the given llvmValue as being loaded externally. This
// is primarily used to mark package initializers that could not be run at
// compile time. As an example, a package initialize might store to a global
// variable. Another package initializer might read from the same global
// variable. By marking this function as being run at runtime, that load
// instruction will need to be run at runtime instead of at compile time.
func (r *runner) markExternalLoad(llvmValue llvm.Value) {
mem := memoryView{r: r}
mem.markExternalLoad(llvmValue)
for index, obj := range mem.objects {
if obj.marked > r.objects[index].marked {
r.objects[index].marked = obj.marked
}
}
}
+1
View File
@@ -17,6 +17,7 @@ func TestInterp(t *testing.T) {
"slice-copy",
"consteval",
"interface",
"revert",
} {
name := name // make tc local to this closure
t.Run(name, func(t *testing.T) {
+22 -10
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"math"
"os"
"strconv"
"strings"
"time"
@@ -589,18 +590,14 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// GetElementPtr does pointer arithmetic, changing the offset of the
// pointer into the underlying object.
var offset uint64
var gepOperands []uint64
for i := 2; i < len(operands); i += 2 {
index := operands[i].Uint()
elementSize := operands[i+1].Uint()
if int64(elementSize) < 0 {
// This is a struct field.
// The field number is encoded by flipping all the bits.
gepOperands = append(gepOperands, ^elementSize)
offset += index
} else {
// This is a normal GEP, probably an array index.
gepOperands = append(gepOperands, index)
offset += elementSize * index
}
}
@@ -930,16 +927,31 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name)
case llvm.ExtractValue:
indices := inst.llvmInst.Indices()
if len(indices) != 1 {
panic("expected exactly one index")
// Note: the Go LLVM API doesn't support multiple indices, so simulate
// this operation with some extra extractvalue instructions. Hopefully
// this is optimized to a single instruction.
agg := operands[0]
for i := 0; i < len(indices)-1; i++ {
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg")
}
result = r.builder.CreateExtractValue(operands[0], int(indices[0]), inst.name)
result = r.builder.CreateExtractValue(agg, int(indices[len(indices)-1]), inst.name)
case llvm.InsertValue:
indices := inst.llvmInst.Indices()
if len(indices) != 1 {
panic("expected exactly one index")
// Similar to extractvalue, we're working around a limitation in the Go
// LLVM API here by splitting the insertvalue into multiple instructions
// if there is more than one operand.
agg := operands[0]
aggregates := []llvm.Value{agg}
for i := 0; i < len(indices)-1; i++ {
agg = r.builder.CreateExtractValue(agg, int(indices[i]), inst.name+".agg"+strconv.Itoa(i))
aggregates = append(aggregates, agg)
}
result = r.builder.CreateInsertValue(operands[0], operands[1], int(indices[0]), inst.name)
result = operands[1]
for i := len(indices) - 1; i >= 0; i-- {
agg := aggregates[i]
result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i))
}
case llvm.Add:
result = r.builder.CreateAdd(operands[0], operands[1], inst.name)
case llvm.Sub:
+14
View File
@@ -178,6 +178,9 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) {
default:
panic("interp: unknown constant expression")
}
} else if !llvmValue.IsAInlineAsm().IsNil() {
// Inline assembly can modify globals but only exported globals. Let's
// hope the author knows what they're doing.
} else {
llvmType := llvmValue.Type()
switch llvmType.TypeKind() {
@@ -569,6 +572,17 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
}
if llvmType.IsNil() {
if v.offset() != 0 {
// If there is an offset, make sure to use a GEP to index into the
// pointer. Because there is no expected type, we use whatever is
// most convenient: an *i8 type. It is trivial to index byte-wise.
if llvmValue.Type() != mem.r.i8ptrType {
llvmValue = llvm.ConstBitCast(llvmValue, mem.r.i8ptrType)
}
llvmValue = llvm.ConstInBoundsGEP(llvmValue, []llvm.Value{
llvm.ConstInt(llvmValue.Type().Context().Int32Type(), uint64(v.offset()), false),
})
}
return llvmValue, nil
}
+22
View File
@@ -8,6 +8,7 @@ target triple = "x86_64--linux"
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
@main.exposedValue1 = global i16 0
@main.exposedValue2 = global i16 0
@main.insertedValue = global {i8, i32, {float, {i64, i16}}} zeroinitializer
declare void @runtime.printint64(i64) unnamed_addr
@@ -65,12 +66,22 @@ entry:
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
store i16 7, i16* @main.exposedValue2
; Test that inline assembly is ignored.
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
; Test switch statement.
%switch1 = call i64 @testSwitch(i64 1) ; 1 returns 6
%switch2 = call i64 @testSwitch(i64 9) ; 9 returns the default value -1
call void @runtime.printint64(i64 %switch1)
call void @runtime.printint64(i64 %switch2)
; Test extractvalue/insertvalue with multiple operands.
%agg = call {i8, i32, {float, {i64, i16}}} @nestedStruct()
%elt = extractvalue {i8, i32, {float, {i64, i16}}} %agg, 2, 1, 0
call void @runtime.printint64(i64 %elt)
%agg2 = insertvalue {i8, i32, {float, {i64, i16}}} %agg, i64 5, 2, 1, 0
store {i8, i32, {float, {i64, i16}}} %agg2, {i8, i32, {float, {i64, i16}}}* @main.insertedValue
ret void
}
@@ -94,6 +105,15 @@ entry:
ret void
}
; Inline assembly should be ignored in the interp package. While it is possible
; to modify other globals that way, usually that's not the case and there is no
; real way to check.
define void @hasInlineAsm() {
entry:
call void asm sideeffect "", ""()
ret void
}
define i64 @testSwitch(i64 %val) {
entry:
; Test switch statement.
@@ -112,3 +132,5 @@ two:
otherwise:
ret i64 -1
}
declare {i8, i32, {float, {i64, i16}}} @nestedStruct()
+21
View File
@@ -7,6 +7,7 @@ target triple = "x86_64--linux"
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
@main.exposedValue1 = global i16 0
@main.exposedValue2 = local_unnamed_addr global i16 0
@main.insertedValue = local_unnamed_addr global { i8, i32, { float, { i64, i16 } } } zeroinitializer
declare void @runtime.printint64(i64) unnamed_addr
@@ -25,8 +26,20 @@ entry:
store i16 5, i16* @main.exposedValue1
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
store i16 7, i16* @main.exposedValue2
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
call void @runtime.printint64(i64 6)
call void @runtime.printint64(i64 -1)
%agg = call { i8, i32, { float, { i64, i16 } } } @nestedStruct()
%elt.agg = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
%elt.agg1 = extractvalue { float, { i64, i16 } } %elt.agg, 1
%elt = extractvalue { i64, i16 } %elt.agg1, 0
call void @runtime.printint64(i64 %elt)
%agg2.agg0 = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
%agg2.agg1 = extractvalue { float, { i64, i16 } } %agg2.agg0, 1
%agg2.insertvalue2 = insertvalue { i64, i16 } %agg2.agg1, i64 5, 0
%agg2.insertvalue1 = insertvalue { float, { i64, i16 } } %agg2.agg0, { i64, i16 } %agg2.insertvalue2, 1
%agg2.insertvalue0 = insertvalue { i8, i32, { float, { i64, i16 } } } %agg, { float, { i64, i16 } } %agg2.insertvalue1, 2
store { i8, i32, { float, { i64, i16 } } } %agg2.insertvalue0, { i8, i32, { float, { i64, i16 } } }* @main.insertedValue
ret void
}
@@ -47,6 +60,12 @@ entry:
ret void
}
define void @hasInlineAsm() {
entry:
call void asm sideeffect "", ""()
ret void
}
define i64 @testSwitch(i64 %val) local_unnamed_addr {
entry:
switch i64 %val, label %otherwise [
@@ -67,3 +86,5 @@ two: ; preds = %entry
otherwise: ; preds = %entry
ret i64 -1
}
declare { i8, i32, { float, { i64, i16 } } } @nestedStruct() local_unnamed_addr
+32
View File
@@ -0,0 +1,32 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
declare void @externalCall(i64)
@foo.knownAtRuntime = global i64 0
@bar.knownAtRuntime = global i64 0
define void @runtime.initAll() unnamed_addr {
entry:
call void @foo.init(i8* undef, i8* undef)
call void @bar.init(i8* undef, i8* undef)
call void @main.init(i8* undef, i8* undef)
ret void
}
define internal void @foo.init(i8* %context, i8* %parentHandle) unnamed_addr {
store i64 5, i64* @foo.knownAtRuntime
unreachable ; this triggers a revert of @foo.init.
}
define internal void @bar.init(i8* %context, i8* %parentHandle) unnamed_addr {
%val = load i64, i64* @foo.knownAtRuntime
store i64 %val, i64* @bar.knownAtRuntime
ret void
}
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
entry:
call void @externalCall(i64 3)
ret void
}
+21
View File
@@ -0,0 +1,21 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@foo.knownAtRuntime = local_unnamed_addr global i64 0
@bar.knownAtRuntime = local_unnamed_addr global i64 0
declare void @externalCall(i64) local_unnamed_addr
define void @runtime.initAll() unnamed_addr {
entry:
call fastcc void @foo.init(i8* undef, i8* undef)
%val = load i64, i64* @foo.knownAtRuntime, align 8
store i64 %val, i64* @bar.knownAtRuntime, align 8
call void @externalCall(i64 3)
ret void
}
define internal fastcc void @foo.init(i8* %context, i8* %parentHandle) unnamed_addr {
store i64 5, i64* @foo.knownAtRuntime, align 8
unreachable
}
+10
View File
@@ -23,3 +23,13 @@ type Error struct {
func (e Error) Error() string {
return e.Err.Error()
}
// Error returned when loading a *Program for a test binary but no test files
// are present.
type NoTestFilesError struct {
ImportPath string
}
func (e NoTestFilesError) Error() string {
return "no test files"
}
+38 -16
View File
@@ -2,6 +2,14 @@ package loader
// This file constructs a new temporary GOROOT directory by merging both the
// standard Go GOROOT and the GOROOT from TinyGo using symlinks.
//
// The goal is to replace specific packages from Go with a TinyGo version. It's
// never a partial replacement, either a package is fully replaced or it is not.
// This is important because if we did allow to merge packages (e.g. by adding
// files to a package), it would lead to a dependency on implementation details
// with all the maintenance burden that results in. Only allowing to replace
// packages as a whole avoids this as packages are already designed to have a
// public (backwards-compatible) API.
import (
"crypto/sha512"
@@ -139,6 +147,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
if err != nil {
return err
}
hasTinyGoFiles := false
for _, e := range tinygoEntries {
if e.IsDir() {
// A directory, so merge this thing.
@@ -154,6 +163,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
if err != nil {
return err
}
hasTinyGoFiles = true
}
}
@@ -164,21 +174,30 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
return err
}
for _, e := range gorootEntries {
if !e.IsDir() {
// Don't merge in files from Go. Otherwise we'd end up with a
// weird syscall package with files from both roots.
continue
}
if _, ok := overrides[path.Join(importPath, e.Name())+"/"]; ok {
// Already included above, so don't bother trying to create this
// symlink.
continue
}
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
oldname := filepath.Join(goroot, "src", importPath, e.Name())
err := symlink(oldname, newname)
if err != nil {
return err
if e.IsDir() {
if _, ok := overrides[path.Join(importPath, e.Name())+"/"]; ok {
// Already included above, so don't bother trying to create this
// symlink.
continue
}
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
oldname := filepath.Join(goroot, "src", importPath, e.Name())
err := symlink(oldname, newname)
if err != nil {
return err
}
} else {
// Only merge files from Go if TinyGo does not have any files.
// Otherwise we'd end up with a weird mix from both Go
// implementations.
if !hasTinyGoFiles {
newname := filepath.Join(tmpgoroot, "src", importPath, e.Name())
oldname := filepath.Join(goroot, "src", importPath, e.Name())
err := symlink(oldname, newname)
if err != nil {
return err
}
}
}
}
}
@@ -189,7 +208,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
// with the TinyGo version. This is the case on some targets.
func needsSyscallPackage(buildTags []string) bool {
for _, tag := range buildTags {
if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "wasi" {
if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "tinygo.wasm" {
return true
}
}
@@ -201,6 +220,8 @@ func needsSyscallPackage(buildTags []string) bool {
func pathsToOverride(needsSyscallPackage bool) map[string]bool {
paths := map[string]bool{
"/": true,
"crypto/": true,
"crypto/rand/": false,
"device/": false,
"examples/": false,
"internal/": true,
@@ -208,6 +229,7 @@ func pathsToOverride(needsSyscallPackage bool) map[string]bool {
"internal/reflectlite/": false,
"internal/task/": false,
"machine/": false,
"net/": true,
"os/": true,
"reflect/": false,
"runtime/": false,
+16 -7
View File
@@ -210,6 +210,12 @@ func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, t
p.Packages[pkg.ImportPath] = pkg
}
if config.TestConfig.CompileTestBinary && !strings.HasSuffix(p.sorted[len(p.sorted)-1].ImportPath, ".test") {
// Trying to compile a test binary but there are no test files in this
// package.
return p, NoTestFilesError{p.sorted[len(p.sorted)-1].ImportPath}
}
return p, nil
}
@@ -337,12 +343,11 @@ func (p *Package) Check() error {
checker.Importer = p
packageName := p.ImportPath
if p.Name == "main" {
// The main package normally has a different import path, such as
// "command-line-arguments" or "./testdata/cgo". Therefore, use the name
// "main" in such a case: this package isn't imported from anywhere.
// This is safe as it isn't possible to import a package with the name
// "main".
if p == p.program.MainPkg() {
if p.Name != "main" {
// Sanity check. Should not ever trigger.
panic("expected main package to have name 'main'")
}
packageName = "main"
}
typesPkg, err := checker.Check(packageName, p.program.fset, p.Files, &p.info)
@@ -381,7 +386,11 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
}
// Do CGo processing.
if len(p.CgoFiles) != 0 {
// This is done when there are any CgoFiles at all. In that case, len(files)
// should be non-zero. However, if len(GoFiles) == 0 and len(CgoFiles) == 1
// and there is a syntax error in a CGo file, len(files) may be 0. Don't try
// to call cgo.Process in that case as it will only cause issues.
if len(p.CgoFiles) != 0 && len(files) != 0 {
var initialCFlags []string
initialCFlags = append(initialCFlags, p.program.config.CFlags()...)
initialCFlags = append(initialCFlags, "-I"+p.Dir)
+323 -101
View File
@@ -15,6 +15,8 @@ import (
"path/filepath"
"regexp"
"runtime"
"runtime/pprof"
"strconv"
"strings"
"sync/atomic"
"time"
@@ -95,12 +97,31 @@ func copyFile(src, dst string) error {
// executeCommand is a simple wrapper to exec.Cmd
func executeCommand(options *compileopts.Options, name string, arg ...string) *exec.Cmd {
if options.PrintCommands {
fmt.Printf("%s %s\n", name, strings.Join(arg, " "))
if options.PrintCommands != nil {
options.PrintCommands(name, arg...)
}
return exec.Command(name, arg...)
}
// printCommand prints a command to stdout while formatting it like a real
// command (escaping characters etc). The resulting command should be easy to
// run directly in a shell, although it is not guaranteed to be a safe shell
// escape. That's not a problem as the primary use case is printing the command,
// not running it.
func printCommand(cmd string, args ...string) {
command := append([]string{cmd}, args...)
for i, arg := range command {
// Source: https://www.oreilly.com/library/view/learning-the-bash/1565923472/ch01s09.html
const specialChars = "~`#$&*()\\|[]{};'\"<>?! "
if strings.ContainsAny(arg, specialChars) {
// See: https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash
arg = "'" + strings.ReplaceAll(arg, `'`, `'\''`) + "'"
command[i] = arg
}
}
fmt.Fprintln(os.Stderr, strings.Join(command, " "))
}
// Build compiles and links the given package and writes it to outpath.
func Build(pkgName, outpath string, options *compileopts.Options) error {
config, err := builder.NewConfig(options)
@@ -136,15 +157,17 @@ func Build(pkgName, outpath string, options *compileopts.Options) error {
})
}
// Test runs the tests in the given package.
func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, outpath string) error {
// Test runs the tests in the given package. Returns whether the test passed and
// possibly an error if the test failed to run.
func Test(pkgName string, options *compileopts.Options, testCompileOnly, testVerbose bool, outpath string) (bool, error) {
options.TestConfig.CompileTestBinary = true
config, err := builder.NewConfig(options)
if err != nil {
return err
return false, err
}
return builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
passed := true
err = builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error {
if testCompileOnly || outpath != "" {
// Write test binary to the specified file name.
if outpath == "" {
@@ -158,48 +181,83 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou
// Do not run the test.
return nil
}
if len(config.Target.Emulator) == 0 {
// Run directly.
cmd := executeCommand(config.Options, result.Binary)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = result.MainDir
err := cmd.Run()
if err != nil {
// Propagate the exit code
if err, ok := err.(*exec.ExitError); ok {
os.Exit(err.ExitCode())
}
return &commandError{"failed to run compiled binary", result.Binary, err}
}
return nil
// Run the test.
start := time.Now()
var err error
passed, err = runPackageTest(config, result, testVerbose)
if err != nil {
return err
}
duration := time.Since(start)
// Print the result.
importPath := strings.TrimSuffix(result.ImportPath, ".test")
if passed {
fmt.Printf("ok \t%s\t%.3fs\n", importPath, duration.Seconds())
} else {
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary)
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
buf := &bytes.Buffer{}
w := io.MultiWriter(os.Stdout, buf)
cmd.Stdout = w
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() {
// Workaround for QEMU which always exits with an error.
return &commandError{"failed to run emulator with", result.Binary, err}
}
fmt.Printf("FAIL\t%s\t%.3fs\n", importPath, duration.Seconds())
}
return nil
})
if err, ok := err.(loader.NoTestFilesError); ok {
fmt.Printf("? \t%s\t[no test files]\n", err.ImportPath)
// Pretend the test passed - it at least didn't fail.
return true, nil
}
return passed, err
}
// runPackageTest runs a test binary that was previously built. The return
// values are whether the test passed and any errors encountered while trying to
// run the binary.
func runPackageTest(config *compileopts.Config, result builder.BuildResult, testVerbose bool) (bool, error) {
if len(config.Target.Emulator) == 0 {
// Run directly.
var flags []string
if testVerbose {
flags = append(flags, "-test.v")
}
cmd := executeCommand(config.Options, result.Binary, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = result.MainDir
err := cmd.Run()
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
// Binary exited with a non-zero exit code, which means the test
// failed.
return false, nil
}
testOutput := string(buf.Bytes())
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") {
// Test passed.
return nil
} else {
// Test failed, either by ending with the word "FAIL" or with a
// panic of some sort.
os.Exit(1)
return nil // unreachable
return false, &commandError{"failed to run compiled binary", result.Binary, err}
}
return true, nil
} else {
// Run in an emulator.
// TODO: pass the -test.v flag if needed.
args := append(config.Target.Emulator[1:], result.Binary)
cmd := executeCommand(config.Options, config.Target.Emulator[0], args...)
buf := &bytes.Buffer{}
w := io.MultiWriter(os.Stdout, buf)
cmd.Stdout = w
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
if err, ok := err.(*exec.ExitError); !ok || !err.Exited() {
// Workaround for QEMU which always exits with an error.
return false, &commandError{"failed to run emulator with", result.Binary, err}
}
}
})
testOutput := string(buf.Bytes())
if testOutput == "PASS\n" || strings.HasSuffix(testOutput, "\nPASS\n") {
// Test passed.
return true, nil
} else {
// Test failed, either by ending with the word "FAIL" or with a
// panic of some sort.
return false, nil
}
}
}
// Flash builds and flashes the built binary to the given serial port.
@@ -224,6 +282,8 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
fileExt = ".bin"
case strings.Contains(config.Target.FlashCommand, "{uf2}"):
fileExt = ".uf2"
case strings.Contains(config.Target.FlashCommand, "{zip}"):
fileExt = ".zip"
default:
return errors.New("invalid target file - did you forget the {hex} token in the 'flash-command' section?")
}
@@ -234,6 +294,8 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
fileExt = filepath.Ext(config.Target.FlashFilename)
case "openocd":
fileExt = ".hex"
case "bmp":
fileExt = ".elf"
case "native":
return errors.New("unknown flash method \"native\" - did you miss a -target flag?")
default:
@@ -243,7 +305,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error {
// do we need port reset to put MCU into bootloader mode?
if config.Target.PortReset == "true" && flashMethod != "openocd" {
port, err := getDefaultPort(strings.FieldsFunc(port, func(c rune) bool { return c == ',' }))
port, err := getDefaultPort(port, config.Target.SerialPort)
if err != nil {
return err
}
@@ -261,36 +323,36 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
case "", "command":
// Create the command.
flashCmd := config.Target.FlashCommand
fileToken := "{" + fileExt[1:] + "}"
flashCmd = strings.ReplaceAll(flashCmd, fileToken, result.Binary)
flashCmdList, err := shlex.Split(flashCmd)
if err != nil {
return fmt.Errorf("could not parse flash command %#v: %w", flashCmd, err)
}
if strings.Contains(flashCmd, "{port}") {
var err error
port, err = getDefaultPort(strings.FieldsFunc(port, func(c rune) bool { return c == ',' }))
port, err = getDefaultPort(port, config.Target.SerialPort)
if err != nil {
return err
}
}
flashCmd = strings.ReplaceAll(flashCmd, "{port}", port)
// Execute the command.
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
command := strings.Split(flashCmd, " ")
if len(command) < 2 {
return errors.New("invalid flash command")
}
cmd = executeCommand(config.Options, command[0], command[1:]...)
default:
cmd = executeCommand(config.Options, "/bin/sh", "-c", flashCmd)
// Fill in fields in the command template.
fileToken := "{" + fileExt[1:] + "}"
for i, arg := range flashCmdList {
arg = strings.ReplaceAll(arg, fileToken, result.Binary)
arg = strings.ReplaceAll(arg, "{port}", port)
flashCmdList[i] = arg
}
// Execute the command.
if len(flashCmdList) < 2 {
return fmt.Errorf("invalid flash command: %#v", flashCmd)
}
cmd := executeCommand(config.Options, flashCmdList[0], flashCmdList[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
err := cmd.Run()
err = cmd.Run()
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
}
@@ -326,6 +388,25 @@ func Flash(pkgName, port string, options *compileopts.Options) error {
return &commandError{"failed to flash", result.Binary, err}
}
return nil
case "bmp":
gdb, err := config.Target.LookupGDB()
if err != nil {
return err
}
var bmpGDBPort string
bmpGDBPort, _, err = getBMPPorts()
if err != nil {
return err
}
args := []string{"-ex", "target extended-remote " + bmpGDBPort, "-ex", "monitor swdp_scan", "-ex", "attach 1", "-ex", "load", filepath.ToSlash(result.Binary)}
cmd := executeCommand(config.Options, gdb, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
}
return nil
default:
return fmt.Errorf("unknown flash method: %s", flashMethod)
}
@@ -380,8 +461,15 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
switch gdbInterface {
case "native":
// Run GDB directly.
case "bmp":
var bmpGDBPort string
bmpGDBPort, _, err = getBMPPorts()
if err != nil {
return err
}
gdbCommands = append(gdbCommands, "target extended-remote "+bmpGDBPort, "monitor swdp_scan", "compare-sections", "attach 1", "load")
case "openocd":
gdbCommands = append(gdbCommands, "target remote :3333", "monitor halt", "load", "monitor reset halt")
gdbCommands = append(gdbCommands, "target extended-remote :3333", "monitor halt", "load", "monitor reset halt")
// We need a separate debugging daemon for on-chip debugging.
args, err := config.OpenOCDConfiguration()
@@ -400,7 +488,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
daemon.Stderr = w
}
case "jlink":
gdbCommands = append(gdbCommands, "target remote :2331", "load", "monitor reset halt")
gdbCommands = append(gdbCommands, "target extended-remote :2331", "load", "monitor reset halt")
// We need a separate debugging daemon for on-chip debugging.
daemon = executeCommand(config.Options, "JLinkGDBServer", "-device", config.Target.JLinkDevice)
@@ -415,7 +503,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
daemon.Stderr = w
}
case "qemu":
gdbCommands = append(gdbCommands, "target remote :1234")
gdbCommands = append(gdbCommands, "target extended-remote :1234")
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary, "-s", "-S")
@@ -423,7 +511,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
case "qemu-user":
gdbCommands = append(gdbCommands, "target remote :1234")
gdbCommands = append(gdbCommands, "target extended-remote :1234")
// Run in an emulator.
args := append(config.Target.Emulator[1:], "-g", "1234", result.Binary)
@@ -431,7 +519,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
case "mgba":
gdbCommands = append(gdbCommands, "target remote :2345")
gdbCommands = append(gdbCommands, "target extended-remote :2345")
// Run in an emulator.
args := append(config.Target.Emulator[1:], result.Binary, "-g")
@@ -439,7 +527,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
daemon.Stdout = os.Stdout
daemon.Stderr = os.Stderr
case "simavr":
gdbCommands = append(gdbCommands, "target remote :1234")
gdbCommands = append(gdbCommands, "target extended-remote :1234")
// Run in an emulator.
args := append(config.Target.Emulator[1:], "-g", result.Binary)
@@ -665,7 +753,8 @@ func windowsFindUSBDrive(volume string, options *compileopts.Options) (string, e
}
// getDefaultPort returns the default serial port depending on the operating system.
func getDefaultPort(portCandidates []string) (port string, err error) {
func getDefaultPort(portFlag string, usbInterfaces []string) (port string, err error) {
portCandidates := strings.FieldsFunc(portFlag, func(c rune) bool { return c == ',' })
if len(portCandidates) == 1 {
return portCandidates[0], nil
}
@@ -681,11 +770,70 @@ func getDefaultPort(portCandidates []string) (port string, err error) {
return "", err
}
for _, p := range portsList {
ports = append(ports, p.Name)
var preferredPortIDs [][2]uint16
for _, s := range usbInterfaces {
parts := strings.Split(s, ":")
if len(parts) != 3 || (parts[0] != "acm" && parts[0] == "usb") {
// acm and usb are the two types of serial ports recognized
// under Linux (ttyACM*, ttyUSB*). Other operating systems don't
// generally make this distinction. If this is not one of the
// given USB devices, don't try to parse the USB IDs.
continue
}
vid, err := strconv.ParseUint(parts[1], 16, 16)
if err != nil {
return "", fmt.Errorf("could not parse USB vendor ID %q: %w", parts[1], err)
}
pid, err := strconv.ParseUint(parts[2], 16, 16)
if err != nil {
return "", fmt.Errorf("could not parse USB product ID %q: %w", parts[1], err)
}
preferredPortIDs = append(preferredPortIDs, [2]uint16{uint16(vid), uint16(pid)})
}
if ports == nil || len(ports) == 0 {
var primaryPorts []string // ports picked from preferred USB VID/PID
var secondaryPorts []string // other ports (as a fallback)
for _, p := range portsList {
if !p.IsUSB {
continue
}
if p.VID != "" && p.PID != "" {
foundPort := false
vid, vidErr := strconv.ParseUint(p.VID, 16, 16)
pid, pidErr := strconv.ParseUint(p.PID, 16, 16)
if vidErr == nil && pidErr == nil {
for _, id := range preferredPortIDs {
if uint16(vid) == id[0] && uint16(pid) == id[1] {
primaryPorts = append(primaryPorts, p.Name)
foundPort = true
continue
}
}
}
if foundPort {
continue
}
}
secondaryPorts = append(secondaryPorts, p.Name)
}
if len(primaryPorts) == 1 {
// There is exactly one match in the set of preferred ports. Use
// this port, even if there may be others available. This allows
// flashing a specific board even if there are multiple available.
return primaryPorts[0], nil
} else if len(primaryPorts) > 1 {
// There are multiple preferred ports, probably because more than
// one device of the same type are connected (e.g. two Arduino
// Unos).
ports = primaryPorts
} else {
// No preferred ports found. Fall back to other serial ports
// available in the system.
ports = secondaryPorts
}
if len(ports) == 0 {
// fallback
switch runtime.GOOS {
case "darwin":
@@ -727,6 +875,35 @@ func getDefaultPort(portCandidates []string) (port string, err error) {
return "", errors.New("port you specified '" + strings.Join(portCandidates, ",") + "' does not exist, available ports are " + strings.Join(ports, ", "))
}
// getBMPPorts returns BlackMagicProbe's serial ports if any
func getBMPPorts() (gdbPort, uartPort string, err error) {
var portsList []*enumerator.PortDetails
portsList, err = enumerator.GetDetailedPortsList()
if err != nil {
return "", "", err
}
var ports []string
for _, p := range portsList {
if !p.IsUSB {
continue
}
if p.VID != "" && p.PID != "" {
vid, vidErr := strconv.ParseUint(p.VID, 16, 16)
pid, pidErr := strconv.ParseUint(p.PID, 16, 16)
if vidErr == nil && pidErr == nil && vid == 0x1d50 && pid == 0x6018 {
ports = append(ports, p.Name)
}
}
}
if len(ports) == 2 {
return ports[0], ports[1], nil
} else if len(ports) == 0 {
return "", "", errors.New("no BMP detected")
} else {
return "", "", fmt.Errorf("expected 2 BMP serial ports, found %d - did you perhaps connect more than one BMP?", len(ports))
}
}
func usage() {
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
fmt.Fprintln(os.Stderr, "version:", goenv.Version)
@@ -895,6 +1072,7 @@ func main() {
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 (none, coroutines, tasks)")
serial := flag.String("serial", "", "which serial output to use (none, uart, usb)")
printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR")
@@ -904,25 +1082,30 @@ func main() {
printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines")
printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed")
printCommands := flag.Bool("x", false, "Print commands")
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
nodebug := flag.Bool("no-debug", false, "strip debug information")
ocdCommandsString := flag.String("ocd-commands", "", "OpenOCD commands, overriding target spec (can specify multiple separated by commas)")
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
port := flag.String("port", "", "flash port (can specify multiple candidates separated by commas)")
programmer := flag.String("programmer", "", "which hardware programmer to use")
ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags")
wasmAbi := flag.String("wasm-abi", "", "WebAssembly ABI conventions: js (no i64 params) or generic")
llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable")
cpuprofile := flag.String("cpuprofile", "", "cpuprofile output")
var flagJSON, flagDeps *bool
var flagJSON, flagDeps, flagTest *bool
if command == "help" || command == "list" {
flagJSON = flag.Bool("json", false, "print data in JSON format")
flagDeps = flag.Bool("deps", false, "")
flagDeps = flag.Bool("deps", false, "supply -deps flag to go list")
flagTest = flag.Bool("test", false, "supply -test flag to go list")
}
var outpath string
if command == "help" || command == "build" || command == "build-library" || command == "test" {
flag.StringVar(&outpath, "o", "", "output filename")
}
var testCompileOnlyFlag *bool
var testCompileOnlyFlag, testVerboseFlag *bool
if command == "help" || command == "test" {
testCompileOnlyFlag = flag.Bool("c", false, "compile the test binary but do not run it")
testVerboseFlag = flag.Bool("v", false, "verbose: print additional output")
}
// Early command processing, before commands are interpreted by the Go flag
@@ -943,6 +1126,7 @@ func main() {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
var printAllocs *regexp.Regexp
if *printAllocsString != "" {
printAllocs, err = regexp.Compile(*printAllocsString)
@@ -951,24 +1135,35 @@ func main() {
os.Exit(1)
}
}
var ocdCommands []string
if *ocdCommandsString != "" {
ocdCommands = strings.Split(*ocdCommandsString, ",")
}
options := &compileopts.Options{
Target: *target,
Opt: *opt,
GC: *gc,
PanicStrategy: *panicStrategy,
Scheduler: *scheduler,
PrintIR: *printIR,
DumpSSA: *dumpSSA,
VerifyIR: *verifyIR,
Debug: !*nodebug,
PrintSizes: *printSize,
PrintStacks: *printStacks,
PrintAllocs: printAllocs,
PrintCommands: *printCommands,
Tags: *tags,
GlobalValues: globalVarValues,
WasmAbi: *wasmAbi,
Programmer: *programmer,
Target: *target,
Opt: *opt,
GC: *gc,
PanicStrategy: *panicStrategy,
Scheduler: *scheduler,
Serial: *serial,
PrintIR: *printIR,
DumpSSA: *dumpSSA,
VerifyIR: *verifyIR,
Debug: !*nodebug,
PrintSizes: *printSize,
PrintStacks: *printStacks,
PrintAllocs: printAllocs,
Tags: *tags,
GlobalValues: globalVarValues,
WasmAbi: *wasmAbi,
Programmer: *programmer,
OpenOCDCommands: ocdCommands,
LLVMFeatures: *llvmFeatures,
}
if *printCommands {
options.PrintCommands = printCommand
}
os.Setenv("CC", "clang -target="+*target)
@@ -980,6 +1175,20 @@ func main() {
os.Exit(1)
}
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
fmt.Fprintln(os.Stderr, "could not create CPU profile: ", err)
os.Exit(1)
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
fmt.Fprintln(os.Stderr, "could not start CPU profile: ", err)
os.Exit(1)
}
defer pprof.StopCPUProfile()
}
switch command {
case "build":
if outpath == "" {
@@ -1061,16 +1270,26 @@ func main() {
err := Run(pkgName, options)
handleCompilerError(err)
case "test":
pkgName := "."
if flag.NArg() == 1 {
pkgName = filepath.ToSlash(flag.Arg(0))
} else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
usage()
var pkgNames []string
for i := 0; i < flag.NArg(); i++ {
pkgNames = append(pkgNames, filepath.ToSlash(flag.Arg(i)))
}
if len(pkgNames) == 0 {
pkgNames = []string{"."}
}
allTestsPassed := true
for _, pkgName := range pkgNames {
// TODO: parallelize building the test binaries
passed, err := Test(pkgName, options, *testCompileOnlyFlag, *testVerboseFlag, outpath)
handleCompilerError(err)
if !passed {
allTestsPassed = false
}
}
if !allTestsPassed {
fmt.Println("FAIL")
os.Exit(1)
}
err := Test(pkgName, options, *testCompileOnlyFlag, outpath)
handleCompilerError(err)
case "targets":
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
entries, err := ioutil.ReadDir(dir)
@@ -1145,6 +1364,9 @@ func main() {
if *flagDeps {
extraArgs = append(extraArgs, "-deps")
}
if *flagTest {
extraArgs = append(extraArgs, "-test")
}
cmd, err := loader.List(config, extraArgs, flag.Args())
if err != nil {
fmt.Fprintln(os.Stderr, "failed to run `go list`:", err)
+88 -65
View File
@@ -35,9 +35,9 @@ func TestCompiler(t *testing.T) {
"calls.go",
"cgo/",
"channel.go",
"coroutines.go",
"float.go",
"gc.go",
"goroutines.go",
"init.go",
"init_multi.go",
"interface.go",
@@ -51,9 +51,18 @@ func TestCompiler(t *testing.T) {
"stdlib.go",
"string.go",
"structs.go",
"testing.go",
"zeroalloc.go",
}
_, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
if err != nil {
t.Fatal("could not read version from GOROOT:", err)
}
if minor >= 17 {
tests = append(tests, "go1.17.go")
}
if *testTarget != "" {
// This makes it possible to run one specific test (instead of all),
// which is especially useful to quickly check whether some changes
@@ -94,21 +103,9 @@ func TestCompiler(t *testing.T) {
t.Run("ARM64Linux", func(t *testing.T) {
runPlatTests("aarch64--linux-gnu", tests, t)
})
goVersion, err := goenv.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", tests, t)
})
}
t.Run("WebAssembly", func(t *testing.T) {
runPlatTests("wasm", tests, t)
})
t.Run("WASI", func(t *testing.T) {
runPlatTests("wasi", tests, t)
})
@@ -125,7 +122,7 @@ func TestCompiler(t *testing.T) {
// Test with few optimizations enabled (no inlining, etc).
t.Run("opt=1", func(t *testing.T) {
t.Parallel()
runTestWithConfig("stdlib.go", "", t, &compileopts.Options{
runTestWithConfig("stdlib.go", "", t, compileopts.Options{
Opt: "1",
}, nil, nil)
})
@@ -134,15 +131,14 @@ func TestCompiler(t *testing.T) {
// TODO: fix this for stdlib.go, which currently fails.
t.Run("opt=0", func(t *testing.T) {
t.Parallel()
runTestWithConfig("print.go", "", t, &compileopts.Options{
runTestWithConfig("print.go", "", t, compileopts.Options{
Opt: "0",
}, nil, nil)
})
t.Run("ldflags", func(t *testing.T) {
t.Parallel()
runTestWithConfig("ldflags.go", "", t, &compileopts.Options{
Opt: "z",
runTestWithConfig("ldflags.go", "", t, compileopts.Options{
GlobalValues: map[string]map[string]string{
"main": {
"someGlobal": "foobar",
@@ -163,14 +159,20 @@ func runPlatTests(target string, tests []string, t *testing.T) {
runTest(name, target, t, nil, nil)
})
}
if target == "wasi" || target == "" {
t.Run("env.go", func(t *testing.T) {
t.Parallel()
runTest("env.go", target, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"})
})
if target == "" || target == "wasi" {
t.Run("filesystem.go", func(t *testing.T) {
t.Parallel()
runTest("filesystem.go", target, t, nil, nil)
})
t.Run("env.go", func(t *testing.T) {
}
if target == "" || target == "wasi" || target == "wasm" {
t.Run("rand.go", func(t *testing.T) {
t.Parallel()
runTest("env.go", target, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"})
runTest("rand.go", target, t, nil, nil)
})
}
}
@@ -188,20 +190,20 @@ func runBuild(src, out string, opts *compileopts.Options) error {
}
func runTest(name, target string, t *testing.T, cmdArgs, environmentVars []string) {
options := &compileopts.Options{
Target: target,
Opt: "z",
PrintIR: false,
DumpSSA: false,
VerifyIR: true,
Debug: true,
PrintSizes: "",
WasmAbi: "",
options := compileopts.Options{
Target: target,
}
runTestWithConfig(name, target, t, options, cmdArgs, environmentVars)
}
func runTestWithConfig(name, target string, t *testing.T, options *compileopts.Options, cmdArgs, environmentVars []string) {
func runTestWithConfig(name, target string, t *testing.T, options compileopts.Options, cmdArgs, environmentVars []string) {
// Set default config.
options.Debug = true
options.VerifyIR = true
if options.Opt == "" {
options.Opt = "z"
}
// Get the expected output for this test.
// Note: not using filepath.Join as it strips the path separator at the end
// of the path.
@@ -217,57 +219,78 @@ func runTestWithConfig(name, target string, t *testing.T, options *compileopts.O
}
// Create a temporary directory for test output files.
tmpdir, err := ioutil.TempDir("", "tinygo-test")
tmpdir := t.TempDir()
// Determine whether we're on a system that supports environment variables
// and command line parameters (operating systems, WASI) or not (baremetal,
// WebAssembly in the browser). If we're on a system without an environment,
// we need to pass command line arguments and environment variables through
// global variables (built into the binary directly) instead of the
// conventional way.
spec, err := compileopts.LoadTarget(target)
if err != nil {
t.Fatal("could not create temporary directory:", err)
t.Fatal("failed to load target spec:", err)
}
defer func() {
rerr := os.RemoveAll(tmpdir)
if rerr != nil {
t.Errorf("failed to remove temporary directory %q: %s", tmpdir, rerr.Error())
needsEnvInVars := spec.GOOS == "js"
for _, tag := range spec.BuildTags {
if tag == "baremetal" {
needsEnvInVars = true
}
}()
}
if needsEnvInVars {
runtimeGlobals := make(map[string]string)
if len(cmdArgs) != 0 {
runtimeGlobals["osArgs"] = strings.Join(cmdArgs, "\x00")
}
if len(environmentVars) != 0 {
runtimeGlobals["osEnv"] = strings.Join(environmentVars, "\x00")
}
if len(runtimeGlobals) != 0 {
// This sets the global variables like they would be set with
// `-ldflags="-X=runtime.osArgs=first\x00second`.
// The runtime package has two variables (osArgs and osEnv) that are
// both strings, from which the parameters and environment variables
// are read.
options.GlobalValues = map[string]map[string]string{
"runtime": runtimeGlobals,
}
}
}
// Build the test binary.
binary := filepath.Join(tmpdir, "test")
err = runBuild("./"+path, binary, options)
err = runBuild("./"+path, binary, &options)
if err != nil {
printCompilerError(t.Log, err)
t.Fail()
return
}
// Run the test.
runComplete := make(chan struct{})
// Create the test command, taking care of emulators etc.
var cmd *exec.Cmd
ranTooLong := false
if target == "" {
if len(spec.Emulator) == 0 {
cmd = exec.Command(binary)
cmd.Env = append(cmd.Env, environmentVars...)
} else {
args := append(spec.Emulator[1:], binary)
cmd = exec.Command(spec.Emulator[0], args...)
}
if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" {
// Allow reading from the current directory.
cmd.Args = append(cmd.Args, "--dir=.")
for _, v := range environmentVars {
cmd.Args = append(cmd.Args, "--env", v)
}
cmd.Args = append(cmd.Args, cmdArgs...)
} else {
spec, err := compileopts.LoadTarget(target)
if err != nil {
t.Fatal("failed to load target spec:", err)
}
if len(spec.Emulator) == 0 {
cmd = exec.Command(binary)
} else {
args := append(spec.Emulator[1:], binary)
cmd = exec.Command(spec.Emulator[0], args...)
}
if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" {
// Allow reading from the current directory.
cmd.Args = append(cmd.Args, "--dir=.")
for _, v := range environmentVars {
cmd.Args = append(cmd.Args, "--env", v)
}
cmd.Args = append(cmd.Args, cmdArgs...)
} else {
if !needsEnvInVars {
cmd.Args = append(cmd.Args, cmdArgs...) // works on qemu-aarch64 etc
cmd.Env = append(cmd.Env, environmentVars...)
}
}
// Run the test.
runComplete := make(chan struct{})
ranTooLong := false
stdout := &bytes.Buffer{}
cmd.Stdout = stdout
cmd.Stderr = os.Stderr
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package rand implements a cryptographically secure
// random number generator.
package rand
import "io"
// Reader is a global, shared instance of a cryptographically
// secure random number generator.
var Reader io.Reader
// Read is a helper function that calls Reader.Read using io.ReadFull.
// On return, n == len(b) if and only if err == nil.
func Read(b []byte) (n int, err error) {
return io.ReadFull(Reader, b)
}
+30
View File
@@ -0,0 +1,30 @@
// +build darwin freebsd tinygo.wasm
// This implementation of crypto/rand uses the arc4random_buf function
// (available on both MacOS and WASI) to generate random numbers.
//
// Note: arc4random_buf (unlike what the name suggets) does not use the insecure
// RC4 cipher. Instead, it uses a high-quality cipher, varying by the libc
// implementation.
package rand
import "unsafe"
func init() {
Reader = &reader{}
}
type reader struct {
}
func (r *reader) Read(b []byte) (n int, err error) {
if len(b) != 0 {
libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b)))
}
return len(b), nil
}
// void arc4random_buf(void *buf, size_t buflen);
//export arc4random_buf
func libc_arc4random_buf(buf unsafe.Pointer, buflen uint)
+38
View File
@@ -0,0 +1,38 @@
// +build linux,!baremetal,!wasi
// This implementation of crypto/rand uses the /dev/urandom pseudo-file to
// generate random numbers.
// TODO: convert to the getentropy or getrandom libc function on Linux once it
// is more widely supported.
package rand
import (
"syscall"
)
func init() {
Reader = &reader{}
}
type reader struct {
fd int
}
func (r *reader) Read(b []byte) (n int, err error) {
if len(b) == 0 {
return
}
// Open /dev/urandom first if needed.
if r.fd == 0 {
fd, err := syscall.Open("/dev/urandom", syscall.O_RDONLY, 0)
if err != nil {
return 0, err
}
r.fd = fd
}
// Read from the file.
return syscall.Read(r.fd, b)
}
+143
View File
@@ -0,0 +1,143 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rand
import (
"errors"
"io"
"math/big"
)
// smallPrimes is a list of small, prime numbers that allows us to rapidly
// exclude some fraction of composite candidates when searching for a random
// prime. This list is truncated at the point where smallPrimesProduct exceeds
// a uint64. It does not include two because we ensure that the candidates are
// odd by construction.
var smallPrimes = []uint8{
3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
}
// smallPrimesProduct is the product of the values in smallPrimes and allows us
// to reduce a candidate prime by this number and then determine whether it's
// coprime to all the elements of smallPrimes without further big.Int
// operations.
var smallPrimesProduct = new(big.Int).SetUint64(16294579238595022365)
// Prime returns a number, p, of the given size, such that p is prime
// with high probability.
// Prime will return error for any error returned by rand.Read or if bits < 2.
func Prime(rand io.Reader, bits int) (p *big.Int, err error) {
if bits < 2 {
err = errors.New("crypto/rand: prime size must be at least 2-bit")
return
}
b := uint(bits % 8)
if b == 0 {
b = 8
}
bytes := make([]byte, (bits+7)/8)
p = new(big.Int)
bigMod := new(big.Int)
for {
_, err = io.ReadFull(rand, bytes)
if err != nil {
return nil, err
}
// Clear bits in the first byte to make sure the candidate has a size <= bits.
bytes[0] &= uint8(int(1<<b) - 1)
// Don't let the value be too small, i.e, set the most significant two bits.
// Setting the top two bits, rather than just the top bit,
// means that when two of these values are multiplied together,
// the result isn't ever one bit short.
if b >= 2 {
bytes[0] |= 3 << (b - 2)
} else {
// Here b==1, because b cannot be zero.
bytes[0] |= 1
if len(bytes) > 1 {
bytes[1] |= 0x80
}
}
// Make the value odd since an even number this large certainly isn't prime.
bytes[len(bytes)-1] |= 1
p.SetBytes(bytes)
// Calculate the value mod the product of smallPrimes. If it's
// a multiple of any of these primes we add two until it isn't.
// The probability of overflowing is minimal and can be ignored
// because we still perform Miller-Rabin tests on the result.
bigMod.Mod(p, smallPrimesProduct)
mod := bigMod.Uint64()
NextDelta:
for delta := uint64(0); delta < 1<<20; delta += 2 {
m := mod + delta
for _, prime := range smallPrimes {
if m%uint64(prime) == 0 && (bits > 6 || m != uint64(prime)) {
continue NextDelta
}
}
if delta > 0 {
bigMod.SetUint64(delta)
p.Add(p, bigMod)
}
break
}
// There is a tiny possibility that, by adding delta, we caused
// the number to be one bit too long. Thus we check BitLen
// here.
if p.ProbablyPrime(20) && p.BitLen() == bits {
return
}
}
}
// Int returns a uniform random value in [0, max). It panics if max <= 0.
func Int(rand io.Reader, max *big.Int) (n *big.Int, err error) {
if max.Sign() <= 0 {
panic("crypto/rand: argument to Int is <= 0")
}
n = new(big.Int)
n.Sub(max, n.SetUint64(1))
// bitLen is the maximum bit length needed to encode a value < max.
bitLen := n.BitLen()
if bitLen == 0 {
// the only valid result is 0
return
}
// k is the maximum byte length needed to encode a value < max.
k := (bitLen + 7) / 8
// b is the number of bits in the most significant byte of max-1.
b := uint(bitLen % 8)
if b == 0 {
b = 8
}
bytes := make([]byte, k)
for {
_, err = io.ReadFull(rand, bytes)
if err != nil {
return nil, err
}
// Clear bits in the first byte to increase the probability
// that the candidate is < max.
bytes[0] &= uint8(int(1<<b) - 1)
n.SetBytes(bytes)
if n.Cmp(max) < 0 {
return
}
}
}
+15 -6
View File
@@ -26,12 +26,21 @@ type SCB_Type struct {
SHPR2 volatile.Register32 // 0xD1C: System Handler Priority Register 2
SHPR3 volatile.Register32 // 0xD20: System Handler Priority Register 3
// the following are only applicable for Cortex-M3/M33/M4/M7
SHCSR volatile.Register32 // 0xD24: System Handler Control and State Register
CFSR volatile.Register32 // 0xD28: Configurable Fault Status Register
HFSR volatile.Register32 // 0xD2C: HardFault Status Register
DFSR volatile.Register32 // 0xD30: Debug Fault Status Register
MMFAR volatile.Register32 // 0xD34: MemManage Fault Address Register
BFAR volatile.Register32 // 0xD38: BusFault Address Register
SHCSR volatile.Register32 // 0xD24: System Handler Control and State Register
CFSR volatile.Register32 // 0xD28: Configurable Fault Status Register
HFSR volatile.Register32 // 0xD2C: HardFault Status Register
DFSR volatile.Register32 // 0xD30: Debug Fault Status Register
MMFAR volatile.Register32 // 0xD34: MemManage Fault Address Register
BFAR volatile.Register32 // 0xD38: BusFault Address Register
AFSR volatile.Register32 // 0xD3C: Auxiliary Fault Status Register
PFR [2]volatile.Register32 // 0xD40: Processor Feature Register
DFR volatile.Register32 // 0xD48: Debug Feature Register
ADR volatile.Register32 // 0xD4C: Auxiliary Feature Register
MMFR [4]volatile.Register32 // 0xD50: Memory Model Feature Register
ISAR [5]volatile.Register32 // 0xD60: Instruction Set Attributes Register
_ [5]uint32 // reserved
CPACR volatile.Register32 // 0xD88: Coprocessor Access Control Register
}
var SCB = (*SCB_Type)(unsafe.Pointer(uintptr(SCB_BASE)))
+49
View File
@@ -0,0 +1,49 @@
// This is a very minimal bootloader for the ESP32-C3. It only initializes the
// flash and then continues with the generic RISC-V initialization code, which
// in turn will call runtime.main.
// It is written in assembly (and not in a higher level language) to make sure
// it is entirely loaded into IRAM and doesn't accidentally call functions
// stored in IROM.
//
// For reference, here is a nice introduction into RISC-V assembly:
// https://www.imperialviolet.org/2016/12/31/riscv.html
.section .init
.global call_start_cpu0
.type call_start_cpu0,@function
call_start_cpu0:
// At this point:
// - The ROM bootloader is finished and has jumped to here.
// - We're running from IRAM: both IRAM and DRAM segments have been loaded
// by the ROM bootloader.
// - We have a usable stack (but not the one we would like to use).
// - No flash mappings (MMU) are set up yet.
// Reset MMU, see bootloader_reset_mmu in the ESP-IDF.
call Cache_Suspend_ICache
mv s0, a0 // autoload value
call Cache_Invalidate_ICache_All
call Cache_MMU_Init
// Set up DROM from flash.
// Somehow, this also sets up IROM from flash. Not sure why, but it avoids
// the need for another such call.
// C equivalent:
// Cache_Dbus_MMU_Set(MMU_ACCESS_FLASH, 0x3C00_0000, 0, 64, 128, 0)
li a0, 0 // ext_ram: MMU_ACCESS_FLASH
li a1, 0x3C000000 // vaddr: address in the data bus
li a2, 0 // paddr: physical address in the flash chip
li a3, 64 // psize: always 64 (kilobytes)
li a4, 128 // num: pages to be set (8192K / 64K = 128)
li a5, 0 // fixed
call Cache_Dbus_MMU_Set
// Enable the flash cache.
mv a0, s0 // restore autoload value from Cache_Suspend_ICache call
call Cache_Resume_ICache
// Jump to generic RISC-V initialization, which initializes the stack
// pointer and globals register. It should not return.
// (It appears that the linker relaxes this jump and instead inserts the
// _start function right after here).
j _start
+16 -14
View File
@@ -2,6 +2,7 @@
// Type definitions, fields, and constants associated with the MPU peripheral
// of the NXP MIMXRT1062.
//go:build nxp && mimxrt1062
// +build nxp,mimxrt1062
package nxp
@@ -216,6 +217,12 @@ func enableIcache(enable bool) {
}
}
var (
dcacheCcsidr volatile.Register32
dcacheSets volatile.Register32
dcacheWays volatile.Register32
)
func enableDcache(enable bool) {
if enable != SystemControl.CCR.HasBits(SCB_CCR_DC_Msk) {
if enable {
@@ -244,11 +251,6 @@ func enableDcache(enable bool) {
isb 0xF
`, nil)
} else {
var (
ccsidr volatile.Register32
sets volatile.Register32
ways volatile.Register32
)
SystemControl.CSSELR.Set(0)
arm.AsmFull(`
dsb 0xF
@@ -257,17 +259,17 @@ func enableDcache(enable bool) {
arm.AsmFull(`
dsb 0xF
`, nil)
ccsidr.Set(SystemControl.CCSIDR.Get())
sets.Set((ccsidr.Get() & SCB_CCSIDR_NUMSETS_Msk) >> SCB_CCSIDR_NUMSETS_Pos)
for sets.Get() != 0 {
ways.Set((ccsidr.Get() & SCB_CCSIDR_ASSOCIATIVITY_Msk) >> SCB_CCSIDR_ASSOCIATIVITY_Pos)
for ways.Get() != 0 {
dcacheCcsidr.Set(SystemControl.CCSIDR.Get())
dcacheSets.Set((dcacheCcsidr.Get() & SCB_CCSIDR_NUMSETS_Msk) >> SCB_CCSIDR_NUMSETS_Pos)
for dcacheSets.Get() != 0 {
dcacheWays.Set((dcacheCcsidr.Get() & SCB_CCSIDR_ASSOCIATIVITY_Msk) >> SCB_CCSIDR_ASSOCIATIVITY_Pos)
for dcacheWays.Get() != 0 {
SystemControl.DCCISW.Set(
((sets.Get() << SCB_DCCISW_SET_Pos) & SCB_DCCISW_SET_Msk) |
((ways.Get() << SCB_DCCISW_WAY_Pos) & SCB_DCCISW_WAY_Msk))
ways.Set(ways.Get() - 1)
((dcacheSets.Get() << SCB_DCCISW_SET_Pos) & SCB_DCCISW_SET_Msk) |
((dcacheWays.Get() << SCB_DCCISW_WAY_Pos) & SCB_DCCISW_WAY_Msk))
dcacheWays.Set(dcacheWays.Get() - 1)
}
sets.Set(sets.Get() - 1)
dcacheSets.Set(dcacheSets.Get() - 1)
}
arm.AsmFull(`
dsb 0xF
+15
View File
@@ -0,0 +1,15 @@
// +build feather_m4_can
package main
import (
"machine"
)
func init() {
// power on the CAN Transceiver
// https://learn.adafruit.com/adafruit-feather-m4-can-express/pinouts#can-bus-3078990-8
boost_en := machine.BOOST_EN
boost_en.Configure(machine.PinConfig{Mode: machine.PinOutput})
boost_en.High()
}
+53
View File
@@ -0,0 +1,53 @@
package main
import (
"fmt"
"machine"
"time"
)
func main() {
can1 := machine.CAN1
can1.Configure(machine.CANConfig{
TransferRate: machine.CANTransferRate500kbps,
TransferRateFD: machine.CANTransferRate1000kbps,
Rx: machine.CAN1_RX,
Tx: machine.CAN1_TX,
Standby: machine.CAN1_STANDBY,
})
can0 := machine.CAN0
can0.Configure(machine.CANConfig{
TransferRate: machine.CANTransferRate500kbps,
TransferRateFD: machine.CANTransferRate1000kbps,
Rx: machine.CAN0_RX,
Tx: machine.CAN0_TX,
Standby: machine.NoPin,
})
rxMsg := machine.CANRxBufferElement{}
for {
can1.Tx(0x123, []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, false, false)
can1.Tx(0x789, []byte{0x02, 0x24, 0x46, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, true, false)
time.Sleep(time.Millisecond * 1000)
sz0 := can0.RxFifoSize()
if sz0 > 0 {
fmt.Printf("CAN0 %d\r\n", sz0)
for i := 0; i < sz0; i++ {
can0.RxRaw(&rxMsg)
fmt.Printf("-> %08X %X %#v\r\n", rxMsg.ID, rxMsg.DLC, rxMsg.Data())
}
}
sz1 := can1.RxFifoSize()
if sz1 > 0 {
fmt.Printf("CAN1 %d\r\n", sz1)
for i := 0; i < sz1; i++ {
can1.RxRaw(&rxMsg)
fmt.Printf("-> %08X %X %#v\r\n", rxMsg.ID, rxMsg.DLC, rxMsg.Data())
}
}
}
}
@@ -0,0 +1,15 @@
// +build feather_m4_can
package main
import (
"machine"
)
func init() {
// power on the CAN Transceiver
// https://learn.adafruit.com/adafruit-feather-m4-can-express/pinouts#can-bus-3078990-8
boost_en := machine.BOOST_EN
boost_en.Configure(machine.PinConfig{Mode: machine.PinOutput})
boost_en.High()
}
+75
View File
@@ -0,0 +1,75 @@
package main
import (
"device/sam"
"fmt"
"machine"
"time"
)
type canMsg struct {
ch byte
id uint32
dlc byte
data []byte
}
func main() {
ch := make(chan canMsg, 10)
go func() {
for {
select {
case m := <-ch:
fmt.Printf("%d %03X %X ", m.ch, m.id, m.dlc)
for _, d := range m.data {
fmt.Printf("%02X ", d)
}
fmt.Printf("\r\n")
}
}
}()
can1 := machine.CAN1
can1.Configure(machine.CANConfig{
TransferRate: machine.CANTransferRate500kbps,
TransferRateFD: machine.CANTransferRate1000kbps,
Rx: machine.CAN1_RX,
Tx: machine.CAN1_TX,
Standby: machine.CAN1_STANDBY,
})
// RF0NE : Rx FIFO 0 New Message Interrupt Enable
can1.SetInterrupt(sam.CAN_IE_RF0NE, func(*machine.CAN) {
rxMsg := machine.CANRxBufferElement{}
can1.RxRaw(&rxMsg)
m := canMsg{ch: 1, id: rxMsg.ID, dlc: rxMsg.DLC, data: rxMsg.Data()}
select {
case ch <- m:
}
})
can0 := machine.CAN0
can0.Configure(machine.CANConfig{
TransferRate: machine.CANTransferRate500kbps,
TransferRateFD: machine.CANTransferRate1000kbps,
Rx: machine.CAN0_RX,
Tx: machine.CAN0_TX,
Standby: machine.NoPin,
})
// RF0NE : Rx FIFO 0 New Message Interrupt Enable
can0.SetInterrupt(sam.CAN_IE_RF0NE, func(*machine.CAN) {
rxMsg := machine.CANRxBufferElement{}
can0.RxRaw(&rxMsg)
m := canMsg{ch: 2, id: rxMsg.ID, dlc: rxMsg.DLC, data: rxMsg.Data()}
select {
case ch <- m:
}
})
for {
can0.Tx(0x123, []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}, false, false)
time.Sleep(time.Millisecond * 500)
can1.Tx(0x456, []byte{0xAA, 0xBB, 0xCC}, false, false)
time.Sleep(time.Millisecond * 1000)
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
// change these to test a different UART or pins if available
var (
uart = machine.UART0
uart = machine.Serial
tx = machine.UART_TX_PIN
rx = machine.UART_RX_PIN
)
+32
View File
@@ -0,0 +1,32 @@
package main
import (
"math/rand"
"runtime"
"time"
)
func main() {
ms := runtime.MemStats{}
for {
escapesToHeap()
runtime.ReadMemStats(&ms)
println("Heap before GC. Used: ", ms.HeapInuse, " Free: ", ms.HeapIdle, " Meta: ", ms.GCSys)
runtime.GC()
runtime.ReadMemStats(&ms)
println("Heap after GC. Used: ", ms.HeapInuse, " Free: ", ms.HeapIdle, " Meta: ", ms.GCSys)
time.Sleep(5 * time.Second)
}
}
func escapesToHeap() {
n := rand.Intn(100)
println("Doing ", n, " iterations")
for i := 0; i < n; i++ {
s := make([]byte, i)
_ = append(s, 42)
}
}
+10
View File
@@ -0,0 +1,10 @@
// +build stm32
package main
import "machine"
const (
buttonMode = machine.PinInputPulldown
buttonPinChange = machine.PinRising | machine.PinFalling
)
+11
View File
@@ -0,0 +1,11 @@
// +build bluepill
package main
import "machine"
var (
pwm = &machine.TIM2
pinA = machine.PA0
pinB = machine.PA1
)
+11
View File
@@ -0,0 +1,11 @@
// +build stm32f7
package main
import "machine"
var (
pwm = &machine.TIM1
pinA = machine.PA8
pinB = machine.PA9
)
+11
View File
@@ -0,0 +1,11 @@
// +build stm32l0
package main
import "machine"
var (
pwm = &machine.TIM2
pinA = machine.PA0
pinB = machine.PB3
)
+11
View File
@@ -0,0 +1,11 @@
// +build stm32l4
package main
import "machine"
var (
pwm = &machine.TIM2
pinA = machine.PA0
pinB = machine.PB3
)
+11
View File
@@ -0,0 +1,11 @@
// +build stm32l5
package main
import "machine"
var (
pwm = &machine.TIM1
pinA = machine.PA8
pinB = machine.PA9
)
+11
View File
@@ -0,0 +1,11 @@
// +build pico
package main
import "machine"
var (
pwm = machine.PWM4 // Pin 25 (LED on pico) corresponds to PWM4.
pinA = machine.LED
pinB = machine.GPIO24
)
+13
View File
@@ -0,0 +1,13 @@
// +build stm32f4disco
package main
import "machine"
var (
// These pins correspond to LEDs on the discovery
// board
pwm = &machine.TIM4
pinA = machine.PD12
pinB = machine.PD13
)
+1 -1
View File
@@ -13,7 +13,7 @@ type Task struct {
Ptr unsafe.Pointer
// Data is a field which can be used for storing state information.
Data uint
Data uint64
// state is the underlying running state of the task.
state state
+58
View File
@@ -0,0 +1,58 @@
.section .text.tinygo_startTask
.global tinygo_startTask
.type tinygo_startTask, %function
tinygo_startTask:
.cfi_startproc
// Small assembly stub for starting a goroutine. This is already run on the
// new stack, with the callee-saved registers already loaded.
// Most importantly, EBX contain the pc of the to-be-started function and
// ESI contain the only argument it is given. Multiple arguments are packed
// into one by storing them in a new allocation.
// Indicate to the unwinder that there is nothing to unwind, this is the
// root frame. It avoids bogus extra frames in GDB.
.cfi_undefined eip
// Set the first argument of the goroutine start wrapper, which contains all
// the arguments.
pushl %esi
// Branch to the "goroutine start" function.
calll *%ebx
// Rebalance the stack (to undo the above push).
addl $4, %esp
// After return, exit this goroutine. This is a tail call.
jmp tinygo_pause
.cfi_endproc
.global tinygo_swapTask
.type tinygo_swapTask, %function
tinygo_swapTask:
// This function gets the following parameters:
movl 4(%esp), %eax // newStack uintptr
movl 8(%esp), %ecx // oldStack *uintptr
// More information on the calling convention:
// https://wiki.osdev.org/System_V_ABI#i386
// Save all callee-saved registers:
pushl %ebp
pushl %edi
pushl %esi
pushl %ebx
// Save the current stack pointer in oldStack.
movl %esp, (%ecx)
// Switch to the new stack pointer.
movl %eax, %esp
// Load saved register from the new stack.
popl %ebx
popl %esi
popl %edi
popl %ebp
// Return into the new task, as if tinygo_swapTask was a regular call.
ret
+62
View File
@@ -0,0 +1,62 @@
// +build scheduler.tasks,386
package task
import "unsafe"
var systemStack uintptr
// calleeSavedRegs is the list of registers that must be saved and restored when
// switching between tasks. Also see task_stack_386.S that relies on the exact
// layout of this struct.
type calleeSavedRegs struct {
ebx uintptr
esi uintptr
edi uintptr
ebp uintptr
pc uintptr
// Pad this struct so that tasks start on a 16-byte aligned stack.
_ [3]uintptr
}
// archInit runs architecture-specific setup for the goroutine startup.
func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) {
// Store the initial sp for the startTask function (implemented in assembly).
s.sp = uintptr(unsafe.Pointer(r))
// Initialize the registers.
// These will be popped off of the stack on the first resume of the goroutine.
// Start the function at tinygo_startTask (defined in
// src/internal/task/task_stack_386.S). This assembly code calls a function
// (passed in EBX) with a single argument (passed in ESI). After the
// function returns, it calls Pause().
r.pc = uintptr(unsafe.Pointer(&startTask))
// Pass the function to call in EBX.
// This function is a compiler-generated wrapper which loads arguments out
// of a struct pointer. See createGoroutineStartWrapper (defined in
// compiler/goroutine.go) for more information.
r.ebx = fn
// Pass the pointer to the arguments struct in ESI.
r.esi = uintptr(args)
}
func (s *state) resume() {
swapTask(s.sp, &systemStack)
}
func (s *state) pause() {
newStack := systemStack
systemStack = 0
swapTask(newStack, &s.sp)
}
// SystemStack returns the system stack pointer when called from a task stack.
// When called from the system stack, it returns 0.
func SystemStack() uintptr {
return systemStack
}

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