From 83ceb8aabcaba9a9014bab56d23afb599d51342a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 11 Apr 2026 13:15:47 -0700 Subject: [PATCH] bind ghostty_alloc/ghostty_free and replace C.malloc/C.free Add alloc.go with exported Alloc and Free functions wrapping the upstream ghostty_alloc() and ghostty_free() from allocator.h. These use the default (NULL) allocator, matching how the rest of the bindings pass nil for the allocator parameter. This is important because it lets us not explicitly depend on any libc functionality. --- TODO.md | 2 +- alloc.go | 36 ++++++++++++++++++++++++++++++++++++ get_multi.go | 24 +++++++++++------------- kitty_graphics.go | 17 ++++++++--------- render_state_cell.go | 5 ++--- render_state_data.go | 5 ++--- render_state_row.go | 5 ++--- screen.go | 9 ++++----- terminal.go | 6 +++--- terminal_data.go | 5 ++--- terminal_effect.go | 19 ++++++++++++------- 11 files changed, 83 insertions(+), 50 deletions(-) create mode 100644 alloc.go diff --git a/TODO.md b/TODO.md index 1887425..5e9652b 100644 --- a/TODO.md +++ b/TODO.md @@ -9,7 +9,7 @@ - [ ] Paste utilities (`paste.h`) - [ ] Focus encoding (`focus.h`) - [x] Kitty graphics (`kitty_graphics.h`) -- [ ] Allocator (`allocator.h` — `ghostty_alloc`, `ghostty_free`) +- [x] Allocator (`allocator.h` — `ghostty_alloc`, `ghostty_free`) - [ ] Selection type (`selection.h`) ## Partially Bound diff --git a/alloc.go b/alloc.go new file mode 100644 index 0000000..b71f313 --- /dev/null +++ b/alloc.go @@ -0,0 +1,36 @@ +package libghostty + +// Memory allocation helpers wrapping the upstream ghostty_alloc() and +// ghostty_free() functions from allocator.h. +// +// These replace direct C.malloc/C.free calls so that all memory is +// allocated and freed through libghostty's allocator. This is critical +// on platforms where the library's internal allocator differs from the +// consumer's C runtime (e.g. Windows, where Zig's libc and MSVC's CRT +// maintain separate heaps). + +/* +#include +*/ +import "C" + +import "unsafe" + +// Alloc allocates len bytes through the default libghostty allocator +// (NULL allocator). Returns a pointer to the allocated memory or nil +// if the allocation failed. +// +// The returned memory must be freed with Free using the same length. +// C: ghostty_alloc +func Alloc(len uintptr) unsafe.Pointer { + return unsafe.Pointer(C.ghostty_alloc(nil, C.size_t(len))) +} + +// Free frees memory allocated by Alloc (or returned by a libghostty +// function) using the default libghostty allocator (NULL allocator). +// The len must match the original allocation size. It is safe to pass +// nil. +// C: ghostty_free +func Free(ptr unsafe.Pointer, len uintptr) { + C.ghostty_free(nil, (*C.uint8_t)(ptr), C.size_t(len)) +} diff --git a/get_multi.go b/get_multi.go index d85323b..7c20a1b 100644 --- a/get_multi.go +++ b/get_multi.go @@ -3,23 +3,21 @@ package libghostty // Shared helpers for the get_multi pattern used by multiple types. // These helpers solve the cgo pointer-passing rule: Go cannot pass // a Go-allocated void** (array of pointers to Go memory) directly -// to C. Instead, we allocate the void** array in C heap memory, -// copy the Go pointer values in, call the C function, then free. - -/* -#include -*/ -import "C" +// to C. Instead, we allocate the void** array via libghostty's +// allocator, copy the Go pointer values in, call the C function, +// then free. import "unsafe" -// cValuesArray allocates a C-heap array of void* pointers, copies the -// Go unsafe.Pointer values into it, and returns the C array pointer. -// The caller must free the returned pointer with C.free when done. -func cValuesArray(values []unsafe.Pointer) *unsafe.Pointer { +// cValuesArray allocates a C-heap array of void* pointers via the +// libghostty allocator, copies the Go unsafe.Pointer values into it, +// and returns the C array pointer and allocation size. The caller must +// free the returned pointer with Free(ptr, size) when done. +func cValuesArray(values []unsafe.Pointer) (*unsafe.Pointer, uintptr) { n := len(values) - cArr := (*unsafe.Pointer)(C.malloc(C.size_t(n) * C.size_t(unsafe.Sizeof(unsafe.Pointer(nil))))) + size := uintptr(n) * unsafe.Sizeof(unsafe.Pointer(nil)) + cArr := (*unsafe.Pointer)(Alloc(size)) dst := unsafe.Slice(cArr, n) copy(dst, values) - return cArr + return cArr, size } diff --git a/kitty_graphics.go b/kitty_graphics.go index 94e622d..738a5f5 100644 --- a/kitty_graphics.go +++ b/kitty_graphics.go @@ -5,7 +5,6 @@ package libghostty // protocol. /* -#include #include // Helper to create a properly initialized GhosttySelection (sized struct). @@ -446,8 +445,8 @@ func (img *KittyGraphicsImage) GetMulti(keys []KittyGraphicsImageData, values [] return nil } // Allocate the void** array in C memory to satisfy cgo pointer-passing rules. - cVals := cValuesArray(values) - defer C.free(unsafe.Pointer(cVals)) + cVals, cValsSize := cValuesArray(values) + defer Free(unsafe.Pointer(cVals), cValsSize) return resultError(C.ghostty_kitty_graphics_image_get_multi( img.ptr, C.size_t(len(keys)), @@ -526,8 +525,8 @@ func (img *KittyGraphicsImage) Info() (*KittyGraphicsImageInfo, error) { unsafe.Pointer(&dataPtr), unsafe.Pointer(&dataLen), } - cVals := cValuesArray(values[:]) - defer C.free(unsafe.Pointer(cVals)) + cVals, cValsSize := cValuesArray(values[:]) + defer Free(unsafe.Pointer(cVals), cValsSize) if err := resultError(C.ghostty_kitty_graphics_image_get_multi( img.ptr, @@ -649,8 +648,8 @@ func (it *KittyGraphicsPlacementIterator) GetMulti(keys []KittyGraphicsPlacement return nil } // Allocate the void** array in C memory to satisfy cgo pointer-passing rules. - cVals := cValuesArray(values) - defer C.free(unsafe.Pointer(cVals)) + cVals, cValsSize := cValuesArray(values) + defer Free(unsafe.Pointer(cVals), cValsSize) return resultError(C.ghostty_kitty_graphics_placement_get_multi( it.ptr, C.size_t(len(keys)), @@ -873,8 +872,8 @@ func (it *KittyGraphicsPlacementIterator) Info() (*KittyGraphicsPlacementInfo, e unsafe.Pointer(&rows), unsafe.Pointer(&z), } - cVals := cValuesArray(values[:]) - defer C.free(unsafe.Pointer(cVals)) + cVals, cValsSize := cValuesArray(values[:]) + defer Free(unsafe.Pointer(cVals), cValsSize) if err := resultError(C.ghostty_kitty_graphics_placement_get_multi( it.ptr, diff --git a/render_state_cell.go b/render_state_cell.go index f399201..290eaa4 100644 --- a/render_state_cell.go +++ b/render_state_cell.go @@ -4,7 +4,6 @@ package libghostty // GhosttyRenderStateRowCells C APIs. /* -#include #include */ import "C" @@ -119,8 +118,8 @@ func (rc *RenderStateRowCells) GetMulti(keys []RenderStateRowCellsData, values [ return nil } // Allocate the void** array in C memory to satisfy cgo pointer-passing rules. - cVals := cValuesArray(values) - defer C.free(unsafe.Pointer(cVals)) + cVals, cValsSize := cValuesArray(values) + defer Free(unsafe.Pointer(cVals), cValsSize) return resultError(C.ghostty_render_state_row_cells_get_multi( rc.ptr, C.size_t(len(keys)), diff --git a/render_state_data.go b/render_state_data.go index 4baa320..ef0140f 100644 --- a/render_state_data.go +++ b/render_state_data.go @@ -5,7 +5,6 @@ package libghostty // Functions are ordered alphabetically. /* -#include #include // Helper to create a properly initialized GhosttyRenderStateColors (sized struct). @@ -209,8 +208,8 @@ func (rs *RenderState) GetMulti(keys []RenderStateData, values []unsafe.Pointer) return nil } // Allocate the void** array in C memory to satisfy cgo pointer-passing rules. - cVals := cValuesArray(values) - defer C.free(unsafe.Pointer(cVals)) + cVals, cValsSize := cValuesArray(values) + defer Free(unsafe.Pointer(cVals), cValsSize) return resultError(C.ghostty_render_state_get_multi( rs.ptr, C.size_t(len(keys)), diff --git a/render_state_row.go b/render_state_row.go index 261643c..3ddb69b 100644 --- a/render_state_row.go +++ b/render_state_row.go @@ -4,7 +4,6 @@ package libghostty // GhosttyRenderStateRowIterator C APIs. /* -#include #include */ import "C" @@ -99,8 +98,8 @@ func (ri *RenderStateRowIterator) GetMulti(keys []RenderStateRowData, values []u return nil } // Allocate the void** array in C memory to satisfy cgo pointer-passing rules. - cVals := cValuesArray(values) - defer C.free(unsafe.Pointer(cVals)) + cVals, cValsSize := cValuesArray(values) + defer Free(unsafe.Pointer(cVals), cValsSize) return resultError(C.ghostty_render_state_row_get_multi( ri.ptr, C.size_t(len(keys)), diff --git a/screen.go b/screen.go index ee7f188..7361a5b 100644 --- a/screen.go +++ b/screen.go @@ -1,7 +1,6 @@ package libghostty /* -#include #include */ import "C" @@ -213,8 +212,8 @@ func (c *Cell) GetMulti(keys []CellData, values []unsafe.Pointer) error { return nil } // Allocate the void** array in C memory to satisfy cgo pointer-passing rules. - cVals := cValuesArray(values) - defer C.free(unsafe.Pointer(cVals)) + cVals, cValsSize := cValuesArray(values) + defer Free(unsafe.Pointer(cVals), cValsSize) return resultError(C.ghostty_cell_get_multi( c.c, C.size_t(len(keys)), @@ -354,8 +353,8 @@ func (r *Row) GetMulti(keys []RowData, values []unsafe.Pointer) error { return nil } // Allocate the void** array in C memory to satisfy cgo pointer-passing rules. - cVals := cValuesArray(values) - defer C.free(unsafe.Pointer(cVals)) + cVals, cValsSize := cValuesArray(values) + defer Free(unsafe.Pointer(cVals), cValsSize) return resultError(C.ghostty_row_get_multi( r.c, C.size_t(len(keys)), diff --git a/terminal.go b/terminal.go index 589b01f..6d0713a 100644 --- a/terminal.go +++ b/terminal.go @@ -1,7 +1,6 @@ package libghostty /* -#include #include */ import "C" @@ -35,7 +34,8 @@ type Terminal struct { // returned by an effect trampoline (e.g. enquiry, xtversion). // libghostty copies the data immediately, so a single buffer // shared across effects is sufficient. - effectBuf unsafe.Pointer + effectBuf unsafe.Pointer + effectBufLen uintptr } // TerminalOption is a functional option for configuring a Terminal. @@ -246,7 +246,7 @@ func (t *Terminal) Close() { t.handle.Delete() C.ghostty_terminal_free(t.ptr) if t.effectBuf != nil { - C.free(t.effectBuf) + Free(t.effectBuf, t.effectBufLen) } } diff --git a/terminal_data.go b/terminal_data.go index 11b54d3..68dc0a2 100644 --- a/terminal_data.go +++ b/terminal_data.go @@ -4,7 +4,6 @@ package libghostty // Functions are ordered alphabetically. /* -#include #include */ import "C" @@ -230,8 +229,8 @@ func (t *Terminal) GetMulti(keys []TerminalData, values []unsafe.Pointer) error return nil } // Allocate the void** array in C memory to satisfy cgo pointer-passing rules. - cVals := cValuesArray(values) - defer C.free(unsafe.Pointer(cVals)) + cVals, cValsSize := cValuesArray(values) + defer Free(unsafe.Pointer(cVals), cValsSize) return resultError(C.ghostty_terminal_get_multi( t.ptr, C.size_t(len(keys)), diff --git a/terminal_effect.go b/terminal_effect.go index d0e9241..fcad634 100644 --- a/terminal_effect.go +++ b/terminal_effect.go @@ -8,7 +8,6 @@ package libghostty // recovers the Terminal and dispatches to the user-supplied Go effect handler. /* -#include #include // Forward declarations for the Go trampolines so we can take their @@ -214,22 +213,28 @@ func goDeviceAttributesTrampoline(_ C.GhosttyTerminal, userdata unsafe.Pointer, return C.bool(true) } -// effectString copies data into C memory, updates effectBuf, and -// returns a GhosttyString pointing to it. The previous effectBuf -// is freed. Returns a zero-length GhosttyString if data is empty. +// effectString copies data into C memory allocated via the libghostty +// allocator, updates effectBuf/effectBufLen, and returns a +// GhosttyString pointing to it. The previous effectBuf is freed. +// Returns a zero-length GhosttyString if data is empty. func (t *Terminal) effectString(data []byte) C.GhosttyString { if t.effectBuf != nil { - C.free(t.effectBuf) + Free(t.effectBuf, t.effectBufLen) + t.effectBuf = nil + t.effectBufLen = 0 } if len(data) == 0 { return C.GhosttyString{} } - cmem := C.CBytes(data) + n := uintptr(len(data)) + cmem := Alloc(n) + copy(unsafe.Slice((*byte)(cmem), n), data) t.effectBuf = cmem + t.effectBufLen = n return C.GhosttyString{ ptr: (*C.uint8_t)(cmem), - len: C.size_t(len(data)), + len: C.size_t(n), } } -- 2.51.2