mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-09-20 20:48:06 +00:00
libghostty: simplify Wasm allocation API
Replace a bunch of type-specific Wasm allocation functions with a generic byte allocator and reusable opaque out-parameters for pointers. This makes it a lot more ergonomic (relatively) to use the Wasm interface and removes a dozen or so exports. This also updates the `ghostty_type_json` `abi` field with a maximum alignment value that host sides can use to keep every allocation aligned properly, easily, without hardcoding numbers. This adds a test to verify this all works as intended and runs in CI.
This commit is contained in:
@@ -319,31 +319,6 @@ GHOSTTY_API GhosttySgrAttributeTag ghostty_sgr_attribute_tag(GhosttySgrAttribute
|
||||
GHOSTTY_API GhosttySgrAttributeValue* ghostty_sgr_attribute_value(
|
||||
GhosttySgrAttribute* attr);
|
||||
|
||||
#ifdef __wasm__
|
||||
/**
|
||||
* Allocate memory for an SGR attribute (WebAssembly only).
|
||||
*
|
||||
* This is a convenience function for WebAssembly environments to allocate
|
||||
* memory for an SGR attribute structure that can be passed to ghostty_sgr_next.
|
||||
*
|
||||
* @return Pointer to the allocated attribute structure
|
||||
*
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API GhosttySgrAttribute* ghostty_wasm_alloc_sgr_attribute(void);
|
||||
|
||||
/**
|
||||
* Free memory for an SGR attribute (WebAssembly only).
|
||||
*
|
||||
* Frees memory allocated by ghostty_wasm_alloc_sgr_attribute.
|
||||
*
|
||||
* @param attr Pointer to the attribute structure to free
|
||||
*
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API void ghostty_wasm_free_sgr_attribute(GhosttySgrAttribute* attr);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -358,7 +358,8 @@ typedef struct {
|
||||
* "schema": 1,
|
||||
* "abi": {
|
||||
* "target": "wasm32", "os": "freestanding", "environment": "none",
|
||||
* "pointer_size": 4, "usize_size": 4, "endian": "little"
|
||||
* "pointer_size": 4, "usize_size": 4, "max_alignment": 16,
|
||||
* "endian": "little"
|
||||
* },
|
||||
* "types": {
|
||||
* "GhosttyRenderStateData": {
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
#ifdef __wasm__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <ghostty/vt/types.h>
|
||||
|
||||
/** @defgroup wasm WebAssembly Utilities
|
||||
*
|
||||
* Convenience functions for allocating various types in WebAssembly builds.
|
||||
* Convenience functions for working with the low-level C ABI in WebAssembly
|
||||
* builds.
|
||||
* **These are only available the libghostty-vt wasm module.**
|
||||
*
|
||||
* Ghostty relies on pointers to various types for ABI compatibility, and
|
||||
@@ -28,37 +28,64 @@
|
||||
* your custom allocator. This is a very rare use case in the WebAssembly
|
||||
* world so these are optimized for simplicity.
|
||||
*
|
||||
* Use ghostty_wasm_alloc() and ghostty_wasm_free() for host-owned scratch
|
||||
* buffers and ABI values. Dynamic-language hosts can use ghostty_type_json()
|
||||
* to discover pointer and size_t widths, maximum alignment, byte order, and
|
||||
* the size and alignment of public C structs. Do not mix allocation families:
|
||||
* buffers returned by libghostty-vt allocating APIs must still be released
|
||||
* with ghostty_free(), and opaque handles must be released with their
|
||||
* type-specific destructor.
|
||||
*
|
||||
* ## Memory growth
|
||||
*
|
||||
* An exported function may grow Wasm linear memory when it allocates. Numeric
|
||||
* pointers and handles remain valid, but JavaScript ArrayBuffer, DataView, and
|
||||
* typed-array objects created before the growth may no longer cover the live
|
||||
* memory. Reacquire `exports.memory.buffer` immediately before every host-side
|
||||
* memory access. A host that caches views should recreate them whenever either
|
||||
* the buffer identity or its byte length changes.
|
||||
*
|
||||
* ## Example Usage
|
||||
*
|
||||
* Here's a simple example of using the Wasm utilities with the key encoder:
|
||||
* Here's a simple example that creates a terminal, writes bytes, and safely
|
||||
* handles memory growth:
|
||||
*
|
||||
* @code
|
||||
* const { exports } = wasmInstance;
|
||||
* const view = new DataView(wasmMemory.buffer);
|
||||
* const memory = exports.memory;
|
||||
* let cachedBuffer = null;
|
||||
* let cachedLength = 0;
|
||||
* let cachedBytes = null;
|
||||
*
|
||||
* // Create key encoder
|
||||
* const encoderPtr = exports.ghostty_wasm_alloc_opaque();
|
||||
* exports.ghostty_key_encoder_new(null, encoderPtr);
|
||||
* const encoder = view.getUint32(encoder, true);
|
||||
* function bytes() {
|
||||
* const buffer = memory.buffer;
|
||||
* if (buffer !== cachedBuffer || buffer.byteLength !== cachedLength) {
|
||||
* cachedBuffer = buffer;
|
||||
* cachedLength = buffer.byteLength;
|
||||
* cachedBytes = new Uint8Array(buffer);
|
||||
* }
|
||||
* return cachedBytes;
|
||||
* }
|
||||
*
|
||||
* // Configure encoder with Kitty protocol flags
|
||||
* const flagsPtr = exports.ghostty_wasm_alloc_u8();
|
||||
* view.setUint8(flagsPtr, 0x1F);
|
||||
* exports.ghostty_key_encoder_setopt(encoder, 5, flagsPtr);
|
||||
* function check(result) {
|
||||
* if (result !== 0) throw new Error(`libghostty-vt error: ${result}`);
|
||||
* }
|
||||
*
|
||||
* // Allocate output buffer and size pointer
|
||||
* const bufferSize = 32;
|
||||
* const bufPtr = exports.ghostty_wasm_alloc_u8_array(bufferSize);
|
||||
* const writtenPtr = exports.ghostty_wasm_alloc_usize();
|
||||
* // One slot can be reused for every opaque-handle constructor.
|
||||
* const slot = exports.ghostty_wasm_alloc_opaque();
|
||||
* if (slot === 0) throw new Error("out of memory");
|
||||
* check(exports.ghostty_terminal_new(0, slot, 80, 24));
|
||||
* const terminal = exports.ghostty_wasm_take_opaque(slot);
|
||||
*
|
||||
* // Encode the key event
|
||||
* exports.ghostty_key_encoder_encode(
|
||||
* encoder, eventPtr, bufPtr, bufferSize, writtenPtr
|
||||
* );
|
||||
* const input = new TextEncoder().encode("Hello, world!");
|
||||
* const inputPtr = exports.ghostty_wasm_alloc(input.length);
|
||||
* if (inputPtr === 0) throw new Error("out of memory");
|
||||
* bytes().set(input, inputPtr); // Acquires the current memory after alloc.
|
||||
* exports.ghostty_terminal_vt_write(terminal, inputPtr, input.length);
|
||||
*
|
||||
* // Read encoded output
|
||||
* const bytesWritten = view.getUint32(writtenPtr, true);
|
||||
* const encoded = new Uint8Array(wasmMemory.buffer, bufPtr, bytesWritten);
|
||||
* exports.ghostty_wasm_free(inputPtr, input.length);
|
||||
* exports.ghostty_terminal_free(terminal);
|
||||
* exports.ghostty_wasm_free_opaque(slot);
|
||||
* @endcode
|
||||
*
|
||||
* @remark The code above is pretty ugly! This is the lowest level interface
|
||||
@@ -68,9 +95,36 @@
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allocate caller-owned storage for a Wasm ABI value or scratch buffer.
|
||||
*
|
||||
* The returned address is aligned to the target's maximum C ABI alignment,
|
||||
* reported as `abi.max_alignment` by ghostty_type_json(). The memory is
|
||||
* uninitialized. A zero-length request returns NULL.
|
||||
*
|
||||
* The returned pointer must be released with ghostty_wasm_free() using the
|
||||
* exact same length.
|
||||
*
|
||||
* @param len Number of bytes to allocate
|
||||
* @return Pointer to allocated storage, or NULL if len is zero or allocation
|
||||
* failed
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API void* ghostty_wasm_alloc(size_t len);
|
||||
|
||||
/**
|
||||
* Free storage allocated by ghostty_wasm_alloc().
|
||||
*
|
||||
* @param ptr Pointer to free, or NULL (NULL is safely ignored)
|
||||
* @param len Original allocation length passed to ghostty_wasm_alloc()
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API void ghostty_wasm_free(void *ptr, size_t len);
|
||||
|
||||
/**
|
||||
* Allocate an opaque pointer. This can be used for any opaque pointer
|
||||
* types such as GhosttyKeyEncoder, GhosttyKeyEvent, etc.
|
||||
* types such as GhosttyKeyEncoder, GhosttyKeyEvent, etc. The allocated slot
|
||||
* is initialized to NULL and may be reused across constructors.
|
||||
*
|
||||
* @return Pointer to allocated opaque pointer, or NULL if allocation failed
|
||||
* @ingroup wasm
|
||||
@@ -86,72 +140,18 @@ GHOSTTY_API void** ghostty_wasm_alloc_opaque(void);
|
||||
GHOSTTY_API void ghostty_wasm_free_opaque(void **ptr);
|
||||
|
||||
/**
|
||||
* Allocate an array of uint8_t values.
|
||||
* Take an opaque handle from an out-parameter slot.
|
||||
*
|
||||
* @param len Number of uint8_t elements to allocate
|
||||
* @return Pointer to allocated array, or NULL if allocation failed
|
||||
* Returns the handle currently stored in @p slot and resets the slot to NULL.
|
||||
* This function does not allocate, free the returned handle, or free the slot.
|
||||
* Always check the GhosttyResult returned by the function that populated the
|
||||
* slot before calling this function.
|
||||
*
|
||||
* @param slot Pointer to an opaque out-parameter slot, or NULL
|
||||
* @return Stored opaque handle, or NULL if slot or its value is NULL
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API uint8_t* ghostty_wasm_alloc_u8_array(size_t len);
|
||||
|
||||
/**
|
||||
* Free an array allocated by ghostty_wasm_alloc_u8_array().
|
||||
*
|
||||
* @param ptr Pointer to the array to free, or NULL (NULL is safely ignored)
|
||||
* @param len Length of the array (must match the length passed to alloc)
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API void ghostty_wasm_free_u8_array(uint8_t *ptr, size_t len);
|
||||
|
||||
/**
|
||||
* Allocate an array of uint16_t values.
|
||||
*
|
||||
* @param len Number of uint16_t elements to allocate
|
||||
* @return Pointer to allocated array, or NULL if allocation failed
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API uint16_t* ghostty_wasm_alloc_u16_array(size_t len);
|
||||
|
||||
/**
|
||||
* Free an array allocated by ghostty_wasm_alloc_u16_array().
|
||||
*
|
||||
* @param ptr Pointer to the array to free, or NULL (NULL is safely ignored)
|
||||
* @param len Length of the array (must match the length passed to alloc)
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API void ghostty_wasm_free_u16_array(uint16_t *ptr, size_t len);
|
||||
|
||||
/**
|
||||
* Allocate a single uint8_t value.
|
||||
*
|
||||
* @return Pointer to allocated uint8_t, or NULL if allocation failed
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API uint8_t* ghostty_wasm_alloc_u8(void);
|
||||
|
||||
/**
|
||||
* Free a uint8_t allocated by ghostty_wasm_alloc_u8().
|
||||
*
|
||||
* @param ptr Pointer to free, or NULL (NULL is safely ignored)
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API void ghostty_wasm_free_u8(uint8_t *ptr);
|
||||
|
||||
/**
|
||||
* Allocate a single size_t value.
|
||||
*
|
||||
* @return Pointer to allocated size_t, or NULL if allocation failed
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API size_t* ghostty_wasm_alloc_usize(void);
|
||||
|
||||
/**
|
||||
* Free a size_t allocated by ghostty_wasm_alloc_usize().
|
||||
*
|
||||
* @param ptr Pointer to free, or NULL (NULL is safely ignored)
|
||||
* @ingroup wasm
|
||||
*/
|
||||
GHOSTTY_API void ghostty_wasm_free_usize(size_t *ptr);
|
||||
GHOSTTY_API void* ghostty_wasm_take_opaque(void **slot);
|
||||
|
||||
/** @} */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user