On Fri, Oct 11, 2024 at 3:25 AM 'Timo Beckers' via golang-nuts <
golang-nuts@googlegroups.com> wrote:

> On Fri, 11 Oct 2024 at 03:15, Robert Engels <reng...@ix.netcom.com> wrote:
> >
> > That’s what I was thinking. Thanks for providing the details. Rule of
> thumb - if you’re trying to do something and you’re the only one that’s
> trying it - you’re probably doing something wrong - or at least there is a
> much easier way.
>
> I wish I could take the credit for the idea, but one of our
> contributors referred me to this package:
> https://github.com/Jille/gcmmap. Yes, it doesn't have any importers
> and it's unpolished, but we thought the idea had merit, so we adapted
> it to our use case.
>
> As for there being an easier way -- it's hard to argue against KISS,
> but the example I gave leaves out many details that made us prefer
> this approach, see below.
>
> > On Oct 10, 2024, at 6:31 PM, 'Keith Randall' via golang-nuts <
> golang-nuts@googlegroups.com> wrote:
> >
> > type Variable struct {
> > *atomic.Uint64
> > m *Memory
> > }
> >
> > I agree that if you have a finalizer on a Variable that unmaps m, and
> you've given out a reference to the internal Uint64 to callers, then you
> can get in a situation where the Variable is dead, its finalizer is run,
> and the memory is unmapped while someone is doing operations on the atomic
> Uint64.
> >
> > So don't embed like that. Instead just have a private field and
> implement the methods you need. Like:
> >
> > type Variable struct {
> >   u *atomic.Uint64
> >   m *Memory
> > }
> > func (v *Variable) Load() uint64 {
> >    x := v.u.Load()
> >    runtime.KeepAlive(v)
> >    return x
> > }
>
> Yes, we couldn't allow the caller to obtain a reference to Uint64
> because of the risk of losing the Memory pointer. My initial
> implementation was slightly different; it defined unexported type
> aliases for all integer types in sync/atomic and embedded those
> instead, so the caller could only invoke the methods promoted from
> e.g. atomic.Uint64 to Variable. However, that still left the
> possibility of Memory becoming unreachable at the start of
> atomic.Uint64.Load(), we'd need to wrap all accessors with
> runtime.KeepAlive() as you suggested above.
>
> The reason this wasn't feasible is because the size of a Variable is
> not known (by us, the library) at compile time. We parse an eBPF ELF
> into a bunch of objects, including a slice of Variables, which
> essentially includes all global vars and consts in the ELF. A Variable
> has a size and an offset into a Memory.
>
> We can't make a Variable generic, because again, types are not known
> at Go compile time and different-size Variables need to be put into a
> slice.
>
> Additionally, Variables can be structs as well, and don't necessarily
> need to be atomic. My initial implementation contained a bunch of
> boilerplate for atomics, but this relegated all struct operations to
> reflection-based marshaling using Variable.Set() and .Get() (some
> amalgamation of binary.Read and .Write underneath).
>
> At the end of the day, to keep things somewhat flexible and ergonomic,
> this was the only option we were left with. Now, the caller can do:
>
> ```
> type foo struct { a, b, uint64 }
> var v *Variable
> s, _ := VariablePointer[foo](v) // returns (*foo, error)
>
> foo.a = 123
> ```
>
> VariablePointer performs a bounds check on the underlying Memory and
> the size of foo, and ensures it doesn't contain any pointers.
> (binary.Size will enforce this)
>
> Accesses to foo now go to shared kernel memory. foo can be passed
> around the Go app without fear of the underlying memory getting
> unmapped. *Memory and *Variable can be lost without issues.
>

