machine: make sure DMA buffers do not escape unnecessarily

Writing the pointer of a buffer to memory-mapped I/O will normally cause
it to escape, which forces the compiler to heap-allocate the buffer. But
we do know how long the value stays alive, so we can tell the compiler
to keep it alive exactly until it is not needed anymore - and tell it to
not treat the pointer-to-uintptr cast as escaping.
This commit is contained in:
Ayke van Laethem
2025-06-20 09:48:25 +02:00
committed by Ron Evans
parent 5ae8fd1f6f
commit b203314c2f
7 changed files with 118 additions and 17 deletions
+36 -8
View File
@@ -1,11 +1,16 @@
package main
import (
"runtime/volatile"
"unsafe"
)
func main() {
n1 := 5
derefInt(&n1)
// This should eventually be modified to not escape.
n2 := 6 // OUT: object allocated on the heap: escapes at line 9
n2 := 6 // OUT: object allocated on the heap: escapes at line 14
returnIntPtr(&n2)
s1 := make([]int, 3)
@@ -15,7 +20,7 @@ func main() {
readIntSlice(s2[:])
// This should also be modified to not escape.
s3 := make([]int, 3) // OUT: object allocated on the heap: escapes at line 19
s3 := make([]int, 3) // OUT: object allocated on the heap: escapes at line 24
returnIntSlice(s3)
useSlice(make([]int, getUnknownNumber())) // OUT: object allocated on the heap: size is not constant
@@ -23,14 +28,14 @@ func main() {
s4 := make([]byte, 300) // OUT: object allocated on the heap: object size 300 exceeds maximum stack allocation size 256
readByteSlice(s4)
s5 := make([]int, 4) // OUT: object allocated on the heap: escapes at line 27
s5 := make([]int, 4) // OUT: object allocated on the heap: escapes at line 32
_ = append(s5, 5)
s6 := make([]int, 3)
s7 := []int{1, 2, 3}
copySlice(s6, s7)
c1 := getComplex128() // OUT: object allocated on the heap: escapes at line 34
c1 := getComplex128() // OUT: object allocated on the heap: escapes at line 39
useInterface(c1)
n3 := 5
@@ -38,13 +43,13 @@ func main() {
return n3
}()
callVariadic(3, 5, 8) // OUT: object allocated on the heap: escapes at line 41
callVariadic(3, 5, 8) // OUT: object allocated on the heap: escapes at line 46
s8 := []int{3, 5, 8} // OUT: object allocated on the heap: escapes at line 44
s8 := []int{3, 5, 8} // OUT: object allocated on the heap: escapes at line 49
callVariadic(s8...)
n4 := 3 // OUT: object allocated on the heap: escapes at line 48
n5 := 7 // OUT: object allocated on the heap: escapes at line 48
n4 := 3 // OUT: object allocated on the heap: escapes at line 53
n5 := 7 // OUT: object allocated on the heap: escapes at line 53
func() {
n4 = n5
}()
@@ -58,6 +63,19 @@ func main() {
var rbuf [5]rune
s = string(rbuf[:])
println(s)
// Unsafe usage of DMA buffers: the compiler thinks this buffer won't be
// used anymore after the volatile store.
var dmaBuf1 [4]byte
pseudoVolatile.Set(uint32(unsafeNoEscape(unsafe.Pointer(&dmaBuf1[0]))))
// Safe usage of DMA buffers: keep the buffer alive until it is no longer
// needed, but don't mark it as needing to be heap allocated. The compiler
// will keep the buffer stack allocated if possible.
var dmaBuf2 [4]byte
pseudoVolatile.Set(uint32(unsafeNoEscape(unsafe.Pointer(&dmaBuf2[0]))))
// ...use the buffer in the DMA peripheral
keepAliveNoEscape(unsafe.Pointer(&dmaBuf2[0]))
}
func derefInt(x *int) int {
@@ -93,3 +111,13 @@ func useInterface(interface{})
func callVariadic(...int)
func useSlice([]int)
// See the function with the same name in the machine package.
//
//go:linkname unsafeNoEscape machine.unsafeNoEscape
func unsafeNoEscape(ptr unsafe.Pointer) uintptr
//go:linkname keepAliveNoEscape machine.keepAliveNoEscape
func keepAliveNoEscape(ptr unsafe.Pointer)
var pseudoVolatile volatile.Register32