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 ma...@eliasnaur.com 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 golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/11c27ff5-0e1b-42a7-9f00-6bda4f860f90n%40googlegroups.com.

Reply via email to