Skip to content

Commit 391a943

Browse files
committed
jit/decoder: Overhaul arm32 decoder
Rewrites the Arm32 instruction decoder to align with high integrity software standards such as MISRA C and BARR-C. Key Architectural Changes: - Added compile time validation. - Removed dependency on memory allocator. The decoder now uses statically allocated global storage. - Implement a hash-based lookup table to reduce decoding complexity to O(1). - Removed decoder_t in favour of a singleton pattern. - Add documentation stating thread safety, preconditions, and postconditions for all public functions. Signed-off-by: Ronald Caesar <github43132@proton.me>
1 parent 04b5af2 commit 391a943

File tree

3 files changed

+233
-122
lines changed

3 files changed

+233
-122
lines changed

src/jit/decoder/arm32.cpp

Lines changed: 169 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -6,137 +6,212 @@
66
#include "common/logging.h"
77

88
namespace pound::jit::decoder {
9-
#define INSTRUCTION_ARRAY_CAPACITY 261
9+
10+
/*! @brief Maximum number of instructions allowed in a single hash bucket. */
11+
#define LOOKUP_TABLE_MAX_BUCKET_SIZE 8
12+
13+
/*! @brief Size of the lookup table (12-bit index). */
14+
#define LOOKUP_TABLE_INDEX_MASK 0xFFF
15+
16+
/*! @brief Expected length of the bitstring representation in the .inc file. */
1017
#define INSTRUCTION_BITSTRING_LENGTH 32
1118

12-
/*
13-
* ============================================================================
14-
* Foward Declarations
15-
* ============================================================================
19+
/*!
20+
* @brief A bucket within the decoding hash table.
21+
* @details Stores a list of instructions that collide on the same hash index.
1622
*/
17-
static void arm32_add_instruction(arm32_decoder_t *p_decoder,
18-
const char *p_name,
19-
const char *p_bitstring);
20-
static void arm32_parse_bitstring(const char *p_bitstring,
21-
uint32_t *p_mask,
22-
uint32_t *p_expected);
23+
typedef struct
24+
{
25+
/*! @brief Array of pointers to instruction definitions. */
26+
const arm32_instruction_info_t *instructions[LOOKUP_TABLE_MAX_BUCKET_SIZE];
27+
28+
/*! @brief Current number of instructions in this bucket. */
29+
size_t count;
30+
} decode_bucket_t;
31+
32+
/*! @brief The internal state of the decoder. */
33+
typedef struct
34+
{
35+
/*!
36+
* @brief The main lookup table.
37+
* @details Index constructed from bits [27:20] and [7:4] of the
38+
* instruction.
39+
*/
40+
decode_bucket_t lookup_table[LOOKUP_TABLE_INDEX_MASK + 1];
41+
42+
/*! @brief Initialization guard flag. */
43+
bool is_initialized;
44+
} decoder_t;
45+
46+
/*! @brief Global decoder instance. */
47+
static decoder_t g_decoder = {};
2348

2449
/*
2550
* ============================================================================
26-
* Public Functions
51+
* Compile-Time Functions
2752
* ============================================================================
2853
*/
29-
void
30-
arm32_init (pound::host::memory::arena_t allocator, arm32_decoder_t *p_decoder)
31-
{
32-
PVM_ASSERT(nullptr != p_decoder);
33-
PVM_ASSERT(nullptr != allocator.data);
3454

35-
(void)memset(p_decoder, 0, sizeof(arm32_decoder_t));
36-
p_decoder->allocator = allocator;
55+
/*! @brief Prototype for a non-constexpr function to force build failure. */
56+
void BUILD_ERROR_ARM32_BITSTRING_MUST_BE_32_CHARS(void);
3757

38-
/* Setup Instructions array.*/
39-
size_t instructions_array_size
40-
= INSTRUCTION_ARRAY_CAPACITY * sizeof(arm32_instruction_info_t);
41-
PVM_ASSERT(instructions_array_size <= p_decoder->allocator.capacity);
42-
LOG_TRACE("Allocated %d bytes to instructions array",
43-
instructions_array_size);
58+
/*!
59+
* @brief Parses a binary string literal into a bitmask at compile time.
60+
*
61+
* @param[in] string The null-terminated string literal.
62+
* @return The calculated uint32_t mask.
63+
*/
64+
consteval uint32_t
65+
parse_mask (const char *string)
66+
{
67+
size_t length = 0;
4468

45-
void *p_memory = pound::host::memory::arena_allocate(
46-
&p_decoder->allocator, instructions_array_size);
47-
PVM_ASSERT(nullptr != p_memory);
69+
for (; string[length] != '\0'; ++length)
70+
{
71+
}
4872

49-
p_decoder->p_instructions = (arm32_instruction_info_t *)p_memory;
50-
p_decoder->instruction_capacity = INSTRUCTION_ARRAY_CAPACITY;
73+
if (length != 32)
74+
{
75+
BUILD_ERROR_ARM32_BITSTRING_MUST_BE_32_CHARS();
76+
}
5177

52-
/* Add all Arm32 instructions */
53-
#define INST(fn, name, bitstring) \
54-
arm32_add_instruction(p_decoder, name, bitstring);
55-
#include "./arm32.inc"
56-
#undef INST
78+
uint32_t mask = 0;
79+
for (int i = 0; i < 32; ++i)
80+
{
81+
const uint32_t bit = 1U << (31 - i);
82+
if ('0' == string[i] || string[i] == '1')
83+
{
84+
mask |= bit;
85+
}
86+
}
87+
return mask;
5788
}
5889

59-
arm32_instruction_info_t *
60-
arm32_decode (arm32_decoder_t *p_decoder, uint32_t instruction)
90+
/*!
91+
* @brief Parses a binary string literal into an expected value at compile time.
92+
*
93+
* @param[in] string The null-terminated string literal.
94+
* @return The calculated uint32_t expected value.
95+
*/
96+
consteval uint32_t
97+
parse_expected (const char *string)
6198
{
62-
for (size_t i = 0; i < p_decoder->instruction_count; ++i)
99+
uint32_t expected = 0;
100+
for (int i = 0; i < 32; ++i)
63101
{
64-
arm32_instruction_info_t *p_info = &p_decoder->p_instructions[i];
65-
if ((instruction & p_info->mask) == p_info->expected)
102+
const uint32_t bit = 1U << (31 - i);
103+
if (string[i] == '1')
66104
{
67-
LOG_TRACE("Instruction found for 0x%08X: %s",
68-
instruction,
69-
p_info->p_name);
70-
return p_info;
105+
expected |= bit;
71106
}
72107
}
73-
PVM_ASSERT_MSG(false, "No instruction found for 0x%08X", instruction);
108+
return expected;
74109
}
75110

111+
/*! @brief List of all supported ARM32 instructions. */
112+
static const arm32_instruction_info_t g_instructions[] = {
113+
#define INST(fn, name, bitstring) \
114+
{ name, parse_mask(bitstring), parse_expected(bitstring) },
115+
#include "arm32.inc"
116+
#undef INST
117+
};
118+
119+
/*! @brief The total number of defined instructions. */
120+
#define INSTRUCTION_ARRAY_CAPACITY \
121+
(sizeof(g_instructions) / sizeof(g_instructions[0]))
122+
76123
/*
77124
* ============================================================================
78-
* Private Functions
125+
* Public Functions
79126
* ============================================================================
80127
*/
81128

82-
static void
83-
arm32_add_instruction (arm32_decoder_t *p_decoder,
84-
const char *p_name,
85-
const char *p_bitstring)
129+
void
130+
arm32_init (void)
86131
{
87-
PVM_ASSERT(nullptr != p_decoder);
88-
PVM_ASSERT(nullptr != p_decoder->allocator.data);
89-
PVM_ASSERT(nullptr != p_name);
90-
PVM_ASSERT(nullptr != p_bitstring);
91-
PVM_ASSERT(p_decoder->instruction_count < p_decoder->instruction_capacity);
132+
PVM_ASSERT_MSG(false == g_decoder.is_initialized,
133+
"Decoder already initialized.");
92134

93-
uint32_t mask = 0;
94-
uint32_t expected = 0;
95-
arm32_parse_bitstring(p_bitstring, &mask, &expected);
135+
(void)memset(g_decoder.lookup_table, 0, sizeof(g_decoder.lookup_table));
136+
137+
// Populate the hash table.
138+
for (uint32_t i = 0; i <= LOOKUP_TABLE_INDEX_MASK; ++i)
139+
{
140+
decode_bucket_t *bucket = &g_decoder.lookup_table[i];
96141

97-
arm32_instruction_info_t *p_info
98-
= &p_decoder->p_instructions[p_decoder->instruction_count];
99-
PVM_ASSERT(nullptr != p_info);
100-
p_info->p_name = p_name;
101-
p_info->mask = mask;
102-
p_info->expected = expected;
142+
// Reconstruct the instruction bits that correspond to this hash index.
143+
// Bits [27:20] and [7:4].
144+
const uint32_t synthetic_instruction
145+
= ((i & 0xFF0) << 16) | ((i & 0xF) << 4);
103146

104-
++p_decoder->instruction_count;
147+
for (size_t ii = 0; ii < INSTRUCTION_ARRAY_CAPACITY; ++ii)
148+
{
149+
const arm32_instruction_info_t *info = &g_instructions[ii];
150+
151+
/* Mask corresponding to the hash bits: 0x0FF000F0 */
152+
const uint32_t index_bits_mask = 0x0FF000F0;
153+
const uint32_t relevant_mask = index_bits_mask | info->mask;
154+
155+
if ((synthetic_instruction & relevant_mask)
156+
== (info->expected & relevant_mask))
157+
{
158+
LOG_TRACE("Mapping instruction '%s' to LUT Index 0x%03X",
159+
info->name,
160+
i);
161+
162+
if (bucket->count >= LOOKUP_TABLE_MAX_BUCKET_SIZE)
163+
{
164+
PVM_ASSERT_MSG(
165+
false,
166+
"ARM32 LUT Collision Overflow at index 0x%03X. "
167+
"Increase MAX_LUT_BUCKET_SIZE.",
168+
i);
169+
}
170+
171+
if (bucket->count >= (LOOKUP_TABLE_MAX_BUCKET_SIZE / 2))
172+
{
173+
LOG_WARNING(
174+
"High collision density at Index 0x%03X (Count: %zu).",
175+
i,
176+
bucket->count + 1);
177+
}
178+
bucket->instructions[bucket->count] = info;
179+
++bucket->count;
180+
}
181+
}
182+
}
105183

106-
LOG_TRACE("Instruction Registered: %s", p_info->p_name);
107-
LOG_TRACE("Mask: 0x%08X", p_info->mask);
108-
LOG_TRACE("Expected: 0x%08X", p_info->expected);
184+
g_decoder.is_initialized = true;
185+
LOG_INFO("ARM32 Decoder initialized with %zu instructions",
186+
INSTRUCTION_ARRAY_CAPACITY);
109187
}
110188

111-
static void
112-
arm32_parse_bitstring (const char *p_bitstring,
113-
uint32_t *p_mask,
114-
uint32_t *p_expected)
189+
const arm32_instruction_info_t *
190+
arm32_decode (const uint32_t instruction)
115191
{
116-
PVM_ASSERT(nullptr != p_bitstring);
117-
PVM_ASSERT(nullptr != p_mask);
118-
PVM_ASSERT(nullptr != p_expected);
119-
PVM_ASSERT(INSTRUCTION_BITSTRING_LENGTH == strlen(p_bitstring));
120-
121-
*p_mask = 0;
122-
*p_expected = 0;
123-
uint8_t instruction_size_bits = 32;
124-
for (unsigned int i = 0;
125-
(i < instruction_size_bits) && (p_bitstring[i] != '\0');
126-
++i)
192+
PVM_ASSERT_MSG(true == g_decoder.is_initialized,
193+
"Decoder needs to initialize.");
194+
195+
/* Extract hash key: Bits [27:20] and [7:4] */
196+
const uint32_t major = (instruction >> 20) & 0xFF;
197+
const uint32_t minor = (instruction >> 4) & 0xF;
198+
199+
const uint16_t index = (uint16_t)((major << 4) | minor);
200+
201+
const decode_bucket_t *bucket = &g_decoder.lookup_table[index];
202+
203+
for (size_t i = 0; i < bucket->count; ++i)
127204
{
128-
uint32_t bit_position = 31 - i;
129-
switch (p_bitstring[i])
205+
const arm32_instruction_info_t *info = bucket->instructions[i];
206+
207+
if ((instruction & info->mask) == info->expected)
130208
{
131-
case '0':
132-
*p_mask |= (1U << bit_position);
133-
break;
134-
case '1':
135-
*p_mask |= (1U << bit_position);
136-
*p_expected |= (1U << bit_position);
137-
default:
138-
break;
209+
210+
return info;
139211
}
140212
}
213+
214+
return nullptr;
141215
}
142-
} // namespace pound::jit::p_decoder
216+
217+
} // namespace pound::jit::decoder

