GreyCat C API and GCL Standard Library reference...
Comprehensive reference for GreyCat native development (C API), the GCL Standard Library, and plugin development patterns. Tracks SDK 8.4 (headers re-verified 2026-10-02 against upstream e7630e693). Changes since 8.3 (C API additive except gc_stream__append's new host param and two enumerator renames; breaking stdlib changes: S3/XML removal, sort null placement, log file moved to a stream, Log id renames, RuntimeUsage removal):
gc_stream__append(host, s, record, len) β new leading gc_host_t *host (the host the stream was registered on). Env enum: gc_env_options__usage_step renamed gc_env_options__host_perf_step (same slot; --host_perf_step, default 60s); new gc_env_options__max_args_memory (--max_args_memory, 1 MiB β larger RPC args stream to the task's arguments file, Task::body() β null). See api_runtime_storage.md.gc_task_class_t (small/regular/large); gc_periodic_options_t gained u8_t task_class in former padding (layout unchanged, 24 bytes; {} gives small β set it). New gc_task__expected_steps(ctx, n) / gc_task__add_steps(ctx, n). Stdlib: enum TaskClass, Job.task_class, PeriodicOptions.task_class, Task::task_class(), SSE task-started frame. See api_runtime_storage.md, standard_library.md.RuntimeUsage / WorkerUsage / ZoneUsage / Runtime::usage() removed β HostPerf log record; LogDataUsage β TaskPerf (once per task); Log.id / id2 β task_id / job_id. See standard_library.md.gc_buffer_options_t.type_tag (write _type on every object) and GC_COLOR_BRAND; stdlib JsonWriter.type_tag, JsonReader.type_tag / Json.type_tag: JsonTypeTag? (lenient / strict). See api_memory_text.md, standard_library.md.gc_program__create_function(program, mod_offset, type_offset, function_name_offset, *result_offset) now public; gc_program__create_from_abi rebuilds symbols, type offsets, inheritance and exposed functions. See api_core.md.runtime::Request removed β its header / headers / uri / body moved to Task:: (Request::x β Task::x; headers() now Map<String, String>?, null when the task answers no request). Every RPC call now runs as a task on tiered worker pools (visible in Task::running/history, cancellable; --request_ttl cancels queued/running RPCs with 503). Env enum: gc_env_options__req_workers renamed gc_env_options__workers_small (same slot/value β ABI-stable, source-breaking for code naming the old enumerator; CLI --req_workers β --workers_small / GREYCAT_WORKERS_SMALL); new gc_env_options__workers_large (--workers_large) appended before gc_env_options_len. See api_runtime_storage.md, standard_library.md.gc_map__get_key(map, key, key_type, *stored_key_type, prog) β returns the map's own (deep-copied-on-insert) key object for a deep-equal probe key; borrowed, not marked. Needed to alias the key object the VM hands out when iterating a map (fix behind task suspend/resume of retained map keys). See api_collections.md.gc_time__join_us(epoch_s, us_offset) β static inline inverse of gc_time__split_us, recombining seconds + sub-second Β΅s in u64_t to avoid signed-overflow UB at time::min. Use it instead of open-coding s * 1000000 + us. See api_services.md.gc_slot__save / _save_value / _load / _load_value are now gc_sdk-exported (reliably linkable from plugins). Signatures unchanged. See api_memory_text.md.gc_env_options_offset_t gained gc_env_options__openapi (GREYCAT_OPENAPI / --openapi, default on). Stdlib: OpenApi::v3() is now @permission("public") and, by default, documents every @exposed function (not just @tag("openapi")), filtered by the caller's permissions. See api_runtime_storage.md, standard_library.md.gc_program_type__inherits(prog, type, target_type_id) β true if type inherits target_type_id at any depth, matching a bare generic through its monomorphized parent (like gc_program__is_type). gc_program_function gained bool is_raw (@raw: exposed result written as text/plain, not JSON). See api_core.md.GC_ARRAY_CELL_SIZE (bytes per element of the single slots+types block) in api_collections.md; gc_buffer_write_check gained an inline fast path (same semantics) β api_memory_text.md.XmlReader<T> and S3 / S3Bucket / S3Object / S3BasicCredentials removed from std::io. XML moved unchanged to the xml library (@library("xml", ...)); S3 is a rewrite in the s3 library (s3::Client, sigv4::Credentials, different method names) β existing S3 code must be rewritten. New stdlib: request access header / headers / uri / body (first as runtime::Request, now on Task:: β see above; authorization/cookie hidden) and Crypto::equals_constant_time(a, b) for webhook signature checks. See standard_library.md.gc/stream.h (not included by greycat.h) β public C API behind io::Stream<T>, all gc_sdk: gc_stream__register / _find / _append / _write / _reader / _last_written_pos / _name / _path / _type over opaque gc_stream_t. Stdlib: new io::Stream<T> (append-only per-user NDJSON, batched callback within max_dephasing, optional fsync-per-write). See api_runtime_storage.md, standard_library.md.files/root/log.csv to the log stream files/root/streams/log.ndjson; Log.time lost @format(DurationUnit::microseconds). Read with Stream::get("log", Log).reader(from) / JsonReader<Log>.Array.sort / sort_by / Table.sort put null keys last in both orders; int/float mixes sort numerically. C side: new gc_sort__by_key(out, count, key, key_ctx, asc, allocator) + gc_sort_key_fn in gc/util.h implement exactly that. See api_services.md.static inline time helpers: gc_time__join_us_checked(epoch_s, us_offset, *out) (bounds-checked join for untrusted instants, false instead of wrapping) and gc_time__utc_offset(epoch_us, localized_epoch_s) (floored UTC offset for gc_strftime_safe). See api_services.md.gc_buffer__add_function_name(self, fn, prog) (bare name, or GC_PROGRAM_LAMBDA_NAME = "<lambda>"); gc_program_function.name_off == 0 marks a lambda / reserved slot 0. See api_memory_text.md, api_core.md.gc_env_options_offset_t gained __max_sse, __max_sse_per_user, __url, __token. Stdlib: new Task::events() β SSE stream on GET /runtime::Task::events (task-progress / task-complete frames), capped by max_sse (2048) / max_sse_per_user (16). See api_runtime_storage.md, standard_library.md.Previously, in 8.3 (headers re-verified 2026-09-16 against upstream 024068c6e):
gc_mktime_safe's year-bound macros removed (GC_MKTIME_MAX_TM_YEAR, GC_MKTIME_MIN_YEAR, GC_MKTIME_MAX_YEAR). No replacement β code referencing any of the three no longer compiles. Not just dead-code cleanup: the year-at-a-time loop those macros bounded (Β±10000 years) was replaced by a constant-time closed-form inverse, so gc_mktime_safe's usable range widened to whatever tm_year can hold, fixing time::min/time::max round-tripping through Date (ffa41d71d). See api_services.md.gc_time__split_us(epoch_us, *us_offset). static inline helper (no linking needed) that floor-divides a microsecond epoch into a whole-second count and a non-negative sub-second remainder β the shape gc_gmtime_r_safe/gc__print_iso expect. Hand-rolled //% truncates toward zero, so it produced a negative remainder (rendering as .-500) for pre-1970 instants with a sub-second part; this is the fix behind GreyCat now reading back the ISO timestamps it writes (b12f39d05). See api_services.md.gc_crypto__sha256_init / _update / _done). Companion to the existing one-shot gc_crypto__sha256, for input that isn't contiguous in memory β a file read chunk by chunk, a stream. _done invalidates the context; re-init before hashing again. See api_services.md.gc_buffer__add_type_name_by_id is back. Writes a type as module::Type (a specialized generic prints as core::Array<core::int>); re-added after being removed in 8.2. Its two siblings, gc_buffer__add_type_name and gc_buffer__add_type_qname, remain gone with no replacement. See api_memory_text.md.gc_machine__create / gc_machine__destroy, promoted from internal to public API β allocate/free a standalone gc_machine_t bound to a gc_host_t outside the machine a native function normally receives from the host (e.g. a per-thread machine in a request-handling loop). See api_core.md.gc_env_options_offset_t gained gc_env_options__registry / gc_env_options__registry_token β the registry URL and auth token for resolving libraries/releases via GREYCAT_REGISTRY, a CLI feature; most plugin authors won't touch these. See api_runtime_storage.md.gc_object__is_instance_of now accounts for monomorphization β a Box<String> instance now tests true against the generic declaration Box, not just exact-type/inheritance matches as before. Signature unchanged. See api_memory_text.md.runtime::Log.time is explicitly @format(DurationUnit::microseconds) β the field was always written as raw epoch microseconds by the log writer, now the type declares it instead of leaving it implicit. Reading files/root/log.csv with CsvReader<runtime::Log> needs CsvFormat { string_delimiter: '\0' }, since the payload column is written unescaped. See standard_library.md.Previously, in 8.2 (headers re-verified 2026-08-28 against upstream e1e7edf54):
gc_dtz_time__parse_format(str, len, format, format_len, tz, out_epoch_us) β parses a date/time string against an explicit format, the counterpart of gc_dtz_time__print and the custom-format arm of gc_dtz_time__parse. A format leaves the instant naive, so tz is the zone it is read in; returns false when the input does not match the format, or names an instant the zone does not have. See api_services.md.gc_program_library. Two optional gc_hook_function_t * fields β install and codegen β with setters gc_program_library__set_install_hook(lib, hook) and gc_program_library__set_codegen_hook(lib, hook). The install hook runs at the end of a successful greycat install (after the project is rebuilt and linked) for every library that registers one; returning false fails the command. The codegen hook makes the library itself a generator for greycat codegen <lang>, dispatched by matching <lang> against library names (the built-in c/ts/java/rust/python2 generators stay native to the CLI). Both are purely additive β a NULL hook opts out. See plugin_development.md, api_core.md.gc/env.h defines gc_env_slot_t (tagged-by-convention union: bool/i64_t/u64_t/f64_t/char *) and gc_env_options_offset_t (one variant per CLI/.env-resolved option, e.g. gc_env_options__port, gc_env_options__ca_path, terminated by the length marker gc_env_options_len). The new gc_host__options(host) returns a const gc_env_slot_t * array indexed by that enum, valid for the life of the host. gc/ca.h's new gc_ssl_ca__pem_bundle(u64_t *len) hands out the resolved TLS trust chain (system CA store + ca_path) as a PEM byte string, owned by the runtime β for libraries that verify their own TLS connections and want to honor the same trust config as the host. Both headers are pulled in automatically via greycat.h. Full detail: api_runtime_storage.md.gc_machine__this_type(self) β the gc_type_t counterpart to gc_machine__this; returns gc_type_undefined (and must not be called) when the current frame has no receiver. New: gc_abi__finalize_ex(abi) β releases everything a load allocated but not abi itself, for callers that own a gc_abi_t inline rather than through gc_abi__create. Both added for the Rust SDK rewrite. See api_core.md, api_runtime_storage.md.gc_slot_t union field order changed. u64_t u64 is now the first member (previously bool b was first), to stop positional (non-designated) initializers from silently truncating through bool. Any code using a non-designated gc_slot_t initializer ((gc_slot_t){x} with no .field =) now targets .u64 instead of .b β designated initializers ({.i64 = x}, {.object = p}, etc., used throughout this SDK's own examples) are unaffected. See api_core.md.gc/buffer.h type-name helpers removed, no replacement. gc_buffer__add_type_name, gc_buffer__add_type_name_by_id, and gc_buffer__add_type_qname are gone from the header entirely (briefly exported, then removed again in the same cycle β never shipped as stable). Code calling any of the three no longer compiles. Every remaining gc/buffer.h function is now uniformly gc_sdk-exported (some previously lacked the export macro and could fail to link from a plugin shared library on strict-visibility builds) β this includes the pre-existing non-static inline writer functions (gc_buffer__write_u8 / _bool / _u16 / _u32 / _u64 / _u64_at / _f64 / _vu32 / _vu64 / _vi64 / _ptr), now reliably linkable from callers (e.g. FFI bindings) that can't call a C static inline function. See api_memory_text.md.Previously, in 8.1 (headers re-verified 2026-08-04 against upstream 78e57676d): gc/buffer.h's read side was overhauled β every unchecked inline reader (gc_buffer_read_bool / _u8 / _i8 / _u16 / _u32 / _i32 / _u64 / _i64 / _f32 / _f64 / _vu32 / _vu64 / _vi64) was removed, replaced by bool-returning _size_checked equivalents; new gc_buffer_read_ptr_size_checked and GC_VU32_MAX_BYTES; gc_buffer_unavailable params are now const-qualified and it no longer special-cases NULL/underflowed cursors (breaking for any code relying on the removed void-returning readers or that fail-closed guard). gc/table.h's gc_table__init (param renamed tableβself) now leaves the table untouched on allocation failure β callers must check self->capacity. Full detail: api_memory_text.md, api_collections.md.
gc_allocator_t *: per-call scratch from gc_machine__allocator(ctx) (= ((gc_ctx_t *)ctx)->allocator), plugin-global state from gc_host__global_allocator(). gc_alloc__create(bool shared) (true = multi-thread arena). gc_alloc__free(a, ptr, size) requires the original size. The thread-bound gc_malloc / gc_free / gc_realloc helpers target whatever gc_alloc__bind set. New in 8.1: gc_alloc__reset(allocator) β destructively resets a whole arena (reclaims even leaked/lost pointers on the jemalloc path), invalidating every prior pointer from that allocator; use between iterations of a long-lived worker loop after tearing everything down, no-op on native-malloc/WASM/standalone. Sizing/stats and full patterns: api_memory_text.md.gc/log.h). gc_log_level_t is none / error / warn / info / perf / trace. Use gc_log__machine / gc_log__machinef (VM context) and gc_log__host / gc_log__hostf (host context); gate hot paths with gc_log__enabled(host, level).gc/str.h: gc_str_t and the gc_core_str / gc_core_t2β¦t4f globals are not public. Use gc_string_t (heap, immutable, hash-cached; buffer IS NUL-terminated at buffer[size] β but size is still the authoritative content length, since the content itself may embed NUL bytes).gc_machine__call_function takes a const gc_program_function_t *fn (not a raw body pointer). On false the result is a synthesized Error object (type gc_core_Error) and *marked_res_type is gc_type_object; the caller owns one mark on the result. gc_machine__impersonate(ctx, user_id) switches the effective user for permission-aware sub-calls.gc/host.h). gc_host__cancel_task(self, task_id, requester_id, requester_permissions, out_task) is now thread-safe and permission-checked (out_task optional, receives a copy of the cancelled task); gc_host__get_task_status still takes just i64_t task_id. gc_host__spawn_task takes a u64_t user_permissions mask. Periodic scheduling via gc_scheduler_t, gc_periodic_task_t, and gc_periodicity_t (a struct holding a gc_periodicity_type_t type: fixed / daily / weekly / monthly / yearly). New in 8.2: gc_host__options(self) (resolved CLI/env/.env config, see gc/env.h) and gc/ca.h's gc_ssl_ca__pem_bundle(len) (resolved TLS trust chain). See api_runtime_storage.md.GC_ABI_PROTO is 3. gc_abi_header_check_error_t includes ..._truncated = 4; gc_abi_t carries its own allocator. New in 8.2: gc_abi__finalize_ex(abi) for inline-owned gc_abi_t instances (frees the load's allocations, not abi itself).gc_block_t gained u64_t node_ref (new in 8.1) β the node reference the block backs, used by suspend/resume serialization to relocate the block's entries. See api_runtime_storage.md.gc_program_iterator_param_t: from=0, to=1, nullable=2, from_excl=3, to_excl=4 (no limit). Geo epsilon constant is GC_CORE_GEO_EPS.gc_tensor_t / gc_tensor_descriptor_t (formerly gc_core_tensor_t / gc_core_tensor_descriptor_t); the gc_core_tensor__* and gc_core_tensor_descriptor__* function names are unchanged, and gc_machine__init_tensor now takes/returns the renamed types. Plugin code that referenced the old struct typedefs must be updated. Full tensor API: api_collections.md.Identity / IdentityGrant / IdentityGrantType (the old User / UserGroup / SecurityPolicy / OpenIDConnect types are gone). GCL logging is via module-level info / warn / error / perf / trace functions β Log is a parse record, not a callable namespace. Remaining 8.0 surface (the HttpMethod/HttpRequest/HttpResponse model (HttpRequest.headers is Map<String, String>?, HttpResponse.headers is Map<String, String>), Csv::analyze(Array<String>), Uuid v4/v7, periodicity field shapes, LogLevel/TaskStatus/LicenseType enums): standard_library.md.ProgressTracker.update(nb) is now absolute, not incremental (breaking). It sets the step counter to nb rather than adding nb to it. New fields speed_smoothed (EMA of the per-update pace) and smoothing (EMA weight, default ProgressTracker.DEFAULT_SMOOTHING = 0.1) drive a more reactive remaining estimate. Also new since the last sync: HttpRequest.max_response_size (caps chunked/unbounded response reads) and Task::live(ids) / Task::tasks(ids) (bulk liveness check / bulk fetch by id).TensorDistance gained lorentz and poincare (hyperbolic distances β Lorentz/hyperboloid model and PoincarΓ© ball model, both curvature fixed at -1). Identity.set_role(name, role) is a new admin-only static native β it returns nothing (void), not bool.gc_common__parse_number's str_len param widened u32_t * β u64_t * (its sibling gc_common__parse_sign_number is still u32_t * β the two now disagree, match the local variable's type to the callee). gc_buffer_read_vu64_size_checked added (the u64_t counterpart of the existing _vu32_ variant), joined later in 8.1 by gc_buffer_read_vi64_size_checked (zig-zag signed) and the GC_VU64_MAX_BYTES (= 9) worst-case varint width constant. gc_object__clone, gc_array__fill, and gc_array__ensure_capacity (replaced gc_array__init; now grow-only/idempotent, rounds to a power of two, preserves contents) round out the collections/memory surface. Stdlib: Task.duration: duration? was replaced by Task.completion: time?, Task gained user_name: String, and nodeGeo<T> gained search(center: geo, max: int): Array<SearchResult<geo,T>>.gc_machine_t - Execution context passed to all native functions. Use to get parameters, set results, report errors, create objects, and access scratch buffers.
gc_slot_t - Universal value container (tagged union) holding any GreyCat value: integers, floats, bools, objects, enums, tuples, etc.
gc_type_t - Type system enum (8-bit, 24 values) defining all GreyCat types: null, bool, char, int, float, node variants, geo, time, duration, cubic, static_field, object, block_ref, block_inline, function, undefined, type, field, stringlit, error.
gc_object_t - Generic handle for heap-allocated objects. Packed to 128 bits. Every collection type (Array, Map, Table, Tensor, String, Buffer) starts with this as its first member.
The few patterns below are the trigger-level essentials. Full runnable examples for every operation (objects, tensors, arrays, maps, strings, buffers, allocators, logging, introspection) live in the five C-API reference files listed under Detailed Reference.
Parameters & results:
gc_slot_t p = gc_machine__get_param(ctx, 0); gc_type_t t = gc_machine__get_param_type(ctx, 0);
u32_t n = gc_machine__get_param_nb(ctx); gc_slot_t self = gc_machine__this(ctx); // instance methods
gc_machine__set_result(ctx, (gc_slot_t){.i64 = 42}, gc_type_int);
gc_machine__set_result(ctx, (gc_slot_t){.object = obj}, gc_type_object);
gc_object__un_mark(obj, ctx); // CRITICAL: every object result must be un-marked or it leaks / GC-faults
Enum parameters & results (CRITICAL β #1 native bug). GCL enum values are NOT gc_type_int; they are gc_type_static_field with the ordinal in .tu32.right (.tu32.left is the enum type offset), never .i64:
// WRONG β always hits the default fallback: (type == gc_type_int) ? slot.i64 : 0
// CORRECT:
i64_t variant = (gc_machine__get_param_type(ctx, 0) == gc_type_static_field) ? (i64_t) slot.tu32.right : 0;
// Returning MyEnum::variant2 (ordinal 1):
gc_machine__set_result(ctx, (gc_slot_t){.tu32 = {.left = 0, .right = 1}}, gc_type_static_field);
Errors: gc_machine__set_runtime_error(ctx, "msg") / gc_machine__set_runtime_error_syserr(ctx) (uses errno); check propagated errors with if (gc_machine__error(ctx)) return;.
The C API reference is split by domain β each file below is linked directly (one level deep) and loads on demand.
references/api_core.md β value model, execution context, type system, logging. Start here.
gc_machine_t (params, result, errors, gc_machine__allocator, gc_machine__impersonate, gc_machine__call_function via gc_program_function_t *, object creation)gc_type_t / gc_slot_t value model, complex c64/c128 arithmetic, gc_node__parsegc_program__link_mod_fn / gc_program__link_type_fn), type configuration, introspection, iterator params, DurationUnitgc/log.h) and the cross-cutting Conventions & Patterns indexreferences/api_memory_text.md β memory, buffers, strings, objects
gc_alloc__create(bool shared), sizing/statsgc_string_t, allocator-aware constructors)references/api_collections.md β array, map, table, tensor
init_Nd, get/set/add for i32/i64/f32/f64/c64/c128, descriptor utilities, raw data access, matmul/bias/sumreferences/api_runtime_storage.md β runtime, persistence, graph nodes
gc_scheduler_t, gc_periodic_task_t), plugin-global allocator (gc_host__allocator / gc_host__global_allocator)gc/stream.h, new in 8.4)gc_node__resolve, gc_node__parse) and direct node-entry read/write (gc_machine_native__node_get / node_set_at via gc_node_single_value_t, released with gc_node_single_value__clear) β this u64_t node_ref API is uniform across all node variants (node, nodeTime, nodeList, nodeGeo, nodeIndex); only the key encoding differs per variant. Breaking in 8.1: node_get now takes an expected_type_id (gc_core_nodeTime / nodeList / nodeGeo / nodeIndex) before ctx and returns a null single plus a ctx runtime error if the resolved block is a different typereferences/api_services.md β crypto, geo, time, math, util
gc_dtz_time__print / parse)Task::header/body/...), License, OpenAPI, MCPxml / s3 libraries in 8.4)File: references/standard_library.md
Load when working with:
Task::header/Task::body)Contains: Complete documentation for all four standard library modules with code examples, usage patterns, and best practices.
Build native GreyCat plugins in C with proper lifecycle management, type configuration, and thread safety.
Function naming (CRITICAL β must match nativegen): gc_<module>_<Type>__<methodName>(gc_machine_t *ctx)
When GreyCat compiles GCL code with native declarations, it auto-generates nativegen.c / nativegen.h. Those extern declarations define the exact C symbol names the runtime resolves at dlopen β your C definitions MUST match or you get undefined symbol errors. Convention:
gc_<gcl_module>_<GclType>__<methodName> (double underscore before the method)gc_<gcl_module>__<functionName> (double underscore before the function)<gcl_module> is the GCL file's module path; <GclType> matches the GCL type (PascalCase); <methodName> matches the GCL method (camelCase)// GCL (module "text_normalizer", type TextNormalizer):
// native static fn rejoinHyphenatedWords(text: String): String;
// nativegen.h generates:
// extern void gc_text_normalizer_TextNormalizer__rejoinHyphenatedWords(gc_machine_t *ctx);
// Your C implementation MUST be named exactly:
void gc_text_normalizer_TextNormalizer__rejoinHyphenatedWords(gc_machine_t *ctx) { ... }
Plugin lifecycle: link -> lib_start -> [worker_start -> native calls -> worker_stop] -> lib_stop
Type configuration: gc_program_type__configure(prog, type_id, sizeof(my_struct_t), finalizer)
Library hooks:
gc_program_library__set_lib_hooks(lib, lib_start, lib_stop);
gc_program_library__set_worker_hooks(lib, worker_start, worker_stop);
gc_program_library__set_install_hook(lib, lib_install); // optional: end of `greycat install`
gc_program_library__set_codegen_hook(lib, lib_codegen); // optional: `greycat codegen <lang>`
File: references/plugin_development.md
Load when:
Contains: Complete project structure, CMake configuration, GCL type definitions, nativegen implementation, lifecycle hooks, custom type configuration with finalizers, global state management, memory management patterns, parameter handling (including type checking with gc_object__is_instance_of), result returning, error handling, conditional logging, and a full end-to-end plugin example.