Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions erpcgen/src/Generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,10 @@ data_list Generator::makeGroupInterfacesTemplateData(Group *group)
}
}

// Generate compile-time hash table for this interface
data_map hashTableInfo = generateHashTableData(iface, functions);
ifaceInfo["hashTable"] = hashTableInfo;

interfaces.push_back(ifaceInfo);
}

Expand Down Expand Up @@ -759,3 +763,132 @@ void Generator::getCallbacksTemplateData(Group *group, const Interface *iface, d
}
}
}

data_map Generator::generateHashTableData(Interface *iface, const data_list &functions)
{
data_map hashTableInfo;

// Get function count and calculate optimal hash table size
uint32_t functionCount = static_cast<uint32_t>(functions.size());
uint32_t hashTableSize = ERPC_HASH_TABLE_MIN_SIZE;

// Find next power of 2 that gives target load factor
while (hashTableSize * ERPC_HASH_TABLE_TARGET_LOAD_FACTOR < functionCount &&
hashTableSize < ERPC_HASH_TABLE_MAX_SIZE)
{
hashTableSize *= 2;
}

// Check if we hit the size limit and report performance impact
double loadFactor = static_cast<double>(functionCount) / hashTableSize;
if (hashTableSize >= ERPC_HASH_TABLE_MAX_SIZE && loadFactor > ERPC_HASH_TABLE_TARGET_LOAD_FACTOR) {
Log::warning("Interface '%s': Hash table size limit reached (%d functions in %d-entry table)\n"
"Load factor: %.3f, Expected probes: %.1f\n"
"Consider splitting interface for better performance.\n",
iface->getName().c_str(), functionCount, hashTableSize,
loadFactor, 0.5 * (1 + 1/(1-loadFactor)));
}

Log::info("Hash table for '%s': size=%d, functions=%d, load_factor=%.3f\n",
iface->getName().c_str(), hashTableSize, functionCount, loadFactor);

hashTableInfo["size"] = make_data(hashTableSize);
hashTableInfo["functionCount"] = make_data(functionCount);
hashTableInfo["loadFactor"] = make_data(static_cast<double>(functionCount) / hashTableSize);

// Hash function implementation (Knuth multiplicative method)
auto hash_function_id = [hashTableSize](uint32_t id) -> uint32_t {
uint32_t hash = id;
hash = ((hash >> 16) ^ hash) * 0x45d9f3bU;
hash = ((hash >> 16) ^ hash) * 0x45d9f3bU;
hash = (hash >> 16) ^ hash;
return hash & (hashTableSize - 1);
};

// Initialize hash table array
vector<data_map> hashTable(hashTableSize);
for (uint32_t i = 0; i < hashTableSize; i++)
{
data_map entry;
entry["isEmpty"] = make_data(true);
entry["index"] = make_data(i);
entry["id"] = make_data(0U);
entry["name"] = make_data(string(""));
entry["comment"] = make_data(string("Empty"));
entry["probeDistance"] = make_data(0);
hashTable[i] = entry;
}

// Place functions in hash table using linear probing
uint32_t collisions = 0;
uint32_t maxProbe = 0;

for (size_t i = 0; i < functions.size(); ++i)
{
data_ptr funcPtr = functions[i];
assert(dynamic_cast<DataMap *>(funcPtr.get().get()));
DataMap *functionData = dynamic_cast<DataMap *>(funcPtr.get().get());

// Extract function ID and name from template data
uint32_t functionId = static_cast<uint32_t>(stoul(functionData->getmap()["id"]->getvalue()));
string functionName = functionData->getmap()["name"]->getvalue();

// Calculate hash position
uint32_t hashPos = hash_function_id(functionId);
uint32_t probeDistance = 0;

// Linear probing collision resolution
while (!hashTable[hashPos]["isEmpty"]->getvalue().empty() &&
hashTable[hashPos]["isEmpty"]->getvalue() != "true")
{
hashPos = (hashPos + 1) % hashTableSize;
probeDistance++;
if (probeDistance > 0 && probeDistance == 1)
{
collisions++;
}
}

// Insert function into hash table
data_map entry;
entry["isEmpty"] = make_data(false);
entry["index"] = make_data(hashPos);
entry["id"] = make_data(functionId);
entry["name"] = make_data(functionName);
entry["probeDistance"] = make_data(probeDistance);

if (probeDistance == 0)
{
entry["comment"] = make_data(string("Direct hash"));
}
else
{
entry["comment"] = make_data(string("Collision resolved (probe +") + to_string(probeDistance) + ")");
}

hashTable[hashPos] = entry;

if (probeDistance > maxProbe)
{
maxProbe = probeDistance;
}
}

// Convert hash table to template data
data_list hashTableEntries;
for (const auto& entry : hashTable)
{
hashTableEntries.push_back(entry);
}

// Statistics
hashTableInfo["entries"] = hashTableEntries;
hashTableInfo["collisions"] = make_data(collisions);
hashTableInfo["maxProbe"] = make_data(maxProbe);
hashTableInfo["primaryHitRate"] = make_data(static_cast<double>(functionCount - collisions) / functionCount * 100.0);

Log::info("Generated hash table for interface %s: size=%d, functions=%d, collisions=%d, maxProbe=%d\n",
iface->getName().c_str(), hashTableSize, functionCount, collisions, maxProbe);

return hashTableInfo;
}
28 changes: 28 additions & 0 deletions erpcgen/src/Generator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@
#include <fstream>
#include <string>

