Skip to content

Commit 4604c9a

Browse files
committed
Fix and document the cabi_realloc implementation
Signed-off-by: Till Schneidereit <till@tillschneidereit.net>
1 parent b0c1cb8 commit 4604c9a

File tree

1 file changed

+72
-1
lines changed

1 file changed

+72
-1
lines changed

x/cabi/realloc.go

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,79 @@ package cabi
22

33
import "unsafe"
44

5+
// The Go runtime-internal sbrk function.
6+
// Note that this isn't *really* sbrk, and doesn't necessarily result in heap
7+
// growth. Instead, it reserves a block of memory of size n bytes, which then
8+
// isn't used by Go's GC allocator.
9+
// The heap will only actually grown if the requested allocation doesn't fit.
10+
//go:linkname sbrk runtime.sbrk
11+
func sbrk(n uintptr) unsafe.Pointer
12+
13+
// Returns the memory region from `v` to `v + n` to the Go runtime, enabling
14+
// its use by the garbage collector.
15+
//go:linkname sysFreeOS runtime.sysFreeOS
16+
func sysFreeOS(v unsafe.Pointer, n uintptr)
17+
18+
var useGCAllocations = false
19+
20+
func init() {
21+
// Once `init` is called, the runtime has been initialized, and we can
22+
// start using managed memory.
23+
useGCAllocations = true
24+
}
25+
526
// realloc allocates or reallocates memory for Component Model calls across
627
// the host-guest boundary.
728
//
8-
// Note: the use of uintptr assumes 32-bit pointers when compiled for wasm or wasm32.
29+
// Note: while in Go, as opposed to TinyGo, `uintptr` is 64-bit, `go:wasmexport`
30+
// properly coerces the parameters/return values to and from 32-bit values when
31+
// compiled for wasm or wasm32.
32+
// TODO: file bug for sysReserveAlignedSbrk not always releasing the memlock.
933
//go:wasmexport cabi_realloc
1034
func realloc(ptr unsafe.Pointer, size, align, newsize uintptr) unsafe.Pointer {
35+
36+
// If the Go runtime has not been initialized, we need to
37+
// allocate memory using sbrk directly.
38+
// This happens because the runtime itself does two calls to
39+
// imported functions during initialization, which result in
40+
// calls to `cabi_realloc` before the runtime is fully initialized.
41+
if !useGCAllocations {
42+
if newsize == 0 {
43+
if ptr != nil {
44+
// Free the old pointer if it is not nil.
45+
// Note that in practice, this case isn't ever hit:
46+
// `cabi_realloc` isn't called with a non-nil pointer before the
47+
// Go runtime is fully initialized.
48+
sysFreeOS(ptr, size)
49+
}
50+
51+
return nil
52+
}
53+
54+
alignedSize := newsize + offset(newsize, align)
55+
unaligned := sbrk(alignedSize)
56+
off := offset(uintptr(unaligned), align)
57+
newptr := unsafe.Add(unaligned, off)
58+
if ptr != nil && newptr != nil && size > 0 {
59+
// Copy the old data to the new pointer.
60+
// Note that in practice, this case isn't ever hit:
61+
// `cabi_realloc` isn't called with a non-nil pointer.
62+
copy(unsafe.Slice((*byte)(newptr), size), unsafe.Slice((*byte)(ptr), size))
63+
}
64+
if ptr != nil {
65+
// Free the old pointer if it is not nil.
66+
sysFreeOS(ptr, size)
67+
}
68+
return newptr
69+
}
70+
1171
if newsize <= size {
1272
return unsafe.Add(ptr, offset(uintptr(ptr), align))
1373
}
1474
newptr := alloc(newsize, align)
1575
if size > 0 {
1676
copy(unsafe.Slice((*byte)(newptr), newsize), unsafe.Slice((*byte)(ptr), size))
77+
1778
}
1879
return newptr
1980
}
@@ -29,6 +90,16 @@ func offset(ptr, align uintptr) uintptr {
2990
// It attempts to align the allocated memory by allocating a slice of
3091
// a type that matches the desired alignment. It aligns to 16 bytes for
3192
// values of align other than 1, 2, 4, or 8.
93+
//
94+
// The allocation itself is done using the Go runtime's GC allocator.
95+
// We then return a raw pointer to the underlying memory, without keeping
96+
// any references to the allocated object itself. That would be very bad
97+
// indeed if there was a risk of the GC running before we're done using
98+
// the memory, or before it's rooted by another GC object.
99+
//
100+
// Since WebAssembly is a single-threaded environment, and the use of the
101+
// returned memory is tightly controlled by generated bindings code, neither
102+
// of these issues is a problem in practice.
32103
func alloc(size, align uintptr) unsafe.Pointer {
33104
switch align {
34105
case 1:

0 commit comments

Comments
 (0)