Added ARM32 support
This commit is contained in:
1 parent
df792729ef
commit
1212cb9dbe
36 files changed
+2381
-783
No files matched your search
@@ -1,23 +1,194 @@
|
||||
/**
|
||||
* @file HotReloadApi.cpp
|
||||
* @brief The extern "C" shim implementing hot_reload.h by delegating to hot_reload::HotReloadContext.
|
||||
* @brief The extern "C" shim implementing hot_reload.h on top of hot_reload::Context.
|
||||
*
|
||||
* Deliberately loose coupling, not the tight opaque-pointer-reinterpret_cast PIMPL
|
||||
* cpp-style-guide.md's own NetManager example uses elsewhere in this codebase: `::HotReloadContext`
|
||||
* (the struct this file defines, matching the opaque forward declaration in `hot_reload.h`) *owns*
|
||||
* a `hot_reload::Context` by value, and every function below reaches it only through Context's own
|
||||
* public methods -- exactly as any other C++ caller of `Context.hpp` would, with no special access
|
||||
* and no reinterpret_cast between unrelated types. `hot_reload::Context` has no idea this file, or
|
||||
* the C API it implements, exists at all.
|
||||
*/
|
||||
#include "hot_reload/hot_reload.h"
|
||||
|
||||
#include "../HotReloadContext.hpp"
|
||||
#include "hot_reload/Context.hpp"
|
||||
#include "hot_reload/Result.hpp"
|
||||
#include "hot_reload/Status.hpp"
|
||||
#include "hot_reload/SymbolEventInfo.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
/// Defines the type `hot_reload.h` only forward-declares (`typedef struct HotReloadContext
|
||||
/// HotReloadContext;`) -- every function in this file sees the real definition, so ordinary
|
||||
/// member access replaces the reinterpret_cast a true opaque pointer would otherwise need.
|
||||
struct HotReloadContext
|
||||
{
|
||||
explicit HotReloadContext(hot_reload::Options options)
|
||||
: impl(std::move(options))
|
||||
{
|
||||
}
|
||||
|
||||
/// A process-wide, never-reused identifier for this context, used only to key
|
||||
/// t_lastErrorByContextId() below -- see that function's own comment for why a raw `this`
|
||||
/// pointer is not used for that instead.
|
||||
std::uint64_t id = nextId();
|
||||
|
||||
/// The real implementation. Composition, not inheritance or a cast: this file uses it
|
||||
/// exactly the way any other hot_reload::Context caller would.
|
||||
hot_reload::Context impl;
|
||||
|
||||
private:
|
||||
static std::uint64_t nextId()
|
||||
{
|
||||
static std::atomic<std::uint64_t> counter{1};
|
||||
return counter.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
/// The opaque ::HotReloadContext the public header declares is
|
||||
/// never actually instantiated as itself -- every instance really
|
||||
/// is a hot_reload::HotReloadContext, following the same
|
||||
/// opaque-pointer PIMPL pattern cpp-style-guide.md's NetManager
|
||||
/// example uses. This is the one place that cast happens.
|
||||
/// @param ctx A context previously returned by hot_reload_context_create(), or null.
|
||||
/// @return The same object viewed as the C++ type it really is (null if @p ctx is null).
|
||||
hot_reload::HotReloadContext* impl(::HotReloadContext* ctx)
|
||||
/// hot_reload.h's `hot_reload_last_error()` is a stateful call: "whatever the most recent
|
||||
/// load()/reload() on this ctx failed with". hot_reload::Result has no equivalent shared
|
||||
/// state at all -- each call's outcome comes back in its own Result -- which is exactly what
|
||||
/// makes the C++ API safe to use concurrently without two threads' errors ever being
|
||||
/// confused (see Context.hpp's thread-safety note). Reintroducing that stateful call for the
|
||||
/// C API's own backward compatibility, now safely, means tracking it per *(thread, context)*
|
||||
/// pair: a plain `thread_local` map needs no lock at all, since no other thread can ever see
|
||||
/// this one's instance of it.
|
||||
///
|
||||
/// Keyed by HotReloadContext::id rather than the context's own address: a destroyed
|
||||
/// context's memory can be reused by an unrelated, later one, and keying by address would
|
||||
/// risk a brand new context momentarily reading a stale error string left behind by
|
||||
/// whatever used to live at that same address.
|
||||
std::unordered_map<std::uint64_t, std::string>& t_lastErrorByContextId()
|
||||
{
|
||||
return reinterpret_cast<hot_reload::HotReloadContext*>(ctx);
|
||||
thread_local std::unordered_map<std::uint64_t, std::string> errors;
|
||||
return errors;
|
||||
}
|
||||
|
||||
/// Records @p result's message, if it has one, as this thread's last error for @p ctx.
|
||||
/// @param ctx The context the call was made on.
|
||||
/// @param result The call's own outcome.
|
||||
void recordLastError(const HotReloadContext& ctx, const hot_reload::Result& result)
|
||||
{
|
||||
if (result.message().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
t_lastErrorByContextId()[ctx.id] = result.message();
|
||||
}
|
||||
|
||||
/// @param status A hot_reload::Status.
|
||||
/// @return The equivalent HotReloadStatus.
|
||||
HotReloadStatus toCStatus(hot_reload::Status status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case hot_reload::Status::Ok:
|
||||
return HOT_RELOAD_OK;
|
||||
case hot_reload::Status::InvalidArgument:
|
||||
return HOT_RELOAD_ERROR_INVALID_ARGUMENT;
|
||||
case hot_reload::Status::LoadFailed:
|
||||
return HOT_RELOAD_ERROR_LOAD_FAILED;
|
||||
case hot_reload::Status::NoPreviousVersion:
|
||||
return HOT_RELOAD_ERROR_NO_PREVIOUS_VERSION;
|
||||
case hot_reload::Status::UnsupportedArchitecture:
|
||||
return HOT_RELOAD_ERROR_UNSUPPORTED_ARCHITECTURE;
|
||||
}
|
||||
return HOT_RELOAD_ERROR_LOAD_FAILED; // unreachable for any enumerator; -Wswitch catches a missing one above
|
||||
}
|
||||
|
||||
/// @param status A HotReloadStatus.
|
||||
/// @return The equivalent hot_reload::Status.
|
||||
hot_reload::Status toCppStatus(HotReloadStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case HOT_RELOAD_OK:
|
||||
return hot_reload::Status::Ok;
|
||||
case HOT_RELOAD_ERROR_INVALID_ARGUMENT:
|
||||
return hot_reload::Status::InvalidArgument;
|
||||
case HOT_RELOAD_ERROR_LOAD_FAILED:
|
||||
return hot_reload::Status::LoadFailed;
|
||||
case HOT_RELOAD_ERROR_NO_PREVIOUS_VERSION:
|
||||
return hot_reload::Status::NoPreviousVersion;
|
||||
case HOT_RELOAD_ERROR_UNSUPPORTED_ARCHITECTURE:
|
||||
return hot_reload::Status::UnsupportedArchitecture;
|
||||
}
|
||||
return hot_reload::Status::LoadFailed; // unreachable for any enumerator; -Wswitch catches a missing one above
|
||||
}
|
||||
|
||||
/// @param kind A hot_reload::SymbolEvent.
|
||||
/// @return The equivalent HotReloadSymbolEvent.
|
||||
HotReloadSymbolEvent toCEvent(hot_reload::SymbolEvent kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case hot_reload::SymbolEvent::Patched:
|
||||
return HOT_RELOAD_SYMBOL_PATCHED;
|
||||
case hot_reload::SymbolEvent::Added:
|
||||
return HOT_RELOAD_SYMBOL_ADDED;
|
||||
case hot_reload::SymbolEvent::Removed:
|
||||
return HOT_RELOAD_SYMBOL_REMOVED;
|
||||
case hot_reload::SymbolEvent::SkippedNotCode:
|
||||
return HOT_RELOAD_SYMBOL_SKIPPED_NOT_CODE;
|
||||
case hot_reload::SymbolEvent::SkippedTooSmall:
|
||||
return HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL;
|
||||
case hot_reload::SymbolEvent::SkippedUnwritable:
|
||||
return HOT_RELOAD_SYMBOL_SKIPPED_UNWRITABLE;
|
||||
}
|
||||
return HOT_RELOAD_SYMBOL_SKIPPED_NOT_CODE; // unreachable for any enumerator; -Wswitch catches one missing above
|
||||
}
|
||||
|
||||
/// Builds the hot_reload::Options this file's two C callback adapters below run through,
|
||||
/// from a (possibly null) HotReloadOptions*.
|
||||
/// @param options The options hot_reload_context_create() was called with; null means none.
|
||||
/// @return Options wrapping @p options's two callbacks (each a no-op if @p options is null,
|
||||
/// or if the specific callback field itself is null), capturing nothing but the
|
||||
/// plain C function pointer and user_data they were given, so this is safe to store
|
||||
/// for the Context's whole lifetime.
|
||||
hot_reload::Options toCppOptions(const HotReloadOptions* options)
|
||||
{
|
||||
hot_reload::Options result;
|
||||
if (options == nullptr)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (options->on_symbol_event != nullptr)
|
||||
{
|
||||
HotReloadSymbolEventFn callback = options->on_symbol_event;
|
||||
void* userData = options->user_data;
|
||||
result.onSymbolEvent = [callback, userData](const hot_reload::SymbolEventInfo& event)
|
||||
{
|
||||
// name.data() is safe to hand out as a NUL-terminated const char* here: it
|
||||
// ultimately points into a detail::ExportedSymbol's own std::string (see
|
||||
// Context.cpp's emitEvent()), which is always NUL-terminated.
|
||||
HotReloadSymbolEventInfo cEvent;
|
||||
cEvent.name = event.name.data();
|
||||
cEvent.kind = toCEvent(event.kind);
|
||||
cEvent.old_address = event.oldAddress;
|
||||
cEvent.new_address = event.newAddress;
|
||||
callback(&cEvent, userData);
|
||||
};
|
||||
}
|
||||
|
||||
if (options->on_deleted_symbol != nullptr)
|
||||
{
|
||||
HotReloadDeletedSymbolFn callback = options->on_deleted_symbol;
|
||||
void* userData = options->user_data;
|
||||
result.onDeletedSymbol = [callback, userData](std::string_view deletedSymbolName) -> void*
|
||||
{
|
||||
// Same reasoning as above for passing .data() directly as a C string.
|
||||
return callback(deletedSymbolName.data(), userData);
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,11 +197,11 @@ namespace
|
||||
// caller that is not itself compiled to expect one, which a plain C
|
||||
// caller of this API never is. Every function here is a thin,
|
||||
// non-throwing wrapper for exactly that reason: std::string/vector/
|
||||
// unordered_map construction is the only thing in hot_reload::
|
||||
// HotReloadContext that could throw at all (std::bad_alloc under
|
||||
// memory pressure), so catching (...) and mapping it to this API's
|
||||
// existing failure values is sufficient without needing per-exception-
|
||||
// type handling.
|
||||
// unordered_map construction (on the hot_reload::Context side of this
|
||||
// boundary) is the only thing that could throw at all (std::bad_alloc
|
||||
// under memory pressure), so catching (...) and mapping it to this
|
||||
// API's existing failure values is sufficient without needing
|
||||
// per-exception-type handling.
|
||||
extern "C"
|
||||
{
|
||||
|
||||
@@ -38,7 +209,7 @@ extern "C"
|
||||
{
|
||||
try
|
||||
{
|
||||
return reinterpret_cast<::HotReloadContext*>(new hot_reload::HotReloadContext(options));
|
||||
return new HotReloadContext(toCppOptions(options));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -48,7 +219,17 @@ extern "C"
|
||||
|
||||
void hot_reload_context_destroy(HotReloadContext* ctx)
|
||||
{
|
||||
delete impl(ctx);
|
||||
if (ctx == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Best-effort: removes this (calling) thread's own entry for ctx, if it has one. Another
|
||||
// thread's thread_local entry for the same id, if it made calls on this ctx too, is left
|
||||
// in place -- there is no way to reach another thread's thread_local storage from here --
|
||||
// a small, bounded-by-distinct-destroyed-context-count leak, accepted as the cost of
|
||||
// hot_reload_last_error() needing no lock at all on the hot (every-call) path.
|
||||
t_lastErrorByContextId().erase(ctx->id);
|
||||
delete ctx;
|
||||
}
|
||||
|
||||
HotReloadStatus hot_reload_load(HotReloadContext* ctx, const char* module_name, const char* path)
|
||||
@@ -59,7 +240,9 @@ extern "C"
|
||||
}
|
||||
try
|
||||
{
|
||||
return impl(ctx)->load(module_name, path);
|
||||
hot_reload::Result result = ctx->impl.load(module_name, path);
|
||||
recordLastError(*ctx, result);
|
||||
return toCStatus(result.status());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -75,7 +258,9 @@ extern "C"
|
||||
}
|
||||
try
|
||||
{
|
||||
return impl(ctx)->reload(module_name, new_path);
|
||||
hot_reload::Result result = ctx->impl.reload(module_name, new_path);
|
||||
recordLastError(*ctx, result);
|
||||
return toCStatus(result.status());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -91,7 +276,7 @@ extern "C"
|
||||
}
|
||||
try
|
||||
{
|
||||
return impl(ctx)->getSymbol(module_name, symbol_name);
|
||||
return ctx->impl.findSymbol(module_name, symbol_name);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -107,7 +292,9 @@ extern "C"
|
||||
}
|
||||
try
|
||||
{
|
||||
return impl(ctx)->lastError().c_str();
|
||||
auto& errors = t_lastErrorByContextId();
|
||||
auto found = errors.find(ctx->id);
|
||||
return found == errors.end() ? "" : found->second.c_str();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -117,20 +304,7 @@ extern "C"
|
||||
|
||||
const char* hot_reload_status_string(HotReloadStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case HOT_RELOAD_OK:
|
||||
return "ok";
|
||||
case HOT_RELOAD_ERROR_INVALID_ARGUMENT:
|
||||
return "invalid argument";
|
||||
case HOT_RELOAD_ERROR_LOAD_FAILED:
|
||||
return "load failed";
|
||||
case HOT_RELOAD_ERROR_NO_PREVIOUS_VERSION:
|
||||
return "no previous version loaded for this module name";
|
||||
case HOT_RELOAD_ERROR_UNSUPPORTED_ARCHITECTURE:
|
||||
return "no patch backend for this CPU architecture";
|
||||
}
|
||||
return "unknown hot reload status";
|
||||
return hot_reload::toString(toCppStatus(status)).data(); // points into a static string literal either way
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user