#ifndef CCDJIT_PUBLIC_H #define CCDJIT_PUBLIC_H /* * CCDJIT public embedding API * * This header is the stable boundary for hosts that want to compile and run C * code at runtime. The implementation lives in ccdjit.c, but the intended use * should be clear from this file alone. * * Basic use: * * ccdjit_context *ctx = ccdjit_context_new(NULL); * ccdjit_module *module = NULL; * * if (ccdjit_compile_string(ctx, source, "plugin.c", &module) == 0) { * int result = 0; * ccdjit_module_call_main(module, 0, NULL, &result); * ccdjit_module_free(module); * } * ccdjit_context_free(ctx); * * Return convention: * Functions returning int use 0 for success and -1 for failure unless * documented otherwise. On failure, call ccdjit_context_last_error(). * * Lifetime: * A context owns options, include paths, registered symbols, and the last * diagnostic. A module owns generated code or a loaded CCDJIT binary. * Modules keep their context state alive, so ccdjit_context_free() may be * called before ccdjit_module_free(); the context is released when the * last module using it is freed. * Function pointers returned by ccdjit_module_symbol() are invalid after * ccdjit_module_free(). * * Runtime policy: * Module calls, symbol lookup, and emission intentionally consult the * current context options, sandbox settings, resource limits, registered * symbols, resolver callback, and loaded libraries. Machine code that * has already been compiled may contain resolved external addresses; use * a new module or function-handle replacement when changing symbol * bindings must affect already compiled call sites. * * Threads: * Public API entry points are internally serialized, and module calls, * symbol lookup, emission, and lazy compilation take a module lock. Raw * function pointers returned by ccdjit_module_symbol() are still host * pointers: do not free or replace their module while those pointers may * run, and prefer typed handle/call helpers for dynamic bindings. * * Safety: * Normal calls run in the host process. Set sandbox_execution to * CCDJIT_SANDBOX_PROCESS to run ccdjit_module_call_main() in a child * process with optional resource, syscall, and filesystem limits. Child * crashes are reported through ccdjit_diagnostic. */ #include #include #include #ifdef __cplusplus extern "C" { #endif #define CCDJIT_VERSION_MAJOR 1 #define CCDJIT_VERSION_MINOR 0 #define CCDJIT_VERSION_PATCH 0 #define CCDJIT_VERSION_STRING "1.0.0" #define CCDJIT_API_ABI_VERSION 1 #define CCDJITB2_FORMAT_VERSION 2 #define CCDJITB2_FLAG_DEBUG_INFO 1u typedef struct ccdjit_context ccdjit_context; typedef struct ccdjit_module ccdjit_module; typedef struct ccdjit_function_handle ccdjit_function_handle; typedef struct ccdjit_execution_result ccdjit_execution_result; typedef struct { /* Host compiler or linker driver. NULL uses $CC, then cc. */ const char *driver; /* Extra linker arguments such as -L, -l, or -Wl options. */ const char *const *link_args; size_t link_arg_count; /* Print the host linker command to stderr when nonzero. */ int verbose; /* Wall-clock driver timeout. Zero uses the default. */ unsigned int timeout_ms; /* Captured driver stdout/stderr cap. Zero uses the default. */ size_t output_limit_bytes; } ccdjit_link_options; /* * Host symbol callback. Return NULL when the name is unknown. * * The returned pointer is used as an external function or object address by * generated code. Keep any pointed-to object alive while modules using it may * run. */ typedef void *(*ccdjit_symbol_resolver)(const char *name, void *userdata); typedef enum { /* Run generated code directly in the host process. */ CCDJIT_SANDBOX_OFF = 0, /* Run ccdjit_module_call_main() in a forked child process. */ CCDJIT_SANDBOX_PROCESS = 1, } ccdjit_sandbox_mode; typedef enum { /* * Minimal child profile: enough to write the result pipe, write * captured output, and exit. Unexpected syscalls are trapped. */ CCDJIT_SANDBOX_SYSCALL_STRICT = 0, /* STRICT plus read/close for simple stdio-style code. */ CCDJIT_SANDBOX_SYSCALL_STDIO = 1, /* STDIO plus mmap/munmap/mprotect/brk/clock_gettime. */ CCDJIT_SANDBOX_SYSCALL_RUNTIME = 2, /* Disable the seccomp filter. Resource limits still apply. */ CCDJIT_SANDBOX_SYSCALL_PERMISSIVE = 3, } ccdjit_sandbox_syscall_policy; typedef enum { /* Runtime file syscalls are denied by the syscall policy. */ CCDJIT_SANDBOX_FS_NONE = 0, /* Permit read-only access under explicitly added roots. */ CCDJIT_SANDBOX_FS_READ_ONLY_ROOTS = 1, /* Permit writes only for roots added with writable != 0. */ CCDJIT_SANDBOX_FS_WRITABLE_ROOTS = 2, } ccdjit_sandbox_filesystem_policy; typedef struct { /* Allow #include to read files from include paths. */ int allow_filesystem_includes; /* Allow the preprocessor to discover and read host system includes. */ int allow_system_includes; /* Allow executable mappings for JIT code and loaded CCDJIT binaries. */ int allow_executable_memory; /* ccdjit_sandbox_mode. Default is CCDJIT_SANDBOX_OFF. */ int sandbox_execution; /* ccdjit_sandbox_syscall_policy. Default is STRICT. */ int sandbox_syscalls; /* ccdjit_sandbox_filesystem_policy. Default is FS_NONE. */ int sandbox_filesystem; /* * In sandbox mode, default dlsym lookup is disabled unless this is set. * Prefer explicit ccdjit_context_register_symbol() capabilities. */ int sandbox_allow_implicit_symbols; /* Wall-clock timeout for one sandboxed main call. Zero means no limit. */ unsigned int sandbox_timeout_ms; /* RLIMIT_CPU for sandboxed calls, rounded up to seconds by the OS. */ unsigned int sandbox_cpu_time_ms; /* RLIMIT_AS for sandboxed calls. Zero means keep the host default. */ size_t sandbox_address_space_bytes; /* RLIMIT_STACK for sandboxed calls. Zero means keep the host default. */ size_t sandbox_stack_bytes; /* RLIMIT_FSIZE for sandboxed calls. Zero means keep the host default. */ size_t sandbox_file_size_bytes; /* Captured stdout/stderr cap for sandboxed calls. Zero means 64 KiB. */ size_t sandbox_output_limit_bytes; /* Emit and retain optional debug/source mapping metadata. */ int debug_info; /* * Preprocessor output byte cap. Zero uses the default currently 128 * MiB. */ size_t preprocessor_output_limit_bytes; /* Macro expansion recursion cap. Zero uses the default. */ unsigned int preprocessor_macro_depth_limit; /* Nested include cap. Zero uses the default. */ unsigned int preprocessor_include_depth_limit; /* * Optional compiler driver used for host system include and builtin * macro probes. NULL auto-detects from $CC, then common cc drivers. */ const char *preprocessor_compiler; /* Wall-clock timeout for one compiler probe. Zero uses the default. */ unsigned int preprocessor_probe_timeout_ms; /* Captured compiler-probe output cap. Zero uses the default. */ size_t preprocessor_probe_output_limit_bytes; } ccdjit_options; typedef struct { /* Compiler phase such as LEXER, PARSER, JIT, API, or SANDBOX. */ const char *phase; /* Source file, binary path, symbol name, or "". */ const char *filename; /* Human-readable error text owned by the context. */ const char *message; /* Optional source excerpt for lexer/parser/type diagnostics. */ const char *source_line; const char *caret_line; /* One-based line and column when available, otherwise zero. */ int line; int column; /* ccdjit_diagnostic_code plus optional code-specific detail. */ int code; int subcode; /* Raw child wait status or errno-style value for sandbox/API failures. */ int status; /* Signal that terminated the sandbox child, or zero. */ int signal_number; /* Faulting address for sandboxed crash diagnostics, when available. */ int has_fault_address; uintptr_t fault_address; } ccdjit_diagnostic; typedef enum { CCDJIT_DIAG_NONE = 0, CCDJIT_DIAG_LEXER = 1, CCDJIT_DIAG_PARSER = 2, CCDJIT_DIAG_PREPROCESSOR = 3, CCDJIT_DIAG_TYPE = 4, CCDJIT_DIAG_JIT = 5, CCDJIT_DIAG_UNRESOLVED_SYMBOL = 6, CCDJIT_DIAG_SANDBOX = 7, CCDJIT_DIAG_SANDBOX_TIMEOUT = 8, CCDJIT_DIAG_SANDBOX_FORBIDDEN_SYSCALL = 9, CCDJIT_DIAG_SANDBOX_CRASH = 10, CCDJIT_DIAG_INVALID_OPTIONS = 11, CCDJIT_DIAG_INVALID_ARGUMENT = 12, CCDJIT_DIAG_UNSUPPORTED_ABI = 13, CCDJIT_DIAG_MALFORMED_BINARY = 14, CCDJIT_DIAG_HOST_LINKER = 15, CCDJIT_DIAG_OUT_OF_MEMORY = 16, } ccdjit_diagnostic_code; typedef enum { CCDJIT_SYMBOL_UNKNOWN = 0, CCDJIT_SYMBOL_FUNCTION = 1, CCDJIT_SYMBOL_OBJECT = 2, } ccdjit_symbol_kind; typedef enum { CCDJIT_TYPE_INT = 0, CCDJIT_TYPE_CHAR = 1, CCDJIT_TYPE_BOOL = 2, CCDJIT_TYPE_VOID = 3, CCDJIT_TYPE_FLOAT = 4, CCDJIT_TYPE_DOUBLE = 5, CCDJIT_TYPE_LONG = 6, CCDJIT_TYPE_SHORT = 7, CCDJIT_TYPE_STRUCT = 8, CCDJIT_TYPE_ENUM = 9, CCDJIT_TYPE_UNION = 10, CCDJIT_TYPE_FLOAT128 = 11, } ccdjit_type_base; typedef struct { int has_type; int base; int pointer_level; int array_size; int is_unsigned; int is_complex; int is_atomic; } ccdjit_type_info; typedef struct { int kind; int has_signature; ccdjit_type_info type; ccdjit_type_info return_type; size_t parameter_count; int is_variadic; size_t size; } ccdjit_symbol_info; typedef struct { int process; int seccomp; int landlock; } ccdjit_sandbox_capabilities; typedef struct { const char *filename; const char *function; int line; int column; uintptr_t address; size_t size; } ccdjit_debug_location; /* * Create a compiler context. Passing NULL uses permissive CLI-like defaults. * Invalid options return NULL; call ccdjit_context_last_error(NULL) for the * thread-local creation diagnostic. */ ccdjit_context *ccdjit_context_new(const ccdjit_options *options); int ccdjit_version_major(void); int ccdjit_version_minor(void); int ccdjit_version_patch(void); const char *ccdjit_version_string(void); int ccdjit_api_abi_version(void); int ccdjit_binary_format_version(void); const char *ccdjit_git_commit(void); int ccdjit_sandbox_capabilities_query(ccdjit_sandbox_capabilities *out); /* Free the context and all context-owned diagnostics/options/lists. */ void ccdjit_context_free(ccdjit_context *ctx); /* Add a preprocessor include search path, like -I. */ int ccdjit_context_add_include_path(ccdjit_context *ctx, const char *path); /* Restrict filesystem includes to this root when include roots are used. */ int ccdjit_context_add_include_root(ccdjit_context *ctx, const char *path); /* Add a preprocessor definition, like -DNAME or -DNAME=value. */ int ccdjit_context_add_define(ccdjit_context *ctx, const char *define); /* Grant generated code an explicit external symbol capability. */ int ccdjit_context_register_symbol(ccdjit_context *ctx, const char *name, void *addr); /* Add an explicit shared library path for external symbol lookup. */ int ccdjit_context_add_library(ccdjit_context *ctx, const char *path); /* Install a fallback external symbol resolver. */ void ccdjit_context_set_symbol_resolver(ccdjit_context *ctx, ccdjit_symbol_resolver resolver, void *userdata); /* Update sandbox mode, syscall policy, filesystem policy, and symbol policy. */ int ccdjit_context_set_sandbox_options(ccdjit_context *ctx, int mode, int syscall_policy, int filesystem_policy, int allow_implicit_symbols); /* Update all sandbox resource limits in one call. */ int ccdjit_context_set_sandbox_limits(ccdjit_context *ctx, unsigned int timeout_ms, unsigned int cpu_time_ms, size_t address_space_bytes, size_t stack_bytes, size_t file_size_bytes, size_t output_limit_bytes); /* Update preprocessor resource limits. Passing zero keeps the default. */ int ccdjit_context_set_preprocessor_limits(ccdjit_context *ctx, size_t output_limit_bytes, unsigned int macro_depth, unsigned int include_depth); /* Select the compiler driver used by system include and builtin macro probes. */ int ccdjit_context_set_preprocessor_compiler(ccdjit_context *ctx, const char *compiler); /* Update compiler-probe timeout/output limits. Passing zero keeps defaults. */ int ccdjit_context_set_preprocessor_probe_limits(ccdjit_context *ctx, unsigned int timeout_ms, size_t output_limit_bytes); /* Free this thread's cached compiler probe results and system include paths. */ void ccdjit_preprocessor_free_probe_cache(void); /* Enable or disable executable memory for future compile/load/call paths. */ int ccdjit_context_set_executable_memory(ccdjit_context *ctx, int allow); /* Enable or disable optional debug/source mapping metadata. */ int ccdjit_context_set_debug_info(ccdjit_context *ctx, int enable); /* * Add a Landlock filesystem root for sandboxed execution. * * writable is only honored when sandbox_filesystem is * CCDJIT_SANDBOX_FS_WRITABLE_ROOTS. On systems without Landlock, requesting a * root-based filesystem policy fails while entering the sandbox. */ int ccdjit_context_add_sandbox_filesystem_root(ccdjit_context *ctx, const char *path, int writable); /* * Return the last error for this context. Passing NULL returns the * thread-local diagnostic for context creation or NULL-context API failures. * The pointer is context-owned or thread-local. */ const ccdjit_diagnostic *ccdjit_context_last_error(ccdjit_context *ctx); /* Return captured sandbox stdout/stderr from the last sandboxed main call. */ const char *ccdjit_context_last_stdout(ccdjit_context *ctx); const char *ccdjit_context_last_stderr(ccdjit_context *ctx); /* * Owned execution result snapshot for bindings and concurrent hosts. * * Result objects keep copies of the call status, integer result, diagnostic, * captured stdout, and captured stderr. They are not invalidated by later * calls on the same context. */ void ccdjit_execution_result_free(ccdjit_execution_result *result); int ccdjit_execution_result_status(const ccdjit_execution_result *result); int ccdjit_execution_result_has_value(const ccdjit_execution_result *result); int ccdjit_execution_result_value(const ccdjit_execution_result *result); const ccdjit_diagnostic *ccdjit_execution_result_diagnostic( const ccdjit_execution_result *result); const char *ccdjit_execution_result_stdout( const ccdjit_execution_result *result); const char *ccdjit_execution_result_stderr( const ccdjit_execution_result *result); int ccdjit_execution_result_signal(const ccdjit_execution_result *result); int ccdjit_execution_result_has_fault_address( const ccdjit_execution_result *result); uintptr_t ccdjit_execution_result_fault_address( const ccdjit_execution_result *result); /* Compile a source file into a live JIT module. */ int ccdjit_compile_file(ccdjit_context *ctx, const char *path, ccdjit_module **out); /* Compile a source string into a live JIT module. */ int ccdjit_compile_string(ccdjit_context *ctx, const char *source, const char *filename, ccdjit_module **out); /* * Compile a source file into a live JIT module without requiring main(). * Use ccdjit_module_symbol() to find and call exported config/plugin symbols. */ int ccdjit_compile_file_module(ccdjit_context *ctx, const char *path, ccdjit_module **out); /* * Compile a source string into a live JIT module without requiring main(). * Use ccdjit_module_symbol() to find and call exported config/plugin symbols. */ int ccdjit_compile_string_module(ccdjit_context *ctx, const char *source, const char *filename, ccdjit_module **out); /* Convenience wrapper: compile a source string, call main, free the module. */ int ccdjit_eval_string(ccdjit_context *ctx, const char *source, const char *filename, int argc, char **argv, int *result_out); /* * Find a function or object symbol in a module. * * Calling the returned function pointer runs in the host process even when the * context has sandbox_execution enabled. Use ccdjit_module_call_main() for * sandboxed execution. */ void *ccdjit_module_symbol(ccdjit_module *module, const char *name); /* * Inspect a module symbol without forcing the caller to cast blindly. * * JIT functions expose return type, parameter count, variadic flag, and * per-parameter metadata through ccdjit_module_function_parameter_type(). * Object symbols expose their object type and size. Loaded CCDJITB2 binaries * expose function/object kind and size but not full C signatures. */ int ccdjit_module_symbol_info(ccdjit_module *module, const char *name, ccdjit_symbol_info *out); int ccdjit_module_function_parameter_type(ccdjit_module *module, const char *name, size_t index, ccdjit_type_info *out); /* * Return a callable function pointer only when the JIT signature exactly * matches the expected return and parameter types. */ void *ccdjit_module_function_symbol_checked(ccdjit_module *module, const char *name, const ccdjit_type_info *return_type, const ccdjit_type_info *parameter_types, size_t parameter_count, int allow_variadic); /* Map a live JIT instruction address to lightweight module-owned debug info. */ int ccdjit_module_debug_location(ccdjit_module *module, const void *address, ccdjit_debug_location *out); /* Call module main(argc, argv). Honors CCDJIT_SANDBOX_PROCESS. */ int ccdjit_module_call_main(ccdjit_module *module, int argc, char **argv, int *result_out); int ccdjit_module_call_main_result(ccdjit_module *module, int argc, char **argv, ccdjit_execution_result **out); /* * Call an exported JIT function with signature int name(int). * * Honors CCDJIT_SANDBOX_PROCESS like ccdjit_module_call_main(). Other * signatures and binary modules are rejected with a diagnostic. */ int ccdjit_module_call_i32(ccdjit_module *module, const char *symbol, int arg, int *result_out); int ccdjit_module_call_i32_result(ccdjit_module *module, const char *symbol, int arg, ccdjit_execution_result **out); /* * Compile and publish a named function behind a stable handle. * * The initial source and every replacement must define the same function * signature. Calls through the handle do not expose raw function pointers, so * replacements can wait for active callers and then free old generated code. */ int ccdjit_function_handle_new(ccdjit_context *ctx, const char *source, const char *filename, const char *symbol, ccdjit_function_handle **out); /* * Compile replacement source and atomically publish it on success. * * On compile failure or signature mismatch the old implementation remains * active and the context diagnostic describes the failure. */ int ccdjit_function_handle_replace(ccdjit_function_handle *handle, const char *source, const char *filename); /* Call a handle whose published function has signature int name(int). */ int ccdjit_function_handle_call_i32(ccdjit_function_handle *handle, int arg, int *result_out); /* Free a function handle and the currently published implementation. */ void ccdjit_function_handle_free(ccdjit_function_handle *handle); /* Emit textual assembly-like bytes for inspection. */ int ccdjit_module_emit_assembly(ccdjit_module *module, FILE *out); /* Emit a CCDJITB2 binary image. */ int ccdjit_module_emit_binary(ccdjit_module *module, FILE *out); /* Emit an ELF64 relocatable object. */ int ccdjit_module_emit_object(ccdjit_module *module, FILE *out); /* * Emit a linked host executable. * * This depends on the host compiler driver. It is intended for trusted build * paths, not sandboxed execution. */ int ccdjit_module_emit_executable_file(ccdjit_module *module, const char *path, const ccdjit_link_options *options); /* Emit a CCDJITB2 binary image directly to a path. */ int ccdjit_module_emit_binary_file(ccdjit_module *module, const char *path); /* Load a CCDJITB2 binary image into executable memory. */ int ccdjit_module_load_binary(ccdjit_context *ctx, const char *path, ccdjit_module **out); /* Free generated code, loaded binary storage, AST, and module-owned source. */ void ccdjit_module_free(ccdjit_module *module); #ifdef __cplusplus } #endif #endif