Q104FreeFirmware
Merge frozen per-core crash logs without allocation
Question
Merge timestamp-sorted crash-log rings from up to eight cores into a deterministic prefix without allocation. Use the supplied eight-node heap and platform pin/unpin adapter. A successful pin establishes visibility, preserves object lifetimes and keeps every view record and non-atomic trace payload immutable until unpin; writers may atomically change generation to revoke a snapshot but must defer plain writes until readers release their pins. No generation wraps while pinned. This is an explicit platform guarantee, not a property created by rereading an atomic counter. Snapshot each view record once after pinning. All supplied nonnull objects have their stated readable/writable extent. The view array, payloads and counters are disjoint from out, scratch and out_count; the three output/scratch objects are also mutually disjoint. Only the caller accesses out/out_count until return. For zero cores, a null view is legal. Nonempty output requires capacity trace slots and eight scratch nodes. Returning zero count invalidates tentative output contents; it does not roll back writes already made to out.
Implementation scaffold
#include <stdatomic.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct {
uint64_t ts;
uint32_t seq;
uint16_t core, code;
} trace_t;
typedef struct {
const trace_t *p;
uint16_t depth, head, count;
const atomic_uint *generation;
} view_t;
typedef enum { MERGE_OK, MERGE_TRUNCATED, MERGE_BAD, MERGE_RACE }
merge_status_t;
typedef struct { uint8_t core; uint16_t pos; } heap_node_t;
/* Supplied platform adapter. A successful pin establishes happens-before
* visibility and keeps the view array, counter objects and trace storage alive.
* Plain metadata/payload remain immutable until unpin. Atomic counters may
* signal revocation, but writers must defer plain stores until all pins release.
* Nonnull payloads are readable for their declared depth; views are real objects.
* The adapter allocates no memory on this path and never changes caller outputs.
*/
typedef struct { uintptr_t opaque; } merge_pin_t;
typedef enum { MERGE_PIN_OK, MERGE_PIN_BAD, MERGE_PIN_RACE } merge_pin_status_t;
extern merge_pin_status_t merge_platform_pin(const view_t view[],
uint8_t core_count, merge_pin_t *pin);
extern void merge_platform_unpin(merge_pin_t *pin);
static const trace_t *merge_entry(const view_t *view, uint16_t logical_pos) {
/* TODO: implement merge_entry. */
}
static bool trace_precedes(const trace_t *left, const trace_t *right) {
/* TODO: implement trace_precedes. */
}
static bool heap_precedes(const view_t view[], heap_node_t left,
heap_node_t right) {
/* TODO: implement heap_precedes. */
}
static void merge_heap_push(const view_t view[], heap_node_t heap[8],
uint8_t *size, heap_node_t node) {
/* TODO: implement merge_heap_push. */
}
static heap_node_t merge_heap_pop(const view_t view[], heap_node_t heap[8],
uint8_t *size) {
/* TODO: implement merge_heap_pop. */
}
static bool merge_generation_changed(const view_t view[], uint8_t core_count,
const unsigned saved[8]) {
/* TODO: implement merge_generation_changed. */
}
merge_status_t merge_crash_logs(
const view_t view[], uint8_t core_count,
trace_t out[], uint16_t capacity,
heap_node_t scratch[8], uint16_t *out_count) {
view_t snapshot[8];
unsigned generation[8] = {0};
uint32_t total = 0;
bool bad = false;
merge_pin_t pin;
merge_status_t result = MERGE_BAD;
/* TODO: implement merge_crash_logs. */
}
Trace one case
core0=[(t1,seq0,A),(t4,seq1,D)]; core1=[(t2,seq0,B),(t4,seq1,C)]merged=[A,B,D,C]The fixed heap sorts by timestamp, then core ID, then sequence; core 0 therefore wins the equal-timestamp tie at t4.
Requirements
- Check null/count arguments, acquire one platform pin, and release it exactly once on every path after successful acquisition. Validate the captured views: a depth-zero ring requires head=count=0; otherwise head<depth, count<=depth and nonempty data is nonnull. Validate logical wrapped indices, each entry core equal to its view index, the entire per-view nondecreasing (timestamp,core,sequence) order and total entries<=65535 before emitting any output.
- Use acquire loads of every valid generation before trace validation, after validation and after merging. Odd or changed values return MERGE_RACE with out_count=0. Race detection has priority over malformed content after valid counter pointers are established; invalid top-level arguments or null counter pointers are MERGE_BAD. Pin failure maps to its declared BAD or RACE result. The pin prevents data races; counter checks detect observed revocation, not changes occurring after a counter's final sample.
- Use only the supplied eight-node minimum heap for active cursors. Order by timestamp, then core, then sequence. Retain equal-key records in their original per-core order and preserve duplicates. Capture metadata once, and use that captured depth/head/count for every later index and cursor advance.
- Return the exact earliest min(total,capacity) records. A short destination, including capacity zero, returns MERGE_TRUNCATED; empty input returns MERGE_OK. Null out/scratch are allowed when no output is emitted. Publish out_count only after the final generation check; BAD and RACE leave it zero, and callers must ignore any tentative output contents.
