Compare commits

...

84 Commits

Author SHA1 Message Date
Ayke van Laethem 52983794d7 all: version 0.35.0 2024-12-20 12:09:22 +01:00
Ayke van Laethem 9d2f52805b builder: show files in size report table
Show which files cause a binary size increase. This makes it easier to
see where the size is going: for example, this makes it easy to see how
much the GC contributes to code size compared to other runtime parts.
2024-12-19 15:08:37 +01:00
Ayke van Laethem b18213805a builder: write HTML size report
This is not a big change over the existing size report with -size=full,
but it is a bit more readable.

More information will be added in subsequent commits.
2024-12-19 15:08:37 +01:00
Thomas Legris eeba90fd5b properly handle unix read on directory 2024-12-19 13:44:11 +01:00
deadprogram 6507765883 feature: make RNG implementation shared for rp2040/rp2350
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-19 12:54:25 +01:00
Ayke van Laethem a98e35ebab reflect: fix incorrect comment on elemType
PR that introduced this: https://github.com/tinygo-org/tinygo/pull/4543
2024-12-19 11:17:26 +01:00
deadprogram c4cfc01ba3 targets: add support for Pimoroni Tiny2350 board
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-19 10:12:57 +01:00
deadprogram f64e70e659 feature: make SPI implementation shared for rp2040/rp2350
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-19 08:01:36 +01:00
deadprogram 64fa7853a7 feature: make i2c implementation shared for rp2040/rp2350
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-18 21:16:07 +01:00
Patricio Whittingslow 37f35f8c91 Add RP2350 support (#4459)
machine/rp2350: add support

* add linker scripts for rp2350
* add bootloader
* begin melding rp2040 and rp2350 APIs
* add UART
* add rp2350 boot patching
* Fix RP2350 memory layout (#4626)
* Remove rp2040-style second stage bootloader.
* Add 'minimum viable' IMAGE_DEF embedded block
* Create a pico2 specific target
* Implement rp2350 init, clock, and uart support
* Merge rp2 reset code back together
* Separate chip-specific clock definitions
* Clear pad isolation bit on rp2350
* Init UART in rp2350 runtime
* Correct usb/serial initialization order
* Implement jump-to-bootloader
* test: add pico2 to smoketests

---------

Signed-off-by: deadprogram <ron@hybridgroup.com>
Co-authored-by: Matthew Mets <matt.mets@cibomahto.com>
Co-authored-by: Matt Mets <matt@blinkinlabs.com>
Co-authored-by: deadprogram <ron@hybridgroup.com>
2024-12-18 19:36:30 +01:00
deadprogram 0d13e61d0c fix: add build tags to ensure that tkey target has stubs for runtime/interrupt package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-18 17:48:55 +01:00
deadprogram 5701bf81f3 feature: modify i2s interface/implementation to better match specification
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-18 15:23:00 +01:00
Ayke van Laethem 6110f0bc1b runtime: make channels parallelism-safe 2024-12-16 17:58:00 +01:00
Ron Evans 17302ca762 targets: add implementation for Tillitis TKey device (#4631)
* initial implementation for Tillitis TKey device
* add UART implementation for TKey
* add Pin interface implementation for TKey touch sensor
* add RNG interface implementation for TKey
* add helpful machine package functions to return identifiers such as name and version for TKey
* use built-in timer for sleep timing on TKey
* modify UART implementation for TKey to implement Serialer interface
* implement BLAKE2s ROM function call for TKey device
* handle abort by triggering TKey device fault using illegal instruction to halt CPU
* simplify TKey implementation by inheriting from existing riscv32 target
* return error for trying to configure invalid baudrates on UART
* add tkey to builder test
* be very specific for features passed to LLVM for specific config in use for TKey
* handle feedback items from TKey device code review

Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-14 14:26:03 +01:00
Ayke van Laethem f246599253 compiler: report error instead of crashing on missing function body
This can happen with generic functions, see:
https://github.com/tinygo-org/tinygo/issues/4486
2024-12-14 13:34:58 +01:00
deadprogram ec3f387f21 fix: specify ubuntu-22.04 during GH actions transition period to ubuntu-24.04
See https://github.com/actions/runner-images/issues/10636

Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-09 20:26:06 +01:00
Ayke van Laethem 31f7214156 test: make tests deterministic with -scheduler=threads 2024-12-06 11:55:34 +01:00
Ayke van Laethem 6faf36fc64 sync: make Cond MT-safe
This actually simplifies the code and avoids a heap allocation in the
call to Wait. Instead, it uses the Data field of the task to store
information on whether a task was signalled early.
2024-12-06 11:04:14 +01:00
deadprogram edb2f2a417 make: modify smoketest for nintendoswitch target to build something that includes the 'os' package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-04 15:53:49 +01:00
deadprogram 3eee686932 fix: allow nintendoswitch target to compile
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-12-04 15:53:49 +01:00
Ayke van Laethem 4aac3cd7b1 sync: implement WaitGroup using a futex
This prepares sync.WaitGroup for multithreading.
Code size for the cooperative scheduler is nearly unchanged.
2024-12-04 11:13:35 +01:00
Ayke van Laethem 2588bf7fa0 internal/task: add cooperative implementation of Futex
See the code comments for details. But in short, this implements a futex
for the cooperative scheduler (that is single threaded and
non-reentrant). Similar implementations can be made for basically every
other operating system, and even WebAssembly with the threading
(actually: atomics) proposal.
Using this basic futex implementation means we can use the same
implementation for synchronisation primitives on cooperative and
multicore systems.

For more information on futex across operating systems:
https://outerproduct.net/futex-dictionary.html
2024-12-04 10:12:55 +01:00
Ayke van Laethem 72f564555e internal/task: add non-atomic atomic operations
This adds some non-atomic types that have the same interface as the ones
in sync/atomic.

We currently don't need these to be atomic (because the scheduler is
entirely cooperative), but once we add support for a scheduler with
multiple threads and/or preemptive scheduling we can trivially add some
type aliases under a different build tag in the future when real
atomicity is needed for threading.
2024-12-04 10:12:52 +01:00
Ayke van Laethem b5a8931fb5 runtime: remove Cond
I don't think this is used anywhere right now, and it would need to be
updated to work with multithreading. So instead of fixing it, I think we
can remove it.

My original intention was to have something like this that could be used
in the machine package, but since this is in the runtime package (and
the runtime package imports the machine package on baremetal) it can't
actually be used that way.

I checked the TinyGo repo and the drivers repo, and `runtime.Cond` isn't
used anywhere except in that one test.
2024-12-04 09:43:10 +01:00
Ayke van Laethem d3810ecd48 ci: cache the Go cache across builds
This should hopefully make the build slightly faster.
2024-12-04 06:58:04 +01:00
Ayke van Laethem 3b8062170c mips: fix a bug when scanning the stack
Previously the assembler was reordering this code:

    jal tinygo_scanstack
    move $a0, $sp

Into this:

    jal tinygo_scanstack
    nop
    move $a0, $sp

So it was "helpfully" inserting a branch delay slot, even though this
was already being taken care of.
Somehow this didn't break, but it does break in the WIP threading branch
(https://github.com/tinygo-org/tinygo/pull/4559) where this bug leads to
a crash.
2024-12-01 11:23:27 +01:00
Ayke van Laethem 26c36d0a2e runtime: lock output in print/println
This ensures that calls to print/println happening in different threads
are not interleaved. It's a task.PMutex, so this should only change
things when threading is used.

This matches the Go compiler, which does the same thing:
https://godbolt.org/z/na5KzE7en

The locks are not recursive, which means that we need to be careful to
not call `print` or `println` inside a runtime.print* implementation,
inside putchar (recursively), and inside signal handlers. Making them
recursive might be useful to do in the future, but it's not really
necessary.
2024-12-01 11:12:00 +01:00
Ayke van Laethem 09a22ac4b4 runtime: make signals parallelism-safe 2024-12-01 07:40:22 +01:00
Ayke van Laethem aed555d858 runtime: implement Goexit
This is needed for full support for the testing package
2024-11-30 11:55:22 +01:00
deadprogram 65b085a5d5 examples: use default UART settings in echo example
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-11-29 11:33:45 +01:00
Ayke van Laethem 392a709b77 runtime: use uint32 for the channel state and select index
This uses uint32 instead of uint64. The reason for this is that uint64
atomic operations aren't universally available (especially on 32-bit
architectures). We could also use uintptr, but that seems needlessly
complicated: it's unlikely real-world programs will use more than a
billion select states (2^30).
2024-11-27 12:16:10 +01:00
Ayke van Laethem ee6fcd76f4 builder: add testing for -size=full
This helps to make sure this feature continues to work and we won't
accidentally introduce regressions.
2024-11-26 12:41:12 +01:00
Ayke van Laethem ca80c52df1 builder: fix wasi-libc path names on Windows with -size=full
Without this fix, wasi-libc is listed as follows:

     65       0       0       0 |      65       0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-bottom-half\sources
     14       0       0       0 |      14       0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-top-half\musl\src\exit
   1398       0       0       0 |    1398       0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-top-half\musl\src\string
   1525       0       0       0 |    1525       0 | C:\Users\Ayke\src\tinygo\tinygo\lib\wasi-libc\libc-top-half\sources

With this fix, it's identified as the wasi-libc C library:

   3002       0       0       0 |    3002       0 | C wasi-libc
2024-11-26 12:41:12 +01:00
Ayke van Laethem b76ea29520 builder: work around bug in DWARF paths in Clang
See bug: https://github.com/llvm/llvm-project/issues/117317
2024-11-26 12:41:12 +01:00
Ayke van Laethem 9172cc15d2 builder: fix cache paths in -size=full output
This fixes long paths from the TinyGo cached GOROOT, as can be seen
here:

   code  rodata    data     bss |   flash     ram | package
------------------------------- | --------------- | -------
      0       5       0       5 |       5       5 | (padding)
    148       0       0       5 |     148       5 | (unknown)
     76       0       0       0 |      76       0 | /home/ayke/.cache/tinygo/goroot-ce8827882be9dc201bed279a631881177ae124ea064510684a3cf4bb66436e1a/src/device/arm
      4       0       0       0 |       4       0 | /home/ayke/.cache/tinygo/goroot-ce8827882be9dc201bed279a631881177ae124ea064510684a3cf4bb66436e1a/src/internal/task

They're now attributed to the correct package instead (device/arm and
internal/task).
2024-11-26 12:41:12 +01:00
Ayke van Laethem 1b83d43cfa cgo: fix build warnings on Windows ARM
Fix the warning, and also remove tinygo_clang_enum_visitor which was
unused.

See: https://github.com/golang/go/issues/49721
2024-11-26 09:30:13 +01:00
Matt Mets 7847f4ea8e Fix invalid assembler syntax from gen-device-svd
This addresses #4608
2024-11-22 14:44:27 +01:00
Matt Mets 19736e5be2 Update cmsis-svd library
This updates the version of the cmsis-svd library, to include a version
that supports rp2350. This is in support of #4452
2024-11-22 14:44:27 +01:00
Ayke van Laethem 51504bfd2e sync: make Pool thread-safe
Make sure the object is locked when trying to modify it.
Binary size seems unaffected when not using threading.
2024-11-22 11:55:52 +01:00
Ayke van Laethem 79164dae71 sync: only use a lock in the Map implementation when needed 2024-11-22 09:42:39 +01:00
Ayke van Laethem 8d04821639 runtime: prepare the leaking GC for concurrent operations
This uses the task.PMutex parallel-only-mutex type to make the leaking
GC parallelism safe. The task.PMutex type is currently a no-op but will
become a real mutex once we add true parallelism.
2024-11-22 08:17:35 +01:00
Ayke van Laethem f75187392d internal/task: implement PMutex
PMutex is a mutex when threading is possible, and a dummy mutex-like
object (that doesn't do anything) otherwise.
2024-11-22 08:17:35 +01:00
Ayke van Laethem ecc6d16f22 interp: align created globals
Use the alignment from the align attribute of the runtime.alloc call.
This is going to be a more accurate alignment, and is typically smaller
than the default.
2024-11-21 10:19:19 +01:00
Ayke van Laethem 5f252b3e16 runtime: fix regression introduced by merging conflicting PRs 2024-11-21 09:37:13 +01:00
Ayke van Laethem dd1ebbd31b runtime: implement race-free signals using futexes
This requires an API introduced in MacOS 11. I think that's fine, since
the version before that (MacOS 10.15) is EOL since 2022. Though if
needed, we could certainly work around it by using an older and slightly
less nice API.
2024-11-20 18:50:34 +01:00
Ayke van Laethem 95671469c7 runtime: use SA_RESTART when registering a signal
This really is the only sane way to register a signal. If this flag is
not set, many syscalls will return EINTR (and not complete their
operation) which will be a massive source of hard-to-debug bugs.
2024-11-20 18:50:34 +01:00
deadprogram 6601c1b1a6 fix: add updated test output to match recent changes
Signed-off-by: deadprogram <ron@hybridgroup.com>
2024-11-20 17:55:04 +01:00
Ayke van Laethem 5ebda89d78 runtime: rewrite channel implementation
This rewrite simplifies the channel implementation considerably, with
34% less LOC. Perhaps the most important change is the removal of the
channel state, which made sense when we had only send and receive
operations but only makes things more compliated when multiple select
operations can be pending on a single channel.

I did this rewrite originally to make it possible to make channels
parallelism-safe. The current implementation is not parallelism-safe,
but it will be easy to make it so (the main additions will be a channel
lock, a global select lock, and an atomic compare-and-swap in
chanQueue.pop).
2024-11-20 14:57:30 +01:00
Ayke van Laethem b7a3fd8d2f cgo: add support for #cgo noescape lines
Here is the proposal:
https://github.com/golang/go/issues/56378

They are documented here:
https://pkg.go.dev/cmd/cgo@master#hdr-Optimizing_calls_of_C_code

This would have been very useful to fix
https://github.com/tinygo-org/bluetooth/issues/176 in a nice way. That
bug is now fixed in a different way using a wrapper function, but once
this new noescape pragma gets included in TinyGo we could remove the
workaround and use `#cgo noescape` instead.
2024-11-20 14:07:55 +01:00
Ayke van Laethem fd625f7265 compiler: add //go:noescape pragma
This only works on declarations, not definitions. This is intentional:
it follows the upstream Go implemetation.

However, we might want to loosen this requirement at some point: TinyGo
sometimes stores pointers in memory mapped I/O knowing they won't
actually escape, but the compiler doesn't know about this.
2024-11-20 14:07:55 +01:00
Ayke van Laethem d1fe02df23 runtime: move scheduler code around
This moves all scheduler code into a separate file that is only compiled
when there's a scheduler in use (the tasks or asyncify scheduler, which
are both cooperative). The main goal of this change is to make it easier
to add a new "scheduler" based on OS threads.

It also fixes a few subtle issues with `-gc=none`:

  - Gosched() panicked. This is now fixed to just return immediately
    (the only logical thing to do when there's only one goroutine).
  - Timers aren't supported without a scheduler, but the relevant code
    was still present and would happily add a timer to the queue. It
    just never ran. So now it exits with a runtime error, similar to any
    blocking operation.
2024-11-20 12:45:09 +01:00
Ayke van Laethem 6593cf22fa cgo: support errno value as second return parameter
Making this work on all targets was interesting but there's now a test
in place to make sure this works on all targets that have the CGo test
enabled (which is almost all targets).
2024-11-20 07:53:59 +01:00
Damian Gryski 548fba82e6 compiler: Fix wasmimport -> wasmexport in error message
Fixes #4615
2024-11-20 07:53:02 +01:00
Ayke van Laethem 2d26b6cc8d windows: don't return, exit via exit(0) instead
This fixes a bug where output would not actually be written to stdout
before exiting the process, leading to a flaky Windows CI. Exiting using
`exit(0)` appears to fix this.

For some background, see: https://github.com/tinygo-org/tinygo/pull/4589
2024-11-19 11:58:07 +01:00
Ayke van Laethem 0087d4c6bf syscall: use wasi-libc tables for wasm/js target
Instead of using fake tables for errno and others, use the ones that
correspond to wasi-libc.
2024-11-19 07:51:58 +01:00
Ayke van Laethem 289fceb3ea syscall: refactor environment handling
* Move environment functions to their own files.
  * Rewrite the WASIp2 version of environment variables to be much
    simpler (don't go through C functions).
2024-11-19 07:51:58 +01:00
Ayke van Laethem e12da15f7d cgo: support function-like macros
This is needed for code like this:

    #define __WASI_ERRNO_INVAL (UINT16_C(28))
    #define EINVAL __WASI_ERRNO_INVAL
2024-11-18 18:35:20 +01:00
Ayke van Laethem c4867c8743 cgo: define idents referenced only from macros 2024-11-18 18:35:20 +01:00
Ayke van Laethem 8068419854 runtime: heapptr only needs to be initialized once
There is no need to initialize it twice.
2024-11-18 17:14:55 +01:00
Ayke van Laethem 9b3eb3fe59 ci: run at least some tests on older Go/LLVM versions
These should make sure basic functionality is still working.
Using the `-short` flag to avoid taking too long to run all tests (and
to install all the necessary emulators), and because some targets might
not work in older Go/LLVM versions (such as WASI).

This does _not_ run tests and checks against expected IR, because LLVM
IR changes a lot across versions.
2024-11-18 16:42:14 +01:00
leongross 1dcccf87b5 linux: add runtime.fcntl function
This is needed for the internal/syscall/unix package.

Signed-off-by: leongross <leon.gross@9elements.com>
2024-11-18 14:44:06 +01:00
Ayke van Laethem d51ef253a9 builder: whitelist temporary directory env var for Clang invocation
It looks like this breaks on Windows:
https://github.com/tinygo-org/tinygo/issues/4557
I haven't confirmed this is indeed the problem, but it would make sense.
And passing through the temporary directory seems like a good idea
regardless, there's not much that could break due to that.
2024-11-15 18:27:48 +01:00
Ayke van Laethem 6d4dfcf72f compiler, runtime: move constants into shared package
Use a single package for certain constants that must be the same between
the compiler and the runtime.

While just using the same values in both places works, this is much more
obvious and harder to mess up. It also avoids the need for comments
pointing to the other location the constant is defined. And having it in
code makes it possible for IDEs to analyze the source.

In the future, more such constants and maybe algorithms can be added.
2024-11-15 10:11:57 +01:00
sivchari ac9f72be61 support to parse devl version
Signed-off-by: sivchari <shibuuuu5@gmail.com>
2024-11-15 09:21:47 +01:00
Ayke van Laethem 258dac2324 wasm: tidy up wasm_exec.js a bit 2024-11-14 11:02:54 -08:00
Ayke van Laethem b7d91e2f33 ci: use TinyGo version in artifact files
This avoids needing to rename them ourselves (which is kinda annoying)
and also avoids mistakes in the process.
2024-11-14 10:38:56 +01:00
Ayke van Laethem 860697257b runtime: optimize findHead
This is similar to https://github.com/tinygo-org/tinygo/pull/3899, but
smaller and hopefully just as efficient.

Thanks to @HattoriHanzo031 for starting this work, benchmarking, and for
improving the performance of the code even further.
2024-11-14 09:28:25 +01:00
Damian Gryski 91563cff1f targets/wasm_exec: call process.exit() when go.run() returns 2024-11-14 09:16:25 +01:00
Ben Krieger 4f96a50fd0 reflect: fix Copy of non-pointer array with size > 64bits 2024-11-13 10:28:33 -07:00
Ben Krieger e98fea0bba reflect: add Value.Clear; support anytype->interface{}, Slice->(*)Array in Value.Convert 2024-11-13 10:28:33 -07:00
Ayke van Laethem c728e031df goenv: read git hash embedded in the binary
The git hash that's part of `tinygo version` is now read from the binary
itself instead of embedding it with a `-ldflags` flag. This means it is
also present when building TinyGo using `go build` or `go install`.
2024-11-10 08:59:22 +01:00
Ayke van Laethem ceb7891986 wasm: correctly return from run() in wasm_exec.js
Instead of hanging forever, it should return the exit code from os.Exit.
2024-11-08 11:55:38 +01:00
Ayke van Laethem 04a7baec3e wasm: support //go:wasmexport functions after a call to time.Sleep
This fixes a bug where `//go:wasmexport` functions would not be allowed
anymore after a call to `time.Sleep` (when using `-buildmode=default`).
2024-11-08 11:55:38 +01:00
Ayke van Laethem 8ff97bdedd runtime: remove unnecessary check for negative sleepTicks duration
This is now fixed for every target in the previous commit.

Also see: https://github.com/tinygo-org/tinygo/pull/4239
2024-11-07 15:37:18 +01:00
Ayke van Laethem a6c4287b4d runtime: don't call sleepTicks with a negative duration
There are rare cases where this can happen, see for example
https://github.com/tinygo-org/tinygo/issues/4568
2024-11-07 15:37:18 +01:00
leongross f9f439ad49 os: implement StartProcess
Signed-off-by: leongross <leon.gross@9elements.com>
2024-11-07 09:45:47 +01:00
Randy Reddig c02a8141c7 internal/wasm-tools, syscall: update to wasm-tools-go@v0.3.1 (#4577)
* internal/wasm-tools, internal/cm: update wasm-tools-go to v0.3.1 and regenerate bindings

* syscall: use new (cm.Result).Result() method instead of OK() and Err()
2024-11-04 14:32:17 -08:00
sago35 5862b481a4 goenv: update to new v0.35.0 development version 2024-11-01 09:14:09 +01:00
Joonas Bergius 1648fe8ef2 runtime/trace: stub all public methods
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-11-01 09:12:59 +01:00
Ayke van Laethem 449eefdb19 compiler: allow deferred panic
This is rare, but apparently some programs do this:

    defer panic("...")

This is emitted in the IR as a builtin function.
2024-11-01 09:11:38 +01:00
Ayke van Laethem 058f62ac08 main: parse extldflags early so we can report the error message
This avoids some weird behavior when the -extldflags flag cannot be
parsed by TinyGo.
2024-11-01 09:10:49 +01:00
Ayke van Laethem 4e49ba597d interrupt: fix bug in interrupt lowering
The alignment wasn't set, so defaulted to 4 (for a 32-bit int). LLVM saw
this, and therefore assumed that a ptrtoint of the pointer would have
had the lowest bits unset. That's an entirely valid optimization, except
that we are using these globals for arbitrary values (and aren't
actually using these globals).

Fixed by setting alignment to 1. It works, though long-term we should
maybe find a different solution for this.
2024-11-01 09:10:25 +01:00
Ayke van Laethem 4b706ae25c test: show output even when a test binary didn't exit cleanly
This was a problem on wasm, where node would exit with a non-zero exit
code when there was a panic.
2024-11-01 08:49:00 +01:00
Ayke van Laethem 1ac26d3d2f test: run TestWasmExportJS tests in parallel 2024-11-01 08:48:19 +01:00
231 changed files with 6183 additions and 2355 deletions
+2
View File
@@ -90,6 +90,8 @@ commands:
name: Check Go code formatting
command: make fmt-check lint
- run: make gen-device -j4
# TODO: change this to -skip='TestErrors|TestWasm' with Go 1.20
- run: go test -tags=llvm<<parameters.llvm>> -short -run='TestBuild|TestTest|TestGetList|TestTraceback'
- run: make smoketest XTENSA=0
- save_cache:
key: go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
+6 -3
View File
@@ -33,6 +33,9 @@ jobs:
uses: actions/checkout@v4
with:
submodules: true
- name: Extract TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install Go
uses: actions/setup-go@v5
with:
@@ -104,7 +107,7 @@ jobs:
- name: Test stdlib packages
run: make tinygo-test
- name: Make release artifact
run: cp -p build/release.tar.gz build/tinygo.darwin-${{ matrix.goarch }}.tar.gz
run: cp -p build/release.tar.gz build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
- name: Publish release artifact
# Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39
@@ -114,8 +117,8 @@ jobs:
# We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v4
with:
name: darwin-${{ matrix.goarch }}-double-zipped
path: build/tinygo.darwin-${{ matrix.goarch }}.tar.gz
name: darwin-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }}
path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
- name: Smoke tests
run: make smoketest TINYGO=$(PWD)/build/tinygo
test-macos-homebrew:
+23 -15
View File
@@ -19,6 +19,8 @@ jobs:
runs-on: ubuntu-latest
container:
image: golang:1.23-alpine
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Install apk dependencies
# tar: needed for actions/cache@v4
@@ -32,6 +34,9 @@ jobs:
uses: actions/checkout@v4
with:
submodules: true
- name: Extract TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Cache Go
uses: actions/cache@v4
with:
@@ -120,15 +125,15 @@ jobs:
- name: Build TinyGo release
run: |
make release deb -j3 STATIC=1
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_amd64.deb
cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
- name: Publish release artifact
uses: actions/upload-artifact@v4
with:
name: linux-amd64-double-zipped
name: linux-amd64-double-zipped-${{ steps.version.outputs.version }}
path: |
/tmp/tinygo.linux-amd64.tar.gz
/tmp/tinygo_amd64.deb
/tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
/tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
test-linux-build:
# Test the binaries built in the build-linux job by running the smoke tests.
runs-on: ubuntu-latest
@@ -152,11 +157,11 @@ jobs:
- name: Download release artifact
uses: actions/download-artifact@v4
with:
name: linux-amd64-double-zipped
name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }}
- name: Extract release tarball
run: |
mkdir -p ~/lib
tar -C ~/lib -xf tinygo.linux-amd64.tar.gz
tar -C ~/lib -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
- run: make tinygo-test-wasip1-fast
- run: make tinygo-test-wasip2-fast
@@ -164,7 +169,7 @@ jobs:
assert-test-linux:
# Run all tests that can run on Linux, with LLVM assertions enabled to catch
# potential bugs.
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -297,6 +302,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install apt dependencies
run: |
sudo apt-get update
@@ -381,11 +389,11 @@ jobs:
- name: Download amd64 release
uses: actions/download-artifact@v4
with:
name: linux-amd64-double-zipped
name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }}
- name: Extract amd64 release
run: |
mkdir -p build/release
tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo
tar -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz -C build/release tinygo
- name: Modify release
run: |
cp -p build/tinygo build/release/tinygo/bin
@@ -393,12 +401,12 @@ jobs:
- name: Create ${{ matrix.goarch }} release
run: |
make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }}
cp -p build/release.tar.gz /tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ matrix.libc }}.deb
cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb
- name: Publish release artifact
uses: actions/upload-artifact@v4
with:
name: linux-${{ matrix.goarch }}-double-zipped
name: linux-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }}
path: |
/tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz
/tmp/tinygo_${{ matrix.libc }}.deb
/tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
/tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
# Extract the version string from the source code, to be stored in a variable.
grep 'const version' goenv/version.go | sed 's/^const version = "\(.*\)"$/version=\1/g'
+22 -9
View File
@@ -14,6 +14,8 @@ concurrency:
jobs:
build-windows:
runs-on: windows-2022
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
@@ -32,6 +34,10 @@ jobs:
uses: actions/checkout@v4
with:
submodules: true
- name: Extract TinyGo version
id: version
shell: bash
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install Go
uses: actions/setup-go@v5
with:
@@ -94,6 +100,13 @@ jobs:
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- name: Cache Go cache
uses: actions/cache@v4
with:
key: go-cache-windows-v1-${{ hashFiles('go.mod') }}
path: |
C:/Users/runneradmin/AppData/Local/go-build
C:/Users/runneradmin/go/pkg/mod
- name: Install wasmtime
run: |
scoop install wasmtime@14.0.4
@@ -108,7 +121,7 @@ jobs:
- name: Make release artifact
shell: bash
working-directory: build/release
run: 7z -tzip a release.zip tinygo
run: 7z -tzip a tinygo${{ steps.version.outputs.version }}.windows-amd64.zip tinygo
- name: Publish release artifact
# Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39
@@ -118,8 +131,8 @@ jobs:
# We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v4
with:
name: windows-amd64-double-zipped
path: build/release/release.zip
name: windows-amd64-double-zipped-${{ steps.version.outputs.version }}
path: build/release/tinygo${{ steps.version.outputs.version }}.windows-amd64.zip
smoke-test-windows:
runs-on: windows-2022
@@ -148,12 +161,12 @@ jobs:
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
run: 7z x tinygo*.windows-amd64.zip -r
- name: Smoke tests
shell: bash
run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo
@@ -178,12 +191,12 @@ jobs:
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
run: 7z x tinygo*.windows-amd64.zip -r
- name: Test stdlib packages
run: make tinygo-test TINYGO=$(PWD)/build/tinygo/bin/tinygo
@@ -214,11 +227,11 @@ jobs:
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
run: 7z x tinygo*.windows-amd64.zip -r
- name: Test stdlib packages on wasip1
run: make tinygo-test-wasip1-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo
+4
View File
@@ -37,5 +37,9 @@ test.exe
test.gba
test.hex
test.nro
test.uf2
test.wasm
wasm.wasm
*.uf2
*.elf
+68
View File
@@ -1,3 +1,71 @@
0.35.0
---
* **general**
- update cmsis-svd library
- use default UART settings in the echo example
- `goenv`: also show git hash with custom build of TinyGo
- `goenv`: support parsing development versions of Go
- `main`: parse extldflags early so we can report the error message
* **compiler**
- `builder`: whitelist temporary directory env var for Clang invocation to fix Windows bug
- `builder`: fix cache paths in `-size=full` output
- `builder`: work around incorrectly escaped DWARF paths on Windows (Clang bug)
- `builder`: fix wasi-libc path names on Windows with `-size=full`
- `builder`: write HTML size report
- `cgo`: support C identifiers only referred to from within macros
- `cgo`: support function-like macros
- `cgo`: support errno value as second return parameter
- `cgo`: add support for `#cgo noescape` lines
- `compiler`: fix bug in interrupt lowering
- `compiler`: allow panic directly in `defer`
- `compiler`: fix wasmimport -> wasmexport in error message
- `compiler`: support `//go:noescape` pragma
- `compiler`: report error instead of crashing when instantiating a generic function without body
- `interp`: align created globals
* **standard library**
- `machine`: modify i2s interface/implementation to better match specification
- `os`: implement `StartProcess`
- `reflect`: add `Value.Clear`
- `reflect`: add interface support to `NumMethods`
- `reflect`: fix `AssignableTo` for named + non-named types
- `reflect`: implement `CanConvert`
- `reflect`: handle more cases in `Convert`
- `reflect`: fix Copy of non-pointer array with size > 64bits
- `runtime`: don't call sleepTicks with a negative duration
- `runtime`: optimize GC scanning (findHead)
- `runtime`: move constants into shared package
- `runtime`: add `runtime.fcntl` function for internal/syscall/unix
- `runtime`: heapptr only needs to be initialized once
- `runtime`: refactor scheduler (this fixes a few bugs with `-scheduler=none`)
- `runtime`: rewrite channel implementation to be smaller and more flexible
- `runtime`: use `SA_RESTART` when registering a signal for os/signal
- `runtime`: implement race-free signals using futexes
- `runtime`: run deferred functions in `Goexit`
- `runtime`: remove `Cond` which seems to be unused
- `runtime`: properly handle unix read on directory
- `runtime/trace`: stub all public methods
- `sync`: don't use volatile in `Mutex`
- `sync`: implement `WaitGroup` using a (pseudo)futex
- `sync`: make `Cond` parallelism-safe
- `syscall`: use wasi-libc tables for wasm/js target
* **targets**
- `mips`: fix a bug when scanning the stack
- `nintendoswitch`: get this target to compile again
- `rp2350`: add support for the new RP2350
- `rp2040/rp2350` : make I2C implementation shared for rp2040/rp2350
- `rp2040/rp2350` : make SPI implementation shared for rp2040/rp2350
- `rp2040/rp2350` : make RNG implementation shared for rp2040/rp2350
- `wasm`: revise and simplify wasmtime argument handling
- `wasm`: support `//go:wasmexport` functions after a call to `time.Sleep`
- `wasm`: correctly return from run() in wasm_exec.js
- `wasm`: call process.exit() when go.run() returns
- `windows`: don't return, exit via exit(0) instead to flush stdout buffer
* **boards**
- add support for the Tillitis TKey
- add support for the Raspberry Pi Pico2 (based on the RP2040)
- add support for Pimoroni Tiny2350
0.34.0
---
* **general**
+11 -2
View File
@@ -291,7 +291,7 @@ endif
tinygo: ## Build the TinyGo compiler
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" .
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" .
test: wasi-libc check-nodejs-version
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS)
@@ -741,6 +741,10 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=thumby examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico2 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=tiny2350 examples/blinky1
@$(MD5SUM) test.hex
# test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex
@@ -853,6 +857,8 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=tkey examples/blinky1
@$(MD5SUM) test.hex
ifneq ($(WASM), 0)
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main
@@ -867,7 +873,7 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=rtt examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
$(TINYGO) build -o test.nro -target=nintendoswitch examples/echo2
@$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex
@@ -926,6 +932,7 @@ endif
@cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/fcntl build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/legacy build/release/tinygo/lib/musl/src
@@ -934,6 +941,7 @@ endif
@cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src
@@ -941,6 +949,7 @@ endif
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/process build/release/tinygo/lib/musl/src
@cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
+54 -10
View File
@@ -61,6 +61,10 @@ type BuildResult struct {
// correctly printing test results: the import path isn't always the same as
// the path listed on the command line.
ImportPath string
// Map from path to package name. It is needed to attribute binary size to
// the right Go package.
PackagePathMap map[string]string
}
// packageAction is the struct that is serialized to JSON and hashed, to work as
@@ -242,6 +246,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return result, err
}
// Store which filesystem paths map to which package name.
result.PackagePathMap = make(map[string]string, len(lprogram.Packages))
for _, pkg := range lprogram.Sorted() {
result.PackagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
}
// 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()
@@ -690,7 +700,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
for _, pkg := range lprogram.Sorted() {
pkg := pkg
for _, filename := range pkg.CFiles {
abspath := filepath.Join(pkg.Dir, filename)
abspath := filepath.Join(pkg.OriginalDir(), filename)
job := &compileJob{
description: "compile CGo file " + abspath,
run: func(job *compileJob) error {
@@ -812,6 +822,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return fmt.Errorf("could not modify stack sizes: %w", err)
}
}
// Apply patches of bootloader in the order they appear.
if len(config.Target.BootPatches) > 0 {
err = applyPatches(result.Executable, config.Target.BootPatches)
}
if config.RP2040BootPatch() {
// Patch the second stage bootloader CRC into the .boot2 section
err = patchRP2040BootCRC(result.Executable)
@@ -915,19 +931,16 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
// Print code size if requested.
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
packagePathMap := make(map[string]string, len(lprogram.Packages))
for _, pkg := range lprogram.Sorted() {
packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
}
sizes, err := loadProgramSize(result.Executable, packagePathMap)
if config.Options.PrintSizes != "" {
sizes, err := loadProgramSize(result.Executable, result.PackagePathMap)
if err != nil {
return err
}
if config.Options.PrintSizes == "short" {
switch config.Options.PrintSizes {
case "short":
fmt.Printf(" code data bss | flash ram\n")
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code+sizes.ROData, sizes.Data, sizes.BSS, sizes.Flash(), sizes.RAM())
} else {
case "full":
if !config.Debug() {
fmt.Println("warning: data incomplete, remove the -no-debug flag for more detail")
}
@@ -939,6 +952,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
fmt.Printf("------------------------------- | --------------- | -------\n")
fmt.Printf("%7d %7d %7d %7d | %7d %7d | total\n", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS, sizes.Code+sizes.ROData+sizes.Data, sizes.Data+sizes.BSS)
case "html":
const filename = "size-report.html"
err := writeSizeReport(sizes, filename, pkgName)
if err != nil {
return err
}
fmt.Println("Wrote size report to", filename)
}
}
@@ -1428,6 +1448,23 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS
}
}
func applyPatches(executable string, bootPatches []string) (err error) {
for _, patch := range bootPatches {
switch patch {
case "rp2040":
err = patchRP2040BootCRC(executable)
// case "rp2350":
// err = patchRP2350BootIMAGE_DEF(executable)
default:
err = errors.New("undefined boot patch name")
}
if err != nil {
return fmt.Errorf("apply boot patch %q: %w", patch, err)
}
}
return nil
}
// RP2040 second stage bootloader CRC32 calculation
//
// Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf
@@ -1439,7 +1476,7 @@ func patchRP2040BootCRC(executable string) error {
}
if len(bytes) != 256 {
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes")
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes, got %d", len(bytes))
}
// From the 'official' RP2040 checksum script:
@@ -1478,3 +1515,10 @@ func lock(path string) func() {
return func() { flock.Close() }
}
func b2u8(b bool) uint8 {
if b {
return 1
}
return 0
}
+1
View File
@@ -33,6 +33,7 @@ func TestClangAttributes(t *testing.T) {
"k210",
"nintendoswitch",
"riscv-qemu",
"tkey",
"wasip1",
"wasip2",
"wasm",
+10
View File
@@ -116,6 +116,7 @@ var libMusl = Library{
"env/*.c",
"errno/*.c",
"exit/*.c",
"fcntl/*.c",
"internal/defsysinfo.c",
"internal/libc.c",
"internal/syscall_ret.c",
@@ -127,6 +128,7 @@ var libMusl = Library{
"malloc/mallocng/*.c",
"mman/*.c",
"math/*.c",
"misc/*.c",
"multibyte/*.c",
"signal/" + arch + "/*.s",
"signal/*.c",
@@ -136,12 +138,20 @@ var libMusl = Library{
"thread/*.c",
"time/*.c",
"unistd/*.c",
"process/*.c",
}
if arch == "arm" {
// These files need to be added to the start for some reason.
globs = append([]string{"thread/arm/*.c"}, globs...)
}
if arch != "aarch64" && arch != "mips" {
//aarch64 and mips have no architecture specific code, either they
// are not supported or don't need any?
globs = append([]string{"process/" + arch + "/*.s"}, globs...)
}
var sources []string
seenSources := map[string]struct{}{}
basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
+1
View File
@@ -34,6 +34,7 @@ var libPicolibc = Library{
"-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU
"-D__OBSOLETE_MATH_DOUBLE=0",
"-D_WANT_IO_C99_FORMATS",
"-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
"-nostdlibinc",
"-isystem", newlibDir + "/libc/include",
"-I" + newlibDir + "/libc/tinystdio",
+56
View File
@@ -0,0 +1,56 @@
package builder
import (
_ "embed"
"fmt"
"html/template"
"os"
)
//go:embed size-report.html
var sizeReportBase string
func writeSizeReport(sizes *programSize, filename, pkgName string) error {
tmpl, err := template.New("report").Parse(sizeReportBase)
if err != nil {
return err
}
f, err := os.Create(filename)
if err != nil {
return fmt.Errorf("could not open report file: %w", err)
}
defer f.Close()
// Prepare data for the report.
type sizeLine struct {
Name string
Size *packageSize
}
programData := []sizeLine{}
for _, name := range sizes.sortedPackageNames() {
pkgSize := sizes.Packages[name]
programData = append(programData, sizeLine{
Name: name,
Size: pkgSize,
})
}
sizeTotal := map[string]uint64{
"code": sizes.Code,
"rodata": sizes.ROData,
"data": sizes.Data,
"bss": sizes.BSS,
"flash": sizes.Flash(),
}
// Write the report.
err = tmpl.Execute(f, map[string]any{
"pkgName": pkgName,
"sizes": programData,
"sizeTotal": sizeTotal,
})
if err != nil {
return fmt.Errorf("could not create report file: %w", err)
}
return nil
}
+109
View File
@@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Size Report for {{.pkgName}}</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<style>
.table-vertical-border {
border-left: calc(var(--bs-border-width) * 2) solid currentcolor;
}
/* Hover on only the rows that are clickable. */
.row-package:hover > * {
--bs-table-color-state: var(--bs-table-hover-color);
--bs-table-bg-state: var(--bs-table-hover-bg);
}
</style>
</head>
<body>
<div class="container-xxl">
<h1>Size Report for {{.pkgName}}</h1>
<p>How much space is used by Go packages, C libraries, and other bits to set up the program environment.</p>
<ul>
<li><strong>Code</strong> is the actual program code (machine code instructions).</li>
<li><strong>Read-only data</strong> are read-only global variables. On most microcontrollers, these are stored in flash and do not take up any RAM.</li>
<li><strong>Data</strong> are writable global variables with a non-zero initializer. On microcontrollers, they are copied from flash to RAM on reset.</li>
<li><strong>BSS</strong> are writable global variables that are zero initialized. They do not take up any space in the binary, but do take up RAM. On microcontrollers, this area is zeroed on reset.</li>
</ul>
<p>The binary size consists of code, read-only data, and data. On microcontrollers, this is exactly the size of the firmware image. On other systems, there is some extra overhead: binary metadata (headers of the ELF/MachO/COFF file), debug information, exception tables, symbol names, etc. Using <code>-no-debug</code> strips most of those.</p>
<h2>Program breakdown</h2>
<p>You can click on the rows below to see which files contribute to the binary size.</p>
<div class="table-responsive">
<table class="table w-auto">
<thead>
<tr>
<th>Package</th>
<th class="table-vertical-border">Code</th>
<th>Read-only data</th>
<th>Data</th>
<th title="zero-initialized data">BSS</th>
<th class="table-vertical-border" style="min-width: 16em">Binary size</th>
</tr>
</thead>
<tbody class="table-group-divider">
{{range $i, $pkg := .sizes}}
<tr class="row-package" data-collapse=".collapse-row-{{$i}}">
<td>{{.Name}}</td>
<td class="table-vertical-border">{{.Size.Code}}</td>
<td>{{.Size.ROData}}</td>
<td>{{.Size.Data}}</td>
<td>{{.Size.BSS}}</td>
<td class="table-vertical-border" style="background: linear-gradient(to right, var(--bs-info-bg-subtle) {{.Size.FlashPercent}}%, var(--bs-table-bg) {{.Size.FlashPercent}}%)">
{{.Size.Flash}}
</td>
</tr>
{{range $filename, $sizes := .Size.Sub}}
<tr class="table-secondary collapse collapse-row-{{$i}}">
<td class="ps-4">
{{if eq $filename ""}}
(unknown file)
{{else}}
{{$filename}}
{{end}}
</td>
<td class="table-vertical-border">{{$sizes.Code}}</td>
<td>{{$sizes.ROData}}</td>
<td>{{$sizes.Data}}</td>
<td>{{$sizes.BSS}}</td>
<td class="table-vertical-border" style="background: linear-gradient(to right, var(--bs-info-bg-subtle) {{$sizes.FlashPercent}}%, var(--bs-table-bg) {{$sizes.FlashPercent}}%)">
{{$sizes.Flash}}
</td>
</tr>
{{end}}
{{end}}
</tbody>
<tfoot class="table-group-divider">
<tr>
<th>Total</th>
<td class="table-vertical-border">{{.sizeTotal.code}}</td>
<td>{{.sizeTotal.rodata}}</td>
<td>{{.sizeTotal.data}}</td>
<td>{{.sizeTotal.bss}}</td>
<td class="table-vertical-border">{{.sizeTotal.flash}}</td>
</tr>
</tfoot>
</table>
</div>
</div>
<script>
// Make table rows toggleable to show filenames.
for (let clickable of document.querySelectorAll('.row-package')) {
clickable.addEventListener('click', e => {
for (let row of document.querySelectorAll(clickable.dataset.collapse)) {
row.classList.toggle('show');
}
});
}
</script>
</body>
</html>
+102 -47
View File
@@ -12,6 +12,7 @@ import (
"os"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
@@ -24,7 +25,7 @@ const sizesDebug = false
// programSize contains size statistics per package of a compiled program.
type programSize struct {
Packages map[string]packageSize
Packages map[string]*packageSize
Code uint64
ROData uint64
Data uint64
@@ -52,13 +53,29 @@ func (ps *programSize) RAM() uint64 {
return ps.Data + ps.BSS
}
// Return the package size information for a given package path, creating it if
// it doesn't exist yet.
func (ps *programSize) getPackage(path string) *packageSize {
if field, ok := ps.Packages[path]; ok {
return field
}
field := &packageSize{
Program: ps,
Sub: map[string]*packageSize{},
}
ps.Packages[path] = field
return field
}
// packageSize contains the size of a package, calculated from the linked object
// file.
type packageSize struct {
Code uint64
ROData uint64
Data uint64
BSS uint64
Program *programSize
Code uint64
ROData uint64
Data uint64
BSS uint64
Sub map[string]*packageSize
}
// Flash usage in regular microcontrollers.
@@ -71,6 +88,31 @@ func (ps *packageSize) RAM() uint64 {
return ps.Data + ps.BSS
}
// Flash usage in regular microcontrollers, as a percentage of the total flash
// usage of the program.
func (ps *packageSize) FlashPercent() float64 {
return float64(ps.Flash()) / float64(ps.Program.Flash()) * 100
}
// Add a single size data point to this package.
// This must only be called while calculating package size, not afterwards.
func (ps *packageSize) addSize(getField func(*packageSize, bool) *uint64, filename string, size uint64, isVariable bool) {
if size == 0 {
return
}
// Add size for the package.
*getField(ps, isVariable) += size
// Add size for file inside package.
sub, ok := ps.Sub[filename]
if !ok {
sub = &packageSize{Program: ps.Program}
ps.Sub[filename] = sub
}
*getField(sub, isVariable) += size
}
// A mapping of a single chunk of code or data to a file path.
type addressLine struct {
Address uint64
@@ -194,11 +236,22 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
if !prevLineEntry.EndSequence {
// The chunk describes the code from prevLineEntry to
// lineEntry.
path := prevLineEntry.File.Name
if runtime.GOOS == "windows" {
// Work around a Clang bug on Windows:
// https://github.com/llvm/llvm-project/issues/117317
path = strings.ReplaceAll(path, "\\\\", "\\")
// wasi-libc likes to use forward slashes, but we
// canonicalize everything to use backwards slashes as
// is common on Windows.
path = strings.ReplaceAll(path, "/", "\\")
}
line := addressLine{
Address: prevLineEntry.Address + codeOffset,
Length: lineEntry.Address - prevLineEntry.Address,
Align: codeAlignment,
File: prevLineEntry.File.Name,
File: path,
}
if line.Length != 0 {
addresses = append(addresses, line)
@@ -773,49 +826,40 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
// Now finally determine the binary/RAM size usage per package by going
// through each allocated section.
sizes := make(map[string]packageSize)
sizes := make(map[string]*packageSize)
program := &programSize{
Packages: sizes,
}
for _, section := range sections {
switch section.Type {
case memoryCode:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
if isVariable {
field.ROData += size
} else {
field.Code += size
return &ps.ROData
}
sizes[path] = field
return &ps.Code
}, packagePathMap)
case memoryROData:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.ROData += size
sizes[path] = field
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
return &ps.ROData
}, packagePathMap)
case memoryData:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.Data += size
sizes[path] = field
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
return &ps.Data
}, packagePathMap)
case memoryBSS:
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
field.BSS += size
sizes[path] = field
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
return &ps.BSS
}, packagePathMap)
case memoryStack:
// We store the C stack as a pseudo-package.
sizes["C stack"] = packageSize{
BSS: section.Size,
}
program.getPackage("C stack").addSize(func(ps *packageSize, isVariable bool) *uint64 {
return &ps.BSS
}, "", section.Size, false)
}
}
// ...and summarize the results.
program := &programSize{
Packages: sizes,
}
for _, pkg := range sizes {
program.Code += pkg.Code
program.ROData += pkg.ROData
@@ -826,8 +870,8 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
}
// readSection determines for each byte in this section to which package it
// belongs. It reports this usage through the addSize callback.
func readSection(section memorySection, addresses []addressLine, addSize func(string, uint64, bool), packagePathMap map[string]string) {
// belongs.
func readSection(section memorySection, addresses []addressLine, program *programSize, getField func(*packageSize, bool) *uint64, packagePathMap map[string]string) {
// The addr variable tracks at which address we are while going through this
// section. We start at the beginning.
addr := section.Address
@@ -849,9 +893,9 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
addrAligned := (addr + line.Align - 1) &^ (line.Align - 1)
if line.Align > 1 && addrAligned >= line.Address {
// It is, assume that's what causes the gap.
addSize("(padding)", line.Address-addr, true)
program.getPackage("(padding)").addSize(getField, "", line.Address-addr, true)
} else {
addSize("(unknown)", line.Address-addr, false)
program.getPackage("(unknown)").addSize(getField, "", line.Address-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %5d: unknown (gap), alignment=%d\n", addr, line.Address, line.Address-addr, line.Align)
}
@@ -873,7 +917,8 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
length = line.Length - (addr - line.Address)
}
// Finally, mark this chunk of memory as used by the given package.
addSize(findPackagePath(line.File, packagePathMap), length, line.IsVariable)
packagePath, filename := findPackagePath(line.File, packagePathMap)
program.getPackage(packagePath).addSize(getField, filename, length, line.IsVariable)
addr = line.Address + line.Length
}
if addr < sectionEnd {
@@ -882,9 +927,9 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
if section.Align > 1 && addrAligned >= sectionEnd {
// The gap is caused by the section alignment.
// For example, if a .rodata section ends with a non-aligned string.
addSize("(padding)", sectionEnd-addr, true)
program.getPackage("(padding)").addSize(getField, "", sectionEnd-addr, true)
} else {
addSize("(unknown)", sectionEnd-addr, false)
program.getPackage("(unknown)").addSize(getField, "", sectionEnd-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %5d: unknown (end), alignment=%d\n", addr, sectionEnd, sectionEnd-addr, section.Align)
}
@@ -894,17 +939,25 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
// findPackagePath returns the Go package (or a pseudo package) for the given
// path. It uses some heuristics, for example for some C libraries.
func findPackagePath(path string, packagePathMap map[string]string) string {
func findPackagePath(path string, packagePathMap map[string]string) (packagePath, filename string) {
// Check whether this path is part of one of the compiled packages.
packagePath, ok := packagePathMap[filepath.Dir(path)]
if !ok {
if ok {
// Directory is known as a Go package.
// Add the file itself as well.
filename = filepath.Base(path)
} else {
if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) {
// Emit C libraries (in the lib subdirectory of TinyGo) as a single
// package, with a "C" prefix. For example: "C compiler-rt" for the
// compiler runtime library from LLVM.
packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1]
} else if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project")) {
// package, with a "C" prefix. For example: "C picolibc" for the
// baremetal libc.
libPath := strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")+string(os.PathSeparator))
parts := strings.SplitN(libPath, string(os.PathSeparator), 2)
packagePath = "C " + parts[0]
filename = parts[1]
} else if prefix := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project", "compiler-rt"); strings.HasPrefix(path, prefix) {
packagePath = "C compiler-rt"
filename = strings.TrimPrefix(path, prefix+string(os.PathSeparator))
} else if packageSymbolRegexp.MatchString(path) {
// Parse symbol names like main$alloc or runtime$string.
packagePath = path[:strings.LastIndex(path, "$")]
@@ -927,9 +980,11 @@ func findPackagePath(path string, packagePathMap map[string]string) string {
// fixed in the compiler.
packagePath = "-"
} else {
// This is some other path. Not sure what it is, so just emit its directory.
packagePath = filepath.Dir(path) // fallback
// This is some other path. Not sure what it is, so just emit its
// directory as a fallback.
packagePath = filepath.Dir(path)
filename = filepath.Base(path)
}
}
return packagePath
return
}
+71 -23
View File
@@ -1,6 +1,7 @@
package builder
import (
"regexp"
"runtime"
"testing"
"time"
@@ -41,9 +42,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 4568, 280, 0, 2268},
{"microbit", "examples/serial", 2868, 388, 8, 2272},
{"wioterminal", "examples/pininterrupt", 6104, 1484, 116, 6832},
{"hifive1b", "examples/echo", 4600, 280, 0, 2268},
{"microbit", "examples/serial", 2908, 388, 8, 2272},
{"wioterminal", "examples/pininterrupt", 6140, 1484, 116, 6824},
// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
@@ -55,26 +56,7 @@ func TestBinarySize(t *testing.T) {
t.Parallel()
// Build the binary.
options := compileopts.Options{
Target: tc.target,
Opt: "z",
Semaphore: sema,
InterpTimeout: 60 * time.Second,
Debug: true,
VerifyIR: true,
}
target, err := compileopts.LoadTarget(&options)
if err != nil {
t.Fatal("could not load target:", err)
}
config := &compileopts.Config{
Options: &options,
Target: target,
}
result, err := Build(tc.path, "", t.TempDir(), config)
if err != nil {
t.Fatal("could not build:", err)
}
result := buildBinary(t, tc.target, tc.path)
// Check whether the size of the binary matches the expected size.
sizes, err := loadProgramSize(result.Executable, nil)
@@ -90,3 +72,69 @@ func TestBinarySize(t *testing.T) {
})
}
}
// Check that the -size=full flag attributes binary size to the correct package
// without filesystem paths and things like that.
func TestSizeFull(t *testing.T) {
tests := []string{
"microbit",
"wasip1",
}
libMatch := regexp.MustCompile(`^C [a-z -]+$`) // example: "C interrupt vector"
pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task"
for _, target := range tests {
target := target
t.Run(target, func(t *testing.T) {
t.Parallel()
// Build the binary.
result := buildBinary(t, target, "examples/serial")
// Check whether the binary doesn't contain any unexpected package
// names.
sizes, err := loadProgramSize(result.Executable, result.PackagePathMap)
if err != nil {
t.Fatal("could not read program size:", err)
}
for _, pkg := range sizes.sortedPackageNames() {
if pkg == "(padding)" || pkg == "(unknown)" {
// TODO: correctly attribute all unknown binary size.
continue
}
if libMatch.MatchString(pkg) {
continue
}
if pkgMatch.MatchString(pkg) {
continue
}
t.Error("unexpected package name in size output:", pkg)
}
})
}
}
func buildBinary(t *testing.T, targetString, pkgName string) BuildResult {
options := compileopts.Options{
Target: targetString,
Opt: "z",
Semaphore: sema,
InterpTimeout: 60 * time.Second,
Debug: true,
VerifyIR: true,
}
target, err := compileopts.LoadTarget(&options)
if err != nil {
t.Fatal("could not load target:", err)
}
config := &compileopts.Config{
Options: &options,
Target: target,
}
result, err := Build(pkgName, "", t.TempDir(), config)
if err != nil {
t.Fatal("could not build:", err)
}
return result
}
+18 -2
View File
@@ -32,10 +32,26 @@ func runCCompiler(flags ...string) error {
cmd.Stderr = os.Stderr
// Make sure the command doesn't use any environmental variables.
// Most importantly, it should not use C_INCLUDE_PATH and the like. But
// removing all environmental variables also works.
// Most importantly, it should not use C_INCLUDE_PATH and the like.
cmd.Env = []string{}
// Let some environment variables through. One important one is the
// temporary directory, especially on Windows it looks like Clang breaks if
// the temporary directory has not been set.
// See: https://github.com/tinygo-org/tinygo/issues/4557
// Also see: https://github.com/llvm/llvm-project/blob/release/18.x/llvm/lib/Support/Unix/Path.inc#L1435
for _, env := range os.Environ() {
// We could parse the key and look it up in a map, but since there are
// only a few keys iterating through them is easier and maybe even
// faster.
for _, prefix := range []string{"TMPDIR=", "TMP=", "TEMP=", "TEMPDIR="} {
if strings.HasPrefix(env, prefix) {
cmd.Env = append(cmd.Env, env)
break
}
}
}
return cmd.Run()
}
+172 -6
View File
@@ -18,6 +18,7 @@ import (
"go/scanner"
"go/token"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -42,6 +43,7 @@ type cgoPackage struct {
fset *token.FileSet
tokenFiles map[string]*token.File
definedGlobally map[string]ast.Node
noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines
anonDecls map[interface{}]string
cflags []string // CFlags from #cgo lines
ldflags []string // LDFlags from #cgo lines
@@ -80,6 +82,13 @@ type bitfieldInfo struct {
endBit int64 // may be 0 meaning "until the end of the field"
}
// Information about a #cgo noescape line in the source code.
type noescapingFunc struct {
name string
pos token.Pos
used bool // true if used somewhere in the source (for proper error reporting)
}
// cgoAliases list type aliases between Go and C, for types that are equivalent
// in both languages. See addTypeAliases.
var cgoAliases = map[string]string{
@@ -138,7 +147,8 @@ typedef unsigned long long _Cgo_ulonglong;
// first.
// These functions will be modified to get a "C." prefix, so the source below
// doesn't reflect the final AST.
const generatedGoFilePrefix = `
const generatedGoFilePrefixBase = `
import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -169,6 +179,74 @@ func __CBytes([]byte) unsafe.Pointer
func CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
//go:linkname C.__get_errno_num runtime.cgo_errno
func __get_errno_num() uintptr
`
const generatedGoFilePrefixOther = generatedGoFilePrefixBase + `
func __get_errno() error {
return syscall.Errno(C.__get_errno_num())
}
`
// Windows uses fake errno values in the syscall package.
// See for example: https://github.com/golang/go/issues/23468
// TinyGo uses mingw-w64 though, which does have defined errno values. Since the
// syscall package is the standard library one we can't change it, but we can
// map the errno values to match the values in the syscall package.
// Source of the errno values: lib/mingw-w64/mingw-w64-headers/crt/errno.h
const generatedGoFilePrefixWindows = generatedGoFilePrefixBase + `
var __errno_mapping = [...]syscall.Errno{
1: syscall.EPERM,
2: syscall.ENOENT,
3: syscall.ESRCH,
4: syscall.EINTR,
5: syscall.EIO,
6: syscall.ENXIO,
7: syscall.E2BIG,
8: syscall.ENOEXEC,
9: syscall.EBADF,
10: syscall.ECHILD,
11: syscall.EAGAIN,
12: syscall.ENOMEM,
13: syscall.EACCES,
14: syscall.EFAULT,
16: syscall.EBUSY,
17: syscall.EEXIST,
18: syscall.EXDEV,
19: syscall.ENODEV,
20: syscall.ENOTDIR,
21: syscall.EISDIR,
22: syscall.EINVAL,
23: syscall.ENFILE,
24: syscall.EMFILE,
25: syscall.ENOTTY,
27: syscall.EFBIG,
28: syscall.ENOSPC,
29: syscall.ESPIPE,
30: syscall.EROFS,
31: syscall.EMLINK,
32: syscall.EPIPE,
33: syscall.EDOM,
34: syscall.ERANGE,
36: syscall.EDEADLK,
38: syscall.ENAMETOOLONG,
39: syscall.ENOLCK,
40: syscall.ENOSYS,
41: syscall.ENOTEMPTY,
42: syscall.EILSEQ,
}
func __get_errno() error {
num := C.__get_errno_num()
if num < uintptr(len(__errno_mapping)) {
if mapped := __errno_mapping[num]; mapped != 0 {
return mapped
}
}
return syscall.Errno(num)
}
`
// Process extracts `import "C"` statements from the AST, parses the comment
@@ -178,7 +256,7 @@ func CBytes(b []byte) unsafe.Pointer {
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
// hashes of the accessed C header files. If there is one or more error, it
// returns these in the []error slice but still modifies the AST.
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) {
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, goos string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{
packageName: files[0].Name.Name,
currentDir: dir,
@@ -186,6 +264,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
fset: fset,
tokenFiles: map[string]*token.File{},
definedGlobally: map[string]ast.Node{},
noescapingFuncs: map[string]*noescapingFunc{},
anonDecls: map[interface{}]string{},
visitedFiles: map[string][]byte{},
}
@@ -210,7 +289,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Construct a new in-memory AST for CGo declarations of this package.
// The first part is written as Go code that is then parsed, but more code
// is added later to the AST to declare functions, globals, etc.
goCode := "package " + files[0].Name.Name + "\n\n" + generatedGoFilePrefix
goCode := "package " + files[0].Name.Name + "\n\n"
if goos == "windows" {
goCode += generatedGoFilePrefixWindows
} else {
goCode += generatedGoFilePrefixOther
}
p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments)
if err != nil {
// This is always a bug in the cgo package.
@@ -225,7 +309,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
switch decl := decl.(type) {
case *ast.FuncDecl:
switch decl.Name.Name {
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes":
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes", "__get_errno_num", "__get_errno", "__errno_mapping":
// Adjust the name to have a "C." prefix so it is correctly
// resolved.
decl.Name.Name = "C." + decl.Name.Name
@@ -344,6 +428,22 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
})
}
// Show an error when a #cgo noescape line isn't used in practice.
// This matches upstream Go. I think the goal is to avoid issues with
// misspelled function names, which seems very useful.
var unusedNoescapeLines []*noescapingFunc
for _, value := range p.noescapingFuncs {
if !value.used {
unusedNoescapeLines = append(unusedNoescapeLines, value)
}
}
sort.SliceStable(unusedNoescapeLines, func(i, j int) bool {
return unusedNoescapeLines[i].pos < unusedNoescapeLines[j].pos
})
for _, value := range unusedNoescapeLines {
p.addError(value.pos, fmt.Sprintf("function %#v in #cgo noescape line is not used", value.name))
}
// Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated)
@@ -409,6 +509,33 @@ func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) strin
}
text = text[:lineStart] + string(spaces) + text[lineEnd:]
allFields := strings.Fields(line[4:])
switch allFields[0] {
case "noescape":
// The code indicates that pointer parameters will not be captured
// by the called C function.
if len(allFields) < 2 {
p.addErrorAfter(pos, text[:lineStart], "missing function name in #cgo noescape line")
continue
}
if len(allFields) > 2 {
p.addErrorAfter(pos, text[:lineStart], "multiple function names in #cgo noescape line")
continue
}
name := allFields[1]
p.noescapingFuncs[name] = &noescapingFunc{
name: name,
pos: pos,
used: false,
}
continue
case "nocallback":
// We don't do anything special when calling a C function, so there
// appears to be no optimization that we can do here.
// Accept, but ignore the parameter for compatibility.
continue
}
// Get the text before the colon in the #cgo directive.
colon := strings.IndexByte(line, ':')
if colon < 0 {
@@ -1148,7 +1275,7 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st
if alias := cgoAliases["C."+name]; alias != "" {
return alias
}
node := f.getASTDeclNode(name, found, iscall)
node := f.getASTDeclNode(name, found)
if node, ok := node.(*ast.FuncDecl); ok {
if !iscall {
return node.Name.Name + "$funcaddr"
@@ -1160,7 +1287,7 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st
// getASTDeclNode will declare the given C AST node (if not already defined) and
// returns it.
func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) ast.Node {
func (f *cgoFile) getASTDeclNode(name string, found clangCursor) ast.Node {
if node, ok := f.defined[name]; ok {
// Declaration was found in the current file, so return it immediately.
return node
@@ -1279,6 +1406,45 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
// separate namespace (no _Cgo_ hacks like in gc).
func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool {
switch node := cursor.Node().(type) {
case *ast.AssignStmt:
// An assign statement could be something like this:
//
// val, errno := C.some_func()
//
// Check whether it looks like that, and if so, read the errno value and
// return it as the second return value. The call will be transformed
// into something like this:
//
// val, errno := C.some_func(), C.__get_errno()
if len(node.Lhs) != 2 || len(node.Rhs) != 1 {
return true
}
rhs, ok := node.Rhs[0].(*ast.CallExpr)
if !ok {
return true
}
fun, ok := rhs.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
x, ok := fun.X.(*ast.Ident)
if !ok {
return true
}
if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" {
// Replace "C"."some_func" into "C.somefunc".
rhs.Fun = &ast.Ident{
NamePos: x.NamePos,
Name: f.getASTDeclName(fun.Sel.Name, found, true),
}
// Add the errno value as the second value in the statement.
node.Rhs = append(node.Rhs, &ast.CallExpr{
Fun: &ast.Ident{
NamePos: node.Lhs[1].End(),
Name: "C.__get_errno",
},
})
}
case *ast.CallExpr:
fun, ok := node.Fun.(*ast.SelectorExpr)
if !ok {
+23 -4
View File
@@ -56,7 +56,7 @@ func TestCGo(t *testing.T) {
}
// Process the AST with CGo.
cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags)
cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "linux")
// Check the AST for type errors.
var typecheckErrors []error
@@ -64,7 +64,7 @@ func TestCGo(t *testing.T) {
Error: func(err error) {
typecheckErrors = append(typecheckErrors, err)
},
Importer: simpleImporter{},
Importer: newSimpleImporter(),
Sizes: types.SizesFor("gccgo", "arm"),
}
_, err = config.Check("", fset, append([]*ast.File{f}, cgoFiles...), nil)
@@ -202,14 +202,33 @@ func Test_cgoPackage_isEquivalentAST(t *testing.T) {
}
// simpleImporter implements the types.Importer interface, but only allows
// importing the unsafe package.
// importing the syscall and unsafe packages.
type simpleImporter struct {
syscallPkg *types.Package
}
func newSimpleImporter() *simpleImporter {
i := &simpleImporter{}
// Implement a dummy syscall package with the Errno type.
i.syscallPkg = types.NewPackage("syscall", "syscall")
obj := types.NewTypeName(token.NoPos, i.syscallPkg, "Errno", nil)
named := types.NewNamed(obj, nil, nil)
i.syscallPkg.Scope().Insert(obj)
named.SetUnderlying(types.Typ[types.Uintptr])
sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, i.syscallPkg, "", types.Typ[types.String])), false)
named.AddMethod(types.NewFunc(token.NoPos, i.syscallPkg, "Error", sig))
i.syscallPkg.MarkComplete()
return i
}
// Import implements the Importer interface. For testing usage only: it only
// supports importing the unsafe package.
func (i simpleImporter) Import(path string) (*types.Package, error) {
func (i *simpleImporter) Import(path string) (*types.Package, error) {
switch path {
case "syscall":
return i.syscallPkg, nil
case "unsafe":
return types.Unsafe, nil
default:
+136 -4
View File
@@ -54,8 +54,72 @@ func init() {
}
// 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)
func parseConst(pos token.Pos, fset *token.FileSet, value string, params []ast.Expr, callerPos token.Pos, f *cgoFile) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value, f)
// If params is non-nil (could be a zero length slice), this const is
// actually a function-call like expression from another macro.
// This means we have to parse a string like "(a, b) (a+b)".
// We do this by parsing the parameters at the start and then treating the
// following like a normal constant expression.
if params != nil {
// Parse opening paren.
if t.curToken != token.LPAREN {
return nil, unexpectedToken(t, token.LPAREN)
}
t.Next()
// Parse parameters (identifiers) and closing paren.
var paramIdents []string
for i := 0; ; i++ {
if i == 0 && t.curToken == token.RPAREN {
// No parameters, break early.
t.Next()
break
}
// Read the parameter name.
if t.curToken != token.IDENT {
return nil, unexpectedToken(t, token.IDENT)
}
paramIdents = append(paramIdents, t.curValue)
t.Next()
// Read the next token: either a continuation (comma) or end of list
// (rparen).
if t.curToken == token.RPAREN {
// End of parameter list.
t.Next()
break
} else if t.curToken == token.COMMA {
// Comma, so there will be another parameter name.
t.Next()
} else {
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + " inside macro parameters, expected ',' or ')'",
}
}
}
// Report an error if there is a mismatch in parameter length.
// The error is reported at the location of the closing paren from the
// caller location.
if len(params) != len(paramIdents) {
return nil, &scanner.Error{
Pos: t.fset.Position(callerPos),
Msg: fmt.Sprintf("unexpected number of parameters: expected %d, got %d", len(paramIdents), len(params)),
}
}
// Assign values to the parameters.
// These parameter names are closer in 'scope' than other identifiers so
// will be used first when parsing an identifier.
for i, name := range paramIdents {
t.params[name] = params[i]
}
}
expr, err := parseConstExpr(t, precedenceLowest)
t.Next()
if t.curToken != token.EOF {
@@ -96,6 +160,68 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
}
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
// If the identifier is one of the parameters of this function-like macro,
// use the parameter value.
if val, ok := t.params[t.curValue]; ok {
return val, nil
}
if t.f != nil {
// Check whether this identifier is actually a macro "call" with
// parameters. In that case, we should parse the parameters and pass it
// on to a new invocation of parseConst.
if t.peekToken == token.LPAREN {
if cursor, ok := t.f.names[t.curValue]; ok && t.f.isFunctionLikeMacro(cursor) {
// We know the current and peek tokens (the peek one is the '('
// token). So skip ahead until the current token is the first
// unknown token.
t.Next()
t.Next()
// Parse the list of parameters until ')' (rparen) is found.
params := []ast.Expr{}
for i := 0; ; i++ {
if i == 0 && t.curToken == token.RPAREN {
break
}
x, err := parseConstExpr(t, precedenceLowest)
if err != nil {
return nil, err
}
params = append(params, x)
t.Next()
if t.curToken == token.COMMA {
t.Next()
} else if t.curToken == token.RPAREN {
break
} else {
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + ", ',' or ')'",
}
}
}
// Evaluate the macro value and use it as the identifier value.
rparen := t.curPos
pos, text := t.f.getMacro(cursor)
return parseConst(pos, t.fset, text, params, rparen, t.f)
}
}
// Normally the name is something defined in the file (like another
// macro) which we get the declaration from using getASTDeclName.
// This ensures that names that are only referenced inside a macro are
// still getting defined.
if cursor, ok := t.f.names[t.curValue]; ok {
return &ast.Ident{
NamePos: t.curPos,
Name: t.f.getASTDeclName(t.curValue, cursor, false),
}, nil
}
}
// t.f is nil during testing. This is a fallback.
return &ast.Ident{
NamePos: t.curPos,
Name: "C." + t.curValue,
@@ -164,21 +290,25 @@ func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
// tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct {
f *cgoFile
curPos, peekPos token.Pos
fset *token.FileSet
curToken, peekToken token.Token
curValue, peekValue string
buf string
params map[string]ast.Expr
}
// newTokenizer initializes a new tokenizer, positioned at the first token in
// the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
func newTokenizer(start token.Pos, fset *token.FileSet, buf string, f *cgoFile) *tokenizer {
t := &tokenizer{
f: f,
peekPos: start,
fset: fset,
buf: buf,
peekToken: token.ILLEGAL,
params: make(map[string]ast.Expr),
}
// Parse the first two tokens (cur and peek).
t.Next()
@@ -230,7 +360,7 @@ func (t *tokenizer) Next() {
t.peekValue = t.buf[:2]
t.buf = t.buf[2:]
return
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^':
case c == '(' || c == ')' || c == ',' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^':
// Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators.
switch c {
@@ -238,6 +368,8 @@ func (t *tokenizer) Next() {
t.peekToken = token.LPAREN
case ')':
t.peekToken = token.RPAREN
case ',':
t.peekToken = token.COMMA
case '+':
t.peekToken = token.ADD
case '-':
+1 -1
View File
@@ -59,7 +59,7 @@ func TestParseConst(t *testing.T) {
} {
fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0)
expr, err := parseConst(startPos, fset, tc.C)
expr, err := parseConst(startPos, fset, tc.C, nil, token.NoPos, nil)
s := "<invalid>"
if err != nil {
if !strings.HasPrefix(tc.Go, "error: ") {
+82 -41
View File
@@ -63,10 +63,24 @@ long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
unsigned tinygo_clang_Cursor_isAnonymous(GoCXCursor c);
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
unsigned tinygo_clang_Cursor_isMacroFunctionLike(GoCXCursor c);
// Fix some warnings on Windows ARM. Without the __declspec(dllexport), it gives warnings like this:
// In file included from _cgo_export.c:4:
// cgo-gcc-export-header-prolog:49:34: warning: redeclaration of 'tinygo_clang_globals_visitor' should not add 'dllexport' attribute [-Wdll-attribute-on-redeclaration]
// libclang.go:68:5: note: previous declaration is here
// See: https://github.com/golang/go/issues/49721
#if defined(_WIN32)
#define CGO_DECL // __declspec(dllexport)
#else
#define CGO_DECL
#endif
CGO_DECL
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
CGO_DECL
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
CGO_DECL
void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data);
*/
import "C"
@@ -255,10 +269,18 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
},
},
}
var doc []string
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
doc = append(doc, "//go:variadic")
}
if _, ok := f.noescapingFuncs[name]; ok {
doc = append(doc, "//go:noescape")
f.noescapingFuncs[name].used = true
}
if len(doc) != 0 {
decl.Doc.List = append(decl.Doc.List, &ast.Comment{
Slash: pos - 1,
Text: "//go:variadic",
Text: strings.Join(doc, "\n"),
})
}
for i := 0; i < numArgs; i++ {
@@ -370,45 +392,8 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
case C.CXCursor_MacroDefinition:
// Extract tokens from the Clang tokenizer.
// See: https://stackoverflow.com/a/19074846/559350
sourceRange := C.tinygo_clang_getCursorExtent(c)
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var rawTokens *C.CXToken
var numTokens C.unsigned
C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens)
tokens := unsafe.Slice(rawTokens, numTokens)
// Convert this range of tokens back to source text.
// Ugly, but it works well enough.
sourceBuf := &bytes.Buffer{}
var startOffset int
for i, token := range tokens {
spelling := getString(C.clang_getTokenSpelling(tu, token))
location := C.clang_getTokenLocation(tu, token)
var tokenOffset C.unsigned
C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset)
if i == 0 {
// The first token is the macro name itself.
// Skip it (after using its location).
startOffset = int(tokenOffset) + len(name)
} else {
// Later tokens are the macro contents.
for int(tokenOffset) > (startOffset + sourceBuf.Len()) {
// Pad the source text with whitespace (that must have been
// present in the original source as well).
sourceBuf.WriteByte(' ')
}
sourceBuf.WriteString(spelling)
}
}
C.clang_disposeTokens(tu, rawTokens, numTokens)
value := sourceBuf.String()
// Try to convert this #define into a Go constant expression.
tokenPos := token.NoPos
if pos != token.NoPos {
tokenPos = pos + token.Pos(len(name))
}
expr, scannerError := parseConst(tokenPos, f.fset, value)
tokenPos, value := f.getMacro(c)
expr, scannerError := parseConst(tokenPos, f.fset, value, nil, token.NoPos, f)
if scannerError != nil {
f.errors = append(f.errors, *scannerError)
return nil, nil
@@ -488,6 +473,62 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}
}
// Return whether this is a macro that's also function-like, like this:
//
// #define add(a, b) (a+b)
func (f *cgoFile) isFunctionLikeMacro(c clangCursor) bool {
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_MacroDefinition {
return false
}
return C.tinygo_clang_Cursor_isMacroFunctionLike(c) != 0
}
// Get the macro value: the position in the source file and the string value of
// the macro.
func (f *cgoFile) getMacro(c clangCursor) (pos token.Pos, value string) {
// Extract tokens from the Clang tokenizer.
// See: https://stackoverflow.com/a/19074846/559350
sourceRange := C.tinygo_clang_getCursorExtent(c)
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var rawTokens *C.CXToken
var numTokens C.unsigned
C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens)
tokens := unsafe.Slice(rawTokens, numTokens)
defer C.clang_disposeTokens(tu, rawTokens, numTokens)
// Convert this range of tokens back to source text.
// Ugly, but it works well enough.
sourceBuf := &bytes.Buffer{}
var startOffset int
for i, token := range tokens {
spelling := getString(C.clang_getTokenSpelling(tu, token))
location := C.clang_getTokenLocation(tu, token)
var tokenOffset C.unsigned
C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset)
if i == 0 {
// The first token is the macro name itself.
// Skip it (after using its location).
startOffset = int(tokenOffset)
} else {
// Later tokens are the macro contents.
for int(tokenOffset) > (startOffset + sourceBuf.Len()) {
// Pad the source text with whitespace (that must have been
// present in the original source as well).
sourceBuf.WriteByte(' ')
}
sourceBuf.WriteString(spelling)
}
}
value = sourceBuf.String()
// Obtain the position of this token. This is the position of the first
// character in the 'value' string and is used to report errors at the
// correct location in the source file.
pos = f.getCursorPosition(c)
return
}
func getString(clangString C.CXString) (s string) {
rawString := C.clang_getCString(clangString)
s = C.GoString(rawString)
+4
View File
@@ -84,3 +84,7 @@ unsigned tinygo_clang_Cursor_isAnonymous(CXCursor c) {
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
return clang_Cursor_isBitField(c);
}
unsigned tinygo_clang_Cursor_isMacroFunctionLike(CXCursor c) {
return clang_Cursor_isMacroFunctionLike(c);
}
+8
View File
@@ -1,5 +1,6 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
//go:linkname C.__get_errno_num runtime.cgo_errno
func C.__get_errno_num() uintptr
func C.__get_errno() error {
return syscall.Errno(C.__get_errno_num())
}
type (
C.char uint8
C.schar int8
+16
View File
@@ -3,10 +3,26 @@ package main
/*
#define foo 3
#define bar foo
#define unreferenced 4
#define referenced unreferenced
#define fnlike() 5
#define fnlike_val fnlike()
#define square(n) (n*n)
#define square_val square(20)
#define add(a, b) (a + b)
#define add_val add(3, 5)
*/
import "C"
const (
Foo = C.foo
Bar = C.bar
Baz = C.referenced
fnlike = C.fnlike_val
square = C.square_val
add = C.add_val
)
+13
View File
@@ -1,5 +1,6 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
//go:linkname C.__get_errno_num runtime.cgo_errno
func C.__get_errno_num() uintptr
func C.__get_errno() error {
return syscall.Errno(C.__get_errno_num())
}
type (
C.char uint8
C.schar int8
@@ -47,3 +55,8 @@ type (
const C.foo = 3
const C.bar = C.foo
const C.unreferenced = 4
const C.referenced = C.unreferenced
const C.fnlike_val = 5
const C.square_val = (20 * 20)
const C.add_val = (3 + 5)
+13
View File
@@ -10,6 +10,11 @@ typedef struct {
typedef someType noType; // undefined type
// Some invalid noescape lines
#cgo noescape
#cgo noescape foo bar
#cgo noescape unusedFunction
#define SOME_CONST_1 5) // invalid const syntax
#define SOME_CONST_2 6) // const not used (so no error)
#define SOME_CONST_3 1234 // const too large for byte
@@ -26,6 +31,11 @@ import "C"
// #warning another warning
import "C"
// #define add(a, b) (a+b)
// #define add_toomuch add(1, 2, 3)
// #define add_toolittle add(1)
import "C"
// Make sure that errors for the following lines won't change with future
// additions to the CGo preamble.
//
@@ -51,4 +61,7 @@ var (
// constants passed by a command line parameter
_ = C.SOME_PARAM_CONST_invalid
_ = C.SOME_PARAM_CONST_valid
_ = C.add_toomuch
_ = C.add_toolittle
)
+20 -5
View File
@@ -1,12 +1,17 @@
// CGo errors:
// testdata/errors.go:14:1: missing function name in #cgo noescape line
// testdata/errors.go:15:1: multiple function names in #cgo noescape line
// testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:26:5: warning: another warning
// testdata/errors.go:13:23: unexpected token ), expected end of expression
// testdata/errors.go:21:26: unexpected token ), expected end of expression
// testdata/errors.go:16:33: unexpected token ), expected end of expression
// testdata/errors.go:17:34: unexpected token ), expected end of expression
// testdata/errors.go:31:5: warning: another warning
// testdata/errors.go:18:23: unexpected token ), expected end of expression
// testdata/errors.go:26:26: unexpected token ), expected end of expression
// testdata/errors.go:21:33: unexpected token ), expected end of expression
// testdata/errors.go:22:34: unexpected token ), expected end of expression
// -: unexpected token INT, expected end of expression
// testdata/errors.go:35:35: unexpected number of parameters: expected 2, got 3
// testdata/errors.go:36:31: unexpected number of parameters: expected 2, got 1
// testdata/errors.go:3:1: function "unusedFunction" in #cgo noescape line is not used
// Type checking errors after CGo processing:
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows)
@@ -17,9 +22,12 @@
// testdata/errors.go:114: undefined: C.SOME_CONST_b
// testdata/errors.go:116: undefined: C.SOME_CONST_startspace
// testdata/errors.go:119: undefined: C.SOME_PARAM_CONST_invalid
// testdata/errors.go:122: undefined: C.add_toomuch
// testdata/errors.go:123: undefined: C.add_toolittle
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -51,6 +59,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
//go:linkname C.__get_errno_num runtime.cgo_errno
func C.__get_errno_num() uintptr
func C.__get_errno() error {
return syscall.Errno(C.__get_errno_num())
}
type (
C.char uint8
C.schar int8
+8
View File
@@ -5,6 +5,7 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -36,6 +37,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
//go:linkname C.__get_errno_num runtime.cgo_errno
func C.__get_errno_num() uintptr
func C.__get_errno() error {
return syscall.Errno(C.__get_errno_num())
}
type (
C.char uint8
C.schar int8
+5
View File
@@ -9,6 +9,10 @@ static void staticfunc(int x);
// Global variable signatures.
extern int someValue;
void notEscapingFunction(int *a);
#cgo noescape notEscapingFunction
*/
import "C"
@@ -18,6 +22,7 @@ func accessFunctions() {
C.variadic0()
C.variadic2(3, 5)
C.staticfunc(3)
C.notEscapingFunction(nil)
}
func accessGlobals() {
+13
View File
@@ -1,5 +1,6 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
//go:linkname C.__get_errno_num runtime.cgo_errno
func C.__get_errno_num() uintptr
func C.__get_errno() error {
return syscall.Errno(C.__get_errno_num())
}
type (
C.char uint8
C.schar int8
@@ -67,5 +75,10 @@ func C.staticfunc!symbols.go(x C.int)
var C.staticfunc!symbols.go$funcaddr unsafe.Pointer
//export notEscapingFunction
//go:noescape
func C.notEscapingFunction(a *C.int)
var C.notEscapingFunction$funcaddr unsafe.Pointer
//go:extern someValue
var C.someValue C.int
+8
View File
@@ -1,5 +1,6 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
@@ -31,6 +32,13 @@ func C.CBytes(b []byte) unsafe.Pointer {
return C.__CBytes(b)
}
//go:linkname C.__get_errno_num runtime.cgo_errno
func C.__get_errno_num() uintptr
func C.__get_errno() error {
return syscall.Errno(C.__get_errno_num())
}
type (
C.char uint8
C.schar int8
+3 -9
View File
@@ -331,6 +331,7 @@ func (c *Config) CFlags(libclang bool) []string {
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(picolibcDir, "include"),
"-isystem", filepath.Join(picolibcDir, "tinystdio"),
"-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
)
case "musl":
root := goenv.Get("TINYGOROOT")
@@ -340,6 +341,7 @@ func (c *Config) CFlags(libclang bool) []string {
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
"-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"),
"-isystem", filepath.Join(root, "lib", "musl", "include"),
)
case "wasi-libc":
@@ -406,15 +408,7 @@ func (c *Config) LDFlags() []string {
if c.Target.LinkerScript != "" {
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
}
if c.Options.ExtLDFlags != "" {
ext, err := shlex.Split(c.Options.ExtLDFlags)
if err != nil {
// if shlex can't split it, pass it as-is and let the external linker complain
ext = []string{c.Options.ExtLDFlags}
}
ldflags = append(ldflags, ext...)
}
ldflags = append(ldflags, c.Options.ExtLDFlags...)
return ldflags
}
+2 -2
View File
@@ -12,7 +12,7 @@ var (
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"}
validSchedulerOptions = []string{"none", "tasks", "asyncify"}
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
validPrintSizeOptions = []string{"none", "short", "full"}
validPrintSizeOptions = []string{"none", "short", "full", "html"}
validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
)
@@ -58,7 +58,7 @@ type Options struct {
Timeout time.Duration
WITPackage string // pass through to wasm-tools component embed invocation
WITWorld string // pass through to wasm-tools component embed -w option
ExtLDFlags string
ExtLDFlags []string
}
// Verify performs a validation on the given options, raising an error if options are not valid.
+1 -1
View File
@@ -11,7 +11,7 @@ func TestVerifyOptions(t *testing.T) {
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify`)
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`)
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, html`)
expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
testCases := []struct {
+3
View File
@@ -46,6 +46,7 @@ type TargetSpec struct {
LinkerScript string `json:"linkerscript,omitempty"`
ExtraFiles []string `json:"extra-files,omitempty"`
RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum
BootPatches []string `json:"boot-patches,omitempty"` // Bootloader patches to be applied in the order they appear.
Emulator string `json:"emulator,omitempty"`
FlashCommand string `json:"flash-command,omitempty"`
GDB []string `json:"gdb,omitempty"`
@@ -390,6 +391,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
"-platform_version", "macos", platformVersion, platformVersion,
)
spec.ExtraFiles = append(spec.ExtraFiles,
"src/internal/futex/futex_darwin.c",
"src/runtime/os_darwin.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
@@ -413,6 +415,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
spec.CFlags = append(spec.CFlags, "-mno-outline-atomics")
}
spec.ExtraFiles = append(spec.ExtraFiles,
"src/internal/futex/futex_linux.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
case "windows":
+7 -5
View File
@@ -19,8 +19,9 @@ const maxFieldsPerParam = 3
// useful while declaring or defining a function.
type paramInfo struct {
llvmType llvm.Type
name string // name, possibly with suffixes for e.g. struct fields
elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
name string // name, possibly with suffixes for e.g. struct fields
elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
flags paramFlags // extra flags for this parameter
}
// paramFlags identifies parameter attributes for flags. Most importantly, it
@@ -28,9 +29,9 @@ type paramInfo struct {
type paramFlags uint8
const (
// Parameter may have the deferenceable_or_null attribute. This attribute
// cannot be applied to unsafe.Pointer and to the data pointer of slices.
paramIsDeferenceableOrNull = 1 << iota
// Whether this is a full or partial Go parameter (int, slice, etc).
// The extra context parameter is not a Go parameter.
paramIsGoParam = 1 << iota
)
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
@@ -195,6 +196,7 @@ func (c *compilerContext) getParamInfo(t llvm.Type, name string, goType types.Ty
info := paramInfo{
llvmType: t,
name: name,
flags: paramIsGoParam,
}
if goType != nil {
switch underlying := goType.Underlying().(type) {
+36 -17
View File
@@ -4,7 +4,9 @@ package compiler
// or pseudo-operations that are lowered during goroutine lowering.
import (
"fmt"
"go/types"
"math"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
@@ -41,17 +43,17 @@ func (b *builder) createChanSend(instr *ssa.Send) {
b.CreateStore(chanValue, valueAlloca)
}
// Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Allocate buffer for the channel operation.
channelOp := b.getLLVMRuntimeType("channelOp")
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
// End the lifetime of the allocas.
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
if !isZeroSize {
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
}
@@ -72,12 +74,12 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
}
// Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Allocate buffer for the channel operation.
channelOp := b.getLLVMRuntimeType("channelOp")
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
var received llvm.Value
if isZeroSize {
received = llvm.ConstNull(valueType)
@@ -85,7 +87,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
received = b.CreateLoad(valueType, valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
}
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
@@ -124,6 +126,20 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
}
}
const maxSelectStates = math.MaxUint32 >> 2
if len(expr.States) > maxSelectStates {
// The runtime code assumes that the number of state must fit in 30 bits
// (so the select index can be stored in a uint32 with two bits reserved
// for other purposes). It seems unlikely that a real program would have
// that many states, but we check for this case anyway to be sure.
// We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic
// operations which aren't available everywhere.
b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates))
// Continue as usual (we'll generate broken code but the error will
// prevent the compilation to complete).
}
// This code create a (stack-allocated) slice containing all the select
// cases and then calls runtime.chanSelect to perform the actual select
// statement.
@@ -198,10 +214,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if expr.Blocking {
// Stack-allocate operation structures.
// If these were simply created as a slice, they would heap-allocate.
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{
opsAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelOp"), len(selectStates))
opsAlloca, opsSize := b.createTemporaryAlloca(opsAllocaType, "select.block.alloca")
opsLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
opsPtr := b.CreateGEP(opsAllocaType, opsAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.block")
@@ -209,15 +225,18 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
results = b.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
opsPtr, opsLen, opsLen, // []channelOp
}, "select.result")
// Terminate the lifetime of the operation structures.
b.emitLifetimeEnd(chBlockAlloca, chBlockSize)
b.emitLifetimeEnd(opsAlloca, opsSize)
} else {
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
opsPtr := llvm.ConstNull(b.dataPtrType)
opsLen := llvm.ConstInt(b.uintptrType, 0, false)
results = b.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
opsPtr, opsLen, opsLen, // []channelOp (nil slice)
}, "select.result")
}
+9 -3
View File
@@ -17,6 +17,7 @@ import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/loader"
"github.com/tinygo-org/tinygo/src/tinygo"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/types/typeutil"
"tinygo.org/x/go-llvm"
@@ -1680,7 +1681,12 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
result = b.CreateSelect(cmp, result, arg, "")
}
return result, nil
case "panic":
// This is rare, but happens in "defer panic()".
b.createRuntimeInvoke("_panic", argValues, "")
return llvm.Value{}, nil
case "print", "println":
b.createRuntimeCall("printlock", nil, "")
for i, value := range argValues {
if i >= 1 && callName == "println" {
b.createRuntimeCall("printspace", nil, "")
@@ -1741,6 +1747,7 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
if callName == "println" {
b.createRuntimeCall("printnl", nil, "")
}
b.createRuntimeCall("printunlock", nil, "")
return llvm.Value{}, nil // print() or println() returns void
case "real":
cplx := argValues[0]
@@ -1865,10 +1872,9 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
}
return llvm.ConstInt(b.ctx.Int1Type(), supportsRecover, false), nil
case name == "runtime.panicStrategy":
// These constants are defined in src/runtime/panic.go.
panicStrategy := map[string]uint64{
"print": 1, // panicStrategyPrint
"trap": 2, // panicStrategyTrap
"print": tinygo.PanicStrategyPrint,
"trap": tinygo.PanicStrategyTrap,
}[b.Config.PanicStrategy]
return llvm.ConstInt(b.ctx.Int8Type(), panicStrategy, false), nil
case name == "runtime/interrupt.New":
+3
View File
@@ -41,6 +41,8 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass.
// It must have an alignment of 1, otherwise LLVM thinks a ptrtoint of the
// global has the lower bits unset.
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType)
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
@@ -48,6 +50,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
global.SetVisibility(llvm.HiddenVisibility)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetAlignment(1)
initializer := llvm.ConstNull(globalLLVMType)
initializer = b.CreateInsertValue(initializer, funcContext, 0, "")
initializer = b.CreateInsertValue(initializer, funcPtr, 1, "")
+5 -11
View File
@@ -6,17 +6,11 @@ import (
"go/token"
"go/types"
"github.com/tinygo-org/tinygo/src/tinygo"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// constants for hashmap algorithms; must match src/runtime/hashmap.go
const (
hashmapAlgorithmBinary = iota
hashmapAlgorithmString
hashmapAlgorithmInterface
)
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
// initializing an appropriately sized object.
func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
@@ -24,20 +18,20 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
keyType := mapType.Key().Underlying()
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
var llvmKeyType llvm.Type
var alg uint64 // must match values in src/runtime/hashmap.go
var alg uint64
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// String keys.
llvmKeyType = b.getLLVMType(keyType)
alg = hashmapAlgorithmString
alg = uint64(tinygo.HashmapAlgorithmString)
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = b.getLLVMType(keyType)
alg = hashmapAlgorithmBinary
alg = uint64(tinygo.HashmapAlgorithmBinary)
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = b.getLLVMRuntimeType("_interface")
alg = hashmapAlgorithmInterface
alg = uint64(tinygo.HashmapAlgorithmInterface)
}
keySize := b.targetData.TypeAllocSize(llvmKeyType)
valueSize := b.targetData.TypeAllocSize(llvmValueType)
+34 -6
View File
@@ -33,6 +33,7 @@ type functionInfo struct {
exported bool // go:export, CGo
interrupt bool // go:interrupt
nobounds bool // go:nobounds
noescape bool // go:noescape
variadic bool // go:variadic (CGo only)
inline inlineType // go:inline
}
@@ -127,11 +128,20 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
c.addStandardDeclaredAttributes(llvmFn)
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, info := range paramInfos {
if info.elemSize != 0 {
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, info.elemSize)
for i, paramInfo := range paramInfos {
if paramInfo.elemSize != 0 {
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, paramInfo.elemSize)
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
}
if info.noescape && paramInfo.flags&paramIsGoParam != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
// Parameters to functions with a //go:noescape parameter should get
// the nocapture attribute. However, the context parameter should
// not.
// (It may be safe to add the nocapture parameter to the context
// parameter, but I'd like to stay on the safe side here).
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)
llvmFn.AddAttributeAtIndex(i+1, nocapture)
}
}
// Set a number of function or parameter attributes, depending on the
@@ -221,6 +231,15 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
}
}
// Build the function if needed.
c.maybeCreateSyntheticFunction(fn, llvmFn)
return fnType, llvmFn
}
// If this is a synthetic function (such as a generic function or a wrapper),
// create it now.
func (c *compilerContext) maybeCreateSyntheticFunction(fn *ssa.Function, llvmFn llvm.Value) {
// Synthetic functions are functions that do not appear in the source code,
// they are artificially constructed. Usually they are wrapper functions
// that are not referenced anywhere except in a SSA call instruction so
@@ -228,6 +247,10 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// The exception is the package initializer, which does appear in the
// *ssa.Package members and so shouldn't be created here.
if fn.Synthetic != "" && fn.Synthetic != "package initializer" && fn.Synthetic != "generic function" && fn.Synthetic != "range-over-func yield" {
if len(fn.Blocks) == 0 {
c.addError(fn.Pos(), "missing function body")
return
}
irbuilder := c.ctx.NewBuilder()
b := newBuilder(c, irbuilder, fn)
b.createFunction()
@@ -235,8 +258,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
llvmFn.SetUnnamedAddr(true)
}
return fnType, llvmFn
}
// getFunctionInfo returns information about a function that is not directly
@@ -345,7 +366,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
continue
}
if len(parts) != 2 {
c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmimport, not %d", len(parts)-1))
c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmexport, not %d", len(parts)-1))
continue
}
name := parts[1]
@@ -394,6 +415,13 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
if hasUnsafeImport(f.Pkg.Pkg) {
info.nobounds = true
}
case "//go:noescape":
// Don't let pointer parameters escape.
// Following the upstream Go implementation, we only do this for
// declarations, not definitions.
if len(f.Blocks) == 0 {
info.noescape = true
}
case "//go:variadic":
// The //go:variadic pragma is emitted by the CGo preprocessing
// pass for C variadic functions. This includes both explicit
+26 -26
View File
@@ -3,7 +3,7 @@ source_filename = "channel.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi"
%runtime.channelBlockedList = type { ptr, ptr, ptr, { ptr, i32, i32 } }
%runtime.channelOp = type { ptr, ptr, i32, ptr }
%runtime.chanSelectState = type { ptr, ptr }
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
@@ -18,15 +18,15 @@ entry:
}
; Function Attrs: nounwind
define hidden void @main.chanIntSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
define hidden void @main.chanIntSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.op = alloca %runtime.channelOp, align 8
%chan.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value)
store i32 3, ptr %chan.value, align 4
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value)
ret void
}
@@ -34,48 +34,48 @@ entry:
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3
declare void @runtime.chanSend(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1
declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3
; Function Attrs: nounwind
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.op = alloca %runtime.channelOp, align 8
%chan.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value)
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value)
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
ret void
}
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1
; Function Attrs: nounwind
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
%chan.op = alloca %runtime.channelOp, align 8
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
ret void
}
; Function Attrs: nounwind
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
%chan.op = alloca %runtime.channelOp, align 8
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
ret void
}
; Function Attrs: nounwind
define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(32) %ch1, ptr dereferenceable_or_null(32) %ch2, ptr %context) unnamed_addr #2 {
define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(36) %ch1, ptr dereferenceable_or_null(36) %ch2, ptr %context) unnamed_addr #2 {
entry:
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
%select.send.value = alloca i32, align 4
@@ -88,7 +88,7 @@ entry:
store ptr %ch2, ptr %0, align 4
%.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1, i32 1
store ptr null, ptr %.repack3, align 4
%select.result = call { i32, i1 } @runtime.tryChanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr undef) #4
%select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca)
%1 = extractvalue { i32, i1 } %select.result, 0
%2 = icmp eq i32 %1, 0
@@ -105,7 +105,7 @@ select.body: ; preds = %select.next
br label %select.done
}
declare { i32, i1 } @runtime.tryChanSelect(ptr, ptr, i32, i32, ptr) #1
declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #1
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
+11 -1
View File
@@ -3,7 +3,7 @@ source_filename = "defer.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv7m-unknown-unknown-eabi"
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i1, %runtime._interface }
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface }
%runtime._interface = type { ptr, ptr }
%runtime._defer = type { i32, ptr }
@@ -122,12 +122,18 @@ declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #2
; Function Attrs: nounwind
define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #1 {
entry:
call void @runtime.printlock(ptr undef) #4
call void @runtime.printint32(i32 3, ptr undef) #4
call void @runtime.printunlock(ptr undef) #4
ret void
}
declare void @runtime.printlock(ptr) #2
declare void @runtime.printint32(i32, ptr) #2
declare void @runtime.printunlock(ptr) #2
; Function Attrs: nounwind
define hidden void @main.deferMultiple(ptr %context) unnamed_addr #1 {
entry:
@@ -250,14 +256,18 @@ rundefers.end7: ; preds = %rundefers.loophead1
; Function Attrs: nounwind
define internal void @"main.deferMultiple$1"(ptr %context) unnamed_addr #1 {
entry:
call void @runtime.printlock(ptr undef) #4
call void @runtime.printint32(i32 3, ptr undef) #4
call void @runtime.printunlock(ptr undef) #4
ret void
}
; Function Attrs: nounwind
define internal void @"main.deferMultiple$2"(ptr %context) unnamed_addr #1 {
entry:
call void @runtime.printlock(ptr undef) #4
call void @runtime.printint32(i32 5, ptr undef) #4
call void @runtime.printunlock(ptr undef) #4
ret void
}
+8 -2
View File
@@ -70,7 +70,9 @@ entry:
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr undef) #9
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9
%2 = load i32, ptr %n, align 4
call void @runtime.printlock(ptr undef) #9
call void @runtime.printint32(i32 %2, ptr undef) #9
call void @runtime.printunlock(ptr undef) #9
ret void
}
@@ -91,8 +93,12 @@ entry:
ret void
}
declare void @runtime.printlock(ptr) #2
declare void @runtime.printint32(i32, ptr) #2
declare void @runtime.printunlock(ptr) #2
; Function Attrs: nounwind
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 {
entry:
@@ -135,13 +141,13 @@ entry:
declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #2
; Function Attrs: nounwind
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #1 {
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
entry:
call void @runtime.chanClose(ptr %ch, ptr undef) #9
ret void
}
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #2
declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #2
; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
+8 -2
View File
@@ -76,7 +76,9 @@ entry:
store ptr %n, ptr %1, align 4
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9
%2 = load i32, ptr %n, align 4
call void @runtime.printlock(ptr undef) #9
call void @runtime.printint32(i32 %2, ptr undef) #9
call void @runtime.printunlock(ptr undef) #9
ret void
}
@@ -98,8 +100,12 @@ entry:
unreachable
}
declare void @runtime.printlock(ptr) #1
declare void @runtime.printint32(i32, ptr) #1
declare void @runtime.printunlock(ptr) #1
; Function Attrs: nounwind
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #2 {
entry:
@@ -144,13 +150,13 @@ entry:
declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #1
; Function Attrs: nounwind
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
entry:
call void @runtime.chanClose(ptr %ch, ptr undef) #9
ret void
}
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #1
declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1
; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 {
+9
View File
@@ -106,3 +106,12 @@ var undefinedGlobalNotInSection uint32
//go:align 1024
//go:section .global_section
var multipleGlobalPragmas uint32
//go:noescape
func doesNotEscapeParam(a *int, b []int, c chan int, d *[0]byte)
// The //go:noescape pragma only works on declarations, not definitions.
//
//go:noescape
func stillEscapes(a *int, b []int, c chan int, d *[0]byte) {
}
+8
View File
@@ -85,6 +85,14 @@ entry:
declare void @main.undefinedFunctionNotInSection(ptr) #1
declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32, i32, ptr nocapture dereferenceable_or_null(36), ptr nocapture, ptr) #1
; Function Attrs: nounwind
define hidden void @main.stillEscapes(ptr dereferenceable_or_null(4) %a, ptr %b.data, i32 %b.len, i32 %b.cap, ptr dereferenceable_or_null(36) %c, ptr %d, ptr %context) unnamed_addr #2 {
entry:
ret void
}
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
+20 -9
View File
@@ -4,29 +4,37 @@ import (
"errors"
"fmt"
"io"
"runtime/debug"
"strings"
)
// Version of TinyGo.
// Update this value before release of new version of software.
const version = "0.34.0"
var (
// This variable is set at build time using -ldflags parameters.
// See: https://stackoverflow.com/a/11355611
GitSha1 string
)
const version = "0.35.0"
// Return TinyGo version, either in the form 0.30.0 or as a development version
// (like 0.30.0-dev-abcd012).
func Version() string {
v := version
if strings.HasSuffix(version, "-dev") && GitSha1 != "" {
v += "-" + GitSha1
if strings.HasSuffix(version, "-dev") {
if hash := readGitHash(); hash != "" {
v += "-" + hash
}
}
return v
}
func readGitHash() string {
if info, ok := debug.ReadBuildInfo(); ok {
for _, setting := range info.Settings {
if setting.Key == "vcs.revision" {
return setting.Value[:8]
}
}
}
return ""
}
// GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
func GetGorootVersion() (major, minor int, err error) {
@@ -42,6 +50,9 @@ func GetGorootVersion() (major, minor int, err error) {
// major, minor, and patch version: 1, 3, and 2 in this example.
// If there is an error, (0, 0, 0) and an error will be returned.
func Parse(version string) (major, minor, patch int, err error) {
if strings.HasPrefix(version, "devel ") {
version = strings.Split(strings.TrimPrefix(version, "devel "), version)[0]
}
if version == "" || version[:2] != "go" {
return 0, 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
}
+1
View File
@@ -21,6 +21,7 @@ func TestParse(t *testing.T) {
{"go1.23.5-rc6", 1, 23, 5, false},
{"go2.0", 2, 0, 0, false},
{"go2.0.15", 2, 0, 15, false},
{"devel go1.24-f99f5da18f Thu Nov 14 22:29:26 2024 +0000 darwin/arm64", 1, 24, 0, false},
}
for _, tt := range tests {
t.Run(tt.v, func(t *testing.T) {
+2 -3
View File
@@ -2,7 +2,7 @@ module github.com/tinygo-org/tinygo/internal/tools
go 1.22.4
require github.com/bytecodealliance/wasm-tools-go v0.3.0
require github.com/bytecodealliance/wasm-tools-go v0.3.1
require (
github.com/coreos/go-semver v0.3.1 // indirect
@@ -12,8 +12,7 @@ require (
github.com/regclient/regclient v0.7.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/urfave/cli/v3 v3.0.0-alpha9 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/urfave/cli/v3 v3.0.0-alpha9.2 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sys v0.26.0 // indirect
)
+6 -8
View File
@@ -1,5 +1,5 @@
github.com/bytecodealliance/wasm-tools-go v0.3.0 h1:9aeDFYpbi3gtIW/nJCH+P+LhFMqezGoOfzqbUZLadho=
github.com/bytecodealliance/wasm-tools-go v0.3.0/go.mod h1:VY+9FlpLi6jnhCrZLkyJjF9rjU4aEekgaRTk28MS2JE=
github.com/bytecodealliance/wasm-tools-go v0.3.1 h1:9Q9PjSzkbiVmkUvZ7nYCfJ02mcQDBalxycA3s8g7kR4=
github.com/bytecodealliance/wasm-tools-go v0.3.1/go.mod h1:vNAQ8DAEp6xvvk+TUHah5DslLEa76f4H6e737OeaxuY=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -23,14 +23,12 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo=
github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/urfave/cli/v3 v3.0.0-alpha9.2 h1:CL8llQj3dGRLVQQzHxS+ZYRLanOuhyK1fXgLKD+qV+Y=
github.com/urfave/cli/v3 v3.0.0-alpha9.2/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+9
View File
@@ -287,9 +287,17 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// Get the object layout, if it is available.
llvmLayoutType := r.getLLVMTypeFromLayout(operands[2])
// Get the alignment of the memory to be allocated.
alignment := 0 // use default alignment if unset
alignAttr := inst.llvmInst.GetCallSiteEnumAttribute(0, llvm.AttributeKindID("align"))
if !alignAttr.IsNil() {
alignment = int(alignAttr.GetEnumValue())
}
// Create the object.
alloc := object{
globalName: r.pkgName + "$alloc",
align: alignment,
llvmLayoutType: llvmLayoutType,
buffer: newRawValue(uint32(size)),
size: uint32(size),
@@ -646,6 +654,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
globalName: r.pkgName + "$alloca",
buffer: newRawValue(uint32(size)),
size: uint32(size),
align: inst.llvmInst.Alignment(),
}
index := len(r.objects)
r.objects = append(r.objects, alloca)
+9 -6
View File
@@ -42,6 +42,7 @@ type object struct {
globalName string // name, if not yet created (not guaranteed to be the final name)
buffer value // buffer with value as given by interp, nil if external
size uint32 // must match buffer.len(), if available
align int // alignment of the object (may be 0 if unknown)
constant bool // true if this is a constant global
marked uint8 // 0 means unmarked, 1 means external read, 2 means external write
}
@@ -593,6 +594,12 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
// runtime.alloc.
// First allocate a new global for this object.
obj := mem.get(v.index())
alignment := obj.align
if alignment == 0 {
// Unknown alignment, perhaps from a direct call to runtime.alloc in
// the runtime. Use a conservative default instead.
alignment = mem.r.maxAlign
}
if obj.llvmType.IsNil() && obj.llvmLayoutType.IsNil() {
// Create an initializer without knowing the global type.
// This is probably the result of a runtime.alloc call.
@@ -603,7 +610,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
globalType := initializer.Type()
llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName)
llvmValue.SetInitializer(initializer)
llvmValue.SetAlignment(mem.r.maxAlign)
llvmValue.SetAlignment(alignment)
obj.llvmGlobal = llvmValue
mem.put(v.index(), obj)
} else {
@@ -642,11 +649,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
return llvm.Value{}, errors.New("interp: allocated value does not match allocated type")
}
llvmValue.SetInitializer(initializer)
if obj.llvmType.IsNil() {
// The exact type isn't known (only the layout), so use the
// alignment that would normally be expected from runtime.alloc.
llvmValue.SetAlignment(mem.r.maxAlign)
}
llvmValue.SetAlignment(alignment)
}
// It should be included in r.globals because otherwise markExternal
+2
View File
@@ -243,6 +243,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
"internal/binary/": false,
"internal/bytealg/": false,
"internal/cm/": false,
"internal/futex/": false,
"internal/fuzz/": false,
"internal/reflectlite/": false,
"internal/gclayout": false,
@@ -256,6 +257,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
"runtime/": false,
"sync/": true,
"testing/": true,
"tinygo/": false,
"unique/": false,
}
+1 -1
View File
@@ -485,7 +485,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
var initialCFlags []string
initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...)
initialCFlags = append(initialCFlags, "-I"+p.Dir)
generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags)
generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.config.GOOS())
p.CFlags = append(initialCFlags, cflags...)
p.CGoHeaders = headerCode
for path, hash := range accessedFiles {
+9 -2
View File
@@ -1509,7 +1509,7 @@ func main() {
stackSize = uint64(size)
return err
})
printSize := flag.String("size", "", "print sizes (none, short, full)")
printSize := flag.String("size", "", "print sizes (none, short, full, html)")
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")
@@ -1641,12 +1641,19 @@ func main() {
Timeout: *timeout,
WITPackage: witPackage,
WITWorld: witWorld,
ExtLDFlags: extLDFlags,
}
if *printCommands {
options.PrintCommands = printCommand
}
if extLDFlags != "" {
options.ExtLDFlags, err = shlex.Split(extLDFlags)
if err != nil {
fmt.Fprintln(os.Stderr, "could not parse -extldflags:", err)
os.Exit(1)
}
}
err = options.Verify()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
+74 -3
View File
@@ -15,7 +15,6 @@ import (
"reflect"
"regexp"
"runtime"
"slices"
"strings"
"sync"
"testing"
@@ -25,6 +24,7 @@ import (
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"github.com/tetratelabs/wazero/sys"
"github.com/tinygo-org/tinygo/builder"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/diagnostics"
@@ -429,6 +429,9 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") {
t.Log(line)
}
if stdout.Len() != 0 {
t.Logf("output:\n%s", stdout.String())
}
t.Fail()
return
}
@@ -517,7 +520,7 @@ func TestWebAssembly(t *testing.T) {
}
}
}
if !slices.Equal(imports, tc.imports) {
if !stringSlicesEqual(imports, tc.imports) {
t.Errorf("import list not as expected!\nexpected: %v\nactual: %v", tc.imports, imports)
}
}
@@ -525,6 +528,20 @@ func TestWebAssembly(t *testing.T) {
}
}
func stringSlicesEqual(s1, s2 []string) bool {
// We can use slices.Equal once we drop support for Go 1.20 (it was added in
// Go 1.21).
if len(s1) != len(s2) {
return false
}
for i, s := range s1 {
if s != s2[i] {
return false
}
}
return true
}
func TestWasmExport(t *testing.T) {
t.Parallel()
@@ -683,7 +700,14 @@ func TestWasmExport(t *testing.T) {
if tc.command {
// Call _start (the entry point), which calls
// tester.callTestMain, which then runs all the tests.
mustCall(mod.ExportedFunction("_start").Call(ctx))
_, err := mod.ExportedFunction("_start").Call(ctx)
if err != nil {
if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() == 0 {
// Exited with code 0. Nothing to worry about.
} else {
t.Error("failed to run _start:", err)
}
}
} else {
// Run the _initialize call, because this is reactor mode wasm.
mustCall(mod.ExportedFunction("_initialize").Call(ctx))
@@ -728,6 +752,7 @@ func TestWasmFuncOf(t *testing.T) {
// Test //go:wasmexport in JavaScript (using NodeJS).
func TestWasmExportJS(t *testing.T) {
t.Parallel()
type testCase struct {
name string
buildMode string
@@ -738,7 +763,9 @@ func TestWasmExportJS(t *testing.T) {
{name: "c-shared", buildMode: "c-shared"},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Build the wasm binary.
tmpdir := t.TempDir()
options := optionsFromTarget("wasm", sema)
@@ -766,12 +793,56 @@ func TestWasmExportJS(t *testing.T) {
}
}
// Test whether Go.run() (in wasm_exec.js) normally returns and returns the
// right exit code.
func TestWasmExit(t *testing.T) {
t.Parallel()
type testCase struct {
name string
output string
}
tests := []testCase{
{name: "normal", output: "exit code: 0\n"},
{name: "exit-0", output: "exit code: 0\n"},
{name: "exit-0-sleep", output: "slept\nexit code: 0\n"},
{name: "exit-1", output: "exit code: 1\n"},
{name: "exit-1-sleep", output: "slept\nexit code: 1\n"},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
options := optionsFromTarget("wasm", sema)
buildConfig, err := builder.NewConfig(&options)
if err != nil {
t.Fatal(err)
}
buildConfig.Target.Emulator = "node testdata/wasmexit.js {}"
output := &bytes.Buffer{}
_, err = buildAndRun("testdata/wasmexit.go", buildConfig, output, []string{tc.name}, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
return cmd.Run()
})
if err != nil {
t.Error(err)
}
expected := "wasmexit test: " + tc.name + "\n" + tc.output
checkOutputData(t, []byte(expected), output.Bytes())
})
}
}
// Check whether the output of a test equals the expected output.
func checkOutput(t *testing.T, filename string, actual []byte) {
expectedOutput, err := os.ReadFile(filename)
if err != nil {
t.Fatal("could not read output file:", err)
}
checkOutputData(t, expectedOutput, actual)
}
func checkOutputData(t *testing.T, expectedOutput, actual []byte) {
expectedOutput = bytes.ReplaceAll(expectedOutput, []byte("\r\n"), []byte("\n"))
actual = bytes.ReplaceAll(actual, []byte("\r\n"), []byte("\n"))
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey
// If you update the above build constraint, you'll probably also need to update
// src/runtime/rand_hwrng.go.
+139
View File
@@ -0,0 +1,139 @@
//go:build tkey
// Hand written file based on https://github.com/tillitis/tkey-libs/blob/main/include/tkey/tk1_mem.h
package tkey
import (
"runtime/volatile"
"unsafe"
)
// Peripherals
var (
TRNG = (*TRNG_Type)(unsafe.Pointer(TK1_MMIO_TRNG_BASE))
TIMER = (*TIMER_Type)(unsafe.Pointer(TK1_MMIO_TIMER_BASE))
UDS = (*UDS_Type)(unsafe.Pointer(TK1_MMIO_UDS_BASE))
UART = (*UART_Type)(unsafe.Pointer(TK1_MMIO_UART_BASE))
TOUCH = (*TOUCH_Type)(unsafe.Pointer(TK1_MMIO_TOUCH_BASE))
TK1 = (*TK1_Type)(unsafe.Pointer(TK1_MMIO_TK1_BASE))
)
// Memory sections
const (
TK1_ROM_BASE uintptr = 0x00000000
TK1_RAM_BASE uintptr = 0x40000000
TK1_MMIO_BASE uintptr = 0xc0000000
TK1_MMIO_TRNG_BASE uintptr = 0xc0000000
TK1_MMIO_TIMER_BASE uintptr = 0xc1000000
TK1_MMIO_UDS_BASE uintptr = 0xc2000000
TK1_MMIO_UART_BASE uintptr = 0xc3000000
TK1_MMIO_TOUCH_BASE uintptr = 0xc4000000
TK1_MMIO_FW_RAM_BASE uintptr = 0xd0000000
TK1_MMIO_TK1_BASE uintptr = 0xff000000
)
// Memory section sizes
const (
TK1_RAM_SIZE uintptr = 0x20000
TK1_MMIO_SIZE uintptr = 0x3fffffff
)
type TRNG_Type struct {
_ [36]byte
STATUS volatile.Register32
_ [88]byte
ENTROPY volatile.Register32
}
type TIMER_Type struct {
_ [32]byte
CTRL volatile.Register32
STATUS volatile.Register32
PRESCALER volatile.Register32
TIMER volatile.Register32
}
type UDS_Type struct {
_ [64]byte
DATA [8]volatile.Register32
}
type UART_Type struct {
_ [128]byte
RX_STATUS volatile.Register32
RX_DATA volatile.Register32
RX_BYTES volatile.Register32
_ [116]byte
TX_STATUS volatile.Register32
TX_DATA volatile.Register32
}
type TOUCH_Type struct {
_ [36]byte
STATUS volatile.Register32
}
type TK1_Type struct {
NAME0 volatile.Register32
NAME1 volatile.Register32
VERSION volatile.Register32
_ [16]byte
SWITCH_APP volatile.Register32
_ [4]byte
LED volatile.Register32
GPIO volatile.Register16
APP_ADDR volatile.Register32
APP_SIZE volatile.Register32
BLAKE2S volatile.Register32
_ [72]byte
CDI_FIRST [8]volatile.Register32
_ [32]byte
UDI_FIRST [2]volatile.Register32
_ [62]byte
RAM_ADDR_RAND volatile.Register16
_ [2]byte
RAM_DATA_RAND volatile.Register16
_ [126]byte
CPU_MON_CTRL volatile.Register16
_ [2]byte
CPU_MON_FIRST volatile.Register32
CPU_MON_LAST volatile.Register32
_ [60]byte
SYSTEM_RESET volatile.Register16
_ [66]byte
SPI_EN volatile.Register32
SPI_XFER volatile.Register32
SPI_DATA volatile.Register32
}
const (
TK1_MMIO_TIMER_CTRL_START_BIT = 0
TK1_MMIO_TIMER_CTRL_STOP_BIT = 1
TK1_MMIO_TIMER_CTRL_START = 1 << TK1_MMIO_TIMER_CTRL_START_BIT
TK1_MMIO_TIMER_CTRL_STOP = 1 << TK1_MMIO_TIMER_CTRL_STOP_BIT
TK1_MMIO_TK1_LED_R_BIT = 2
TK1_MMIO_TK1_LED_G_BIT = 1
TK1_MMIO_TK1_LED_B_BIT = 0
TK1_MMIO_TK1_GPIO1_BIT = 0
TK1_MMIO_TK1_GPIO2_BIT = 1
TK1_MMIO_TK1_GPIO3_BIT = 2
TK1_MMIO_TK1_GPIO4_BIT = 3
)
+2 -4
View File
@@ -7,15 +7,13 @@ import (
"time"
)
// change these to test a different UART or pins if available
var (
uart = machine.Serial
tx = machine.UART_TX_PIN
rx = machine.UART_RX_PIN
)
func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
// use default settings for UART
uart.Configure(machine.UARTConfig{})
uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n"))
input := make([]byte, 64)
+2 -2
View File
@@ -13,11 +13,11 @@ func main() {
Stereo: true,
})
data := make([]uint32, 64)
data := make([]uint16, 64)
for {
// get the next group of samples
machine.I2S0.Read(data)
machine.I2S0.ReadMono(data)
println("data", data[0], data[1], data[2], data[4], "...")
}
+10
View File
@@ -71,6 +71,16 @@ func (r *result[Shape, OK, Err]) Err() *Err {
return (*Err)(unsafe.Pointer(&r.data))
}
// Result returns (OK, zero value of Err, false) if r represents the OK case,
// or (zero value of OK, Err, true) if r represents the error case.
// This does not have a pointer receiver, so it can be chained.
func (r result[Shape, OK, Err]) Result() (ok OK, err Err, isErr bool) {
if r.isErr {
return ok, *(*Err)(unsafe.Pointer(&r.data)), true
}
return *(*OK)(unsafe.Pointer(&r.data)), err, false
}
// This function is sized so it can be inlined and optimized away.
func (r *result[Shape, OK, Err]) validate() {
var shape Shape
+72
View File
@@ -0,0 +1,72 @@
package futex
// Cross platform futex implementation.
// Futexes are supported on all major operating systems and on WebAssembly.
//
// For more information, see: https://outerproduct.net/futex-dictionary.html
import (
"sync/atomic"
"unsafe"
)
// A futex is a way for userspace to wait with the pointer as the key, and for
// another thread to wake one or all waiting threads keyed on the same pointer.
//
// A futex does not change the underlying value, it only reads it before going
// to sleep (atomically) to prevent lost wake-ups.
type Futex struct {
atomic.Uint32
}
// Atomically check for cmp to still be equal to the futex value and if so, go
// to sleep. Return true if we were definitely awoken by a call to Wake or
// WakeAll, and false if we can't be sure of that.
func (f *Futex) Wait(cmp uint32) bool {
tinygo_futex_wait((*uint32)(unsafe.Pointer(&f.Uint32)), cmp)
// We *could* detect a zero return value from the futex system call which
// would indicate we got awoken by a Wake or WakeAll call. However, this is
// what the manual page has to say:
//
// > Note that a wake-up can also be caused by common futex usage patterns
// > in unrelated code that happened to have previously used the futex
// > word's memory location (e.g., typical futex-based implementations of
// > Pthreads mutexes can cause this under some conditions). Therefore,
// > callers should always conservatively assume that a return value of 0
// > can mean a spurious wake-up, and use the futex word's value (i.e., the
// > user-space synchronization scheme) to decide whether to continue to
// > block or not.
//
// I'm not sure whether we do anything like pthread does, so to be on the
// safe side we say we don't know whether the wakeup was spurious or not and
// return false.
return false
}
// Like Wait, but times out after the number of nanoseconds in timeout.
func (f *Futex) WaitUntil(cmp uint32, timeout uint64) {
tinygo_futex_wait_timeout((*uint32)(unsafe.Pointer(&f.Uint32)), cmp, timeout)
}
// Wake a single waiter.
func (f *Futex) Wake() {
tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32)))
}
// Wake all waiters.
func (f *Futex) WakeAll() {
tinygo_futex_wake_all((*uint32)(unsafe.Pointer(&f.Uint32)))
}
//export tinygo_futex_wait
func tinygo_futex_wait(addr *uint32, cmp uint32)
//export tinygo_futex_wait_timeout
func tinygo_futex_wait_timeout(addr *uint32, cmp uint32, timeout uint64)
//export tinygo_futex_wake
func tinygo_futex_wake(addr *uint32)
//export tinygo_futex_wake_all
func tinygo_futex_wake_all(addr *uint32)
+49
View File
@@ -0,0 +1,49 @@
//go:build none
// This file is manually included, to avoid CGo which would cause a circular
// import.
#include <stdint.h>
// This API isn't documented by Apple, but it is used by LLVM libc++ (so should
// be stable) and has been documented extensively here:
// https://outerproduct.net/futex-dictionary.html
int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout_us);
int __ulock_wait2(uint32_t operation, void *addr, uint64_t value, uint64_t timeout_ns, uint64_t value2);
int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value);
// Operation code.
#define UL_COMPARE_AND_WAIT 1
// Flags to the operation value.
#define ULF_WAKE_ALL 0x00000100
#define ULF_NO_ERRNO 0x01000000
void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) {
__ulock_wait(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, 0);
}
void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) {
// Make sure that an accidental use of a zero timeout is not treated as an
// infinite timeout. Return if it's zero since it wouldn't be waiting for
// any significant time anyway.
// Probably unnecessary, but guards against potential bugs.
if (timeout == 0) {
return;
}
// Note: __ulock_wait2 is available since MacOS 11.
// I think that's fine, since the version before that (MacOS 10.15) is EOL
// since 2022. Though if needed, we could certainly use __ulock_wait instead
// and deal with the smaller timeout value.
__ulock_wait2(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, timeout, 0);
}
void tinygo_futex_wake(uint32_t *addr) {
__ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, 0);
}
void tinygo_futex_wake_all(uint32_t *addr) {
__ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO|ULF_WAKE_ALL, addr, 0);
}
+33
View File
@@ -0,0 +1,33 @@
//go:build none
// This file is manually included, to avoid CGo which would cause a circular
// import.
#include <limits.h>
#include <stdint.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
#define FUTEX_WAIT 0
#define FUTEX_WAKE 1
#define FUTEX_PRIVATE_FLAG 128
void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) {
syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, NULL, NULL, 0);
}
void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) {
struct timespec ts = {0};
ts.tv_sec = timeout / 1000000000;
ts.tv_nsec = timeout % 1000000000;
syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, &ts, NULL, 0);
}
void tinygo_futex_wake(uint32_t *addr) {
syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0);
}
void tinygo_futex_wake_all(uint32_t *addr) {
syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, INT_MAX, NULL, NULL, 0);
}
+41
View File
@@ -0,0 +1,41 @@
package task
// Atomics implementation for cooperative systems. The atomic types here aren't
// actually atomic, they assume that accesses cannot be interrupted by a
// different goroutine or interrupt happening at the same time.
type atomicIntegerType interface {
uintptr | uint32 | uint64
}
type pseudoAtomic[T atomicIntegerType] struct {
v T
}
func (x *pseudoAtomic[T]) Add(delta T) T { x.v += delta; return x.v }
func (x *pseudoAtomic[T]) Load() T { return x.v }
func (x *pseudoAtomic[T]) Store(val T) { x.v = val }
func (x *pseudoAtomic[T]) CompareAndSwap(old, new T) (swapped bool) {
if x.v != old {
return false
}
x.v = new
return true
}
func (x *pseudoAtomic[T]) Swap(new T) (old T) {
old = x.v
x.v = new
return
}
// Uintptr is an atomic uintptr when multithreading is enabled, and a plain old
// uintptr otherwise.
type Uintptr = pseudoAtomic[uintptr]
// Uint32 is an atomic uint32 when multithreading is enabled, and a plain old
// uint32 otherwise.
type Uint32 = pseudoAtomic[uint32]
// Uint64 is an atomic uint64 when multithreading is enabled, and a plain old
// uint64 otherwise.
type Uint64 = pseudoAtomic[uint64]
+44
View File
@@ -0,0 +1,44 @@
package task
// A futex is a way for userspace to wait with the pointer as the key, and for
// another thread to wake one or all waiting threads keyed on the same pointer.
//
// A futex does not change the underlying value, it only reads it before to prevent
// lost wake-ups.
type Futex struct {
Uint32
waiters Stack
}
// Atomically check for cmp to still be equal to the futex value and if so, go
// to sleep. Return true if we were definitely awoken by a call to Wake or
// WakeAll, and false if we can't be sure of that.
func (f *Futex) Wait(cmp uint32) (awoken bool) {
if f.Uint32.v != cmp {
return false
}
// Push the current goroutine onto the waiter stack.
f.waiters.Push(Current())
// Pause until the waiters are awoken by Wake/WakeAll.
Pause()
// We were awoken by a call to Wake or WakeAll. There is no chance for
// spurious wakeups.
return true
}
// Wake a single waiter.
func (f *Futex) Wake() {
if t := f.waiters.Pop(); t != nil {
scheduleTask(t)
}
}
// Wake all waiters.
func (f *Futex) WakeAll() {
for t := f.waiters.Pop(); t != nil; t = f.waiters.Pop() {
scheduleTask(t)
}
}
+16
View File
@@ -0,0 +1,16 @@
package task
// PMutex is a real mutex on systems that can be either preemptive or threaded,
// and a dummy lock on other (purely cooperative) systems.
//
// It is mainly useful for short operations that need a lock when threading may
// be involved, but which do not need a lock with a purely cooperative
// scheduler.
type PMutex struct {
}
func (m *PMutex) Lock() {
}
func (m *PMutex) Unlock() {
}
+20
View File
@@ -26,6 +26,23 @@ type Task struct {
DeferFrame unsafe.Pointer
}
// DataUint32 returns the Data field as a uint32. The value is only valid after
// setting it through SetDataUint32 or by storing to it using DataAtomicUint32.
func (t *Task) DataUint32() uint32 {
return *(*uint32)(unsafe.Pointer(&t.Data))
}
// SetDataUint32 updates the uint32 portion of the Data field (which could be
// the first 4 or last 4 bytes depending on the architecture endianness).
func (t *Task) SetDataUint32(val uint32) {
*(*uint32)(unsafe.Pointer(&t.Data)) = val
}
// DataAtomicUint32 returns the Data field as an atomic-if-needed Uint32 value.
func (t *Task) DataAtomicUint32() *Uint32 {
return (*Uint32)(unsafe.Pointer(&t.Data))
}
// getGoroutineStackSize is a compiler intrinsic that returns the stack size for
// the given function and falls back to the default stack size. It is replaced
// with a load from a special section just before codegen.
@@ -33,3 +50,6 @@ func getGoroutineStackSize(fn uintptr) uintptr
//go:linkname runtime_alloc runtime.alloc
func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
//go:linkname scheduleTask runtime.scheduleTask
func scheduleTask(*Task)
+1 -4
View File
@@ -52,7 +52,7 @@ type stackState struct {
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
t := &Task{}
t.state.initialize(fn, args, stackSize)
runqueuePushBack(t)
scheduleTask(t)
}
//export tinygo_launch
@@ -82,9 +82,6 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
s.csp = unsafe.Add(stack, stackSize)
}
//go:linkname runqueuePushBack runtime.runqueuePushBack
func runqueuePushBack(*Task)
// currentTask is the current running task, or nil if currently in the scheduler.
var currentTask *Task
+1 -4
View File
@@ -101,15 +101,12 @@ func swapTask(oldStack uintptr, newStack *uintptr)
//go:extern tinygo_startTask
var startTask [0]uint8
//go:linkname runqueuePushBack runtime.runqueuePushBack
func runqueuePushBack(*Task)
// start creates and starts a new goroutine with the given function and arguments.
// The new goroutine is scheduled to run later.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
t := &Task{}
t.state.initialize(fn, args, stackSize)
runqueuePushBack(t)
scheduleTask(t)
}
// OnSystemStack returns whether the caller is running on the system stack.
+4
View File
@@ -1,3 +1,7 @@
// Do not reorder instructions to insert a branch delay slot.
// We know what we're doing, and will manually fill the branch delay slot.
.set noreorder
.section .text.tinygo_startTask
.global tinygo_startTask
.type tinygo_startTask, %function
+3 -2
View File
@@ -74,8 +74,9 @@ const (
// I2S pins
const (
I2S_SCK_PIN Pin = PA10
I2S_SD_PIN Pin = PA07
I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33.
I2S_SDO_PIN Pin = PA07
I2S_SDI_PIN = NoPin
I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR1000
)
// USB CDC identifiers
+2 -1
View File
@@ -74,7 +74,8 @@ const (
// I2S pins
const (
I2S_SCK_PIN Pin = PA10
I2S_SD_PIN Pin = PA07
I2S_SDO_PIN Pin = PA07
I2S_SDI_PIN = NoPin
I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR WiFi 1010.
)
+2 -1
View File
@@ -118,7 +118,8 @@ const (
// I2S pins
const (
I2S_SCK_PIN Pin = PA10
I2S_SD_PIN Pin = PA08
I2S_SDO_PIN Pin = PA08
I2S_SDI_PIN = NoPin
I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33.
)
+2 -1
View File
@@ -69,7 +69,8 @@ const (
// I2S pins - might not be exposed
const (
I2S_SCK_PIN Pin = PA10
I2S_SD_PIN Pin = PA07
I2S_SDO_PIN Pin = PA07
I2S_SDI_PIN = NoPin
I2S_WS_PIN Pin = PA11
)
+2 -1
View File
@@ -102,7 +102,8 @@ var SPI0 = sercomSPIM3
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_SDO_PIN = PA08
I2S_SDI_PIN = NoPin
I2S_WS_PIN = NoPin // no WS, instead uses SCK to sync
)
+2 -1
View File
@@ -81,7 +81,8 @@ var SPI0 = sercomSPIM4
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA07
I2S_SDO_PIN = PA07
I2S_SDI_PIN = NoPin
I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0 Express.
)
+2 -1
View File
@@ -76,7 +76,8 @@ var SPI0 = sercomSPIM4
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_SDO_PIN = PA08
I2S_SDI_PIN = NoPin
I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0.
)
+2 -1
View File
@@ -76,7 +76,8 @@ var (
// I2S (not connected, needed for atsamd21).
const (
I2S_SCK_PIN = NoPin
I2S_SD_PIN = NoPin
I2S_SDO_PIN = NoPin
I2S_SDI_PIN = NoPin
I2S_WS_PIN = NoPin
)
+2 -1
View File
@@ -224,7 +224,8 @@ const (
I2S_SCK_PIN = I2S0_SCK_PIN // default pins
I2S_WS_PIN = I2S0_FS_PIN //
I2S_SD_PIN = I2S0_SDO_PIN //
I2S_SDO_PIN = I2S0_SDO_PIN
I2S_SDI_PIN = NoPin
)
// SD card pins
+2 -1
View File
@@ -89,7 +89,8 @@ var SPI1 = sercomSPIM5
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_SDO_PIN = PA08
I2S_SDI_PIN = NoPin
I2S_WS_PIN = NoPin // TODO: figure out what this is on ItsyBitsy M0.
)
+2 -1
View File
@@ -123,7 +123,8 @@ var (
// I2S pins
const (
I2S_SCK_PIN Pin = D2
I2S_SD_PIN Pin = A6
I2S_SDO_PIN Pin = A6
I2S_SDI_PIN = NoPin
I2S_WS_PIN = D3
)
+88
View File
@@ -0,0 +1,88 @@
//go:build pico2
package machine
// GPIO pins
const (
GP0 Pin = GPIO0
GP1 Pin = GPIO1
GP2 Pin = GPIO2
GP3 Pin = GPIO3
GP4 Pin = GPIO4
GP5 Pin = GPIO5
GP6 Pin = GPIO6
GP7 Pin = GPIO7
GP8 Pin = GPIO8
GP9 Pin = GPIO9
GP10 Pin = GPIO10
GP11 Pin = GPIO11
GP12 Pin = GPIO12
GP13 Pin = GPIO13
GP14 Pin = GPIO14
GP15 Pin = GPIO15
GP16 Pin = GPIO16
GP17 Pin = GPIO17
GP18 Pin = GPIO18
GP19 Pin = GPIO19
GP20 Pin = GPIO20
GP21 Pin = GPIO21
GP22 Pin = GPIO22
GP26 Pin = GPIO26
GP27 Pin = GPIO27
GP28 Pin = GPIO28
// Onboard LED
LED Pin = GPIO25
// Onboard crystal oscillator frequency, in MHz.
xoscFreq = 12 // MHz
)
// I2C Default pins on Raspberry Pico.
const (
I2C0_SDA_PIN = GP4
I2C0_SCL_PIN = GP5
I2C1_SDA_PIN = GP2
I2C1_SCL_PIN = GP3
)
// SPI default pins
const (
// Default Serial Clock Bus 0 for SPI communications
SPI0_SCK_PIN = GPIO18
// Default Serial Out Bus 0 for SPI communications
SPI0_SDO_PIN = GPIO19 // Tx
// Default Serial In Bus 0 for SPI communications
SPI0_SDI_PIN = GPIO16 // Rx
// Default Serial Clock Bus 1 for SPI communications
SPI1_SCK_PIN = GPIO10
// Default Serial Out Bus 1 for SPI communications
SPI1_SDO_PIN = GPIO11 // Tx
// Default Serial In Bus 1 for SPI communications
SPI1_SDI_PIN = GPIO12 // Rx
)
// UART pins
const (
UART0_TX_PIN = GPIO0
UART0_RX_PIN = GPIO1
UART1_TX_PIN = GPIO8
UART1_RX_PIN = GPIO9
UART_TX_PIN = UART0_TX_PIN
UART_RX_PIN = UART0_RX_PIN
)
var DefaultUART = UART0
// USB identifiers
const (
usb_STRING_PRODUCT = "Pico2"
usb_STRING_MANUFACTURER = "Raspberry Pi"
)
var (
usb_VID uint16 = 0x2E8A
usb_PID uint16 = 0x000A
)
+2 -1
View File
@@ -81,7 +81,8 @@ var (
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_SDO_PIN = PA08
I2S_SDI_PIN = NoPin // TODO: figure out what this is on QT Py M0.
I2S_WS_PIN = NoPin // TODO: figure out what this is on QT Py M0.
)
+82
View File
@@ -0,0 +1,82 @@
//go:build tiny2350
package machine
// GPIO pins
const (
GP0 Pin = GPIO0
GP1 Pin = GPIO1
GP2 Pin = GPIO2
GP3 Pin = GPIO3
GP4 Pin = GPIO4
GP5 Pin = GPIO5
GP6 Pin = GPIO6
GP7 Pin = GPIO7
GP12 Pin = GPIO12
GP13 Pin = GPIO13
GP18 Pin = GPIO18
GP19 Pin = GPIO19
GP20 Pin = GPIO20
GP26 Pin = GPIO26
GP27 Pin = GPIO27
GP28 Pin = GPIO28
GP29 Pin = GPIO29
// Onboard LED
LED_RED Pin = GPIO18
LED_GREEN Pin = GPIO19
LED_BLUE Pin = GPIO20
LED = LED_RED
// Onboard crystal oscillator frequency, in MHz.
xoscFreq = 12 // MHz
)
// I2C Default pins on Tiny2350.
const (
I2C0_SDA_PIN = GP12
I2C0_SCL_PIN = GP13
I2C1_SDA_PIN = GP2
I2C1_SCL_PIN = GP3
)
// SPI default pins
const (
// Default Serial Clock Bus 0 for SPI communications
SPI0_SCK_PIN = GPIO6
// Default Serial Out Bus 0 for SPI communications
SPI0_SDO_PIN = GPIO7 // Tx
// Default Serial In Bus 0 for SPI communications
SPI0_SDI_PIN = GPIO4 // Rx
// Default Serial Clock Bus 1 for SPI communications
SPI1_SCK_PIN = GPIO26
// Default Serial Out Bus 1 for SPI communications
SPI1_SDO_PIN = GPIO27 // Tx
// Default Serial In Bus 1 for SPI communications
SPI1_SDI_PIN = GPIO28 // Rx
)
// UART pins
const (
UART0_TX_PIN = GPIO0
UART0_RX_PIN = GPIO1
UART1_TX_PIN = GPIO4
UART1_RX_PIN = GPIO5
UART_TX_PIN = UART0_TX_PIN
UART_RX_PIN = UART0_RX_PIN
)
var DefaultUART = UART0
// USB identifiers
const (
usb_STRING_PRODUCT = "Tiny2350"
usb_STRING_MANUFACTURER = "Pimoroni"
)
var (
usb_VID uint16 = 0x2E8A
usb_PID uint16 = 0x000F
)
+2 -1
View File
@@ -67,7 +67,8 @@ var (
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_SDO_PIN = PA08
I2S_SDI_PIN = NoPin // TODO: figure out what this is on Trinket M0.
I2S_WS_PIN = NoPin // TODO: figure out what this is on Trinket M0.
)
+8
View File
@@ -376,6 +376,14 @@ var (
I2C1 = sercomI2CM3
)
// I2S pins
const (
I2S_SCK_PIN = BCM18
I2S_SDO_PIN = BCM21
I2S_SDI_PIN = BCM20
I2S_WS_PIN = BCM19
)
// SPI pins
const (
SPI0_SCK_PIN = SCK // SCK: SERCOM5/PAD[1]
+2 -1
View File
@@ -82,7 +82,8 @@ var SPI0 = sercomSPIM0
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_SDO_PIN = PA08
I2S_SDI_PIN = NoPin // TODO: figure out what this is on Xiao
I2S_WS_PIN = NoPin // TODO: figure out what this is on Xiao
)
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32
//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32
package machine
+30 -4
View File
@@ -1,4 +1,4 @@
//go:build sam
//go:build sam && atsamd21
// This is the definition for I2S bus functions.
// Actual implementations if available for any given hardware
@@ -9,6 +9,22 @@
package machine
import "errors"
// If you are getting a compile error on this line please check to see you've
// correctly implemented the methods on the I2S type. They must match
// the interface method signatures type to type perfectly.
// If not implementing the I2S type please remove your target from the build tags
// at the top of this file.
var _ interface {
SetSampleFrequency(freq uint32) error
ReadMono(b []uint16) (int, error)
ReadStereo(b []uint32) (int, error)
WriteMono(b []uint16) (int, error)
WriteStereo(b []uint32) (int, error)
Enable(enabled bool)
} = (*I2S)(nil)
type I2SMode uint8
type I2SStandard uint8
type I2SClockSource uint8
@@ -18,6 +34,7 @@ const (
I2SModeSource I2SMode = iota
I2SModeReceiver
I2SModePDM
I2SModeSourceReceiver
)
const (
@@ -39,11 +56,20 @@ const (
I2SDataFormat32bit = 32
)
var (
ErrInvalidSampleFrequency = errors.New("i2s: invalid sample frequency")
)
// All fields are optional and may not be required or used on a particular platform.
type I2SConfig struct {
SCK Pin
WS Pin
SD Pin
// clock
SCK Pin
// word select
WS Pin
// data out
SDO Pin
// data in
SDI Pin
Mode I2SMode
Standard I2SStandard
ClockSource I2SClockSource
+104 -49
View File
@@ -926,23 +926,26 @@ func (i2c *I2C) readByte() byte {
// I2S
type I2S struct {
Bus *sam.I2S_Type
Bus *sam.I2S_Type
Frequency uint32
DataFormat I2SDataFormat
}
var I2S0 = I2S{Bus: sam.I2S}
// Configure is used to configure the I2S interface. You must call this
// before you can use the I2S bus.
func (i2s I2S) Configure(config I2SConfig) {
func (i2s *I2S) Configure(config I2SConfig) error {
// handle defaults
if config.SCK == 0 {
config.SCK = I2S_SCK_PIN
config.WS = I2S_WS_PIN
config.SD = I2S_SD_PIN
config.SDO = I2S_SDO_PIN
config.SDI = I2S_SDI_PIN
}
if config.AudioFrequency == 0 {
config.AudioFrequency = 48000
config.AudioFrequency = 44100
}
if config.DataFormat == I2SDataFormatDefault {
@@ -952,39 +955,17 @@ func (i2s I2S) Configure(config I2SConfig) {
config.DataFormat = I2SDataFormat32bit
}
}
i2s.DataFormat = config.DataFormat
// Turn on clock for I2S
sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_I2S_)
// setting clock rate for sample.
division_factor := CPUFrequency() / (config.AudioFrequency * uint32(config.DataFormat))
// Switch Generic Clock Generator 3 to DFLL48M.
sam.GCLK.GENDIV.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENDIV_ID_Pos) |
(division_factor << sam.GCLK_GENDIV_DIV_Pos))
waitForSync()
sam.GCLK.GENCTRL.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENCTRL_ID_Pos) |
(sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_IDC |
sam.GCLK_GENCTRL_GENEN)
waitForSync()
// Use Generic Clock Generator 3 as source for I2S.
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_I2S_0 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
waitForSync()
// reset the device
i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_SWRST)
for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_SWRST) {
if err := i2s.SetSampleFrequency(config.AudioFrequency); err != nil {
return err
}
// disable device before continuing
for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) {
}
i2s.Bus.CTRLA.ClearBits(sam.I2S_CTRLA_ENABLE)
i2s.Enable(false)
// setup clock
if config.ClockSource == I2SClockSourceInternal {
@@ -1067,19 +1048,25 @@ func (i2s I2S) Configure(config I2SConfig) {
}
// set serializer mode.
if config.Mode == I2SModePDM {
switch config.Mode {
case I2SModePDM:
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_SERMODE_PDM2)
} else {
case I2SModeSource:
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_SERMODE_TX)
case I2SModeReceiver:
i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_SERMODE_RX)
}
// configure data pin
config.SD.Configure(PinConfig{Mode: PinCom})
// configure data pins
if config.SDO != NoPin {
config.SDO.Configure(PinConfig{Mode: PinCom})
}
if config.SDI != NoPin {
config.SDI.Configure(PinConfig{Mode: PinCom})
}
// re-enable
i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_ENABLE)
for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) {
}
i2s.Enable(true)
// enable i2s clock
i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_CKEN0)
@@ -1090,11 +1077,23 @@ func (i2s I2S) Configure(config I2SConfig) {
i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_SEREN1)
for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_SEREN1) {
}
return nil
}
// Read data from the I2S bus into the provided slice.
// Read mono data from the I2S bus into the provided slice.
// The I2S bus must already have been configured correctly.
func (i2s I2S) Read(p []uint32) (n int, err error) {
func (i2s *I2S) ReadMono(p []uint16) (n int, err error) {
return i2sRead(i2s, p)
}
// Read stereo data from the I2S bus into the provided slice.
// The I2S bus must already have been configured correctly.
func (i2s *I2S) ReadStereo(p []uint32) (n int, err error) {
return i2sRead(i2s, p)
}
func i2sRead[T uint16 | uint32](i2s *I2S, p []T) (int, error) {
i := 0
for i = 0; i < len(p); i++ {
// Wait until ready
@@ -1105,7 +1104,7 @@ func (i2s I2S) Read(p []uint32) (n int, err error) {
}
// read data
p[i] = i2s.Bus.DATA1.Get()
p[i] = T(i2s.Bus.DATA1.Get())
// indicate read complete
i2s.Bus.INTFLAG.Set(sam.I2S_INTFLAG_RXRDY1)
@@ -1114,9 +1113,19 @@ func (i2s I2S) Read(p []uint32) (n int, err error) {
return i, nil
}
// Write data to the I2S bus from the provided slice.
// Write mono data to the I2S bus from the provided slice.
// The I2S bus must already have been configured correctly.
func (i2s I2S) Write(p []uint32) (n int, err error) {
func (i2s *I2S) WriteMono(p []uint16) (n int, err error) {
return i2sWrite(i2s, p)
}
// Write stereo data to the I2S bus from the provided slice.
// The I2S bus must already have been configured correctly.
func (i2s *I2S) WriteStereo(p []uint32) (n int, err error) {
return i2sWrite(i2s, p)
}
func i2sWrite[T uint16 | uint32](i2s *I2S, p []T) (int, error) {
i := 0
for i = 0; i < len(p); i++ {
// Wait until ready
@@ -1127,7 +1136,7 @@ func (i2s I2S) Write(p []uint32) (n int, err error) {
}
// write data
i2s.Bus.DATA1.Set(p[i])
i2s.Bus.DATA1.Set(uint32(p[i]))
// indicate write complete
i2s.Bus.INTFLAG.Set(sam.I2S_INTFLAG_TXRDY1)
@@ -1136,18 +1145,64 @@ func (i2s I2S) Write(p []uint32) (n int, err error) {
return i, nil
}
// Close the I2S bus.
func (i2s I2S) Close() error {
// Sync wait
for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) {
// SetSampleFrequency is used to set the sample frequency for the I2S bus.
func (i2s *I2S) SetSampleFrequency(freq uint32) error {
if freq == 0 {
return ErrInvalidSampleFrequency
}
// disable I2S
i2s.Bus.CTRLA.ClearBits(sam.I2S_CTRLA_ENABLE)
if i2s.Frequency == freq {
return nil
}
i2s.Frequency = freq
// setting clock rate for sample.
division_factor := CPUFrequency() / (i2s.Frequency * uint32(i2s.DataFormat))
// Switch Generic Clock Generator 3 to DFLL48M.
sam.GCLK.GENDIV.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENDIV_ID_Pos) |
(division_factor << sam.GCLK_GENDIV_DIV_Pos))
waitForSync()
sam.GCLK.GENCTRL.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENCTRL_ID_Pos) |
(sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) |
sam.GCLK_GENCTRL_IDC |
sam.GCLK_GENCTRL_GENEN)
waitForSync()
// Use Generic Clock Generator 3 as source for I2S.
sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_I2S_0 << sam.GCLK_CLKCTRL_ID_Pos) |
(sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_CLKCTRL_GEN_Pos) |
sam.GCLK_CLKCTRL_CLKEN)
waitForSync()
// reset the device
i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_SWRST)
for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_SWRST) {
}
return nil
}
// Enabled is used to enable or disable the I2S bus.
func (i2s *I2S) Enable(enabled bool) {
if enabled {
i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_ENABLE)
for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) {
}
return
}
// disable
for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) {
}
i2s.Bus.CTRLA.ClearBits(sam.I2S_CTRLA_ENABLE)
return
}
func waitForSync() {
for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) {
}
@@ -1,38 +1,55 @@
//go:build rp2040
//go:build rp2040 || rp2350
package machine
import (
"device/rp"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
)
const deviceName = rp.Device
const (
// Number of spin locks available
// Note: On RP2350, most spinlocks are unusable due to Errata 2
_NUMSPINLOCKS = 32
_PICO_SPINLOCK_ID_IRQ = 9
)
// UART on the RP2040
var (
UART0 = &_UART0
_UART0 = UART{
Buffer: NewRingBuffer(),
Bus: rp.UART0,
}
UART1 = &_UART1
_UART1 = UART{
Buffer: NewRingBuffer(),
Bus: rp.UART1,
}
)
func init() {
UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt)
UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt)
}
//go:linkname machineInit runtime.machineInit
func machineInit() {
// Reset all peripherals to put system into a known state,
// except for QSPI pads and the XIP IO bank, as this is fatal if running from flash
// and the PLLs, as this is fatal if clock muxing has not been reset on this boot
// and USB, syscfg, as this disturbs USB-to-SWD on core 1
bits := ^uint32(rp.RESETS_RESET_IO_QSPI |
rp.RESETS_RESET_PADS_QSPI |
rp.RESETS_RESET_PLL_USB |
rp.RESETS_RESET_USBCTRL |
rp.RESETS_RESET_SYSCFG |
rp.RESETS_RESET_PLL_SYS)
bits := ^uint32(initDontReset)
resetBlock(bits)
// Remove reset from peripherals which are clocked only by clkSys and
// clkRef. Other peripherals stay in reset until we've configured clocks.
bits = ^uint32(rp.RESETS_RESET_ADC |
rp.RESETS_RESET_RTC |
rp.RESETS_RESET_SPI0 |
rp.RESETS_RESET_SPI1 |
rp.RESETS_RESET_UART0 |
rp.RESETS_RESET_UART1 |
rp.RESETS_RESET_USBCTRL)
bits = ^uint32(initUnreset)
unresetBlockWait(bits)
clocks.init()
@@ -94,4 +111,25 @@ const (
)
// DMA channels usable on the RP2040.
var dmaChannels = (*[12]dmaChannel)(unsafe.Pointer(rp.DMA))
var dmaChannels = (*[12 + 4*rp2350ExtraReg]dmaChannel)(unsafe.Pointer(rp.DMA))
//go:inline
func boolToBit(a bool) uint32 {
if a {
return 1
}
return 0
}
//go:inline
func u32max(a, b uint32) uint32 {
if a > b {
return a
}
return b
}
//go:inline
func isReservedI2CAddr(addr uint8) bool {
return (addr&0x78) == 0 || (addr&0x78) == 0x78
}
+1 -1
View File
@@ -97,7 +97,7 @@ func toAlarmTime(delay uint32) rtcTime {
func (rtc *rtcType) setDivider() {
// Get clk_rtc freq and make sure it is running
rtcFreq := configuredFreq[clkRTC]
rtcFreq := configuredFreq[ClkRTC]
if rtcFreq == 0 {
panic("can not set RTC divider, clock is not running")
}
+153
View File
@@ -0,0 +1,153 @@
//go:build tinygo && rp2350
package machine
import ()
/*
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned long uint32_t;
typedef unsigned long size_t;
typedef unsigned long uintptr_t;
#define false 0
#define true 1
typedef int bool;
// https://github.com/raspberrypi/pico-sdk
// src/rp2_common/pico_platform_compiler/include/pico/platform/compiler.h
#define pico_default_asm_volatile(...) __asm volatile (".syntax unified\n" __VA_ARGS__)
// https://github.com/raspberrypi/pico-sdk
// src/rp2350/pico_platform/include/pico/platform.h
static bool pico_processor_state_is_nonsecure(void) {
// // todo add a define to disable NS checking at all?
// // IDAU-Exempt addresses return S=1 when tested in the Secure state,
// // whereas executing a tt in the NonSecure state will always return S=0.
// uint32_t tt;
// pico_default_asm_volatile (
// "movs %0, #0\n"
// "tt %0, %0\n"
// : "=r" (tt) : : "cc"
// );
// return !(tt & (1u << 22));
return false;
}
// https://github.com/raspberrypi/pico-sdk
// src/rp2_common/pico_bootrom/include/pico/bootrom_constants.h
// RP2040 & RP2350
#define ROM_DATA_SOFTWARE_GIT_REVISION ROM_TABLE_CODE('G', 'R')
#define ROM_FUNC_FLASH_ENTER_CMD_XIP ROM_TABLE_CODE('C', 'X')
#define ROM_FUNC_FLASH_EXIT_XIP ROM_TABLE_CODE('E', 'X')
#define ROM_FUNC_FLASH_FLUSH_CACHE ROM_TABLE_CODE('F', 'C')
#define ROM_FUNC_CONNECT_INTERNAL_FLASH ROM_TABLE_CODE('I', 'F')
#define ROM_FUNC_FLASH_RANGE_ERASE ROM_TABLE_CODE('R', 'E')
#define ROM_FUNC_FLASH_RANGE_PROGRAM ROM_TABLE_CODE('R', 'P')
// RP2350 only
#define ROM_FUNC_PICK_AB_PARTITION ROM_TABLE_CODE('A', 'B')
#define ROM_FUNC_CHAIN_IMAGE ROM_TABLE_CODE('C', 'I')
#define ROM_FUNC_EXPLICIT_BUY ROM_TABLE_CODE('E', 'B')
#define ROM_FUNC_FLASH_RUNTIME_TO_STORAGE_ADDR ROM_TABLE_CODE('F', 'A')
#define ROM_DATA_FLASH_DEVINFO16_PTR ROM_TABLE_CODE('F', 'D')
#define ROM_FUNC_FLASH_OP ROM_TABLE_CODE('F', 'O')
#define ROM_FUNC_GET_B_PARTITION ROM_TABLE_CODE('G', 'B')
#define ROM_FUNC_GET_PARTITION_TABLE_INFO ROM_TABLE_CODE('G', 'P')
#define ROM_FUNC_GET_SYS_INFO ROM_TABLE_CODE('G', 'S')
#define ROM_FUNC_GET_UF2_TARGET_PARTITION ROM_TABLE_CODE('G', 'U')
#define ROM_FUNC_LOAD_PARTITION_TABLE ROM_TABLE_CODE('L', 'P')
#define ROM_FUNC_OTP_ACCESS ROM_TABLE_CODE('O', 'A')
#define ROM_DATA_PARTITION_TABLE_PTR ROM_TABLE_CODE('P', 'T')
#define ROM_FUNC_FLASH_RESET_ADDRESS_TRANS ROM_TABLE_CODE('R', 'A')
#define ROM_FUNC_REBOOT ROM_TABLE_CODE('R', 'B')
#define ROM_FUNC_SET_ROM_CALLBACK ROM_TABLE_CODE('R', 'C')
#define ROM_FUNC_SECURE_CALL ROM_TABLE_CODE('S', 'C')
#define ROM_FUNC_SET_NS_API_PERMISSION ROM_TABLE_CODE('S', 'P')
#define ROM_FUNC_BOOTROM_STATE_RESET ROM_TABLE_CODE('S', 'R')
#define ROM_FUNC_SET_BOOTROM_STACK ROM_TABLE_CODE('S', 'S')
#define ROM_DATA_SAVED_XIP_SETUP_FUNC_PTR ROM_TABLE_CODE('X', 'F')
#define ROM_FUNC_FLASH_SELECT_XIP_READ_MODE ROM_TABLE_CODE('X', 'M')
#define ROM_FUNC_VALIDATE_NS_BUFFER ROM_TABLE_CODE('V', 'B')
#define BOOTSEL_FLAG_GPIO_PIN_SPECIFIED 0x20
#define BOOTROM_FUNC_TABLE_OFFSET 0x14
// todo remove this (or #ifdef it for A1/A2)
#define BOOTROM_IS_A2() ((*(volatile uint8_t *)0x13) == 2)
#define BOOTROM_WELL_KNOWN_PTR_SIZE (BOOTROM_IS_A2() ? 2 : 4)
#define BOOTROM_VTABLE_OFFSET 0x00
#define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE)
// https://github.com/raspberrypi/pico-sdk
// src/common/boot_picoboot_headers/include/boot/picoboot_constants.h
// values 0-7 are secure/non-secure
#define REBOOT2_FLAG_REBOOT_TYPE_NORMAL 0x0 // param0 = diagnostic partition
#define REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL 0x2 // param0 = bootsel_flags, param1 = gpio_config
#define REBOOT2_FLAG_REBOOT_TYPE_RAM_IMAGE 0x3 // param0 = image_base, param1 = image_end
#define REBOOT2_FLAG_REBOOT_TYPE_FLASH_UPDATE 0x4 // param0 = update_base
#define REBOOT2_FLAG_NO_RETURN_ON_SUCCESS 0x100
#define RT_FLAG_FUNC_ARM_SEC 0x0004
#define RT_FLAG_FUNC_ARM_NONSEC 0x0010
// https://github.com/raspberrypi/pico-sdk
// src/rp2_common/pico_bootrom/include/pico/bootrom.h
#define ROM_TABLE_CODE(c1, c2) ((c1) | ((c2) << 8))
typedef void *(*rom_table_lookup_fn)(uint32_t code, uint32_t mask);
__attribute__((always_inline))
static void *rom_func_lookup_inline(uint32_t code) {
rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) (uintptr_t)*(uint16_t*)(BOOTROM_TABLE_LOOKUP_OFFSET);
if (pico_processor_state_is_nonsecure()) {
return rom_table_lookup(code, RT_FLAG_FUNC_ARM_NONSEC);
} else {
return rom_table_lookup(code, RT_FLAG_FUNC_ARM_SEC);
}
}
typedef int (*rom_reboot_fn)(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1);
__attribute__((always_inline))
int rom_reboot(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1) {
rom_reboot_fn func = (rom_reboot_fn) rom_func_lookup_inline(ROM_FUNC_REBOOT);
return func(flags, delay_ms, p0, p1);
}
// https://github.com/raspberrypi/pico-sdk
// src/rp2_common/pico_bootrom/bootrom.c
void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interface_mask) {
uint32_t flags = disable_interface_mask;
if (usb_activity_gpio_pin_mask) {
flags |= BOOTSEL_FLAG_GPIO_PIN_SPECIFIED;
// the parameter is actually the gpio number, but we only care if BOOTSEL_FLAG_GPIO_PIN_SPECIFIED
usb_activity_gpio_pin_mask = (uint32_t)__builtin_ctz(usb_activity_gpio_pin_mask);
}
rom_reboot(REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL | REBOOT2_FLAG_NO_RETURN_ON_SUCCESS, 10, flags, usb_activity_gpio_pin_mask);
__builtin_unreachable();
}
*/
import "C"
func enterBootloader() {
C.reset_usb_boot(0, 0)
}
+380
View File
@@ -0,0 +1,380 @@
//go:build rp2350
package machine
import (
"device/rp"
"machine/usb"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
)
var (
sendOnEP0DATADONE struct {
offset int
data []byte
pid uint32
}
)
// Configure the USB peripheral. The config is here for compatibility with the UART interface.
func (dev *USBDevice) Configure(config UARTConfig) {
// Reset usb controller
resetBlock(rp.RESETS_RESET_USBCTRL)
unresetBlockWait(rp.RESETS_RESET_USBCTRL)
// Clear any previous state in dpram just in case
usbDPSRAM.clear()
// Enable USB interrupt at processor
rp.USB.INTE.Set(0)
intr := interrupt.New(rp.IRQ_USBCTRL_IRQ, handleUSBIRQ)
intr.SetPriority(0x00)
intr.Enable()
irqSet(rp.IRQ_USBCTRL_IRQ, true)
// Mux the controller to the onboard usb phy
rp.USB.USB_MUXING.Set(rp.USB_USB_MUXING_TO_PHY | rp.USB_USB_MUXING_SOFTCON)
// Force VBUS detect so the device thinks it is plugged into a host
rp.USB.USB_PWR.Set(rp.USB_USB_PWR_VBUS_DETECT | rp.USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN)
// Enable the USB controller in device mode.
rp.USB.MAIN_CTRL.Set(rp.USB_MAIN_CTRL_CONTROLLER_EN)
// Enable an interrupt per EP0 transaction
rp.USB.SIE_CTRL.Set(rp.USB_SIE_CTRL_EP0_INT_1BUF)
// Enable interrupts for when a buffer is done, when the bus is reset,
// and when a setup packet is received
rp.USB.INTE.Set(rp.USB_INTE_BUFF_STATUS |
rp.USB_INTE_BUS_RESET |
rp.USB_INTE_SETUP_REQ)
// Present full speed device by enabling pull up on DP
rp.USB.SIE_CTRL.SetBits(rp.USB_SIE_CTRL_PULLUP_EN)
// 12.7.2 Disable phy isolation
rp.USB.SetMAIN_CTRL_PHY_ISO(0x0)
}
func handleUSBIRQ(intr interrupt.Interrupt) {
status := rp.USB.INTS.Get()
// Setup packet received
if (status & rp.USB_INTS_SETUP_REQ) > 0 {
rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_SETUP_REC)
setup := usb.NewSetup(usbDPSRAM.setupBytes())
ok := false
if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD {
// Standard Requests
ok = handleStandardSetup(setup)
} else {
// Class Interface Requests
if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil {
ok = usbSetupHandler[setup.WIndex](setup)
}
}
if !ok {
// Stall endpoint?
sendStallViaEPIn(0)
}
}
// Buffer status, one or more buffers have completed
if (status & rp.USB_INTS_BUFF_STATUS) > 0 {
if sendOnEP0DATADONE.offset > 0 {
ep := uint32(0)
data := sendOnEP0DATADONE.data
count := len(data) - sendOnEP0DATADONE.offset
if ep == 0 && count > usb.EndpointPacketSize {
count = usb.EndpointPacketSize
}
sendViaEPIn(ep, data[sendOnEP0DATADONE.offset:], count)
sendOnEP0DATADONE.offset += count
if sendOnEP0DATADONE.offset == len(data) {
sendOnEP0DATADONE.offset = 0
}
}
s2 := rp.USB.BUFF_STATUS.Get()
// OUT (PC -> rp2040)
for i := 0; i < 16; i++ {
if s2&(1<<(i*2+1)) > 0 {
buf := handleEndpointRx(uint32(i))
if usbRxHandler[i] != nil {
usbRxHandler[i](buf)
}
handleEndpointRxComplete(uint32(i))
}
}
// IN (rp2040 -> PC)
for i := 0; i < 16; i++ {
if s2&(1<<(i*2)) > 0 {
if usbTxHandler[i] != nil {
usbTxHandler[i]()
}
}
}
rp.USB.BUFF_STATUS.Set(s2)
}
// Bus is reset
if (status & rp.USB_INTS_BUS_RESET) > 0 {
rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_BUS_RESET)
//fixRP2040UsbDeviceEnumeration()
rp.USB.ADDR_ENDP.Set(0)
initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL)
}
}
func initEndpoint(ep, config uint32) {
val := uint32(usbEpControlEnable) | uint32(usbEpControlInterruptPerBuff)
offset := ep*2*USBBufferLen + 0x100
val |= offset
switch config {
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
val |= usbEpControlEndpointTypeInterrupt
usbDPSRAM.EPxControl[ep].In.Set(val)
case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut:
val |= usbEpControlEndpointTypeBulk
usbDPSRAM.EPxControl[ep].Out.Set(val)
usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask)
usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut:
val |= usbEpControlEndpointTypeInterrupt
usbDPSRAM.EPxControl[ep].Out.Set(val)
usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask)
usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn:
val |= usbEpControlEndpointTypeBulk
usbDPSRAM.EPxControl[ep].In.Set(val)
case usb.ENDPOINT_TYPE_CONTROL:
val |= usbEpControlEndpointTypeControl
usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid)
usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
}
}
func handleUSBSetAddress(setup usb.Setup) bool {
sendUSBPacket(0, []byte{}, 0)
// last, set the device address to that requested by host
// wait for transfer to complete
timeout := 3000
rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_ACK_REC)
for (rp.USB.SIE_STATUS.Get() & rp.USB_SIE_STATUS_ACK_REC) == 0 {
timeout--
if timeout == 0 {
return true
}
}
rp.USB.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USB_ADDR_ENDP_ADDRESS_Msk)
return true
}
// SendUSBInPacket sends a packet for USB (interrupt in / bulk in).
func SendUSBInPacket(ep uint32, data []byte) bool {
sendUSBPacket(ep, data, 0)
return true
}
//go:noinline
func sendUSBPacket(ep uint32, data []byte, maxsize uint16) {
count := len(data)
if 0 < int(maxsize) && int(maxsize) < count {
count = int(maxsize)
}
if ep == 0 {
if count > usb.EndpointPacketSize {
count = usb.EndpointPacketSize
sendOnEP0DATADONE.offset = count
sendOnEP0DATADONE.data = data
} else {
sendOnEP0DATADONE.offset = 0
}
epXdata0[ep] = true
}
sendViaEPIn(ep, data, count)
}
func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
var b [cdcLineInfoSize]byte
ep := 0
for !usbDPSRAM.EPxBufferControl[ep].Out.HasBits(usbBuf0CtrlFull) {
// TODO: timeout
}
ctrl := usbDPSRAM.EPxBufferControl[ep].Out.Get()
usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask)
sz := ctrl & usbBuf0CtrlLenMask
copy(b[:], usbDPSRAM.EPxBuffer[ep].Buffer0[:sz])
usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid)
usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
return b, nil
}
func handleEndpointRx(ep uint32) []byte {
ctrl := usbDPSRAM.EPxBufferControl[ep].Out.Get()
usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask)
sz := ctrl & usbBuf0CtrlLenMask
return usbDPSRAM.EPxBuffer[ep].Buffer0[:sz]
}
func handleEndpointRxComplete(ep uint32) {
epXdata0[ep] = !epXdata0[ep]
if epXdata0[ep] || ep == 0 {
usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid)
}
usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
}
func SendZlp() {
sendUSBPacket(0, []byte{}, 0)
}
func sendViaEPIn(ep uint32, data []byte, count int) {
// Prepare buffer control register value
val := uint32(count) | usbBuf0CtrlAvail
// DATA0 or DATA1
epXdata0[ep&0x7F] = !epXdata0[ep&0x7F]
if !epXdata0[ep&0x7F] {
val |= usbBuf0CtrlData1Pid
}
// Mark as full
val |= usbBuf0CtrlFull
copy(usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count])
usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val)
}
func sendStallViaEPIn(ep uint32) {
// Prepare buffer control register value
if ep == 0 {
rp.USB.EP_STALL_ARM.Set(rp.USB_EP_STALL_ARM_EP0_IN)
}
val := uint32(usbBuf0CtrlFull)
usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val)
val |= uint32(usbBuf0CtrlStall)
usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val)
}
type USBDPSRAM struct {
// Note that EPxControl[0] is not EP0Control but 8-byte setup data.
EPxControl [16]USBEndpointControlRegister
EPxBufferControl [16]USBBufferControlRegister
EPxBuffer [16]USBBuffer
}
type USBEndpointControlRegister struct {
In volatile.Register32
Out volatile.Register32
}
type USBBufferControlRegister struct {
In volatile.Register32
Out volatile.Register32
}
type USBBuffer struct {
Buffer0 [USBBufferLen]byte
Buffer1 [USBBufferLen]byte
}
var (
usbDPSRAM = (*USBDPSRAM)(unsafe.Pointer(uintptr(0x50100000)))
epXdata0 [16]bool
setupBytes [8]byte
)
func (d *USBDPSRAM) setupBytes() []byte {
data := d.EPxControl[usb.CONTROL_ENDPOINT].In.Get()
setupBytes[0] = byte(data)
setupBytes[1] = byte(data >> 8)
setupBytes[2] = byte(data >> 16)
setupBytes[3] = byte(data >> 24)
data = d.EPxControl[usb.CONTROL_ENDPOINT].Out.Get()
setupBytes[4] = byte(data)
setupBytes[5] = byte(data >> 8)
setupBytes[6] = byte(data >> 16)
setupBytes[7] = byte(data >> 24)
return setupBytes[:]
}
func (d *USBDPSRAM) clear() {
for i := 0; i < len(d.EPxControl); i++ {
d.EPxControl[i].In.Set(0)
d.EPxControl[i].Out.Set(0)
d.EPxBufferControl[i].In.Set(0)
d.EPxBufferControl[i].Out.Set(0)
}
}
const (
// DPRAM : Endpoint control register
usbEpControlEnable = 0x80000000
usbEpControlDoubleBuffered = 0x40000000
usbEpControlInterruptPerBuff = 0x20000000
usbEpControlInterruptPerDoubleBuff = 0x10000000
usbEpControlEndpointType = 0x0c000000
usbEpControlInterruptOnStall = 0x00020000
usbEpControlInterruptOnNak = 0x00010000
usbEpControlBufferAddress = 0x0000ffff
usbEpControlEndpointTypeControl = 0x00000000
usbEpControlEndpointTypeISO = 0x04000000
usbEpControlEndpointTypeBulk = 0x08000000
usbEpControlEndpointTypeInterrupt = 0x0c000000
// Endpoint buffer control bits
usbBuf1CtrlFull = 0x80000000
usbBuf1CtrlLast = 0x40000000
usbBuf1CtrlData0Pid = 0x20000000
usbBuf1CtrlData1Pid = 0x00000000
usbBuf1CtrlSel = 0x10000000
usbBuf1CtrlStall = 0x08000000
usbBuf1CtrlAvail = 0x04000000
usbBuf1CtrlLenMask = 0x03FF0000
usbBuf0CtrlFull = 0x00008000
usbBuf0CtrlLast = 0x00004000
usbBuf0CtrlData0Pid = 0x00000000
usbBuf0CtrlData1Pid = 0x00002000
usbBuf0CtrlSel = 0x00001000
usbBuf0CtrlStall = 0x00000800
usbBuf0CtrlAvail = 0x00000400
usbBuf0CtrlLenMask = 0x000003FF
USBBufferLen = 64
)
+199
View File
@@ -0,0 +1,199 @@
//go:build rp2040
package machine
import (
"device/rp"
"runtime/volatile"
"unsafe"
)
const (
_NUMBANK0_GPIOS = 30
_NUMBANK0_IRQS = 4
_NUMIRQ = 32
rp2350ExtraReg = 0
RESETS_RESET_Msk = 0x01ffffff
initUnreset = rp.RESETS_RESET_ADC |
rp.RESETS_RESET_RTC |
rp.RESETS_RESET_SPI0 |
rp.RESETS_RESET_SPI1 |
rp.RESETS_RESET_UART0 |
rp.RESETS_RESET_UART1 |
rp.RESETS_RESET_USBCTRL
initDontReset = rp.RESETS_RESET_IO_QSPI |
rp.RESETS_RESET_PADS_QSPI |
rp.RESETS_RESET_PLL_USB |
rp.RESETS_RESET_USBCTRL |
rp.RESETS_RESET_SYSCFG |
rp.RESETS_RESET_PLL_SYS
padEnableMask = rp.PADS_BANK0_GPIO0_IE_Msk |
rp.PADS_BANK0_GPIO0_OD_Msk
)
const (
PinOutput PinMode = iota
PinInput
PinInputPulldown
PinInputPullup
PinAnalog
PinUART
PinPWM
PinI2C
PinSPI
PinPIO0
PinPIO1
)
const (
ClkGPOUT0 clockIndex = iota // GPIO Muxing 0
ClkGPOUT1 // GPIO Muxing 1
ClkGPOUT2 // GPIO Muxing 2
ClkGPOUT3 // GPIO Muxing 3
ClkRef // Watchdog and timers reference clock
ClkSys // Processors, bus fabric, memory, memory mapped registers
ClkPeri // Peripheral clock for UART and SPI
ClkUSB // USB clock
ClkADC // ADC clock
ClkRTC // Real time clock
NumClocks
)
func CalcClockDiv(srcFreq, freq uint32) uint32 {
// Div register is 24.8 int.frac divider so multiply by 2^8 (left shift by 8)
return uint32((uint64(srcFreq) << 8) / uint64(freq))
}
type clocksType struct {
clk [NumClocks]clockType
resus struct {
ctrl volatile.Register32
status volatile.Register32
}
fc0 fc
wakeEN0 volatile.Register32
wakeEN1 volatile.Register32
sleepEN0 volatile.Register32
sleepEN1 volatile.Register32
enabled0 volatile.Register32
enabled1 volatile.Register32
intR volatile.Register32
intE volatile.Register32
intF volatile.Register32
intS volatile.Register32
}
// GPIO function selectors
const (
fnJTAG pinFunc = 0
fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO
fnUART pinFunc = 2
fnI2C pinFunc = 3
// Connect a PWM slice to GPIO. There are eight PWM slices,
// each with two outputchannels (A/B). The B pin can also be used as an input,
// for frequency and duty cyclemeasurement
fnPWM pinFunc = 4
// Software control of GPIO, from the single-cycle IO (SIO) block.
// The SIO function (F5)must be selected for the processors to drive a GPIO,
// but the input is always connected,so software can check the state of GPIOs at any time.
fnSIO pinFunc = 5
// Connect one of the programmable IO blocks (PIO) to GPIO. PIO can implement a widevariety of interfaces,
// and has its own internal pin mapping hardware, allowing flexibleplacement of digital interfaces on bank 0 GPIOs.
// The PIO function (F6, F7) must beselected for PIO to drive a GPIO, but the input is always connected,
// so the PIOs canalways see the state of all pins.
fnPIO0, fnPIO1 pinFunc = 6, 7
// General purpose clock inputs/outputs. Can be routed to a number of internal clock domains onRP2040,
// e.g. Input: to provide a 1 Hz clock for the RTC, or can be connected to an internalfrequency counter.
// e.g. Output: optional integer divide
fnGPCK pinFunc = 8
// USB power control signals to/from the internal USB controller
fnUSB pinFunc = 9
fnNULL pinFunc = 0x1f
fnXIP pinFunc = 0
)
// Configure configures the gpio pin as per mode.
func (p Pin) Configure(config PinConfig) {
if p == NoPin {
return
}
p.init()
mask := uint32(1) << p
switch config.Mode {
case PinOutput:
p.setFunc(fnSIO)
rp.SIO.GPIO_OE_SET.Set(mask)
case PinInput:
p.setFunc(fnSIO)
p.pulloff()
case PinInputPulldown:
p.setFunc(fnSIO)
p.pulldown()
case PinInputPullup:
p.setFunc(fnSIO)
p.pullup()
case PinAnalog:
p.setFunc(fnNULL)
p.pulloff()
case PinUART:
p.setFunc(fnUART)
case PinPWM:
p.setFunc(fnPWM)
case PinI2C:
// IO config according to 4.3.1.3 of rp2040 datasheet.
p.setFunc(fnI2C)
p.pullup()
p.setSchmitt(true)
p.setSlew(false)
case PinSPI:
p.setFunc(fnSPI)
case PinPIO0:
p.setFunc(fnPIO0)
case PinPIO1:
p.setFunc(fnPIO1)
}
}
var (
timer = (*timerType)(unsafe.Pointer(rp.TIMER))
)
// Enable or disable a specific interrupt on the executing core.
// num is the interrupt number which must be in [0,31].
func irqSet(num uint32, enabled bool) {
if num >= _NUMIRQ {
return
}
irqSetMask(1<<num, enabled)
}
func irqSetMask(mask uint32, enabled bool) {
if enabled {
// Clear pending before enable
// (if IRQ is actually asserted, it will immediately re-pend)
rp.PPB.NVIC_ICPR.Set(mask)
rp.PPB.NVIC_ISER.Set(mask)
} else {
rp.PPB.NVIC_ICER.Set(mask)
}
}
func (clks *clocksType) initRTC() {
// ClkRTC = pllUSB (48MHz) / 1024 = 46875Hz
clkrtc := clks.clock(ClkRTC)
clkrtc.configure(0, // No GLMUX
rp.CLOCKS_CLK_RTC_CTRL_AUXSRC_CLKSRC_PLL_USB,
48*MHz,
46875)
}
func (clks *clocksType) initTicks() {} // No ticks on RP2040
// startTick starts the watchdog tick.
// cycles needs to be a divider that when applied to the xosc input,
// produces a 1MHz clock. So if the xosc frequency is 12MHz,
// this will need to be 12.
func (wd *watchdogImpl) startTick(cycles uint32) {
rp.WATCHDOG.TICK.Set(cycles | rp.WATCHDOG_TICK_ENABLE)
}

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