The demo code: https://github.com/digitalentity/tinygo_gotchas/tree/master/src/alloc_woes
There is a heap alloc in main.go at line 12:
func PerformMath() vec3.T {
a := vec3.T{1, 2, 3}
b := vec3.T{4, 5, 6} // <<< TinyGo allocates this on heap
c := b.Scale(0.5)
return vec3.Cross(&a, c)
}
The vec3.Cross creates and returns a new object, making a, b and c visibility scope only limited to the PerformMath():
func Cross(a, b *T) T {
return T{
a[1]*b[2] - a[2]*b[1],
a[2]*b[0] - a[0]*b[2],
a[0]*b[1] - a[1]*b[0],
}
}
Building with -print-allocs=. confirms the unnecessary heap allocation:
$ make build-alloc-woes
tinygo version 0.41.1 linux/amd64 (using go version go1.25.0 and LLVM version 20.1.1)
toolchain/tinygo/bin/tinygo build -size=short -opt=1 -panic=trap -gc=conservative -scheduler=tasks -print-allocs=. -target=nucleo-f722ze -o build/demo-.hex ./src/alloc_woes
mode: set
/home/ksharlaimov/dev/helvionics/tinygo_gotchas/src/alloc_woes/main.go:12.1,12.54 1 0
/home/ksharlaimov/.cache/tinygo/goroot-4f69c74a8da2c4abfdf60ed68a17fdb99d38c77006ea7da25c73db3862d5a0c8/src/runtime/baremetal.go:44.1,44.24 1 0
/home/ksharlaimov/.cache/tinygo/goroot-4f69c74a8da2c4abfdf60ed68a17fdb99d38c77006ea7da25c73db3862d5a0c8/src/internal/task/task_stack.go:43.1,43.39 1 0
/home/ksharlaimov/.cache/tinygo/goroot-4f69c74a8da2c4abfdf60ed68a17fdb99d38c77006ea7da25c73db3862d5a0c8/src/internal/task/task_stack.go:75.1,75.13 1 0
code data bss | flash ram
13560 1684 4324 | 15244 6008
A test of PerformMath() function with testing.AllocsPerRun says no allocations:
func TestPerformMathAllocations(t *testing.T) {
allocs := testing.AllocsPerRun(100, func() {
PerformMath()
})
if allocs > 0 {
t.Errorf("PerformMath allocated %f times, expected 0", allocs)
}
}
The concern here is
- Unnecessary heap allocation and GC pressure, making it hard to write predictable latency and memory usage code.
- Inconsistent escape analysis behavior between TinyGo compiler and testing environment.
The demo code: https://github.com/digitalentity/tinygo_gotchas/tree/master/src/alloc_woes
There is a heap alloc in main.go at line 12:
The
vec3.Crosscreates and returns a new object, makinga,bandcvisibility scope only limited to thePerformMath():Building with
-print-allocs=.confirms the unnecessary heap allocation:A test of
PerformMath()function withtesting.AllocsPerRunsays no allocations:The concern here is