Fix documentation

This commit is contained in:
alejo committed 2026-09-25 03:20:35 -03:00
1 parent a317c07124
commit ab21b3f30d
27 files changed
+2137 -1043

No files matched your search

+451 -200
View File
@@ -1,79 +1,126 @@
/* hot_reload.h
/**
* @file hot_reload.h
* @brief Public C API for the hot-reload library.
*
* Public C API for the hot-reload library -- see cpp-style-guide.md
* Part II (C API Style) for the naming/formatting rules this header
* follows, and section 11 ("Runtime Loading") for why this boundary is
* plain C in the first place: anything crossing a dlopen()/LoadLibrary()
* boundary must have C linkage, so this is exactly the kind of surface
* that section calls out.
* See @c cpp-style-guide.md Part II (C API Style) for the naming/formatting
* rules this header follows, and section 11 ("Runtime Loading") for why
* this boundary is plain C in the first place: anything crossing a
* `dlopen()`/`LoadLibrary()` boundary must have C linkage, so this is
* exactly the kind of surface that section calls out.
*
* WHAT THIS LIBRARY DOES
* -----------------------
* hot_reload_load() loads a shared library (.so/.dll) under a logical
* name. hot_reload_reload() loads a NEW build of that same shared
* library and, for every function the two builds have in common,
* rewrites the FIRST FEW BYTES of the old function in memory into a
* jump to the new function -- so every existing call site (anyone who
* already resolved the old function's address, including via a plain
* C function pointer) transparently starts running the new code,
* without restarting the process.
* @section hotreload_what_it_does What this library does
*
* WHAT THIS LIBRARY DELIBERATELY DOES NOT DO
* --------------------------------------------
* It never patches anything that isn't executable code. A symbol is
* only ever a patch candidate if the loader's own symbol table marks
* it as a function (ELF STT_FUNC / a PE export whose address falls
* inside an executable section) -- see the design note in
* SymbolTable.hpp for exactly how that's determined. Global variables,
* class/struct instances, vtables, and anything else classified as
* data are always left alone, on both the old and the new module --
* there is no code path in this library that ever writes to a data
* symbol. This is a mechanical guarantee from how symbols are
* classified, not a naming convention you opt into.
* hot_reload_load() loads a shared library (`.so`/`.dll`) under a logical
* name. hot_reload_reload() loads a *new* build of that same shared
* library and, for every function the two builds have in common, rewrites
* the first few bytes of the old function in memory into a jump to the
* new function -- so every existing call site (anyone who already
* resolved the old function's address, including via a plain C function
* pointer) transparently starts running the new code, without restarting
* the process.
*
* It also cannot verify that a hot-reloadable function's SIGNATURE
* @section hotreload_what_it_wont What this library deliberately does not do
*
* It never patches anything that isn't executable code. A symbol is only
* ever a patch candidate if the loader's own symbol table marks it as a
* function (ELF `STT_FUNC` / a PE export whose address falls inside an
* executable section) -- see the design note at the top of
* `SymbolTable.cpp` (and detail::ExportedSymbol::isFunction) for exactly
* how that's determined. Global variables, class/struct
* instances, vtables, and anything else classified as data are always
* left alone, on both the old and the new module -- there is no code path
* in this library that ever writes to a data symbol. This is a mechanical
* guarantee from how symbols are classified, not a naming convention you
* opt into.
*
* It also cannot verify that a hot-reloadable function's *signature*
* hasn't changed. Patching only ever redirects control flow at a fixed
* entry point; it has no way to know, from a symbol table alone, that
* `void tick(int)` became `void tick(int, int)` between builds. If a
* function's signature changes, give it a new exported name (so the
* old symbol is treated as removed and the new one as added -- see
* HOT_RELOAD_SYMBOL_REMOVED below) rather than reusing the old name.
* See the top-level README for the full list of hazards this library
* cannot detect for you.
* function's signature changes, give it a new exported name (so the old
* symbol is treated as removed and the new one as added -- see
* ::HOT_RELOAD_SYMBOL_REMOVED) rather than reusing the old name. See the
* "Limitations" section of the top-level README (and "What this cannot
* detect" in `source/HotReload/README.md`) for the full list of hazards
* this library cannot detect for you.
*
* @section hotreload_requirements Requirements on a hot-reloadable module
*
* REQUIREMENTS ON A HOT-RELOADABLE MODULE
* ------------------------------------------
* Matching an old build's function up with the new build's requires a
* name that is stable across recompiles. extern "C" linkage -- see
* cpp-style-guide.md section 11 -- is the simplest way to get one: no
* name that is stable across recompiles. `extern "C"` linkage -- see
* `cpp-style-guide.md` section 11 -- is the simplest way to get one: no
* mangling at all, so the name never depends on the compiler, its
* version, or the function's signature.
*
* A real, unmodified C++ mangled name works too, and needs no special
* handling by this library -- classification only ever looks at a
* symbol's type and section, never at what its name looks like, so an
* ordinary out-of-line method, a virtual method (redirected wherever
* it's reached from -- directly or through a vtable slot, since both
* hold the same address), and an operator overload all patch exactly
* like any extern "C" function. What extern "C" actually buys you is
* a name you can type by hand into hot_reload_get_symbol() and a
* guarantee that it won't change if you rebuild with a different
* compiler; a mangled name gives up both of those, only stays stable
* within one ABI family (GCC and Clang share the Itanium C++ ABI;
* MSVC does not), and -- the one hazard that has nothing to do with
* this library and everything to do with what "hot-reloadable" can
* even mean -- only exists to patch at all if the compiler actually
* emitted an out-of-line body for it, which an implicitly-inline
* method defined in a class body, or a template instantiated at the
* call site, generally will not. See the top-level README's "Hot
* reloading C++ symbols" section for the full picture, including how
* to get a readable name for logging with hot_reload_demangle().
* ordinary out-of-line method, a virtual method (redirected wherever it's
* reached from -- directly or through a vtable slot, since both hold the
* same address), and an operator overload all patch exactly like any
* `extern "C"` function. What `extern "C"` actually buys you is a name
* you can type by hand into hot_reload_get_symbol() and a guarantee that
* it won't change if you rebuild with a different compiler; a mangled
* name gives up both of those, only stays stable within one ABI family
* (GCC and Clang share the Itanium C++ ABI; MSVC does not), and -- the
* one hazard that has nothing to do with this library and everything to
* do with what "hot-reloadable" can even mean -- only exists to patch at
* all if the compiler actually emitted an out-of-line body for it, which
* an implicitly-inline method defined in a class body, or a template
* instantiated at the call site, generally will not. See the "Hot
* reloading C++ symbols" section of `source/HotReload/README.md` for the
* full picture, including how to get a readable name for logging with
* hot_reload_demangle().
*
* Every function the module exports needs room at its entry point for the
* jump this library writes over it. Compile the module with
* `-fpatchable-function-entry=32,0` (GCC/Clang) or `/hotpatch` (MSVC) to
* guarantee that; without it, a function too small to hold the jump is
* skipped and reported as ::HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL rather than
* patched. Also avoid identical-code-folding linker options (MSVC
* `/OPT:ICF`, `--icf` in gold/lld) for the module: two different exported
* functions folded into one address can only be patched to one target.
*
* @section hotreload_lifetime What carries over a reload -- and what does not
*
* Only *code* is redirected. Every build of the module is a separate
* mapping with its own copy of everything else, so:
* - Global and `static` variables are not carried over. Code that runs
* after a reload uses the *new* build's copies, freshly initialised;
* keep state you need to survive in memory the host owns and pass it in.
* - Static constructors of the new build run when it is loaded (on every
* reload); static destructors of every version run when the context is
* destroyed.
* - Every version stays loaded until hot_reload_context_destroy(). Old
* entry points keep working (they are now jumps), one extra hop per
* reload; destroying the context unloads them all, so no pointer into
* any of the modules may be used afterwards.
*
* @section hotreload_threads Threading
*
* A context is not thread-safe: serialize all calls that use one context.
* Call hot_reload_reload() from a quiescent point -- one where no thread is
* executing inside the module's functions -- since a function that is being
* executed while its entry bytes are rewritten cannot be protected.
*/
#ifndef HOT_RELOAD_H
#define HOT_RELOAD_H
#include <stddef.h>
/**
* @def HOT_RELOAD_API
* @brief Marks every function of this library's own C API.
*
* Expands to nothing in the default configuration, where the library is linked statically.
* Define `HOT_RELOAD_BUILD_SHARED` while building the library as a shared library, and
* `HOT_RELOAD_USE_SHARED` while compiling code that uses such a build, to get
* `__declspec(dllexport)`/`__declspec(dllimport)` on Windows or default symbol visibility
* elsewhere.
*
* Not to be confused with ::HOT_RELOAD_EXPORT, which marks symbols in a hot-reloadable
* *module* rather than in this library.
*/
#ifdef _WIN32
#ifdef HOT_RELOAD_BUILD_SHARED
#define HOT_RELOAD_API __declspec(dllexport)
@@ -90,23 +137,29 @@
#endif
#endif
/* Marks a single declaration for export under
* hot_reload_restrict_exports() (cmake/HotReload.cmake) without
* otherwise changing its linkage: pair with extern "C" yourself for a
* free function you want a stable, human-typable name for (see this
* file's own comment on when you'd want that) -- extern "C" first,
* then this macro, then the return type, e.g.
* `extern "C" HOT_RELOAD_EXPORT int my_function(...)`, which is the
* one ordering GCC/Clang accept without a warning here. Leave the
* extern "C" off to keep a C++ class or method's real mangled name,
* e.g. a virtual method a host only ever reaches through a vtable
* slot (see the top-level README's "Hot reloading C++ symbols"
* section).
/**
* @def HOT_RELOAD_EXPORT
* @brief Marks a single declaration in a hot-reloadable *module* for export
* under `hot_reload_restrict_exports()` (`cmake/HotReload.cmake`).
*
* hot_reload_restrict_exports() is a narrower export surface, not a
* quieter one: see the README's "Why do std::string/std::vector
* symbols show up as PATCHED?" section for why it does not, and
* structurally cannot, suppress those specifically. */
* This is for the plugins you hot-reload, not for this library's own API
* above (::HOT_RELOAD_API). It does not otherwise change a declaration's
* linkage: pair it with `extern "C"` yourself for a free function you want
* a stable, human-typable name for (see @ref hotreload_requirements for
* when you'd want that) -- `extern "C"` first, then this macro, then the
* return type, e.g. `extern "C" HOT_RELOAD_EXPORT int my_function(...)`,
* which is the one ordering GCC/Clang accept without a warning here. Leave
* the `extern "C"` off to keep a C++ class or method's real mangled name,
* e.g. a virtual method a host only ever reaches through a vtable slot
* (see the "Hot reloading C++ symbols" section of
* `source/HotReload/README.md`).
*
* @note `hot_reload_restrict_exports()` is a *narrower* export surface,
* not a *quieter* one: see the "Why do std::string/std::vector
* symbols show up as PATCHED?" section of `source/HotReload/README.md`
* for why it does not, and structurally cannot, suppress those
* specifically.
*/
#ifdef _WIN32
#define HOT_RELOAD_EXPORT __declspec(dllexport)
#else
@@ -118,196 +171,394 @@ extern "C"
{
#endif
/* Opaque -- see cpp-style-guide.md section 16 ("Do: use opaque
* pointers ... to hide implementation details"). The real definition
* is the C++ class hot_reload::HotReloadContext (HotReloadContext.hpp),
* never visible from this header. */
/**
* @defgroup hotreload_types Types and callbacks
* @brief The status codes, event/option structs, and callback signatures
* used throughout the rest of this API.
* @{
*/
/**
* @brief Opaque handle to a hot-reload context.
*
* See `cpp-style-guide.md` section 16 ("Do: use opaque pointers ... to
* hide implementation details"). The real definition is the C++ class
* `hot_reload::HotReloadContext` (`HotReloadContext.hpp`), never visible
* from this header.
*/
typedef struct HotReloadContext HotReloadContext;
/* Every fallible function returns one of these -- see section 16
* ("Do: return an integer error code / status enum from every
* fallible function"). */
/**
* @brief Result of any fallible call in this API.
*
* See `cpp-style-guide.md` section 16 ("Do: return an integer error
* code / status enum from every fallible function"). Render one of these
* as text with hot_reload_status_string(); for a fuller, platform-specific
* explanation of a ::HOT_RELOAD_ERROR_LOAD_FAILED, see
* hot_reload_last_error().
*/
typedef enum HotReloadStatus
{
/** Success. */
HOT_RELOAD_OK = 0,
/* ctx, module_name, or path/new_path was NULL, or module_name
* was not found for hot_reload_reload / hot_reload_get_symbol. */
/**
* A required argument (@c ctx, @c module_name, or @c path / @c new_path)
* was `NULL`, or hot_reload_load() was called for a @c module_name that is
* already loaded -- call hot_reload_reload() for that name instead.
*/
HOT_RELOAD_ERROR_INVALID_ARGUMENT,
/* dlopen()/LoadLibrary() (or the equivalent parse of the file on
* disk) failed for the given path. On hot_reload_reload(),
* this is non-destructive: the previously loaded version is left
* running untouched, exactly as if reload had not been called --
* see the file comment above HotReloadContext::reload(). Call
* hot_reload_last_error() for the platform's own description of
* why -- this one status code covers everything from a missing
* file to a genuinely corrupt image to (a real case, found while
* building this feature) a shared library with an unresolved
* symbol reference, and those need very different fixes. */
/**
* `dlopen()`/`LoadLibrary()` (or the equivalent parse of the file on
* disk) failed for the given path.
*
* On hot_reload_reload(), this is non-destructive: the previously
* loaded version is left running untouched, exactly as if reload had
* not been called. Call hot_reload_last_error() for the
* platform's own description of why -- this one status code covers
* everything from a missing file to a genuinely corrupt image to (a
* real case, found while building this feature) a shared library
* with an unresolved symbol reference, and those need very different
* fixes.
*
* It is also reported when the file could not be copied to its staging
* name (see hot_reload_load()): the source is unreadable, or the directory
* it lives in is not writable.
*/
HOT_RELOAD_ERROR_LOAD_FAILED,
/* hot_reload_reload() was called for a module_name that was never
* hot_reload_load()'ed first, so there is no "old" version to
* diff against. Call hot_reload_load() once, up front. */
/**
* hot_reload_reload() was called for a @c module_name that was never
* hot_reload_load()'ed first, so there is no "old" version to diff
* against. Call hot_reload_load() once, up front.
*/
HOT_RELOAD_ERROR_NO_PREVIOUS_VERSION,
/* This process's CPU architecture has no patch backend compiled
* in (see Architecture.hpp -- currently x86, x86-64, and ARM64).
* Nothing was patched; hot_reload_load() still succeeded, you
* just cannot hot_reload_reload() on this target. */
/**
* This process's CPU architecture has no patch backend compiled in
* (see `Architecture.hpp` -- currently x86, x86-64, and ARM64).
* Nothing was patched; hot_reload_load() still succeeded, you just
* cannot hot_reload_reload() on this target.
*/
HOT_RELOAD_ERROR_UNSUPPORTED_ARCHITECTURE
} HotReloadStatus;
/* Reported once per matched/unmatched symbol during hot_reload_reload()
* via HotReloadOptions::on_symbol_event, so the host application can
* log (or assert on, in a test) exactly what happened -- nothing here
* is silent. */
/**
* @brief What happened to one symbol during a hot_reload_reload() call.
*
* Reported once per matched/unmatched symbol via
* HotReloadOptions::on_symbol_event, so the host application can log (or
* assert on, in a test) exactly what happened -- nothing here is silent.
*/
typedef enum HotReloadSymbolEvent
{
/* Existed in both builds and was classified as code in both --
* the old function's entry now jumps to the new one. */
/**
* Existed in both builds and was classified as code in both -- the
* old function's entry now jumps to the new one.
*/
HOT_RELOAD_SYMBOL_PATCHED,
/* Exists in the new build only. Nothing to patch -- it becomes
* reachable the normal way, e.g. via hot_reload_get_symbol(). */
/**
* Exists in the new build only. Nothing to patch -- it becomes
* reachable the normal way, e.g. via hot_reload_get_symbol().
*/
HOT_RELOAD_SYMBOL_ADDED,
/* Existed in the old build, missing from the new one. Per
* cpp-style-guide.md's spirit of never leaving a caller with an
/**
* Existed in the old build, missing from the new one.
*
* Per `cpp-style-guide.md`'s spirit of never leaving a caller with an
* ambiguous, half-updated state: the old implementation is left
* running as-is unless HotReloadOptions::on_deleted_symbol
* supplies a stub to redirect to instead -- see that field. */
* running as-is unless HotReloadOptions::on_deleted_symbol supplies a
* stub to redirect to instead -- see that field.
*/
HOT_RELOAD_SYMBOL_REMOVED,
/* Present in both builds by name, but the symbol table classifies
* it as data (or something else non-function) in either build --
* e.g. an accidentally-exported global. Never touched, in either
* direction. This is the mechanical half of the "only executable
* code changes" guarantee described at the top of this file. */
/**
* Present in both builds by name, but the symbol table classifies it
* as data (or something else non-function) in either build -- e.g.
* an accidentally-exported global.
*
* Never touched, in either direction. This is the mechanical half of
* the "only executable code changes" guarantee described in
* @ref hotreload_what_it_wont.
*/
HOT_RELOAD_SYMBOL_SKIPPED_NOT_CODE,
/* Classified as a function in both builds, but the OLD function's
* compiled size is smaller than the number of bytes this
* platform's jump sequence needs to write -- patching it would
* overwrite whatever comes after it in memory. Skipped rather
* than guessed; see hot_reload_enable_patchable_functions() in
* cmake/HotReload.cmake for how to stop this from happening. */
/**
* Classified as a function in both builds, but the old function's
* compiled size is smaller than the number of bytes this platform's
* jump sequence needs to write -- patching it would overwrite
* whatever comes after it in memory.
*
* Skipped rather than guessed. Compile the module with
* `-fpatchable-function-entry=32,0` (GCC/Clang) or `/hotpatch` (MSVC) to
* stop this from happening; `hot_reload_enable_patchable_functions()` in
* `cmake/HotReload.cmake` applies whichever one your compiler needs.
*/
HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL,
/* Classified as a function in both builds, correctly sized, but
* the OS refused to make its page temporarily writable (mprotect /
* VirtualProtect failed -- e.g. a hardened environment that denies
* W^X transitions). Rare in a normal developer setup; reported
* rather than silently ignored either way. */
/**
* Classified as a function in both builds, correctly sized, but the
* OS refused to make its page temporarily writable (`mprotect` /
* `VirtualProtect` failed -- e.g. a hardened environment that denies
* W^X transitions).
*
* Rare in a normal developer setup; reported rather than silently
* ignored either way.
*/
HOT_RELOAD_SYMBOL_SKIPPED_UNWRITABLE
} HotReloadSymbolEvent;
/* One event report, passed to HotReloadOptions::on_symbol_event.
* old_address/new_address are NULL when not applicable (e.g.
* old_address is NULL for HOT_RELOAD_SYMBOL_ADDED). */
/**
* @brief One symbol's outcome, passed to HotReloadOptions::on_symbol_event.
*/
typedef struct HotReloadSymbolEventInfo
{
const char* name;
HotReloadSymbolEvent kind;
void* old_address;
void* new_address;
const char* name; /**< The symbol's real name (mangled, if it's a C++ symbol -- see hot_reload_demangle()). */
HotReloadSymbolEvent kind; /**< What happened to it -- see ::HotReloadSymbolEvent. */
void* old_address; /**< The symbol's address in the previous build, or `NULL` if not applicable (e.g. ::HOT_RELOAD_SYMBOL_ADDED). */
void* new_address; /**< The symbol's address in the new build, or `NULL` if not applicable (e.g. ::HOT_RELOAD_SYMBOL_REMOVED). */
} HotReloadSymbolEventInfo;
/**
* @brief Callback invoked once per symbol on every hot_reload_reload() call.
*
* Called synchronously, on the thread that called hot_reload_reload(), while the reload is in
* progress.
*
* @param event Describes one symbol's outcome; valid only for the duration of the call.
* @param user_data Whatever was passed as HotReloadOptions::user_data, untouched.
*
* @warning Do not call hot_reload_load() or hot_reload_reload() on the same context from inside
* this callback.
*/
typedef void (*HotReloadSymbolEventFn)(const HotReloadSymbolEventInfo* event, void* user_data);
/* Called instead of patching, once per HOT_RELOAD_SYMBOL_REMOVED
* event, so the host can catch stale references during development
* instead of them silently continuing to run old code forever. Return
* the address of a replacement to redirect the old function to (its
* only safe use is a fixed-signature stub -- e.g. `void stub(void)`--
* that logs/asserts; this library has no way to know the deleted
* function's real signature, so redirecting to anything that reads
* arguments the caller didn't pass is undefined behavior, exactly
* like calling any function through the wrong function-pointer type).
* Return NULL (or pass a NULL callback in HotReloadOptions to begin
* with) to leave the old implementation running untouched -- the
* default, and the safe choice unless you specifically want deleted
* functions to be loud. */
/**
* @brief Callback invoked instead of patching, once per ::HOT_RELOAD_SYMBOL_REMOVED event.
*
* Lets the host catch stale references during development instead of them
* silently continuing to run old code forever.
*
* Called synchronously, on the thread that called hot_reload_reload(), while the reload is in
* progress; like HotReloadSymbolEventFn, it must not call hot_reload_load() or
* hot_reload_reload() on the same context.
*
* @param symbol_name The removed function's real (possibly mangled) name.
* @param user_data Whatever was passed as HotReloadOptions::user_data, untouched.
* @return The address of a replacement to redirect the old function to, or
* `NULL` to leave the old implementation running untouched (the
* default, and the safe choice unless you specifically want
* deleted functions to be loud).
*
* @warning A non-`NULL` return's only safe use is a fixed-signature stub
* (e.g. `void stub(void)`) that logs/asserts; this library has
* no way to know the deleted function's real signature, so
* redirecting to anything that reads arguments the caller didn't
* pass is undefined behavior, exactly like calling any function
* through the wrong function-pointer type.
*/
typedef void* (*HotReloadDeletedSymbolFn)(const char* symbol_name, void* user_data);
/* Passed to hot_reload_context_create(); NULL is a valid options
* pointer meaning "no callbacks, default policy for everything". */
/**
* @brief Options passed to hot_reload_context_create().
*
* A `NULL` options pointer is valid and means "no callbacks, default
* policy for everything."
*/
typedef struct HotReloadOptions
{
HotReloadSymbolEventFn on_symbol_event; /* nullable */
HotReloadDeletedSymbolFn on_deleted_symbol; /* nullable */
void* user_data; /* passed back to both callbacks, untouched */
HotReloadSymbolEventFn on_symbol_event; /**< Nullable. See ::HotReloadSymbolEventFn. */
HotReloadDeletedSymbolFn on_deleted_symbol; /**< Nullable. See ::HotReloadDeletedSymbolFn. */
void* user_data; /**< Passed back to both callbacks, untouched. */
} HotReloadOptions;
/* Caller owns the returned pointer and must call
* hot_reload_context_destroy() -- see cpp-style-guide.md section 16
* ("Document ownership transfer explicitly"). options may be NULL. */
/** @} */ /* end of hotreload_types */
/**
* @defgroup hotreload_lifecycle Context lifecycle
* @brief Creating and destroying a hot-reload context.
* @{
*/
/**
* @brief Creates a new hot-reload context.
* @param options Nullable; see ::HotReloadOptions. Its contents are copied, so it need not
* outlive this call.
* @return A new context, or `NULL` if allocation failed. The caller owns it and must call
* hot_reload_context_destroy() (see `cpp-style-guide.md` section 16,
* "Document ownership transfer explicitly").
*/
HOT_RELOAD_API HotReloadContext* hot_reload_context_create(const HotReloadOptions* options);
/**
* @brief Destroys a context created by hot_reload_context_create().
*
* Unloads every version of every module the context loaded (running their static destructors).
* From then on, every pointer obtained through this context -- functions, data, and the old
* entry points that had been patched to jump into newer versions -- is dangling and must not be
* used. Destroy a context only when nothing can still call into its modules.
*
* @param ctx The context to destroy. Safe to pass `NULL`.
*/
HOT_RELOAD_API void hot_reload_context_destroy(HotReloadContext* ctx);
/* Loads path for the first time under module_name -- an arbitrary
* caller-chosen key used to look the module back up in later
* hot_reload_reload()/hot_reload_get_symbol() calls; it does not need
* to match anything on disk. Returns HOT_RELOAD_ERROR_INVALID_ARGUMENT
* if module_name was already loaded -- call hot_reload_reload()
* instead of hot_reload_load() again for the same name. */
/** @} */ /* end of hotreload_lifecycle */
/**
* @defgroup hotreload_loading Loading and reloading
* @brief The core operations: register a module, hot-reload it, and
* resolve its symbols.
* @{
*/
/**
* @brief Loads a module for the first time under a logical name.
*
* The file is not loaded in place: it is first copied to a uniquely named sibling
* (`<path>.hotreload.<module_name>.<n>`) and that copy is loaded, which keeps a rebuild that
* overwrites @c path in place from being mistaken for the already-loaded version. The copy is
* deleted again right after loading on POSIX; on Windows it stays until the module is unloaded
* (see the top-level README). This means the directory containing @c path must be writable.
*
* The module's static constructors run as part of loading it.
*
* @note If the module's symbol table cannot be read (for instance a `.so` whose section headers
* were stripped), loading still succeeds but the module exports nothing as far as this
* library is concerned: hot_reload_get_symbol() finds nothing and a later
* hot_reload_reload() has nothing to patch.
*
* @param ctx The context to load into.
* @param module_name An arbitrary caller-chosen key used to look the
* module back up in later hot_reload_reload()/
* hot_reload_get_symbol() calls; it does not need to match
* anything on disk.
* @param path Path to the shared library (`.so`/`.dll`) to load. Always treated as a file path:
* a bare file name such as `plugin.so` means the file of that name in the current
* working directory (it is never looked up through the library search path).
* @return ::HOT_RELOAD_OK, or ::HOT_RELOAD_ERROR_INVALID_ARGUMENT if
* an argument was `NULL` or @c module_name was already loaded (call
* hot_reload_reload() instead of hot_reload_load() again for the same
* name), or ::HOT_RELOAD_ERROR_LOAD_FAILED (see hot_reload_last_error()
* for why).
*/
HOT_RELOAD_API HotReloadStatus hot_reload_load(HotReloadContext* ctx, const char* module_name, const char* path);
/* Loads a NEW build of the module previously registered under
* module_name, diffs its exported functions against the previous
* version, and patches every one that is safe to patch in place --
* see HotReloadSymbolEvent above for exactly what happens to each
* kind of change. On any HOT_RELOAD_ERROR_* return, nothing was
* changed: the previous version keeps running exactly as it was. */
/**
* @brief Hot-reloads a module previously registered with hot_reload_load().
*
* Loads a new build of the module, diffs its exported functions against
* the previous version, and patches every one that is safe to patch in
* place -- see ::HotReloadSymbolEvent for exactly what happens to each
* kind of change. The previous version stays loaded (its entry points now
* jump into the new one), and the new build's static constructors run.
* If the new build exports no functions at all -- including when its symbol
* table could not be read -- every previously exported function is reported
* as ::HOT_RELOAD_SYMBOL_REMOVED.
*
* Call this from a quiescent point (see @ref hotreload_threads), and see
* @ref hotreload_lifetime for what does not carry over. @c new_path follows
* the same rules as @c path in hot_reload_load().
*
* @param ctx The context @c module_name was hot_reload_load()'ed into.
* @param module_name The same logical name passed to hot_reload_load().
* @param new_path Path to the new build of the shared library.
* @return ::HOT_RELOAD_OK, or an `HOT_RELOAD_ERROR_*` code. On any error,
* nothing was changed: the previous version keeps running exactly
* as it was.
*/
HOT_RELOAD_API HotReloadStatus hot_reload_reload(HotReloadContext* ctx, const char* module_name, const char* new_path);
/* Resolves symbol_name in module_name's CURRENT build (i.e. always
* the freshest one loaded, patched or not) -- for calling a function
* that did not exist yet when the host first started, such as one
* reported via HOT_RELOAD_SYMBOL_ADDED. Returns NULL if module_name
* is unknown or symbol_name is not an exported function in it. */
/**
* @brief Resolves a symbol in a module's current build.
*
* Always resolves against the freshest build loaded for @c module_name,
* patched or not -- useful for calling a function that did not exist yet
* when the host first started, such as one reported via
* ::HOT_RELOAD_SYMBOL_ADDED. Any exported symbol resolves, data included
* (a global variable, say); only functions are ever patched.
*
* @param ctx The context @c module_name was hot_reload_load()'ed into.
* @param module_name The module to resolve @c symbol_name in.
* @param symbol_name The symbol's real (possibly mangled) name.
* @return The symbol's current address, or `NULL` if an argument is `NULL`,
* @c module_name is unknown, or @c symbol_name is not exported by
* the module's newest build.
*/
HOT_RELOAD_API void* hot_reload_get_symbol(HotReloadContext* ctx, const char* module_name, const char* symbol_name);
/* Only meaningful immediately after hot_reload_load() or
* hot_reload_reload() on this SAME ctx returned something other than
* HOT_RELOAD_OK: the underlying platform's own description of why --
* dlerror() on POSIX, the formatted message for GetLastError() on
* Windows -- e.g. "undefined symbol: _ZN6Widget4tickEi" for a shared
* library with an unresolved reference (a real, confusing-until-you-
* see-this-message case: a non-inline static class member that is
* declared but never given an out-of-class definition compiles fine
* and links fine into a shared library -- the linker allows a shared
* library to keep undefined symbols by default -- and only fails,
* right here, once something ODR-uses it and the loader tries to
* resolve it for real). Overwritten by the next
* hot_reload_load()/hot_reload_reload() call on this ctx; copy it out
* immediately if you need to keep it past that point (e.g. for a log
* line). Empty if the platform gave no further detail, or if the
* failure never reached the platform loader at all (e.g. a NULL
* argument, which is HOT_RELOAD_ERROR_INVALID_ARGUMENT before
* anything platform-specific happens). A static string internally --
* never free() this. */
/** @} */ /* end of hotreload_loading */
/**
* @defgroup hotreload_diagnostics Diagnostics
* @brief Turning a status code or a mangled name into something readable.
* @{
*/
/**
* @brief The platform's own explanation for the most recent failure on this context.
*
* Only meaningful immediately after hot_reload_load() or
* hot_reload_reload() on this *same* @c ctx returned something other than
* ::HOT_RELOAD_OK: the underlying platform's own description of why --
* `dlerror()` on POSIX, the formatted message for `GetLastError()` on
* Windows -- e.g. `"undefined symbol: _ZN6Widget4tickEi"` for a shared
* library with an unresolved reference (a real, confusing-until-you-see-
* this-message case: a non-inline static class member that is declared
* but never given an out-of-class definition compiles fine and links fine
* into a shared library -- the linker allows a shared library to keep
* undefined symbols by default -- and only fails, right here, once
* something ODR-uses it and the loader tries to resolve it for real).
*
* @param ctx The context to query.
* @return The platform's error message. Empty if it gave no further
* detail, or if the failure never reached the platform loader at
* all (e.g. a `NULL` argument, which is
* ::HOT_RELOAD_ERROR_INVALID_ARGUMENT before anything
* platform-specific happens). Owned by @c ctx -- never `free()`
* this. It stays valid only until the next hot_reload_load()/
* hot_reload_reload() call on this @c ctx, or until the context is
* destroyed; copy it out immediately if you need to keep it past
* that point (e.g. for a log line).
*/
HOT_RELOAD_API const char* hot_reload_last_error(HotReloadContext* ctx);
/* A static string literal -- never free() this. */
/**
* @brief Renders a ::HotReloadStatus as text.
* @param status The status to render.
* @return A static string literal -- never `free()` this.
*/
HOT_RELOAD_API const char* hot_reload_status_string(HotReloadStatus status);
/* Renders name for a human to read: a real C++ mangled name (Itanium
* on GCC/Clang/MinGW, MSVC's own scheme under MSVC -- whichever this
* library itself was built with) becomes something like
* "Widget::tick(int)"; anything else, including an ordinary extern "C"
* name, comes back unchanged. Safe to call unconditionally on any
* HotReloadSymbolEventInfo::name or hot_reload_get_symbol() key --
* this never changes what name means for lookup purposes, it is only
* ever applied when you ask for it, purely for display. Returns NULL
* only if name itself is NULL or allocation fails; caller must free
* a non-NULL result with hot_reload_free_string(). */
/**
* @brief Renders a symbol name for a human to read.
*
* A real C++ mangled name (Itanium on GCC/Clang/MinGW, MSVC's own scheme
* under MSVC -- whichever this library itself was built with) becomes
* something like `"Widget::tick(int)"`; anything else, including an
* ordinary `extern "C"` name, comes back unchanged. Safe to call
* unconditionally on any HotReloadSymbolEventInfo::name or
* hot_reload_get_symbol() key -- this never changes what a name means for
* lookup purposes, it is only ever applied when you ask for it, purely
* for display.
*
* @param name The name to render.
* @return The rendered name, or `NULL` if @c name itself is `NULL` or
* allocation fails. Caller must free a non-`NULL` result with
* hot_reload_free_string().
*/
HOT_RELOAD_API char* hot_reload_demangle(const char* name);
/**
* @brief Frees a string returned by hot_reload_demangle().
* @param str The string to free. Safe to pass `NULL`.
*/
HOT_RELOAD_API void hot_reload_free_string(char* str);
/** @} */ /* end of hotreload_diagnostics */
#ifdef __cplusplus
}
#endif