src/jit/decoder/arm32.h

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,74 @@
1+
/**
2+
* @file arm32.h
3+
* @brief A32 Instruction Decoder Interface.
4+
*
5+
* @details
6+
* This module provides the interface for decoding 32-bit ARM instructions
7+
* into internal metadata structures.
8+
*
9+
* @note
10+
* While the decoding lookup is thread-safe after initialization, the
11+
* initialization phase itself is NOT thread-safe.
12+
*/
13+
114
#ifndef POUND_JIT_DECODER_ARM32_H
215
#define POUND_JIT_DECODER_ARM32_H
316

4-
#include <stddef.h>
517
#include <stdint.h>
6-
#include "host/memory/arena.h"
718

819
namespace pound::jit::decoder {
9-
typedef struct arm32_decoder arm32_decoder_t;
1020

21+
/*! @brief Represents static metadata associated with a specific ARM32 instruction. */
1122
typedef struct
1223
{
13-
const char *p_name;
24+
/*! @brief The instruction mnemonic (e.g., "ADD", "LDR"). */
25+
const char *name;
26+
27+
/*!
28+
* @brief The bitmask indicating which bits in the instruction word are significant.
29+
* @details 1 = significant bit, 0 = variable field (register, immediate, etc.).
30+
*/
1431
uint32_t mask;
32+
33+
/*!
34+
* @brief The expected value of the instruction after applying the mask.
35+
* @details (instruction & mask) == expected.
36+
*/
1537
uint32_t expected;
1638
} arm32_instruction_info_t;
1739

18-
struct arm32_decoder
19-
{
20-
pound::host::memory::arena_t allocator;
21-
arm32_instruction_info_t *p_instructions;
22-
size_t instruction_count;
23-
size_t instruction_capacity;
24-
};
25-
26-
void arm32_init(pound::host::memory::arena_t allocator,
27-
arm32_decoder_t *p_decoder);
28-
arm32_instruction_info_t *arm32_decode(arm32_decoder_t *p_decoder,
29-
uint32_t instruction);
30-
}
40+
/*!
41+
* @brief Initializes the ARM32 decoder lookup tables.
42+
*
43+
* @details
44+
* Populates the internal hash table used for O(1) instruction decoding.
45+
*
46+
* @pre This function must be called during the application startup phase.
47+
* @post The internal global decoder state is initialized and ready for use.
48+
*
49+
* @warning *!Thread Safety*!: Unsafe. This function modifies global state without locking.
50+
*/
51+
void arm32_init(void);
52+
53+
/*!
54+
* @brief Decodes a raw 32-bit ARM instruction.
55+
*
56+
* @details
57+
* Performs a hash lookup on the provided instruction word to find a matching
58+
* definition.
59+
*
60+
* @param[in] instruction The raw 32-bit ARM machine code to decode.
61+
*
62+
* @return A pointer to the instruction metadata if a match is found.
63+
* @return `nullptr` if the instruction is undefined or invalid.
64+
*
65+
* @pre `arm32_init()` must have been called successfully.
66+
* @post The returned pointer (if not null) points to static read-only memory.
67+
*
68+
* @note *!Thread Safety*!: Safe. This function is read-only re-entrant.
69+
*/
70+
const arm32_instruction_info_t *arm32_decode(const uint32_t instruction);
71+
72+
} // namespace pound::jit::decoder
73+
3174
#endif // POUND_JIT_DECODER_ARM32_H

0 commit comments

Comments
 (0)