mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-03 02:27:48 +00:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 80c89809a9 | |||
| 266d70816f | |||
| 27cedf09e8 | |||
| db9f1182f5 | |||
| f7fd5cf2d8 | |||
| 8d9c9a16e3 | |||
| 82deccac40 | |||
| 0e540dc3da | |||
| 897f2bc5fd | |||
| 750b0e5c18 | |||
| a65ac105cc | |||
| 6970c80d34 | |||
| dfbc42f833 | |||
| e356daa229 | |||
| 1c27ecb66f | |||
| d01d0bb358 | |||
| 8fcf3658b1 | |||
| b37535bcfb | |||
| ee0a10ec51 | |||
| 39e0333663 | |||
| 3afd2c3597 | |||
| bd19f3baa3 | |||
| 908fa84d49 | |||
| 571f34e0e6 | |||
| 01723025d7 | |||
| 505b5ee565 | |||
| 26ac03a3f6 | |||
| c9aa88b8ef | |||
| 887ac286ff | |||
| b1083d78c7 | |||
| 7863bdb484 | |||
| 5c3363bf25 | |||
| 9404bb8712 | |||
| 20e22d4507 | |||
| 9118e91b72 | |||
| fbbaa5e580 | |||
| 97a2cb4a37 | |||
| 6dee98c3a1 | |||
| 484ccf830a | |||
| 5690204224 | |||
| b203314c2f | |||
| 5ae8fd1f6f | |||
| c80da7eb0d | |||
| 326ca4202e | |||
| 852dde671e | |||
| bf613175e7 | |||
| 4d62cca267 | |||
| a63aa143bb | |||
| ed0fb774b9 | |||
| 9be956f4bc | |||
| 48d741f68a | |||
| 35e6b8a67d | |||
| 8a31ae14a0 | |||
| 26ee3fb93e | |||
| 8872d5ebfc | |||
| faf455283d | |||
| 562b4c5020 | |||
| 7460734522 | |||
| 3be7100e89 |
@@ -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 }}
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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: .
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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¶mIsReadonly != 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.
|
||||||
|
|||||||
@@ -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
@@ -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" }
|
||||||
|
|||||||
Vendored
+20
@@ -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++ {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+1
-1
@@ -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
|
||||||
|
|||||||
Vendored
+3
-3
@@ -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
|
||||||
|
|||||||
Vendored
+3
-3
@@ -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
|
||||||
|
|||||||
Vendored
+8
-8
@@ -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
@@ -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).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,4 +1,4 @@
|
|||||||
//go:build scheduler.tasks && esp32
|
//go:build scheduler.tasks && (esp32 || esp32s3)
|
||||||
|
|
||||||
package task
|
package task
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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, 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, 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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ package machine
|
|||||||
var Serial Serialer
|
var Serial Serialer
|
||||||
|
|
||||||
func InitSerial() {
|
func InitSerial() {
|
||||||
|
initUSB()
|
||||||
Serial = USBCDC
|
Serial = USBCDC
|
||||||
}
|
}
|
||||||
|
|||||||
+83
-37
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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" {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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
Reference in New Issue
Block a user