////////////////////////////////////////////////////////////////////////////////
// Hash Table Configuration
////////////////////////////////////////////////////////////////////////////////

/*! @brief Maximum hash table size limit (power of 2) */
#define ERPC_HASH_TABLE_MAX_SIZE 4096

/*! @brief Target load factor for optimal performance */
#define ERPC_HASH_TABLE_TARGET_LOAD_FACTOR 0.65

/*! @brief Warning threshold for load factor */
#define ERPC_HASH_TABLE_WARNING_LOAD_FACTOR 0.75

/*! @brief Minimum hash table size */
#define ERPC_HASH_TABLE_MIN_SIZE 8

////////////////////////////////////////////////////////////////////////////////
// Classes
////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -407,6 +423,18 @@ class Generator
*/
void getCallbacksTemplateData(Group *group, const Interface *iface, cpptempl::data_list &callbackTypesInt,
cpptempl::data_list &callbackTypesExt, cpptempl::data_list &callbackTypesAll);

/*!
* @brief Generate compile-time hash table data for efficient function dispatch.
*
* This function generates hash table data similar to your symbols implementation,
* creating a pre-computed static array for O(1) function lookup.
*
* @param[in] iface Interface to generate hash table for
* @param[in] functions List of functions in the interface
* @return Template data for hash table generation
*/
cpptempl::data_map generateHashTableData(Interface *iface, const cpptempl::data_list &functions);
};

} // namespace erpcgen
Expand Down
4 changes: 2 additions & 2 deletions erpcgen/src/templates/cpp_interface_header.template
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ class {$iface.interfaceClassName}
{% endfor %}

{%endif %}
static const uint8_t m_serviceId = {$iface.id};
static inline const uint32_t m_serviceId = {$iface.id};
{% for fn in iface.functions %}
static const uint8_t {$getClassFunctionIdName(fn)} = {$fn.id};
static inline const uint32_t {$getClassFunctionIdName(fn)} = {$fn.id};
{% endfor -- fn %}

virtual ~{$iface.interfaceClassName}(void);
Expand Down
29 changes: 29 additions & 0 deletions erpcgen/src/templates/cpp_server_header.template
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,35 @@ public:
virtual erpc_status_t handleInvocation(uint32_t methodId, uint32_t sequence, erpc::Codec * codec, erpc::MessageBufferFactory *messageFactory, erpc::Transport * transport);

private:
/*! @brief Function pointer type for shim functions */
typedef erpc_status_t ({$iface.serviceClassName}::*ShimFunction)(erpc::{$codecClass} * codec, erpc::MessageBufferFactory *messageFactory, erpc::Transport * transport, uint32_t sequence);

/*! @brief Compile-time hash table entry structure */
struct FunctionEntry {
uint32_t id;
ShimFunction func;
const char* name; // For debugging
};

/*! @brief Hash table size (power of 2, load factor ~0.65) */
static constexpr uint32_t HASH_TABLE_SIZE = {$iface.hashTable.size};
static constexpr uint32_t FUNCTION_COUNT = {$iface.hashTable.functionCount};

