-
Notifications
You must be signed in to change notification settings - Fork 1
Description
The documentation says:
// WARNING: If the returned pointer will be cast to a struct with fields // containing pointers, then data stored in those specific // fields must be cleared before casting.
However this is wrong, you can have whatever you want in there and it will never crash by itself.
This links to golang/go#76352 (comment) but this message is does not apply to what you are doing here nor to what golang/go#76352's original post is doing either.
The critical part is this:
after the memory is typed
From golang/go#76352:
func main() { // "calloc" a block of contiguous memory. Great if this was a "malloc". block := [unsafe.Sizeof(B{})]byte{} // array, not slice // Pretend it is a valid B struct newB := (*B)(unsafe.Pointer(&block)) newB.car = "new car" newB.year = 2022 }
And your package documentation:
// Example: // // type Person struct { // name string // age int // phone *int // } // // ptr := Malloc(unsafe.Sizeof(Person{}), false, FindPointerFields(reflect.TypeFor[Person]())) // p := *(*Person)(ptr)
I'll risk myself to say that golang/go#76352's OP and yourself believe casting an unsafe.Pointer to *T types the memory as a T.
In other words I think you might describe the GC as looking in your program, finding a *T value on the stack, and then going to follow this point to scan the pointers in the T type.
And unsafe never does this.
It works is the other way around, mallocgc types the memory:
//go:linkname mallocgc runtime.mallocgc
func mallocgc(size uintptr, typ uintptr, needzero bool) unsafe.PointerWhen calling mallocgc the compiler generate the typ argument as a pointer to a type descriptor.
The type descriptor contains the offset of pointers.
mallocgc then uses this to setup metadata in the heap data structures saying which group of 8 or 4 bytes contain pointers.
But because you use a nil *_type pointer the GC thinks you are allocating a type without pointers.
This cause your allocation to be moved to to special part of heap called NoScan.
The idea is during GC we do not need to scan the content of types like [64]byte, we know by definition it cannot ever point to anything.
The GC the applies an optimization, objects in no scan heap tables are never moved in the grey set, they jump from the white set to the black set instantly.
The thought is that if a type does not contain pointers we should never bother to scan it.
(see tri-coloring: https://pusher.github.io/tricolor-gc-visualization/)
This optimization ensures your object are never scanned, thus the GC would never crash because of garbage data remaining.
Even without this optimization because you don't pass a type descriptor to mallocgc the GC heap bits telling it where are pointers located would forever remain unset causing the same issue.
Experimental proof, unsafe.Malloc does not keep alive childrens
package main
import (
"fmt"
"runtime"
"runtime/debug"
"github.com/rocketlaunchr/unsafe"
)
func main() {
someData := make([]byte, 1024*1024)
for i := range someData {
someData[i] = 42
}
x := unsafe.NewZero[[]byte]()
*x = someData
runtime.GC()
debug.FreeOSMemory()
for i, v := range *x {
if v != 42 {
panic(fmt.Sprint("data corrupted: at: ", i, " got: ", v, " want: 42"))
}
}
}hugo@rikus /t/a [1]> go run .
panic: data corrupted: at: 0 got: 0 want: 42
goroutine 1 [running]:
main.main()
/tmp/a/a.go:25 +0x186
exit status 2See how the OS reset the pages of my allocation because the GC freed it.