Compare commits

..

1 Commits

Author SHA1 Message Date
Ayke van Laethem 91b0bbaae6 testing: switch to upstream version 2025-10-05 15:33:49 +02:00
103 changed files with 911 additions and 5144 deletions
+7 -8
View File
@@ -16,15 +16,14 @@ jobs:
name: build-macos
strategy:
matrix:
# macos-14: arm64 (oldest supported version as of 18-11-2025)
# macos-15-intel: amd64 (last intel version to be supported by github runners)
# See https://github.com/actions/runner-images/issues/13046
os: [macos-14, macos-15-intel]
# macos-13: amd64 (oldest supported version as of 18-10-2024)
# macos-14: arm64 (oldest arm64 version)
os: [macos-13, macos-14]
include:
- os: macos-13
goarch: amd64
- os: macos-14
goarch: arm64
- os: macos-15-intel
goarch: amd64
runs-on: ${{ matrix.os }}
steps:
- name: Install Dependencies
@@ -40,7 +39,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.25.5'
go-version: '1.25.1'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v4
@@ -135,7 +134,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.25.5'
go-version: '1.25.1'
cache: true
- name: Build TinyGo (LLVM ${{ matrix.version }})
run: go install -tags=llvm${{ matrix.version }}
+2 -2
View File
@@ -31,7 +31,7 @@ jobs:
sudo rm -rf /usr/local/share/boost
df -h
- name: Check out the repo
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Docker Buildx
@@ -58,7 +58,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
with:
context: .
push: true
+10 -10
View File
@@ -31,7 +31,7 @@ jobs:
# We're not on a multi-user machine, so this is safe.
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
submodules: true
- name: Extract TinyGo version
@@ -131,13 +131,13 @@ jobs:
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: '1.25.5'
go-version: '1.25.0'
cache: true
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
@@ -164,7 +164,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
submodules: true
- name: Install apt dependencies
@@ -179,9 +179,9 @@ jobs:
simavr \
ninja-build
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: '1.25.5'
go-version: '1.25.0'
cache: true
- name: Install Node.js
uses: actions/setup-node@v4
@@ -284,7 +284,7 @@ jobs:
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Get TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
@@ -296,9 +296,9 @@ jobs:
g++-${{ matrix.toolchain }} \
libc6-dev-${{ matrix.libc }}-cross
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: '1.25.5'
go-version: '1.25.0'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v4
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Docker Buildx
@@ -52,7 +52,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
with:
target: tinygo-llvm-build
context: .
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
# See: https://github.com/tinygo-org/tinygo/pull/4516#issuecomment-2416363668
run: sudo apt-get remove llvm-18
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Pull musl, bdwgc
run: |
git submodule update --init lib/musl lib/bdwgc
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
run: |
echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
fetch-depth: 0 # fetch all history (no sparse checkout)
submodules: true
+12 -12
View File
@@ -31,7 +31,7 @@ jobs:
run: |
scoop install ninja binaryen
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
with:
submodules: true
- name: Extract TinyGo version
@@ -39,9 +39,9 @@ jobs:
shell: bash
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: '1.25.5'
go-version: '1.25.0'
cache: true
- name: Restore cached LLVM source
uses: actions/cache/restore@v4
@@ -143,11 +143,11 @@ jobs:
run: |
scoop install binaryen
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: '1.25.5'
go-version: '1.25.0'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
@@ -173,11 +173,11 @@ jobs:
maximum-size: 24GB
disk-root: "C:"
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: '1.25.5'
go-version: '1.25.0'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
@@ -209,11 +209,11 @@ jobs:
run: |
scoop install binaryen && scoop install wasmtime@29.0.1
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v5
with:
go-version: '1.25.5'
go-version: '1.25.0'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
-89
View File
@@ -1,92 +1,3 @@
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
---
* **general**
+2 -8
View File
@@ -363,7 +363,6 @@ TEST_PACKAGES_FAST = \
path \
reflect \
sync \
testing \
testing/iotest \
text/scanner \
unicode \
@@ -480,8 +479,7 @@ TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS)
TEST_IOFS := false
endif
TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic'
TEST_ADDITIONAL_FLAGS ?=
TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic|^Fuzz'
# Test known-working standard library packages.
# TODO: parallelize, and only show failing tests (no implied -v flag).
@@ -489,7 +487,7 @@ TEST_ADDITIONAL_FLAGS ?=
tinygo-test:
@# 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
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
$(TINYGO) test $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
@# 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.
@# For more details, see the comments on issue #3143.
@@ -622,8 +620,6 @@ smoketest: testchdir
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/device-id
@$(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
ifneq ($(WASM), 0)
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1
@@ -915,8 +911,6 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1
@$(MD5SUM) test.bin
endif
$(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1
@$(MD5SUM) test.bin
+1 -1
View File
@@ -1042,7 +1042,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if err != nil {
return result, err
}
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266":
case "esp32", "esp32-img", "esp32c3", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
result.Binary = filepath.Join(tmpdir, "main"+outext)
-1
View File
@@ -28,7 +28,6 @@ func TestClangAttributes(t *testing.T) {
"cortex-m4",
"cortex-m7",
"esp32c3",
"esp32s3",
"fe310",
"gameboy-advance",
"k210",
+4 -7
View File
@@ -33,13 +33,10 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if err != nil {
return nil, err
}
if options.GoCompatibility {
if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax {
// 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)
}
if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax {
// 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
+1 -2
View File
@@ -100,12 +100,11 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
chip_id := map[string]uint16{
"esp32": 0x0000,
"esp32c3": 0x0005,
"esp32s3": 0x0009,
}[chip]
// Image header.
switch chip {
case "esp32", "esp32c3", "esp32s3":
case "esp32", "esp32c3":
// Header format:
// 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
+3 -3
View File
@@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 3668, 280, 0, 2244},
{"microbit", "examples/serial", 2694, 342, 8, 2248},
{"wioterminal", "examples/pininterrupt", 7187, 1489, 116, 6888},
{"hifive1b", "examples/echo", 3884, 280, 0, 2268},
{"microbit", "examples/serial", 2852, 360, 8, 2272},
{"wioterminal", "examples/pininterrupt", 7337, 1491, 116, 6912},
// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
+1 -1
View File
@@ -226,7 +226,7 @@ func (c *Config) StackSize() uint64 {
// MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap.
func (c *Config) MaxStackAlloc() uint64 {
if c.StackSize() >= 16*1024 {
if c.StackSize() > 32*1024 {
return 1024
}
-1
View File
@@ -59,7 +59,6 @@ type Options struct {
WITPackage string // pass through to wasm-tools component embed invocation
WITWorld string // pass through to wasm-tools component embed -w option
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.
+1 -1
View File
@@ -245,7 +245,7 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
// current insert position.
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.insertBasicBlock(blockPrefix + ".next")
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block.
b.CreateCondBr(assert, faultBlock, nextBlock)
-8
View File
@@ -32,9 +32,6 @@ const (
// Whether this is a full or partial Go parameter (int, slice, etc).
// The extra context parameter is not a Go parameter.
paramIsGoParam = 1 << iota
// Whether this is a readonly parameter (for example, a string pointer).
paramIsReadonly
)
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
@@ -170,7 +167,6 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
continue
}
suffix := strconv.Itoa(i)
isString := false
if goType != nil {
// Try to come up with a good suffix for this struct field,
// depending on which Go type it's based on.
@@ -187,16 +183,12 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
suffix = []string{"r", "i"}[i]
case types.String:
suffix = []string{"data", "len"}[i]
isString = true
}
case *types.Signature:
suffix = []string{"context", "funcptr"}[i]
}
}
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
if isString {
subInfos[0].flags |= paramIsReadonly
}
paramInfos = append(paramInfos, subInfos...)
}
return paramInfos
+13 -42
View File
@@ -152,12 +152,10 @@ type builder struct {
llvmFnType llvm.Type
llvmFn llvm.Value
info functionInfo
locals map[ssa.Value]llvm.Value // local variables
blockInfo []blockInfo
locals map[ssa.Value]llvm.Value // local variables
blockEntries map[*ssa.BasicBlock]llvm.BasicBlock // a *ssa.BasicBlock may be split up
blockExits map[*ssa.BasicBlock]llvm.BasicBlock // these are the exit blocks
currentBlock *ssa.BasicBlock
currentBlockInfo *blockInfo
tarjanStack []uint
tarjanIndex uint
phis []phiNode
deferPtr llvm.Value
deferFrame llvm.Value
@@ -189,22 +187,11 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *bu
info: c.getFunctionInfo(f),
locals: make(map[ssa.Value]llvm.Value),
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 {
callName string
pos token.Pos
@@ -1233,29 +1220,14 @@ func (b *builder) createFunctionStart(intrinsic bool) {
// intrinsic (like an atomic operation). Create the entry block
// manually.
entryBlock = b.ctx.AddBasicBlock(b.llvmFn, "entry")
// Intrinsics may create internal branches (e.g. nil checks).
// They will attempt to access b.currentBlockInfo to update the exit block.
// Create some fake block info for them to access.
blockInfo := []blockInfo{
{
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.blockEntries[block] = llvmBlock
b.blockExits[block] = llvmBlock
}
b.blockInfo = blockInfo
// Normal functions have an entry block.
entryBlock = blockInfo[0].entry
entryBlock = b.blockEntries[b.fn.Blocks[0]]
}
b.SetInsertPointAtEnd(entryBlock)
@@ -1351,9 +1323,8 @@ func (b *builder) createFunction() {
if b.DumpSSA {
fmt.Printf("%d: %s:\n", block.Index, block.Comment)
}
b.SetInsertPointAtEnd(b.blockEntries[block])
b.currentBlock = block
b.currentBlockInfo = &b.blockInfo[block.Index]
b.SetInsertPointAtEnd(b.currentBlockInfo.entry)
for _, instr := range block.Instrs {
if instr, ok := instr.(*ssa.DebugRef); ok {
if !b.Debug {
@@ -1413,7 +1384,7 @@ func (b *builder) createFunction() {
block := phi.ssa.Block()
for i, edge := range phi.ssa.Edges {
llvmVal := b.getValue(edge, getPos(phi.ssa))
llvmBlock := b.blockInfo[block.Preds[i].Index].exit
llvmBlock := b.blockExits[block.Preds[i]]
phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock})
}
}
@@ -1527,11 +1498,11 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
case *ssa.If:
cond := b.getValue(instr.Cond, getPos(instr))
block := instr.Block()
blockThen := b.blockInfo[block.Succs[0].Index].entry
blockElse := b.blockInfo[block.Succs[1].Index].entry
blockThen := b.blockEntries[block.Succs[0]]
blockElse := b.blockEntries[block.Succs[1]]
b.CreateCondBr(cond, blockThen, blockElse)
case *ssa.Jump:
blockJump := b.blockInfo[instr.Block().Succs[0].Index].entry
blockJump := b.blockEntries[instr.Block().Succs[0]]
b.CreateBr(blockJump)
case *ssa.MapUpdate:
m := b.getValue(instr.Map, getPos(instr))
+29 -99
View File
@@ -100,7 +100,7 @@ func (b *builder) createLandingPad() {
// Continue at the 'recover' block, which returns to the parent in an
// appropriate way.
b.CreateBr(b.blockInfo[b.fn.Recover.Index].entry)
b.CreateBr(b.blockEntries[b.fn.Recover])
}
// Create a checkpoint (similar to setjmp). This emits inline assembly that
@@ -234,108 +234,41 @@ func (b *builder) createInvokeCheckpoint() {
continueBB := b.insertBasicBlock("")
b.CreateCondBr(isZero, continueBB, b.landingpad)
b.SetInsertPointAtEnd(continueBB)
b.currentBlockInfo.exit = continueBB
b.blockExits[b.currentBlock] = continueBB
}
// isInLoop checks if there is a path from the current block to itself.
// Use Tarjan's strongly connected components algorithm to search for cycles.
// A one-node SCC is a cycle iff there is an edge from the node to itself.
// A multi-node SCC is always a cycle.
// 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
}
// isInLoop checks if there is a path from a basic block to itself.
func isInLoop(start *ssa.BasicBlock) bool {
// Use a breadth-first search to scan backwards through the block graph.
queue := []*ssa.BasicBlock{start}
checked := map[*ssa.BasicBlock]struct{}{}
func (b *builder) strongConnect(block *ssa.BasicBlock) {
// Assign a new index.
// Indices start from 1 so that 0 can be used as a sentinel.
assignedIndex := b.tarjanIndex + 1
b.tarjanIndex = assignedIndex
for len(queue) > 0 {
// pop a block off of the queue
block := queue[len(queue)-1]
queue = queue[:len(queue)-1]
// Apply the new index.
blockIndex := block.Index
node := &b.blockInfo[blockIndex].tarjan
node.lowLink = assignedIndex
// Push the node onto the stack.
node.onStack = true
b.tarjanStack = append(b.tarjanStack, uint(blockIndex))
// Process the successors.
for _, successor := range block.Succs {
// 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
}
}
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
// Search through predecessors.
// Searching backwards means that this is pretty fast when the block is close to the start of the function.
// Defers are often placed near the start of the function.
for _, pred := range block.Preds {
if pred == start {
// cycle found
return true
}
if _, ok := checked[pred]; ok {
// block already checked
continue
}
// add to queue and checked map
queue = append(queue, pred)
checked[pred] = struct{}{}
}
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
return false
}
// createDefer emits a single defer instruction, to be run when this function
@@ -477,10 +410,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Put this struct in an allocation.
var alloca llvm.Value
if instr.Block() != b.currentBlock {
panic("block mismatch")
}
if !b.isInLoop() {
if !isInLoop(instr.Block()) {
// This can safely use a stack allocation.
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferredCallType, "defer.alloca")
} else {
+1 -1
View File
@@ -737,7 +737,7 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
prevBlock := b.GetInsertBlock()
okBlock := b.insertBasicBlock("typeassert.ok")
nextBlock := b.insertBasicBlock("typeassert.next")
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was
-16
View File
@@ -27,8 +27,6 @@ func (b *builder) defineIntrinsicFunction() {
b.createStackSaveImpl()
case name == "runtime.KeepAlive":
b.createKeepAliveImpl()
case name == "machine.keepAliveNoEscape":
b.createMachineKeepAliveImpl()
case strings.HasPrefix(name, "runtime/volatile.Load"):
b.createVolatileLoad()
case strings.HasPrefix(name, "runtime/volatile.Store"):
@@ -146,20 +144,6 @@ func (b *builder) createAbiEscapeImpl() {
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{
"math.Ceil": "llvm.ceil.f64",
"math.Exp": "llvm.exp.f64",
-3
View File
@@ -250,9 +250,6 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
func hashmapIsBinaryKey(keyType types.Type) bool {
switch keyType := keyType.Underlying().(type) {
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
case *types.Pointer:
return true
-7
View File
@@ -142,11 +142,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)
llvmFn.AddAttributeAtIndex(i+1, nocapture)
}
if paramInfo.flags&paramIsReadonly != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
// Readonly pointer parameters (like strings) benefit from being marked as readonly.
readonly := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)
llvmFn.AddAttributeAtIndex(i+1, readonly)
}
}
// Set a number of function or parameter attributes, depending on the
@@ -159,8 +154,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
case "internal/abi.NoEscape":
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":
// 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.
-8
View File
@@ -74,14 +74,6 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
return b.CreateCall(fnType, target, args, ""), nil
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.
// Source: syscall(2) man page.
args := []llvm.Value{}
-235
View File
@@ -270,241 +270,6 @@ entry:
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 #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" }
-20
View File
@@ -18,23 +18,3 @@ func deferMultiple() {
}()
external()
}
func deferInfiniteLoop() {
for {
defer print(8)
}
}
func deferLoop() {
for i := 0; i < 10; i++ {
defer print(i)
}
}
func deferBetweenLoops() {
for i := 0; i < 10; i++ {
}
defer print(1)
for i := 0; i < 10; i++ {
}
}
+1 -1
View File
@@ -50,7 +50,7 @@ unsafe.String.throw: ; preds = %entry
declare void @runtime.unsafeSlicePanic(ptr) #1
; Function Attrs: nounwind
define hidden ptr @main.unsafeStringData(ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #2 {
define hidden ptr @main.unsafeStringData(ptr %s.data, i32 %s.len, ptr %context) unnamed_addr #2 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr %s.data, ptr nonnull %stackalloc, ptr undef) #3
+3 -3
View File
@@ -77,7 +77,7 @@ entry:
}
; Function Attrs: nounwind
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 {
define hidden %runtime._string @main.minString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 {
entry:
%0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0
%1 = insertvalue %runtime._string %0, i32 %a.len, 1
@@ -91,7 +91,7 @@ entry:
ret %runtime._string %5
}
declare i1 @runtime.stringLess(ptr readonly, i32, ptr readonly, i32, ptr) #1
declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) #1
; Function Attrs: nounwind
define hidden i32 @main.maxInt(i32 %a, i32 %b, ptr %context) unnamed_addr #2 {
@@ -116,7 +116,7 @@ entry:
}
; Function Attrs: nounwind
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 {
define hidden %runtime._string @main.maxString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 {
entry:
%0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0
%1 = insertvalue %runtime._string %0, i32 %a.len, 1
+8 -8
View File
@@ -31,13 +31,13 @@ entry:
}
; Function Attrs: nounwind
define hidden i32 @main.stringLen(ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #2 {
define hidden i32 @main.stringLen(ptr %s.data, i32 %s.len, ptr %context) unnamed_addr #2 {
entry:
ret i32 %s.len
}
; Function Attrs: nounwind
define hidden i8 @main.stringIndex(ptr readonly %s.data, i32 %s.len, i32 %index, ptr %context) unnamed_addr #2 {
define hidden i8 @main.stringIndex(ptr %s.data, i32 %s.len, i32 %index, ptr %context) unnamed_addr #2 {
entry:
%.not = icmp ult i32 %index, %s.len
br i1 %.not, label %lookup.next, label %lookup.throw
@@ -55,16 +55,16 @@ lookup.throw: ; preds = %entry
declare void @runtime.lookupPanic(ptr) #1
; Function Attrs: nounwind
define hidden i1 @main.stringCompareEqual(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 {
define hidden i1 @main.stringCompareEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 {
entry:
%0 = call i1 @runtime.stringEqual(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr undef) #3
ret i1 %0
}
declare i1 @runtime.stringEqual(ptr readonly, i32, ptr readonly, i32, ptr) #1
declare i1 @runtime.stringEqual(ptr, i32, ptr, i32, ptr) #1
; Function Attrs: nounwind
define hidden i1 @main.stringCompareUnequal(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 {
define hidden i1 @main.stringCompareUnequal(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 {
entry:
%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
@@ -72,16 +72,16 @@ entry:
}
; Function Attrs: nounwind
define hidden i1 @main.stringCompareLarger(ptr readonly %s1.data, i32 %s1.len, ptr readonly %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 {
define hidden i1 @main.stringCompareLarger(ptr %s1.data, i32 %s1.len, ptr %s2.data, i32 %s2.len, ptr %context) unnamed_addr #2 {
entry:
%0 = call i1 @runtime.stringLess(ptr %s2.data, i32 %s2.len, ptr %s1.data, i32 %s1.len, ptr undef) #3
ret i1 %0
}
declare i1 @runtime.stringLess(ptr readonly, i32, ptr readonly, i32, ptr) #1
declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) #1
; Function Attrs: nounwind
define hidden i8 @main.stringLookup(ptr readonly %s.data, i32 %s.len, i8 %x, ptr %context) unnamed_addr #2 {
define hidden i8 @main.stringLookup(ptr %s.data, i32 %s.len, i8 %x, ptr %context) unnamed_addr #2 {
entry:
%0 = zext i8 %x to i32
%.not = icmp ugt i32 %s.len, %0
+1 -1
View File
@@ -10,7 +10,7 @@ import (
// Version of TinyGo.
// Update this value before release of new version of software.
const version = "0.40.0"
const version = "0.40.0-dev"
// Return TinyGo version, either in the form 0.30.0 or as a development version
// (like 0.30.0-dev-abcd012).
-1
View File
@@ -256,7 +256,6 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
"reflect/": false,
"runtime/": false,
"sync/": true,
"testing/": true,
"tinygo/": false,
"unique/": false,
}
-12
View File
@@ -1635,7 +1635,6 @@ func main() {
cpuprofile := flag.String("cpuprofile", "", "cpuprofile output")
monitor := flag.Bool("monitor", false, "enable 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.
printIR := flag.Bool("internal-printir", false, "print LLVM IR")
@@ -1713,16 +1712,6 @@ func main() {
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{
GOOS: goenv.Get("GOOS"),
GOARCH: goenv.Get("GOARCH"),
@@ -1759,7 +1748,6 @@ func main() {
Timeout: *timeout,
WITPackage: witPackage,
WITWorld: witWorld,
GoCompatibility: *gocompatibility,
}
if *printCommands {
options.PrintCommands = printCommand
-7
View File
@@ -108,10 +108,6 @@ func (*stackState) unwind()
func (t *Task) Resume() {
// The current task must be saved and restored because this can nest on WASM with JS.
prevTask := currentTask
if prevTask == nil {
// Save the system stack pointer.
saveStackPointer()
}
t.gcData.swap()
currentTask = t
if !t.state.launched {
@@ -127,9 +123,6 @@ func (t *Task) Resume() {
}
}
//go:linkname saveStackPointer runtime.saveStackPointer
func saveStackPointer()
//export tinygo_rewind
func (*state) rewind()
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build scheduler.tasks && (esp32 || esp32s3)
//go:build scheduler.tasks && esp32
package task
-6
View File
@@ -120,21 +120,15 @@ int tinygo_task_start(uintptr_t fn, void *args, void *task, pthread_t *thread, u
#endif
pthread_attr_t attrs;
pthread_attr_init(&attrs);
pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
pthread_attr_setstacksize(&attrs, stackSize);
int result = pthread_create(thread, &attrs, &start_wrapper, &state);
pthread_attr_destroy(&attrs);
if (result != 0) {
return result;
}
// Wait until the thread has been created and read all state_pass variables.
#if __APPLE__
dispatch_semaphore_wait(state.startlock, DISPATCH_TIME_FOREVER);
dispatch_release(state.startlock);
#else
sem_wait(&state.startlock);
sem_destroy(&state.startlock);
#endif
return result;
+65 -101
View File
@@ -23,15 +23,16 @@ type state struct {
// is needed to be able to scan the stack.
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.
QueueNext *Task
// Semaphore to pause/resume the thread atomically.
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.
@@ -95,9 +96,6 @@ func (t *Task) Resume() {
t.state.pauseSem.Post()
}
// otherGoroutines is the total number of live goroutines minus one.
var otherGoroutines uint32
// Start a new OS thread.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
t := &Task{}
@@ -117,7 +115,6 @@ func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
}
t.state.QueueNext = activeTasks
activeTasks = t
otherGoroutines++
activeTaskLock.Unlock()
}
@@ -138,7 +135,6 @@ func taskExited(t *Task) {
break
}
}
otherGoroutines--
activeTaskLock.Unlock()
// Sanity check.
@@ -147,42 +143,9 @@ func taskExited(t *Task) {
}
}
// scanWaitGroup is used to wait on until all threads have finished the current state transition.
var scanWaitGroup waitGroup
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
)
// Futex to wait on until all tasks have finished scanning the stack.
// This is basically a sync.WaitGroup.
var scanDoneFutex Futex
// GC scan phase. Because we need to stop the world while scanning, this kinda
// needs to be done in the tasks package.
@@ -192,71 +155,65 @@ const (
func GCStopWorldAndScan() {
current := Current()
// NOTE: This does not need to be atomic.
if gcState.Load() == gcStateResumed {
// Don't allow new goroutines to be started while pausing/resuming threads
// in the stop-the-world phase.
activeTaskLock.Lock()
// Don't allow new goroutines to be started while pausing/resuming threads
// in the stop-the-world phase.
activeTaskLock.Lock()
// Wait for threads to finish resuming.
scanWaitGroup.wait()
// Change the gc state to stopped.
// NOTE: This does not need to be atomic.
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()
}
// Scan other thread stacks.
// Pause all other threads.
numOtherThreads := uint32(0)
for t := activeTasks; t != nil; t = t.state.QueueNext {
if t != current {
markRoots(t.state.stackBottom, t.state.stackTop)
numOtherThreads++
tinygo_task_send_gc_signal(t.state.thread)
}
}
// Store the number of threads to wait for in the futex.
// This is the equivalent of doing an initial wg.Add(numOtherThreads).
scanDoneFutex.Store(numOtherThreads)
// Scan the current stack, and all current registers.
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).
gcScanGlobals()
}
// After the GC is done scanning, resume all other threads.
//
// This must only be called after a GCStopWorldAndScan call.
func GCResumeWorld() {
// NOTE: This does not need to be atomic.
if gcState.Load() == gcStateResumed {
// This is already resumed.
return
current := Current()
// Wake each paused thread for the second time, so they will resume normal
// operation.
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.
activeTaskLock.Unlock()
}
//go:linkname markRoots runtime.markRoots
func markRoots(start, end uintptr)
// Scan globals, implemented in the runtime package.
func gcScanGlobals()
@@ -264,27 +221,34 @@ var stackScanLock PMutex
//export tinygo_task_gc_pause
func tingyo_task_gc_pause(sig int32) {
// Write the entrty stack pointer to the state.
Current().state.stackBottom = uintptr(stacksave())
// Wait until we get the signal to start scanning the stack.
Current().state.gcSem.Wait()
// Notify the GC that we are stopped.
scanWaitGroup.done()
// Scan the thread stack.
// Only scan a single thread stack at a time, because the GC marking phase
// 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()
// Wait for the GC to resume.
for gcState.Load() == gcStateStopped {
gcState.Wait(gcStateStopped)
// Equivalent of wg.Done(): subtract one from the futex and if the result is
// 0 (meaning we were the last in the waitgroup), wake the waiting thread.
n := uint32(1)
if scanDoneFutex.Add(-n) == 0 {
scanDoneFutex.Wake()
}
// Notify the GC that we have resumed.
scanWaitGroup.done()
// Wait until we get the signal we can resume normally (after the mark phase
// has finished).
Current().state.gcSem.Wait()
}
//go:export tinygo_scanCurrentStack
func scanCurrentStack()
//go:linkname stacksave runtime.stacksave
func stacksave() unsafe.Pointer
// Return the highest address of the current stack.
func StackTop() uintptr {
return Current().state.stackTop
-173
View File
@@ -1,173 +0,0 @@
//go:build pico2_ice
// Most of the info is from
// https://pico2-ice.tinyvision.ai/md_pinout.html although
// (2025-09-07) RP4 appears twice in that pinout - the schematic is
// more clear. Consistent with other RPi boards, we use GPn instead of
// RPn to reference the RPi connected pins.
package machine
// GPIO pins
const (
GP0 = GPIO0
GP1 = GPIO1
GP2 = GPIO2
GP3 = GPIO3
GP4 = GPIO4
GP5 = GPIO5
GP6 = GPIO6
GP7 = GPIO7
GP8 = GPIO8
GP9 = GPIO9
GP10 = GPIO10
GP11 = GPIO11
GP12 = GPIO12
GP13 = GPIO13
GP14 = GPIO14
GP15 = GPIO15
GP16 = GPIO16
GP17 = GPIO17
GP18 = GPIO18
GP19 = GPIO19
GP20 = GPIO20
GP21 = GPIO21
GP22 = GPIO22
GP23 = GPIO23
GP24 = GPIO24
GP25 = GPIO25
GP26 = GPIO26
GP27 = GPIO27
GP28 = GPIO28
GP29 = GPIO29
GP30 = GPIO30
GP31 = GPIO31
GP32 = GPIO32
GP33 = GPIO33
GP34 = GPIO34
GP35 = GPIO35
GP36 = GPIO36
GP37 = GPIO37
GP38 = GPIO38
GP39 = GPIO39
GP40 = GPIO40
GP41 = GPIO41
GP42 = GPIO42
GP43 = GPIO43
GP44 = GPIO44
GP45 = GPIO45
GP46 = GPIO46
GP47 = GPIO47
// RPi pins shared with ICE. The ICE number is what appears on
// the board silkscreen.
ICE9 = GP28
ICE11 = GP29
ICE14 = GP7
ICE15 = GP6
ICE16 = GP5
ICE17 = GP4
ICE18 = GP27
ICE19 = GP23
ICE20 = GP22
ICE21 = GP26
ICE23 = GP25
ICE25 = GP30
ICE26 = GP24
ICE27 = GP20
// FPGA Clock pin.
ICE35_G0 = GP21
// Silkscreen & Pinout names
ICE_SSN = ICE16
ICE_SO = ICE14
ICE_SI = ICE17
ICE_CK = ICE15
SD = GP2
SC = GP3
FPGA_RSTN = GP31
A3 = GP32
A1 = GP33
A4 = GP34
A2 = GP35
B3 = GP36
B1 = GP37
B4 = GP38
B2 = GP39
N0 = GP40 // On the board these are labeled "~0"
N1 = GP41
N2 = GP42
N3 = GP43
N4 = GP44
N5 = GP45
N6 = GP46
// Functions from Schematic.
ICE_DONE = GP40
USB_BOOT = GP42
// Button
SW1 = GP42
BOOTSEL = GP42
// Tricolor LEDs
LED_RED = GP1
LED_GREEN = GP0
LED_BLUE = GP9
// Onboard LED
LED = LED_GREEN
// Onboard crystal oscillator frequency, in MHz.
xoscFreq = 12 // MHz
)
// This board does not define default i2c pins.
const (
I2C0_SDA_PIN = NoPin
I2C0_SCL_PIN = NoPin
I2C1_SDA_PIN = NoPin
I2C1_SCL_PIN = NoPin
)
// SPI default pins
const (
// Default Serial Clock Bus 0 for SPI communications
SPI0_SCK_PIN = GPIO18
// Default Serial Out Bus 0 for SPI communications
SPI0_SDO_PIN = GPIO19 // Tx
// Default Serial In Bus 0 for SPI communications
SPI0_SDI_PIN = GPIO16 // Rx
// Default Serial Clock Bus 1 for SPI communications
SPI1_SCK_PIN = GPIO10
// Default Serial Out Bus 1 for SPI communications
SPI1_SDO_PIN = GPIO11 // Tx
// Default Serial In Bus 1 for SPI communications
SPI1_SDI_PIN = GPIO12 // Rx
)
// UART pins
const (
UART0_TX_PIN = GPIO0
UART0_RX_PIN = GPIO1
UART1_TX_PIN = GPIO8
UART1_RX_PIN = GPIO9
UART_TX_PIN = UART0_TX_PIN
UART_RX_PIN = UART0_RX_PIN
)
var DefaultUART = UART0
// USB identifiers
const (
usb_STRING_PRODUCT = "Pico2"
usb_STRING_MANUFACTURER = "Raspberry Pi"
)
var (
usb_VID uint16 = 0x2E8A
usb_PID uint16 = 0x000A
)
-58
View File
@@ -1,58 +0,0 @@
//go:build xiao_esp32s3
// This file contains the pin mappings for the Seeed XIAO ESP32S3 boards.
//
// Seeed Studio XIAO ESP32S3 is an IoT mini development board based on
// the Espressif ESP32-S3 WiFi/Bluetooth dual-mode chip.
//
// - https://www.seeedstudio.com/XIAO-ESP32S3-p-5627.html
// - https://wiki.seeedstudio.com/xiao_esp32s3_getting_started/
package machine
// Digital Pins
const (
D0 = GPIO1
D1 = GPIO2
D2 = GPIO3
D3 = GPIO4
D4 = GPIO5
D5 = GPIO6
D6 = GPIO43
D7 = GPIO44
D8 = GPIO7
D9 = GPIO8
D10 = GPIO9
)
// Analog pins
const (
A0 = GPIO1
A1 = GPIO2
A2 = GPIO3
A3 = GPIO4
)
// UART pins
const (
UART_RX_PIN = GPIO44
UART_TX_PIN = GPIO43
)
// I2C pins
const (
SDA_PIN = GPIO5
SCL_PIN = GPIO6
)
// SPI pins
const (
SPI_SCK_PIN = GPIO7
SPI_SDI_PIN = GPIO9
SPI_SDO_PIN = GPIO8
)
// Onboard LEDs
const (
LED = GPIO21
)
-5
View File
@@ -43,11 +43,6 @@ type BlockDevice interface {
io.ReaderAt
// 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
// Size returns the number of bytes in this block device.
+1 -31
View File
@@ -1,9 +1,6 @@
package machine
import (
"errors"
"unsafe"
)
import "errors"
var (
ErrTimeoutRNG = errors.New("machine: RNG Timeout")
@@ -65,30 +62,3 @@ func (p Pin) Low() {
type ADC struct {
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)
-4
View File
@@ -519,10 +519,6 @@ type Serialer interface {
RTS() bool
}
func initUSB() {
// nothing to do here
}
// USB Serial/JTAG Controller
// See esp32-c3_technical_reference_manual_en.pdf
// pg. 736
-312
View File
@@ -1,312 +0,0 @@
//go:build esp32s3
package machine
import (
"device/esp"
"errors"
"runtime/volatile"
"unsafe"
)
const deviceName = esp.Device
const xtalClock = 40_000000 // 40MHz
const apbClock = 80_000000 // 80MHz
const cryptoPWMClock = 160_000000 // 160MHz
// GetCPUFrequency returns the current CPU frequency of the chip.
func GetCPUFrequency() (uint32, error) {
switch esp.SYSTEM.GetSYSCLK_CONF_SOC_CLK_SEL() {
case 0:
return xtalClock / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil
case 1:
switch esp.SYSTEM.GetCPU_PER_CONF_CPUPERIOD_SEL() {
case 0:
return 80e6, nil
case 1:
return 160e6, nil
case 2:
// If esp.SYSTEM.GetCPU_PER_CONF_PLL_FREQ_SEL() == 1, this is undefined
return 240e6, nil
}
case 2:
//RC Fast Clock
return (175e5) / (esp.SYSTEM.GetSYSCLK_CONF_PRE_DIV_CNT() + 1), nil
}
return 0, errors.New("machine: Unable to determine current cpu frequency")
}
// SetCPUFrequency sets the frequency of the CPU to one of several targets
func SetCPUFrequency(frequency uint32) error {
// Always assume we are on PLL. Lower frequencies can be set with a different
// clock source, but this will change the behavior of APB clock and Crypto PWM
// clock
//esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1)
switch frequency {
case 80_000000:
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(0)
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0) // Reduce PLL freq when possible
return nil
case 160_000000:
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(1)
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(0)
return nil
case 240_000000:
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1) // Increase PLL freq when needed
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2)
return nil
}
return errors.New("machine: Unsupported CPU frequency selected. Supported: 80, 160, 240 MHz")
}
var (
ErrInvalidSPIBus = errors.New("machine: invalid SPI bus")
)
const (
PinOutput PinMode = iota
PinInput
PinInputPullup
PinInputPulldown
)
// Hardware pin numbers
const (
GPIO0 Pin = 0
GPIO1 Pin = 1
GPIO2 Pin = 2
GPIO3 Pin = 3
GPIO4 Pin = 4
GPIO5 Pin = 5
GPIO6 Pin = 6
GPIO7 Pin = 7
GPIO8 Pin = 8
GPIO9 Pin = 9
GPIO10 Pin = 10
GPIO11 Pin = 11
GPIO12 Pin = 12
GPIO13 Pin = 13
GPIO14 Pin = 14
GPIO15 Pin = 15
GPIO16 Pin = 16
GPIO17 Pin = 17
GPIO18 Pin = 18
GPIO19 Pin = 19
GPIO20 Pin = 20
GPIO21 Pin = 21
GPIO26 Pin = 26
GPIO27 Pin = 27
GPIO28 Pin = 28
GPIO29 Pin = 29
GPIO30 Pin = 30
GPIO31 Pin = 31
GPIO32 Pin = 32
GPIO33 Pin = 33
GPIO34 Pin = 34
GPIO35 Pin = 35
GPIO36 Pin = 36
GPIO37 Pin = 37
GPIO38 Pin = 38
GPIO39 Pin = 39
GPIO40 Pin = 40
GPIO41 Pin = 41
GPIO42 Pin = 42
GPIO43 Pin = 43
GPIO44 Pin = 44
GPIO45 Pin = 45
GPIO46 Pin = 46
GPIO47 Pin = 47
GPIO48 Pin = 48
)
// Configure this pin with the given configuration.
func (p Pin) Configure(config PinConfig) {
// Output function 256 is a special value reserved for use as a regular GPIO
// pin. Peripherals (SPI etc) can set a custom output function by calling
// lowercase configure() instead with a signal name.
p.configure(config, 256)
}
// configure is the same as Configure, but allows for setting a specific input
// or output signal.
// Signals are always routed through the GPIO matrix for simplicity. Output
// signals are configured in FUNCx_OUT_SEL_CFG which selects a particular signal
// to output on a given pin. Input signals are configured in FUNCy_IN_SEL_CFG,
// which sets the pin to use for a particular input signal.
func (p Pin) configure(config PinConfig, signal uint32) {
if p == NoPin {
// This simplifies pin configuration in peripherals such as SPI.
return
}
ioConfig := uint32(0)
// MCU_SEL: Function 1 is always GPIO
ioConfig |= (1 << esp.IO_MUX_GPIO_MCU_SEL_Pos)
// FUN_IE: Make this pin an input pin (always set for GPIO operation)
ioConfig |= esp.IO_MUX_GPIO_FUN_IE
// DRV: Set drive strength to 20 mA as a default. Pins 17 and 18 are special
var drive uint32
if p == GPIO17 || p == GPIO18 {
drive = 1 // 20 mA
} else {
drive = 2 // 20 mA
}
ioConfig |= (drive << esp.IO_MUX_GPIO_FUN_DRV_Pos)
// WPU/WPD: Select pull mode.
if config.Mode == PinInputPullup {
ioConfig |= esp.IO_MUX_GPIO_FUN_WPU
} else if config.Mode == PinInputPulldown {
ioConfig |= esp.IO_MUX_GPIO_FUN_WPD
}
// Set configuration
ioRegister := p.ioMuxReg()
ioRegister.Set(ioConfig)
switch config.Mode {
case PinOutput:
// Set the 'output enable' bit.
if p < 32 {
esp.GPIO.ENABLE_W1TS.Set(1 << p)
} else {
esp.GPIO.ENABLE1_W1TS.Set(1 << (p - 32))
}
// Set the signal to read the output value from. It can be a peripheral
// output signal, or the special value 256 which indicates regular GPIO
// usage.
p.outFunc().Set(signal)
case PinInput, PinInputPullup, PinInputPulldown:
// Clear the 'output enable' bit.
if p < 32 {
esp.GPIO.ENABLE_W1TC.Set(1 << p)
} else {
esp.GPIO.ENABLE1_W1TC.Set(1 << (p - 32))
}
if signal != 256 {
// Signal is a peripheral function (not a simple GPIO). Connect this
// signal to the pin.
// Note that outFunc and inFunc work in the opposite direction.
// outFunc configures a pin to use a given output signal, while
// inFunc specifies a pin to use to read the signal from.
inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p)<<esp.GPIO_FUNC_IN_SEL_CFG_IN_SEL_Pos)
}
}
}
// ioMuxReg returns the IO_MUX_n_REG register used for configuring the io mux for
// this pin
func (p Pin) ioMuxReg() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.IO_MUX.GPIO0), uintptr(p)*4))
}
// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the
// output function selection.
func (p Pin) outFunc() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4))
}
// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input
// function selection.
func inFunc(signal uint32) *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG), uintptr(signal)*4))
}
// Set the pin to high or low.
// Warning: only use this on an output pin!
func (p Pin) Set(value bool) {
if value {
reg, mask := p.portMaskSet()
reg.Set(mask)
} else {
reg, mask := p.portMaskClear()
reg.Set(mask)
}
}
// Return the register and mask to enable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskSet() (*uint32, uint32) {
reg, mask := p.portMaskSet()
return &reg.Reg, mask
}
// Return the register and mask to disable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskClear() (*uint32, uint32) {
reg, mask := p.portMaskClear()
return &reg.Reg, mask
}
func (p Pin) portMaskSet() (*volatile.Register32, uint32) {
if p < 32 {
return &esp.GPIO.OUT_W1TS, 1 << p
} else {
return &esp.GPIO.OUT1_W1TS, 1 << (p - 32)
}
}
func (p Pin) portMaskClear() (*volatile.Register32, uint32) {
if p < 32 {
return &esp.GPIO.OUT_W1TC, 1 << p
} else {
return &esp.GPIO.OUT1_W1TC, 1 << (p - 32)
}
}
// Get returns the current value of a GPIO pin when the pin is configured as an
// input or as an output.
func (p Pin) Get() bool {
if p < 32 {
return esp.GPIO.IN.Get()&(1<<p) != 0
} else {
return esp.GPIO.IN1.Get()&(1<<(p-32)) != 0
}
}
var DefaultUART = UART0
var (
UART0 = &_UART0
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
UART1 = &_UART1
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
UART2 = &_UART2
_UART2 = UART{Bus: esp.UART2, Buffer: NewRingBuffer()}
)
type UART struct {
Bus *esp.UART_Type
Buffer *RingBuffer
}
func (uart *UART) Configure(config UARTConfig) {
if config.BaudRate == 0 {
config.BaudRate = 115200
}
// Crystal clock source is selected by default
uart.Bus.CLKDIV.Set(xtalClock / config.BaudRate)
}
func (uart *UART) writeByte(b byte) error {
for (uart.Bus.STATUS.Get()>>16)&0xff >= 128 {
// Read UART_TXFIFO_CNT from the status register, which indicates how
// many bytes there are in the transmit buffer. Wait until there are
// less than 128 bytes in this buffer (the default buffer size).
}
uart.Bus.FIFO.Set(uint32(b))
return nil
}
func (uart *UART) flush() {}
// TODO: SPI
-117
View File
@@ -3,9 +3,7 @@
package machine
import (
"device/arm"
"device/nrf"
"errors"
"internal/binary"
"runtime/interrupt"
"unsafe"
@@ -382,10 +380,6 @@ 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.
// This method assumes that the destination is already erased.
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() {
return 0, errFlashCannotWritePastEOF
}
@@ -393,34 +387,6 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
address := FlashDataStart() + uintptr(off)
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
result := arm.SVCall3(0x20+9, address, &p[0], uint32(len(p)))
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()
nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Wen)
@@ -462,40 +428,6 @@ func (f flashBlockDevice) EraseBlockSize() int64 {
// supports this. The start and len parameters are in block numbers, use
// EraseBlockSize to map addresses to blocks.
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())
waitWhileFlashBusy()
@@ -515,52 +447,3 @@ func waitWhileFlashBusy() {
for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready {
}
}
var flashError = errors.New("machine: flash operation failed")
const (
flashStatusOk = iota
flashStatusError
flashStatusBusy
)
var flashStatus uint8 = flashStatusOk
var sdEvent uint32
// Process all queued SoftDevice events. May only be called when the SoftDevice is enabled.
//
// Normally these are handled in the same interrupt where Bluetooth events are
// handled. But in TinyGo, that's complicated. One option would be to put it in
// the tinygo.org/x/bluetooth package, but that would cause a circular
// dependency between the machine and the bluetooth package. Another would be to
// put it here, but let the bluetooth package call handleSoftDeviceEvents, but
// that relies on updating the bluetooth package at the same time. As a
// compromise, these events are handled directly where they are expected (here
// in the machine package). This works in practice since there are only very few
// of such events (at the moment, only flash-related ones which is in the
// machine package anyway).
func handleSoftDeviceEvents() {
for {
var result uintptr
if nrf.Device == "nrf52" || nrf.Device == "nrf52840" || nrf.Device == "nrf52833" {
// sd_evt_get: SOC_SVC_BASE_NOT_AVAILABLE + 31
result = arm.SVCall1(0x2C+31, &sdEvent)
} else {
return // TODO: nrf51 etc
}
if result != 0 {
// Some error occured. The only possible error is
// NRF_ERROR_NOT_FOUND, which means there are no more events.
return
}
// The following events are the same numbers in all SoftDevices I've checked.
switch sdEvent {
case 2: // NRF_EVT_FLASH_OPERATION_SUCCESS
flashStatus = flashStatusOk
case 3: // NRF_EVT_FLASH_OPERATION_ERROR
flashStatus = flashStatusError
}
}
}
-2
View File
@@ -69,5 +69,3 @@ const eraseBlockSizeValue = 4096
func eraseBlockSize() int64 {
return eraseBlockSizeValue
}
const spiMaxBufferSize = 255 // from the datasheet: TXD.MAXCNT and RXD.MAXCNT
-2
View File
@@ -90,5 +90,3 @@ const eraseBlockSizeValue = 4096
func eraseBlockSize() int64 {
return eraseBlockSizeValue
}
const spiMaxBufferSize = 0xffff // from the datasheet: TXD.MAXCNT and RXD.MAXCNT
-6
View File
@@ -108,9 +108,3 @@ const eraseBlockSizeValue = 4096
func eraseBlockSize() int64 {
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}
+4 -17
View File
@@ -49,7 +49,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
// Configure for a single shot to perform both write and read (as applicable)
if len(w) != 0 {
i2c.Bus.TXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(w)))))
i2c.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0]))))
i2c.Bus.TXD.MAXCNT.Set(uint32(len(w)))
// 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 {
i2c.Bus.RXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(r)))))
i2c.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0]))))
i2c.Bus.RXD.MAXCNT.Set(uint32(len(r)))
// Auto-start Rx after Tx and Stop after Rx
@@ -89,11 +89,6 @@ 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
}
@@ -122,7 +117,7 @@ func (i2c *I2C) Listen(addr uint8) error {
//
// 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) {
i2c.BusT.RXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(buf)))))
i2c.BusT.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0]))))
i2c.BusT.RXD.MAXCNT.Set(uint32(len(buf)))
i2c.BusT.TASKS_PREPARERX.Set(nrf.TWIS_TASKS_PREPARERX_TASKS_PREPARERX_Trigger)
@@ -139,10 +134,6 @@ 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
evt = I2CFinish
err = nil
@@ -172,7 +163,7 @@ func (i2c *I2C) WaitForEvent(buf []byte) (evt I2CTargetEvent, count int, err err
// Reply supplies the response data the controller.
func (i2c *I2C) Reply(buf []byte) error {
i2c.BusT.TXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(buf)))))
i2c.BusT.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&buf[0]))))
i2c.BusT.TXD.MAXCNT.Set(uint32(len(buf)))
i2c.BusT.EVENTS_STOPPED.Set(0)
@@ -189,10 +180,6 @@ 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)
return nil
+24 -37
View File
@@ -12,8 +12,6 @@ func CPUFrequency() uint32 {
return 64000000
}
var adcVDDHPin = Pin(254) // special pin number for VDDH on the nrf52840
// InitADC initializes the registers needed for ADC.
func InitADC() {
// Enable ADC.
@@ -28,7 +26,7 @@ func InitADC() {
// Samples can be 1(default), 2, 4, 8, 16, 32, 64, 128, 256 samples
func (a *ADC) Configure(config ADCConfig) {
var configVal uint32 = nrf.SAADC_CH_CONFIG_RESP_Bypass<<nrf.SAADC_CH_CONFIG_RESP_Pos |
nrf.SAADC_CH_CONFIG_RESN_Bypass<<nrf.SAADC_CH_CONFIG_RESN_Pos |
nrf.SAADC_CH_CONFIG_RESP_Bypass<<nrf.SAADC_CH_CONFIG_RESN_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
@@ -118,43 +116,36 @@ func (a *ADC) Configure(config ADCConfig) {
// Get returns the current value of an ADC pin in the range 0..0xffff.
func (a *ADC) Get() uint16 {
var adcPin uint32
var pwmPin uint32
var rawValue volatile.Register16
switch a.Pin {
case 2:
adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput0
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput0
case 3:
adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput1
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput1
case 4:
adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput2
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput2
case 5:
adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput3
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput3
case 28:
adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput4
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput4
case 29:
adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput5
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput5
case 30:
adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput6
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput6
case 31:
adcPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput7
case adcVDDHPin:
if Device == "nrf52840" {
adcPin = 0x0D // VDDHDIV5 on the nrf52840
} else {
return 0
}
pwmPin = nrf.SAADC_CH_PSELP_PSELP_AnalogInput7
default:
return 0
}
// Set pin to read.
nrf.SAADC.CH[0].PSELP.Set(adcPin)
nrf.SAADC.CH[0].PSELN.Set(pwmPin)
nrf.SAADC.CH[0].PSELP.Set(pwmPin)
// Destination for sample result.
// 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.PTR.Set(uint32(uintptr(unsafe.Pointer(&rawValue))))
nrf.SAADC.RESULT.MAXCNT.Set(1) // One sample
// Start tasks.
@@ -310,18 +301,20 @@ func (spi *SPI) Transfer(w byte) (byte, error) {
// 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.
func (spi *SPI) Tx(w, r []byte) error {
// Unfortunately the hardware (on the nrf52832) only supports a limited
// amount of bytes in the buffers (depending on the chip), so if either w or
// r is longer than that the transfer needs to be broken up in pieces.
// Unfortunately the hardware (on the nrf52832) only supports up to 255
// bytes in the buffers, so if either w or 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 {
// Prepare the SPI transfer: set the DMA pointers and lengths.
// read buffer
nr := uint32(len(r))
if nr > 0 {
if nr > spiMaxBufferSize {
nr = spiMaxBufferSize
if nr > 255 {
nr = 255
}
spi.Bus.RXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(r)))))
spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0]))))
r = r[nr:]
}
spi.Bus.RXD.MAXCNT.Set(nr)
@@ -329,10 +322,10 @@ func (spi *SPI) Tx(w, r []byte) error {
// write buffer
nw := uint32(len(w))
if nw > 0 {
if nw > spiMaxBufferSize {
nw = spiMaxBufferSize
if nw > 255 {
nw = 255
}
spi.Bus.TXD.PTR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(w)))))
spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0]))))
w = w[nw:]
}
spi.Bus.TXD.MAXCNT.Set(nw)
@@ -342,16 +335,10 @@ func (spi *SPI) Tx(w, r []byte) error {
// finished if the transfer is send-only (a common case).
spi.Bus.TASKS_START.Set(1)
for spi.Bus.EVENTS_END.Get() == 0 {
gosched()
}
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
}
-4
View File
@@ -7,7 +7,3 @@ package machine
func GetRNG() (ret uint32, err error) {
return getRNG()
}
func isSoftDeviceEnabled() bool {
return false
}
+6 -8
View File
@@ -11,8 +11,9 @@ import (
// avoid a heap allocation in GetRNG.
var (
bytesAvailable uint8
buf [4]uint8
softdeviceEnabled uint8
bytesAvailable uint8
buf [4]uint8
errNoSoftDeviceSupport = errors.New("rng: softdevice not supported on this device")
errNotEnoughRandomData = errors.New("rng: not enough random data available")
@@ -23,7 +24,9 @@ var (
func GetRNG() (ret uint32, err error) {
// First check whether the SoftDevice is enabled.
// sd_rand_application_bytes_available_get cannot be called when the SoftDevice is not enabled.
if !isSoftDeviceEnabled() {
arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled
if softdeviceEnabled == 0 {
return getRNG()
}
@@ -54,8 +57,3 @@ func GetRNG() (ret uint32, err error) {
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
-25
View File
@@ -124,31 +124,6 @@ const (
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.
func (p Pin) Configure(config PinConfig) {
if p == NoPin {
-27
View File
@@ -2,8 +2,6 @@
package machine
import "device/rp"
// Analog pins on RP2350a.
const (
ADC0 Pin = GPIO26
@@ -14,28 +12,3 @@ const (
// fifth ADC channel.
thermADC = 30
)
// validPins confirms that the SPI pin selection is a legitimate one
// for the the 2350a chip.
func (spi *SPI) validPins(config SPIConfig) error {
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22
case rp.SPI1:
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
}
switch {
case !okSDI:
return errSPIInvalidSDI
case !okSDO:
return errSPIInvalidSDO
case !okSCK:
return errSPIInvalidSCK
}
return nil
}
-27
View File
@@ -2,8 +2,6 @@
package machine
import "device/rp"
// RP2350B has additional pins.
const (
@@ -48,28 +46,3 @@ var (
PWM10 = getPWMGroup(10)
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
}
+22 -9
View File
@@ -108,7 +108,7 @@ func (spi *SPI) SetBaudRate(br uint32) error {
var prescale, postdiv uint32
freq := CPUFrequency()
for prescale = 2; prescale < 255; prescale += 2 {
if uint64(freq) < uint64((prescale+2)*256)*uint64(br) {
if freq < (prescale+2)*256*br {
break
}
}
@@ -165,9 +165,27 @@ func (spi *SPI) Configure(config SPIConfig) error {
config.SDI = SPI1_SDI_PIN
}
}
if err := spi.validPins(config); err != nil {
return err
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
}
if config.Frequency == 0 {
config.Frequency = defaultBaud
}
@@ -291,7 +309,7 @@ func (spi *SPI) tx(tx []byte) error {
// - set data size to single bytes
// - set the DREQ so that the DMA will fill the SPI FIFO as needed
// - start the transfer
ch.READ_ADDR.Set(uint32(unsafeNoEscape(unsafe.Pointer(unsafe.SliceData(tx)))))
ch.READ_ADDR.Set(uint32(uintptr(unsafe.Pointer(&tx[0]))))
ch.WRITE_ADDR.Set(uint32(uintptr(unsafe.Pointer(&spi.Bus.SSPDR))))
ch.TRANS_COUNT.Set(uint32(len(tx)))
ch.CTRL_TRIG.Set(rp.DMA_CH0_CTRL_TRIG_INCR_READ |
@@ -310,11 +328,6 @@ func (spi *SPI) tx(tx []byte) error {
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
// overflown. We have to clean up this mess now.
-1
View File
@@ -6,6 +6,5 @@ package machine
var Serial Serialer
func InitSerial() {
initUSB()
Serial = USBCDC
}
-11
View File
@@ -19,17 +19,6 @@ var (
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 {
WriteByte(c byte) error
Write(data []byte) (n int, err error)
+10 -11
View File
@@ -35,12 +35,12 @@ type msc struct {
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
cbw *CBW // Last received Command Block Wrapper
queuedBytes uint32 // Number of bytes queued for sending
sentBytes uint32 // Number of bytes sent
transferBytes uint32 // Total bytes to send
cswBuf []byte // CSW response buffer
state mscState
cbw *CBW // Last received Command Block Wrapper
queuedBytes uint32 // Number of bytes queued for sending
sentBytes uint32 // Number of bytes sent
totalBytes uint32 // Total bytes to send
cswBuf []byte // CSW response buffer
state mscState
maxLUN uint8 // Maximum Logical Unit Number (n-1 for n LUNs)
dev machine.BlockDevice
@@ -160,9 +160,8 @@ func (m *msc) sendUSBPacket(b []byte) {
func (m *msc) sendCSW(status csw.Status) {
// Generate CSW packet into m.cswBuf and send it
residue := uint32(0)
expected := m.cbw.transferLength()
if expected >= m.sentBytes {
residue = expected - m.sentBytes
if m.totalBytes >= m.sentBytes {
residue = m.totalBytes - m.sentBytes
}
m.cbw.CSW(status, residue, m.cswBuf)
m.state = mscStateStatusSent
@@ -246,7 +245,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)
m.state = mscStateData
m.transferBytes = cbw.transferLength()
m.totalBytes = cbw.transferLength()
m.queuedBytes = 0
m.sentBytes = 0
m.respStatus = csw.StatusPassed
@@ -282,7 +281,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
// setting the sense key/add'l code/qualifier internally
if m.state == mscStateStatus && !m.txStalled {
if m.cbw.transferLength() > m.sentBytes && m.cbw.isIn() {
if m.totalBytes > m.sentBytes && m.cbw.isIn() {
// 6.7.2 The Thirteen Cases - Case 5 (Hi > Di): STALL before status
m.stallEndpoint(usb.MSC_ENDPOINT_IN)
} else if m.sendZLP {
+11 -16
View File
@@ -21,9 +21,9 @@ func (m *msc) scsiCmdBegin() {
return
}
if m.transferBytes > 0 && m.cbw.isOut() {
if m.totalBytes > 0 && m.cbw.isOut() {
// Reject any other multi-packet commands
if m.transferBytes > m.maxPacketSize {
if m.totalBytes > m.maxPacketSize {
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode)
return
} else {
@@ -53,7 +53,7 @@ func (m *msc) scsiCmdBegin() {
}
if len(m.buf) == 0 {
if m.transferBytes > 0 {
if m.totalBytes > 0 {
// 6.7.2 The Thirteen Cases - Case 4 (Hi > Dn)
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0)
@@ -63,7 +63,7 @@ func (m *msc) scsiCmdBegin() {
m.state = mscStateStatus
}
} else {
if m.transferBytes == 0 {
if m.totalBytes == 0 {
// 6.7.1 The Thirteen Cases - Case 2 (Hn < Di)
// https://usb.org/sites/default/files/usbmassbulk_10.pdf
m.sendScsiError(csw.StatusFailed, scsi.SenseIllegalRequest, 0)
@@ -72,7 +72,6 @@ func (m *msc) scsiCmdBegin() {
if m.cbw.transferLength() < uint32(len(m.buf)) {
m.buf = m.buf[:m.cbw.transferLength()]
}
m.queuedBytes = uint32(len(m.buf))
m.sendUSBPacket(m.buf)
}
}
@@ -94,7 +93,7 @@ func (m *msc) scsiDataTransfer(b []byte) bool {
// Update our sent bytes count to include the just-confirmed bytes
m.sentBytes += m.queuedBytes
if m.sentBytes >= m.transferBytes {
if m.sentBytes >= m.totalBytes {
// Transfer complete, send CSW after transfer confirmed
m.state = mscStateStatus
} else if cmdType == scsi.CmdRead {
@@ -159,8 +158,8 @@ func (m *msc) scsiCmdModeSense(cmd scsi.Cmd) {
// The host allows a good amount of leeway in response size
// Reset total bytes to what we'll actually send
if m.transferBytes > respLen {
m.transferBytes = respLen
if m.totalBytes > respLen {
m.totalBytes = respLen
m.sendZLP = true
}
@@ -211,7 +210,7 @@ func (m *msc) scsiCmdRequestSense() {
// Set the buffer size to the SCSI sense message size and clear
m.resetBuffer(scsi.RequestSenseRespLen)
m.queuedBytes = scsi.RequestSenseRespLen
m.transferBytes = scsi.RequestSenseRespLen
m.totalBytes = scsi.RequestSenseRespLen
// 0x70 - current error, 0x71 - deferred error (not used)
m.buf[0] = 0xF0 // 0x70 for current error plus 0x80 for valid flag bit
@@ -267,7 +266,7 @@ func (m *msc) scsiQueueTask(cmdType scsi.CmdType, b []byte) bool {
switch cmdType {
case scsi.CmdWrite:
// 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)) || (m.sentBytes+m.queuedBytes >= m.transferBytes) {
if m.queuedBytes == uint32(cap(m.blockCache)) {
m.taskQueued = true
}
case scsi.CmdUnmap:
@@ -280,11 +279,7 @@ func (m *msc) scsiQueueTask(cmdType scsi.CmdType, b []byte) bool {
func (m *msc) sendScsiError(status csw.Status, key scsi.Sense, code scsi.SenseCode) {
// Generate CSW into m.cswBuf
expected := m.cbw.transferLength()
residue := uint32(0)
if expected > m.sentBytes {
residue = expected - m.sentBytes
}
residue := m.totalBytes - m.sentBytes
// Prepare to send CSW
m.sendZLP = true // Ensure the transaction is signaled as ended before a CSW is sent
@@ -296,7 +291,7 @@ func (m *msc) sendScsiError(status csw.Status, key scsi.Sense, code scsi.SenseCo
m.addlSenseCode = code
m.addlSenseQualifier = 0x00 // Not used
if expected > 0 && residue > 0 {
if m.totalBytes > 0 && residue > 0 {
if m.cbw.isIn() {
m.stallEndpoint(usb.MSC_ENDPOINT_IN)
} else {
+2 -2
View File
@@ -136,13 +136,13 @@ func (m *msc) scsiEvpdInquiry(cmd scsi.Cmd, pageCode uint8) {
// Set total bytes to the length of our response
m.queuedBytes = uint32(len(m.buf))
m.transferBytes = uint32(len(m.buf))
m.totalBytes = uint32(len(m.buf))
}
func (m *msc) scsiStdInquiry(cmd scsi.Cmd) {
m.resetBuffer(scsi.InquiryRespLen)
m.queuedBytes = scsi.InquiryRespLen
m.transferBytes = scsi.InquiryRespLen
m.totalBytes = scsi.InquiryRespLen
// byte 0 - Device Type (0x00 for direct access block device)
// byte 1 - Removable media bit
+5 -5
View File
@@ -12,7 +12,7 @@ func (m *msc) scsiCmdReadWrite(cmd scsi.Cmd) {
status := m.validateScsiReadWrite(cmd)
if status != csw.StatusPassed {
m.sendScsiError(status, scsi.SenseIllegalRequest, scsi.SenseCodeInvalidCmdOpCode)
} else if m.transferBytes > 0 {
} else if m.totalBytes > 0 {
if cmd.CmdType() == scsi.CmdRead {
m.scsiRead(cmd)
} else {
@@ -28,7 +28,7 @@ func (m *msc) scsiCmdReadWrite(cmd scsi.Cmd) {
func (m *msc) validateScsiReadWrite(cmd scsi.Cmd) csw.Status {
blockCount := cmd.BlockCount()
// CBW wrapper transfer length
if m.transferBytes == 0 {
if m.totalBytes == 0 {
// If the SCSI command's block count doesn't loosely match the wrapper's transfer length something's wrong
if blockCount > 0 {
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
return csw.StatusFailed
}
if m.transferBytes/blockCount == 0 {
if m.totalBytes/blockCount == 0 {
// 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.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) {
// Make sure we don't exceed the buffer size
readEnd := m.transferBytes - m.sentBytes
readEnd := m.totalBytes - m.sentBytes
if readEnd > m.maxPacketSize {
readEnd = m.maxPacketSize
}
@@ -136,7 +136,7 @@ func (m *msc) scsiWrite(cmd scsi.Cmd, b []byte) {
m.sentBytes += uint32(len(b))
}
if m.sentBytes >= m.transferBytes {
if m.sentBytes >= m.totalBytes {
// Data transfer is complete, send CSW
m.state = mscStateStatus
m.run([]byte{}, true)
+1 -1
View File
@@ -60,7 +60,7 @@ func (m *msc) scsiUnmap(b []byte) {
// FIXME: We need to handle erase block alignment
m.sentBytes += uint32(len(b))
if m.sentBytes >= m.transferBytes {
if m.sentBytes >= m.totalBytes {
// Order 66 complete, send CSW to establish galactic empire
m.state = mscStateStatus
m.run([]byte{}, true)
+9 -15
View File
@@ -3,6 +3,7 @@ package msc
import (
"machine"
"machine/usb"
"machine/usb/msc/csw"
)
func setupPacketHandler(setup usb.Setup) bool {
@@ -17,17 +18,11 @@ func (m *msc) setupPacketHandler(setup usb.Setup) bool {
wValue := (uint16(setup.WValueH) << 8) | uint16(setup.WValueL)
switch setup.BRequest {
case usb.CLEAR_FEATURE:
if setup.BmRequestType == 0x02 { // Host-to-Device | Standard | Endpoint
ok = m.handleClearFeature(setup, wValue)
}
ok = m.handleClearFeature(setup, wValue)
case usb.GET_MAX_LUN:
if setup.BmRequestType == 0xA1 { // Device-to-Host | Class | Interface
ok = m.handleGetMaxLun(setup, wValue)
}
ok = m.handleGetMaxLun(setup, wValue)
case usb.MSC_RESET:
if setup.BmRequestType == 0x21 { // Host-to-Device | Class | Interface
ok = m.handleReset(setup, wValue)
}
ok = m.handleReset(setup, wValue)
}
return ok
}
@@ -58,25 +53,24 @@ func (m *msc) handleClearFeature(setup usb.Setup, wValue uint16) bool {
} else if wIndex == usb.MSC_ENDPOINT_OUT {
m.stallEndpoint(usb.MSC_ENDPOINT_OUT)
}
machine.SendZlp()
return true
return ok
}
// Clear the direction bit from the endpoint address for comparison
wIndex := setup.WIndex & 0x7F
// Clear the IN/OUT stalls if addressed to the endpoint
if wIndex == usb.MSC_ENDPOINT_IN {
// Clear the IN/OUT stalls if addressed to the endpoint, or both if addressed to the interface
if wIndex == usb.MSC_ENDPOINT_IN || wIndex == mscInterface {
m.clearStallEndpoint(usb.MSC_ENDPOINT_IN)
ok = true
}
if wIndex == usb.MSC_ENDPOINT_OUT {
if wIndex == usb.MSC_ENDPOINT_OUT || wIndex == mscInterface {
m.clearStallEndpoint(usb.MSC_ENDPOINT_OUT)
ok = true
}
// Send a CSW if needed to resume after the IN endpoint stall is cleared
if m.state == mscStateStatus && wIndex == usb.MSC_ENDPOINT_IN {
m.sendCSW(m.respStatus)
m.sendCSW(csw.StatusPassed)
ok = true
}
-8
View File
@@ -73,14 +73,6 @@ func align(ptr uintptr) uintptr {
//export tinygo_getCurrentStackPointer
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
// otherwise.
func growHeap() bool {
+12 -19
View File
@@ -8,21 +8,16 @@ import "unsafe"
// code linked from other languages can allocate memory without colliding with
// our GC allocations.
// 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)
var allocs = make(map[uintptr][]byte)
//export malloc
func libc_malloc(size uintptr) unsafe.Pointer {
if size == 0 {
return nil
}
ptr := alloc(size, nil)
allocs[(*byte)(ptr)] = size
buf := make([]byte, size)
ptr := unsafe.Pointer(&buf[0])
allocs[uintptr(ptr)] = buf
return ptr
}
@@ -31,8 +26,8 @@ func libc_free(ptr unsafe.Pointer) {
if ptr == nil {
return
}
if _, ok := allocs[(*byte)(ptr)]; ok {
delete(allocs, (*byte)(ptr))
if _, ok := allocs[uintptr(ptr)]; ok {
delete(allocs, uintptr(ptr))
} else {
panic("free: invalid pointer")
}
@@ -53,20 +48,18 @@ 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 is theoretically possible. For now, just always allocate fresh.
// TODO: we could skip this if the new allocation is smaller than the old.
ptr := alloc(size, nil)
buf := make([]byte, size)
if oldPtr != nil {
if oldSize, ok := allocs[(*byte)(oldPtr)]; ok {
oldBuf := unsafe.Slice((*byte)(oldPtr), oldSize)
newBuf := unsafe.Slice((*byte)(ptr), size)
copy(newBuf, oldBuf)
delete(allocs, (*byte)(oldPtr))
if oldBuf, ok := allocs[uintptr(oldPtr)]; ok {
copy(buf, oldBuf)
delete(allocs, uintptr(oldPtr))
} else {
panic("realloc: invalid pointer")
}
}
allocs[(*byte)(ptr)] = size
ptr := unsafe.Pointer(&buf[0])
allocs[uintptr(ptr)] = buf
return ptr
}
-3
View File
@@ -78,8 +78,5 @@ tinygo_longjmp:
ld r28, X+
ld r29, X+
; Mark that we returned from a longjmp.
ldi r24, 1
; Jump to the PC (stored in the Z register)
icall
+3 -1
View File
@@ -30,7 +30,9 @@ func SetPanicOnFault(enabled bool) bool {
func WriteHeapDump(fd uintptr)
func SetTraceback(level string)
// Unimplemented.
func SetTraceback(level string) {
}
func SetMemoryLimit(limit int64) int64 {
return limit
+5
View File
@@ -1,6 +1,11 @@
package runtime
func Callers(skip int, pc []uintptr) int {
if len(pc) > 0 {
// The testing package expects at least one caller in all cases.
pc[0] = 0
return 1
}
return 0
}
+292 -402
View File
@@ -46,15 +46,18 @@ const (
bytesPerBlock = wordsPerBlock * unsafe.Sizeof(heapStart)
stateBits = 2 // how many bits a block state takes (see blockState type)
blocksPerStateByte = 8 / stateBits
markStackSize = 8 * unsafe.Sizeof((*int)(nil)) // number of to-be-marked blocks to queue before forcing a rescan
)
var (
metadataStart unsafe.Pointer // pointer to the start of the heap metadata
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
nextAlloc gcBlock // the next block that should be tried by the allocator
endBlock gcBlock // the block just past the end of the available space
gcTotalAlloc uint64 // total number of bytes allocated
gcTotalBlocks uint64 // total number of allocated blocks
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
)
@@ -63,28 +66,24 @@ var zeroSizedAlloc uint8
// Provide some abstraction over heap blocks.
// blockState stores the four states in which a block can be.
// 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.
// blockState stores the four states in which a block can be. It is two bits in
// size.
type blockState uint8
const (
blockStateLow blockState = 1
blockStateHigh blockState = 1 << blocksPerStateByte
blockStateFree blockState = 0
blockStateHead blockState = blockStateLow
blockStateTail blockState = blockStateHigh
blockStateMark blockState = blockStateLow | blockStateHigh
blockStateMask blockState = blockStateLow | blockStateHigh
blockStateFree blockState = 0 // 00
blockStateHead blockState = 1 // 01
blockStateTail blockState = 2 // 10
blockStateMark blockState = 3 // 11
blockStateMask blockState = 3 // 11
)
// 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.
const blockStateByteAllTails = byte(blockStateTail) * blockStateEach
const blockStateByteAllTails = 0 |
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.
func (s blockState) String() string {
@@ -181,7 +180,7 @@ func (b gcBlock) stateByte() byte {
// Return the block state given a state byte. The state byte must have been
// obtained using b.stateByte(), otherwise the result is incorrect.
func (b gcBlock) stateFromByte(stateByte byte) blockState {
return blockState(stateByte>>(b%blocksPerStateByte)) & blockStateMask
return blockState(stateByte>>((b%blocksPerStateByte)*stateBits)) & blockStateMask
}
// State returns the current block state.
@@ -194,112 +193,36 @@ func (b gcBlock) state() blockState {
// from head to mark.
func (b gcBlock) setState(newState blockState) {
stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte))
*stateBytePtr |= uint8(newState << (b % blocksPerStateByte))
*stateBytePtr |= uint8(newState << ((b % blocksPerStateByte) * stateBits))
if gcAsserts && b.state() != newState {
runtimePanic("gc: setState() was not successful")
}
}
// objHeader is a structure prepended to every heap object to hold metadata.
type objHeader struct {
// next is the next object to scan after this.
next *objHeader
// 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")
// markFree sets the block state to free, no matter what state it was in before.
func (b gcBlock) markFree() {
stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte))
*stateBytePtr &^= uint8(blockStateMask << ((b % blocksPerStateByte) * stateBits))
if gcAsserts && b.state() != blockStateFree {
runtimePanic("gc: markFree() was not successful")
}
// 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
if gcAsserts {
*(*[wordsPerBlock]uintptr)(unsafe.Pointer(b.address())) = [wordsPerBlock]uintptr{}
}
}
// popFreeRange removes a range of len blocks from the freeRanges list.
// It returns nil if there are no sufficiently long ranges.
func popFreeRange(len uintptr) unsafe.Pointer {
if gcAsserts && len == 0 {
runtimePanic("gc: pop 0-length free range")
// unmark changes the state of the block from mark to head. It must be marked
// before calling this function.
func (b gcBlock) unmark() {
if gcAsserts && b.state() != blockStateMark {
runtimePanic("gc: unmark() on a block that is not marked")
}
// Find the removal point by length.
// Skip until the next range is at least the target length.
remDst := &freeRanges
for *remDst != nil && (*remDst).len < len {
remDst = &(*remDst).nextLen
clearMask := blockStateMask ^ blockStateHead // the bits to clear from the state
stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte))
*stateBytePtr &^= uint8(clearMask << ((b % blocksPerStateByte) * stateBits))
if gcAsserts && b.state() != blockStateHead {
runtimePanic("gc: unmark() was not successful")
}
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 {
@@ -316,9 +239,6 @@ func initHeap() {
// Set all block states to 'free'.
metadataSize := heapEnd - uintptr(metadataStart)
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.
@@ -350,9 +270,6 @@ func setHeapEnd(newHeapEnd uintptr) {
if gcAsserts && uintptr(metadataStart) < uintptr(oldMetadataStart)+oldMetadataSize {
runtimePanic("gc: heap did not grow enough at once")
}
// Rebuild the free ranges list.
buildFreeRanges()
}
// calculateHeapAddresses initializes variables such as metadataStart and
@@ -394,88 +311,118 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
return unsafe.Pointer(&zeroSizedAlloc)
}
if preciseHeap {
size += align(unsafe.Sizeof(layout))
}
if interrupt.In() {
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
// designed for concurrent alloc/GC.
gcLock.Lock()
// Update the total allocation counters.
gcTotalAlloc += uint64(rawSize)
gcTotalAlloc += uint64(size)
gcMallocs++
// Acquire a range of free blocks.
var ranGC bool
var grewHeap bool
var pointer unsafe.Pointer
neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock
gcTotalBlocks += uint64(neededBlocks)
// Continue looping until a run of free blocks has been found that fits the
// requested size.
index := nextAlloc
numFreeBlocks := uintptr(0)
heapScanCount := uint8(0)
for {
pointer = popFreeRange(neededBlocks)
if pointer != nil {
break
}
if !ranGC {
// Run the collector and try again.
freeBytes := runGC()
ranGC = true
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()
if index == nextAlloc {
if heapScanCount == 0 {
heapScanCount = 1
} 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 index == endBlock {
index = 0
// Reset numFreeBlocks as allocations cannot wrap.
numFreeBlocks = 0
// In rare cases, the initial heap might be so small that there are
// no blocks at all. In this case, it's better to jump back to the
// start of the loop and try again, until the GC realizes there is
// no memory and grows the heap.
// This can sometimes happen on WebAssembly, where the initial heap
// is created by whatever is left on the last memory page.
continue
}
if gcDebug && !grewHeap {
println("grow heap for request:", uint(neededBlocks))
dumpFreeRangeCounts()
}
if growHeap() {
grewHeap = true
// Is the block we're looking at free?
if index.state() != blockStateFree {
// This block is in use. Try again from this point.
numFreeBlocks = 0
index++
continue
}
numFreeBlocks++
index++
// 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")
// Are we finished?
if numFreeBlocks == neededBlocks {
// Found a big enough range of free blocks!
nextAlloc = index
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 {
@@ -560,10 +507,7 @@ func runGC() (freeBytes uintptr) {
// Sweep phase: free all non-marked objects and unmark marked objects for
// the next collection cycle.
sweep()
// Rebuild the free ranges list.
freeBytes = buildFreeRanges()
freeBytes = sweep()
// Show how much has been sweeped, for debugging.
if gcDebug {
@@ -575,7 +519,8 @@ func runGC() (freeBytes uintptr) {
// 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
// well (recursively). The starting address must be valid and aligned.
// well (recursively). The start and end parameters must be valid pointers and
// must be aligned.
func markRoots(start, end uintptr) {
if gcDebug {
println("mark from", start, "to", end, int(end-start))
@@ -587,21 +532,18 @@ func markRoots(start, end uintptr) {
if start%unsafe.Alignof(start) != 0 {
runtimePanic("gc: unaligned start pointer")
}
if end%unsafe.Alignof(end) != 0 {
runtimePanic("gc: unaligned end pointer")
}
}
// Scan the range conservatively.
scanConservative(start, end-start)
}
// Reduce the end bound to avoid reading too far on platforms where pointer alignment is smaller than pointer size.
// If the size of the range is 0, then end will be slightly below start after this.
end -= unsafe.Sizeof(end) - unsafe.Alignof(end)
// 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) {
for addr := start; addr < end; addr += unsafe.Alignof(addr) {
root := *(*uintptr)(unsafe.Pointer(addr))
markRoot(addr, root)
addr += unsafe.Alignof(addr)
len -= unsafe.Alignof(addr)
}
}
@@ -611,164 +553,159 @@ func markCurrentGoroutineStack(sp uintptr) {
markRoot(0, sp)
}
// finishMark finishes the marking process by scanning all heap objects on scanList.
func finishMark() {
for {
// Remove an object from the scan list.
obj := scanList
if obj == nil {
return
}
scanList = obj.next
// stackOverflow is a flag which is set when the GC scans too deep while marking.
// After it is set, all marked allocations must be re-scanned.
var stackOverflow bool
// Check if the object may contain pointers.
if obj.layout.pointerFree() {
// startMark starts the marking process on a root and all of its children.
func startMark(root gcBlock) {
var stack [markStackSize]gcBlock
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)
}
// Scan all pointers inside the block.
scanner := newGCObjectScanner(block)
if scanner.pointerFree() {
// This object doesn't contain any pointers.
// This is a fast path for objects like make([]int, 4096).
// It skips the length calculation.
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))
// Compute the scan bounds.
objAddr := uintptr(unsafe.Pointer(obj))
start := objAddr + align(unsafe.Sizeof(objHeader{}))
end := blockFromAddr(objAddr).findNext().address()
if !scanner.nextIsPointer(word, root.address(), addr) {
// Not a heap pointer.
continue
}
// Scan the object.
obj.layout.scan(start, end-start)
// Find the corresponding memory block.
referencedBlock := blockFromAddr(word)
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.
func markRoot(addr, root uintptr) {
// Find the heap block corresponding to the root.
if !isOnHeap(root) {
// This is not a heap pointer.
return
if isOnHeap(root) {
block := blockFromAddr(root)
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()
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.
func sweep() {
metadataEnd := unsafe.Add(metadataStart, (endBlock+(blocksPerStateByte-1))/blocksPerStateByte)
var carry byte
for meta := metadataStart; meta != metadataEnd; meta = unsafe.Add(meta, 1) {
// Fetch the state byte.
stateBytePtr := (*byte)(unsafe.Pointer(meta))
stateByte := *stateBytePtr
// Separate blocks by type.
// Split the nibbles.
// Each nibble is a mask of blocks.
high := stateByte >> blocksPerStateByte
low := stateByte & blockStateEach
// Marked heads are in both nibbles.
markedHeads := low & high
// Unmarked heads are in the low nibble but not the high nibble.
unmarkedHeads := low &^ high
// Tails are in the high nibble but not the low nibble.
tails := high &^ low
// Clear all tail runs after unmarked (freed) heads.
//
// Adding 1 to the start of a bit run will clear the run and set the next bit:
// (2^k - 1) + 1 = 2^k
// e.g. 0b0011 + 1 = 0b0100
// Bitwise-and with the original mask to clear the newly set bit.
// 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--
// It returns how many bytes are free in the heap after the sweep.
func sweep() (freeBytes uintptr) {
freeCurrentObject := false
var freed uint64
for block := gcBlock(0); block < endBlock; block++ {
switch block.state() {
case blockStateHead:
// Unmarked head. Free it, including all tail blocks following it.
block.markFree()
freeCurrentObject = true
gcFrees++
freed++
case blockStateTail:
if freeCurrentObject {
// This is a tail object following an unmarked head.
// Free it now.
block.markFree()
freed++
}
case blockStateMark:
// This is a marked object. The next tail blocks must not be freed,
// but the mark bit must be removed so the next GC cycle will
// collect this object if it is unreferenced then.
block.unmark()
freeCurrentObject = false
case blockStateFree:
freeBytes += bytesPerBlock
}
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
@@ -798,75 +735,28 @@ func dumpHeap() {
// call to ReadMemStats. This would not do GC implicitly for you.
func ReadMemStats(m *MemStats) {
gcLock.Lock()
// Calculate the raw size of the heap.
heapEnd := heapEnd
heapStart := heapStart
m.Sys = uint64(heapEnd - heapStart)
m.HeapSys = uint64(uintptr(metadataStart) - heapStart)
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])
m.HeapIdle = 0
m.HeapInuse = 0
for block := gcBlock(0); block < endBlock; block++ {
bstate := block.state()
if bstate == blockStateFree {
m.HeapIdle += uint64(bytesPerBlock)
} else {
m.HeapInuse += uint64(bytesPerBlock)
}
}
// 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.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.HeapAlloc = (gcTotalBlocks - gcFreedBlocks) * uint64(bytesPerBlock)
m.Alloc = m.HeapAlloc
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{}) {
// Unimplemented.
}
+24 -2
View File
@@ -31,6 +31,10 @@ var zeroSizedAlloc uint8
var gcLock task.PMutex
// Normally false, set to true during a GC scan when all other threads get
// paused.
var needsResumeWorld bool
func initHeap() {
libgc_init()
@@ -46,6 +50,18 @@ func gcInit()
func gcCallback() {
// Mark globals and all stacks, and stop the world if we're using threading.
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) {
@@ -71,6 +87,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
}
gcLock.Lock()
needsResumeWorld = false
var ptr unsafe.Pointer
if layout == gclayout.NoPtrs.AsPtr() {
// This object is entirely pointer free, for example make([]int, ...).
@@ -87,7 +104,9 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
// Memory returned from libgc_malloc has already been zeroed, so nothing
// to do here.
}
gcResumeWorld()
if needsResumeWorld {
gcResumeWorld()
}
gcLock.Unlock()
if ptr == nil {
runtimePanic("gc: out of memory")
@@ -102,8 +121,11 @@ func free(ptr unsafe.Pointer) {
func GC() {
gcLock.Lock()
needsResumeWorld = false
libgc_gcollect()
gcResumeWorld()
if needsResumeWorld {
gcResumeWorld()
}
gcLock.Unlock()
}
+10 -11
View File
@@ -6,25 +6,24 @@
package runtime
import "unsafe"
const preciseHeap = false
// 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{}
type gcObjectScanner struct {
}
// gcLayout tracks pointer locations in a heap object.
// The conservative GC treats all locations as potential pointers, so this doesn't need to store anything.
type gcLayout struct {
func newGCObjectScanner(block gcBlock) gcObjectScanner {
return gcObjectScanner{}
}
func (l gcLayout) pointerFree() bool {
func (scanner *gcObjectScanner) pointerFree() bool {
// We don't know whether this object contains pointers, so conservatively
// return false.
return false
}
func (l gcLayout) scan(start, len uintptr) {
scanConservative(start, len)
// nextIsPointer returns whether this could be a pointer. Because the GC is
// conservative, we can't do much more than check whether the object lies
// somewhere in the heap.
func (scanner gcObjectScanner) nextIsPointer(ptr, parent, addrOfWord uintptr) bool {
return isOnHeap(ptr)
}
+73 -78
View File
@@ -57,96 +57,91 @@ package runtime
import "unsafe"
const sizeFieldBits = 4 + (unsafe.Sizeof(uintptr(0)) / 4)
const preciseHeap = true
// parseGCLayout stores the layout information passed to alloc into a gcLayout value.
func parseGCLayout(layout unsafe.Pointer) gcLayout {
return gcLayout(layout)
type gcObjectScanner struct {
index uintptr
size uintptr
bitmap uintptr
bitmapAddr unsafe.Pointer
}
// gcLayout tracks pointer locations in a heap object.
type gcLayout uintptr
func newGCObjectScanner(block gcBlock) gcObjectScanner {
if gcAsserts && block != block.findHead() {
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")
}
func (layout gcLayout) pointerFree() bool {
return layout&1 != 0 && layout>>(sizeFieldBits+1) == 0
}
// scan an object with this element layout.
// 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.
// Extract values from the bitfields.
// See comment at the top of this file for more information.
scanner.size = (layout >> 1) & (1<<sizeFieldBits - 1)
scanner.bitmap = layout >> (1 + sizeFieldBits)
} else {
// Layout is stored separately in a global object.
layoutAddr := unsafe.Pointer(layout)
size := *(*uintptr)(layoutAddr)
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)
scanner.size = *(*uintptr)(layoutAddr)
scanner.bitmapAddr = unsafe.Add(layoutAddr, unsafe.Sizeof(uintptr(0)))
}
return scanner
}
// scanSimple scans an object with an integer bitmask of pointer locations.
// The starting address must be valid and pointer-aligned.
func scanSimple(start, len, size, mask uintptr) {
for len >= size {
// Scan this element.
scanWithMask(start, mask)
// Move to the next element.
start += size
len -= size
func (scanner *gcObjectScanner) pointerFree() bool {
if scanner.bitmapAddr != nil {
// While the format allows for large objects without pointers, this is
// optimized by the compiler so if bitmapAddr is set, we know that there
// are at least some pointers in the object.
return false
}
// If the bitmap is zero, there are definitely no pointers in the object.
return scanner.bitmap == 0
}
// scanComplex scans an object with a bitmap of pointer locations.
// The starting address must be valid and pointer-aligned.
func scanComplex(start, len, size uintptr, bitmap []byte) {
for len >= size {
// Scan this element.
for i, mask := range bitmap {
addr := start + 8*unsafe.Alignof(start)*uintptr(i)
scanWithMask(addr, uintptr(mask))
func (scanner *gcObjectScanner) nextIsPointer(word, parent, addrOfWord uintptr) bool {
index := scanner.index
scanner.index++
if scanner.index == scanner.size {
scanner.index = 0
}
if !isOnHeap(word) {
// 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
}
// Move to the next element.
start += size
len -= size
return true
}
}
// 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)
if (scanner.bitmap>>index)&1 == 0 {
// not a pointer!
return false
}
// Probably a pointer.
return true
}
+11 -10
View File
@@ -29,6 +29,16 @@ type stackChainObject struct {
// - The system stack (aka startup stack) is not heap allocated, so even
// 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
// stackChainStart. Luckily we don't need to scan these, as these globals are
// stored on the goroutine stack and are therefore already getting scanned.
@@ -40,18 +50,9 @@ func markStack() {
// live.
volatile.LoadUint32((*uint32)(unsafe.Pointer(&stackChainStart)))
// Scan the system stack.
var sysSP uintptr
if task.OnSystemStack() {
// 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(getCurrentStackPointer(), stackTop)
}
markRoots(sysSP, stackTop)
}
// trackPointer is a stub function call inserted by the compiler during IR
+16
View File
@@ -181,3 +181,19 @@ func getAuxv() []uintptr {
func cgo_errno() uintptr {
return uintptr(*libc_errno_location())
}
// Unimplemented.
var MemProfileRate int = 0
// Unimplemented.
func SetBlockProfileRate(rate int) {
}
var mutexProfileFraction int
// Unimplemented.
func SetMutexProfileFraction(rate int) int {
previous := mutexProfileFraction
mutexProfileFraction = rate
return previous
}
+3 -4
View File
@@ -6,7 +6,7 @@ import (
"device/arm"
"device/sam"
"machine"
_ "machine/usb/cdc"
"machine/usb/cdc"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
@@ -26,6 +26,8 @@ func init() {
initUSBClock()
initADCClock()
cdc.EnableUSBCDC()
machine.USBDev.Configure(machine.UARTConfig{})
machine.InitSerial()
}
@@ -271,9 +273,6 @@ func nanosecondsToTicks(ns int64) timeUnit {
func sleepTicks(d timeUnit) {
for d != 0 {
ticks := uint32(d)
if d > 0xffff_ffff {
ticks = 0xffff_ffff
}
if !timerSleep(ticks) {
// Bail out early to handle a non-time interrupt.
return
+3 -4
View File
@@ -6,7 +6,7 @@ import (
"device/arm"
"device/sam"
"machine"
_ "machine/usb/cdc"
"machine/usb/cdc"
"runtime/interrupt"
"runtime/volatile"
)
@@ -27,6 +27,8 @@ func init() {
initADCClock()
enableCache()
cdc.EnableUSBCDC()
machine.USBDev.Configure(machine.UARTConfig{})
machine.InitSerial()
}
@@ -264,9 +266,6 @@ func nanosecondsToTicks(ns int64) timeUnit {
func sleepTicks(d timeUnit) {
for d != 0 {
ticks := uint32(d)
if d > 0xffff_ffff {
ticks = 0xffff_ffff
}
if !timerSleep(ticks) {
return
}
-82
View File
@@ -1,82 +0,0 @@
//go:build esp32s3
package runtime
import (
"device/esp"
)
// This is the function called on startup after the flash (IROM/DROM) is
// initialized and the stack pointer has been set.
//
//export main
func main() {
// This initialization configures the following things:
// * It disables all watchdog timers. They might be useful at some point in
// the future, but will need integration into the scheduler. For now,
// they're all disabled.
// * It sets the CPU frequency to 240MHz, which is the maximum speed allowed
// for this CPU. Lower frequencies might be possible in the future, but
// running fast and sleeping quickly is often also a good strategy to save
// power.
// TODO: protect certain memory regions, especially the area below the stack
// to protect against stack overflows. See
// esp_cpu_configure_region_protection in ESP-IDF.
// Disable RTC watchdog.
esp.RTC_CNTL.WDTWPROTECT.Set(0x50D83AA1)
esp.RTC_CNTL.WDTCONFIG0.Set(0)
esp.RTC_CNTL.WDTWPROTECT.Set(0x0) // Re-enable write protect
// Disable Timer 0 watchdog.
esp.TIMG1.WDTWPROTECT.Set(0x50D83AA1) // write protect
esp.TIMG1.WDTCONFIG0.Set(0) // disable TG0 WDT
esp.TIMG1.WDTWPROTECT.Set(0x0) // Re-enable write protect
esp.TIMG0.WDTWPROTECT.Set(0x50D83AA1) // write protect
esp.TIMG0.WDTCONFIG0.Set(0) // disable TG0 WDT
esp.TIMG0.WDTWPROTECT.Set(0x0) // Re-enable write protect
// Disable super watchdog.
esp.RTC_CNTL.SWD_WPROTECT.Set(0x8F1D312A)
esp.RTC_CNTL.SWD_CONF.Set(esp.RTC_CNTL_SWD_CONF_SWD_DISABLE)
esp.RTC_CNTL.SWD_WPROTECT.Set(0x0) // Re-enable write protect
// Change CPU frequency from 20MHz to 80MHz, by switching from the XTAL to the
// PLL clock source (see table "CPU Clock Frequency" in the reference manual).
esp.SYSTEM.SetSYSCLK_CONF_SOC_CLK_SEL(1)
// Change CPU frequency from 80MHz to 240MHz by setting SYSTEM_PLL_FREQ_SEL to
// 1 and SYSTEM_CPUPERIOD_SEL to 2 (see table "CPU Clock Frequency" in the
// reference manual).
esp.SYSTEM.SetCPU_PER_CONF_PLL_FREQ_SEL(1)
esp.SYSTEM.SetCPU_PER_CONF_CPUPERIOD_SEL(2)
// Clear bss. Repeat many times while we wait for cpu/clock to stabilize
for x := 0; x < 30; x++ {
clearbss()
}
// Initialize main system timer used for time.Now.
initTimer()
// Initialize the heap, call main.main, etc.
run()
// Fallback: if main ever returns, hang the CPU.
exit(0)
}
func abort() {
// lock up forever
print("abort called\n")
}
//go:extern _vector_table
var _vector_table [0]uintptr
//go:extern _sbss
var _sbss [0]byte
//go:extern _ebss
var _ebss [0]byte
-86
View File
@@ -1,86 +0,0 @@
//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
View File
@@ -1,4 +1,4 @@
//go:build nrf
//go:build nrf && !nrf52840
package runtime
@@ -78,9 +78,6 @@ func buffered() int {
func sleepTicks(d timeUnit) {
for d != 0 {
ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side)
if d > 0x7fffff {
ticks = 0x7fffff
}
rtc_sleep(ticks)
d -= timeUnit(ticks)
}
+144 -3
View File
@@ -2,6 +2,147 @@
package runtime
// This package needs to be present so that the machine package can go:linkname
// EnableUSBCDC from it.
import _ "machine/usb/cdc"
import (
"device/arm"
"device/nrf"
"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()
}
}
+5 -12
View File
@@ -13,24 +13,17 @@ func sd_app_evt_wait()
// This is a global variable to avoid a heap allocation in waitForEvents.
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() {
// 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
// 500µA-1mA). It is really needed to call sd_app_evt_wait for low power
// consumption.
if isSoftDeviceEnabled() {
// 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
if softdeviceEnabled != 0 {
// Now pick the appropriate SVCall number. Hopefully they won't change
// in the future with a different SoftDevice version.
if nrf.Device == "nrf51" {
+3 -1
View File
@@ -7,7 +7,7 @@ import (
"device/rp"
"internal/task"
"machine"
_ "machine/usb/cdc"
"machine/usb/cdc"
"runtime/interrupt"
"runtime/volatile"
"unsafe"
@@ -360,6 +360,8 @@ func machineInit()
func init() {
machineInit()
cdc.EnableUSBCDC()
machine.USBDev.Configure(machine.UARTConfig{})
machine.InitSerial()
}
-1
View File
@@ -212,7 +212,6 @@ func writeStream(stream *wasiStream, buf *byte, count uint, offset int64) int {
return -1
}
remaining -= len
src = src[len:]
}
return int(count)
-521
View File
@@ -1,521 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// This file has been modified for use by the TinyGo compiler.
package testing
import (
"flag"
"fmt"
"io"
"math"
"os"
"runtime"
"strconv"
"strings"
"time"
)
func initBenchmarkFlags() {
matchBenchmarks = flag.String("test.bench", "", "run only benchmarks matching `regexp`")
benchmarkMemory = flag.Bool("test.benchmem", false, "print memory allocations for benchmarks")
flag.Var(&benchTime, "test.benchtime", "run each benchmark for duration `d`")
}
var (
matchBenchmarks *string
benchmarkMemory *bool
benchTime = benchTimeFlag{d: 1 * time.Second} // changed during test of testing package
)
type benchTimeFlag struct {
d time.Duration
n int
}
func (f *benchTimeFlag) String() string {
if f.n > 0 {
return fmt.Sprintf("%dx", f.n)
}
return time.Duration(f.d).String()
}
func (f *benchTimeFlag) Set(s string) error {
if strings.HasSuffix(s, "x") {
n, err := strconv.ParseInt(s[:len(s)-1], 10, 0)
if err != nil || n <= 0 {
return fmt.Errorf("invalid count")
}
*f = benchTimeFlag{n: int(n)}
return nil
}
d, err := time.ParseDuration(s)
if err != nil || d <= 0 {
return fmt.Errorf("invalid duration")
}
*f = benchTimeFlag{d: d}
return nil
}
// InternalBenchmark is an internal type but exported because it is cross-package;
// it is part of the implementation of the "go test" command.
type InternalBenchmark struct {
Name string
F func(b *B)
}
// B is a type passed to Benchmark functions to manage benchmark
// timing and to specify the number of iterations to run.
//
// A benchmark ends when its Benchmark function returns or calls any of the methods
// FailNow, Fatal, Fatalf, SkipNow, Skip, or Skipf. Those methods must be called
// only from the goroutine running the Benchmark function.
// The other reporting methods, such as the variations of Log and Error,
// may be called simultaneously from multiple goroutines.
//
// Like in tests, benchmark logs are accumulated during execution
// and dumped to standard output when done. Unlike in tests, benchmark logs
// are always printed, so as not to hide output whose existence may be
// affecting benchmark results.
type B struct {
common
context *benchContext
N int
benchFunc func(b *B)
bytes int64
missingBytes bool // one of the subbenchmarks does not have bytes set.
benchTime benchTimeFlag
timerOn bool
result BenchmarkResult
// report memory statistics
showAllocResult bool
// initial state of MemStats.Mallocs and MemStats.TotalAlloc
startAllocs uint64
startBytes uint64
// net total after running benchmar
netAllocs uint64
netBytes uint64
}
// StartTimer starts timing a test. This function is called automatically
// before a benchmark starts, but it can also be used to resume timing after
// a call to StopTimer.
func (b *B) StartTimer() {
if !b.timerOn {
b.start = time.Now()
b.timerOn = true
var mstats runtime.MemStats
runtime.ReadMemStats(&mstats)
b.startAllocs = mstats.Mallocs
b.startBytes = mstats.TotalAlloc
}
}
// StopTimer stops timing a test. This can be used to pause the timer
// while performing complex initialization that you don't
// want to measure.
func (b *B) StopTimer() {
if b.timerOn {
b.duration += time.Since(b.start)
b.timerOn = false
var mstats runtime.MemStats
runtime.ReadMemStats(&mstats)
b.netAllocs += mstats.Mallocs - b.startAllocs
b.netBytes += mstats.TotalAlloc - b.startBytes
}
}
// ResetTimer zeroes the elapsed benchmark time and memory allocation counters
// and deletes user-reported metrics.
func (b *B) ResetTimer() {
if b.timerOn {
b.start = time.Now()
var mstats runtime.MemStats
runtime.ReadMemStats(&mstats)
b.startAllocs = mstats.Mallocs
b.startBytes = mstats.TotalAlloc
}
b.duration = 0
b.netAllocs = 0
b.netBytes = 0
}
// SetBytes records the number of bytes processed in a single operation.
// If this is called, the benchmark will report ns/op and MB/s.
func (b *B) SetBytes(n int64) { b.bytes = n }
// ReportAllocs enables malloc statistics for this benchmark.
// It is equivalent to setting -test.benchmem, but it only affects the
// benchmark function that calls ReportAllocs.
func (b *B) ReportAllocs() {
b.showAllocResult = true
}
// runN runs a single benchmark for the specified number of iterations.
func (b *B) runN(n int) {
b.N = n
runtime.GC()
b.ResetTimer()
b.StartTimer()
b.benchFunc(b)
b.StopTimer()
}
func min(x, y int64) int64 {
if x > y {
return y
}
return x
}
func max(x, y int64) int64 {
if x < y {
return y
}
return x
}
// run1 runs the first iteration of benchFunc. It reports whether more
// iterations of this benchmarks should be run.
func (b *B) run1() bool {
if ctx := b.context; ctx != nil {
// Extend maxLen, if needed.
if n := len(b.name); n > ctx.maxLen {
ctx.maxLen = n + 8 // Add additional slack to avoid too many jumps in size.
}
}
b.runN(1)
return !b.hasSub
}
// run executes the benchmark.
func (b *B) run() {
if b.context != nil {
// Running go test --test.bench
b.processBench(b.context) // calls doBench and prints results
} else {
// Running func Benchmark.
b.doBench()
}
}
func (b *B) doBench() BenchmarkResult {
// in upstream, this uses a goroutine
b.launch()
return b.result
}
// launch launches the benchmark function. It gradually increases the number
// of benchmark iterations until the benchmark runs for the requested benchtime.
// run1 must have been called on b.
func (b *B) launch() {
// Run the benchmark for at least the specified amount of time.
if b.benchTime.n > 0 {
b.runN(b.benchTime.n)
} else {
d := b.benchTime.d
b.failed = false
b.duration = 0
for n := int64(1); !b.failed && b.duration < d && n < 1e9; {
last := n
// Predict required iterations.
goalns := d.Nanoseconds()
prevIters := int64(b.N)
prevns := b.duration.Nanoseconds()
if prevns <= 0 {
// Round up, to avoid div by zero.
prevns = 1
}
// Order of operations matters.
// For very fast benchmarks, prevIters ~= prevns.
// If you divide first, you get 0 or 1,
// which can hide an order of magnitude in execution time.
// So multiply first, then divide.
n = goalns * prevIters / prevns
// Run more iterations than we think we'll need (1.2x).
n += n / 5
// Don't grow too fast in case we had timing errors previously.
n = min(n, 100*last)
// Be sure to run at least one more than last time.
n = max(n, last+1)
// Don't run more than 1e9 times. (This also keeps n in int range on 32 bit platforms.)
n = min(n, 1e9)
b.runN(int(n))
}
}
b.result = BenchmarkResult{b.N, b.duration, b.bytes, b.netAllocs, b.netBytes}
}
// BenchmarkResult contains the results of a benchmark run.
type BenchmarkResult struct {
N int // The number of iterations.
T time.Duration // The total time taken.
Bytes int64 // Bytes processed in one iteration.
MemAllocs uint64 // The total number of memory allocations.
MemBytes uint64 // The total number of bytes allocated.
}
// NsPerOp returns the "ns/op" metric.
func (r BenchmarkResult) NsPerOp() int64 {
if r.N <= 0 {
return 0
}
return r.T.Nanoseconds() / int64(r.N)
}
// mbPerSec returns the "MB/s" metric.
func (r BenchmarkResult) mbPerSec() float64 {
if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 {
return 0
}
return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
}
// AllocsPerOp returns the "allocs/op" metric,
// which is calculated as r.MemAllocs / r.N.
func (r BenchmarkResult) AllocsPerOp() int64 {
if r.N <= 0 {
return 0
}
return int64(r.MemAllocs) / int64(r.N)
}
// AllocedBytesPerOp returns the "B/op" metric,
// which is calculated as r.MemBytes / r.N.
func (r BenchmarkResult) AllocedBytesPerOp() int64 {
if r.N <= 0 {
return 0
}
return int64(r.MemBytes) / int64(r.N)
}
// String returns a summary of the benchmark results.
// It follows the benchmark result line format from
// https://golang.org/design/14313-benchmark-format, not including the
// benchmark name.
// Extra metrics override built-in metrics of the same name.
// String does not include allocs/op or B/op, since those are reported
// by MemString.
func (r BenchmarkResult) String() string {
buf := new(strings.Builder)
fmt.Fprintf(buf, "%8d", r.N)
// Get ns/op as a float.
ns := float64(r.T.Nanoseconds()) / float64(r.N)
if ns != 0 {
buf.WriteByte('\t')
prettyPrint(buf, ns, "ns/op")
}
if mbs := r.mbPerSec(); mbs != 0 {
fmt.Fprintf(buf, "\t%7.2f MB/s", mbs)
}
return buf.String()
}
// MemString returns r.AllocedBytesPerOp and r.AllocsPerOp in the same format as 'go test'.
func (r BenchmarkResult) MemString() string {
return fmt.Sprintf("%8d B/op\t%8d allocs/op",
r.AllocedBytesPerOp(), r.AllocsPerOp())
}
func prettyPrint(w io.Writer, x float64, unit string) {
// Print all numbers with 10 places before the decimal point
// and small numbers with four sig figs. Field widths are
// chosen to fit the whole part in 10 places while aligning
// the decimal point of all fractional formats.
var format string
switch y := math.Abs(x); {
case y == 0 || y >= 999.95:
format = "%10.0f %s"
case y >= 99.995:
format = "%12.1f %s"
case y >= 9.9995:
format = "%13.2f %s"
case y >= 0.99995:
format = "%14.3f %s"
case y >= 0.099995:
format = "%15.4f %s"
case y >= 0.0099995:
format = "%16.5f %s"
case y >= 0.00099995:
format = "%17.6f %s"
default:
format = "%18.7f %s"
}
fmt.Fprintf(w, format, x, unit)
}
type benchContext struct {
match *matcher
maxLen int // The largest recorded benchmark name.
}
func runBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) bool {
// If no flag was specified, don't run benchmarks.
if len(*matchBenchmarks) == 0 {
return true
}
ctx := &benchContext{
match: newMatcher(matchString, *matchBenchmarks, "-test.bench", flagSkipRegexp),
}
var bs []InternalBenchmark
for _, Benchmark := range benchmarks {
if _, matched, _ := ctx.match.fullName(nil, Benchmark.Name); matched {
bs = append(bs, Benchmark)
benchName := Benchmark.Name
if l := len(benchName); l > ctx.maxLen {
ctx.maxLen = l
}
}
}
main := &B{
common: common{
output: &logger{},
name: "Main",
},
benchTime: benchTime,
benchFunc: func(b *B) {
for _, Benchmark := range bs {
b.Run(Benchmark.Name, Benchmark.F)
}
},
context: ctx,
}
main.runN(1)
return true
}
// processBench runs bench b and prints the results.
func (b *B) processBench(ctx *benchContext) {
benchName := b.name
for i := 0; i < flagCount; i++ {
if ctx != nil {
fmt.Printf("%-*s\t", ctx.maxLen, benchName)
}
r := b.doBench()
if b.failed {
// The output could be very long here, but probably isn't.
// We print it all, regardless, because we don't want to trim the reason
// the benchmark failed.
fmt.Printf("--- FAIL: %s\n%s", benchName, "") // b.output)
return
}
if ctx != nil {
results := r.String()
if *benchmarkMemory || b.showAllocResult {
results += "\t" + r.MemString()
}
fmt.Println(results)
// Print any benchmark output
if b.output.Len() > 0 {
fmt.Printf("--- BENCH: %s\n", benchName)
b.output.WriteTo(os.Stdout)
}
}
}
}
// Run benchmarks f as a subbenchmark with the given name. It reports
// true if the subbenchmark succeeded.
//
// A subbenchmark is like any other benchmark. A benchmark that calls Run at
// least once will not be measured itself and will be called once with N=1.
func (b *B) Run(name string, f func(b *B)) bool {
benchName, ok, partial := b.name, true, false
if b.context != nil {
benchName, ok, partial = b.context.match.fullName(&b.common, name)
}
if !ok {
return true
}
b.hasSub = true
sub := &B{
common: common{
output: &logger{},
name: benchName,
level: b.level + 1,
},
benchFunc: f,
benchTime: b.benchTime,
context: b.context,
}
if partial {
// Partial name match, like -bench=X/Y matching BenchmarkX.
// Only process sub-benchmarks, if any.
sub.hasSub = true
}
if sub.run1() {
sub.run()
}
b.add(sub.result)
return !sub.failed
}
// add simulates running benchmarks in sequence in a single iteration. It is
// used to give some meaningful results in case func Benchmark is used in
// combination with Run.
func (b *B) add(other BenchmarkResult) {
r := &b.result
// The aggregated BenchmarkResults resemble running all subbenchmarks as
// in sequence in a single benchmark.
r.N = 1
r.T += time.Duration(other.NsPerOp())
if other.Bytes == 0 {
// Summing Bytes is meaningless in aggregate if not all subbenchmarks
// set it.
b.missingBytes = true
r.Bytes = 0
}
if !b.missingBytes {
r.Bytes += other.Bytes
}
}
// A PB is used by RunParallel for running parallel benchmarks.
type PB struct {
}
// Next reports whether there are more iterations to execute.
func (pb *PB) Next() bool {
return false
}
// RunParallel runs a benchmark in parallel.
//
// Not implemented
func (b *B) RunParallel(body func(*PB)) {
return
}
func (b *B) Loop() bool {
panic("unimplemented: testing.B.Loop")
}
// Benchmark benchmarks a single function. It is useful for creating
// custom benchmarks that do not use the "go test" command.
//
// If f calls Run, the result will be an estimate of running all its
// subbenchmarks that don't call Run in sequence in a single benchmark.
func Benchmark(f func(b *B)) BenchmarkResult {
b := &B{
benchFunc: f,
benchTime: benchTime,
}
if b.run1() {
b.run()
}
return b.result
}
-55
View File
@@ -1,55 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testing_test
import (
"testing"
)
var buf = make([]byte, 13579)
func NonASCII(b []byte, i int, offset int) int {
for i = offset; i < len(b)+offset; i++ {
if b[i%len(b)] >= 0x80 {
break
}
}
return i
}
func BenchmarkFastNonASCII(b *testing.B) {
var val int
for i := 0; i < b.N; i++ {
val += NonASCII(buf, 0, 0)
}
}
func BenchmarkSlowNonASCII(b *testing.B) {
var val int
for i := 0; i < b.N; i++ {
val += NonASCII(buf, 0, 0)
val += NonASCII(buf, 0, 1)
}
}
// TestBenchmark simply uses Benchmark twice and makes sure it does not crash.
func TestBenchmark(t *testing.T) {
// FIXME: reduce runtime from the current 3 seconds.
rslow := testing.Benchmark(BenchmarkSlowNonASCII)
rfast := testing.Benchmark(BenchmarkFastNonASCII)
tslow := rslow.NsPerOp()
tfast := rfast.NsPerOp()
// Be exceedingly forgiving; do not fail even if system gets busy.
speedup := float64(tslow) / float64(tfast)
if speedup < 0.3 {
t.Errorf("Expected speedup >= 0.3, got %f", speedup)
}
}
func BenchmarkSub(b *testing.B) {
b.Run("Fast", func(b *testing.B) { BenchmarkFastNonASCII(b) })
b.Run("Slow", func(b *testing.B) { BenchmarkSlowNonASCII(b) })
}
-6
View File
@@ -1,6 +0,0 @@
package testing
/*
This is a sad stub of the upstream testing package because it doesn't compile
with tinygo right now.
*/
-143
View File
@@ -1,143 +0,0 @@
package testing
import (
"errors"
"fmt"
"reflect"
"time"
)
// InternalFuzzTarget is an internal type but exported because it is
// cross-package; it is part of the implementation of the "go test" command.
type InternalFuzzTarget struct {
Name string
Fn func(f *F)
}
// F is a type passed to fuzz tests.
//
// Fuzz tests run generated inputs against a provided fuzz target, which can
// find and report potential bugs in the code being tested.
//
// A fuzz test runs the seed corpus by default, which includes entries provided
// by (*F).Add and entries in the testdata/fuzz/<FuzzTestName> directory. After
// any necessary setup and calls to (*F).Add, the fuzz test must then call
// (*F).Fuzz to provide the fuzz target. See the testing package documentation
// for an example, and see the F.Fuzz and F.Add method documentation for
// details.
//
// *F methods can only be called before (*F).Fuzz. Once the test is
// executing the fuzz target, only (*T) methods can be used. The only *F methods
// that are allowed in the (*F).Fuzz function are (*F).Failed and (*F).Name.
type F struct {
common
fuzzContext *fuzzContext
testContext *testContext
// inFuzzFn is true when the fuzz function is running. Most F methods cannot
// be called when inFuzzFn is true.
inFuzzFn bool
// corpus is a set of seed corpus entries, added with F.Add and loaded
// from testdata.
corpus []corpusEntry
result fuzzResult
fuzzCalled bool
}
// corpusEntry is an alias to the same type as internal/fuzz.CorpusEntry.
// We use a type alias because we don't want to export this type, and we can't
// import internal/fuzz from testing.
type corpusEntry = struct {
Parent string
Path string
Data []byte
Values []interface{}
Generation int
IsSeed bool
}
// Add will add the arguments to the seed corpus for the fuzz test. This will be
// a no-op if called after or within the fuzz target, and args must match the
// arguments for the fuzz target.
func (f *F) Add(args ...interface{}) {
var values []interface{}
for i := range args {
if t := reflect.TypeOf(args[i]); !supportedTypes[t] {
panic(fmt.Sprintf("testing: unsupported type to Add %v", t))
}
values = append(values, args[i])
}
f.corpus = append(f.corpus, corpusEntry{Values: values, IsSeed: true, Path: fmt.Sprintf("seed#%d", len(f.corpus))})
}
// supportedTypes represents all of the supported types which can be fuzzed.
var supportedTypes = map[reflect.Type]bool{
reflect.TypeOf(([]byte)("")): true,
reflect.TypeOf((string)("")): true,
reflect.TypeOf((bool)(false)): true,
reflect.TypeOf((byte)(0)): true,
reflect.TypeOf((rune)(0)): true,
reflect.TypeOf((float32)(0)): true,
reflect.TypeOf((float64)(0)): true,
reflect.TypeOf((int)(0)): true,
reflect.TypeOf((int8)(0)): true,
reflect.TypeOf((int16)(0)): true,
reflect.TypeOf((int32)(0)): true,
reflect.TypeOf((int64)(0)): true,
reflect.TypeOf((uint)(0)): true,
reflect.TypeOf((uint8)(0)): true,
reflect.TypeOf((uint16)(0)): true,
reflect.TypeOf((uint32)(0)): true,
reflect.TypeOf((uint64)(0)): true,
}
// Fuzz runs the fuzz function, ff, for fuzz testing. If ff fails for a set of
// arguments, those arguments will be added to the seed corpus.
//
// ff must be a function with no return value whose first argument is *T and
// whose remaining arguments are the types to be fuzzed.
// For example:
//
// f.Fuzz(func(t *testing.T, b []byte, i int) { ... })
//
// The following types are allowed: []byte, string, bool, byte, rune, float32,
// float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64.
// More types may be supported in the future.
//
// ff must not call any *F methods, e.g. (*F).Log, (*F).Error, (*F).Skip. Use
// the corresponding *T method instead. The only *F methods that are allowed in
// the (*F).Fuzz function are (*F).Failed and (*F).Name.
//
// This function should be fast and deterministic, and its behavior should not
// depend on shared state. No mutatable input arguments, or pointers to them,
// should be retained between executions of the fuzz function, as the memory
// backing them may be mutated during a subsequent invocation. ff must not
// modify the underlying data of the arguments provided by the fuzzing engine.
//
// When fuzzing, F.Fuzz does not return until a problem is found, time runs out
// (set with -fuzztime), or the test process is interrupted by a signal. F.Fuzz
// should be called exactly once, unless F.Skip or F.Fail is called beforehand.
func (f *F) Fuzz(ff interface{}) {
f.failed = true
f.result.N = 0
f.result.T = 0
f.result.Error = errors.New("operation not implemented")
return
}
// fuzzContext holds fields common to all fuzz tests.
type fuzzContext struct {
deps testDeps
mode fuzzMode
}
type fuzzMode uint8
// fuzzResult contains the results of a fuzz run.
type fuzzResult struct {
N int // The number of iterations.
T time.Duration // The total time taken.
Error error // Error is the error from the failing input
}
-9
View File
@@ -1,9 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
//go:build baremetal
package testing
const isBaremetal = true
-9
View File
@@ -1,9 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
//go:build !baremetal
package testing
const isBaremetal = false
-323
View File
@@ -1,323 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testing
import (
"fmt"
"os"
"strconv"
"strings"
"sync"
)
// matcher sanitizes, uniques, and filters names of subtests and subbenchmarks.
type matcher struct {
filter filterMatch
skip filterMatch
matchFunc func(pat, str string) (bool, error)
mu sync.Mutex
// subNames is used to deduplicate subtest names.
// Each key is the subtest name joined to the deduplicated name of the parent test.
// Each value is the count of the number of occurrences of the given subtest name
// already seen.
subNames map[string]int32
}
type filterMatch interface {
// matches checks the name against the receiver's pattern strings using the
// given match function.
matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool)
// verify checks that the receiver's pattern strings are valid filters by
// calling the given match function.
verify(name string, matchString func(pat, str string) (bool, error)) error
}
// simpleMatch matches a test name if all of the pattern strings match in
// sequence.
type simpleMatch []string
// alternationMatch matches a test name if one of the alternations match.
type alternationMatch []filterMatch
// TODO: fix test_main to avoid race and improve caching, also allowing to
// eliminate this Mutex.
var matchMutex sync.Mutex
func allMatcher() *matcher {
return newMatcher(nil, "", "", "")
}
func newMatcher(matchString func(pat, str string) (bool, error), patterns, name, skips string) *matcher {
if isBaremetal {
matchString = fakeMatchString
}
var filter, skip filterMatch
if patterns == "" {
filter = simpleMatch{} // always partial true
} else {
filter = splitRegexp(patterns)
if err := filter.verify(name, matchString); err != nil {
fmt.Fprintf(os.Stderr, "testing: invalid regexp for %s\n", err)
os.Exit(1)
}
}
if skips == "" {
skip = alternationMatch{} // always false
} else {
skip = splitRegexp(skips)
if err := skip.verify("-test.skip", matchString); err != nil {
fmt.Fprintf(os.Stderr, "testing: invalid regexp for %v\n", err)
os.Exit(1)
}
}
return &matcher{
filter: filter,
skip: skip,
matchFunc: matchString,
subNames: map[string]int32{},
}
}
func (m *matcher) fullName(c *common, subname string) (name string, ok, partial bool) {
name = subname
m.mu.Lock()
defer m.mu.Unlock()
if c != nil && c.level > 0 {
name = m.unique(c.name, rewrite(subname))
}
matchMutex.Lock()
defer matchMutex.Unlock()
// We check the full array of paths each time to allow for the case that a pattern contains a '/'.
elem := strings.Split(name, "/")
// filter must match.
// accept partial match that may produce full match later.
ok, partial = m.filter.matches(elem, m.matchFunc)
if !ok {
return name, false, false
}
// skip must not match.
// ignore partial match so we can get to more precise match later.
skip, partialSkip := m.skip.matches(elem, m.matchFunc)
if skip && !partialSkip {
return name, false, false
}
return name, ok, partial
}
// clearSubNames clears the matcher's internal state, potentially freeing
// memory. After this is called, T.Name may return the same strings as it did
// for earlier subtests.
func (m *matcher) clearSubNames() {
m.mu.Lock()
defer m.mu.Unlock()
for key := range m.subNames {
delete(m.subNames, key)
}
}
func (m simpleMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) {
for i, s := range name {
if i >= len(m) {
break
}
if ok, _ := matchString(m[i], s); !ok {
return false, false
}
}
return true, len(name) < len(m)
}
func (m simpleMatch) verify(name string, matchString func(pat, str string) (bool, error)) error {
for i, s := range m {
m[i] = rewrite(s)
}
// Verify filters before doing any processing.
for i, s := range m {
if _, err := matchString(s, "non-empty"); err != nil {
return fmt.Errorf("element %d of %s (%q): %s", i, name, s, err)
}
}
return nil
}
func (m alternationMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) {
for _, m := range m {
if ok, partial = m.matches(name, matchString); ok {
return ok, partial
}
}
return false, false
}
func (m alternationMatch) verify(name string, matchString func(pat, str string) (bool, error)) error {
for i, m := range m {
if err := m.verify(name, matchString); err != nil {
return fmt.Errorf("alternation %d of %s", i, err)
}
}
return nil
}
func splitRegexp(s string) filterMatch {
a := make(simpleMatch, 0, strings.Count(s, "/"))
b := make(alternationMatch, 0, strings.Count(s, "|"))
cs := 0
cp := 0
for i := 0; i < len(s); {
switch s[i] {
case '[':
cs++
case ']':
if cs--; cs < 0 { // An unmatched ']' is legal.
cs = 0
}
case '(':
if cs == 0 {
cp++
}
case ')':
if cs == 0 {
cp--
}
case '\\':
i++
case '/':
if cs == 0 && cp == 0 {
a = append(a, s[:i])
s = s[i+1:]
i = 0
continue
}
case '|':
if cs == 0 && cp == 0 {
a = append(a, s[:i])
s = s[i+1:]
i = 0
b = append(b, a)
a = make(simpleMatch, 0, len(a))
continue
}
}
i++
}
a = append(a, s)
if len(b) == 0 {
return a
}
return append(b, a)
}
// unique creates a unique name for the given parent and subname by affixing it
// with one or more counts, if necessary.
func (m *matcher) unique(parent, subname string) string {
base := parent + "/" + subname
for {
n := m.subNames[base]
if n < 0 {
panic("subtest count overflow")
}
m.subNames[base] = n + 1
if n == 0 && subname != "" {
prefix, nn := parseSubtestNumber(base)
if len(prefix) < len(base) && nn < m.subNames[prefix] {
// This test is explicitly named like "parent/subname#NN",
// and #NN was already used for the NNth occurrence of "parent/subname".
// Loop to add a disambiguating suffix.
continue
}
return base
}
name := fmt.Sprintf("%s#%02d", base, n)
if m.subNames[name] != 0 {
// This is the nth occurrence of base, but the name "parent/subname#NN"
// collides with the first occurrence of a subtest *explicitly* named
// "parent/subname#NN". Try the next number.
continue
}
return name
}
}
// parseSubtestNumber splits a subtest name into a "#%02d"-formatted int32
// suffix (if present), and a prefix preceding that suffix (always).
func parseSubtestNumber(s string) (prefix string, nn int32) {
i := strings.LastIndex(s, "#")
if i < 0 {
return s, 0
}
prefix, suffix := s[:i], s[i+1:]
if len(suffix) < 2 || (len(suffix) > 2 && suffix[0] == '0') {
// Even if suffix is numeric, it is not a possible output of a "%02" format
// string: it has either too few digits or too many leading zeroes.
return s, 0
}
if suffix == "00" {
if !strings.HasSuffix(prefix, "/") {
// We only use "#00" as a suffix for subtests named with the empty
// string — it isn't a valid suffix if the subtest name is non-empty.
return s, 0
}
}
n, err := strconv.ParseInt(suffix, 10, 32)
if err != nil || n < 0 {
return s, 0
}
return prefix, int32(n)
}
// rewrite rewrites a subname to having only printable characters and no white
// space.
func rewrite(s string) string {
b := []byte{}
for _, r := range s {
switch {
case isSpace(r):
b = append(b, '_')
case !strconv.IsPrint(r):
s := strconv.QuoteRune(r)
b = append(b, s[1:len(s)-1]...)
default:
b = append(b, string(r)...)
}
}
return string(b)
}
func isSpace(r rune) bool {
if r < 0x2000 {
switch r {
// Note: not the same as Unicode Z class.
case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680:
return true
}
} else {
if r <= 0x200a {
return true
}
switch r {
case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000:
return true
}
}
return false
}
-259
View File
@@ -1,259 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testing
import (
"fmt"
"reflect"
"regexp"
"strings"
"unicode"
)
// Verify that our IsSpace agrees with unicode.IsSpace.
func TestIsSpace(t *T) {
n := 0
for r := rune(0); r <= unicode.MaxRune; r++ {
if isSpace(r) != unicode.IsSpace(r) {
t.Errorf("IsSpace(%U)=%t incorrect", r, isSpace(r))
n++
if n > 10 {
return
}
}
}
}
func TestSplitRegexp(t *T) {
res := func(s ...string) filterMatch { return simpleMatch(s) }
alt := func(m ...filterMatch) filterMatch { return alternationMatch(m) }
testCases := []struct {
pattern string
result filterMatch
}{
// Correct patterns
// If a regexp pattern is correct, all split regexps need to be correct
// as well.
{"", res("")},
{"/", res("", "")},
{"//", res("", "", "")},
{"A", res("A")},
{"A/B", res("A", "B")},
{"A/B/", res("A", "B", "")},
{"/A/B/", res("", "A", "B", "")},
{"[A]/(B)", res("[A]", "(B)")},
{"[/]/[/]", res("[/]", "[/]")},
{"[/]/[:/]", res("[/]", "[:/]")},
{"/]", res("", "]")},
{"]/", res("]", "")},
{"]/[/]", res("]", "[/]")},
{`([)/][(])`, res(`([)/][(])`)},
{"[(]/[)]", res("[(]", "[)]")},
{"A/B|C/D", alt(res("A", "B"), res("C", "D"))},
// Faulty patterns
// Errors in original should produce at least one faulty regexp in results.
{")/", res(")/")},
{")/(/)", res(")/(", ")")},
{"a[/)b", res("a[/)b")},
{"(/]", res("(/]")},
{"(/", res("(/")},
{"[/]/[/", res("[/]", "[/")},
{`\p{/}`, res(`\p{`, "}")},
{`\p/`, res(`\p`, "")},
{`[[:/:]]`, res(`[[:/:]]`)},
}
for _, tc := range testCases {
a := splitRegexp(tc.pattern)
if !reflect.DeepEqual(a, tc.result) {
t.Errorf("splitRegexp(%q) = %#v; want %#v", tc.pattern, a, tc.result)
}
// If there is any error in the pattern, one of the returned subpatterns
// needs to have an error as well.
if _, err := regexp.Compile(tc.pattern); err != nil {
ok := true
if err := a.verify("", regexp.MatchString); err != nil {
ok = false
}
if ok {
t.Errorf("%s: expected error in any of %q", tc.pattern, a)
}
}
}
}
func TestMatcher(t *T) {
testCases := []struct {
pattern string
skip string
parent, sub string
ok bool
partial bool
}{
// Behavior without subtests.
{"", "", "", "TestFoo", true, false},
{"TestFoo", "", "", "TestFoo", true, false},
{"TestFoo/", "", "", "TestFoo", true, true},
{"TestFoo/bar/baz", "", "", "TestFoo", true, true},
{"TestFoo", "", "", "TestBar", false, false},
{"TestFoo/", "", "", "TestBar", false, false},
{"TestFoo/bar/baz", "", "", "TestBar/bar/baz", false, false},
{"", "TestBar", "", "TestFoo", true, false},
{"", "TestBar", "", "TestBar", false, false},
// Skipping a non-existent test doesn't change anything.
{"", "TestFoo/skipped", "", "TestFoo", true, false},
{"TestFoo", "TestFoo/skipped", "", "TestFoo", true, false},
{"TestFoo/", "TestFoo/skipped", "", "TestFoo", true, true},
{"TestFoo/bar/baz", "TestFoo/skipped", "", "TestFoo", true, true},
{"TestFoo", "TestFoo/skipped", "", "TestBar", false, false},
{"TestFoo/", "TestFoo/skipped", "", "TestBar", false, false},
{"TestFoo/bar/baz", "TestFoo/skipped", "", "TestBar/bar/baz", false, false},
// with subtests
{"", "", "TestFoo", "x", true, false},
{"TestFoo", "", "TestFoo", "x", true, false},
{"TestFoo/", "", "TestFoo", "x", true, false},
{"TestFoo/bar/baz", "", "TestFoo", "bar", true, true},
{"", "TestFoo/skipped", "TestFoo", "x", true, false},
{"TestFoo", "TestFoo/skipped", "TestFoo", "x", true, false},
{"TestFoo", "TestFoo/skipped", "TestFoo", "skipped", false, false},
{"TestFoo/", "TestFoo/skipped", "TestFoo", "x", true, false},
{"TestFoo/bar/baz", "TestFoo/skipped", "TestFoo", "bar", true, true},
// Subtest with a '/' in its name still allows for copy and pasted names
// to match.
{"TestFoo/bar/baz", "", "TestFoo", "bar/baz", true, false},
{"TestFoo/bar/baz", "TestFoo/bar/baz", "TestFoo", "bar/baz", false, false},
{"TestFoo/bar/baz", "TestFoo/bar/baz/skip", "TestFoo", "bar/baz", true, false},
{"TestFoo/bar/baz", "", "TestFoo/bar", "baz", true, false},
{"TestFoo/bar/baz", "", "TestFoo", "x", false, false},
{"TestFoo", "", "TestBar", "x", false, false},
{"TestFoo/", "", "TestBar", "x", false, false},
{"TestFoo/bar/baz", "", "TestBar", "x/bar/baz", false, false},
{"A/B|C/D", "", "TestA", "B", true, false},
{"A/B|C/D", "", "TestC", "D", true, false},
{"A/B|C/D", "", "TestA", "C", false, false},
// subtests only
{"", "", "TestFoo", "x", true, false},
{"/", "", "TestFoo", "x", true, false},
{"./", "", "TestFoo", "x", true, false},
{"./.", "", "TestFoo", "x", true, false},
{"/bar/baz", "", "TestFoo", "bar", true, true},
{"/bar/baz", "", "TestFoo", "bar/baz", true, false},
{"//baz", "", "TestFoo", "bar/baz", true, false},
{"//", "", "TestFoo", "bar/baz", true, false},
{"/bar/baz", "", "TestFoo/bar", "baz", true, false},
{"//foo", "", "TestFoo", "bar/baz", false, false},
{"/bar/baz", "", "TestFoo", "x", false, false},
{"/bar/baz", "", "TestBar", "x/bar/baz", false, false},
}
for _, tc := range testCases {
m := newMatcher(regexp.MatchString, tc.pattern, "-test.run", tc.skip)
parent := &common{name: tc.parent}
if tc.parent != "" {
parent.level = 1
}
if n, ok, partial := m.fullName(parent, tc.sub); ok != tc.ok || partial != tc.partial {
t.Errorf("for pattern %q, fullName(parent=%q, sub=%q) = %q, ok %v partial %v; want ok %v partial %v",
tc.pattern, tc.parent, tc.sub, n, ok, partial, tc.ok, tc.partial)
}
}
}
var namingTestCases = []struct{ name, want string }{
// Uniqueness
{"", "x/#00"},
{"", "x/#01"},
{"#0", "x/#0"}, // Doesn't conflict with #00 because the number of digits differs.
{"#00", "x/#00#01"}, // Conflicts with implicit #00 (used above), so add a suffix.
{"#", "x/#"},
{"#", "x/##01"},
{"t", "x/t"},
{"t", "x/t#01"},
{"t", "x/t#02"},
{"t#00", "x/t#00"}, // Explicit "#00" doesn't conflict with the unsuffixed first subtest.
{"a#01", "x/a#01"}, // user has subtest with this name.
{"a", "x/a"}, // doesn't conflict with this name.
{"a", "x/a#02"}, // This string is claimed now, so resume
{"a", "x/a#03"}, // with counting.
{"a#02", "x/a#02#01"}, // We already used a#02 once, so add a suffix.
{"b#00", "x/b#00"},
{"b", "x/b"}, // Implicit 0 doesn't conflict with explicit "#00".
{"b", "x/b#01"},
{"b#9223372036854775807", "x/b#9223372036854775807"}, // MaxInt64
{"b", "x/b#02"},
{"b", "x/b#03"},
// Sanitizing
{"A:1 B:2", "x/A:1_B:2"},
{"s\t\r\u00a0", "x/s___"},
{"\x01", `x/\x01`},
{"\U0010ffff", `x/\U0010ffff`},
}
func TestNaming(t *T) {
m := newMatcher(regexp.MatchString, "", "", "")
parent := &common{name: "x", level: 1} // top-level test.
for i, tc := range namingTestCases {
if got, _, _ := m.fullName(parent, tc.name); got != tc.want {
t.Errorf("%d:%s: got %q; want %q", i, tc.name, got, tc.want)
}
}
}
func FuzzNaming(f *F) {
for _, tc := range namingTestCases {
f.Add(tc.name)
}
parent := &common{name: "x", level: 1}
var m *matcher
var seen map[string]string
reset := func() {
m = allMatcher()
seen = make(map[string]string)
}
reset()
f.Fuzz(func(t *T, subname string) {
if len(subname) > 10 {
// Long names attract the OOM killer.
t.Skip()
}
name := m.unique(parent.name, subname)
if !strings.Contains(name, "/"+subname) {
t.Errorf("name %q does not contain subname %q", name, subname)
}
if prev, ok := seen[name]; ok {
t.Errorf("name %q generated by both %q and %q", name, prev, subname)
}
if len(seen) > 1e6 {
// Free up memory.
reset()
}
seen[name] = subname
})
}
// GoString returns a string that is more readable than the default, which makes
// it easier to read test errors.
func (m alternationMatch) GoString() string {
s := make([]string, len(m))
for i, m := range m {
s[i] = fmt.Sprintf("%#v", m)
}
return fmt.Sprintf("(%s)", strings.Join(s, " | "))
}
-80
View File
@@ -1,80 +0,0 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testing
import (
"reflect"
)
func TestCleanup(t *T) {
var cleanups []int
t.Run("test", func(t *T) {
t.Cleanup(func() { cleanups = append(cleanups, 1) })
t.Cleanup(func() { cleanups = append(cleanups, 2) })
})
if got, want := cleanups, []int{2, 1}; !reflect.DeepEqual(got, want) {
t.Errorf("unexpected cleanup record; got %v want %v", got, want)
}
}
func TestRunCleanup(t *T) {
outerCleanup := 0
innerCleanup := 0
t.Run("test", func(t *T) {
t.Cleanup(func() { outerCleanup++ })
t.Run("x", func(t *T) {
t.Cleanup(func() { innerCleanup++ })
})
})
if innerCleanup != 1 {
t.Errorf("unexpected inner cleanup count; got %d want 1", innerCleanup)
}
if outerCleanup != 1 {
t.Errorf("unexpected outer cleanup count; got %d want 1", outerCleanup) // wrong upstream!
}
}
func TestCleanupParallelSubtests(t *T) {
ranCleanup := 0
t.Run("test", func(t *T) {
t.Cleanup(func() { ranCleanup++ })
t.Run("x", func(t *T) {
t.Parallel()
if ranCleanup > 0 {
t.Error("outer cleanup ran before parallel subtest")
}
})
})
if ranCleanup != 1 {
t.Errorf("unexpected cleanup count; got %d want 1", ranCleanup)
}
}
func TestNestedCleanup(t *T) {
ranCleanup := 0
t.Run("test", func(t *T) {
t.Cleanup(func() {
if ranCleanup != 2 {
t.Errorf("unexpected cleanup count in first cleanup: got %d want 2", ranCleanup)
}
ranCleanup++
})
t.Cleanup(func() {
if ranCleanup != 0 {
t.Errorf("unexpected cleanup count in second cleanup: got %d want 0", ranCleanup)
}
ranCleanup++
t.Cleanup(func() {
if ranCleanup != 1 {
t.Errorf("unexpected cleanup count in nested cleanup: got %d want 1", ranCleanup)
}
ranCleanup++
})
})
})
if ranCleanup != 3 {
t.Errorf("unexpected cleanup count: got %d want 3", ranCleanup)
}
}
-709
View File
@@ -1,709 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// This file has been modified for use by the TinyGo compiler.
// src: https://github.com/golang/go/blob/61bb56ad/src/testing/testing.go
// Package testing provides support for automated testing of Go packages.
package testing
import (
"bytes"
"context"
"errors"
"flag"
"fmt"
"io"
"io/fs"
"math/rand"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
)
// Testing flags.
var (
flagVerbose bool
flagShort bool
flagRunRegexp string
flagSkipRegexp string
flagShuffle string
flagCount int
)
var initRan bool
// Init registers testing flags. It has no effect if it has already run.
func Init() {
if initRan {
return
}
initRan = true
flag.BoolVar(&flagVerbose, "test.v", false, "verbose: print additional output")
flag.BoolVar(&flagShort, "test.short", false, "short: run smaller test suite to save time")
flag.StringVar(&flagRunRegexp, "test.run", "", "run: regexp of tests to run")
flag.StringVar(&flagSkipRegexp, "test.skip", "", "skip: regexp of tests to run")
flag.StringVar(&flagShuffle, "test.shuffle", "off", "shuffle: off, on, <numeric-seed>")
flag.IntVar(&flagCount, "test.count", 1, "run each test or benchmark `count` times")
initBenchmarkFlags()
}
// common holds the elements common between T and B and
// captures common methods such as Errorf.
type common struct {
output *logger
indent string
ran bool // Test or benchmark (or one of its subtests) was executed.
failed bool // Test or benchmark has failed.
skipped bool // Test of benchmark has been skipped.
cleanups []func() // optional functions to be called at the end of the test
finished bool // Test function has completed.
hasSub bool // TODO: should be atomic
parent *common
level int // Nesting depth of test or benchmark.
name string // Name of test or benchmark.
start time.Time // Time test or benchmark started
duration time.Duration
tempDir string
tempDirErr error
tempDirSeq int32
ctx context.Context
cancelCtx context.CancelFunc
}
type logger struct {
logToStdout bool
b bytes.Buffer
}
func (l *logger) Write(p []byte) (int, error) {
if l.logToStdout {
return os.Stdout.Write(p)
}
return l.b.Write(p)
}
func (l *logger) WriteTo(w io.Writer) (int64, error) {
if l.logToStdout {
// We've already been logging to stdout; nothing to do.
return 0, nil
}
return l.b.WriteTo(w)
}
func (l *logger) Len() int {
return l.b.Len()
}
// Short reports whether the -test.short flag is set.
func Short() bool {
return flagShort
}
// CoverMode reports what the test coverage mode is set to.
//
// Test coverage is not supported; this returns the empty string.
func CoverMode() string {
return ""
}
// Verbose reports whether the -test.v flag is set.
func Verbose() bool {
return flagVerbose
}
// String constant that is being set when running a test.
var testBinary string
// Testing returns whether the program was compiled as a test, using "tinygo
// test". It returns false when built using "tinygo build", "tinygo flash", etc.
func Testing() bool {
return testBinary == "1"
}
// flushToParent writes c.output to the parent after first writing the header
// with the given format and arguments.
func (c *common) flushToParent(testName, format string, args ...interface{}) {
if c.parent == nil {
// The fake top-level test doesn't want a FAIL or PASS banner.
// Not quite sure how this works upstream.
c.output.WriteTo(os.Stdout)
} else {
fmt.Fprintf(c.parent.output, format, args...)
c.output.WriteTo(c.parent.output)
}
}
// fmtDuration returns a string representing d in the form "87.00s".
func fmtDuration(d time.Duration) string {
return fmt.Sprintf("%.2fs", d.Seconds())
}
// TB is the interface common to T and B.
type TB interface {
Cleanup(func())
Context() context.Context
Error(args ...interface{})
Errorf(format string, args ...interface{})
Fail()
FailNow()
Failed() bool
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Helper()
Log(args ...interface{})
Logf(format string, args ...interface{})
Name() string
Setenv(key, value string)
Skip(args ...interface{})
SkipNow()
Skipf(format string, args ...interface{})
Skipped() bool
TempDir() string
}
var _ TB = (*T)(nil)
var _ TB = (*B)(nil)
// T is a type passed to Test functions to manage test state and support formatted test logs.
// Logs are accumulated during execution and dumped to standard output when done.
type T struct {
common
context *testContext // For running tests and subtests.
}
// Name returns the name of the running test or benchmark.
func (c *common) Name() string {
return c.name
}
func (c *common) setRan() {
if c.parent != nil {
c.parent.setRan()
}
c.ran = true
}
// Fail marks the function as having failed but continues execution.
func (c *common) Fail() {
c.failed = true
}
// Failed reports whether the function has failed.
func (c *common) Failed() bool {
failed := c.failed
return failed
}
// FailNow marks the function as having failed and stops its execution
// by calling runtime.Goexit (which then runs all deferred calls in the
// current goroutine).
func (c *common) FailNow() {
c.Fail()
c.finished = true
c.Error("FailNow is incomplete, requires runtime.Goexit()")
}
// log generates the output.
func (c *common) log(s string) {
// This doesn't print the same as in upstream go, but works for now.
if len(s) != 0 && s[len(s)-1] == '\n' {
s = s[:len(s)-1]
}
lines := strings.Split(s, "\n")
// First line.
fmt.Fprintf(c.output, "%s %s\n", c.indent, lines[0])
// More lines.
for _, line := range lines[1:] {
fmt.Fprintf(c.output, "%s %s\n", c.indent, line)
}
}
// Log formats its arguments using default formatting, analogous to Println,
// and records the text in the error log. For tests, the text will be printed only if
// the test fails or the -test.v flag is set. For benchmarks, the text is always
// printed to avoid having performance depend on the value of the -test.v flag.
func (c *common) Log(args ...interface{}) { c.log(fmt.Sprintln(args...)) }
// Logf formats its arguments according to the format, analogous to Printf, and
// records the text in the error log. A final newline is added if not provided. For
// tests, the text will be printed only if the test fails or the -test.v flag is
// set. For benchmarks, the text is always printed to avoid having performance
// depend on the value of the -test.v flag.
func (c *common) Logf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) }
// Error is equivalent to Log followed by Fail.
func (c *common) Error(args ...interface{}) {
c.log(fmt.Sprintln(args...))
c.Fail()
}
// Errorf is equivalent to Logf followed by Fail.
func (c *common) Errorf(format string, args ...interface{}) {
c.log(fmt.Sprintf(format, args...))
c.Fail()
}
// Fatal is equivalent to Log followed by FailNow.
func (c *common) Fatal(args ...interface{}) {
c.log(fmt.Sprintln(args...))
c.FailNow()
}
// Fatalf is equivalent to Logf followed by FailNow.
func (c *common) Fatalf(format string, args ...interface{}) {
c.log(fmt.Sprintf(format, args...))
c.FailNow()
}
// Skip is equivalent to Log followed by SkipNow.
func (c *common) Skip(args ...interface{}) {
c.log(fmt.Sprintln(args...))
c.SkipNow()
}
// Skipf is equivalent to Logf followed by SkipNow.
func (c *common) Skipf(format string, args ...interface{}) {
c.log(fmt.Sprintf(format, args...))
c.SkipNow()
}
// SkipNow marks the test as having been skipped and stops its execution
// by calling runtime.Goexit.
func (c *common) SkipNow() {
c.skip()
c.finished = true
c.Error("SkipNow is incomplete, requires runtime.Goexit()")
}
func (c *common) skip() {
c.skipped = true
}
// Skipped reports whether the test was skipped.
func (c *common) Skipped() bool {
return c.skipped
}
// Helper is not implemented, it is only provided for compatibility.
func (c *common) Helper() {
// Unimplemented.
}
// Cleanup registers a function to be called when the test (or subtest) and all its
// subtests complete. Cleanup functions will be called in last added,
// first called order.
func (c *common) Cleanup(f func()) {
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.
// The directory is automatically removed by Cleanup when the test and
// all its subtests complete.
// Each subsequent call to t.TempDir returns a unique directory;
// if the directory creation fails, TempDir terminates the test by calling Fatal.
func (c *common) TempDir() string {
// Use a single parent directory for all the temporary directories
// created by a test, each numbered sequentially.
var nonExistent bool
if c.tempDir == "" { // Usually the case with js/wasm
nonExistent = true
} else {
_, err := os.Stat(c.tempDir)
nonExistent = errors.Is(err, fs.ErrNotExist)
if err != nil && !nonExistent {
c.Fatalf("TempDir: %v", err)
}
}
if nonExistent {
c.Helper()
// Drop unusual characters (such as path separators or
// characters interacting with globs) from the directory name to
// avoid surprising os.MkdirTemp behavior.
mapper := func(r rune) rune {
if r < utf8.RuneSelf {
const allowed = "!#$%&()+,-.=@^_{}~ "
if '0' <= r && r <= '9' ||
'a' <= r && r <= 'z' ||
'A' <= r && r <= 'Z' {
return r
}
if strings.ContainsRune(allowed, r) {
return r
}
} else if unicode.IsLetter(r) || unicode.IsNumber(r) {
return r
}
return -1
}
pattern := strings.Map(mapper, c.Name())
c.tempDir, c.tempDirErr = os.MkdirTemp("", pattern)
if c.tempDirErr == nil {
c.Cleanup(func() {
if err := os.RemoveAll(c.tempDir); err != nil {
c.Errorf("TempDir RemoveAll cleanup: %v", err)
}
})
}
}
if c.tempDirErr != nil {
c.Fatalf("TempDir: %v", c.tempDirErr)
}
seq := c.tempDirSeq
c.tempDirSeq++
dir := fmt.Sprintf("%s%c%03d", c.tempDir, os.PathSeparator, seq)
if err := os.Mkdir(dir, 0777); err != nil {
c.Fatalf("TempDir: %v", err)
}
return dir
}
// Setenv calls os.Setenv(key, value) and uses Cleanup to
// restore the environment variable to its original value
// after the test.
func (c *common) Setenv(key, value string) {
prevValue, ok := os.LookupEnv(key)
if err := os.Setenv(key, value); err != nil {
c.Fatalf("cannot set environment variable: %v", err)
}
if ok {
c.Cleanup(func() {
os.Setenv(key, prevValue)
})
} else {
c.Cleanup(func() {
os.Unsetenv(key)
})
}
}
// Chdir calls os.Chdir(dir) and uses Cleanup to restore the current
// working directory to its original value after the test. On Unix, it
// also sets PWD environment variable for the duration of the test.
//
// Because Chdir affects the whole process, it cannot be used
// in parallel tests or tests with parallel ancestors.
func (c *common) Chdir(dir string) {
// Note: function copied from the Go 1.24.0 source tree.
oldwd, err := os.Open(".")
if err != nil {
c.Fatal(err)
}
if err := os.Chdir(dir); err != nil {
c.Fatal(err)
}
// On POSIX platforms, PWD represents “an absolute pathname of the
// current working directory.” Since we are changing the working
// directory, we should also set or update PWD to reflect that.
switch runtime.GOOS {
case "windows", "plan9":
// Windows and Plan 9 do not use the PWD variable.
default:
if !filepath.IsAbs(dir) {
dir, err = os.Getwd()
if err != nil {
c.Fatal(err)
}
}
c.Setenv("PWD", dir)
}
c.Cleanup(func() {
err := oldwd.Chdir()
oldwd.Close()
if err != nil {
// It's not safe to continue with tests if we can't
// get back to the original working directory. Since
// we are holding a dirfd, this is highly unlikely.
panic("testing.Chdir: " + err.Error())
}
})
}
// runCleanup is called at the end of the test.
func (c *common) runCleanup() {
for {
var cleanup func()
if len(c.cleanups) > 0 {
last := len(c.cleanups) - 1
cleanup = c.cleanups[last]
c.cleanups = c.cleanups[:last]
}
if cleanup == nil {
return
}
if c.cancelCtx != nil {
c.cancelCtx()
}
cleanup()
}
}
// Parallel is not implemented, it is only provided for compatibility.
func (t *T) Parallel() {
// Unimplemented.
}
// InternalTest is a reference to a test that should be called during a test suite run.
type InternalTest struct {
Name string
F func(*T)
}
func tRunner(t *T, fn func(t *T)) {
defer func() {
t.runCleanup()
}()
// Run the test.
t.start = time.Now()
fn(t)
t.duration += time.Since(t.start) // TODO: capture cleanup time, too.
t.report() // Report after all subtests have finished.
if t.parent != nil && !t.hasSub {
t.setRan()
}
}
// Run runs f as a subtest of t called name. It waits until the subtest is finished
// and returns whether the subtest succeeded.
func (t *T) Run(name string, f func(t *T)) bool {
t.hasSub = true
testName, ok, _ := t.context.match.fullName(&t.common, name)
if !ok {
return true
}
// Create a subtest.
ctx, cancelCtx := context.WithCancel(context.Background())
sub := T{
common: common{
output: &logger{logToStdout: flagVerbose},
name: testName,
parent: &t.common,
level: t.level + 1,
ctx: ctx,
cancelCtx: cancelCtx,
},
context: t.context,
}
if t.level > 0 {
sub.indent = sub.indent + " "
}
if flagVerbose {
fmt.Fprintf(t.output, "=== RUN %s\n", sub.name)
}
tRunner(&sub, f)
return !sub.failed
}
// Deadline reports the time at which the test binary will have
// exceeded the timeout specified by the -timeout flag.
//
// The ok result is false if the -timeout flag indicates “no timeout” (0).
// For now tinygo always return 0, false.
//
// Not Implemented.
func (t *T) Deadline() (deadline time.Time, ok bool) {
deadline = t.context.deadline
return deadline, !deadline.IsZero()
}
// testContext holds all fields that are common to all tests. This includes
// synchronization primitives to run at most *parallel tests.
type testContext struct {
match *matcher
deadline time.Time
}
func newTestContext(m *matcher) *testContext {
return &testContext{
match: m,
}
}
// M is a test suite.
type M struct {
// tests is a list of the test names to execute
Tests []InternalTest
Benchmarks []InternalBenchmark
deps testDeps
// value to pass to os.Exit, the outer test func main
// harness calls os.Exit with this code. See #34129.
exitCode int
}
type testDeps interface {
MatchString(pat, str string) (bool, error)
}
func (m *M) shuffle() error {
var n int64
if flagShuffle == "on" {
n = time.Now().UnixNano()
} else {
var err error
n, err = strconv.ParseInt(flagShuffle, 10, 64)
if err != nil {
m.exitCode = 2
return fmt.Errorf(`testing: -shuffle should be "off", "on", or a valid integer: %v`, err)
}
}
fmt.Println("-test.shuffle", n)
rng := rand.New(rand.NewSource(n))
rng.Shuffle(len(m.Tests), func(i, j int) { m.Tests[i], m.Tests[j] = m.Tests[j], m.Tests[i] })
rng.Shuffle(len(m.Benchmarks), func(i, j int) { m.Benchmarks[i], m.Benchmarks[j] = m.Benchmarks[j], m.Benchmarks[i] })
return nil
}
// Run runs the tests. It returns an exit code to pass to os.Exit.
func (m *M) Run() (code int) {
defer func() {
code = m.exitCode
}()
if !flag.Parsed() {
flag.Parse()
}
if flagShuffle != "off" {
if err := m.shuffle(); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
}
testRan, testOk := runTests(m.deps.MatchString, m.Tests)
if !testRan && *matchBenchmarks == "" {
fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
}
if !testOk || !runBenchmarks(m.deps.MatchString, m.Benchmarks) {
fmt.Println("FAIL")
m.exitCode = 1
} else {
fmt.Println("PASS")
m.exitCode = 0
}
return
}
func runTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ran, ok bool) {
ok = true
ctx := newTestContext(newMatcher(matchString, flagRunRegexp, "-test.run", flagSkipRegexp))
runCtx, cancelCtx := context.WithCancel(context.Background())
t := &T{
common: common{
output: &logger{logToStdout: flagVerbose},
ctx: runCtx,
cancelCtx: cancelCtx,
},
context: ctx,
}
for i := 0; i < flagCount; i++ {
tRunner(t, func(t *T) {
for _, test := range tests {
t.Run(test.Name, test.F)
ok = ok && !t.Failed()
}
})
}
return t.ran, ok
}
func (t *T) report() {
dstr := fmtDuration(t.duration)
format := t.indent + "--- %s: %s (%s)\n"
if t.Failed() {
if t.parent != nil {
t.parent.failed = true
}
t.flushToParent(t.name, format, "FAIL", t.name, dstr)
} else if flagVerbose {
if t.Skipped() {
t.flushToParent(t.name, format, "SKIP", t.name, dstr)
} else {
t.flushToParent(t.name, format, "PASS", t.name, dstr)
}
}
}
// AllocsPerRun returns the average number of allocations during calls to f.
// Although the return value has type float64, it will always be an integral
// value.
//
// Not implemented.
func AllocsPerRun(runs int, f func()) (avg float64) {
f()
for i := 0; i < runs; i++ {
f()
}
return 0
}
type InternalExample struct {
Name string
F func()
Output string
Unordered bool
}
// MainStart is meant for use by tests generated by 'go test'.
// It is not meant to be called directly and is not subject to the Go 1 compatibility document.
// It may change signature from release to release.
func MainStart(deps interface{}, tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) *M {
Init()
return &M{
Tests: tests,
Benchmarks: benchmarks,
deps: deps.(testDeps),
}
}
// A fake regexp matcher.
// Inflexible, but saves 50KB of flash and 50KB of RAM per -size full,
// and lets tests pass on cortex-m.
func fakeMatchString(pat, str string) (bool, error) {
if pat == ".*" {
return true, nil
}
matched := strings.Contains(str, pat)
return matched, nil
}
-204
View File
@@ -1,204 +0,0 @@
//go:build !windows
// TODO: implement readdir for windows, then enable this file
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testing_test
import (
"errors"
"io/fs"
"os"
"path/filepath"
"runtime"
"testing"
)
// This is exactly what a test would do without a TestMain.
// It's here only so that there is at least one package in the
// standard library with a TestMain, so that code is executed.
func TestMain(m *testing.M) {
os.Exit(m.Run())
}
func TestTempDirInCleanup(t *testing.T) {
if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" {
t.Log("Skipping. TODO: implement RemoveAll for wasi")
return
}
var dir string
t.Run("test", func(t *testing.T) {
t.Cleanup(func() {
dir = t.TempDir()
})
_ = t.TempDir()
})
fi, err := os.Stat(dir)
if fi != nil {
t.Fatalf("Directory %q from user Cleanup still exists", dir)
}
if !errors.Is(err, fs.ErrNotExist) {
t.Fatalf("Unexpected error: %v", err)
}
}
func TestTempDirInBenchmark(t *testing.T) {
testing.Benchmark(func(b *testing.B) {
if !b.Run("test", func(b *testing.B) {
// Add a loop so that the test won't fail. See issue 38677.
for i := 0; i < b.N; i++ {
_ = b.TempDir()
}
}) {
t.Fatal("Sub test failure in a benchmark")
}
})
}
func TestTempDir(t *testing.T) {
if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" {
t.Log("Skipping. TODO: implement RemoveAll for wasi")
return
}
testTempDir(t)
t.Run("InSubtest", testTempDir)
t.Run("test/subtest", testTempDir)
t.Run("test\\subtest", testTempDir)
t.Run("test:subtest", testTempDir)
t.Run("test/..", testTempDir)
t.Run("../test", testTempDir)
t.Run("test[]", testTempDir)
t.Run("test*", testTempDir)
t.Run("äöüéè", testTempDir)
}
func testTempDir(t *testing.T) {
dirCh := make(chan string, 1)
t.Cleanup(func() {
// Verify directory has been removed.
select {
case dir := <-dirCh:
fi, err := os.Stat(dir)
if errors.Is(err, fs.ErrNotExist) {
// All good
return
}
if err != nil {
t.Fatal(err)
}
t.Errorf("directory %q still exists: %v, isDir=%v", dir, fi, fi.IsDir())
default:
if !t.Failed() {
t.Fatal("never received dir channel")
}
}
})
dir := t.TempDir()
if dir == "" {
t.Fatal("expected dir")
}
dir2 := t.TempDir()
if dir == dir2 {
t.Fatal("subsequent calls to TempDir returned the same directory")
}
if filepath.Dir(dir) != filepath.Dir(dir2) {
t.Fatalf("calls to TempDir do not share a parent; got %q, %q", dir, dir2)
}
dirCh <- dir
fi, err := os.Stat(dir)
if err != nil {
t.Fatal(err)
}
if !fi.IsDir() {
t.Errorf("dir %q is not a dir", dir)
}
files, err := os.ReadDir(dir)
if err != nil {
t.Fatal(err)
}
if len(files) > 0 {
t.Errorf("unexpected %d files in TempDir: %v", len(files), files)
}
glob := filepath.Join(dir, "*.txt")
if _, err := filepath.Glob(glob); err != nil {
t.Error(err)
}
err = os.Remove(dir)
if err != nil {
t.Errorf("unexpected files in TempDir")
}
}
func TestSetenv(t *testing.T) {
tests := []struct {
name string
key string
initialValueExists bool
initialValue string
newValue string
}{
{
name: "initial value exists",
key: "GO_TEST_KEY_1",
initialValueExists: true,
initialValue: "111",
newValue: "222",
},
{
name: "initial value exists but empty",
key: "GO_TEST_KEY_2",
initialValueExists: true,
initialValue: "",
newValue: "222",
},
{
name: "initial value is not exists",
key: "GO_TEST_KEY_3",
initialValueExists: false,
initialValue: "",
newValue: "222",
},
}
for _, test := range tests {
if test.initialValueExists {
if err := os.Setenv(test.key, test.initialValue); err != nil {
t.Fatalf("unable to set env: got %v", err)
}
} else {
os.Unsetenv(test.key)
}
t.Run(test.name, func(t *testing.T) {
t.Setenv(test.key, test.newValue)
if os.Getenv(test.key) != test.newValue {
t.Fatalf("unexpected value after t.Setenv: got %s, want %s", os.Getenv(test.key), test.newValue)
}
})
got, exists := os.LookupEnv(test.key)
if got != test.initialValue {
t.Fatalf("unexpected value after t.Setenv cleanup: got %s, want %s", got, test.initialValue)
}
if exists != test.initialValueExists {
t.Fatalf("unexpected value after t.Setenv cleanup: got %t, want %t", exists, test.initialValueExists)
}
}
}
func TestTesting(t *testing.T) {
if !testing.Testing() {
t.Error("Expected testing.Testing() to return true while in a test")
}
}
-21
View File
@@ -1,21 +0,0 @@
{
"inherits": ["xtensa"],
"cpu": "esp32s3",
"features": "+atomctl,+bool,+clamps,+coprocessor,+debug,+density,+div32,+esp32s3,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+minmax,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed",
"build-tags": ["esp32s3", "esp"],
"scheduler": "tasks",
"serial": "uart",
"linker": "ld.lld",
"default-stack-size": 2048,
"rtlib": "compiler-rt",
"libc": "picolibc",
"linkerscript": "targets/esp32s3.ld",
"extra-files": [
"src/device/esp/esp32.S",
"src/internal/task/task_stack_esp32.S"
],
"binary-format": "esp32s3",
"flash-command": "esptool.py --chip=esp32s3 --port {port} write_flash 0x0000 {bin} -ff 80m -fm dout",
"emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw",
"gdb": ["xtensa-esp32-elf-gdb"]
}
-208
View File
@@ -1,208 +0,0 @@
/* Linker script for the ESP32-S3 */
MEMORY
{
/* Note: DRAM and IRAM below are actually in the same 416K address space. */
DRAM (rw) : ORIGIN = 0x3FC88000, LENGTH = 416K /* Internal SRAM 1 (data bus) */
IRAM (x) : ORIGIN = 0x40370000, LENGTH = 416K /* Internal SRAM 1 (instruction bus) */
/* Note: DROM and IROM below are actually in the same 32M address space. */
DROM (r) : ORIGIN = 0x3C000000, LENGTH = 32M /* Data bus (read-only) */
IROM (rx) : ORIGIN = 0x42000000, LENGTH = 32M /* Instruction bus */
}
/* The entry point. It is set in the image flashed to the chip, so must be
* defined.
*/
ENTRY(call_start_cpu0)
SECTIONS
{
/* Put the stack at the bottom of DRAM, so that the application will
* crash on stack overflow instead of silently corrupting memory.
* See: http://blog.japaric.io/stack-overflow-protection/ */
.stack (NOLOAD) :
{
. = ALIGN(16);
. += _stack_size;
_stack_top = .;
} >DRAM
/* Constant literals and code. Loaded into IRAM for now. Eventually, most
* code should be executed directly from flash.
* Note that literals must be before code for the l32r instruction to work.
*/
.text.call_start_cpu0 : ALIGN(4)
{
*(.literal.call_start_cpu0)
*(.text.call_start_cpu0)
} >IRAM AT >DRAM
/* All other code and literals */
.text : ALIGN(4)
{
*(.literal .text)
*(.literal.* .text.*)
*(.text)
*(.text.*)
} >IRAM AT >DRAM
/* Constant global variables.
* They are loaded in DRAM for ease of use. Eventually they should be stored
* in flash and loaded directly from there but they're kept in RAM to make
* sure they can always be accessed (even in interrupts).
*/
.rodata : ALIGN(4)
{
*(.rodata)
*(.rodata.*)
} >DRAM
/* Mutable global variables.
*/
.data : ALIGN(4)
{
_sdata = ABSOLUTE(.);
*(.data)
*(.data.*)
_edata = ABSOLUTE(.);
} >DRAM
/* Check that the boot ROM stack (for the APP CPU) does not overlap with the
* data that is loaded by the boot ROM. There may be ways to avoid this
* issue if it occurs in practice.
* The magic value here is _stack_sentry in the boot ROM ELF file.
*/
ASSERT(_edata < 0x3ffe1320, "the .data section overlaps with the stack used by the boot ROM, possibly causing corruption at startup")
/* Global variables that are mutable and zero-initialized.
* These must be zeroed at startup (unlike data, which is loaded by the
* bootloader).
*/
.bss (NOLOAD) : ALIGN(4)
{
. = ALIGN (4);
_sbss = ABSOLUTE(.);
*(.bss)
*(.bss.*)
. = ALIGN (4);
_ebss = ABSOLUTE(.);
} >DRAM
}
/* For the garbage collector.
*/
_globals_start = _sdata;
_globals_end = _ebss;
_heap_start = _ebss;
_heap_end = ORIGIN(DRAM) + LENGTH(DRAM);
_stack_size = 4K;
/* From ESP-IDF:
* components/esp_rom/esp32/ld/esp32.rom.newlib-funcs.ld
* This is the subset that is sometimes used by LLVM during codegen, and thus
* must always be present.
*/
memset = 0x400011e8;
memcpy = 0x400011f4;
memmove = 0x40001200;
memcmp = 0x4000120c;
/* From ESP-IDF:
* components/esp_rom/esp32/ld/esp32.rom.libgcc.ld
* These are called from LLVM during codegen. The original license is Apache
* 2.0, but I believe that a list of function names and addresses can't really
* be copyrighted.
*/
__absvdi2 = 0x4000216c;
__absvsi2 = 0x40002178;
__adddf3 = 0x40002184;
__addsf3 = 0x40002190;
__addvdi3 = 0x4000219c;
__addvsi3 = 0x400021a8;
__ashldi3 = 0x400021b4;
__ashrdi3 = 0x400021c0;
__bswapdi2 = 0x400021cc;
__bswapsi2 = 0x400021d8;
__clear_cache = 0x400021e4;
__clrsbdi2 = 0x400021f0;
__clrsbsi2 = 0x400021fc;
__clzdi2 = 0x40002208;
__clzsi2 = 0x40002214;
__cmpdi2 = 0x40002220;
__ctzdi2 = 0x4000222c;
__ctzsi2 = 0x40002238;
__divdc3 = 0x40002244;
__divdf3 = 0x40002250;
__divdi3 = 0x4000225c;
__divsc3 = 0x40002268;
__divsf3 = 0x40002274;
__divsi3 = 0x40002280;
__eqdf2 = 0x4000228c;
__eqsf2 = 0x40002298;
__extendsfdf2 = 0x400022a4;
__ffsdi2 = 0x400022b0;
__ffssi2 = 0x400022bc;
__fixdfdi = 0x400022c8;
__fixdfsi = 0x400022d4;
__fixsfdi = 0x400022e0;
__fixsfsi = 0x400022ec;
__fixunsdfsi = 0x400022f8;
__fixunssfdi = 0x40002304;
__fixunssfsi = 0x40002310;
__floatdidf = 0x4000231c;
__floatdisf = 0x40002328;
__floatsidf = 0x40002334;
__floatsisf = 0x40002340;
__floatundidf = 0x4000234c;
__floatundisf = 0x40002358;
__floatunsidf = 0x40002364;
__floatunsisf = 0x40002370;
__gcc_bcmp = 0x4000237c;
__gedf2 = 0x40002388;
__gesf2 = 0x40002394;
__gtdf2 = 0x400023a0;
__gtsf2 = 0x400023ac;
__ledf2 = 0x400023b8;
__lesf2 = 0x400023c4;
__lshrdi3 = 0x400023d0;
__ltdf2 = 0x400023dc;
__ltsf2 = 0x400023e8;
__moddi3 = 0x400023f4;
__modsi3 = 0x40002400;
__muldc3 = 0x4000240c;
__muldf3 = 0x40002418;
__muldi3 = 0x40002424;
__mulsc3 = 0x40002430;
__mulsf3 = 0x4000243c;
__mulsi3 = 0x40002448;
__mulvdi3 = 0x40002454;
__mulvsi3 = 0x40002460;
__nedf2 = 0x4000246c;
__negdf2 = 0x40002478;
__negdi2 = 0x40002484;
__negsf2 = 0x40002490;
__negvdi2 = 0x4000249c;
__negvsi2 = 0x400024a8;
__nesf2 = 0x400024b4;
__paritysi2 = 0x400024c0;
__popcountdi2 = 0x400024cc;
__popcountsi2 = 0x400024d8;
__powidf2 = 0x400024e4;
__powisf2 = 0x400024f0;
__subdf3 = 0x400024fc;
__subsf3 = 0x40002508;
__subvdi3 = 0x40002514;
__subvsi3 = 0x40002520;
__truncdfsf2 = 0x4000252c;
__ucmpdi2 = 0x40002538;
__udivdi3 = 0x40002544;
__udivmoddi4 = 0x40002550;
__udivsi3 = 0x4000255c;
__udiv_w_sdiv = 0x40002568;
__umoddi3 = 0x40002574;
__umodsi3 = 0x40002580;
__unorddf2 = 0x4000258c;
__unordsf2 = 0x40002598;
-11
View File
@@ -1,11 +0,0 @@
{
"inherits": [
"rp2350b"
],
"build-tags": ["pico2_ice"],
"serial-port": ["2e8a:000A"],
"default-stack-size": 8192,
"ldflags": [
"--defsym=__flash_size=4M"
]
}
-4
View File
@@ -1,4 +0,0 @@
{
"inherits": ["esp32s3"],
"build-tags": ["xiao_esp32s3"]
}
+1 -5
View File
@@ -89,7 +89,6 @@
- repo: github.com/dgryski/go-spooky
- repo: github.com/dgryski/go-spritz
- repo: github.com/dgryski/go-timewindow
- repo: github.com/dgryski/go-tinylz
- repo: github.com/dgryski/go-tinymap
- repo: github.com/dgryski/go-trigram
- repo: github.com/dgryski/go-twine
@@ -270,7 +269,7 @@
- repo: github.com/cloudflare/bm
- repo: github.com/cloudflare/bn256
tags: generic
- repo: github.com/cloudflare/ahocorasick
#- repo: cloudflare/ahocorasick # interp timeout building regexps in test
#- repo: github.com/google/open-location-code # unfortunately, Go discards the test files
# version: master
# skipwasi: true # needs file access
@@ -282,6 +281,3 @@
version: v2
- repo: github.com/soypat/mu8
- repo: github.com/brandondube/pctl
- repo: github.com/julienschmidt/httprouter
- repo: github.com/openhistogram/circonusllhist
skipwasi: true # math.MaxInt64 (untyped int constant 9223372036854775807) overflows int
+32 -46
View File
@@ -4,11 +4,6 @@ import (
"tinygo.org/x/go-llvm"
)
// This is somewhat ugly to access through the API.
// https://github.com/llvm/llvm-project/blob/94ebcfd16dac67486bae624f74e1c5c789448bae/llvm/include/llvm/Support/ModRef.h#L62
// https://github.com/llvm/llvm-project/blob/94ebcfd16dac67486bae624f74e1c5c789448bae/llvm/include/llvm/Support/ModRef.h#L87
const shiftExcludeArgMem = 2
// MakeGCStackSlots converts all calls to runtime.trackPointer to explicit
// stores to stack slots that are scannable by the GC.
func MakeGCStackSlots(mod llvm.Module) bool {
@@ -41,49 +36,20 @@ func MakeGCStackSlots(mod llvm.Module) bool {
defer targetData.Dispose()
uintptrType := ctx.IntType(targetData.PointerSize() * 8)
// All functions that call runtime.alloc needs stack objects.
trackFuncs := map[llvm.Value]struct{}{}
markParentFunctions(trackFuncs, alloc)
// External functions may indirectly suspend the goroutine or perform a heap allocation.
// Their callers should get stack objects.
memAttr := llvm.AttributeKindID("memory")
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if _, ok := trackFuncs[fn]; ok {
continue // already found
}
if !fn.FirstBasicBlock().IsNil() {
// This is not an external function.
continue
}
if fn == trackPointer {
// Manually exclude trackPointer.
continue
}
mem := fn.GetEnumFunctionAttribute(memAttr)
if !mem.IsNil() && mem.GetEnumValue()>>shiftExcludeArgMem == 0 {
// This does not access non-argument memory.
// Exclude it.
continue
}
// The callers need stack objects.
markParentFunctions(trackFuncs, fn)
}
// Look at all other functions to see whether they contain function pointer
// Look at *all* functions to see whether they are free of function pointer
// calls.
// This takes less than 5ms for ~100kB of WebAssembly but would perhaps be
// faster when written in C++ (to avoid the CGo overhead).
funcsWithFPCall := map[llvm.Value]struct{}{}
n := 0
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if _, ok := trackFuncs[fn]; ok {
n++
if _, ok := funcsWithFPCall[fn]; ok {
continue // already found
}
scanBody:
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for call := bb.FirstInstruction(); !call.IsNil(); call = llvm.NextInstruction(call) {
done := false
for bb := fn.FirstBasicBlock(); !bb.IsNil() && !done; bb = llvm.NextBasicBlock(bb) {
for call := bb.FirstInstruction(); !call.IsNil() && !done; call = llvm.NextInstruction(call) {
if call.IsACallInst().IsNil() {
continue // only looking at calls
}
@@ -91,13 +57,33 @@ func MakeGCStackSlots(mod llvm.Module) bool {
if !called.IsAFunction().IsNil() {
continue // only looking for function pointers
}
trackFuncs[fn] = struct{}{}
markParentFunctions(trackFuncs, fn)
break scanBody
funcsWithFPCall[fn] = struct{}{}
markParentFunctions(funcsWithFPCall, fn)
done = true
}
}
}
// Determine which functions need stack objects. Many leaf functions don't
// need it: it only causes overhead for them.
// Actually, in one test it was only able to eliminate stack object from 12%
// of functions that had a call to runtime.trackPointer (8 out of 68
// functions), so this optimization is not as big as it may seem.
allocatingFunctions := map[llvm.Value]struct{}{} // set of allocating functions
// Work from runtime.alloc and trace all parents to check which functions do
// a heap allocation (and thus which functions do not).
markParentFunctions(allocatingFunctions, alloc)
// Also trace all functions that call a function pointer.
for fn := range funcsWithFPCall {
// Assume that functions that call a function pointer do a heap
// allocation as a conservative guess because the called function might
// do a heap allocation.
allocatingFunctions[fn] = struct{}{}
markParentFunctions(allocatingFunctions, fn)
}
// Collect some variables used below in the loop.
stackChainStart := mod.NamedGlobal("runtime.stackChainStart")
if stackChainStart.IsNil() {
@@ -124,7 +110,7 @@ func MakeGCStackSlots(mod llvm.Module) bool {
// Pick the parent function.
fn := call.InstructionParent().Parent()
if _, ok := trackFuncs[fn]; !ok {
if _, ok := allocatingFunctions[fn]; !ok {
// This function nor any of the functions it calls (recursively)
// allocate anything from the heap, so it will not trigger a garbage
// collection cycle. Thus, it does not need to track local pointer

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