/*! @brief Compile-time hash function (Knuth multiplicative method) */
static constexpr uint32_t hash_function_id(uint32_t id) {
uint32_t hash = id;
hash = ((hash >> 16) ^ hash) * 0x45d9f3bU;
hash = ((hash >> 16) ^ hash) * 0x45d9f3bU;
hash = (hash >> 16) ^ hash;
return hash & (HASH_TABLE_SIZE - 1);
}

/*! @brief Get the pre-computed hash table */
static const FunctionEntry* getFunctionTable();

/*! @brief Find function by ID using compile-time hash table */
ShimFunction findFunction(uint32_t methodId) const;

{$iface.interfaceClassName} *m_handler;
{% for fn in iface.functions %}
/*! @brief Server shim for {$fn.name} of {$iface.name} interface. */
Expand Down
78 changes: 60 additions & 18 deletions erpcgen/src/templates/cpp_server_source.template
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,54 @@ static erpc_status_t {$iface.interfaceClassName}_{$cb.name}_shim({$iface.interfa
{
}

// Get the pre-computed compile-time hash table
const {$iface.serviceClassName}::FunctionEntry* {$iface.serviceClassName}::getFunctionTable()
{
/*
* Compile-time computed hash table with collision resolution
* Hash table size: {$iface.hashTable.size}
* Function count: {$iface.hashTable.functionCount}
* Load factor: {$iface.hashTable.loadFactor}
* Collisions resolved: {$iface.hashTable.collisions}
* Maximum probe distance: {$iface.hashTable.maxProbe}
* Primary hit rate: {$iface.hashTable.primaryHitRate}%
*/
static const FunctionEntry s_functionTable[HASH_TABLE_SIZE] = {
{% for entry in iface.hashTable.entries %}
{% if entry.isEmpty %}
/* Index {$entry.index} - {$entry.comment} */
{0U, nullptr, nullptr},
{% else %}
/* Index {$entry.index} - {$entry.comment} */
{{$entry.id}U, &{$iface.serviceClassName}::{$entry.name}_shim, "{$entry.name}"},
{% endif %}
{% endfor -- entry %}
};
return s_functionTable;
}

// Find function by ID using compile-time hash table with linear probing
{$iface.serviceClassName}::ShimFunction {$iface.serviceClassName}::findFunction(uint32_t methodId) const
{
const FunctionEntry* table = getFunctionTable();
uint32_t hash_pos = hash_function_id(methodId);
uint32_t original_pos = hash_pos;

// Linear probing search (same as your symbols implementation)
do {
if (table[hash_pos].id == methodId && table[hash_pos].func != nullptr) {
return table[hash_pos].func;
}
if (table[hash_pos].id == 0) { // Empty slot
break;
}
// Move to next slot
hash_pos = (hash_pos + 1) & (HASH_TABLE_SIZE - 1);
} while (hash_pos != original_pos);

return nullptr; // Not found
}

// return service interface handler.
{$iface.interfaceClassName}* {$iface.serviceClassName}::getHandler(void)
{
Expand All @@ -215,28 +263,22 @@ static erpc_status_t {$iface.interfaceClassName}_{$cb.name}_shim({$iface.interfa
// Call the correct server shim based on method unique ID.
erpc_status_t {$iface.serviceClassName}::handleInvocation(uint32_t methodId, uint32_t sequence, Codec * codec, MessageBufferFactory *messageFactory, Transport * transport)
{
erpc_status_t erpcStatus;
{% if codecClass != "Codec" %}
{$codecClass} *_codec = static_cast<{$codecClass} *>(codec);
{% endif %}
switch (methodId)
{% endif %}

// O(1) compile-time hash table lookup for function dispatch
ShimFunction shimFunc = findFunction(methodId);
if (shimFunc != nullptr)
{
{% for fn in iface.functions %}
case {$iface.interfaceClassName}::{$getClassFunctionIdName(fn)}:
{
erpcStatus = {$fn.name}_shim({%if codecClass == "Codec" %}codec{% else %}_codec{% endif %}, messageFactory, transport, sequence);
break;
}

{% endfor -- fn %}
default:
{
erpcStatus = kErpcStatus_InvalidArgument;
break;
}
// Call the found shim function using function pointer
return (this->*shimFunc)({%if codecClass == "Codec" %}codec{% else %}_codec{% endif %}, messageFactory, transport, sequence);
}
else
{
// Method ID not found
return kErpcStatus_InvalidArgument;
}

return erpcStatus;
}
{% for fn in iface.functions %}

Expand Down