In cases like this where I don't know the type at compile time, I've
usually opted to define an interface that I can assign any instantiation of
a generic type to.
This way, you make your return value (or members of a slice, struct, etc.)
that interface type and the user can type-assert wherever necessary.
(which has better safety properties than needing to do additional safety
checks on your library's side)
I'd generally recommend having some interface-value accessors, though.
(note: interface method calls are wholly distinct from reflection (although
both to involve the runtime resolving types))


e.g.
```
type Fizzle interface {

fizzleImpl()

}

type Foobar[A any] struct {

field A
Ptr   atomic.Pointer[A]

}

func (f *Foobar[A]) fizzleImpl() {}

func (f *Foobar[A]) SomeOtherMethod() A { return f.field }

func MakeFizzle(i int) Fizzle {

switch i {
case 0:

return &Foobar[int]{}

case 1:

return &Foobar[int64]{}

default:

return &Foobar[string]{}

}

}
```


> > ... and ditto for other methods of atomic.Uint64 that your clients need.
> It's a bit of boilerplate, but it should work without having to mmap Go
> heap pages.
> >
> >
> > On Thursday, October 10, 2024 at 12:41:45 PM UTC-7 Robert Engels wrote:
> >>
> >> I think you had a bug in your Memory implementation. That is the
> correct way to implement and it’s straightforward. Not sure why you ran
> into a problem.
> >>
> >> > On Oct 10, 2024, at 9:31 AM, 'Timo Beckers' via golang-nuts <
> golan...@googlegroups.com> wrote:
> >> >
> >> > On Thu, 10 Oct 2024 at 14:48, Jan Mercl <0xj...@gmail.com> wrote:
> >> >>
> >> >>> On Thu, Oct 10, 2024 at 2:23 PM 'Timo Beckers' via golang-nuts
> >> >>> <golan...@googlegroups.com> wrote:
> >> >>>
> >> >>> I've been searching around for some info or existing conversations
> around this topic, but that hasn't turned up anything useful so far. I had
> a question around some implicit behaviour of Go's heap allocator.
> >> >>
> >> >> From the specs point of view, there's no heap and no allocator. The
> >> >> language specification covers allocation at
> >> >> https://go.dev/ref/spec#Allocation, but the allocator is an
> >> >> implementation detail with no particular guarantees.
> >> >
> >> > Thanks, I didn't realize this was mentioned in the spec. I guess that
> >> > puts an end to the conversation immediately. :)
> >> > I'll allocate an extra page to make sure we can always align to a
> page boundary.
> >> >
> >> >>
> >> >>> I'm working on implementing BPF map operations through direct
> shared memory access (without going through syscalls). To make the API
> somewhat ergonomic, I've settled on mmapping BPF map (kernel) memory over a
> part of the Go heap using MAP_FIXED. Feel free to tell me how bad of an
> idea this is, I can elaborate if needed.
> >> >>
> >> >> I'm not sure what exactly do you mean by "over" as there is more than
> >> >> one possibility I can think of. Go managed memory and manually mmaped
> >> >> memory can intermix under certain conditions. One of them is that the
> >> >> runtime must know which is which, ie. be able to distinguish pointers
> >> >> to memory allocated by itself from memory allocated by something else
> >> >> - manually mmaped, C allocated etc.
> >> >>
> >> >
> >> > Precisely, we want this memory to be both tracked by the GC _and_
> >> > backed by shared kernel memory.
> >> > I can only hope this isn't too common. :)
> >> >
> >> >>> In order for this to work and to minimize allocations, I need a
> heap allocation that starts on a page boundary. Initially, I experimented
> with //go:linkname'ing mallocgc(), but I figured allocating a regular slice
> the size of a page has basically the same effect. Here's a playground link:
> https://go.dev/play/p/ua2NJ-rEIlC. As long as the slice/backing array is
> a multiple of the architecture's page size, it seems to start on a page
> boundary. I've tried allocating a bunch of ballast, forcing GCs, etc. and
> it hasn't failed once.
> >> >>
> >> >> Those are guarantees possibly provided by the kernel. No promises
> from Go.
> >> >
> >> > The kernel guarantees handing out page-aligned slabs of memory to Go,
> >> > but after that point, isn't it up to Go to slice that up however it
> >> > pleases.
> >> >
> >> >>
> >> >>> Here's my question: which property of the Go allocator is this
> banking on? Intuitively, this makes sense to me. An 8-byte alloc needs to
> be 8-byte aligned in case it's a pointer. Does a 4k allocation need to be
> 4k-aligned as well (e.g. in case it's a struct), since a compiler would
> align members to the start of the struct? I'm reading larger (> 8 or 16
> bytes depending on arch?) allocs have an alignment of 1 byte, and
> unsafe.Alignof([4096]byte) tells me the same, but that's not the behaviour
> exhibited by the allocator.
> >> >>
> >> >> Implementation details that can change. You cannot rely on it. You
> >> >> have to code it manually and look for platform details to work out.
> >> >>
> >> >> -j
> >> >
> >> > Yep, clear. Thanks again for the pointer to the spec, not sure why I
> >> > didn't check there first.
> >> >
> >> > Take care,
> >> >
> >> > Timo
> >> >
> >> > --
> >> > 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...@googlegroups.com.
> >> > To view this discussion on the web visit
> https://groups.google.com/d/msgid/golang-nuts/CANgQc9gD3uphTpX3V46caqQvuSyNJ3ieOuz%2BOJJcyXPThTi%3D1Q%40mail.gmail.com
> .
> >
> > --
> > 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/65ec6573-03a0-4586-8ca1-2da2497628a0n%40googlegroups.com
> .
> >
> > --
> > You received this message because you are subscribed to a topic in the
> Google Groups "golang-nuts" group.
> > To unsubscribe from this topic, visit
> https://groups.google.com/d/topic/golang-nuts/LzUMguyouXM/unsubscribe.
> > To unsubscribe from this group and all its topics, 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/F9F590B4-A75E-4846-BF3E-A53AE41A2489%40ix.netcom.com
> .
>
> --
> 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/CANgQc9id%3DAa-C%3DL8Hqyi__W%2BcreY1N8m8j22mThNq-3g1TWXQQ%40mail.gmail.com
> .
>

-- 
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/CANrC0BisA-4upx%3D9RLqFg9Uauznn3EpdP8GxW9ffQX_df4XnZQ%40mail.gmail.com.

Reply via email to