Compare commits

...

59 Commits

Author SHA1 Message Date
Nia Waldvogel 80c89809a9 machine: fix usb truncation?
Remove the sendUSBPacket maxLen param because this greatly confused the compiler.
It also fixes a bug where the length provided to the hardware may not match the length of the packet.
sendUSBPacket now panics if the sent packet is too big.

I also fixed some of the string descriptor logic where we could create a packet without fully populating it.

RP2* systems might require some more work since they are implemented very differently?
I don't have any of those to test with yet, so maybe someone can deal with them in a seperate PR?
2025-12-30 15:51:27 -05:00
Nia Waldvogel 266d70816f compiler/runtime: add element layout to sliceAppend
Attach the memory layout of the element type to newly created slices via append when using the precise collector.
2025-12-26 21:27:24 -05:00
Damian Gryski 27cedf09e8 testdata: more corpus repos 2025-12-22 13:15:06 +00:00
deadprogram db9f1182f5 Release 0.40.1
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-18 21:59:18 +00:00
MakKi (makki_d) f7fd5cf2d8 runtime: use rand_hwrng hardwareRand for RP2040/RP2350 (#5135)
* runtime: use rand_hwrng hardwareRand for RP2040/RP2350

* add a comment

* insert 'implementations'
2025-12-18 20:52:12 +01:00
deadprogram 8d9c9a16e3 picolibc: use updated location for git repo
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-18 18:20:22 +00:00
deadprogram 82deccac40 runtime: call initRand() before initHeap() during initialization.
The reason is that allocating without a heap is usually more visible
than using an uninitialized random generator which is subtle and may
lead to security vulnerabilities.

Based on suggestion from @eliasnaur

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-18 16:42:46 +00:00
deadprogram 0e540dc3da runtime: add calls to initRand() during run() for all schedulers.
Intended to ensure that the random number seed used is always initialized on startup.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-18 16:42:46 +00:00
MakKi (makki_d) 897f2bc5fd runtime: avoid fixed math/rand sequence on RP2040/RP2350 (#5124)
* runtime: avoid fixed math/rand sequence on RP2040/RP2350

* fix typo
2025-12-17 20:05:43 +01:00
Ayke van Laethem 750b0e5c18 nrf: fix flash writes when SoftDevice is enabled
The data to write is specififed in words, not in bytes.

This fix is needed for correctness. Without it, a
`machine.Flash.WriteAt` call can result in **data loss** since it will
overwrite more data than it should.
2025-12-17 14:58:40 +00:00
deadprogram a65ac105cc build: remove post-build triggers for associated repos like drivers.
Now that the drivers repo and friends are intended to target the most
recent release version of TinyGo instead of the development version,
this trigger does not serve any puppose to trigger a build that will have
already been tested.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-17 13:34:19 +00:00
deadprogram 6970c80d34 Release 0.40.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-15 09:33:17 +00:00
Damian Gryski dfbc42f833 testdata: some more packages for the test corpus
ahocorasick no longer times out during interp.  I don't believe
this is because we fixed a bug in interp but rather because "something"
(garbage collector fixes?) has moved the tree building to runtime.
2025-12-13 11:17:41 +00:00
Damian Gryski e356daa229 internal/task: prevent semaphore resource leak for threads scheduler 2025-12-13 00:40:54 -08:00
deadprogram 1c27ecb66f build: go back to normal scheduler instead of tasks scheduler for macos CI
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-12 07:58:30 +00:00
Nia Waldvogel d01d0bb358 runtime (gc_blocks.go): make sweep branchless
Instead of looping over each block, we can use bit hacks to operate on an entire state byte.
I deinterleaved the state bits in order to enable these tricks.

Sweep used to count free/freed allocations/blocks.
I managed to move/remove all of these counters:
- The free space is now calculated in buildFreeRanges by adding the range lengths.
- ReadMemStats counts freed objects by subtracting live objects from allocated objects.
- gcFreedBlocks was never necessary because MemStats.HeapAlloc is the same as MemStats.HeapInUse.
2025-12-10 18:39:50 -05:00
Nia Waldvogel 8fcf3658b1 runtime (gc_blocks.go): use best-fit allocation
The allocator originally just looped through the blocks until it found a sufficiently-long range.
This is simple, but it fragments very easily and can degrade to a full heap scan for long requests.

Instead, we now maintain a sorted nested list of free ranges by size.
The allocator will select the shortest sufficient-length range, generally reducing fragmentation.
This data structure can find a range in time directly proportional to the requested length.
2025-12-10 06:58:07 +00:00
Nia Waldvogel b37535bcfb transform (gc): create stack slots in callers of external functions
This updates the stack slot pass to include callers of external functions which may access non-argument memory.
Wiithout this change, a use-after-free could occur on WASM when calling a reentrant function or switching to another goroutine.
2025-12-09 21:12:55 +00:00
Nia Waldvogel ee0a10ec51 runtime (wasm): scan the system stack
This saves the system stack pointer into a global and uses it to scan.
It should fix some use-after-free issues?
2025-12-08 17:27:20 +00:00
Michael Smith 39e0333663 fix: don't hardcode success return state
fix: validate bmRequestType\nfix: mimic tinyusb behavior in mscStateNeedReset\nfix: CLEAR_FEATURE requests to iface addr are against spec
2025-12-06 06:34:32 +00:00
Michael Smith 3afd2c3597 fix: update m.queuedBytes when clamping output to avoid corrupting sentBytes 2025-12-06 06:34:32 +00:00
Michael Smith bd19f3baa3 fix: separate host expected bytes from device intended bytes 2025-12-06 06:34:32 +00:00
Ayke van Laethem 908fa84d49 runtime: remove copied code for nrf52840
We really shouldn't have copies around like that, instead the code
should be factored out.
In this case, it's a single import that's needed.
2025-12-05 20:50:06 +00:00
Ayke van Laethem 571f34e0e6 machine: only enable USB-CDC when needed
Enabling USB consumes a lot of power. So it's better to keep it disabled
by default when the serial port is set to something other than "usb"
(USB-CDC).

On my nice!nano clone, it reduces current consumption from 1mA to
0.13mA (and most of the rest is probably consumed by a connected SCD41
sensor).

This change might break some expectations, when a board uses USB but not
USB-CDC (I think this is rare, but I didn't check specifically).
2025-12-05 20:50:06 +00:00
Nia Waldvogel 01723025d7 runtime (avr): fix infinite longjmp loop if stack is aligned to 256 bytes
The setjmp code sets r24 to 0 and checks if it is still 0 when it finishes.
This usually works because tinygo_longjmp stores the lower half of the stack pointer into r24.
However, r24 will be set to 0 if the stack is aligned to 256 bytes when entering the setjmp.

Manually set r24 to 1 in tinygo_longjmp to fix this issue.
2025-12-05 13:22:37 -05:00
deadprogram 505b5ee565 build: update CI to use Go 1.25.5
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-12-05 11:49:11 +00:00
Nia Waldvogel 26ac03a3f6 runtime (gc_blocks.go): simplify scanning logic
Loop over valid pointer locations in heap objects instead of checking if each location is valid.
The conservative scanning code is now shared between markRoots and the heap scan.

This also removes the ending alignment requirement from markRoots, since the new scan* functions do not require an aligned length.
This requirement was occasionally violated by the linux global marking code.

This saves some code space and has negligible impact on performance.
2025-12-05 10:16:38 +00:00
Nia Waldvogel c9aa88b8ef runtime (gc_blocks.go): use a linked stack to scan marked objects
The blocks GC originally used a fixed-size stack to hold objects to scan.
When this stack overflowed, the GC would fully rescan all marked objects.
This could cause the GC to degrade to O(n^2) when scanning large linked data structures.

Instead of using a fixed-size stack, we now add a pointer field to the start of each object.
This pointer field is used to implement an unbounded linked stack.
This also consolidates the heap object scanning into one place, which simplifies the process.

This comes at the cost of introducing a pointer field to the start of the object, plus the cost of aligning the result.
This translates to:
- 16 bytes of overhead on x86/arm64 with the conservative collector
- 0 bytes of overhead on x86/arm64 with the precise collector (the layout field cost gets aligned up to 16 bytes anyway)
- 8 bytes of overhead on other 64-bit systems
- 4 bytes of overhead on 32-bit systems
- 2 bytes of overhead on AVR
2025-12-05 10:16:38 +00:00
Ayke van Laethem 887ac286ff nrf: add ADC_VDDH which is an ADC pin for VDDH
VDDH is typically connected to USB (~5V) or directly to a battery.
Measuring the voltage on VDDH is a way to measure the current battery
voltage, which in turn can be useful to determine how much power is left
in the battery.

I've special cased the nrf52840 in a somewhat unusual way, maybe there
is a better way, feel free to comment!
2025-12-04 18:48:22 +00:00
Ayke van Laethem b1083d78c7 nrf: rename pwmPin to adcPin
Just a small refactor, to fix the naming.
2025-12-04 18:48:22 +00:00
Ayke van Laethem 7863bdb484 nrf: don't set PSELN, it's ignored in single ended mode anyway
We use single ended mode, so according to the datasheet PSELN is
ignored.
2025-12-04 18:48:22 +00:00
Ayke van Laethem 5c3363bf25 nrf: fix typo in ADC configuration
This shouldn't change behavior (both values are 0), but it's a bit more
correct.
2025-12-04 18:48:22 +00:00
Nia Waldvogel 9404bb8712 internal/task (threads): save stack bounds instead of scanning under a lock
In order to scan stacks, the GC preempts all other threads and has them scan their own stack.
This is somewhat expensive since all of these threads have to fight over a single lock.
Instead, save the stack bounds and let the GC thread perform the scan.

This also fixes a few other bugs I ran into:
1. The GC starts scanning before the world stops. This can cause it to miss some objects (and mistakenly free them) if memory is modified while stopping.
2. The GC does not wait for threads to resume. This can cause notifications to be misinterpreted due to signal nesting if the GC is re-run before all threads wake.
2025-12-04 11:22:46 +00:00
Nia Waldvogel 20e22d4507 runtime (gc_blocks.go): clear full size of allocation
This fixes a bug where runtime.alloc would not clear the padding from rounding the allocation up to a multiple of the block size.
The GC still has to scan this, and this could result in permanent false positives (and therefore memory leaks).

It also handles overflow in the size calculation.
2025-11-30 08:46:20 +00:00
deadprogram 9118e91b72 target: add xiao-esp32s3 board target
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-11-24 14:30:50 +00:00
cjpeterson fbbaa5e580 Add ESP32-S3 support (#5091)
* feat: add initial support for ESP32-S3 (#3442)

* feat: add initial support for esp32-s3

* esp32s3: fix merge errors

* esp32s3: Fix Watchdog registers bad names

* esp32s3: fix linker relocation errors and support for ESP binary

* esp32s3: fix memory section overlap

* esp32s3: correct clock frequencies

* esp32s3: more stable cpu

* esp32s3: enable basic gpio support

* esp32s3: simplify loading and check extensions

* esp32s3: synchronize cpu features with clang

* esp32s3: correct iram origin

---------

Co-authored-by: Denys Vitali <denys@denv.it>
Co-authored-by: Olivier Fauchon <ofauchon2204@gmail.com>
2025-11-24 12:11:47 +01:00
deadprogram 97a2cb4a37 build: update macOS GH actions builds to handle sunset of macOS 13
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-11-19 16:15:12 +01:00
Ayke van Laethem 6dee98c3a1 nrf: support flash operations while the SoftDevice is enabled
Flash operations must go through the SoftDevice if it is enabled,
otherwise they will crash the chip. (And even then the SoftDevice
doesn't guarantee they'll succeed, depending on advertisement frequency
etc). But this patch makes sure to call the appropriate APIs so that
flash is usable while using Bluetooth.
2025-11-16 19:25:14 +01:00
Ayke van Laethem 484ccf830a nrf: refactor SoftDevice enabled check
Share the code with the runtime.
2025-11-16 19:25:14 +01:00
Ayke van Laethem 5690204224 nrf: don't block SPI transfer
Ideally we'd use something like
https://github.com/tinygo-org/tinygo/pull/5016 but that's a bit more
involved. As a quick improvement, call gosched() instead.

Example where I use this: a custom WS2812 driver that uses SPI to
transfer the data. It's useful to be able to switch back to the main
goroutine during the transfer to render the next LED update.
2025-11-16 11:24:32 +01:00
Ayke van Laethem b203314c2f machine: make sure DMA buffers do not escape unnecessarily
Writing the pointer of a buffer to memory-mapped I/O will normally cause
it to escape, which forces the compiler to heap-allocate the buffer. But
we do know how long the value stays alive, so we can tell the compiler
to keep it alive exactly until it is not needed anymore - and tell it to
not treat the pointer-to-uintptr cast as escaping.
2025-11-12 18:07:53 +01:00
Ayke van Laethem 5ae8fd1f6f machine: clarify WriteAt semantics of BlockDevice
Flash can generally only be written after it has been erased, which
wasn't spelled out explicitly in the interface.
2025-11-12 11:24:49 +01:00
Nia Waldvogel c80da7eb0d runtime (gc_boehm.go): fix world already stopped check
This fixes the "gc: world already stopped" check by moving it before gcMarkReachable.
Previously the check did not work because gcMarkReachable would hang while waiting for activeTaskLock.
2025-11-10 11:19:31 +01:00
Nia Waldvogel 326ca4202e internal/task: create detatched threads and fix error handling
Previously, we created joinable threads.
As a result, all threads would be kept alive after completion so that pthread_join could wait for them.
We never call pthread_join, so threads would never get cleaned up.

Additionally, the thread creation error check was performed after waiting for the thread to start.
If pthread_create failed, the caller would get stuck waiting for a thread that was never created.
2025-11-10 10:07:19 +01:00
Nia Waldvogel 852dde671e compiler: use Tarjan's SCC algorithm to detect loops for defer
The compiler needs to know whether a defer is in a loop to determine whether to allocate stack or heap memory.
Previously, this performed a DFS of the CFG every time a defer was found.
This resulted in time complexity jointly proportional to the number of defers and the number of blocks in the function.

Now, the compiler will instead use Tarjan's strongly connected components algorithm to find cycles in linear time.
The search is performed lazily, so this has minimal performance impact on functions without defers.

In order to implement Tarjan's SCC algorithm, additional state needed to be attached to the blocks.
I chose to merge all of the per-block state into a single slice to simplify memory management.
2025-11-10 07:27:40 +01:00
deadprogram bf613175e7 ci: update GH actions builds to use Go 1.25.4
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-11-07 10:04:33 +01:00
sivchari 4d62cca267 feature: Add flag to ignore go compatibility matrix (#5078)
* add go-compatibility feature

Signed-off-by: sivchari <shibuuuu5@gmail.com>

* rename env

Signed-off-by: sivchari <shibuuuu5@gmail.com>

* fix description

Signed-off-by: sivchari <shibuuuu5@gmail.com>

---------

Signed-off-by: sivchari <shibuuuu5@gmail.com>
2025-11-06 21:28:06 +01:00
deadprogram a63aa143bb build: use task scheduler on macOS builds to avoid test race condition lockups.
Fixes #4995

Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-11-06 19:19:20 +01:00
Ben Krieger ed0fb774b9 Add testing.T.Context() and testing.B.Context() 2025-11-05 10:33:15 +01:00
Damian Gryski 9be956f4bc src/syscall: update src buffer after write
Fixes #5012
2025-10-28 12:43:23 -07:00
Ayke van Laethem 48d741f68a compiler: emit an error when the actual arch doesn't match GOARCH
See: https://github.com/tinygo-org/tinygo/issues/4959
This makes sure to emit an error instead of generating inline assembly,
which leads to hard-to-debug linker errors. Instead, it will now produce
errors like the following:

    # golang.org/x/sys/unix
    ../../../../pkg/mod/golang.org/x/sys@v0.0.0-20220722155257-8c9f86f7a55f/unix/affinity_linux.go:20:23: system calls are not supported: target emulates a linux/arm system on wasm32
    ../../../../pkg/mod/golang.org/x/sys@v0.0.0-20220722155257-8c9f86f7a55f/unix/fcntl.go:17:29: system calls are not supported: target emulates a linux/arm system on wasm32
    ../../../../pkg/mod/golang.org/x/sys@v0.0.0-20220722155257-8c9f86f7a55f/unix/fcntl.go:32:24: system calls are not supported: target emulates a linux/arm system on wasm32
2025-10-23 10:13:29 +02:00
Elias Naur 35e6b8a67d compileopts: lower "large stack" limit to 16kb
I'm running with `-stack-size 16kb` and running into excessive allocations
because the otherwise non-escaping buffer in

https://github.com/golang/go/blob/c53cb642deea152e28281133bc0053f5ec0ce358/src/crypto/internal/fips140/sha512/sha512block.go#L97

is moved to the heap.
2025-10-22 13:04:24 +02:00
Ayke van Laethem 8a31ae14a0 machine: use larger SPI MAXCNT on nrf52833 and nrf52840
These chips have a larger upper limit for the DMA transfer than the
nrf52832. For best performance, we should be splitting the transfer in
as large blocks as possible on the given hardware.
2025-10-21 13:33:13 +02:00
Ayke van Laethem 26ee3fb93e wasm: fix C realloc and optimize it a bit
- Do not use make([]byte, ...) to allocate, instead call the allocator
    directly with a nil (undefined) layout. This makes sure the precise
    GC will scan the contents of the allocation, since C could very well
    put pointers in there.
  - Simplify the map to use the pointer as the key and the size as the
    value, instead of storing the slices directly in the map.
2025-10-20 12:58:11 +02:00
Ayke van Laethem 8872d5ebfc runtime: fix sleep duration for long sleeps
Long sleep durations weren't implemented correctly, by simply casting to
a smaller number of bits. What we want is a saturating downcast, which
is what I've used here instead.

This should fix https://github.com/tinygo-org/tinygo/issues/3356.
2025-10-16 17:20:50 +02:00
deadprogram faf455283d chore: update all CI builds to use latest stable Go release. Also update some of the actions to their latest releases.
Signed-off-by: deadprogram <ron@hybridgroup.com>
2025-10-16 09:04:00 +02:00
tinkerator 562b4c5020 Create "pico2-ice" target board (#5062)
* Create "pico2-ice" target board

This board has an rp2350b chip on it, as well as a Lattice Semiconductor
iCE40UP5K FPGA. More details of this open hardware board here:

  https://pico2-ice.tinyvision.ai/

Tested on a pico2-ice board with:

  ~/go/bin/tinygo flash -target=pico2-ice src/examples/blinky1/blinky1.go

which blinks the GREEN LED (connected to GPIO0) on this board.

Signed-off-by: Tinkerer <tinkerer@zappem.net>

* More silkscreen labels for pico2-ice board

Reading the schematic and the rev2 board viewer:

https://raw.githubusercontent.com/tinyvision-ai-inc/pico2-ice/refs/heads/main/Board/Rev2/bom/ibom.html

concluded that the RP2350B GPIO pins are not labeled with these
pin numbers in the silkscreen. Instead, the silkscreen refers to
uses of the GPIOs or the ICE numbered pins. RP2340B devoted pins
map to the A1..4 B1..4 pins on the RP PMOD connector. Also
silkscreen "~0".."~6" are labeled as pins N0..N6.

Signed-off-by: Tinkerer <tinkerer@zappem.net>

* Added a smoketest for pico2-ice board and tidied up GPIO defs

Addresses review comments from aykevl.

Signed-off-by: Tinkerer <tinkerer@zappem.net>
2025-10-13 13:19:04 +02:00
Ayke van Laethem 7460734522 compiler: mark string parameters as readonly
Strings are readonly, but the compiler doesn't always know this. Marking
them as readonly in the frontend allows the compiler to optimize based
on this knowledge.

This provides some small code size benefits. I didn't measure running
speed.
2025-10-11 23:03:18 +02:00
Olaf Flebbe 3be7100e89 fix(rp2): possible integer overflow while computing factors for SPI baudrate
Incorrect factors are calculated for baudrates which are a bit
larger than integer multiples of 4194304.
For example for baudrates of 8_400_000 or 58_800_000.
Fixed the same way in newer versions of the RPI SDK.
2025-10-09 11:48:27 +02:00
106 changed files with 3031 additions and 1059 deletions
+8 -7
View File
@@ -16,14 +16,15 @@ jobs:
name: build-macos name: build-macos
strategy: strategy:
matrix: matrix:
# macos-13: amd64 (oldest supported version as of 18-10-2024) # macos-14: arm64 (oldest supported version as of 18-11-2025)
# macos-14: arm64 (oldest arm64 version) # macos-15-intel: amd64 (last intel version to be supported by github runners)
os: [macos-13, macos-14] # See https://github.com/actions/runner-images/issues/13046
os: [macos-14, macos-15-intel]
include: include:
- os: macos-13
goarch: amd64
- os: macos-14 - os: macos-14
goarch: arm64 goarch: arm64
- os: macos-15-intel
goarch: amd64
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Install Dependencies - name: Install Dependencies
@@ -39,7 +40,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.25.1' go-version: '1.25.5'
cache: true cache: true
- name: Restore LLVM source cache - name: Restore LLVM source cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v4
@@ -134,7 +135,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.25.1' go-version: '1.25.5'
cache: true cache: true
- name: Build TinyGo (LLVM ${{ matrix.version }}) - name: Build TinyGo (LLVM ${{ matrix.version }})
run: go install -tags=llvm${{ matrix.version }} run: go install -tags=llvm${{ matrix.version }}
+2 -44
View File
@@ -31,7 +31,7 @@ jobs:
sudo rm -rf /usr/local/share/boost sudo rm -rf /usr/local/share/boost
df -h df -h
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: recursive submodules: recursive
- name: Set up Docker Buildx - name: Set up Docker Buildx
@@ -58,7 +58,7 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
with: with:
context: . context: .
push: true push: true
@@ -66,45 +66,3 @@ jobs:
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
- name: Trigger Drivers repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/drivers/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger Bluetooth repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/bluetooth/actions/workflows/linux.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyFS repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinyfs/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyFont repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinyfont/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyDraw repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinydraw/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyTerm repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/tinyterm/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
+10 -10
View File
@@ -31,7 +31,7 @@ jobs:
# We're not on a multi-user machine, so this is safe. # We're not on a multi-user machine, so this is safe.
run: git config --global --add safe.directory "$GITHUB_WORKSPACE" run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: true submodules: true
- name: Extract TinyGo version - name: Extract TinyGo version
@@ -131,13 +131,13 @@ jobs:
needs: build-linux needs: build-linux
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: true submodules: true
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: '1.25.0' go-version: '1.25.5'
cache: true cache: true
- name: Install wasmtime - name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1 uses: bytecodealliance/actions/wasmtime/setup@v1
@@ -164,7 +164,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: true submodules: true
- name: Install apt dependencies - name: Install apt dependencies
@@ -179,9 +179,9 @@ jobs:
simavr \ simavr \
ninja-build ninja-build
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: '1.25.0' go-version: '1.25.5'
cache: true cache: true
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4
@@ -284,7 +284,7 @@ jobs:
needs: build-linux needs: build-linux
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Get TinyGo version - name: Get TinyGo version
id: version id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
@@ -296,9 +296,9 @@ jobs:
g++-${{ matrix.toolchain }} \ g++-${{ matrix.toolchain }} \
libc6-dev-${{ matrix.libc }}-cross libc6-dev-${{ matrix.libc }}-cross
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: '1.25.0' go-version: '1.25.5'
cache: true cache: true
- name: Restore LLVM source cache - name: Restore LLVM source cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v4
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
contents: read contents: read
steps: steps:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: recursive submodules: recursive
- name: Set up Docker Buildx - name: Set up Docker Buildx
@@ -52,7 +52,7 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v5 uses: docker/build-push-action@v6
with: with:
target: tinygo-llvm-build target: tinygo-llvm-build
context: . context: .
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
# See: https://github.com/tinygo-org/tinygo/pull/4516#issuecomment-2416363668 # See: https://github.com/tinygo-org/tinygo/pull/4516#issuecomment-2416363668
run: sudo apt-get remove llvm-18 run: sudo apt-get remove llvm-18
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Pull musl, bdwgc - name: Pull musl, bdwgc
run: | run: |
git submodule update --init lib/musl lib/bdwgc git submodule update --init lib/musl lib/bdwgc
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
run: | run: |
echo "$HOME/go/bin" >> $GITHUB_PATH echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 0 # fetch all history (no sparse checkout) fetch-depth: 0 # fetch all history (no sparse checkout)
submodules: true submodules: true
+12 -12
View File
@@ -31,7 +31,7 @@ jobs:
run: | run: |
scoop install ninja binaryen scoop install ninja binaryen
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
submodules: true submodules: true
- name: Extract TinyGo version - name: Extract TinyGo version
@@ -39,9 +39,9 @@ jobs:
shell: bash shell: bash
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: '1.25.0' go-version: '1.25.5'
cache: true cache: true
- name: Restore cached LLVM source - name: Restore cached LLVM source
uses: actions/cache/restore@v4 uses: actions/cache/restore@v4
@@ -143,11 +143,11 @@ jobs:
run: | run: |
scoop install binaryen scoop install binaryen
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: '1.25.0' go-version: '1.25.5'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@@ -173,11 +173,11 @@ jobs:
maximum-size: 24GB maximum-size: 24GB
disk-root: "C:" disk-root: "C:"
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: '1.25.0' go-version: '1.25.5'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@@ -209,11 +209,11 @@ jobs:
run: | run: |
scoop install binaryen && scoop install wasmtime@29.0.1 scoop install binaryen && scoop install wasmtime@29.0.1
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version: '1.25.0' go-version: '1.25.5'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
+1 -1
View File
@@ -16,7 +16,7 @@
url = https://github.com/WebAssembly/wasi-libc url = https://github.com/WebAssembly/wasi-libc
[submodule "lib/picolibc"] [submodule "lib/picolibc"]
path = lib/picolibc path = lib/picolibc
url = https://github.com/keith-packard/picolibc.git url = https://github.com/picolibc/picolibc.git
[submodule "lib/stm32-svd"] [submodule "lib/stm32-svd"]
path = lib/stm32-svd path = lib/stm32-svd
url = https://github.com/tinygo-org/stm32-svd url = https://github.com/tinygo-org/stm32-svd
+101
View File
@@ -1,3 +1,104 @@
0.40.1
---
* **machine**
- nrf: fix flash writes when SoftDevice is enabled
* **runtime**
- runtime: avoid fixed math/rand sequence on RP2040/RP2350 (#5124)
- runtime: add calls to initRand() during run() for all schedulers
- runtime: call initRand() before initHeap() during initialization
- runtime: use rand_hwrng hardwareRand for RP2040/RP2350 (#5135)
* **libs**
- picolibc: use updated location for git repo
0.40.0
---
* **general**
- all: add full LLVM 20 support
- core: feat: enable //go:linkname pragma for globals
- core: feature: Add flag to ignore go compatibility matrix (#5078)
- chore: update version for 0.40 development cycle
* **compiler**
- emit an error when the actual arch doesn't match GOARCH
- mark string parameters as readonly
- use Tarjan's SCC algorithm to detect loops for defer
- lower "large stack" limit to 16kb
* **core**
- shrink bdwgc library
- Fix linker errors for runtime.vgetrandom and crypto/internal/sysrand.fatal
- fix: add TryLock to sync.RWMutex
- fix: correct linter issues exposed by the fix in #4679
- fix: don't hardcode success return state
- fix: expand RTT debugger compatibility
- internal/task (threads): save stack bounds instead of scanning under a lock
- internal/task: create detached threads and fix error handling
- interp: better errors when debugging interp
- transform (gc): create stack slots in callers of external functions
- internal/task: prevent semaphore resource leak for threads scheduler
* **machine**
- cortexm: optimize code size for the HardFault_Handler
- fe310: add I2C pins for the HiFive1b
- clarify WriteAt semantics of BlockDevice
- fix deprecated AsmFull comment (#5005)
- make sure DMA buffers do not escape unnecessarily
- only enable USB-CDC when needed
- use larger SPI MAXCNT on nrf52833 and nrf52840
- fix: update m.queuedBytes when clamping output to avoid corrupting sentBytes
- fix: use int64 in ReadTemperature to avoid overflow
- fix(rp2): disable DBGPAUSE on startup
- fix(rp2): possible integer overflow while computing factors for SPI baudrate
- fix(rp2): reset spinlocks at startup
- fix(rp2): switch spinlock busy loop to wfe
- fix(rp2): use side-effect-free spinlocks
- nrf: add ADC_VDDH which is an ADC pin for VDDH
- nrf: don't block SPI transfer
- nrf: don't set PSELN, it's ignored in single ended mode anyway
- nrf: fix typo in ADC configuration
- nrf: refactor SoftDevice enabled check
- nrf: rename pwmPin to adcPin
- nrf: support flash operations while the SoftDevice is enabled
- rp2040: allow writing to the UART inside interrupts
- machine,nrf528: stop the bus only once on I2C bus error and ensures the first error is returned
* **net**
- update submodule to latest commits
* **runtime**
- (avr): fix infinite longjmp loop if stack is aligned to 256 bytes
- (gc_blocks.go): clear full size of allocation
- (gc_blocks.go): make sweep branchless
- (gc_blocks.go): simplify scanning logic
- (gc_blocks.go): use a linked stack to scan marked objects
- (gc_blocks.go): use best-fit allocation
- (gc_boehm.go): fix world already stopped check
- (wasm): scan the system stack
- fix sleep duration for long sleeps
- remove copied code for nrf52840
- src/syscall: update src buffer after write
- wasm: fix C realloc and optimize it a bit
* **targets**
- add xiao-esp32s3 board target
- Add ESP32-S3 support (#5091)
- Added Gopher ARCADE board
- Create "pico2-ice" target board (#5062)
* **build/test**
- Add testing.T.Context() and testing.B.Context()
- create separate go.mod file for testing dependencies for wasm tests that use Chromium headless browser to avoid use of older incompatible version.
- go back to normal scheduler instead of tasks scheduler for macos CI
- update CI to use Go 1.25.5
- update macOS GH actions builds to handle sunset of macOS 13
- use task scheduler on macOS builds to avoid test race condition lockups
- update all CI builds to use latest stable Go release. Also update some of the actions to their latest releases.
- update GH actions builds to use Go 1.25.4
- uninstall cmake before install
- fix: point the submodule for musl-lib to a mirror in the TinyGo GitHub org
- fix: remove macOS 15 from CI build matrix (conflicts with macOS 14 build)
- fix: separate host expected bytes from device intended bytes
- fix/typo: makeESPFirmwareImage
- make: GNUmakefile: shrink TinyGo binaries on Linux
- move the directory list into a variable
- several improvements to the macOS GH actions build
- Fix for #4678: top-level 'make lint' wasn't working
- fix: increase the timeout for chromedp to connect to the headless browser used for running the wasm tests.
- testdata: some more packages for the test corpus
0.39.0 0.39.0
--- ---
* **general** * **general**
+6 -1
View File
@@ -481,6 +481,7 @@ TEST_IOFS := false
endif endif
TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic' TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic'
TEST_ADDITIONAL_FLAGS ?=
# Test known-working standard library packages. # Test known-working standard library packages.
# TODO: parallelize, and only show failing tests (no implied -v flag). # TODO: parallelize, and only show failing tests (no implied -v flag).
@@ -488,7 +489,7 @@ TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generi
tinygo-test: tinygo-test:
@# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented. @# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented.
@# TestParseAndBytesRoundTrip/P256/Generic: relies on t.Skip() which is not implemented @# TestParseAndBytesRoundTrip/P256/Generic: relies on t.Skip() which is not implemented
$(TINYGO) test $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW) $(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
@# io/fs requires os.ReadDir, not yet supported on windows or wasi. It also @# io/fs requires os.ReadDir, not yet supported on windows or wasi. It also
@# requires a large stack-size. Hence, io/fs is only run conditionally. @# requires a large stack-size. Hence, io/fs is only run conditionally.
@# For more details, see the comments on issue #3143. @# For more details, see the comments on issue #3143.
@@ -621,6 +622,8 @@ smoketest: testchdir
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/device-id $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/device-id
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico2-ice examples/blinky1
@$(MD5SUM) test.hex
# test simulated boards on play.tinygo.org # test simulated boards on play.tinygo.org
ifneq ($(WASM), 0) ifneq ($(WASM), 0)
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1
@@ -912,6 +915,8 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest $(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1
@$(MD5SUM) test.bin
endif endif
$(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1 $(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
+1 -1
View File
@@ -1042,7 +1042,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if err != nil { if err != nil {
return result, err return result, err
} }
case "esp32", "esp32-img", "esp32c3", "esp8266": case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM // Special format for the ESP family of chips (parsed by the ROM
// bootloader). // bootloader).
result.Binary = filepath.Join(tmpdir, "main"+outext) result.Binary = filepath.Join(tmpdir, "main"+outext)
+1
View File
@@ -28,6 +28,7 @@ func TestClangAttributes(t *testing.T) {
"cortex-m4", "cortex-m4",
"cortex-m7", "cortex-m7",
"esp32c3", "esp32c3",
"esp32s3",
"fe310", "fe310",
"gameboy-advance", "gameboy-advance",
"k210", "k210",
+7 -4
View File
@@ -33,10 +33,13 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax {
// Note: when this gets updated, also update the Go compatibility matrix: if options.GoCompatibility {
// https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax {
return nil, fmt.Errorf("requires go version 1.%d through 1.%d, got go%d.%d", minorMin, minorMax, gorootMajor, gorootMinor) // Note: when this gets updated, also update the Go compatibility matrix:
// https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md
return nil, fmt.Errorf("requires go version 1.%d through 1.%d, got go%d.%d", minorMin, minorMax, gorootMajor, gorootMinor)
}
} }
// Check that the Go toolchain version isn't too new, if we haven't been // Check that the Go toolchain version isn't too new, if we haven't been
+2 -1
View File
@@ -100,11 +100,12 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
chip_id := map[string]uint16{ chip_id := map[string]uint16{
"esp32": 0x0000, "esp32": 0x0000,
"esp32c3": 0x0005, "esp32c3": 0x0005,
"esp32s3": 0x0009,
}[chip] }[chip]
// Image header. // Image header.
switch chip { switch chip {
case "esp32", "esp32c3": case "esp32", "esp32c3", "esp32s3":
// Header format: // Header format:
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71 // https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
// Note: not adding a SHA256 hash as the binary is modified by // Note: not adding a SHA256 hash as the binary is modified by
+3 -3
View File
@@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test. // This is a small number of very diverse targets that we want to test.
tests := []sizeTest{ tests := []sizeTest{
// microcontrollers // microcontrollers
{"hifive1b", "examples/echo", 3884, 280, 0, 2268}, {"hifive1b", "examples/echo", 3668, 280, 0, 2244},
{"microbit", "examples/serial", 2852, 360, 8, 2272}, {"microbit", "examples/serial", 2694, 342, 8, 2248},
{"wioterminal", "examples/pininterrupt", 7337, 1491, 116, 6912}, {"wioterminal", "examples/pininterrupt", 6833, 1491, 120, 6888},
// TODO: also check wasm. Right now this is difficult, because // TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the // wasm binaries are run through wasm-opt and therefore the
+1 -1
View File
@@ -226,7 +226,7 @@ func (c *Config) StackSize() uint64 {
// MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap. // MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap.
func (c *Config) MaxStackAlloc() uint64 { func (c *Config) MaxStackAlloc() uint64 {
if c.StackSize() > 32*1024 { if c.StackSize() >= 16*1024 {
return 1024 return 1024
} }
+1
View File
@@ -59,6 +59,7 @@ type Options struct {
WITPackage string // pass through to wasm-tools component embed invocation WITPackage string // pass through to wasm-tools component embed invocation
WITWorld string // pass through to wasm-tools component embed -w option WITWorld string // pass through to wasm-tools component embed -w option
ExtLDFlags []string ExtLDFlags []string
GoCompatibility bool // enable to check for Go version compatibility
} }
// Verify performs a validation on the given options, raising an error if options are not valid. // Verify performs a validation on the given options, raising an error if options are not valid.
+1 -1
View File
@@ -245,7 +245,7 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
// current insert position. // current insert position.
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw") faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.insertBasicBlock(blockPrefix + ".next") nextBlock := b.insertBasicBlock(blockPrefix + ".next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block. // Now branch to the out-of-bounds or the regular block.
b.CreateCondBr(assert, faultBlock, nextBlock) b.CreateCondBr(assert, faultBlock, nextBlock)
+8
View File
@@ -32,6 +32,9 @@ const (
// Whether this is a full or partial Go parameter (int, slice, etc). // Whether this is a full or partial Go parameter (int, slice, etc).
// The extra context parameter is not a Go parameter. // The extra context parameter is not a Go parameter.
paramIsGoParam = 1 << iota paramIsGoParam = 1 << iota
// Whether this is a readonly parameter (for example, a string pointer).
paramIsReadonly
) )
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
@@ -167,6 +170,7 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
continue continue
} }
suffix := strconv.Itoa(i) suffix := strconv.Itoa(i)
isString := false
if goType != nil { if goType != nil {
// Try to come up with a good suffix for this struct field, // Try to come up with a good suffix for this struct field,
// depending on which Go type it's based on. // depending on which Go type it's based on.
@@ -183,12 +187,16 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
suffix = []string{"r", "i"}[i] suffix = []string{"r", "i"}[i]
case types.String: case types.String:
suffix = []string{"data", "len"}[i] suffix = []string{"data", "len"}[i]
isString = true
} }
case *types.Signature: case *types.Signature:
suffix = []string{"context", "funcptr"}[i] suffix = []string{"context", "funcptr"}[i]
} }
} }
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i)) subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
if isString {
subInfos[0].flags |= paramIsReadonly
}
paramInfos = append(paramInfos, subInfos...) paramInfos = append(paramInfos, subInfos...)
} }
return paramInfos return paramInfos
+47 -17
View File
@@ -152,10 +152,12 @@ type builder struct {
llvmFnType llvm.Type llvmFnType llvm.Type
llvmFn llvm.Value llvmFn llvm.Value
info functionInfo info functionInfo
locals map[ssa.Value]llvm.Value // local variables locals map[ssa.Value]llvm.Value // local variables
blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up blockInfo []blockInfo
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
currentBlock *ssa.BasicBlock currentBlock *ssa.BasicBlock
currentBlockInfo *blockInfo
tarjanStack []uint
tarjanIndex uint
phis []phiNode phis []phiNode
deferPtr llvm.Value deferPtr llvm.Value
deferFrame llvm.Value deferFrame llvm.Value
@@ -187,11 +189,22 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *bu
info: c.getFunctionInfo(f), info: c.getFunctionInfo(f),
locals: make(map[ssa.Value]llvm.Value), locals: make(map[ssa.Value]llvm.Value),
dilocals: make(map[*types.Var]llvm.Metadata), dilocals: make(map[*types.Var]llvm.Metadata),
blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock),
blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock),
} }
} }
type blockInfo struct {
// entry is the LLVM basic block corresponding to the start of this *ssa.Block.
entry llvm.BasicBlock
// exit is the LLVM basic block corresponding to the end of this *ssa.Block.
// It will be different than entry if any of the block's instructions contain internal branches.
exit llvm.BasicBlock
// tarjan holds state for applying Tarjan's strongly connected components algorithm to the CFG.
// This is used by defer.go to determine whether to stack- or heap-allocate defer data.
tarjan tarjanNode
}
type deferBuiltin struct { type deferBuiltin struct {
callName string callName string
pos token.Pos pos token.Pos
@@ -1220,14 +1233,29 @@ func (b *builder) createFunctionStart(intrinsic bool) {
// intrinsic (like an atomic operation). Create the entry block // intrinsic (like an atomic operation). Create the entry block
// manually. // manually.
entryBlock = b.ctx.AddBasicBlock(b.llvmFn, "entry") entryBlock = b.ctx.AddBasicBlock(b.llvmFn, "entry")
} else { // Intrinsics may create internal branches (e.g. nil checks).
for _, block := range b.fn.DomPreorder() { // They will attempt to access b.currentBlockInfo to update the exit block.
llvmBlock := b.ctx.AddBasicBlock(b.llvmFn, block.Comment) // Create some fake block info for them to access.
b.blockEntries[block] = llvmBlock blockInfo := []blockInfo{
b.blockExits[block] = llvmBlock {
entry: entryBlock,
exit: entryBlock,
},
} }
b.blockInfo = blockInfo
b.currentBlockInfo = &blockInfo[0]
} else {
blocks := b.fn.Blocks
blockInfo := make([]blockInfo, len(blocks))
for _, block := range b.fn.DomPreorder() {
info := &blockInfo[block.Index]
llvmBlock := b.ctx.AddBasicBlock(b.llvmFn, block.Comment)
info.entry = llvmBlock
info.exit = llvmBlock
}
b.blockInfo = blockInfo
// Normal functions have an entry block. // Normal functions have an entry block.
entryBlock = b.blockEntries[b.fn.Blocks[0]] entryBlock = blockInfo[0].entry
} }
b.SetInsertPointAtEnd(entryBlock) b.SetInsertPointAtEnd(entryBlock)
@@ -1323,8 +1351,9 @@ func (b *builder) createFunction() {
if b.DumpSSA { if b.DumpSSA {
fmt.Printf("%d: %s:\n", block.Index, block.Comment) fmt.Printf("%d: %s:\n", block.Index, block.Comment)
} }
b.SetInsertPointAtEnd(b.blockEntries[block])
b.currentBlock = block b.currentBlock = block
b.currentBlockInfo = &b.blockInfo[block.Index]
b.SetInsertPointAtEnd(b.currentBlockInfo.entry)
for _, instr := range block.Instrs { for _, instr := range block.Instrs {
if instr, ok := instr.(*ssa.DebugRef); ok { if instr, ok := instr.(*ssa.DebugRef); ok {
if !b.Debug { if !b.Debug {
@@ -1384,7 +1413,7 @@ func (b *builder) createFunction() {
block := phi.ssa.Block() block := phi.ssa.Block()
for i, edge := range phi.ssa.Edges { for i, edge := range phi.ssa.Edges {
llvmVal := b.getValue(edge, getPos(phi.ssa)) llvmVal := b.getValue(edge, getPos(phi.ssa))
llvmBlock := b.blockExits[block.Preds[i]] llvmBlock := b.blockInfo[block.Preds[i].Index].exit
phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock}) phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock})
} }
} }
@@ -1498,11 +1527,11 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
case *ssa.If: case *ssa.If:
cond := b.getValue(instr.Cond, getPos(instr)) cond := b.getValue(instr.Cond, getPos(instr))
block := instr.Block() block := instr.Block()
blockThen := b.blockEntries[block.Succs[0]] blockThen := b.blockInfo[block.Succs[0].Index].entry
blockElse := b.blockEntries[block.Succs[1]] blockElse := b.blockInfo[block.Succs[1].Index].entry
b.CreateCondBr(cond, blockThen, blockElse) b.CreateCondBr(cond, blockThen, blockElse)
case *ssa.Jump: case *ssa.Jump:
blockJump := b.blockEntries[instr.Block().Succs[0]] blockJump := b.blockInfo[instr.Block().Succs[0].Index].entry
b.CreateBr(blockJump) b.CreateBr(blockJump)
case *ssa.MapUpdate: case *ssa.MapUpdate:
m := b.getValue(instr.Map, getPos(instr)) m := b.getValue(instr.Map, getPos(instr))
@@ -1570,7 +1599,8 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
elemsLen := b.CreateExtractValue(elems, 1, "append.elemsLen") elemsLen := b.CreateExtractValue(elems, 1, "append.elemsLen")
elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem()) elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem())
elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false)
result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcBuf, elemsBuf, srcLen, srcCap, elemsLen, elemSize}, "append.new") elemLayout := b.createObjectLayout(elemType, pos)
result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcBuf, elemsBuf, srcLen, srcCap, elemsLen, elemSize, elemLayout}, "append.new")
newPtr := b.CreateExtractValue(result, 0, "append.newPtr") newPtr := b.CreateExtractValue(result, 0, "append.newPtr")
newLen := b.CreateExtractValue(result, 1, "append.newLen") newLen := b.CreateExtractValue(result, 1, "append.newLen")
newCap := b.CreateExtractValue(result, 2, "append.newCap") newCap := b.CreateExtractValue(result, 2, "append.newCap")
+98 -28
View File
@@ -100,7 +100,7 @@ func (b *builder) createLandingPad() {
// Continue at the 'recover' block, which returns to the parent in an // Continue at the 'recover' block, which returns to the parent in an
// appropriate way. // appropriate way.
b.CreateBr(b.blockEntries[b.fn.Recover]) b.CreateBr(b.blockInfo[b.fn.Recover.Index].entry)
} }
// Create a checkpoint (similar to setjmp). This emits inline assembly that // Create a checkpoint (similar to setjmp). This emits inline assembly that
@@ -234,41 +234,108 @@ func (b *builder) createInvokeCheckpoint() {
continueBB := b.insertBasicBlock("") continueBB := b.insertBasicBlock("")
b.CreateCondBr(isZero, continueBB, b.landingpad) b.CreateCondBr(isZero, continueBB, b.landingpad)
b.SetInsertPointAtEnd(continueBB) b.SetInsertPointAtEnd(continueBB)
b.blockExits[b.currentBlock] = continueBB b.currentBlockInfo.exit = continueBB
} }
// isInLoop checks if there is a path from a basic block to itself. // isInLoop checks if there is a path from the current block to itself.
func isInLoop(start *ssa.BasicBlock) bool { // Use Tarjan's strongly connected components algorithm to search for cycles.
// Use a breadth-first search to scan backwards through the block graph. // A one-node SCC is a cycle iff there is an edge from the node to itself.
queue := []*ssa.BasicBlock{start} // A multi-node SCC is always a cycle.
checked := map[*ssa.BasicBlock]struct{}{} // https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
func (b *builder) isInLoop() bool {
if b.currentBlockInfo.tarjan.lowLink == 0 {
b.strongConnect(b.currentBlock)
}
return b.currentBlockInfo.tarjan.cyclic
}
for len(queue) > 0 { func (b *builder) strongConnect(block *ssa.BasicBlock) {
// pop a block off of the queue // Assign a new index.
block := queue[len(queue)-1] // Indices start from 1 so that 0 can be used as a sentinel.
queue = queue[:len(queue)-1] assignedIndex := b.tarjanIndex + 1
b.tarjanIndex = assignedIndex
// Search through predecessors. // Apply the new index.
// Searching backwards means that this is pretty fast when the block is close to the start of the function. blockIndex := block.Index
// Defers are often placed near the start of the function. node := &b.blockInfo[blockIndex].tarjan
for _, pred := range block.Preds { node.lowLink = assignedIndex
if pred == start {
// cycle found
return true
}
if _, ok := checked[pred]; ok { // Push the node onto the stack.
// block already checked node.onStack = true
continue b.tarjanStack = append(b.tarjanStack, uint(blockIndex))
}
// add to queue and checked map // Process the successors.
queue = append(queue, pred) for _, successor := range block.Succs {
checked[pred] = struct{}{} // Look up the successor's state.
successorIndex := successor.Index
if successorIndex == blockIndex {
// Handle a self-cycle specially.
node.cyclic = true
continue
}
successorNode := &b.blockInfo[successorIndex].tarjan
switch {
case successorNode.lowLink == 0:
// This node has not yet been visisted.
b.strongConnect(successor)
case !successorNode.onStack:
// This node has been visited, but is in a different SCC.
// Ignore it, and do not update lowLink.
continue
}
// Update the lowLink index.
// This always uses the min-of-lowlink instead of using index in the on-stack case.
// This is done for two reasons:
// 1. The lowLink update can be shared between the new-node and on-stack cases.
// 2. The assigned index does not need to be saved - it is only needed for root node detection.
if successorNode.lowLink < node.lowLink {
node.lowLink = successorNode.lowLink
} }
} }
return false if node.lowLink == assignedIndex {
// This is a root node.
// Pop the SCC off the stack.
stack := b.tarjanStack
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
blocks := b.blockInfo
topNode := &blocks[top].tarjan
topNode.onStack = false
if top != uint(blockIndex) {
// The root node is not the only node in the SCC.
// Mark all nodes in this SCC as cyclic.
topNode.cyclic = true
for top != uint(blockIndex) {
top = stack[len(stack)-1]
stack = stack[:len(stack)-1]
topNode = &blocks[top].tarjan
topNode.onStack = false
topNode.cyclic = true
}
}
b.tarjanStack = stack
}
}
// tarjanNode holds per-block state for isInLoop and strongConnect.
type tarjanNode struct {
// lowLink is the index of the first visited node that is reachable from this block.
// The lowLink indices are assigned by the SCC search, and do not correspond to b.Index.
// A lowLink of 0 is used as a sentinel to mark a node which has not yet been visited.
lowLink uint
// onStack tracks whether this node is currently on the SCC search stack.
onStack bool
// cyclic indicates whether this block is in a loop.
// If lowLink is 0, strongConnect must be called before reading this field.
cyclic bool
} }
// createDefer emits a single defer instruction, to be run when this function // createDefer emits a single defer instruction, to be run when this function
@@ -410,7 +477,10 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Put this struct in an allocation. // Put this struct in an allocation.
var alloca llvm.Value var alloca llvm.Value
if !isInLoop(instr.Block()) { if instr.Block() != b.currentBlock {
panic("block mismatch")
}
if !b.isInLoop() {
// This can safely use a stack allocation. // This can safely use a stack allocation.
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferredCallType, "defer.alloca") alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferredCallType, "defer.alloca")
} else { } else {
+1 -1
View File
@@ -737,7 +737,7 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
prevBlock := b.GetInsertBlock() prevBlock := b.GetInsertBlock()
okBlock := b.insertBasicBlock("typeassert.ok") okBlock := b.insertBasicBlock("typeassert.ok")
nextBlock := b.insertBasicBlock("typeassert.next") nextBlock := b.insertBasicBlock("typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock) b.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was // Retrieve the value from the interface if the type assert was
+16
View File
@@ -27,6 +27,8 @@ func (b *builder) defineIntrinsicFunction() {
b.createStackSaveImpl() b.createStackSaveImpl()
case name == "runtime.KeepAlive": case name == "runtime.KeepAlive":
b.createKeepAliveImpl() b.createKeepAliveImpl()
case name == "machine.keepAliveNoEscape":
b.createMachineKeepAliveImpl()
case strings.HasPrefix(name, "runtime/volatile.Load"): case strings.HasPrefix(name, "runtime/volatile.Load"):
b.createVolatileLoad() b.createVolatileLoad()
case strings.HasPrefix(name, "runtime/volatile.Store"): case strings.HasPrefix(name, "runtime/volatile.Store"):
@@ -144,6 +146,20 @@ func (b *builder) createAbiEscapeImpl() {
b.CreateRet(result) b.CreateRet(result)
} }
// Implement machine.keepAliveNoEscape, which makes sure the compiler keeps the
// pointer parameter alive until this point (for GC).
func (b *builder) createMachineKeepAliveImpl() {
b.createFunctionStart(true)
pointerValue := b.getValue(b.fn.Params[0], getPos(b.fn))
// See createKeepAliveImpl for details.
asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false)
asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false)
b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "")
b.CreateRetVoid()
}
var mathToLLVMMapping = map[string]string{ var mathToLLVMMapping = map[string]string{
"math.Ceil": "llvm.ceil.f64", "math.Ceil": "llvm.ceil.f64",
"math.Exp": "llvm.exp.f64", "math.Exp": "llvm.exp.f64",
+3
View File
@@ -250,6 +250,9 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
func hashmapIsBinaryKey(keyType types.Type) bool { func hashmapIsBinaryKey(keyType types.Type) bool {
switch keyType := keyType.Underlying().(type) { switch keyType := keyType.Underlying().(type) {
case *types.Basic: case *types.Basic:
// TODO: unsafe.Pointer is also a binary key, but to support that we
// need to fix an issue with interp first (see
// https://github.com/tinygo-org/tinygo/pull/4898).
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0 return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
case *types.Pointer: case *types.Pointer:
return true return true
+7
View File
@@ -142,6 +142,11 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0) nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)
llvmFn.AddAttributeAtIndex(i+1, nocapture) llvmFn.AddAttributeAtIndex(i+1, nocapture)
} }
if paramInfo.flags&paramIsReadonly != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
// Readonly pointer parameters (like strings) benefit from being marked as readonly.
readonly := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)
llvmFn.AddAttributeAtIndex(i+1, readonly)
}
} }
// Set a number of function or parameter attributes, depending on the // Set a number of function or parameter attributes, depending on the
@@ -154,6 +159,8 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0)) llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
case "internal/abi.NoEscape": case "internal/abi.NoEscape":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.alloc": case "runtime.alloc":
// Tell the optimizer that runtime.alloc is an allocator, meaning that it // Tell the optimizer that runtime.alloc is an allocator, meaning that it
// returns values that are never null and never alias to an existing value. // returns values that are never null and never alias to an existing value.
+8
View File
@@ -74,6 +74,14 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
return b.CreateCall(fnType, target, args, ""), nil return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "arm" && b.GOOS == "linux": case b.GOARCH == "arm" && b.GOOS == "linux":
if arch := b.archFamily(); arch != "arm" {
// Some targets pretend to be linux/arm for compatibility but aren't
// actually such a system. Make sure we emit an error instead of
// creating inline assembly that will fail to compile.
// See: https://github.com/tinygo-org/tinygo/issues/4959
return llvm.Value{}, b.makeError(call.Pos(), "system calls are not supported: target emulates a linux/arm system on "+arch)
}
// Implement the EABI system call convention for Linux. // Implement the EABI system call convention for Linux.
// Source: syscall(2) man page. // Source: syscall(2) man page.
args := []llvm.Value{} args := []llvm.Value{}
+235
View File
@@ -270,6 +270,241 @@ entry:
ret void ret void
} }
; Function Attrs: nounwind
define hidden void @main.deferInfiniteLoop(ptr %context) unnamed_addr #1 {
entry:
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4
%0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
br label %for.body
for.body: ; preds = %for.body, %entry
%defer.next = load ptr, ptr %deferPtr, align 4
%defer.alloc.call = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4
store i32 0, ptr %defer.alloc.call, align 4
%defer.alloc.call.repack1 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4
store ptr %defer.next, ptr %defer.alloc.call.repack1, align 4
%defer.alloc.call.repack3 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 8
store i32 8, ptr %defer.alloc.call.repack3, align 4
store ptr %defer.alloc.call, ptr %deferPtr, align 4
br label %for.body
recover: ; preds = %rundefers.end
ret void
lpad: ; No predecessors!
br label %rundefers.loophead
rundefers.loophead: ; preds = %rundefers.callback0, %lpad
br i1 poison, label %rundefers.end, label %rundefers.loop
rundefers.loop: ; preds = %rundefers.loophead
switch i32 poison, label %rundefers.default [
i32 0, label %rundefers.callback0
]
rundefers.callback0: ; preds = %rundefers.loop
br label %rundefers.loophead
rundefers.default: ; preds = %rundefers.loop
unreachable
rundefers.end: ; preds = %rundefers.loophead
br label %recover
}
; Function Attrs: nounwind
define hidden void @main.deferLoop(ptr %context) unnamed_addr #1 {
entry:
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4
%0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
br label %for.loop
for.loop: ; preds = %for.body, %entry
%1 = phi i32 [ 0, %entry ], [ %3, %for.body ]
%2 = icmp slt i32 %1, 10
br i1 %2, label %for.body, label %for.done
for.body: ; preds = %for.loop
%defer.next = load ptr, ptr %deferPtr, align 4
%defer.alloc.call = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4
store i32 0, ptr %defer.alloc.call, align 4
%defer.alloc.call.repack13 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4
store ptr %defer.next, ptr %defer.alloc.call.repack13, align 4
%defer.alloc.call.repack15 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 8
store i32 %1, ptr %defer.alloc.call.repack15, align 4
store ptr %defer.alloc.call, ptr %deferPtr, align 4
%3 = add i32 %1, 1
br label %for.loop
for.done: ; preds = %for.loop
br label %rundefers.block
rundefers.after: ; preds = %rundefers.end
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
ret void
rundefers.block: ; preds = %for.done
br label %rundefers.loophead
rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block
%4 = load ptr, ptr %deferPtr, align 4
%stackIsNil = icmp eq ptr %4, null
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
rundefers.loop: ; preds = %rundefers.loophead
%stack.next.gep = getelementptr inbounds nuw i8, ptr %4, i32 4
%stack.next = load ptr, ptr %stack.next.gep, align 4
store ptr %stack.next, ptr %deferPtr, align 4
%callback = load i32, ptr %4, align 4
switch i32 %callback, label %rundefers.default [
i32 0, label %rundefers.callback0
]
rundefers.callback0: ; preds = %rundefers.loop
%gep = getelementptr inbounds nuw i8, ptr %4, i32 8
%param = load i32, ptr %gep, align 4
call void @runtime.printlock(ptr undef) #4
call void @runtime.printint32(i32 %param, ptr undef) #4
call void @runtime.printunlock(ptr undef) #4
br label %rundefers.loophead
rundefers.default: ; preds = %rundefers.loop
unreachable
rundefers.end: ; preds = %rundefers.loophead
br label %rundefers.after
recover: ; preds = %rundefers.end1
ret void
lpad: ; No predecessors!
br label %rundefers.loophead4
rundefers.loophead4: ; preds = %rundefers.callback010, %lpad
br i1 poison, label %rundefers.end1, label %rundefers.loop3
rundefers.loop3: ; preds = %rundefers.loophead4
switch i32 poison, label %rundefers.default2 [
i32 0, label %rundefers.callback010
]
rundefers.callback010: ; preds = %rundefers.loop3
br label %rundefers.loophead4
rundefers.default2: ; preds = %rundefers.loop3
unreachable
rundefers.end1: ; preds = %rundefers.loophead4
br label %recover
}
; Function Attrs: nounwind
define hidden void @main.deferBetweenLoops(ptr %context) unnamed_addr #1 {
entry:
%defer.alloca = alloca { i32, ptr, i32 }, align 4
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4
%0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
br label %for.loop
for.loop: ; preds = %for.body, %entry
%1 = phi i32 [ 0, %entry ], [ %3, %for.body ]
%2 = icmp slt i32 %1, 10
br i1 %2, label %for.body, label %for.done
for.body: ; preds = %for.loop
%3 = add i32 %1, 1
br label %for.loop
for.done: ; preds = %for.loop
%defer.next = load ptr, ptr %deferPtr, align 4
store i32 0, ptr %defer.alloca, align 4
%defer.alloca.repack16 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
store ptr %defer.next, ptr %defer.alloca.repack16, align 4
%defer.alloca.repack18 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 8
store i32 1, ptr %defer.alloca.repack18, align 4
store ptr %defer.alloca, ptr %deferPtr, align 4
br label %for.loop1
for.loop1: ; preds = %for.body2, %for.done
%4 = phi i32 [ 0, %for.done ], [ %6, %for.body2 ]
%5 = icmp slt i32 %4, 10
br i1 %5, label %for.body2, label %for.done3
for.body2: ; preds = %for.loop1
%6 = add i32 %4, 1
br label %for.loop1
for.done3: ; preds = %for.loop1
br label %rundefers.block
rundefers.after: ; preds = %rundefers.end
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
ret void
rundefers.block: ; preds = %for.done3
br label %rundefers.loophead
rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block
%7 = load ptr, ptr %deferPtr, align 4
%stackIsNil = icmp eq ptr %7, null
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
rundefers.loop: ; preds = %rundefers.loophead
%stack.next.gep = getelementptr inbounds nuw i8, ptr %7, i32 4
%stack.next = load ptr, ptr %stack.next.gep, align 4
store ptr %stack.next, ptr %deferPtr, align 4
%callback = load i32, ptr %7, align 4
switch i32 %callback, label %rundefers.default [
i32 0, label %rundefers.callback0
]
rundefers.callback0: ; preds = %rundefers.loop
%gep = getelementptr inbounds nuw i8, ptr %7, i32 8
%param = load i32, ptr %gep, align 4
call void @runtime.printlock(ptr undef) #4
call void @runtime.printint32(i32 %param, ptr undef) #4
call void @runtime.printunlock(ptr undef) #4
br label %rundefers.loophead
rundefers.default: ; preds = %rundefers.loop
unreachable
rundefers.end: ; preds = %rundefers.loophead
br label %rundefers.after
recover: ; preds = %rundefers.end4
ret void
lpad: ; No predecessors!
br label %rundefers.loophead7
rundefers.loophead7: ; preds = %rundefers.callback013, %lpad
br i1 poison, label %rundefers.end4, label %rundefers.loop6
rundefers.loop6: ; preds = %rundefers.loophead7
switch i32 poison, label %rundefers.default5 [
i32 0, label %rundefers.callback013
]
rundefers.callback013: ; preds = %rundefers.loop6
br label %rundefers.loophead7
rundefers.default5: ; preds = %rundefers.loop6
unreachable
rundefers.end4: ; preds = %rundefers.loophead7
br label %recover
}
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" }
attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" }
attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" }
+20
View File
@@ -18,3 +18,23 @@ func deferMultiple() {
}() }()
external() external()
} }
func deferInfiniteLoop() {
for {
defer print(8)
}
}
func deferLoop() {
for i := 0; i < 10; i++ {
defer print(i)
}
}
func deferBetweenLoops() {
for i := 0; i < 10; i++ {
}
defer print(1)
for i := 0; i < 10; i++ {
}
}
+1 -1
View File
@@ -50,7 +50,7 @@ unsafe.String.throw: ; preds = %entry
declare void @runtime.unsafeSlicePanic(ptr) #1 declare void @runtime.unsafeSlicePanic(ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden ptr @main.unsafeStringData(ptr %s.data, i32 %s.len, ptr %context) unnamed_addr #2 { define hidden ptr @main.unsafeStringData(ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #2 {
entry: entry:
%stackalloc = alloca i8, align 1 %stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr %s.data, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr %s.data, ptr nonnull %stackalloc, ptr undef) #3
+3 -3
View File
@@ -77,7 +77,7 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden %runtime._string @main.minString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { define hidden %runtime._string @main.minString(ptr readonly %a.data, i32 %a.len, ptr readonly %b.data, i32 %b.len, ptr %context) unnamed_addr #2 {
entry: entry:
%0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0
%1 = insertvalue %runtime._string %0, i32 %a.len, 1 %1 = insertvalue %runtime._string %0, i32 %a.len, 1
@@ -91,7 +91,7 @@ entry:
ret %runtime._string %5 ret %runtime._string %5
} }
declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) #1 declare i1 @runtime.stringLess(ptr readonly, i32, ptr readonly, i32, ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.maxInt(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { define hidden i32 @main.maxInt(i32 %a, i32 %b, ptr %context) unnamed_addr #2 {
@@ -116,7 +116,7 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden %runtime._string @main.maxString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { define hidden %runtime._string @main.maxString(ptr readonly %a.data, i32 %a.len, ptr readonly %b.data, i32 %b.len, ptr %context) unnamed_addr #2 {
entry: entry:
%0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0
%1 = insertvalue %runtime._string %0, i32 %a.len, 1 %1 = insertvalue %runtime._string %0, i32 %a.len, 1
+3 -3
View File
@@ -55,7 +55,7 @@ entry:
store i32 2, ptr %0, align 4 store i32 2, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %varargs, i32 8 %1 = getelementptr inbounds nuw i8, ptr %varargs, i32 8
store i32 3, ptr %1, align 4 store i32 3, ptr %1, align 4
%append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, ptr undef) #3 %append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
%append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0 %append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0
%append.newLen = extractvalue { ptr, i32, i32 } %append.new, 1 %append.newLen = extractvalue { ptr, i32, i32 } %append.new, 1
%append.newCap = extractvalue { ptr, i32, i32 } %append.new, 2 %append.newCap = extractvalue { ptr, i32, i32 } %append.new, 2
@@ -66,13 +66,13 @@ entry:
ret { ptr, i32, i32 } %4 ret { ptr, i32, i32 } %4
} }
declare { ptr, i32, i32 } @runtime.sliceAppend(ptr, ptr nocapture readonly, i32, i32, i32, i32, ptr) #1 declare { ptr, i32, i32 } @runtime.sliceAppend(ptr, ptr nocapture readonly, i32, i32, i32, i32, ptr, ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden { ptr, i32, i32 } @main.sliceAppendSlice(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %added.data, i32 %added.len, i32 %added.cap, ptr %context) unnamed_addr #2 { define hidden { ptr, i32, i32 } @main.sliceAppendSlice(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %added.data, i32 %added.len, i32 %added.cap, ptr %context) unnamed_addr #2 {
entry: entry:
%stackalloc = alloca i8, align 1 %stackalloc = alloca i8, align 1
%append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr %added.data, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, ptr undef) #3 %append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr %added.data, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
%append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0 %append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0
%append.newLen = extractvalue { ptr, i32, i32 } %append.new, 1 %append.newLen = extractvalue { ptr, i32, i32 } %append.new, 1
%append.newCap = extractvalue { ptr, i32, i32 } %append.new, 2 %append.newCap = extractvalue { ptr, i32, i32 } %append.new, 2
+8 -8
View File
@@ -31,13 +31,13 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.stringLen(ptr %s.data, i32 %s.len, ptr %context) unnamed_addr #2 { define hidden i32 @main.stringLen(ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #2 {
entry: entry:
ret i32 %s.len ret i32 %s.len
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i8 @main.stringIndex(ptr %s.data, i32 %s.len, i32 %index, ptr %context) unnamed_addr #2 { define hidden i8 @main.stringIndex(ptr readonly %s.data, i32 %s.len, i32 %index, ptr %context) unnamed_addr #2 {
entry: entry:
%.not = icmp ult i32 %index, %s.len %.not = icmp ult i32 %index, %s.len
br i1 %.not, label %lookup.next, label %lookup.throw br i1 %.not, label %lookup.next, label %lookup.throw
@@ -55,16 +55,16 @@ lookup.throw: ; preds = %entry
declare void @runtime.lookupPanic(ptr) #1 declare void @runtime.lookupPanic(ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.stringCompareEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { define hidden i1 @main.stringCompareEqual(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 {
entry: entry:
%0 = call i1 @runtime.stringEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr undef) #3 %0 = call i1 @runtime.stringEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr undef) #3
ret i1 %0 ret i1 %0
} }
declare i1 @runtime.stringEqual(ptr, i32, ptr, i32, ptr) #1 declare i1 @runtime.stringEqual(ptr readonly, i32, ptr readonly, i32, ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.stringCompareUnequal(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { define hidden i1 @main.stringCompareUnequal(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 {
entry: entry:
%0 = call i1 @runtime.stringEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr undef) #3 %0 = call i1 @runtime.stringEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr undef) #3
%1 = xor i1 %0, true %1 = xor i1 %0, true
@@ -72,16 +72,16 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.stringCompareLarger(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 { define hidden i1 @main.stringCompareLarger(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 {
entry: entry:
%0 = call i1 @runtime.stringLess(ptr %s2.data, i32 %s2.len, ptr %s1.data, i32 %s1.len, ptr undef) #3 %0 = call i1 @runtime.stringLess(ptr %s2.data, i32 %s2.len, ptr %s1.data, i32 %s1.len, ptr undef) #3
ret i1 %0 ret i1 %0
} }
declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) #1 declare i1 @runtime.stringLess(ptr readonly, i32, ptr readonly, i32, ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i8 @main.stringLookup(ptr %s.data, i32 %s.len, i8 %x, ptr %context) unnamed_addr #2 { define hidden i8 @main.stringLookup(ptr readonly %s.data, i32 %s.len, i8 %x, ptr %context) unnamed_addr #2 {
entry: entry:
%0 = zext i8 %x to i32 %0 = zext i8 %x to i32
%.not = icmp ugt i32 %s.len, %0 %.not = icmp ugt i32 %s.len, %0
+1 -1
View File
@@ -10,7 +10,7 @@ import (
// Version of TinyGo. // Version of TinyGo.
// Update this value before release of new version of software. // Update this value before release of new version of software.
const version = "0.40.0-dev" const version = "0.40.1"
// Return TinyGo version, either in the form 0.30.0 or as a development version // Return TinyGo version, either in the form 0.30.0 or as a development version
// (like 0.30.0-dev-abcd012). // (like 0.30.0-dev-abcd012).
+12
View File
@@ -1635,6 +1635,7 @@ func main() {
cpuprofile := flag.String("cpuprofile", "", "cpuprofile output") cpuprofile := flag.String("cpuprofile", "", "cpuprofile output")
monitor := flag.Bool("monitor", false, "enable serial monitor") monitor := flag.Bool("monitor", false, "enable serial monitor")
baudrate := flag.Int("baudrate", 115200, "baudrate of serial monitor") baudrate := flag.Int("baudrate", 115200, "baudrate of serial monitor")
gocompatibility := flag.Bool("go-compatibility", true, "enable to check for Go versions compatibility, you can also configure this by setting the TINYGO_GOCOMPATIBILITY environment variable")
// Internal flags, that are only intended for TinyGo development. // Internal flags, that are only intended for TinyGo development.
printIR := flag.Bool("internal-printir", false, "print LLVM IR") printIR := flag.Bool("internal-printir", false, "print LLVM IR")
@@ -1712,6 +1713,16 @@ func main() {
ocdCommands = strings.Split(*ocdCommandsString, ",") ocdCommands = strings.Split(*ocdCommandsString, ",")
} }
val, ok := os.LookupEnv("TINYGO_GOCOMPATIBILITY")
if ok {
b, err := strconv.ParseBool(val)
if err != nil {
fmt.Fprintf(os.Stderr, "could not parse TINYGO_GOCOMPATIBILITY value %q: %v\n", val, err)
os.Exit(1)
}
*gocompatibility = b
}
options := &compileopts.Options{ options := &compileopts.Options{
GOOS: goenv.Get("GOOS"), GOOS: goenv.Get("GOOS"),
GOARCH: goenv.Get("GOARCH"), GOARCH: goenv.Get("GOARCH"),
@@ -1748,6 +1759,7 @@ func main() {
Timeout: *timeout, Timeout: *timeout,
WITPackage: witPackage, WITPackage: witPackage,
WITWorld: witWorld, WITWorld: witWorld,
GoCompatibility: *gocompatibility,
} }
if *printCommands { if *printCommands {
options.PrintCommands = printCommand options.PrintCommands = printCommand
+10 -5
View File
@@ -1745,7 +1745,7 @@ func memzero(ptr unsafe.Pointer, size uintptr)
func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
//go:linkname sliceAppend runtime.sliceAppend //go:linkname sliceAppend runtime.sliceAppend
func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr, layout unsafe.Pointer) (unsafe.Pointer, uintptr, uintptr)
//go:linkname sliceCopy runtime.sliceCopy //go:linkname sliceCopy runtime.sliceCopy
func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int
@@ -1810,7 +1810,7 @@ func buflen(v Value) (unsafe.Pointer, uintptr) {
} }
//go:linkname sliceGrow runtime.sliceGrow //go:linkname sliceGrow runtime.sliceGrow
func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr, layout unsafe.Pointer) (unsafe.Pointer, uintptr, uintptr)
// extend slice to hold n new elements // extend slice to hold n new elements
func extendSlice(v Value, n int) sliceHeader { func extendSlice(v Value, n int) sliceHeader {
@@ -1823,7 +1823,10 @@ func extendSlice(v Value, n int) sliceHeader {
old = *(*sliceHeader)(v.value) old = *(*sliceHeader)(v.value)
} }
nbuf, nlen, ncap := sliceGrow(old.data, old.len, old.cap, old.len+uintptr(n), v.typecode.elem().Size()) elem := v.typecode.elem()
elemSize := elem.Size()
elemLayout := elem.gcLayout()
nbuf, nlen, ncap := sliceGrow(old.data, old.len, old.cap, old.len+uintptr(n), elemSize, elemLayout)
return sliceHeader{ return sliceHeader{
data: nbuf, data: nbuf,
@@ -1862,8 +1865,10 @@ func AppendSlice(s, t Value) Value {
} }
sSlice := (*sliceHeader)(s.value) sSlice := (*sliceHeader)(s.value)
tSlice := (*sliceHeader)(t.value) tSlice := (*sliceHeader)(t.value)
elemSize := s.typecode.elem().Size() elem := s.typecode.elem()
ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize) elemSize := elem.Size()
elemLayout := elem.gcLayout()
ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize, elemLayout)
result := &sliceHeader{ result := &sliceHeader{
data: ptr, data: ptr,
len: len, len: len,
+7
View File
@@ -108,6 +108,10 @@ func (*stackState) unwind()
func (t *Task) Resume() { func (t *Task) Resume() {
// The current task must be saved and restored because this can nest on WASM with JS. // The current task must be saved and restored because this can nest on WASM with JS.
prevTask := currentTask prevTask := currentTask
if prevTask == nil {
// Save the system stack pointer.
saveStackPointer()
}
t.gcData.swap() t.gcData.swap()
currentTask = t currentTask = t
if !t.state.launched { if !t.state.launched {
@@ -123,6 +127,9 @@ func (t *Task) Resume() {
} }
} }
//go:linkname saveStackPointer runtime.saveStackPointer
func saveStackPointer()
//export tinygo_rewind //export tinygo_rewind
func (*state) rewind() func (*state) rewind()
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build scheduler.tasks && esp32 //go:build scheduler.tasks && (esp32 || esp32s3)
package task package task
+6
View File
@@ -120,15 +120,21 @@ int tinygo_task_start(uintptr_t fn, void *args, void *task, pthread_t *thread, u
#endif #endif
pthread_attr_t attrs; pthread_attr_t attrs;
pthread_attr_init(&attrs); pthread_attr_init(&attrs);
pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
pthread_attr_setstacksize(&attrs, stackSize); pthread_attr_setstacksize(&attrs, stackSize);
int result = pthread_create(thread, &attrs, &start_wrapper, &state); int result = pthread_create(thread, &attrs, &start_wrapper, &state);
pthread_attr_destroy(&attrs); pthread_attr_destroy(&attrs);
if (result != 0) {
return result;
}
// Wait until the thread has been created and read all state_pass variables. // Wait until the thread has been created and read all state_pass variables.
#if __APPLE__ #if __APPLE__
dispatch_semaphore_wait(state.startlock, DISPATCH_TIME_FOREVER); dispatch_semaphore_wait(state.startlock, DISPATCH_TIME_FOREVER);
dispatch_release(state.startlock);
#else #else
sem_wait(&state.startlock); sem_wait(&state.startlock);
sem_destroy(&state.startlock);
#endif #endif
return result; return result;
+102 -66
View File
@@ -23,16 +23,15 @@ type state struct {
// is needed to be able to scan the stack. // is needed to be able to scan the stack.
stackTop uintptr stackTop uintptr
// Lowest address of the stack.
// This is populated when the thread is stopped by the GC.
stackBottom uintptr
// Next task in the activeTasks queue. // Next task in the activeTasks queue.
QueueNext *Task QueueNext *Task
// Semaphore to pause/resume the thread atomically. // Semaphore to pause/resume the thread atomically.
pauseSem Semaphore pauseSem Semaphore
// Semaphore used for stack scanning.
// We can't reuse pauseSem here since the thread might have been paused for
// other reasons (for example, because it was waiting on a channel).
gcSem Semaphore
} }
// Goroutine counter, starting at 0 for the main goroutine. // Goroutine counter, starting at 0 for the main goroutine.
@@ -96,6 +95,9 @@ func (t *Task) Resume() {
t.state.pauseSem.Post() t.state.pauseSem.Post()
} }
// otherGoroutines is the total number of live goroutines minus one.
var otherGoroutines uint32
// Start a new OS thread. // Start a new OS thread.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
t := &Task{} t := &Task{}
@@ -115,6 +117,7 @@ func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
} }
t.state.QueueNext = activeTasks t.state.QueueNext = activeTasks
activeTasks = t activeTasks = t
otherGoroutines++
activeTaskLock.Unlock() activeTaskLock.Unlock()
} }
@@ -135,6 +138,7 @@ func taskExited(t *Task) {
break break
} }
} }
otherGoroutines--
activeTaskLock.Unlock() activeTaskLock.Unlock()
// Sanity check. // Sanity check.
@@ -143,9 +147,42 @@ func taskExited(t *Task) {
} }
} }
// Futex to wait on until all tasks have finished scanning the stack. // scanWaitGroup is used to wait on until all threads have finished the current state transition.
// This is basically a sync.WaitGroup. var scanWaitGroup waitGroup
var scanDoneFutex Futex
type waitGroup struct {
f Futex
}
func initWaitGroup(n uint32) waitGroup {
var wg waitGroup
wg.f.Store(n)
return wg
}
func (wg *waitGroup) done() {
if wg.f.Add(^uint32(0)) == 0 {
wg.f.WakeAll()
}
}
func (wg *waitGroup) wait() {
for {
val := wg.f.Load()
if val == 0 {
return
}
wg.f.Wait(val)
}
}
// gcState is used to track and notify threads when the GC is stopping/resuming.
var gcState Futex
const (
gcStateResumed = iota
gcStateStopped
)
// GC scan phase. Because we need to stop the world while scanning, this kinda // GC scan phase. Because we need to stop the world while scanning, this kinda
// needs to be done in the tasks package. // needs to be done in the tasks package.
@@ -155,65 +192,71 @@ var scanDoneFutex Futex
func GCStopWorldAndScan() { func GCStopWorldAndScan() {
current := Current() current := Current()
// Don't allow new goroutines to be started while pausing/resuming threads // NOTE: This does not need to be atomic.
// in the stop-the-world phase. if gcState.Load() == gcStateResumed {
activeTaskLock.Lock() // Don't allow new goroutines to be started while pausing/resuming threads
// in the stop-the-world phase.
activeTaskLock.Lock()
// Pause all other threads. // Wait for threads to finish resuming.
numOtherThreads := uint32(0) scanWaitGroup.wait()
for t := activeTasks; t != nil; t = t.state.QueueNext {
if t != current { // Change the gc state to stopped.
numOtherThreads++ // NOTE: This does not need to be atomic.
tinygo_task_send_gc_signal(t.state.thread) gcState.Store(gcStateStopped)
// Set the number of threads to wait for.
scanWaitGroup = initWaitGroup(otherGoroutines)
// Pause all other threads.
for t := activeTasks; t != nil; t = t.state.QueueNext {
if t != current {
tinygo_task_send_gc_signal(t.state.thread)
}
} }
// Wait for the threads to finish stopping.
scanWaitGroup.wait()
} }
// Store the number of threads to wait for in the futex. // Scan other thread stacks.
// This is the equivalent of doing an initial wg.Add(numOtherThreads). for t := activeTasks; t != nil; t = t.state.QueueNext {
scanDoneFutex.Store(numOtherThreads) if t != current {
markRoots(t.state.stackBottom, t.state.stackTop)
}
}
// Scan the current stack, and all current registers. // Scan the current stack, and all current registers.
scanCurrentStack() scanCurrentStack()
// Wake each paused thread for the first time so it will scan the stack.
for t := activeTasks; t != nil; t = t.state.QueueNext {
if t != current {
t.state.gcSem.Post()
}
}
// Wait until all threads have finished scanning their stack.
// This is the equivalent of wg.Wait()
for {
val := scanDoneFutex.Load()
if val == 0 {
break
}
scanDoneFutex.Wait(val)
}
// Scan all globals (implemented in the runtime). // Scan all globals (implemented in the runtime).
gcScanGlobals() gcScanGlobals()
} }
// After the GC is done scanning, resume all other threads. // After the GC is done scanning, resume all other threads.
//
// This must only be called after a GCStopWorldAndScan call.
func GCResumeWorld() { func GCResumeWorld() {
current := Current() // NOTE: This does not need to be atomic.
if gcState.Load() == gcStateResumed {
// Wake each paused thread for the second time, so they will resume normal // This is already resumed.
// operation. return
for t := activeTasks; t != nil; t = t.state.QueueNext {
if t != current {
t.state.gcSem.Post()
}
} }
// Set the wait group to track resume progress.
scanWaitGroup = initWaitGroup(otherGoroutines)
// Set the state to resumed.
gcState.Store(gcStateResumed)
// Wake all of the stopped threads.
gcState.WakeAll()
// Allow goroutines to start and exit again. // Allow goroutines to start and exit again.
activeTaskLock.Unlock() activeTaskLock.Unlock()
} }
//go:linkname markRoots runtime.markRoots
func markRoots(start, end uintptr)
// Scan globals, implemented in the runtime package. // Scan globals, implemented in the runtime package.
func gcScanGlobals() func gcScanGlobals()
@@ -221,34 +264,27 @@ var stackScanLock PMutex
//export tinygo_task_gc_pause //export tinygo_task_gc_pause
func tingyo_task_gc_pause(sig int32) { func tingyo_task_gc_pause(sig int32) {
// Wait until we get the signal to start scanning the stack. // Write the entrty stack pointer to the state.
Current().state.gcSem.Wait() Current().state.stackBottom = uintptr(stacksave())
// Scan the thread stack. // Notify the GC that we are stopped.
// Only scan a single thread stack at a time, because the GC marking phase scanWaitGroup.done()
// doesn't support parallelism.
// TODO: it may be possible to call markRoots directly (without saving
// registers) since we are in a signal handler that already saved a bunch of
// registers. This is an optimization left for a future time.
stackScanLock.Lock()
scanCurrentStack()
stackScanLock.Unlock()
// Equivalent of wg.Done(): subtract one from the futex and if the result is // Wait for the GC to resume.
// 0 (meaning we were the last in the waitgroup), wake the waiting thread. for gcState.Load() == gcStateStopped {
n := uint32(1) gcState.Wait(gcStateStopped)
if scanDoneFutex.Add(-n) == 0 {
scanDoneFutex.Wake()
} }
// Wait until we get the signal we can resume normally (after the mark phase // Notify the GC that we have resumed.
// has finished). scanWaitGroup.done()
Current().state.gcSem.Wait()
} }
//go:export tinygo_scanCurrentStack //go:export tinygo_scanCurrentStack
func scanCurrentStack() func scanCurrentStack()
//go:linkname stacksave runtime.stacksave
func stacksave() unsafe.Pointer
// Return the highest address of the current stack. // Return the highest address of the current stack.
func StackTop() uintptr { func StackTop() uintptr {
return Current().state.stackTop return Current().state.stackTop
+173
View File
@@ -0,0 +1,173 @@
//go:build pico2_ice
// Most of the info is from
// https://pico2-ice.tinyvision.ai/md_pinout.html although
// (2025-09-07) RP4 appears twice in that pinout - the schematic is
// more clear. Consistent with other RPi boards, we use GPn instead of
// RPn to reference the RPi connected pins.
package machine
// GPIO pins
const (
GP0 = GPIO0
GP1 = GPIO1
GP2 = GPIO2
GP3 = GPIO3
GP4 = GPIO4
GP5 = GPIO5
GP6 = GPIO6
GP7 = GPIO7
GP8 = GPIO8
GP9 = GPIO9
GP10 = GPIO10
GP11 = GPIO11
GP12 = GPIO12
GP13 = GPIO13
GP14 = GPIO14
GP15 = GPIO15
GP16 = GPIO16
GP17 = GPIO17
GP18 = GPIO18
GP19 = GPIO19
GP20 = GPIO20
GP21 = GPIO21
GP22 = GPIO22
GP23 = GPIO23
GP24 = GPIO24
GP25 = GPIO25
GP26 = GPIO26
GP27 = GPIO27
GP28 = GPIO28
GP29 = GPIO29
GP30 = GPIO30
GP31 = GPIO31
GP32 = GPIO32
GP33 = GPIO33
GP34 = GPIO34
GP35 = GPIO35
GP36 = GPIO36
GP37 = GPIO37
GP38 = GPIO38
GP39 = GPIO39
GP40 = GPIO40
GP41 = GPIO41
GP42 = GPIO42
GP43 = GPIO43
GP44 = GPIO44
GP45 = GPIO45
GP46 = GPIO46
GP47 = GPIO47
// RPi pins shared with ICE. The ICE number is what appears on
// the board silkscreen.
ICE9 = GP28
ICE11 = GP29
ICE14 = GP7
ICE15 = GP6
ICE16 = GP5
ICE17 = GP4
ICE18 = GP27
ICE19 = GP23
ICE20 = GP22
ICE21 = GP26
ICE23 = GP25
ICE25 = GP30
ICE26 = GP24
ICE27 = GP20
// FPGA Clock pin.
ICE35_G0 = GP21
// Silkscreen & Pinout names
ICE_SSN = ICE16
ICE_SO = ICE14
ICE_SI = ICE17
ICE_CK = ICE15
SD = GP2
SC = GP3
FPGA_RSTN = GP31
A3 = GP32
A1 = GP33
A4 = GP34
A2 = GP35
B3 = GP36
B1 = GP37
B4 = GP38
B2 = GP39
N0 = GP40 // On the board these are labeled "~0"
N1 = GP41
N2 = GP42
N3 = GP43
N4 = GP44
N5 = GP45
N6 = GP46
// Functions from Schematic.
ICE_DONE = GP40
USB_BOOT = GP42
// Button
SW1 = GP42
BOOTSEL = GP42
// Tricolor LEDs
LED_RED = GP1
LED_GREEN = GP0
LED_BLUE = GP9
// Onboard LED
LED = LED_GREEN
// Onboard crystal oscillator frequency, in MHz.
xoscFreq = 12 // MHz
)
// This board does not define default i2c pins.
const (
I2C0_SDA_PIN = NoPin
I2C0_SCL_PIN = NoPin
I2C1_SDA_PIN = NoPin
I2C1_SCL_PIN = NoPin
)
// 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
)
+58
View File
@@ -0,0 +1,58 @@
//go:build xiao_esp32s3
// This file contains the pin mappings for the Seeed XIAO ESP32S3 boards.
//
// Seeed Studio XIAO ESP32S3 is an IoT mini development board based on
// the Espressif ESP32-S3 WiFi/Bluetooth dual-mode chip.
//
// - https://www.seeedstudio.com/XIAO-ESP32S3-p-5627.html
// - https://wiki.seeedstudio.com/xiao_esp32s3_getting_started/
package machine
// Digital Pins
const (
D0 = GPIO1
D1 = GPIO2
D2 = GPIO3
D3 = GPIO4
D4 = GPIO5
D5 = GPIO6
D6 = GPIO43
D7 = GPIO44
D8 = GPIO7
D9 = GPIO8
D10 = GPIO9
)
// Analog pins
const (
A0 = GPIO1
A1 = GPIO2
A2 = GPIO3
A3 = GPIO4
)
// UART pins
const (
UART_RX_PIN = GPIO44
UART_TX_PIN = GPIO43
)
// I2C pins
const (
SDA_PIN = GPIO5
SCL_PIN = GPIO6
)
// SPI pins
const (
SPI_SCK_PIN = GPIO7
SPI_SDI_PIN = GPIO9
SPI_SDO_PIN = GPIO8
)
// Onboard LEDs
const (
LED = GPIO21
)
+5
View File
@@ -43,6 +43,11 @@ type BlockDevice interface {
io.ReaderAt io.ReaderAt
// WriteAt writes the given number of bytes to the block device. // WriteAt writes the given number of bytes to the block device.
//
// This interface directly writes data to the underlying block device.
// Different kinds of devices have different requirements: most can only
// write data after the page has been erased, and many can only write data
// with specific alignment (such as 4-byte alignment).
io.WriterAt io.WriterAt
// Size returns the number of bytes in this block device. // Size returns the number of bytes in this block device.
+31 -1
View File
@@ -1,6 +1,9 @@
package machine package machine
import "errors" import (
"errors"
"unsafe"
)
var ( var (
ErrTimeoutRNG = errors.New("machine: RNG Timeout") ErrTimeoutRNG = errors.New("machine: RNG Timeout")
@@ -62,3 +65,30 @@ func (p Pin) Low() {
type ADC struct { type ADC struct {
Pin Pin Pin Pin
} }
// Convert the pointer to a uintptr, to be used for memory I/O (DMA for
// example). It also means the pointer is "gone" as far as the compiler is
// concerned, and a GC cycle might deallocate the object. To prevent this from
// happening, also call keepAliveNoEscape at a point after the address isn't
// accessed anymore by the hardware.
// The only exception is if the pointer is accessed later in a volatile way
// (volatile read/write), which also forces the value to stay alive until that
// point.
//
// This function is treated specially by the compiler to mark the 'ptr'
// parameter as not escaping.
//
// TODO: this function should eventually be replaced with the proposed ptrtoaddr
// instruction in LLVM. See:
// https://discourse.llvm.org/t/clarifiying-the-semantics-of-ptrtoint/83987/10
// https://github.com/llvm/llvm-project/pull/139357
func unsafeNoEscape(ptr unsafe.Pointer) uintptr {
return uintptr(ptr)
}
// Make sure the given pointer stays alive until this point. This is similar to
// runtime.KeepAlive, with the difference that it won't let the pointer escape.
// This is typically used together with unsafeNoEscape.
//
// This is a compiler intrinsic.
func keepAliveNoEscape(ptr unsafe.Pointer)
+17 -16
View File
@@ -337,7 +337,7 @@ func handleUSBSetAddress(setup usb.Setup) bool {
// SendUSBInPacket sends a packet for USB (interrupt in / bulk in). // SendUSBInPacket sends a packet for USB (interrupt in / bulk in).
func SendUSBInPacket(ep uint32, data []byte) bool { func SendUSBInPacket(ep uint32, data []byte) bool {
sendUSBPacket(ep, data, 0) sendUSBPacket(ep, data)
// clear transfer complete flag // clear transfer complete flag
setEPINTFLAG(ep, sam.USB_DEVICE_EPINTFLAG_TRCPT1) setEPINTFLAG(ep, sam.USB_DEVICE_EPINTFLAG_TRCPT1)
@@ -351,27 +351,28 @@ func SendUSBInPacket(ep uint32, data []byte) bool {
// Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998 // Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998
// //
//go:noinline //go:noinline
func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { func sendUSBPacket(ep uint32, data []byte) {
l := uint16(len(data)) // Select the corresponding buffer.
if 0 < maxsize && maxsize < l { buffer := udd_ep_control_cache_buffer[:]
l = maxsize if ep != 0 {
buffer = udd_ep_in_cache_buffer[ep][:]
} }
// Set endpoint address for sending data // Copy the packet to the buffer.
if ep == 0 { copy(buffer[:len(data)], data)
copy(udd_ep_control_cache_buffer[:], data[:l])
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_control_cache_buffer)))) // Select the corresponding endpoint descriptor.
} else { endpoint := &usbEndpointDescriptors[ep].DeviceDescBank[1]
copy(udd_ep_in_cache_buffer[ep][:], data[:l])
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) // Set the endpoint address.
} endpoint.ADDR.Set(uint32(uintptr(unsafe.Pointer(unsafe.SliceData(buffer)))))
// clear multi-packet size which is total bytes already sent // clear multi-packet size which is total bytes already sent
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos)
// set byte count, which is total number of bytes to be sent // set byte count, which is total number of bytes to be sent
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits((uint32(l) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) endpoint.PCKSIZE.SetBits((uint32(len(data)) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
} }
func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
+17 -16
View File
@@ -340,7 +340,7 @@ func handleUSBSetAddress(setup usb.Setup) bool {
// SendUSBInPacket sends a packet for USB (interrupt in / bulk in). // SendUSBInPacket sends a packet for USB (interrupt in / bulk in).
func SendUSBInPacket(ep uint32, data []byte) bool { func SendUSBInPacket(ep uint32, data []byte) bool {
sendUSBPacket(ep, data, 0) sendUSBPacket(ep, data)
// clear transfer complete flag // clear transfer complete flag
setEPINTFLAG(ep, sam.USB_DEVICE_ENDPOINT_EPINTFLAG_TRCPT1) setEPINTFLAG(ep, sam.USB_DEVICE_ENDPOINT_EPINTFLAG_TRCPT1)
@@ -354,27 +354,28 @@ func SendUSBInPacket(ep uint32, data []byte) bool {
// Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998 // Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998
// //
//go:noinline //go:noinline
func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { func sendUSBPacket(ep uint32, data []byte) {
l := uint16(len(data)) // Select the corresponding buffer.
if 0 < maxsize && maxsize < l { buffer := udd_ep_control_cache_buffer[:]
l = maxsize if ep != 0 {
buffer = udd_ep_in_cache_buffer[ep][:]
} }
// Set endpoint address for sending data // Copy the packet to the buffer.
if ep == 0 { copy(buffer[:len(data)], data)
copy(udd_ep_control_cache_buffer[:], data[:l])
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_control_cache_buffer)))) // Select the corresponding endpoint descriptor.
} else { endpoint := &usbEndpointDescriptors[ep].DeviceDescBank[1]
copy(udd_ep_in_cache_buffer[ep][:], data[:l])
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep])))) // Set the endpoint address.
} endpoint.ADDR.Set(uint32(uintptr(unsafe.Pointer(unsafe.SliceData(buffer)))))
// clear multi-packet size which is total bytes already sent // clear multi-packet size which is total bytes already sent
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos) endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Mask << usb_DEVICE_PCKSIZE_MULTI_PACKET_SIZE_Pos)
// set byte count, which is total number of bytes to be sent // set byte count, which is total number of bytes to be sent
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) endpoint.PCKSIZE.ClearBits(usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
usbEndpointDescriptors[ep].DeviceDescBank[1].PCKSIZE.SetBits((uint32(l) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos) endpoint.PCKSIZE.SetBits((uint32(len(data)) & usb_DEVICE_PCKSIZE_BYTE_COUNT_Mask) << usb_DEVICE_PCKSIZE_BYTE_COUNT_Pos)
} }
func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
+4
View File
@@ -519,6 +519,10 @@ type Serialer interface {
RTS() bool RTS() bool
} }
func initUSB() {
// nothing to do here
}
// USB Serial/JTAG Controller // USB Serial/JTAG Controller
// See esp32-c3_technical_reference_manual_en.pdf // See esp32-c3_technical_reference_manual_en.pdf
// pg. 736 // pg. 736
+312
View File
@@ -0,0 +1,312 @@
//go:build esp32s3
package machine
import (
"device/esp"
"errors"
"runtime/volatile"
"unsafe"
)
const deviceName = esp.Device
const xtalClock = 40_000000 // 40MHz
const apbClock = 80_000000 // 80MHz
const cryptoPWMClock = 160_000000 // 160MHz
// GetCPUFrequency returns the current CPU frequency of the chip.
func GetCPUFrequency() (uint32, error) {
switch esp.SYSTEM.GetSYSCLK_CONF_SOC_CLK_SEL() {
case 0:
return xtalClock / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil
case 1:
switch esp.SYSTEM.GetCPU_PER_CONF_CPUPERIOD_SEL() {
case 0:
return 80e6, nil
case 1:
return 160e6, nil
case 2:
// If esp.SYSTEM.GetCPU_PER_CONF_PLL_FREQ_SEL() == 1, this is undefined
return 240e6, nil
}
case 2:
//RC Fast Clock
return (175e5) / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil
}
return 0, errors.New("machine: Unable to determine current cpu frequency")
}
// SetCPUFrequency sets the frequency of the CPU to one of several targets
func SetCPUFrequency(frequency uint32) error {
// Always assume we are on PLL. Lower frequencies can be set with a different
// clock source, but this will change the behavior of APB clock and Crypto PWM
// clock
//esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1)
switch frequency {
case 80_000000:
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(0)
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0) // Reduce PLL freq when possible
return nil
case 160_000000:
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(1)
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0)
return nil
case 240_000000:
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1) // Increase PLL freq when needed
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2)
return nil
}
return errors.New("machine: Unsupported CPU frequency selected. Supported: 80, 160, 240 MHz")
}
var (
ErrInvalidSPIBus = errors.New("machine: invalid SPI bus")
)
const (
PinOutput PinMode = iota
PinInput
PinInputPullup
PinInputPulldown
)
// Hardware pin numbers
const (
GPIO0 Pin = 0
GPIO1 Pin = 1
GPIO2 Pin = 2
GPIO3 Pin = 3
GPIO4 Pin = 4
GPIO5 Pin = 5
GPIO6 Pin = 6
GPIO7 Pin = 7
GPIO8 Pin = 8
GPIO9 Pin = 9
GPIO10 Pin = 10
GPIO11 Pin = 11
GPIO12 Pin = 12
GPIO13 Pin = 13
GPIO14 Pin = 14
GPIO15 Pin = 15
GPIO16 Pin = 16
GPIO17 Pin = 17
GPIO18 Pin = 18
GPIO19 Pin = 19
GPIO20 Pin = 20
GPIO21 Pin = 21
GPIO26 Pin = 26
GPIO27 Pin = 27
GPIO28 Pin = 28
GPIO29 Pin = 29
GPIO30 Pin = 30
GPIO31 Pin = 31
GPIO32 Pin = 32
GPIO33 Pin = 33
GPIO34 Pin = 34
GPIO35 Pin = 35
GPIO36 Pin = 36
GPIO37 Pin = 37
GPIO38 Pin = 38
GPIO39 Pin = 39
GPIO40 Pin = 40
GPIO41 Pin = 41
GPIO42 Pin = 42
GPIO43 Pin = 43
GPIO44 Pin = 44
GPIO45 Pin = 45
GPIO46 Pin = 46
GPIO47 Pin = 47
GPIO48 Pin = 48
)
// Configure this pin with the given configuration.
func (p Pin) Configure(config PinConfig) {
// Output function 256 is a special value reserved for use as a regular GPIO
// pin. Peripherals (SPI etc) can set a custom output function by calling
// lowercase configure() instead with a signal name.
p.configure(config, 256)
}
// configure is the same as Configure, but allows for setting a specific input
// or output signal.
// Signals are always routed through the GPIO matrix for simplicity. Output
// signals are configured in FUNCx_OUT_SEL_CFG which selects a particular signal
// to output on a given pin. Input signals are configured in FUNCy_IN_SEL_CFG,
// which sets the pin to use for a particular input signal.
func (p Pin) configure(config PinConfig, signal uint32) {
if p == NoPin {
// This simplifies pin configuration in peripherals such as SPI.
return
}
ioConfig := uint32(0)
// MCU_SEL: Function 1 is always GPIO
ioConfig |= (1 << esp.IO_MUX_GPIO_MCU_SEL_Pos)
// FUN_IE: Make this pin an input pin (always set for GPIO operation)
ioConfig |= esp.IO_MUX_GPIO_FUN_IE
// DRV: Set drive strength to 20 mA as a default. Pins 17 and 18 are special
var drive uint32
if p == GPIO17 || p == GPIO18 {
drive = 1 // 20 mA
} else {
drive = 2 // 20 mA
}
ioConfig |= (drive << esp.IO_MUX_GPIO_FUN_DRV_Pos)
// WPU/WPD: Select pull mode.
if config.Mode == PinInputPullup {
ioConfig |= esp.IO_MUX_GPIO_FUN_WPU
} else if config.Mode == PinInputPulldown {
ioConfig |= esp.IO_MUX_GPIO_FUN_WPD
}
// Set configuration
ioRegister := p.ioMuxReg()
ioRegister.Set(ioConfig)
switch config.Mode {
case PinOutput:
// Set the 'output enable' bit.
if p < 32 {
esp.GPIO.ENABLE_W1TS.Set(1 << p)
} else {
esp.GPIO.ENABLE1_W1TS.Set(1 << (p - 32))
}
// Set the signal to read the output value from. It can be a peripheral
// output signal, or the special value 256 which indicates regular GPIO
// usage.
p.outFunc().Set(signal)
case PinInput, PinInputPullup, PinInputPulldown:
// Clear the 'output enable' bit.
if p < 32 {
esp.GPIO.ENABLE_W1TC.Set(1 << p)
} else {
esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32))
}
if signal != 256 {
// Signal is a peripheral function (not a simple GPIO). Connect this
// signal to the pin.
// Note that outFunc and inFunc work in the opposite direction.
// outFunc configures a pin to use a given output signal, while
// inFunc specifies a pin to use to read the signal from.
inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)<<esp.GPIO_FUNC_IN_SEL_CFG_IN_SEL_Pos)
}
}
}
// ioMuxReg returns the IO_MUX_n_REG register used for configuring the io mux for
// this pin
func (p Pin) ioMuxReg() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.IO_MUX.GPIO0), uintptr(p)*4))
}
// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the
// output function selection.
func (p Pin) outFunc() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4))
}
// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input
// function selection.
func inFunc(signal uint32) *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG), uintptr(signal)*4))
}
// Set the pin to high or low.
// Warning: only use this on an output pin!
func (p Pin) Set(value bool) {
if value {
reg, mask := p.portMaskSet()
reg.Set(mask)
} else {
reg, mask := p.portMaskClear()
reg.Set(mask)
}
}
// Return the register and mask to enable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskSet() (*uint32, uint32) {
reg, mask := p.portMaskSet()
return &reg.Reg, mask
}
// Return the register and mask to disable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskClear() (*uint32, uint32) {
reg, mask := p.portMaskClear()
return &reg.Reg, mask
}
func (p Pin) portMaskSet() (*volatile.Register32, uint32) {
if p < 32 {
return &esp.GPIO.OUT_W1TS, 1 << p
} else {
return &esp.GPIO.OUT1_W1TS, 1 << (p - 32)
}
}
func (p Pin) portMaskClear() (*volatile.Register32, uint32) {
if p < 32 {
return &esp.GPIO.OUT_W1TC, 1 << p
} else {
return &esp.GPIO.OUT1_W1TC, 1 << (p - 32)
}
}
// Get returns the current value of a GPIO pin when the pin is configured as an
// input or as an output.
func (p Pin) Get() bool {
if p < 32 {
return esp.GPIO.IN.Get()&(1<<p) != 0
} else {
return esp.GPIO.IN1.Get()&(1<<(p-32)) != 0
}
}
var DefaultUART = UART0
var (
UART0 = &_UART0
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
UART1 = &_UART1
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
UART2 = &_UART2
_UART2 = UART{Bus: esp.UART2, Buffer: NewRingBuffer()}
)
type UART struct {
Bus *esp.UART_Type
Buffer *RingBuffer
}
func (uart *UART) Configure(config UARTConfig) {
if config.BaudRate == 0 {
config.BaudRate = 115200
}
// Crystal clock source is selected by default
uart.Bus.CLKDIV.Set(xtalClock / config.BaudRate)
}
func (uart *UART) writeByte(b byte) error {
for (uart.Bus.STATUS.Get()>>16)&0xff >= 128 {
// Read UART_TXFIFO_CNT from the status register, which indicates how
// many bytes there are in the transmit buffer. Wait until there are
// less than 128 bytes in this buffer (the default buffer size).
}
uart.Bus.FIFO.Set(uint32(b))
return nil
}
func (uart *UART) flush() {}
// TODO: SPI
+118
View File
@@ -3,7 +3,9 @@
package machine package machine
import ( import (
"device/arm"
"device/nrf" "device/nrf"
"errors"
"internal/binary" "internal/binary"
"runtime/interrupt" "runtime/interrupt"
"unsafe" "unsafe"
@@ -380,6 +382,10 @@ func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
// If the length of p is not long enough it will be padded with 0xFF bytes. // If the length of p is not long enough it will be padded with 0xFF bytes.
// This method assumes that the destination is already erased. // This method assumes that the destination is already erased.
func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
if len(p) == 0 {
return 0, nil // nothing to do (and it would fail in sd_flash_write)
}
if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() { if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
return 0, errFlashCannotWritePastEOF return 0, errFlashCannotWritePastEOF
} }
@@ -387,6 +393,35 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
address := FlashDataStart() + uintptr(off) address := FlashDataStart() + uintptr(off)
padded := flashPad(p, int(f.WriteBlockSize())) padded := flashPad(p, int(f.WriteBlockSize()))
// When the SoftDevice is enabled, access to the flash is restricted and
// must go through the SoftDevice API.
if isSoftDeviceEnabled() {
// Call sd_flash_write, which is SVC_SOC_BASE + 9 in all the
// SoftDevices I've checked.
// Documentation:
// https://docs.nordicsemi.com/bundle/s140_v6.0.0_api/page/group_n_r_f_s_o_c_f_u_n_c_t_i_o_n_s.html
numberOfWords := len(padded) / 4 // flash access goes in 32-bit words
result := arm.SVCall3(0x20+9, address, &padded[0], uint32(numberOfWords))
if result != 0 {
// Could not queue flash operation? Not sure when this can
// happen.
return 0, flashError
}
// Wait until the SoftDevice is finished.
flashStatus = flashStatusBusy
for flashStatus == flashStatusBusy {
handleSoftDeviceEvents()
}
// Check whether the operation was successful.
if flashStatus != flashStatusOk {
flashStatus = flashStatusOk
return 0, flashError
}
return len(p), nil
}
waitWhileFlashBusy() waitWhileFlashBusy()
nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Wen) nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Wen)
@@ -428,6 +463,40 @@ func (f flashBlockDevice) EraseBlockSize() int64 {
// supports this. The start and len parameters are in block numbers, use // supports this. The start and len parameters are in block numbers, use
// EraseBlockSize to map addresses to blocks. // EraseBlockSize to map addresses to blocks.
func (f flashBlockDevice) EraseBlocks(start, len int64) error { func (f flashBlockDevice) EraseBlocks(start, len int64) error {
// When the SoftDevice is enabled, access to the flash is restricted and
// must go through the SoftDevice API.
if isSoftDeviceEnabled() {
for i := range uint32(len) {
flashPage := uint32(FlashDataStart())/eraseBlockSizeValue + uint32(start) + i
// Call sd_flash_page_erase, which is SVC_SOC_BASE + 8 in all the
// SoftDevices I've checked.
// Documentation:
// https://docs.nordicsemi.com/bundle/s140_v6.0.0_api/page/group_n_r_f_s_o_c_f_u_n_c_t_i_o_n_s.html#ga9c93dd94a138ad8b5ed3693ea38ffb3e
result := arm.SVCall1(0x20+8, flashPage)
if result != 0 {
// Could not queue flash operation? Not sure when this can
// happen.
return flashError
}
// Wait until the SoftDevice is finished.
flashStatus = flashStatusBusy
for flashStatus == flashStatusBusy {
handleSoftDeviceEvents()
}
// Check whether the operation was successful.
if flashStatus != flashStatusOk {
flashStatus = flashStatusOk
return flashError
}
}
return nil
}
// SoftDevice is not used or enabled. Use NVIC directly.
address := FlashDataStart() + uintptr(start*f.EraseBlockSize()) address := FlashDataStart() + uintptr(start*f.EraseBlockSize())
waitWhileFlashBusy() waitWhileFlashBusy()
@@ -447,3 +516,52 @@ func waitWhileFlashBusy() {
for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready { for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready {
} }
} }
var flashError = errors.New("machine: flash operation failed")
const (
flashStatusOk = iota
flashStatusError
flashStatusBusy
)
var flashStatus uint8 = flashStatusOk
var sdEvent uint32
// Process all queued SoftDevice events. May only be called when the SoftDevice is enabled.
//
// Normally these are handled in the same interrupt where Bluetooth events are
// handled. But in TinyGo, that's complicated. One option would be to put it in
// the tinygo.org/x/bluetooth package, but that would cause a circular
// dependency between the machine and the bluetooth package. Another would be to
// put it here, but let the bluetooth package call handleSoftDeviceEvents, but
// that relies on updating the bluetooth package at the same time. As a
// compromise, these events are handled directly where they are expected (here
// in the machine package). This works in practice since there are only very few
// of such events (at the moment, only flash-related ones which is in the
// machine package anyway).
func handleSoftDeviceEvents() {
for {
var result uintptr
if nrf.Device == "nrf52" || nrf.Device == "nrf52840" || nrf.Device == "nrf52833" {
// sd_evt_get: SOC_SVC_BASE_NOT_AVAILABLE + 31
result = arm.SVCall1(0x2C+31, &sdEvent)
} else {
return // TODO: nrf51 etc
}
if result != 0 {
// Some error occured. The only possible error is
// NRF_ERROR_NOT_FOUND, which means there are no more events.
return
}
// The following events are the same numbers in all SoftDevices I've checked.
switch sdEvent {
case 2: // NRF_EVT_FLASH_OPERATION_SUCCESS
flashStatus = flashStatusOk
case 3: // NRF_EVT_FLASH_OPERATION_ERROR
flashStatus = flashStatusError
}
}
}
+2
View File
@@ -69,3 +69,5 @@ const eraseBlockSizeValue = 4096
func eraseBlockSize() int64 { func eraseBlockSize() int64 {
return eraseBlockSizeValue return eraseBlockSizeValue
} }
const spiMaxBufferSize = 255 // from the datasheet: TXD.MAXCNT and RXD.MAXCNT
+2
View File
@@ -90,3 +90,5 @@ const eraseBlockSizeValue = 4096
func eraseBlockSize() int64 { func eraseBlockSize() int64 {
return eraseBlockSizeValue return eraseBlockSizeValue
} }
const spiMaxBufferSize = 0xffff // from the datasheet: TXD.MAXCNT and RXD.MAXCNT
+6
View File
@@ -108,3 +108,9 @@ const eraseBlockSizeValue = 4096
func eraseBlockSize() int64 { func eraseBlockSize() int64 {
return eraseBlockSizeValue return eraseBlockSizeValue
} }
const spiMaxBufferSize = 0xffff // from the datasheet: TXD.MAXCNT and RXD.MAXCNT
// ADC instance for the VDDH input pin. This pin is typically connected to USB
// input voltage (~5V) or directly to a battery.
var ADC_VDDH = ADC{adcVDDHPin}
+18 -19
View File
@@ -256,7 +256,7 @@ func initEndpoint(ep, config uint32) {
// SendUSBInPacket sends a packet for USBHID (interrupt in / bulk in). // SendUSBInPacket sends a packet for USBHID (interrupt in / bulk in).
func SendUSBInPacket(ep uint32, data []byte) bool { func SendUSBInPacket(ep uint32, data []byte) bool {
sendUSBPacket(ep, data, 0) sendUSBPacket(ep, data)
// clear transfer complete flag // clear transfer complete flag
nrf.USBD.INTENCLR.Set(nrf.USBD_INTENCLR_ENDEPOUT0 << 4) nrf.USBD.INTENCLR.Set(nrf.USBD_INTENCLR_ENDEPOUT0 << 4)
@@ -267,33 +267,32 @@ func SendUSBInPacket(ep uint32, data []byte) bool {
// Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998 // Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998
// //
//go:noinline //go:noinline
func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { func sendUSBPacket(ep uint32, data []byte) {
// Select the corresponding buffer.
count := len(data) count := len(data)
if 0 < int(maxsize) && int(maxsize) < count { var buffer []byte
count = int(maxsize)
}
if ep == 0 { if ep == 0 {
copy(udd_ep_control_cache_buffer[:], data[:count]) buffer = udd_ep_control_cache_buffer[:]
if count > usb.EndpointPacketSize { if count > usb.EndpointPacketSize {
// The packet must be sent in chunks.
sendOnEP0DATADONE.offset = usb.EndpointPacketSize sendOnEP0DATADONE.offset = usb.EndpointPacketSize
sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[sendOnEP0DATADONE.offset] sendOnEP0DATADONE.ptr = &udd_ep_control_cache_buffer[usb.EndpointPacketSize]
sendOnEP0DATADONE.count = count - usb.EndpointPacketSize sendOnEP0DATADONE.count = count - usb.EndpointPacketSize
count = usb.EndpointPacketSize count = usb.EndpointPacketSize
} }
sendViaEPIn(
ep,
&udd_ep_control_cache_buffer[0],
count,
)
} else { } else {
copy(udd_ep_in_cache_buffer[ep][:], data[:count]) buffer = udd_ep_in_cache_buffer[ep][:]
sendViaEPIn(
ep,
&udd_ep_in_cache_buffer[ep][0],
count,
)
} }
// Copy the packet to the buffer.
copy(buffer[:len(data)], data)
// Send the first chunk of the packet.
sendViaEPIn(
ep,
&buffer[0],
count,
)
} }
func handleEndpointRx(ep uint32) []byte { func handleEndpointRx(ep uint32) []byte {
+17 -4
View File
@@ -49,7 +49,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
// Configure for a single shot to perform both write and read (as applicable) // Configure for a single shot to perform both write and read (as applicable)
if len(w) != 0 { if len(w) != 0 {
i2c.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0])))) i2c.Bus.TXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(w)))))
i2c.Bus.TXD.MAXCNT.Set(uint32(len(w))) i2c.Bus.TXD.MAXCNT.Set(uint32(len(w)))
// If no read, immediately signal stop after TX // If no read, immediately signal stop after TX
@@ -58,7 +58,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
} }
} }
if len(r) != 0 { if len(r) != 0 {
i2c.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0])))) i2c.Bus.RXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(r)))))
i2c.Bus.RXD.MAXCNT.Set(uint32(len(r))) i2c.Bus.RXD.MAXCNT.Set(uint32(len(r)))
// Auto-start Rx after Tx and Stop after Rx // Auto-start Rx after Tx and Stop after Rx
@@ -89,6 +89,11 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
} }
} }
// Make sure the w and r buffers stay alive until this point, so they won't
// be garbage collected while the buffers are used by the hardware.
keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(w)))
keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(r)))
return return
} }
@@ -117,7 +122,7 @@ func (i2c *I2C) Listen(addr uint8) error {
// //
// For request events, the caller MUST call `Reply` to avoid hanging the i2c bus indefinitely. // For request events, the caller MUST call `Reply` to avoid hanging the i2c bus indefinitely.
func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) { func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err error) {
i2c.BusT.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0])))) i2c.BusT.RXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(buf)))))
i2c.BusT.RXD.MAXCNT.Set(uint32(len(buf))) i2c.BusT.RXD.MAXCNT.Set(uint32(len(buf)))
i2c.BusT.TASKS_PREPARERX.Set(nrf.TWIS_TASKS_PREPARERX_TASKS_PREPARERX_Trigger) i2c.BusT.TASKS_PREPARERX.Set(nrf.TWIS_TASKS_PREPARERX_TASKS_PREPARERX_Trigger)
@@ -134,6 +139,10 @@ func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err err
} }
} }
// Make sure buf stays alive until this point, so it won't be garbage
// collected while it is used by the hardware.
keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(buf)))
count = 0 count = 0
evt = I2CFinish evt = I2CFinish
err = nil err = nil
@@ -163,7 +172,7 @@ func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err err
// Reply supplies the response data the controller. // Reply supplies the response data the controller.
func (i2c *I2C) Reply(buf []byte) error { func (i2c *I2C) Reply(buf []byte) error {
i2c.BusT.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0])))) i2c.BusT.TXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(buf)))))
i2c.BusT.TXD.MAXCNT.Set(uint32(len(buf))) i2c.BusT.TXD.MAXCNT.Set(uint32(len(buf)))
i2c.BusT.EVENTS_STOPPED.Set(0) i2c.BusT.EVENTS_STOPPED.Set(0)
@@ -180,6 +189,10 @@ func (i2c *I2C) Reply(buf []byte) error {
} }
} }
// Make sure the buffer stays alive until this point, so it won't be garbage
// collected while it is used by the hardware.
keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(buf)))
i2c.BusT.EVENTS_STOPPED.Set(0) i2c.BusT.EVENTS_STOPPED.Set(0)
return nil return nil
+37 -24
View File
@@ -12,6 +12,8 @@ func CPUFrequency() uint32 {
return 64000000 return 64000000
} }
var adcVDDHPin = Pin(254) // special pin number for VDDH on the nrf52840
// InitADC initializes the registers needed for ADC. // InitADC initializes the registers needed for ADC.
func InitADC() { func InitADC() {
// Enable ADC. // Enable ADC.
@@ -26,7 +28,7 @@ func InitADC() {
// Samples can be 1(default), 2, 4, 8, 16, 32, 64, 128, 256 samples // Samples can be 1(default), 2, 4, 8, 16, 32, 64, 128, 256 samples
func (a *ADC) Configure(config ADCConfig) { func (a *ADC) Configure(config ADCConfig) {
var configVal uint32 = nrf.SAADC_CH_CONFIG_RESP_Bypass<<nrf.SAADC_CH_CONFIG_RESP_Pos | var configVal uint32 = nrf.SAADC_CH_CONFIG_RESP_Bypass<<nrf.SAADC_CH_CONFIG_RESP_Pos |
nrf.SAADC_CH_CONFIG_RESP_Bypass<<nrf.SAADC_CH_CONFIG_RESN_Pos | nrf.SAADC_CH_CONFIG_RESN_Bypass<<nrf.SAADC_CH_CONFIG_RESN_Pos |
nrf.SAADC_CH_CONFIG_REFSEL_Internal<<nrf.SAADC_CH_CONFIG_REFSEL_Pos | nrf.SAADC_CH_CONFIG_REFSEL_Internal<<nrf.SAADC_CH_CONFIG_REFSEL_Pos |
nrf.SAADC_CH_CONFIG_MODE_SE<<nrf.SAADC_CH_CONFIG_MODE_Pos nrf.SAADC_CH_CONFIG_MODE_SE<<nrf.SAADC_CH_CONFIG_MODE_Pos
@@ -116,36 +118,43 @@ func (a *ADC) Configure(config ADCConfig) {
// Get returns the current value of an ADC pin in the range 0..0xffff. // Get returns the current value of an ADC pin in the range 0..0xffff.
func (a *ADC) Get() uint16 { func (a *ADC) Get() uint16 {
var pwmPin uint32 var adcPin uint32
var rawValue volatile.Register16 var rawValue volatile.Register16
switch a.Pin { switch a.Pin {
case 2: case 2:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput0 adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput0
case 3: case 3:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput1 adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput1
case 4: case 4:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput2 adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput2
case 5: case 5:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput3 adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput3
case 28: case 28:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput4 adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput4
case 29: case 29:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput5 adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput5
case 30: case 30:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput6 adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput6
case 31: case 31:
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput7 adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput7
case adcVDDHPin:
if Device == "nrf52840" {
adcPin = 0x0D // VDDHDIV5 on the nrf52840
} else {
return 0
}
default: default:
return 0 return 0
} }
// Set pin to read. // Set pin to read.
nrf.SAADC.CH[0].PSELN.Set(pwmPin) nrf.SAADC.CH[0].PSELP.Set(adcPin)
nrf.SAADC.CH[0].PSELP.Set(pwmPin)
// Destination for sample result. // Destination for sample result.
nrf.SAADC.RESULT.PTR.Set(uint32(uintptr(unsafe.Pointer(&rawValue)))) // Note: rawValue doesn't need to be kept alive for the GC, since the
// volatile read later will force it to stay alive.
nrf.SAADC.RESULT.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(&rawValue))))
nrf.SAADC.RESULT.MAXCNT.Set(1) // One sample nrf.SAADC.RESULT.MAXCNT.Set(1) // One sample
// Start tasks. // Start tasks.
@@ -301,20 +310,18 @@ func (spi *SPI) Transfer(w byte) (byte, error) {
// padded until they fit: if len(w) > len(r) the extra bytes received will be // padded until they fit: if len(w) > len(r) the extra bytes received will be
// dropped and if len(w) < len(r) extra 0 bytes will be sent. // dropped and if len(w) < len(r) extra 0 bytes will be sent.
func (spi *SPI) Tx(w, r []byte) error { func (spi *SPI) Tx(w, r []byte) error {
// Unfortunately the hardware (on the nrf52832) only supports up to 255 // Unfortunately the hardware (on the nrf52832) only supports a limited
// bytes in the buffers, so if either w or r is longer than that the // amount of bytes in the buffers (depending on the chip), so if either w or
// transfer needs to be broken up in pieces. // r is longer than that the transfer needs to be broken up in pieces.
// The nrf52840 supports far larger buffers however, which isn't yet
// supported.
for len(r) != 0 || len(w) != 0 { for len(r) != 0 || len(w) != 0 {
// Prepare the SPI transfer: set the DMA pointers and lengths. // Prepare the SPI transfer: set the DMA pointers and lengths.
// read buffer // read buffer
nr := uint32(len(r)) nr := uint32(len(r))
if nr > 0 { if nr > 0 {
if nr > 255 { if nr > spiMaxBufferSize {
nr = 255 nr = spiMaxBufferSize
} }
spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0])))) spi.Bus.RXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(r)))))
r = r[nr:] r = r[nr:]
} }
spi.Bus.RXD.MAXCNT.Set(nr) spi.Bus.RXD.MAXCNT.Set(nr)
@@ -322,10 +329,10 @@ func (spi *SPI) Tx(w, r []byte) error {
// write buffer // write buffer
nw := uint32(len(w)) nw := uint32(len(w))
if nw > 0 { if nw > 0 {
if nw > 255 { if nw > spiMaxBufferSize {
nw = 255 nw = spiMaxBufferSize
} }
spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0])))) spi.Bus.TXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(w)))))
w = w[nw:] w = w[nw:]
} }
spi.Bus.TXD.MAXCNT.Set(nw) spi.Bus.TXD.MAXCNT.Set(nw)
@@ -335,10 +342,16 @@ func (spi *SPI) Tx(w, r []byte) error {
// finished if the transfer is send-only (a common case). // finished if the transfer is send-only (a common case).
spi.Bus.TASKS_START.Set(1) spi.Bus.TASKS_START.Set(1)
for spi.Bus.EVENTS_END.Get() == 0 { for spi.Bus.EVENTS_END.Get() == 0 {
gosched()
} }
spi.Bus.EVENTS_END.Set(0) spi.Bus.EVENTS_END.Set(0)
} }
// Make sure the w and r buffers stay alive for the GC until this point,
// since they are used by the hardware but not otherwise visible.
keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(r)))
keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(w)))
return nil return nil
} }
+4
View File
@@ -7,3 +7,7 @@ package machine
func GetRNG() (ret uint32, err error) { func GetRNG() (ret uint32, err error) {
return getRNG() return getRNG()
} }
func isSoftDeviceEnabled() bool {
return false
}
+8 -6
View File
@@ -11,9 +11,8 @@ import (
// avoid a heap allocation in GetRNG. // avoid a heap allocation in GetRNG.
var ( var (
softdeviceEnabled uint8 bytesAvailable uint8
bytesAvailable uint8 buf [4]uint8
buf [4]uint8
errNoSoftDeviceSupport = errors.New("rng: softdevice not supported on this device") errNoSoftDeviceSupport = errors.New("rng: softdevice not supported on this device")
errNotEnoughRandomData = errors.New("rng: not enough random data available") errNotEnoughRandomData = errors.New("rng: not enough random data available")
@@ -24,9 +23,7 @@ var (
func GetRNG() (ret uint32, err error) { func GetRNG() (ret uint32, err error) {
// First check whether the SoftDevice is enabled. // First check whether the SoftDevice is enabled.
// sd_rand_application_bytes_available_get cannot be called when the SoftDevice is not enabled. // sd_rand_application_bytes_available_get cannot be called when the SoftDevice is not enabled.
arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled if !isSoftDeviceEnabled() {
if softdeviceEnabled == 0 {
return getRNG() return getRNG()
} }
@@ -57,3 +54,8 @@ func GetRNG() (ret uint32, err error) {
return uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24, nil return uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24, nil
} }
// This function is defined in the runtime, but we need it too.
//
//go:linkname isSoftDeviceEnabled runtime.isSoftDeviceEnabled
func isSoftDeviceEnabled() bool
+1 -1
View File
@@ -128,7 +128,7 @@ func handleUSBSetAddress(setup usb.Setup) bool {
const ackTimeout = 570 const ackTimeout = 570
rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC)
sendUSBPacket(0, []byte{}, 0) sendUSBPacket(0, []byte{})
// Wait for transfer to complete with a timeout. // Wait for transfer to complete with a timeout.
t := timer.timeElapsed() t := timer.timeElapsed()
+1 -1
View File
@@ -131,7 +131,7 @@ func handleUSBSetAddress(setup usb.Setup) bool {
const ackTimeout = 570 const ackTimeout = 570
rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_ACK_REC) rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_ACK_REC)
sendUSBPacket(0, []byte{}, 0) sendUSBPacket(0, []byte{})
// Wait for transfer to complete with a timeout. // Wait for transfer to complete with a timeout.
t := timer.timeElapsed() t := timer.timeElapsed()
+25
View File
@@ -124,6 +124,31 @@ const (
fnXIP pinFunc = 0 fnXIP pinFunc = 0
) )
// validPins confirms that the SPI pin selection is a legitimate one
// for the the 2040 chip.
func (spi *SPI) validPins(config SPIConfig) error {
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22
case rp.SPI1:
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
}
switch {
case !okSDI:
return errSPIInvalidSDI
case !okSDO:
return errSPIInvalidSDO
case !okSCK:
return errSPIInvalidSCK
}
return nil
}
// Configure configures the gpio pin as per mode. // Configure configures the gpio pin as per mode.
func (p Pin) Configure(config PinConfig) { func (p Pin) Configure(config PinConfig) {
if p == NoPin { if p == NoPin {
+27
View File
@@ -2,6 +2,8 @@
package machine package machine
import "device/rp"
// Analog pins on RP2350a. // Analog pins on RP2350a.
const ( const (
ADC0 Pin = GPIO26 ADC0 Pin = GPIO26
@@ -12,3 +14,28 @@ const (
// fifth ADC channel. // fifth ADC channel.
thermADC = 30 thermADC = 30
) )
// validPins confirms that the SPI pin selection is a legitimate one
// for the the 2350a chip.
func (spi *SPI) validPins(config SPIConfig) error {
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22
case rp.SPI1:
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
}
switch {
case !okSDI:
return errSPIInvalidSDI
case !okSDO:
return errSPIInvalidSDO
case !okSCK:
return errSPIInvalidSCK
}
return nil
}
+27
View File
@@ -2,6 +2,8 @@
package machine package machine
import "device/rp"
// RP2350B has additional pins. // RP2350B has additional pins.
const ( const (
@@ -46,3 +48,28 @@ var (
PWM10 = getPWMGroup(10) PWM10 = getPWMGroup(10)
PWM11 = getPWMGroup(11) PWM11 = getPWMGroup(11)
) )
// validPins confirms that the SPI pin selection is a legitimate one
// for the the 2350b chip.
func (spi *SPI) validPins(config SPIConfig) error {
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20 || config.SDI == 32 || config.SDI == 36
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23 || config.SDO == 35 || config.SDO == 39
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22 || config.SCK == 34 || config.SCK == 38
case rp.SPI1:
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28 || config.SDI == 40 || config.SDI == 44
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27 || config.SDO == 31 || config.SDO == 43 || config.SDO == 47
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26 || config.SCK == 30 || config.SCK == 42 || config.SCK == 46
}
switch {
case !okSDI:
return errSPIInvalidSDI
case !okSDO:
return errSPIInvalidSDO
case !okSCK:
return errSPIInvalidSCK
}
return nil
}
+9 -22
View File
@@ -108,7 +108,7 @@ func (spi *SPI) SetBaudRate(br uint32) error {
var prescale, postdiv uint32 var prescale, postdiv uint32
freq := CPUFrequency() freq := CPUFrequency()
for prescale = 2; prescale < 255; prescale += 2 { for prescale = 2; prescale < 255; prescale += 2 {
if freq < (prescale+2)*256*br { if uint64(freq) < uint64((prescale+2)*256)*uint64(br) {
break break
} }
} }
@@ -165,27 +165,9 @@ func (spi *SPI) Configure(config SPIConfig) error {
config.SDI = SPI1_SDI_PIN config.SDI = SPI1_SDI_PIN
} }
} }
var okSDI, okSDO, okSCK bool if err := spi.validPins(config); err != nil {
switch spi.Bus { return err
case rp.SPI0:
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22
case rp.SPI1:
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
} }
switch {
case !okSDI:
return errSPIInvalidSDI
case !okSDO:
return errSPIInvalidSDO
case !okSCK:
return errSPIInvalidSCK
}
if config.Frequency == 0 { if config.Frequency == 0 {
config.Frequency = defaultBaud config.Frequency = defaultBaud
} }
@@ -309,7 +291,7 @@ func (spi *SPI) tx(tx []byte) error {
// - set data size to single bytes // - set data size to single bytes
// - set the DREQ so that the DMA will fill the SPI FIFO as needed // - set the DREQ so that the DMA will fill the SPI FIFO as needed
// - start the transfer // - start the transfer
ch.READ_ADDR.Set(uint32(uintptr(unsafe.Pointer(&tx[0])))) ch.READ_ADDR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(tx)))))
ch.WRITE_ADDR.Set(uint32(uintptr(unsafe.Pointer(&spi.Bus.SSPDR)))) ch.WRITE_ADDR.Set(uint32(uintptr(unsafe.Pointer(&spi.Bus.SSPDR))))
ch.TRANS_COUNT.Set(uint32(len(tx))) ch.TRANS_COUNT.Set(uint32(len(tx)))
ch.CTRL_TRIG.Set(rp.DMA_CH0_CTRL_TRIG_INCR_READ | ch.CTRL_TRIG.Set(rp.DMA_CH0_CTRL_TRIG_INCR_READ |
@@ -328,6 +310,11 @@ func (spi *SPI) tx(tx []byte) error {
for ch.CTRL_TRIG.Get()&rp.DMA_CH0_CTRL_TRIG_BUSY != 0 { for ch.CTRL_TRIG.Get()&rp.DMA_CH0_CTRL_TRIG_BUSY != 0 {
} }
// Make sure the read buffer stays alive until this point (in the unlikely
// case the tx slice wasn't read after this function returns and a GC cycle
// happened inbetween).
keepAliveNoEscape(unsafe.Pointer(unsafe.SliceData(tx)))
// We didn't read any result values, which means the RX FIFO has likely // We didn't read any result values, which means the RX FIFO has likely
// overflown. We have to clean up this mess now. // overflown. We have to clean up this mess now.
+3 -7
View File
@@ -72,19 +72,15 @@ func initEndpoint(ep, config uint32) {
// SendUSBInPacket sends a packet for USB (interrupt in / bulk in). // SendUSBInPacket sends a packet for USB (interrupt in / bulk in).
func SendUSBInPacket(ep uint32, data []byte) bool { func SendUSBInPacket(ep uint32, data []byte) bool {
sendUSBPacket(ep, data, 0) sendUSBPacket(ep, data)
return true return true
} }
// Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998 // Prevent file size increases: https://github.com/tinygo-org/tinygo/pull/998
// //
//go:noinline //go:noinline
func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { func sendUSBPacket(ep uint32, data []byte) {
count := len(data) count := len(data)
if 0 < int(maxsize) && int(maxsize) < count {
count = int(maxsize)
}
if ep == 0 { if ep == 0 {
if count > usb.EndpointPacketSize { if count > usb.EndpointPacketSize {
count = usb.EndpointPacketSize count = usb.EndpointPacketSize
@@ -145,7 +141,7 @@ func setEPDataPID(ep uint32, dataOne bool) {
} }
func SendZlp() { func SendZlp() {
sendUSBPacket(0, []byte{}, 0) sendUSBPacket(0, []byte{})
} }
func sendViaEPIn(ep uint32, data []byte, count int) { func sendViaEPIn(ep uint32, data []byte, count int) {
+1
View File
@@ -6,5 +6,6 @@ package machine
var Serial Serialer var Serial Serialer
func InitSerial() { func InitSerial() {
initUSB()
Serial = USBCDC Serial = USBCDC
} }
+83 -37
View File
@@ -19,6 +19,17 @@ var (
USBCDC Serialer USBCDC Serialer
) )
func initUSB() {
enableUSBCDC()
USBDev.Configure(UARTConfig{})
}
// Using go:linkname here because there's a circular dependency between the
// machine package and the machine/usb/cdc package.
//
//go:linkname enableUSBCDC machine/usb/cdc.EnableUSBCDC
func enableUSBCDC()
type Serialer interface { type Serialer interface {
WriteByte(c byte) error WriteByte(c byte) error
Write(data []byte) (n int, err error) Write(data []byte) (n int, err error)
@@ -70,21 +81,6 @@ func usbSerial() string {
return "" return ""
} }
// strToUTF16LEDescriptor converts a utf8 string into a string descriptor
// note: the following code only converts ascii characters to UTF16LE. In order
// to do a "proper" conversion, we would need to pull in the 'unicode/utf16'
// package, which at the time this was written added 512 bytes to the compiled
// binary.
func strToUTF16LEDescriptor(in string, out []byte) {
out[0] = byte(len(out))
out[1] = descriptor.TypeString
for i, rune := range in {
out[(i<<1)+2] = byte(rune)
out[(i<<1)+3] = 0
}
return
}
const cdcLineInfoSize = 7 const cdcLineInfoSize = 7
var ( var (
@@ -126,51 +122,53 @@ var (
usbStallHandler [NumberOfUSBEndpoints]func(usb.Setup) bool usbStallHandler [NumberOfUSBEndpoints]func(usb.Setup) bool
) )
var usbLangInfo = [4]byte{
// length = 4 bytes
0x04,
// descriptor type = string
0x03,
// language codes
// 0x0409 = English (United States)
0x09, 0x04,
}
// sendDescriptor creates and sends the various USB descriptor types that // sendDescriptor creates and sends the various USB descriptor types that
// can be requested by the host. // can be requested by the host.
func sendDescriptor(setup usb.Setup) { func sendDescriptor(setup usb.Setup) {
switch setup.WValueH { switch setup.WValueH {
case descriptor.TypeConfiguration: case descriptor.TypeConfiguration:
sendUSBPacket(0, usbDescriptor.Configuration, setup.WLength) sendDescriptorData(usbDescriptor.Configuration, setup.WLength)
return return
case descriptor.TypeDevice: case descriptor.TypeDevice:
usbDescriptor.Configure(usbVendorID(), usbProductID()) usbDescriptor.Configure(usbVendorID(), usbProductID())
sendUSBPacket(0, usbDescriptor.Device, setup.WLength) sendDescriptorData(usbDescriptor.Device, setup.WLength)
return return
case descriptor.TypeString: case descriptor.TypeString:
switch setup.WValueL { switch setup.WValueL {
case 0: case 0:
usb_trans_buffer[0] = 0x04 sendDescriptorData(usbLangInfo[:], setup.WLength)
usb_trans_buffer[1] = 0x03
usb_trans_buffer[2] = 0x09
usb_trans_buffer[3] = 0x04
sendUSBPacket(0, usb_trans_buffer[:4], setup.WLength)
case usb.IPRODUCT: case usb.IPRODUCT:
b := usb_trans_buffer[:(len(usbProduct())<<1)+2] sendDescriptorString(usbProduct(), setup.WLength)
strToUTF16LEDescriptor(usbProduct(), b)
sendUSBPacket(0, b, setup.WLength)
case usb.IMANUFACTURER: case usb.IMANUFACTURER:
b := usb_trans_buffer[:(len(usbManufacturer())<<1)+2] sendDescriptorString(usbManufacturer(), setup.WLength)
strToUTF16LEDescriptor(usbManufacturer(), b)
sendUSBPacket(0, b, setup.WLength)
case usb.ISERIAL: case usb.ISERIAL:
sz := len(usbSerial()) serial := usbSerial()
if sz == 0 { if len(serial) == 0 {
SendZlp() SendZlp()
} else { } else {
b := usb_trans_buffer[:(sz<<1)+2] sendDescriptorString(serial, setup.WLength)
strToUTF16LEDescriptor(usbSerial(), b)
sendUSBPacket(0, b, setup.WLength)
} }
} }
// TODO: why do we do this when WValueL is unknown?
return return
case descriptor.TypeHIDReport: case descriptor.TypeHIDReport:
if h, ok := usbDescriptor.HID[setup.WIndex]; ok { if h, ok := usbDescriptor.HID[setup.WIndex]; ok {
sendUSBPacket(0, h, setup.WLength) sendDescriptorData(h, setup.WLength)
return return
} }
case descriptor.TypeDeviceQualifier: case descriptor.TypeDeviceQualifier:
@@ -183,6 +181,39 @@ func sendDescriptor(setup usb.Setup) {
return return
} }
// sendDescriptorString sends a string descriptor, truncating it to fit maxLen or the buffer size.
// note: the following code only converts ascii characters to UTF16LE. In order
// to do a "proper" conversion, we would need to pull in the 'unicode/utf16'
// package, which at the time this was written added 512 bytes to the compiled
// binary.
// TODO: old comment, re-evaluate
func sendDescriptorString(data string, maxLen uint16) {
if maxLen < 2 {
// Something has gone horribly wrong.
SendZlp()
return
}
// Clamp the length.
maxEncBytes := min(len(usb_trans_buffer), len(udd_ep_control_cache_buffer), int(maxLen))
data = data[:min(len(data), (maxEncBytes-2)/2)]
// Write the header.
buf := usb_trans_buffer[:2*len(data)+2]
hdr, body := buf[:2], buf[2:]
hdr[0] = byte(len(buf))
hdr[1] = descriptor.TypeString
// Convert the string to UTF16.
// NOTE: Using range here would cause the length to disagree when multibyte codepoints are present.
for i := 0; i < len(data); i++ {
body[2*i] = byte(data[i])
body[2*i+1] = 0
}
sendUSBPacket(0, buf)
}
func handleStandardSetup(setup usb.Setup) bool { func handleStandardSetup(setup usb.Setup) bool {
switch setup.BRequest { switch setup.BRequest {
case usb.GET_STATUS: case usb.GET_STATUS:
@@ -195,7 +226,7 @@ func handleStandardSetup(setup usb.Setup) bool {
} }
} }
sendUSBPacket(0, usb_trans_buffer[:2], setup.WLength) sendDescriptorData(usb_trans_buffer[:2], setup.WLength)
return true return true
case usb.CLEAR_FEATURE: case usb.CLEAR_FEATURE:
@@ -240,7 +271,7 @@ func handleStandardSetup(setup usb.Setup) bool {
case usb.GET_CONFIGURATION: case usb.GET_CONFIGURATION:
usb_trans_buffer[0] = usbConfiguration usb_trans_buffer[0] = usbConfiguration
sendUSBPacket(0, usb_trans_buffer[:1], setup.WLength) sendDescriptorData(usb_trans_buffer[:1], setup.WLength)
return true return true
case usb.SET_CONFIGURATION: case usb.SET_CONFIGURATION:
@@ -260,7 +291,7 @@ func handleStandardSetup(setup usb.Setup) bool {
case usb.GET_INTERFACE: case usb.GET_INTERFACE:
usb_trans_buffer[0] = usbSetInterface usb_trans_buffer[0] = usbSetInterface
sendUSBPacket(0, usb_trans_buffer[:1], setup.WLength) sendDescriptorData(usb_trans_buffer[:1], setup.WLength)
return true return true
case usb.SET_INTERFACE: case usb.SET_INTERFACE:
@@ -274,6 +305,21 @@ func handleStandardSetup(setup usb.Setup) bool {
} }
} }
// sendDescriptorData sends a descriptor, truncating it to fit maxLen or the buffer size.
func sendDescriptorData(data []byte, maxLen uint16) {
data = lenToCap(data)
data = data[:min(len(data), len(udd_ep_control_cache_buffer), int(maxLen))]
sendUSBPacket(0, data)
}
// Set the cap of the slice to the length.
// This is safe, but cannot be proven by the compiler.
//
//go:nobounds
func lenToCap(b []byte) []byte {
return b[:len(b):len(b)]
}
func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) {
if len(usbDescriptor.Device) == 0 { if len(usbDescriptor.Device) == 0 {
usbDescriptor = descriptor.CDC usbDescriptor = descriptor.CDC
+11 -10
View File
@@ -35,12 +35,12 @@ type msc struct {
respStatus csw.Status // Response status for the last command respStatus csw.Status // Response status for the last command
sendZLP bool // Flag to indicate if a zero-length packet should be sent before sending CSW sendZLP bool // Flag to indicate if a zero-length packet should be sent before sending CSW
cbw *CBW // Last received Command Block Wrapper cbw *CBW // Last received Command Block Wrapper
queuedBytes uint32 // Number of bytes queued for sending queuedBytes uint32 // Number of bytes queued for sending
sentBytes uint32 // Number of bytes sent sentBytes uint32 // Number of bytes sent
totalBytes uint32 // Total bytes to send transferBytes uint32 // Total bytes to send
cswBuf []byte // CSW response buffer cswBuf []byte // CSW response buffer
state mscState state mscState
maxLUN uint8 // Maximum Logical Unit Number (n-1 for n LUNs) maxLUN uint8 // Maximum Logical Unit Number (n-1 for n LUNs)
dev machine.BlockDevice dev machine.BlockDevice
@@ -160,8 +160,9 @@ func (m *msc) sendUSBPacket(b []byte) {
func (m *msc) sendCSW(status csw.Status) { func (m *msc) sendCSW(status csw.Status) {
// Generate CSW packet into m.cswBuf and send it // Generate CSW packet into m.cswBuf and send it
residue := uint32(0) residue := uint32(0)
if m.totalBytes >= m.sentBytes { expected := m.cbw.transferLength()
residue = m.totalBytes - m.sentBytes if expected >= m.sentBytes {
residue = expected - m.sentBytes
} }
m.cbw.CSW(status, residue, m.cswBuf) m.cbw.CSW(status, residue, m.cswBuf)
m.state = mscStateStatusSent m.state = mscStateStatusSent
@@ -245,7 +246,7 @@ func (m *msc) run(b []byte, isEpOut bool) bool {
// Move on to the data transfer phase next go around (after sending the first message) // Move on to the data transfer phase next go around (after sending the first message)
m.state = mscStateData m.state = mscStateData
m.totalBytes = cbw.transferLength() m.transferBytes = cbw.transferLength()
m.queuedBytes = 0 m.queuedBytes = 0
m.sentBytes = 0 m.sentBytes = 0
m.respStatus = csw.StatusPassed m.respStatus = csw.StatusPassed
@@ -281,7 +282,7 @@ func (m *msc) run(b []byte, isEpOut bool) bool {
// to cycle back through this block, e.g. with TEST UNIT READY which sends only a CSW after // to cycle back through this block, e.g. with TEST UNIT READY which sends only a CSW after
// setting the sense key/add'l code/qualifier internally // setting the sense key/add'l code/qualifier internally
if m.state == mscStateStatus && !m.txStalled { if m.state == mscStateStatus && !m.txStalled {
if m.totalBytes > m.sentBytes && m.cbw.isIn() { if m.cbw.transferLength() > m.sentBytes && m.cbw.isIn() {
// 6.7.2 The Thirteen Cases - Case 5 (Hi > Di): STALL before status // 6.7.2 The Thirteen Cases - Case 5 (Hi > Di): STALL before status
m.stallEndpoint(usb.MSC_ENDPOINT_IN) m.stallEndpoint(usb.MSC_ENDPOINT_IN)
} else if m.sendZLP { } else if m.sendZLP {
+16 -11
View File
@@ -21,9 +21,9 @@ func (m *msc) scsiCmdBegin() {
return return
} }
if m.totalBytes > 0 && m.cbw.isOut() { if m.transferBytes > 0 && m.cbw.isOut() {
// Reject any other multi-packet commands // Reject any other multi-packet commands
if m.totalBytes > m.maxPacketSize { if m.transferBytes > m.maxPacketSize {
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode) m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode)
return return
} else { } else {
@@ -53,7 +53,7 @@ func (m *msc) scsiCmdBegin() {
} }
if len(m.buf) == 0 { if len(m.buf) == 0 {
if m.totalBytes > 0 { if m.transferBytes > 0 {
// 6.7.2 The Thirteen Cases - Case 4 (Hi > Dn) // 6.7.2 The Thirteen Cases - Case 4 (Hi > Dn)
// https://usb.org/sites/default/files/usbmassbulk_10.pdf // https://usb.org/sites/default/files/usbmassbulk_10.pdf
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0) m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0)
@@ -63,7 +63,7 @@ func (m *msc) scsiCmdBegin() {
m.state = mscStateStatus m.state = mscStateStatus
} }
} else { } else {
if m.totalBytes == 0 { if m.transferBytes == 0 {
// 6.7.1 The Thirteen Cases - Case 2 (Hn < Di) // 6.7.1 The Thirteen Cases - Case 2 (Hn < Di)
// https://usb.org/sites/default/files/usbmassbulk_10.pdf // https://usb.org/sites/default/files/usbmassbulk_10.pdf
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0) m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0)
@@ -72,6 +72,7 @@ func (m *msc) scsiCmdBegin() {
if m.cbw.transferLength() < uint32(len(m.buf)) { if m.cbw.transferLength() < uint32(len(m.buf)) {
m.buf = m.buf[:m.cbw.transferLength()] m.buf = m.buf[:m.cbw.transferLength()]
} }
m.queuedBytes = uint32(len(m.buf))
m.sendUSBPacket(m.buf) m.sendUSBPacket(m.buf)
} }
} }
@@ -93,7 +94,7 @@ func (m *msc) scsiDataTransfer(b []byte) bool {
// Update our sent bytes count to include the just-confirmed bytes // Update our sent bytes count to include the just-confirmed bytes
m.sentBytes += m.queuedBytes m.sentBytes += m.queuedBytes
if m.sentBytes >= m.totalBytes { if m.sentBytes >= m.transferBytes {
// Transfer complete, send CSW after transfer confirmed // Transfer complete, send CSW after transfer confirmed
m.state = mscStateStatus m.state = mscStateStatus
} else if cmdType == scsi.CmdRead { } else if cmdType == scsi.CmdRead {
@@ -158,8 +159,8 @@ func (m *msc) scsiCmdModeSense(cmd scsi.Cmd) {
// The host allows a good amount of leeway in response size // The host allows a good amount of leeway in response size
// Reset total bytes to what we'll actually send // Reset total bytes to what we'll actually send
if m.totalBytes > respLen { if m.transferBytes > respLen {
m.totalBytes = respLen m.transferBytes = respLen
m.sendZLP = true m.sendZLP = true
} }
@@ -210,7 +211,7 @@ func (m *msc) scsiCmdRequestSense() {
// Set the buffer size to the SCSI sense message size and clear // Set the buffer size to the SCSI sense message size and clear
m.resetBuffer(scsi.RequestSenseRespLen) m.resetBuffer(scsi.RequestSenseRespLen)
m.queuedBytes = scsi.RequestSenseRespLen m.queuedBytes = scsi.RequestSenseRespLen
m.totalBytes = scsi.RequestSenseRespLen m.transferBytes = scsi.RequestSenseRespLen
// 0x70 - current error, 0x71 - deferred error (not used) // 0x70 - current error, 0x71 - deferred error (not used)
m.buf[0] = 0xF0 // 0x70 for current error plus 0x80 for valid flag bit m.buf[0] = 0xF0 // 0x70 for current error plus 0x80 for valid flag bit
@@ -266,7 +267,7 @@ func (m *msc) scsiQueueTask(cmdType scsi.CmdType, b []byte) bool {
switch cmdType { switch cmdType {
case scsi.CmdWrite: case scsi.CmdWrite:
// If we're writing data wait until we have a full write block of data that can be processed. // If we're writing data wait until we have a full write block of data that can be processed.
if m.queuedBytes == uint32(cap(m.blockCache)) { if m.queuedBytes == uint32(cap(m.blockCache)) || (m.sentBytes+m.queuedBytes >= m.transferBytes) {
m.taskQueued = true m.taskQueued = true
} }
case scsi.CmdUnmap: case scsi.CmdUnmap:
@@ -279,7 +280,11 @@ func (m *msc) scsiQueueTask(cmdType scsi.CmdType, b []byte) bool {
func (m *msc) sendScsiError(status csw.Status, key scsi.Sense, code scsi.SenseCode) { func (m *msc) sendScsiError(status csw.Status, key scsi.Sense, code scsi.SenseCode) {
// Generate CSW into m.cswBuf // Generate CSW into m.cswBuf
residue := m.totalBytes - m.sentBytes expected := m.cbw.transferLength()
residue := uint32(0)
if expected > m.sentBytes {
residue = expected - m.sentBytes
}
// Prepare to send CSW // Prepare to send CSW
m.sendZLP = true // Ensure the transaction is signaled as ended before a CSW is sent m.sendZLP = true // Ensure the transaction is signaled as ended before a CSW is sent
@@ -291,7 +296,7 @@ func (m *msc) sendScsiError(status csw.Status, key scsi.Sense, code scsi.SenseCo
m.addlSenseCode = code m.addlSenseCode = code
m.addlSenseQualifier = 0x00 // Not used m.addlSenseQualifier = 0x00 // Not used
if m.totalBytes > 0 && residue > 0 { if expected > 0 && residue > 0 {
if m.cbw.isIn() { if m.cbw.isIn() {
m.stallEndpoint(usb.MSC_ENDPOINT_IN) m.stallEndpoint(usb.MSC_ENDPOINT_IN)
} else { } else {
+2 -2
View File
@@ -136,13 +136,13 @@ func (m *msc) scsiEvpdInquiry(cmd scsi.Cmd, pageCode uint8) {
// Set total bytes to the length of our response // Set total bytes to the length of our response
m.queuedBytes = uint32(len(m.buf)) m.queuedBytes = uint32(len(m.buf))
m.totalBytes = uint32(len(m.buf)) m.transferBytes = uint32(len(m.buf))
} }
func (m *msc) scsiStdInquiry(cmd scsi.Cmd) { func (m *msc) scsiStdInquiry(cmd scsi.Cmd) {
m.resetBuffer(scsi.InquiryRespLen) m.resetBuffer(scsi.InquiryRespLen)
m.queuedBytes = scsi.InquiryRespLen m.queuedBytes = scsi.InquiryRespLen
m.totalBytes = scsi.InquiryRespLen m.transferBytes = scsi.InquiryRespLen
// byte 0 - Device Type (0x00 for direct access block device) // byte 0 - Device Type (0x00 for direct access block device)
// byte 1 - Removable media bit // byte 1 - Removable media bit
+5 -5
View File
@@ -12,7 +12,7 @@ func (m *msc) scsiCmdReadWrite(cmd scsi.Cmd) {
status := m.validateScsiReadWrite(cmd) status := m.validateScsiReadWrite(cmd)
if status != csw.StatusPassed { if status != csw.StatusPassed {
m.sendScsiError(status, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode) m.sendScsiError(status, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode)
} else if m.totalBytes > 0 { } else if m.transferBytes > 0 {
if cmd.CmdType() == scsi.CmdRead { if cmd.CmdType() == scsi.CmdRead {
m.scsiRead(cmd) m.scsiRead(cmd)
} else { } else {
@@ -28,7 +28,7 @@ func (m *msc) scsiCmdReadWrite(cmd scsi.Cmd) {
func (m *msc) validateScsiReadWrite(cmd scsi.Cmd) csw.Status { func (m *msc) validateScsiReadWrite(cmd scsi.Cmd) csw.Status {
blockCount := cmd.BlockCount() blockCount := cmd.BlockCount()
// CBW wrapper transfer length // CBW wrapper transfer length
if m.totalBytes == 0 { if m.transferBytes == 0 {
// If the SCSI command's block count doesn't loosely match the wrapper's transfer length something's wrong // If the SCSI command's block count doesn't loosely match the wrapper's transfer length something's wrong
if blockCount > 0 { if blockCount > 0 {
return csw.StatusPhaseError return csw.StatusPhaseError
@@ -50,7 +50,7 @@ func (m *msc) validateScsiReadWrite(cmd scsi.Cmd) csw.Status {
// https://usb.org/sites/default/files/usbmassbulk_10.pdf // https://usb.org/sites/default/files/usbmassbulk_10.pdf
return csw.StatusFailed return csw.StatusFailed
} }
if m.totalBytes/blockCount == 0 { if m.transferBytes/blockCount == 0 {
// Block size shouldn't be small enough to round to zero // Block size shouldn't be small enough to round to zero
// 6.7.2 The Thirteen Cases - Case 7 (Hi < Di) READ(10) or // 6.7.2 The Thirteen Cases - Case 7 (Hi < Di) READ(10) or
// 6.7.3 The Thirteen Cases - Case 13 (Ho < Do) WRITE(10) // 6.7.3 The Thirteen Cases - Case 13 (Ho < Do) WRITE(10)
@@ -103,7 +103,7 @@ func (m *msc) writeBlock(b []byte, lba, offset uint32) (n int, err error) {
func (m *msc) scsiRead(cmd scsi.Cmd) { func (m *msc) scsiRead(cmd scsi.Cmd) {
// Make sure we don't exceed the buffer size // Make sure we don't exceed the buffer size
readEnd := m.totalBytes - m.sentBytes readEnd := m.transferBytes - m.sentBytes
if readEnd > m.maxPacketSize { if readEnd > m.maxPacketSize {
readEnd = m.maxPacketSize readEnd = m.maxPacketSize
} }
@@ -136,7 +136,7 @@ func (m *msc) scsiWrite(cmd scsi.Cmd, b []byte) {
m.sentBytes += uint32(len(b)) m.sentBytes += uint32(len(b))
} }
if m.sentBytes >= m.totalBytes { if m.sentBytes >= m.transferBytes {
// Data transfer is complete, send CSW // Data transfer is complete, send CSW
m.state = mscStateStatus m.state = mscStateStatus
m.run([]byte{}, true) m.run([]byte{}, true)
+1 -1
View File
@@ -60,7 +60,7 @@ func (m *msc) scsiUnmap(b []byte) {
// FIXME: We need to handle erase block alignment // FIXME: We need to handle erase block alignment
m.sentBytes += uint32(len(b)) m.sentBytes += uint32(len(b))
if m.sentBytes >= m.totalBytes { if m.sentBytes >= m.transferBytes {
// Order 66 complete, send CSW to establish galactic empire // Order 66 complete, send CSW to establish galactic empire
m.state = mscStateStatus m.state = mscStateStatus
m.run([]byte{}, true) m.run([]byte{}, true)
+15 -9
View File
@@ -3,7 +3,6 @@ package msc
import ( import (
"machine" "machine"
"machine/usb" "machine/usb"
"machine/usb/msc/csw"
) )
func setupPacketHandler(setup usb.Setup) bool { func setupPacketHandler(setup usb.Setup) bool {
@@ -18,11 +17,17 @@ func (m *msc) setupPacketHandler(setup usb.Setup) bool {
wValue := (uint16(setup.WValueH) << 8) | uint16(setup.WValueL) wValue := (uint16(setup.WValueH) << 8) | uint16(setup.WValueL)
switch setup.BRequest { switch setup.BRequest {
case usb.CLEAR_FEATURE: case usb.CLEAR_FEATURE:
ok = m.handleClearFeature(setup, wValue) if setup.BmRequestType == 0x02 { // Host-to-Device | Standard | Endpoint
ok = m.handleClearFeature(setup, wValue)
}
case usb.GET_MAX_LUN: case usb.GET_MAX_LUN:
ok = m.handleGetMaxLun(setup, wValue) if setup.BmRequestType == 0xA1 { // Device-to-Host | Class | Interface
ok = m.handleGetMaxLun(setup, wValue)
}
case usb.MSC_RESET: case usb.MSC_RESET:
ok = m.handleReset(setup, wValue) if setup.BmRequestType == 0x21 { // Host-to-Device | Class | Interface
ok = m.handleReset(setup, wValue)
}
} }
return ok return ok
} }
@@ -53,24 +58,25 @@ func (m *msc) handleClearFeature(setup usb.Setup, wValue uint16) bool {
} else if wIndex == usb.MSC_ENDPOINT_OUT { } else if wIndex == usb.MSC_ENDPOINT_OUT {
m.stallEndpoint(usb.MSC_ENDPOINT_OUT) m.stallEndpoint(usb.MSC_ENDPOINT_OUT)
} }
return ok machine.SendZlp()
return true
} }
// Clear the direction bit from the endpoint address for comparison // Clear the direction bit from the endpoint address for comparison
wIndex := setup.WIndex & 0x7F wIndex := setup.WIndex & 0x7F
// Clear the IN/OUT stalls if addressed to the endpoint, or both if addressed to the interface // Clear the IN/OUT stalls if addressed to the endpoint
if wIndex == usb.MSC_ENDPOINT_IN || wIndex == mscInterface { if wIndex == usb.MSC_ENDPOINT_IN {
m.clearStallEndpoint(usb.MSC_ENDPOINT_IN) m.clearStallEndpoint(usb.MSC_ENDPOINT_IN)
ok = true ok = true
} }
if wIndex == usb.MSC_ENDPOINT_OUT || wIndex == mscInterface { if wIndex == usb.MSC_ENDPOINT_OUT {
m.clearStallEndpoint(usb.MSC_ENDPOINT_OUT) m.clearStallEndpoint(usb.MSC_ENDPOINT_OUT)
ok = true ok = true
} }
// Send a CSW if needed to resume after the IN endpoint stall is cleared // Send a CSW if needed to resume after the IN endpoint stall is cleared
if m.state == mscStateStatus && wIndex == usb.MSC_ENDPOINT_IN { if m.state == mscStateStatus && wIndex == usb.MSC_ENDPOINT_IN {
m.sendCSW(csw.StatusPassed) m.sendCSW(m.respStatus)
ok = true ok = true
} }
+8
View File
@@ -73,6 +73,14 @@ func align(ptr uintptr) uintptr {
//export tinygo_getCurrentStackPointer //export tinygo_getCurrentStackPointer
func getCurrentStackPointer() uintptr func getCurrentStackPointer() uintptr
// savedStackPointer is used to save the system stack pointer while in a goroutine.
var savedStackPointer uintptr
// saveStackPointer is called by internal/task.state.Resume() to save the stack pointer before entering a goroutine from the system stack.
func saveStackPointer() {
savedStackPointer = getCurrentStackPointer()
}
// growHeap tries to grow the heap size. It returns true if it succeeds, false // growHeap tries to grow the heap size. It returns true if it succeeds, false
// otherwise. // otherwise.
func growHeap() bool { func growHeap() bool {
+19 -12
View File
@@ -8,16 +8,21 @@ import "unsafe"
// code linked from other languages can allocate memory without colliding with // code linked from other languages can allocate memory without colliding with
// our GC allocations. // our GC allocations.
var allocs = make(map[uintptr][]byte) // Map of allocations, where the key is the allocated pointer and the value is
// the size of the allocation.
// TODO: make this a map[unsafe.Pointer]uintptr, since that results in slightly
// smaller binaries. But for that to work, unsafe.Pointer needs to be seen as a
// binary key (which it is not at the moment).
// See https://github.com/tinygo-org/tinygo/pull/4898 for details.
var allocs = make(map[*byte]uintptr)
//export malloc //export malloc
func libc_malloc(size uintptr) unsafe.Pointer { func libc_malloc(size uintptr) unsafe.Pointer {
if size == 0 { if size == 0 {
return nil return nil
} }
buf := make([]byte, size) ptr := alloc(size, nil)
ptr := unsafe.Pointer(&buf[0]) allocs[(*byte)(ptr)] = size
allocs[uintptr(ptr)] = buf
return ptr return ptr
} }
@@ -26,8 +31,8 @@ func libc_free(ptr unsafe.Pointer) {
if ptr == nil { if ptr == nil {
return return
} }
if _, ok := allocs[uintptr(ptr)]; ok { if _, ok := allocs[(*byte)(ptr)]; ok {
delete(allocs, uintptr(ptr)) delete(allocs, (*byte)(ptr))
} else { } else {
panic("free: invalid pointer") panic("free: invalid pointer")
} }
@@ -48,18 +53,20 @@ func libc_realloc(oldPtr unsafe.Pointer, size uintptr) unsafe.Pointer {
// It's hard to optimize this to expand the current buffer with our GC, but // It's hard to optimize this to expand the current buffer with our GC, but
// it is theoretically possible. For now, just always allocate fresh. // it is theoretically possible. For now, just always allocate fresh.
buf := make([]byte, size) // TODO: we could skip this if the new allocation is smaller than the old.
ptr := alloc(size, nil)
if oldPtr != nil { if oldPtr != nil {
if oldBuf, ok := allocs[uintptr(oldPtr)]; ok { if oldSize, ok := allocs[(*byte)(oldPtr)]; ok {
copy(buf, oldBuf) oldBuf := unsafe.Slice((*byte)(oldPtr), oldSize)
delete(allocs, uintptr(oldPtr)) newBuf := unsafe.Slice((*byte)(ptr), size)
copy(newBuf, oldBuf)
delete(allocs, (*byte)(oldPtr))
} else { } else {
panic("realloc: invalid pointer") panic("realloc: invalid pointer")
} }
} }
ptr := unsafe.Pointer(&buf[0]) allocs[(*byte)(ptr)] = size
allocs[uintptr(ptr)] = buf
return ptr return ptr
} }
+3
View File
@@ -78,5 +78,8 @@ tinygo_longjmp:
ld r28, X+ ld r28, X+
ld r29, X+ ld r29, X+
; Mark that we returned from a longjmp.
ldi r24, 1
; Jump to the PC (stored in the Z register) ; Jump to the PC (stored in the Z register)
icall icall
+402 -292
View File
@@ -46,18 +46,15 @@ const (
bytesPerBlock = wordsPerBlock * unsafe.Sizeof(heapStart) bytesPerBlock = wordsPerBlock * unsafe.Sizeof(heapStart)
stateBits = 2 // how many bits a block state takes (see blockState type) stateBits = 2 // how many bits a block state takes (see blockState type)
blocksPerStateByte = 8 / stateBits blocksPerStateByte = 8 / stateBits
markStackSize = 8 * unsafe.Sizeof((*int)(nil)) // number of to-be-marked blocks to queue before forcing a rescan
) )
var ( var (
metadataStart unsafe.Pointer // pointer to the start of the heap metadata metadataStart unsafe.Pointer // pointer to the start of the heap metadata
nextAlloc gcBlock // the next block that should be tried by the allocator scanList *objHeader // scanList is a singly linked list of heap objects that have been marked but not scanned
freeRanges *freeRange // freeRanges is a linked list of free block ranges
endBlock gcBlock // the block just past the end of the available space endBlock gcBlock // the block just past the end of the available space
gcTotalAlloc uint64 // total number of bytes allocated gcTotalAlloc uint64 // total number of bytes allocated
gcTotalBlocks uint64 // total number of allocated blocks
gcMallocs uint64 // total number of allocations gcMallocs uint64 // total number of allocations
gcFrees uint64 // total number of objects freed
gcFreedBlocks uint64 // total number of freed blocks
gcLock task.PMutex // lock to avoid race conditions on multicore systems gcLock task.PMutex // lock to avoid race conditions on multicore systems
) )
@@ -66,24 +63,28 @@ var zeroSizedAlloc uint8
// Provide some abstraction over heap blocks. // Provide some abstraction over heap blocks.
// blockState stores the four states in which a block can be. It is two bits in // blockState stores the four states in which a block can be.
// size. // It holds 1 bit in each nibble.
// When stored into a state byte, each bit in a nibble corresponds to a different block.
// For blocks A-D, a state byte would be laid out as 0bDCBA_DCBA.
type blockState uint8 type blockState uint8
const ( const (
blockStateFree blockState = 0 // 00 blockStateLow blockState = 1
blockStateHead blockState = 1 // 01 blockStateHigh blockState = 1 << blocksPerStateByte
blockStateTail blockState = 2 // 10
blockStateMark blockState = 3 // 11 blockStateFree blockState = 0
blockStateMask blockState = 3 // 11 blockStateHead blockState = blockStateLow
blockStateTail blockState = blockStateHigh
blockStateMark blockState = blockStateLow | blockStateHigh
blockStateMask blockState = blockStateLow | blockStateHigh
) )
// blockStateEach is a mask that can be used to extract a nibble from the block state.
const blockStateEach = 1<<blocksPerStateByte - 1
// The byte value of a block where every block is a 'tail' block. // The byte value of a block where every block is a 'tail' block.
const blockStateByteAllTails = 0 | const blockStateByteAllTails = byte(blockStateTail) * blockStateEach
uint8(blockStateTail<<(stateBits*3)) |
uint8(blockStateTail<<(stateBits*2)) |
uint8(blockStateTail<<(stateBits*1)) |
uint8(blockStateTail<<(stateBits*0))
// String returns a human-readable version of the block state, for debugging. // String returns a human-readable version of the block state, for debugging.
func (s blockState) String() string { func (s blockState) String() string {
@@ -180,7 +181,7 @@ func (b gcBlock) stateByte() byte {
// Return the block state given a state byte. The state byte must have been // Return the block state given a state byte. The state byte must have been
// obtained using b.stateByte(), otherwise the result is incorrect. // obtained using b.stateByte(), otherwise the result is incorrect.
func (b gcBlock) stateFromByte(stateByte byte) blockState { func (b gcBlock) stateFromByte(stateByte byte) blockState {
return blockState(stateByte>>((b%blocksPerStateByte)*stateBits)) & blockStateMask return blockState(stateByte>>(b%blocksPerStateByte)) & blockStateMask
} }
// State returns the current block state. // State returns the current block state.
@@ -193,36 +194,112 @@ func (b gcBlock) state() blockState {
// from head to mark. // from head to mark.
func (b gcBlock) setState(newState blockState) { func (b gcBlock) setState(newState blockState) {
stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte))
*stateBytePtr |= uint8(newState << ((b % blocksPerStateByte) * stateBits)) *stateBytePtr |= uint8(newState << (b % blocksPerStateByte))
if gcAsserts && b.state() != newState { if gcAsserts && b.state() != newState {
runtimePanic("gc: setState() was not successful") runtimePanic("gc: setState() was not successful")
} }
} }
// markFree sets the block state to free, no matter what state it was in before. // objHeader is a structure prepended to every heap object to hold metadata.
func (b gcBlock) markFree() { type objHeader struct {
stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) // next is the next object to scan after this.
*stateBytePtr &^= uint8(blockStateMask << ((b % blocksPerStateByte) * stateBits)) next *objHeader
if gcAsserts && b.state() != blockStateFree {
runtimePanic("gc: markFree() was not successful") // layout holds the layout bitmap used to find pointers in the object.
layout gcLayout
}
// freeRange is a node on the outer list of range lengths.
// The free ranges are structured as two nested singly-linked lists:
// - The outer level (freeRange) has one entry for each unique range length.
// - The inner level (freeRangeMore) has one entry for each additional range of the same length.
// This two-level structure ensures that insertion/removal times are proportional to the requested length.
type freeRange struct {
// len is the length of this free range.
len uintptr
// nextLen is the next longer free range.
nextLen *freeRange
// nextWithLen is the next free range with this length.
nextWithLen *freeRangeMore
}
// freeRangeMore is a node on the inner list of equal-length ranges.
type freeRangeMore struct {
next *freeRangeMore
}
// insertFreeRange inserts a range of len blocks starting at ptr into the free list.
func insertFreeRange(ptr unsafe.Pointer, len uintptr) {
if gcAsserts && len == 0 {
runtimePanic("gc: insert 0-length free range")
} }
if gcAsserts {
*(*[wordsPerBlock]uintptr)(unsafe.Pointer(b.address())) = [wordsPerBlock]uintptr{} // Find the insertion point by length.
// Skip until the next range is at least the target length.
insDst := &freeRanges
for *insDst != nil && (*insDst).len < len {
insDst = &(*insDst).nextLen
}
// Create the new free range.
next := *insDst
if next != nil && next.len == len {
// Insert into the list with this length.
newRange := (*freeRangeMore)(ptr)
newRange.next = next.nextWithLen
next.nextWithLen = newRange
} else {
// Insert into the list of lengths.
newRange := (*freeRange)(ptr)
*newRange = freeRange{
len: len,
nextLen: next,
nextWithLen: nil,
}
*insDst = newRange
} }
} }
// unmark changes the state of the block from mark to head. It must be marked // popFreeRange removes a range of len blocks from the freeRanges list.
// before calling this function. // It returns nil if there are no sufficiently long ranges.
func (b gcBlock) unmark() { func popFreeRange(len uintptr) unsafe.Pointer {
if gcAsserts && b.state() != blockStateMark { if gcAsserts && len == 0 {
runtimePanic("gc: unmark() on a block that is not marked") runtimePanic("gc: pop 0-length free range")
} }
clearMask := blockStateMask ^ blockStateHead // the bits to clear from the state
stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte)) // Find the removal point by length.
*stateBytePtr &^= uint8(clearMask << ((b % blocksPerStateByte) * stateBits)) // Skip until the next range is at least the target length.
if gcAsserts && b.state() != blockStateHead { remDst := &freeRanges
runtimePanic("gc: unmark() was not successful") for *remDst != nil && (*remDst).len < len {
remDst = &(*remDst).nextLen
} }
rangeWithLength := *remDst
if rangeWithLength == nil {
// No ranges are long enough.
return nil
}
removedLen := rangeWithLength.len
// Remove the range.
var ptr unsafe.Pointer
if nextWithLen := rangeWithLength.nextWithLen; nextWithLen != nil {
// Remove from the list with this length.
rangeWithLength.nextWithLen = nextWithLen.next
ptr = unsafe.Pointer(nextWithLen)
} else {
// Remove from the list of lengths.
*remDst = rangeWithLength.nextLen
ptr = unsafe.Pointer(rangeWithLength)
}
if removedLen > len {
// Insert the leftover range.
insertFreeRange(unsafe.Add(ptr, len*bytesPerBlock), removedLen-len)
}
return ptr
} }
func isOnHeap(ptr uintptr) bool { func isOnHeap(ptr uintptr) bool {
@@ -239,6 +316,9 @@ func initHeap() {
// Set all block states to 'free'. // Set all block states to 'free'.
metadataSize := heapEnd - uintptr(metadataStart) metadataSize := heapEnd - uintptr(metadataStart)
memzero(unsafe.Pointer(metadataStart), metadataSize) memzero(unsafe.Pointer(metadataStart), metadataSize)
// Rebuild the free ranges list.
buildFreeRanges()
} }
// setHeapEnd is called to expand the heap. The heap can only grow, not shrink. // setHeapEnd is called to expand the heap. The heap can only grow, not shrink.
@@ -270,6 +350,9 @@ func setHeapEnd(newHeapEnd uintptr) {
if gcAsserts && uintptr(metadataStart) < uintptr(oldMetadataStart)+oldMetadataSize { if gcAsserts && uintptr(metadataStart) < uintptr(oldMetadataStart)+oldMetadataSize {
runtimePanic("gc: heap did not grow enough at once") runtimePanic("gc: heap did not grow enough at once")
} }
// Rebuild the free ranges list.
buildFreeRanges()
} }
// calculateHeapAddresses initializes variables such as metadataStart and // calculateHeapAddresses initializes variables such as metadataStart and
@@ -311,118 +394,88 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
return unsafe.Pointer(&zeroSizedAlloc) return unsafe.Pointer(&zeroSizedAlloc)
} }
if preciseHeap {
size += align(unsafe.Sizeof(layout))
}
if interrupt.In() { if interrupt.In() {
runtimePanicAt(returnAddress(0), "heap alloc in interrupt") runtimePanicAt(returnAddress(0), "heap alloc in interrupt")
} }
// Round the size up to a multiple of blocks, adding space for the header.
rawSize := size
size += align(unsafe.Sizeof(objHeader{}))
size += bytesPerBlock - 1
if size < rawSize {
// The size overflowed.
runtimePanicAt(returnAddress(0), "out of memory")
}
neededBlocks := size / bytesPerBlock
size = neededBlocks * bytesPerBlock
// Make sure there are no concurrent allocations. The heap is not currently // Make sure there are no concurrent allocations. The heap is not currently
// designed for concurrent alloc/GC. // designed for concurrent alloc/GC.
gcLock.Lock() gcLock.Lock()
gcTotalAlloc += uint64(size) // Update the total allocation counters.
gcTotalAlloc += uint64(rawSize)
gcMallocs++ gcMallocs++
neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock // Acquire a range of free blocks.
gcTotalBlocks += uint64(neededBlocks) var ranGC bool
var grewHeap bool
// Continue looping until a run of free blocks has been found that fits the var pointer unsafe.Pointer
// requested size.
index := nextAlloc
numFreeBlocks := uintptr(0)
heapScanCount := uint8(0)
for { for {
if index == nextAlloc { pointer = popFreeRange(neededBlocks)
if heapScanCount == 0 { if pointer != nil {
heapScanCount = 1 break
} else if heapScanCount == 1 {
// The entire heap has been searched for free memory, but none
// could be found. Run a garbage collection cycle to reclaim
// free memory and try again.
heapScanCount = 2
freeBytes := runGC()
heapSize := uintptr(metadataStart) - heapStart
if freeBytes < heapSize/3 {
// Ensure there is at least 33% headroom.
// This percentage was arbitrarily chosen, and may need to
// be tuned in the future.
growHeap()
}
} else {
// Even after garbage collection, no free memory could be found.
// Try to increase heap size.
if growHeap() {
// Success, the heap was increased in size. Try again with a
// larger heap.
} else {
// Unfortunately the heap could not be increased. This
// happens on baremetal systems for example (where all
// available RAM has already been dedicated to the heap).
runtimePanicAt(returnAddress(0), "out of memory")
}
}
} }
// Wrap around the end of the heap. if !ranGC {
if index == endBlock { // Run the collector and try again.
index = 0 freeBytes := runGC()
// Reset numFreeBlocks as allocations cannot wrap. ranGC = true
numFreeBlocks = 0 heapSize := uintptr(metadataStart) - heapStart
// In rare cases, the initial heap might be so small that there are if freeBytes < heapSize/3 {
// no blocks at all. In this case, it's better to jump back to the // Ensure there is at least 33% headroom.
// start of the loop and try again, until the GC realizes there is // This percentage was arbitrarily chosen, and may need to
// no memory and grows the heap. // be tuned in the future.
// This can sometimes happen on WebAssembly, where the initial heap growHeap()
// is created by whatever is left on the last memory page. }
continue continue
} }
// Is the block we're looking at free? if gcDebug && !grewHeap {
if index.state() != blockStateFree { println("grow heap for request:", uint(neededBlocks))
// This block is in use. Try again from this point. dumpFreeRangeCounts()
numFreeBlocks = 0 }
index++ if growHeap() {
grewHeap = true
continue continue
} }
numFreeBlocks++
index++
// Are we finished? // Unfortunately the heap could not be increased. This
if numFreeBlocks == neededBlocks { // happens on baremetal systems for example (where all
// Found a big enough range of free blocks! // available RAM has already been dedicated to the heap).
nextAlloc = index runtimePanicAt(returnAddress(0), "out of memory")
thisAlloc := index - gcBlock(neededBlocks)
if gcDebug {
println("found memory:", thisAlloc.pointer(), int(size))
}
// Set the following blocks as being allocated.
thisAlloc.setState(blockStateHead)
for i := thisAlloc + 1; i != nextAlloc; i++ {
i.setState(blockStateTail)
}
// We've claimed this allocation, now we can unlock the heap.
gcLock.Unlock()
// Return a pointer to this allocation.
pointer := thisAlloc.pointer()
if preciseHeap {
// Store the object layout at the start of the object.
// TODO: this wastes a little bit of space on systems with
// larger-than-pointer alignment requirements.
*(*unsafe.Pointer)(pointer) = layout
add := align(unsafe.Sizeof(layout))
pointer = unsafe.Add(pointer, add)
size -= add
}
memzero(pointer, size)
return pointer
}
} }
// Set the backing blocks as being allocated.
block := blockFromAddr(uintptr(pointer))
block.setState(blockStateHead)
for i := block + 1; i != block+gcBlock(neededBlocks); i++ {
i.setState(blockStateTail)
}
// Create the object header.
header := (*objHeader)(pointer)
header.layout = parseGCLayout(layout)
// We've claimed this allocation, now we can unlock the heap.
gcLock.Unlock()
// Return a pointer to this allocation.
add := align(unsafe.Sizeof(objHeader{}))
pointer = unsafe.Add(pointer, add)
size -= add
memzero(pointer, size)
return pointer
} }
func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer { func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer {
@@ -507,7 +560,10 @@ func runGC() (freeBytes uintptr) {
// Sweep phase: free all non-marked objects and unmark marked objects for // Sweep phase: free all non-marked objects and unmark marked objects for
// the next collection cycle. // the next collection cycle.
freeBytes = sweep() sweep()
// Rebuild the free ranges list.
freeBytes = buildFreeRanges()
// Show how much has been sweeped, for debugging. // Show how much has been sweeped, for debugging.
if gcDebug { if gcDebug {
@@ -519,8 +575,7 @@ func runGC() (freeBytes uintptr) {
// markRoots reads all pointers from start to end (exclusive) and if they look // markRoots reads all pointers from start to end (exclusive) and if they look
// like a heap pointer and are unmarked, marks them and scans that object as // like a heap pointer and are unmarked, marks them and scans that object as
// well (recursively). The start and end parameters must be valid pointers and // well (recursively). The starting address must be valid and aligned.
// must be aligned.
func markRoots(start, end uintptr) { func markRoots(start, end uintptr) {
if gcDebug { if gcDebug {
println("mark from", start, "to", end, int(end-start)) println("mark from", start, "to", end, int(end-start))
@@ -532,18 +587,21 @@ func markRoots(start, end uintptr) {
if start%unsafe.Alignof(start) != 0 { if start%unsafe.Alignof(start) != 0 {
runtimePanic("gc: unaligned start pointer") runtimePanic("gc: unaligned start pointer")
} }
if end%unsafe.Alignof(end) != 0 {
runtimePanic("gc: unaligned end pointer")
}
} }
// Reduce the end bound to avoid reading too far on platforms where pointer alignment is smaller than pointer size. // Scan the range conservatively.
// If the size of the range is 0, then end will be slightly below start after this. scanConservative(start, end-start)
end -= unsafe.Sizeof(end) - unsafe.Alignof(end) }
for addr := start; addr < end; addr += unsafe.Alignof(addr) { // scanConservative scans all possible pointer locations in a range and marks referenced heap allocations.
// The starting address must be valid and pointer-aligned.
func scanConservative(addr, len uintptr) {
for len >= unsafe.Sizeof(addr) {
root := *(*uintptr)(unsafe.Pointer(addr)) root := *(*uintptr)(unsafe.Pointer(addr))
markRoot(addr, root) markRoot(addr, root)
addr += unsafe.Alignof(addr)
len -= unsafe.Alignof(addr)
} }
} }
@@ -553,159 +611,164 @@ func markCurrentGoroutineStack(sp uintptr) {
markRoot(0, sp) markRoot(0, sp)
} }
// stackOverflow is a flag which is set when the GC scans too deep while marking. // finishMark finishes the marking process by scanning all heap objects on scanList.
// After it is set, all marked allocations must be re-scanned. func finishMark() {
var stackOverflow bool for {
// Remove an object from the scan list.
// startMark starts the marking process on a root and all of its children. obj := scanList
func startMark(root gcBlock) { if obj == nil {
var stack [markStackSize]gcBlock return
stack[0] = root
root.setState(blockStateMark)
stackLen := 1
for stackLen > 0 {
// Pop a block off of the stack.
stackLen--
block := stack[stackLen]
if gcDebug {
println("stack popped, remaining stack:", stackLen)
} }
scanList = obj.next
// Scan all pointers inside the block. // Check if the object may contain pointers.
scanner := newGCObjectScanner(block) if obj.layout.pointerFree() {
if scanner.pointerFree() {
// This object doesn't contain any pointers. // This object doesn't contain any pointers.
// This is a fast path for objects like make([]int, 4096). // This is a fast path for objects like make([]int, 4096).
// It skips the length calculation.
continue continue
} }
start, end := block.address(), block.findNext().address()
if preciseHeap {
// The first word of the object is just the pointer layout value.
// Skip it.
start += align(unsafe.Sizeof(uintptr(0)))
}
for addr := start; addr != end; addr += unsafe.Alignof(addr) {
// Load the word.
word := *(*uintptr)(unsafe.Pointer(addr))
if !scanner.nextIsPointer(word, root.address(), addr) { // Compute the scan bounds.
// Not a heap pointer. objAddr := uintptr(unsafe.Pointer(obj))
continue start := objAddr + align(unsafe.Sizeof(objHeader{}))
} end := blockFromAddr(objAddr).findNext().address()
// Find the corresponding memory block. // Scan the object.
referencedBlock := blockFromAddr(word) obj.layout.scan(start, end-start)
if referencedBlock.state() == blockStateFree {
// The to-be-marked object doesn't actually exist.
// This is probably a false positive.
if gcDebug {
println("found reference to free memory:", word, "at:", addr)
}
continue
}
// Move to the block's head.
referencedBlock = referencedBlock.findHead()
if referencedBlock.state() == blockStateMark {
// The block has already been marked by something else.
continue
}
// Mark block.
if gcDebug {
println("marking block:", referencedBlock)
}
referencedBlock.setState(blockStateMark)
if stackLen == len(stack) {
// The stack is full.
// It is necessary to rescan all marked blocks once we are done.
stackOverflow = true
if gcDebug {
println("gc stack overflowed")
}
continue
}
// Push the pointer onto the stack to be scanned later.
stack[stackLen] = referencedBlock
stackLen++
}
}
}
// finishMark finishes the marking process by processing all stack overflows.
func finishMark() {
for stackOverflow {
// Re-mark all blocks.
stackOverflow = false
for block := gcBlock(0); block < endBlock; block++ {
if block.state() != blockStateMark {
// Block is not marked, so we do not need to rescan it.
continue
}
// Re-mark the block.
startMark(block)
}
} }
} }
// mark a GC root at the address addr. // mark a GC root at the address addr.
func markRoot(addr, root uintptr) { func markRoot(addr, root uintptr) {
if isOnHeap(root) { // Find the heap block corresponding to the root.
block := blockFromAddr(root) if !isOnHeap(root) {
if block.state() == blockStateFree { // This is not a heap pointer.
// The to-be-marked object doesn't actually exist. return
// This could either be a dangling pointer (oops!) but most likely
// just a false positive.
return
}
head := block.findHead()
if head.state() != blockStateMark {
if gcDebug {
println("found unmarked pointer", root, "at address", addr)
}
startMark(head)
}
} }
block := blockFromAddr(root)
// Find the head of the corresponding object.
if block.state() == blockStateFree {
// The to-be-marked object doesn't actually exist.
// This could either be a dangling pointer (oops!) but most likely
// just a false positive.
return
}
head := block.findHead()
// Mark the object.
if head.state() == blockStateMark {
// This object is already marked.
return
}
if gcDebug {
println("found unmarked pointer", root, "at address", addr)
}
head.setState(blockStateMark)
// Add the object to the scan list.
header := (*objHeader)(head.pointer())
header.next = scanList
scanList = header
} }
// Sweep goes through all memory and frees unmarked memory. // Sweep goes through all memory and frees unmarked memory.
// It returns how many bytes are free in the heap after the sweep. func sweep() {
func sweep() (freeBytes uintptr) { metadataEnd := unsafe.Add(metadataStart, (endBlock+(blocksPerStateByte-1))/blocksPerStateByte)
freeCurrentObject := false var carry byte
var freed uint64 for meta := metadataStart; meta != metadataEnd; meta = unsafe.Add(meta, 1) {
for block := gcBlock(0); block < endBlock; block++ { // Fetch the state byte.
switch block.state() { stateBytePtr := (*byte)(unsafe.Pointer(meta))
case blockStateHead: stateByte := *stateBytePtr
// Unmarked head. Free it, including all tail blocks following it.
block.markFree() // Separate blocks by type.
freeCurrentObject = true // Split the nibbles.
gcFrees++ // Each nibble is a mask of blocks.
freed++ high := stateByte >> blocksPerStateByte
case blockStateTail: low := stateByte & blockStateEach
if freeCurrentObject { // Marked heads are in both nibbles.
// This is a tail object following an unmarked head. markedHeads := low & high
// Free it now. // Unmarked heads are in the low nibble but not the high nibble.
block.markFree() unmarkedHeads := low &^ high
freed++ // Tails are in the high nibble but not the low nibble.
} tails := high &^ low
case blockStateMark:
// This is a marked object. The next tail blocks must not be freed, // Clear all tail runs after unmarked (freed) heads.
// but the mark bit must be removed so the next GC cycle will //
// collect this object if it is unreferenced then. // Adding 1 to the start of a bit run will clear the run and set the next bit:
block.unmark() // (2^k - 1) + 1 = 2^k
freeCurrentObject = false // e.g. 0b0011 + 1 = 0b0100
case blockStateFree: // Bitwise-and with the original mask to clear the newly set bit.
freeBytes += bytesPerBlock // e.g. (0b0011 + 1) & 0b0011 = 0b0100 & 0b0011 = 0b0000
} // This will not clear bits after the run because the gap stops the carry:
// e.g. (0b1011 + 1) & 0b1011 = 0b1100 & 0b1011 = 0b1000
// This can clear multiple runs in a single addition:
// e.g. (0b1101 + 0b0101) & 0b1101 = 0b10010 & 0b1101 = 0b0000
//
// In order to find tail run starts after unmarked heads we could use tails & (unmarkedHeads << 1).
// It is possible omit the bitwise-and because the clear still works if the next block is not a tail.
// A head is not a tail, so corresponding missing tail bit will stop the carry from a previous tail run.
// As such it will set the next bit which will be cleared back away later.
// e.g. HHTH: (0b0010 + (0b1101 << 1)) & 0b0010 = 0b11100 & 0b0010 = 0b0000
//
// Treat the whole heap as a single pair of integer masks.
// This is accomplished for addition by carrying the overflow to the next state byte.
// The unmarkedHeads << 1 is equivalent to unmarkedHeads + unmarkedHeads, so it can be merged with the sum.
// This does not require any special work for the bitwise-and because it operates bitwise.
tailClear := tails + (unmarkedHeads << 1) + carry
carry = tailClear >> blocksPerStateByte
tails &= tailClear
// Construct the new state byte.
*stateBytePtr = markedHeads | (tails << blocksPerStateByte)
}
}
// buildFreeRanges rebuilds the freeRanges list.
// This must be called after a GC sweep or heap grow.
// It returns how many bytes are free in the heap.
func buildFreeRanges() uintptr {
freeRanges = nil
block := endBlock
var totalBlocks uintptr
for {
// Skip backwards over occupied blocks.
for block > 0 && (block-1).state() != blockStateFree {
block--
}
if block == 0 {
break
}
// Find the start of the free range.
end := block
for block > 0 && (block-1).state() == blockStateFree {
block--
}
// Insert the free range.
len := uintptr(end - block)
totalBlocks += len
insertFreeRange(block.pointer(), len)
}
if gcDebug {
println("free ranges after rebuild:")
dumpFreeRangeCounts()
}
return totalBlocks * bytesPerBlock
}
func dumpFreeRangeCounts() {
for rangeWithLength := freeRanges; rangeWithLength != nil; rangeWithLength = rangeWithLength.nextLen {
totalRanges := uintptr(1)
for nextWithLen := rangeWithLength.nextWithLen; nextWithLen != nil; nextWithLen = nextWithLen.next {
totalRanges++
}
println("-", uint(rangeWithLength.len), "x", uint(totalRanges))
} }
gcFreedBlocks += freed
freeBytes += uintptr(freed) * bytesPerBlock
return
} }
// dumpHeap can be used for debugging purposes. It dumps the state of each heap // dumpHeap can be used for debugging purposes. It dumps the state of each heap
@@ -735,28 +798,75 @@ func dumpHeap() {
// call to ReadMemStats. This would not do GC implicitly for you. // call to ReadMemStats. This would not do GC implicitly for you.
func ReadMemStats(m *MemStats) { func ReadMemStats(m *MemStats) {
gcLock.Lock() gcLock.Lock()
m.HeapIdle = 0
m.HeapInuse = 0 // Calculate the raw size of the heap.
for block := gcBlock(0); block < endBlock; block++ { heapEnd := heapEnd
bstate := block.state() heapStart := heapStart
if bstate == blockStateFree {
m.HeapIdle += uint64(bytesPerBlock)
} else {
m.HeapInuse += uint64(bytesPerBlock)
}
}
m.HeapReleased = 0 // always 0, we don't currently release memory back to the OS.
m.HeapSys = m.HeapInuse + m.HeapIdle
m.GCSys = uint64(heapEnd - uintptr(metadataStart))
m.TotalAlloc = gcTotalAlloc
m.Mallocs = gcMallocs
m.Frees = gcFrees
m.Sys = uint64(heapEnd - heapStart) m.Sys = uint64(heapEnd - heapStart)
m.HeapAlloc = (gcTotalBlocks - gcFreedBlocks) * uint64(bytesPerBlock) m.HeapSys = uint64(uintptr(metadataStart) - heapStart)
m.Alloc = m.HeapAlloc metadataStart := metadataStart
// TODO: should GCSys include objHeaders?
m.GCSys = uint64(heapEnd - uintptr(metadataStart))
m.HeapReleased = 0 // always 0, we don't currently release memory back to the OS.
// Count live heads and tails.
var liveHeads, liveTails uintptr
endBlock := endBlock
metadataEnd := unsafe.Add(metadataStart, (endBlock+(blocksPerStateByte-1))/blocksPerStateByte)
for meta := metadataStart; meta != metadataEnd; meta = unsafe.Add(meta, 1) {
// Since we are outside of a GC, nothing is marked.
// A bit in the low nibble implies a head.
// A bit in the high nibble implies a tail.
stateByte := *(*byte)(unsafe.Pointer(meta))
liveHeads += uintptr(count4LUT[stateByte&blockStateEach])
liveTails += uintptr(count4LUT[stateByte>>blocksPerStateByte])
}
// Add heads and tails to count live blocks.
liveBlocks := liveHeads + liveTails
liveBytes := uint64(liveBlocks * bytesPerBlock)
m.HeapInuse = liveBytes
m.HeapAlloc = liveBytes
m.Alloc = liveBytes
// Subtract live blocks from total blocks to count free blocks.
freeBlocks := uintptr(endBlock) - liveBlocks
m.HeapIdle = uint64(freeBlocks * bytesPerBlock)
// Record the number of allocated objects.
gcMallocs := gcMallocs
m.Mallocs = gcMallocs
// Subtract live objects from allocated objects to count freed objects.
m.Frees = gcMallocs - uint64(liveHeads)
// Record the total allocated bytes.
m.TotalAlloc = gcTotalAlloc
gcLock.Unlock() gcLock.Unlock()
} }
// count4LUT is a lookup table used to count set bits in a 4-bit mask.
// TODO: replace with popcnt when available
var count4LUT = [16]uint8{
0b0000: 0,
0b0001: 1,
0b0010: 1,
0b0011: 2,
0b0100: 1,
0b0101: 2,
0b0110: 2,
0b0111: 3,
0b1000: 1,
0b1001: 2,
0b1010: 2,
0b1011: 3,
0b1100: 2,
0b1101: 3,
0b1110: 3,
0b1111: 4,
}
func SetFinalizer(obj interface{}, finalizer interface{}) { func SetFinalizer(obj interface{}, finalizer interface{}) {
// Unimplemented. // Unimplemented.
} }
+2 -24
View File
@@ -31,10 +31,6 @@ var zeroSizedAlloc uint8
var gcLock task.PMutex var gcLock task.PMutex
// Normally false, set to true during a GC scan when all other threads get
// paused.
var needsResumeWorld bool
func initHeap() { func initHeap() {
libgc_init() libgc_init()
@@ -50,18 +46,6 @@ func gcInit()
func gcCallback() { func gcCallback() {
// Mark globals and all stacks, and stop the world if we're using threading. // Mark globals and all stacks, and stop the world if we're using threading.
gcMarkReachable() gcMarkReachable()
// If we use a scheduler with parallelism (the threads scheduler for
// example), we need to call gcResumeWorld() after scanning has finished.
if hasParallelism {
if needsResumeWorld {
// Should never happen, check for it anyway.
runtimePanic("gc: world already stopped")
}
// Note that we need to resume the world after finishing the GC call.
needsResumeWorld = true
}
} }
func markRoots(start, end uintptr) { func markRoots(start, end uintptr) {
@@ -87,7 +71,6 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
} }
gcLock.Lock() gcLock.Lock()
needsResumeWorld = false
var ptr unsafe.Pointer var ptr unsafe.Pointer
if layout == gclayout.NoPtrs.AsPtr() { if layout == gclayout.NoPtrs.AsPtr() {
// This object is entirely pointer free, for example make([]int, ...). // This object is entirely pointer free, for example make([]int, ...).
@@ -104,9 +87,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
// Memory returned from libgc_malloc has already been zeroed, so nothing // Memory returned from libgc_malloc has already been zeroed, so nothing
// to do here. // to do here.
} }
if needsResumeWorld { gcResumeWorld()
gcResumeWorld()
}
gcLock.Unlock() gcLock.Unlock()
if ptr == nil { if ptr == nil {
runtimePanic("gc: out of memory") runtimePanic("gc: out of memory")
@@ -121,11 +102,8 @@ func free(ptr unsafe.Pointer) {
func GC() { func GC() {
gcLock.Lock() gcLock.Lock()
needsResumeWorld = false
libgc_gcollect() libgc_gcollect()
if needsResumeWorld { gcResumeWorld()
gcResumeWorld()
}
gcLock.Unlock() gcLock.Unlock()
} }
+11 -10
View File
@@ -6,24 +6,25 @@
package runtime package runtime
const preciseHeap = false import "unsafe"
type gcObjectScanner struct { // parseGCLayout stores the layout information passed to alloc into a gcLayout value.
// The conservative GC discards this information.
func parseGCLayout(layout unsafe.Pointer) gcLayout {
return gcLayout{}
} }
func newGCObjectScanner(block gcBlock) gcObjectScanner { // gcLayout tracks pointer locations in a heap object.
return gcObjectScanner{} // The conservative GC treats all locations as potential pointers, so this doesn't need to store anything.
type gcLayout struct {
} }
func (scanner *gcObjectScanner) pointerFree() bool { func (l gcLayout) pointerFree() bool {
// We don't know whether this object contains pointers, so conservatively // We don't know whether this object contains pointers, so conservatively
// return false. // return false.
return false return false
} }
// nextIsPointer returns whether this could be a pointer. Because the GC is func (l gcLayout) scan(start, len uintptr) {
// conservative, we can't do much more than check whether the object lies scanConservative(start, len)
// somewhere in the heap.
func (scanner gcObjectScanner) nextIsPointer(ptr, parent, addrOfWord uintptr) bool {
return isOnHeap(ptr)
} }
+79 -74
View File
@@ -57,91 +57,96 @@ package runtime
import "unsafe" import "unsafe"
const preciseHeap = true const sizeFieldBits = 4 + (unsafe.Sizeof(uintptr(0)) / 4)
type gcObjectScanner struct { // parseGCLayout stores the layout information passed to alloc into a gcLayout value.
index uintptr func parseGCLayout(layout unsafe.Pointer) gcLayout {
size uintptr return gcLayout(layout)
bitmap uintptr
bitmapAddr unsafe.Pointer
} }
func newGCObjectScanner(block gcBlock) gcObjectScanner { // gcLayout tracks pointer locations in a heap object.
if gcAsserts && block != block.findHead() { type gcLayout uintptr
runtimePanic("gc: object scanner must start at head")
}
scanner := gcObjectScanner{}
layout := *(*uintptr)(unsafe.Pointer(block.address()))
if layout == 0 {
// Unknown layout. Assume all words in the object could be pointers.
// This layout value below corresponds to a slice of pointers like:
// make(*byte, N)
scanner.size = 1
scanner.bitmap = 1
} else if layout&1 != 0 {
// Layout is stored directly in the integer value.
// Determine format of bitfields in the integer.
const layoutBits = uint64(unsafe.Sizeof(layout) * 8)
var sizeFieldBits uint64
switch layoutBits { // note: this switch should be resolved at compile time
case 16:
sizeFieldBits = 4
case 32:
sizeFieldBits = 5
case 64:
sizeFieldBits = 6
default:
runtimePanic("unknown pointer size")
}
// Extract values from the bitfields. func (layout gcLayout) pointerFree() bool {
// See comment at the top of this file for more information. return layout&1 != 0 && layout>>(sizeFieldBits+1) == 0
scanner.size = (layout >> 1) & (1<<sizeFieldBits - 1) }
scanner.bitmap = layout >> (1 + sizeFieldBits)
} else { // scan an object with this element layout.
// Layout is stored separately in a global object. // The starting address must be valid and pointer-aligned.
// The length is rounded down to a multiple of the element size.
func (layout gcLayout) scan(start, len uintptr) {
switch {
case layout == 0:
// This is an unknown layout.
// Scan conservatively.
// NOTE: This is *NOT* equivalent to a slice of pointers on AVR.
scanConservative(start, len)
case layout&1 != 0:
// The layout is stored directly in the integer value.
// Extract the bitfields.
size := uintptr(layout>>1) & (1<<sizeFieldBits - 1)
mask := uintptr(layout) >> (1 + sizeFieldBits)
// Scan with the extracted mask.
scanSimple(start, len, size*unsafe.Alignof(start), mask)
default:
// The layout is stored seperately in a global object.
// Extract the size and bitmap.
layoutAddr := unsafe.Pointer(layout) layoutAddr := unsafe.Pointer(layout)
scanner.size = *(*uintptr)(layoutAddr) size := *(*uintptr)(layoutAddr)
scanner.bitmapAddr = unsafe.Add(layoutAddr, unsafe.Sizeof(uintptr(0))) bitmapPtr := unsafe.Add(layoutAddr, unsafe.Sizeof(uintptr(0)))
bitmapLen := (size + 7) / 8
bitmap := unsafe.Slice((*byte)(bitmapPtr), bitmapLen)
// Scan with the bitmap.
scanComplex(start, len, size*unsafe.Alignof(start), bitmap)
} }
return scanner
} }
func (scanner *gcObjectScanner) pointerFree() bool { // scanSimple scans an object with an integer bitmask of pointer locations.
if scanner.bitmapAddr != nil { // The starting address must be valid and pointer-aligned.
// While the format allows for large objects without pointers, this is func scanSimple(start, len, size, mask uintptr) {
// optimized by the compiler so if bitmapAddr is set, we know that there for len >= size {
// are at least some pointers in the object. // Scan this element.
return false scanWithMask(start, mask)
// Move to the next element.
start += size
len -= size
} }
// If the bitmap is zero, there are definitely no pointers in the object.
return scanner.bitmap == 0
} }
func (scanner *gcObjectScanner) nextIsPointer(word, parent, addrOfWord uintptr) bool { // scanComplex scans an object with a bitmap of pointer locations.
index := scanner.index // The starting address must be valid and pointer-aligned.
scanner.index++ func scanComplex(start, len, size uintptr, bitmap []byte) {
if scanner.index == scanner.size { for len >= size {
scanner.index = 0 // Scan this element.
} for i, mask := range bitmap {
addr := start + 8*unsafe.Alignof(start)*uintptr(i)
if !isOnHeap(word) { scanWithMask(addr, uintptr(mask))
// Definitely isn't a pointer.
return false
}
// Might be a pointer. Now look at the object layout to know for sure.
if scanner.bitmapAddr != nil {
if (*(*uint8)(unsafe.Add(scanner.bitmapAddr, index/8))>>(index%8))&1 == 0 {
return false
} }
return true
}
if (scanner.bitmap>>index)&1 == 0 {
// not a pointer!
return false
}
// Probably a pointer. // Move to the next element.
return true start += size
len -= size
}
}
// scanWithMask scans a portion of an object with a mask of pointer locations.
// The address must be valid and pointer-aligned.
func scanWithMask(addr, mask uintptr) {
// TODO: use ctz when available
for mask != 0 {
if mask&1 != 0 {
// Load and mark this pointer.
root := *(*uintptr)(unsafe.Pointer(addr))
markRoot(addr, root)
}
// Move to the next offset.
mask >>= 1
addr += unsafe.Alignof(addr)
}
} }
+10 -11
View File
@@ -29,16 +29,6 @@ type stackChainObject struct {
// - The system stack (aka startup stack) is not heap allocated, so even // - The system stack (aka startup stack) is not heap allocated, so even
// though it may be referenced it will not be scanned by default. // though it may be referenced it will not be scanned by default.
// //
// Therefore, we only need to scan the system stack.
// It is relatively easy to scan the system stack while we're on it: we can
// simply read __stack_pointer and __global_base and scan the area in between.
// Unfortunately, it's hard to get the system stack pointer while we're on a
// goroutine stack. But when we're on a goroutine stack, the system stack is in
// the scheduler which means there shouldn't be anything on the system stack
// anyway.
// ...I hope this assumption holds, otherwise we will need to store the system
// stack in a global or something.
//
// The compiler also inserts code to store all globals in a chain via // The compiler also inserts code to store all globals in a chain via
// stackChainStart. Luckily we don't need to scan these, as these globals are // stackChainStart. Luckily we don't need to scan these, as these globals are
// stored on the goroutine stack and are therefore already getting scanned. // stored on the goroutine stack and are therefore already getting scanned.
@@ -50,9 +40,18 @@ func markStack() {
// live. // live.
volatile.LoadUint32((*uint32)(unsafe.Pointer(&stackChainStart))) volatile.LoadUint32((*uint32)(unsafe.Pointer(&stackChainStart)))
// Scan the system stack.
var sysSP uintptr
if task.OnSystemStack() { if task.OnSystemStack() {
markRoots(getCurrentStackPointer(), stackTop) // We are on the system stack.
// Use the current stack pointer.
sysSP = getCurrentStackPointer()
} else {
// We are in a goroutine.
// Use the saved stack pointer.
sysSP = savedStackPointer
} }
markRoots(sysSP, stackTop)
} }
// trackPointer is a stub function call inserted by the compiler during IR // trackPointer is a stub function call inserted by the compiler during IR
+6 -1
View File
@@ -1,7 +1,12 @@
//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt)) //go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350)
// If you update the above build constraint, you'll probably also need to update // If you update the above build constraint, you'll probably also need to update
// src/crypto/rand/rand_baremetal.go. // src/crypto/rand/rand_baremetal.go.
//
// The rp2040 and rp2350 implementations are not included in src/crypto/rand/rand_baremetal.go
// due to not being sufficiently random for the Go crypto libs.
// However since the randomness here does not provide those same guarantees,
// they are included in the list for hardwareRand() implementations.
package runtime package runtime
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt)) //go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) || rp2040 || rp2350)
package runtime package runtime
+4 -3
View File
@@ -6,7 +6,7 @@ import (
"device/arm" "device/arm"
"device/sam" "device/sam"
"machine" "machine"
"machine/usb/cdc" _ "machine/usb/cdc"
"runtime/interrupt" "runtime/interrupt"
"runtime/volatile" "runtime/volatile"
"unsafe" "unsafe"
@@ -26,8 +26,6 @@ func init() {
initUSBClock() initUSBClock()
initADCClock() initADCClock()
cdc.EnableUSBCDC()
machine.USBDev.Configure(machine.UARTConfig{})
machine.InitSerial() machine.InitSerial()
} }
@@ -273,6 +271,9 @@ func nanosecondsToTicks(ns int64) timeUnit {
func sleepTicks(d timeUnit) { func sleepTicks(d timeUnit) {
for d != 0 { for d != 0 {
ticks := uint32(d) ticks := uint32(d)
if d > 0xffff_ffff {
ticks = 0xffff_ffff
}
if !timerSleep(ticks) { if !timerSleep(ticks) {
// Bail out early to handle a non-time interrupt. // Bail out early to handle a non-time interrupt.
return return
+4 -3
View File
@@ -6,7 +6,7 @@ import (
"device/arm" "device/arm"
"device/sam" "device/sam"
"machine" "machine"
"machine/usb/cdc" _ "machine/usb/cdc"
"runtime/interrupt" "runtime/interrupt"
"runtime/volatile" "runtime/volatile"
) )
@@ -27,8 +27,6 @@ func init() {
initADCClock() initADCClock()
enableCache() enableCache()
cdc.EnableUSBCDC()
machine.USBDev.Configure(machine.UARTConfig{})
machine.InitSerial() machine.InitSerial()
} }
@@ -266,6 +264,9 @@ func nanosecondsToTicks(ns int64) timeUnit {
func sleepTicks(d timeUnit) { func sleepTicks(d timeUnit) {
for d != 0 { for d != 0 {
ticks := uint32(d) ticks := uint32(d)
if d > 0xffff_ffff {
ticks = 0xffff_ffff
}
if !timerSleep(ticks) { if !timerSleep(ticks) {
return return
} }
+82
View File
@@ -0,0 +1,82 @@
//go:build esp32s3
package runtime
import (
"device/esp"
)
// This is the function called on startup after the flash (IROM/DROM) is
// initialized and the stack pointer has been set.
//
//export main
func main() {
// This initialization configures the following things:
// * It disables all watchdog timers. They might be useful at some point in
// the future, but will need integration into the scheduler. For now,
// they're all disabled.
// * It sets the CPU frequency to 240MHz, which is the maximum speed allowed
// for this CPU. Lower frequencies might be possible in the future, but
// running fast and sleeping quickly is often also a good strategy to save
// power.
// TODO: protect certain memory regions, especially the area below the stack
// to protect against stack overflows. See
// esp_cpu_configure_region_protection in ESP-IDF.
// Disable RTC watchdog.
esp.RTC_CNTL.WDTWPROTECT.Set(0x50D83AA1)
esp.RTC_CNTL.WDTCONFIG0.Set(0)
esp.RTC_CNTL.WDTWPROTECT.Set(0x0) // Re-enable write protect
// Disable Timer 0 watchdog.
esp.TIMG1.WDTWPROTECT.Set(0x50D83AA1) // write protect
esp.TIMG1.WDTCONFIG0.Set(0) // disable TG0 WDT
esp.TIMG1.WDTWPROTECT.Set(0x0) // Re-enable write protect
esp.TIMG0.WDTWPROTECT.Set(0x50D83AA1) // write protect
esp.TIMG0.WDTCONFIG0.Set(0) // disable TG0 WDT
esp.TIMG0.WDTWPROTECT.Set(0x0) // Re-enable write protect
// Disable super watchdog.
esp.RTC_CNTL.SWD_WPROTECT.Set(0x8F1D312A)
esp.RTC_CNTL.SWD_CONF.Set(esp.RTC_CNTL_SWD_CONF_SWD_DISABLE)
esp.RTC_CNTL.SWD_WPROTECT.Set(0x0) // Re-enable write protect
// Change CPU frequency from 20MHz to 80MHz, by switching from the XTAL to the
// PLL clock source (see table "CPU Clock Frequency" in the reference manual).
esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1)
// Change CPU frequency from 80MHz to 240MHz by setting SYSTEM_PLL_FREQ_SEL to
// 1 and SYSTEM_CPUPERIOD_SEL to 2 (see table "CPU Clock Frequency" in the
// reference manual).
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1)
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2)
// Clear bss. Repeat many times while we wait for cpu/clock to stabilize
for x := 0; x < 30; x++ {
clearbss()
}
// Initialize main system timer used for time.Now.
initTimer()
// Initialize the heap, call main.main, etc.
run()
// Fallback: if main ever returns, hang the CPU.
exit(0)
}
func abort() {
// lock up forever
print("abort called\n")
}
//go:extern _vector_table
var _vector_table [0]uintptr
//go:extern _sbss
var _sbss [0]byte
//go:extern _ebss
var _ebss [0]byte
+86
View File
@@ -0,0 +1,86 @@
//go:build esp32s3
package runtime
import (
"device/esp"
"machine"
"unsafe"
)
//type timeUnit int64
func putchar(c byte) {
machine.Serial.WriteByte(c)
}
func getchar() byte {
for machine.Serial.Buffered() == 0 {
Gosched()
}
v, _ := machine.Serial.ReadByte()
return v
}
func buffered() int {
return machine.Serial.Buffered()
}
// Initialize .bss: zero-initialized global variables.
// The .data section has already been loaded by the ROM bootloader.
func clearbss() {
ptr := unsafe.Pointer(&_sbss)
for ptr != unsafe.Pointer(&_ebss) {
*(*uint32)(ptr) = 0
ptr = unsafe.Add(ptr, 4)
}
}
func initTimer() {
// Configure timer 0 in timer group 0, for timekeeping.
// EN: Enable the timer.
// INCREASE: Count up every tick (as opposed to counting down).
// DIVIDER: 16-bit prescaler, set to 2 for dividing the APB clock by two
// (40MHz).
// esp.TIMG0.T0CONFIG.Set(0 << esp.TIMG_T0CONFIG_T0_EN_Pos)
esp.TIMG0.T0CONFIG.Set(esp.TIMG_TCONFIG_EN | esp.TIMG_TCONFIG_INCREASE | 2<<esp.TIMG_TCONFIG_DIVIDER_Pos)
// esp.TIMG0.T0CONFIG.Set(1 << esp.TIMG_T0CONFIG_T0_DIVCNT_RST_Pos)
// esp.TIMG0.T0CONFIG.Set(esp.TIMG_T0CONFIG_T0_EN)
// Set the timer counter value to 0.
esp.TIMG0.T0LOADLO.Set(0)
esp.TIMG0.T0LOADHI.Set(0)
esp.TIMG0.T0LOAD.Set(0) // value doesn't matter.
}
func ticks() timeUnit {
// First, update the LO and HI register pair by writing any value to the
// register. This allows reading the pair atomically.
esp.TIMG0.T0UPDATE.Set(0)
// Then read the two 32-bit parts of the timer.
return timeUnit(uint64(esp.TIMG0.T0LO.Get()) | uint64(esp.TIMG0.T0HI.Get())<<32)
}
func nanosecondsToTicks(ns int64) timeUnit {
// Calculate the number of ticks from the number of nanoseconds. At a 80MHz
// APB clock, that's 25 nanoseconds per tick with a timer prescaler of 2:
// 25 = 1e9 / (80MHz / 2)
return timeUnit(ns / 25)
}
func ticksToNanoseconds(ticks timeUnit) int64 {
// See nanosecondsToTicks.
return int64(ticks) * 25
}
// sleepTicks busy-waits until the given number of ticks have passed.
func sleepTicks(d timeUnit) {
sleepUntil := ticks() + d
for ticks() < sleepUntil {
// TODO: suspend the CPU to not burn power here unnecessarily.
}
}
func exit(code int) {
abort()
}
+4 -1
View File
@@ -1,4 +1,4 @@
//go:build nrf && !nrf52840 //go:build nrf
package runtime package runtime
@@ -78,6 +78,9 @@ func buffered() int {
func sleepTicks(d timeUnit) { func sleepTicks(d timeUnit) {
for d != 0 { for d != 0 {
ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side) ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side)
if d > 0x7fffff {
ticks = 0x7fffff
}
rtc_sleep(ticks) rtc_sleep(ticks)
d -= timeUnit(ticks) d -= timeUnit(ticks)
} }
+3 -144
View File
@@ -2,147 +2,6 @@
package runtime package runtime
import ( // This package needs to be present so that the machine package can go:linkname
"device/arm" // EnableUSBCDC from it.
"device/nrf" import _ "machine/usb/cdc"
"machine"
"machine/usb/cdc"
"runtime/interrupt"
"runtime/volatile"
)
//go:linkname systemInit SystemInit
func systemInit()
//export Reset_Handler
func main() {
if nrf.FPUPresent {
arm.SCB.CPACR.Set(0) // disable FPU if it is enabled
}
systemInit()
preinit()
run()
exit(0)
}
func init() {
cdc.EnableUSBCDC()
machine.USBDev.Configure(machine.UARTConfig{})
machine.InitSerial()
initLFCLK()
initRTC()
}
func initLFCLK() {
if machine.HasLowFrequencyCrystal {
nrf.CLOCK.LFCLKSRC.Set(nrf.CLOCK_LFCLKSTAT_SRC_Xtal)
}
nrf.CLOCK.TASKS_LFCLKSTART.Set(1)
for nrf.CLOCK.EVENTS_LFCLKSTARTED.Get() == 0 {
}
nrf.CLOCK.EVENTS_LFCLKSTARTED.Set(0)
}
func initRTC() {
nrf.RTC1.TASKS_START.Set(1)
intr := interrupt.New(nrf.IRQ_RTC1, func(intr interrupt.Interrupt) {
if nrf.RTC1.EVENTS_COMPARE[0].Get() != 0 {
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
nrf.RTC1.INTENCLR.Set(nrf.RTC_INTENSET_COMPARE0)
nrf.RTC1.EVENTS_COMPARE[0].Set(0)
rtc_wakeup.Set(1)
}
if nrf.RTC1.EVENTS_OVRFLW.Get() != 0 {
nrf.RTC1.EVENTS_OVRFLW.Set(0)
rtcOverflows.Set(rtcOverflows.Get() + 1)
}
})
nrf.RTC1.INTENSET.Set(nrf.RTC_INTENSET_OVRFLW)
intr.SetPriority(0xc0) // low priority
intr.Enable()
}
func putchar(c byte) {
machine.Serial.WriteByte(c)
}
func getchar() byte {
for machine.Serial.Buffered() == 0 {
Gosched()
}
v, _ := machine.Serial.ReadByte()
return v
}
func buffered() int {
return machine.Serial.Buffered()
}
func sleepTicks(d timeUnit) {
for d != 0 {
ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side)
rtc_sleep(ticks)
d -= timeUnit(ticks)
}
}
var rtcOverflows volatile.Register32 // number of times the RTC wrapped around
// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds.
func ticksToNanoseconds(ticks timeUnit) int64 {
// The following calculation is actually the following, but with both sides
// reduced to reduce the risk of overflow:
// ticks * 1e9 / 32768
return int64(ticks) * 1953125 / 64
}
// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz).
func nanosecondsToTicks(ns int64) timeUnit {
// The following calculation is actually the following, but with both sides
// reduced to reduce the risk of overflow:
// ns * 32768 / 1e9
return timeUnit(ns * 64 / 1953125)
}
// Monotonically increasing number of ticks since start.
func ticks() timeUnit {
// For some ways of capturing the time atomically, see this thread:
// https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617
// Here, instead of re-reading the counter register if an overflow has been
// detected, we simply try again because that results in (slightly) smaller
// code and is perhaps easier to prove correct.
for {
mask := interrupt.Disable()
counter := uint32(nrf.RTC1.COUNTER.Get())
overflows := rtcOverflows.Get()
hasOverflow := nrf.RTC1.EVENTS_OVRFLW.Get() != 0
interrupt.Restore(mask)
if hasOverflow {
// There was an overflow. Try again.
continue
}
// The counter is 24 bits in size, so the number of overflows form the
// upper 32 bits (together 56 bits, which covers 71493 years at
// 32768kHz: I'd argue good enough for most purposes).
return timeUnit(overflows)<<24 + timeUnit(counter)
}
}
var rtc_wakeup volatile.Register8
func rtc_sleep(ticks uint32) {
nrf.RTC1.INTENSET.Set(nrf.RTC_INTENSET_COMPARE0)
rtc_wakeup.Set(0)
if ticks == 1 {
// Race condition (even in hardware) at ticks == 1.
// TODO: fix this in a better way by detecting it, like the manual
// describes.
ticks = 2
}
nrf.RTC1.CC[0].Set((nrf.RTC1.COUNTER.Get() + ticks) & 0x00ffffff)
for rtc_wakeup.Get() == 0 {
waitForEvents()
}
}
+12 -5
View File
@@ -13,17 +13,24 @@ func sd_app_evt_wait()
// This is a global variable to avoid a heap allocation in waitForEvents. // This is a global variable to avoid a heap allocation in waitForEvents.
var softdeviceEnabled uint8 var softdeviceEnabled uint8
// Check whether the SoftDevice is currently enabled.
// This function is also called from the machine package, so the signature has
// to stay consistent.
func isSoftDeviceEnabled() bool {
// First check whether the SoftDevice is enabled. Unfortunately,
// sd_app_evt_wait cannot be called when the SoftDevice is not enabled.
arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled
return softdeviceEnabled != 0
}
func waitForEvents() { func waitForEvents() {
// Call into the SoftDevice to sleep. This is necessary here because a // Call into the SoftDevice to sleep. This is necessary here because a
// normal wfe will not put the chip in low power mode (it still consumes // normal wfe will not put the chip in low power mode (it still consumes
// 500µA-1mA). It is really needed to call sd_app_evt_wait for low power // 500µA-1mA). It is really needed to call sd_app_evt_wait for low power
// consumption. // consumption.
// First check whether the SoftDevice is enabled. Unfortunately, if isSoftDeviceEnabled() {
// sd_app_evt_wait cannot be called when the SoftDevice is not enabled.
arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled
if softdeviceEnabled != 0 {
// Now pick the appropriate SVCall number. Hopefully they won't change // Now pick the appropriate SVCall number. Hopefully they won't change
// in the future with a different SoftDevice version. // in the future with a different SoftDevice version.
if nrf.Device == "nrf51" { if nrf.Device == "nrf51" {
+1 -3
View File
@@ -7,7 +7,7 @@ import (
"device/rp" "device/rp"
"internal/task" "internal/task"
"machine" "machine"
"machine/usb/cdc" _ "machine/usb/cdc"
"runtime/interrupt" "runtime/interrupt"
"runtime/volatile" "runtime/volatile"
"unsafe" "unsafe"
@@ -360,8 +360,6 @@ func machineInit()
func init() { func init() {
machineInit() machineInit()
cdc.EnableUSBCDC()
machine.USBDev.Configure(machine.UARTConfig{})
machine.InitSerial() machine.InitSerial()
} }
+1 -1
View File
@@ -34,8 +34,8 @@ func wasmEntryReactor() {
// Initialize the heap. // Initialize the heap.
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize)
initHeap()
initRand() initRand()
initHeap()
if hasScheduler { if hasScheduler {
// A package initializer might do funky stuff like start a goroutine and // A package initializer might do funky stuff like start a goroutine and
+1 -1
View File
@@ -243,8 +243,8 @@ func sleep(duration int64) {
// run is called by the program entry point to execute the go program. // run is called by the program entry point to execute the go program.
// With a scheduler, init and the main function are invoked in a goroutine before starting the scheduler. // With a scheduler, init and the main function are invoked in a goroutine before starting the scheduler.
func run() { func run() {
initHeap()
initRand() initRand()
initHeap()
go func() { go func() {
initAll() initAll()
callMain() callMain()
+1
View File
@@ -135,6 +135,7 @@ func sleep(duration int64) {
// This function is called on the first core in the system. It will wake up the // This function is called on the first core in the system. It will wake up the
// other cores when ready. // other cores when ready.
func run() { func run() {
initRand()
initHeap() initHeap()
go func() { go func() {
+1 -1
View File
@@ -21,8 +21,8 @@ var schedulerExit bool
// run is called by the program entry point to execute the go program. // run is called by the program entry point to execute the go program.
// With the "none" scheduler, init and the main function are invoked directly. // With the "none" scheduler, init and the main function are invoked directly.
func run() { func run() {
initHeap()
initRand() initRand()
initHeap()
initAll() initAll()
callMain() callMain()
mainExited = true mainExited = true
+1
View File
@@ -21,6 +21,7 @@ var (
// Because we just use OS threads, we don't need to do anything special here. We // Because we just use OS threads, we don't need to do anything special here. We
// can just initialize everything and run main.main on the main thread. // can just initialize everything and run main.main on the main thread.
func run() { func run() {
initRand()
initHeap() initHeap()
task.Init(stackTop) task.Init(stackTop)
initAll() initAll()
+3 -10
View File
@@ -3,19 +3,18 @@ package runtime
// This file implements compiler builtins for slices: append() and copy(). // This file implements compiler builtins for slices: append() and copy().
import ( import (
"internal/gclayout"
"math/bits" "math/bits"
"unsafe" "unsafe"
) )
// Builtin append(src, elements...) function: append elements to src and return // Builtin append(src, elements...) function: append elements to src and return
// the modified (possibly expanded) slice. // the modified (possibly expanded) slice.
func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) { func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen, elemSize uintptr, layout unsafe.Pointer) (unsafe.Pointer, uintptr, uintptr) {
newLen := srcLen + elemsLen newLen := srcLen + elemsLen
if elemsLen > 0 { if elemsLen > 0 {
// Allocate a new slice with capacity for elemsLen more elements, if necessary; // Allocate a new slice with capacity for elemsLen more elements, if necessary;
// otherwise, reuse the passed slice. // otherwise, reuse the passed slice.
srcBuf, _, srcCap = sliceGrow(srcBuf, srcLen, srcCap, newLen, elemSize) srcBuf, _, srcCap = sliceGrow(srcBuf, srcLen, srcCap, newLen, elemSize, layout)
// Append the new elements in-place. // Append the new elements in-place.
memmove(unsafe.Add(srcBuf, srcLen*elemSize), elemsBuf, elemsLen*elemSize) memmove(unsafe.Add(srcBuf, srcLen*elemSize), elemsBuf, elemsLen*elemSize)
@@ -36,7 +35,7 @@ func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr
} }
// sliceGrow returns a new slice with space for at least newCap elements // sliceGrow returns a new slice with space for at least newCap elements
func sliceGrow(oldBuf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) { func sliceGrow(oldBuf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr, layout unsafe.Pointer) (unsafe.Pointer, uintptr, uintptr) {
if oldCap >= newCap { if oldCap >= newCap {
// No need to grow, return the input slice. // No need to grow, return the input slice.
return oldBuf, oldLen, oldCap return oldBuf, oldLen, oldCap
@@ -48,12 +47,6 @@ func sliceGrow(oldBuf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr)
// memory allocators, this causes some difficult to debug issues. // memory allocators, this causes some difficult to debug issues.
newCap = 1 << bits.Len(uint(newCap)) newCap = 1 << bits.Len(uint(newCap))
var layout unsafe.Pointer
// less type info here; can only go off element size
if elemSize < unsafe.Sizeof(uintptr(0)) {
layout = gclayout.NoPtrs.AsPtr()
}
buf := alloc(newCap*elemSize, layout) buf := alloc(newCap*elemSize, layout)
if oldLen > 0 { if oldLen > 0 {
// copy any data to new slice // copy any data to new slice
+1
View File
@@ -212,6 +212,7 @@ func writeStream(stream *wasiStream, buf *byte, count uint, offset int64) int {
return -1 return -1
} }
remaining -= len remaining -= len
src = src[len:]
} }
return int(count) return int(count)
+28 -5
View File
@@ -10,6 +10,7 @@ package testing
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
@@ -78,6 +79,9 @@ type common struct {
tempDir string tempDir string
tempDirErr error tempDirErr error
tempDirSeq int32 tempDirSeq int32
ctx context.Context
cancelCtx context.CancelFunc
} }
type logger struct { type logger struct {
@@ -152,6 +156,7 @@ func fmtDuration(d time.Duration) string {
// TB is the interface common to T and B. // TB is the interface common to T and B.
type TB interface { type TB interface {
Cleanup(func()) Cleanup(func())
Context() context.Context
Error(args ...interface{}) Error(args ...interface{})
Errorf(format string, args ...interface{}) Errorf(format string, args ...interface{})
Fail() Fail()
@@ -307,6 +312,15 @@ func (c *common) Cleanup(f func()) {
c.cleanups = append(c.cleanups, f) c.cleanups = append(c.cleanups, f)
} }
// Context returns a context that is canceled just before
// Cleanup-registered functions are called.
//
// Cleanup functions can wait for any resources
// that shut down on [context.Context.Done] before the test or benchmark completes.
func (c *common) Context() context.Context {
return c.ctx
}
// TempDir returns a temporary directory for the test to use. // TempDir returns a temporary directory for the test to use.
// The directory is automatically removed by Cleanup when the test and // The directory is automatically removed by Cleanup when the test and
// all its subtests complete. // all its subtests complete.
@@ -447,6 +461,9 @@ func (c *common) runCleanup() {
if cleanup == nil { if cleanup == nil {
return return
} }
if c.cancelCtx != nil {
c.cancelCtx()
}
cleanup() cleanup()
} }
} }
@@ -488,12 +505,15 @@ func (t *T) Run(name string, f func(t *T)) bool {
} }
// Create a subtest. // Create a subtest.
ctx, cancelCtx := context.WithCancel(context.Background())
sub := T{ sub := T{
common: common{ common: common{
output: &logger{logToStdout: flagVerbose}, output: &logger{logToStdout: flagVerbose},
name: testName, name: testName,
parent: &t.common, parent: &t.common,
level: t.level + 1, level: t.level + 1,
ctx: ctx,
cancelCtx: cancelCtx,
}, },
context: t.context, context: t.context,
} }
@@ -606,9 +626,12 @@ func runTests(matchString func(pat, str string) (bool, error), tests []InternalT
ok = true ok = true
ctx := newTestContext(newMatcher(matchString, flagRunRegexp, "-test.run", flagSkipRegexp)) ctx := newTestContext(newMatcher(matchString, flagRunRegexp, "-test.run", flagSkipRegexp))
runCtx, cancelCtx := context.WithCancel(context.Background())
t := &T{ t := &T{
common: common{ common: common{
output: &logger{logToStdout: flagVerbose}, output: &logger{logToStdout: flagVerbose},
ctx: runCtx,
cancelCtx: cancelCtx,
}, },
context: ctx, context: ctx,
} }
+21
View File
@@ -0,0 +1,21 @@
{
"inherits": ["xtensa"],
"cpu": "esp32s3",
"features": "+atomctl,+bool,+clamps,+coprocessor,+debug,+density,+div32,+esp32s3,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+minmax,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed",
"build-tags": ["esp32s3", "esp"],
"scheduler": "tasks",
"serial": "uart",
"linker": "ld.lld",
"default-stack-size": 2048,
"rtlib": "compiler-rt",
"libc": "picolibc",
"linkerscript": "targets/esp32s3.ld",
"extra-files": [
"src/device/esp/esp32.S",
"src/internal/task/task_stack_esp32.S"
],
"binary-format": "esp32s3",
"flash-command": "esptool.py --chip=esp32s3 --port {port} write_flash 0x0000 {bin} -ff 80m -fm dout",
"emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw",
"gdb": ["xtensa-esp32-elf-gdb"]
}
+208
View File
@@ -0,0 +1,208 @@
/* Linker script for the ESP32-S3 */
MEMORY
{
/* Note: DRAM and IRAM below are actually in the same 416K address space. */
DRAM (rw) : ORIGIN = 0x3FC88000, LENGTH = 416K /* Internal SRAM 1 (data bus) */
IRAM (x) : ORIGIN = 0x40370000, LENGTH = 416K /* Internal SRAM 1 (instruction bus) */
/* Note: DROM and IROM below are actually in the same 32M address space. */
DROM (r) : ORIGIN = 0x3C000000, LENGTH = 32M /* Data bus (read-only) */
IROM (rx) : ORIGIN = 0x42000000, LENGTH = 32M /* Instruction bus */
}
/* The entry point. It is set in the image flashed to the chip, so must be
* defined.
*/
ENTRY(call_start_cpu0)
SECTIONS
{
/* Put the stack at the bottom of DRAM, so that the application will
* crash on stack overflow instead of silently corrupting memory.
* See: http://blog.japaric.io/stack-overflow-protection/ */
.stack (NOLOAD) :
{
. = ALIGN(16);
. += _stack_size;
_stack_top = .;
} >DRAM
/* Constant literals and code. Loaded into IRAM for now. Eventually, most
* code should be executed directly from flash.
* Note that literals must be before code for the l32r instruction to work.
*/
.text.call_start_cpu0 : ALIGN(4)
{
*(.literal.call_start_cpu0)
*(.text.call_start_cpu0)
} >IRAM AT >DRAM
/* All other code and literals */
.text : ALIGN(4)
{
*(.literal .text)
*(.literal.* .text.*)
*(.text)
*(.text.*)
} >IRAM AT >DRAM
/* Constant global variables.
* They are loaded in DRAM for ease of use. Eventually they should be stored
* in flash and loaded directly from there but they're kept in RAM to make
* sure they can always be accessed (even in interrupts).
*/
.rodata : ALIGN(4)
{
*(.rodata)
*(.rodata.*)
} >DRAM
/* Mutable global variables.
*/
.data : ALIGN(4)
{
_sdata = ABSOLUTE(.);
*(.data)
*(.data.*)
_edata = ABSOLUTE(.);
} >DRAM
/* Check that the boot ROM stack (for the APP CPU) does not overlap with the
* data that is loaded by the boot ROM. There may be ways to avoid this
* issue if it occurs in practice.
* The magic value here is _stack_sentry in the boot ROM ELF file.
*/
ASSERT(_edata < 0x3ffe1320, "the .data section overlaps with the stack used by the boot ROM, possibly causing corruption at startup")
/* Global variables that are mutable and zero-initialized.
* These must be zeroed at startup (unlike data, which is loaded by the
* bootloader).
*/
.bss (NOLOAD) : ALIGN(4)
{
. = ALIGN (4);
_sbss = ABSOLUTE(.);
*(.bss)
*(.bss.*)
. = ALIGN (4);
_ebss = ABSOLUTE(.);
} >DRAM
}
/* For the garbage collector.
*/
_globals_start = _sdata;
_globals_end = _ebss;
_heap_start = _ebss;
_heap_end = ORIGIN(DRAM) + LENGTH(DRAM);
_stack_size = 4K;
/* From ESP-IDF:
* components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld
* This is the subset that is sometimes used by LLVM during codegen, and thus
* must always be present.
*/
memset = 0x400011e8;
memcpy = 0x400011f4;
memmove = 0x40001200;
memcmp = 0x4000120c;
/* From ESP-IDF:
* components/esp_rom/esp32/ld/esp32.rom.libgcc.ld
* These are called from LLVM during codegen. The original license is Apache
* 2.0, but I believe that a list of function names and addresses can't really
* be copyrighted.
*/
__absvdi2 = 0x4000216c;
__absvsi2 = 0x40002178;
__adddf3 = 0x40002184;
__addsf3 = 0x40002190;
__addvdi3 = 0x4000219c;
__addvsi3 = 0x400021a8;
__ashldi3 = 0x400021b4;
__ashrdi3 = 0x400021c0;
__bswapdi2 = 0x400021cc;
__bswapsi2 = 0x400021d8;
__clear_cache = 0x400021e4;
__clrsbdi2 = 0x400021f0;
__clrsbsi2 = 0x400021fc;
__clzdi2 = 0x40002208;
__clzsi2 = 0x40002214;
__cmpdi2 = 0x40002220;
__ctzdi2 = 0x4000222c;
__ctzsi2 = 0x40002238;
__divdc3 = 0x40002244;
__divdf3 = 0x40002250;
__divdi3 = 0x4000225c;
__divsc3 = 0x40002268;
__divsf3 = 0x40002274;
__divsi3 = 0x40002280;
__eqdf2 = 0x4000228c;
__eqsf2 = 0x40002298;
__extendsfdf2 = 0x400022a4;
__ffsdi2 = 0x400022b0;
__ffssi2 = 0x400022bc;
__fixdfdi = 0x400022c8;
__fixdfsi = 0x400022d4;
__fixsfdi = 0x400022e0;
__fixsfsi = 0x400022ec;
__fixunsdfsi = 0x400022f8;
__fixunssfdi = 0x40002304;
__fixunssfsi = 0x40002310;
__floatdidf = 0x4000231c;
__floatdisf = 0x40002328;
__floatsidf = 0x40002334;
__floatsisf = 0x40002340;
__floatundidf = 0x4000234c;
__floatundisf = 0x40002358;
__floatunsidf = 0x40002364;
__floatunsisf = 0x40002370;
__gcc_bcmp = 0x4000237c;
__gedf2 = 0x40002388;
__gesf2 = 0x40002394;
__gtdf2 = 0x400023a0;
__gtsf2 = 0x400023ac;
__ledf2 = 0x400023b8;
__lesf2 = 0x400023c4;
__lshrdi3 = 0x400023d0;
__ltdf2 = 0x400023dc;
__ltsf2 = 0x400023e8;
__moddi3 = 0x400023f4;
__modsi3 = 0x40002400;
__muldc3 = 0x4000240c;
__muldf3 = 0x40002418;
__muldi3 = 0x40002424;
__mulsc3 = 0x40002430;
__mulsf3 = 0x4000243c;
__mulsi3 = 0x40002448;
__mulvdi3 = 0x40002454;
__mulvsi3 = 0x40002460;
__nedf2 = 0x4000246c;
__negdf2 = 0x40002478;
__negdi2 = 0x40002484;
__negsf2 = 0x40002490;
__negvdi2 = 0x4000249c;
__negvsi2 = 0x400024a8;
__nesf2 = 0x400024b4;
__paritysi2 = 0x400024c0;
__popcountdi2 = 0x400024cc;
__popcountsi2 = 0x400024d8;
__powidf2 = 0x400024e4;
__powisf2 = 0x400024f0;
__subdf3 = 0x400024fc;
__subsf3 = 0x40002508;
__subvdi3 = 0x40002514;
__subvsi3 = 0x40002520;
__truncdfsf2 = 0x4000252c;
__ucmpdi2 = 0x40002538;
__udivdi3 = 0x40002544;
__udivmoddi4 = 0x40002550;
__udivsi3 = 0x4000255c;
__udiv_w_sdiv = 0x40002568;
__umoddi3 = 0x40002574;
__umodsi3 = 0x40002580;
__unorddf2 = 0x4000258c;
__unordsf2 = 0x40002598;
+11
View File
@@ -0,0 +1,11 @@
{
"inherits": [
"rp2350b"
],
"build-tags": ["pico2_ice"],
"serial-port": ["2e8a:000A"],
"default-stack-size": 8192,
"ldflags": [
"--defsym=__flash_size=4M"
]
}

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