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), } }