diff --git a/abi/memory.go b/abi/memory.go new file mode 100644 index 00000000..a4e6a8bd --- /dev/null +++ b/abi/memory.go @@ -0,0 +1,57 @@ +package abi + +import "unsafe" + +// Align aligns ptr with alignment align. +func Align(ptr, align uintptr) uintptr { + // (dividend + divisor - 1) / divisor + // http://www.cs.nott.ac.uk/~rcb/G51MPC/slides/NumberLogic.pdf + return ((ptr + align - 1) / align) * align +} + +// Realloc allocates or reallocates memory for Component Model calls across +// the host-guest boundary. +// +// Note: the use of uintptr assumes 32-bit pointers, e.g. GOOS=wasm32 when compiled for WebAssembly. +// +//go:wasmexport cabi_realloc +func Realloc(ptr, size, align, newsize uintptr) uintptr { + if ptr == 0 { + if newsize == 0 { + return Align(ptr, align) + } + return uintptr(alloc(newsize, align)) + } + + if newsize <= size { + return Align(ptr, align) + } + + newptr := alloc(newsize, align) + if size > 0 { + // Appease vet, see https://github.com/golang/go/issues/58625 + src := *(*unsafe.Pointer)(unsafe.Pointer(&ptr)) + copy(unsafe.Slice((*byte)(newptr), newsize), unsafe.Slice((*byte)(src), size)) + } + return uintptr(newptr) +} + +func alloc(size, align uintptr) unsafe.Pointer { + switch align { + case 1: + s := make([]uint8, size) + return unsafe.Pointer(unsafe.SliceData(s)) + case 2: + s := make([]uint16, min(size/align, 1)) + return unsafe.Pointer(unsafe.SliceData(s)) + case 4: + s := make([]uint32, min(size/align, 1)) + return unsafe.Pointer(unsafe.SliceData(s)) + case 8: + s := make([]uint64, min(size/align, 1)) + return unsafe.Pointer(unsafe.SliceData(s)) + default: + s := make([][16]uint8, min(size/align, 1)) + return unsafe.Pointer(unsafe.SliceData(s)) + } +} diff --git a/abi/memory_test.go b/abi/memory_test.go new file mode 100644 index 00000000..a2a9438e --- /dev/null +++ b/abi/memory_test.go @@ -0,0 +1,63 @@ +package abi + +import ( + "fmt" + "testing" +) + +func TestAlign(t *testing.T) { + tests := []struct { + ptr uintptr + align uintptr + want uintptr + }{ + {0, 1, 0}, {0, 2, 0}, {0, 4, 0}, {0, 8, 0}, + {1, 1, 1}, {1, 2, 2}, {1, 4, 4}, {1, 8, 8}, + {2, 1, 2}, {2, 2, 2}, {2, 4, 4}, {2, 8, 8}, + {3, 1, 3}, {3, 2, 4}, {3, 4, 4}, {3, 8, 8}, + {4, 1, 4}, {4, 2, 4}, {4, 4, 4}, {4, 8, 8}, + {5, 1, 5}, {5, 2, 6}, {5, 4, 8}, {5, 8, 8}, + {6, 1, 6}, {6, 2, 6}, {6, 4, 8}, {6, 8, 8}, + {7, 1, 7}, {7, 2, 8}, {7, 4, 8}, {7, 8, 8}, + {8, 1, 8}, {8, 2, 8}, {8, 4, 8}, {8, 8, 8}, + {9, 1, 9}, {9, 2, 10}, {9, 4, 12}, {9, 8, 16}, + {10, 1, 10}, {10, 2, 10}, {10, 4, 12}, {10, 8, 16}, + } + for _, tt := range tests { + name := fmt.Sprintf("%d,%d=%d", tt.ptr, tt.align, tt.want) + t.Run(name, func(t *testing.T) { + got := Align(tt.ptr, tt.align) + if got != tt.want { + t.Errorf("Align(%d, %d): expected %d, got %d", tt.ptr, tt.align, tt.want, got) + } + }) + } +} + +func TestRealloc(t *testing.T) { + var sentinel uintptr + sentinel -= 1 // wraparound + tests := []struct { + name string + ptr uintptr + size uintptr + align uintptr + newsize uintptr + want uintptr + }{ + {"nil", 0, 0, 1, 0, 0}, + {"nil with align", 0, 0, 2, 0, 0}, + {"align to 2", 1, 0, 2, 0, 2}, + {"align to 8", 1, 0, 8, 0, 8}, + {"align to 8", 1, 0, 8, 0, 8}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Realloc(tt.ptr, tt.size, tt.align, tt.newsize) + if got != tt.want { + t.Errorf("Realloc(%d, %d, %d, %d): expected %d, got %d", + tt.ptr, tt.size, tt.align, tt.newsize, tt.want, got) + } + }) + } +}