For the record, allocating the memory as []unsafe.Pointer fixes the issues.
(Working example below.)
That said, I'm not sure this is a future proof idea. It seems the GC
handles it correctly now, but may change in the future.
I'd greatly prefer to find a spec-compliant solution...
package debug
import (
"testing"
"runtime"
"unsafe"
)
type Num struct {
val uint64
ptr *uint64
}
func Test(t *testing.T) {
var sliceDataPtr unsafe.Pointer
func() {
n := uint64(123456789)
numMemSize := 2
b := make([]unsafe.Pointer, numMemSize, numMemSize)
sliceDataPtr = unsafe.Pointer(unsafe.SliceData(b))
numVal := (*Num)(sliceDataPtr)
numVal.val = 123
numVal.ptr = &n
escape(sliceDataPtr) // force on heap
}()
runtime.GC()
numVal := (*Num)(sliceDataPtr)
if numVal.val != 123 {
t.Fatalf("v[%X]", numVal.val)
}
if *numVal.ptr != 123456789 {
t.Fatalf("v[%X]", *numVal.ptr)
}
}
var sink any
func escape(x any) {
sink = x
sink = nil
}
On Friday, August 18, 2023 at 5:12:18 PM UTC+2 Tibor Halter wrote:
> Hi Elias, thanks for chiming in!
>
> > The Go garbage collector is precise in that it only considers pointers
> when determining memory that can be freed.
>
> Agreed, but it is not as precise on _what_ is a pointer. Specifically,
> which matters?
> - the type used upon allocating the memory, or
> - the type used upon writing to it.
>
> Because *T or unsafe.Pointer is required upon writing to a memory location
> for it to be marked as a pointer, I came to the conclusion that the type
> upon writing is what matters.
>
> So then both types need to be pointers?
>
> Does that mean there is no way to define a struct-like memory layout
> dynamically, that contains a mix of pointer and non-pointer types, in a way
> that is supported by the GC?
> (This is what I'm trying to do.)
>
> On Friday, August 18, 2023 at 3:49:03 PM UTC+2 [email protected] wrote:
>
>> On Friday, 18 August 2023 at 05:57:38 UTC-6 Tibor Halter wrote:
>>
>> Thanks Ian!
>>
>> I have managed to strip it down to its essence, see code below.
>>
>> *Summary of what I'm doing*
>> - reserve memory with make([]byte)
>> - get pointer to underlying array
>> - cast it to type Num
>> - write pointer #2 into it
>> - force GC()
>> - GC does not follow pointer #2, memory is freed
>>
>>
>> The Go garbage collector is precise in that it only considers pointers
>> when determining memory that can be freed. In your scenario and
>> demonstration program you explicitly store a pointer in a byte slice,
>> effectively making it invisible to the collector.
>>
>>
>> --
>>
>>
>> package debug
>>
>> import (
>> "testing"
>> "runtime"
>> "unsafe"
>> )
>>
>> type Num struct {
>> numData *uint64
>> }
>>
>> func Test(t *testing.T) {
>> n := uint64(123456789)
>> numMemSize := 8
>> b := make([]byte, numMemSize, numMemSize)
>> sliceDataPtr := unsafe.Pointer(unsafe.SliceData(b))
>> (*Num)(sliceDataPtr).numData = &n
>>
>>
>> The pointer to &n stored in b here is effectively hidden from the
>> collector.
>>
>>
>>
>> runtime.GC()
>>
>>
>> With no more visible references to (the supposedly heap-allocated) n, it
>> can be freed.
>>
>>
>>
>> act := (*Num)(sliceDataPtr).numData
>> if *act != 123456789 {
>> t.Fatalf("v[%X]", *act)
>> }
>> }
>>
>>
>> Elias
>>
>
--
You received this message because you are subscribed to the Google Groups
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/golang-nuts/11c27ff5-0e1b-42a7-9f00-6bda4f860f90n%40googlegroups.com.