// This file is part of the 64k demo project. // Provides fatal error checking macros that can be stripped for final builds. // These macros compile to nothing when FINAL_STRIP is enabled. #pragma once #include #include // Build Mode Hierarchy: // - Debug: Full error checking with messages and file:line info // - STRIP_ALL: Full error checking (same as Debug) // - FINAL_STRIP: NO error checking (all macros compile to nothing) // // FINAL_STRIP implies STRIP_ALL (stricter superset) #if defined(FINAL_STRIP) // ============================================================================== // FINAL_STRIP: All error checking compiled out (zero runtime cost) // ============================================================================== // Conditional fatal error check - compiles to nothing #define FATAL_CHECK(cond, ...) ((void)0) // Unconditional fatal error - compiles to nothing #define FATAL_ERROR(...) ((void)0) // Unreachable code marker - compiles to nothing #define FATAL_UNREACHABLE() ((void)0) // Assertion-style check (for documenting invariants) - compiles to nothing #define FATAL_ASSERT(cond) ((void)0) // Block-based error checking - code inside is stripped entirely #define FATAL_CODE_BEGIN if (0) { #define FATAL_CODE_END } #else // ============================================================================== // Debug / STRIP_ALL: Full error checking enabled // ============================================================================== // Conditional fatal error check with formatted message // Usage: FATAL_CHECK(x >= max, "x out of bounds: %d >= %d\n", x, max); // // If condition is TRUE (error detected): // - Prints "FATAL: [file.cc:line]" to stderr // - Calls abort() // // Example output: // FATAL: write_pos out of bounds: write=100 >= capacity=50 [ring_buffer.cc:57] #define FATAL_CHECK(cond, ...) \ do { \ if (cond) { \ fprintf(stderr, "FATAL: " __VA_ARGS__); \ fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \ abort(); \ } \ } while (0) // Unconditional fatal error (for impossible code paths) // Usage: FATAL_ERROR("Should never reach here!"); // // Always prints message and aborts. // // Example output: // FATAL: Should never reach here! [main.cc:123] #define FATAL_ERROR(...) \ do { \ fprintf(stderr, "FATAL: " __VA_ARGS__); \ fprintf(stderr, " [%s:%d]\n", __FILE__, __LINE__); \ abort(); \ } while (0) // Unreachable code marker (for switch default cases, impossible branches) // Usage: // switch (type) { // case A: return handle_a(); // case B: return handle_b(); // default: FATAL_UNREACHABLE(); // } #define FATAL_UNREACHABLE() FATAL_ERROR("Unreachable code executed") // Assertion-style check (for documenting invariants) // Usage: FATAL_ASSERT(buffer != nullptr); // // If condition is FALSE (assertion failed): // - Prints "FATAL: Assertion failed: [file.cc:line]" // - Calls abort() // // Example output: // FATAL: Assertion failed: buffer != nullptr [audio.cc:42] #define FATAL_ASSERT(cond) \ do { \ if (!(cond)) { \ fprintf(stderr, "FATAL: Assertion failed: %s [%s:%d]\n", #cond, \ __FILE__, __LINE__); \ abort(); \ } \ } while (0) // Block-based error checking (for complex validation logic) // Usage: // FATAL_CODE_BEGIN // static int callback_depth = 0; // ++callback_depth; // if (callback_depth > 1) { // FATAL_ERROR("Callback re-entered! depth=%d", callback_depth); // } // FATAL_CODE_END // // The entire block is stripped when FINAL_STRIP is enabled. #define FATAL_CODE_BEGIN #define FATAL_CODE_END #endif /* defined(FINAL_STRIP) */ // ============================================================================== // Usage Guidelines // ============================================================================== // // When to use each macro: // // FATAL_CHECK(cond, msg, ...): // - Most common use case (90% of error checks) // - Bounds checking: FATAL_CHECK(idx >= size, "Index %d >= %d\n", idx, size) // - Null checking: FATAL_CHECK(ptr == nullptr, "Null pointer: %s\n", name) // - Range validation: FATAL_CHECK(x < min || x > max, "Out of range\n") // // FATAL_ERROR(msg, ...): // - Unconditional errors (should-never-happen cases) // - Example: FATAL_ERROR("Invalid state: %d\n", state) // // FATAL_UNREACHABLE(): // - Switch default cases that should never be reached // - Impossible code paths after exhaustive checks // - Example: switch (enum) { ... default: FATAL_UNREACHABLE(); } // // FATAL_ASSERT(cond): // - Documenting invariants (not error handling) // - Pre/post conditions // - Example: FATAL_ASSERT(count > 0 && count < MAX_COUNT) // // FATAL_CODE_BEGIN/END: // - Complex validation logic that cannot be expressed as single condition // - Temporary state tracking for validation // - Example: Callback re-entry detection with static counter // // ============================================================================== // Size Impact // ============================================================================== // // Debug / STRIP_ALL builds: // - Each FATAL_CHECK: ~50-80 bytes (depends on message length) // - Includes fprintf() call, error message string, file:line info // // FINAL_STRIP builds: // - All macros: 0 bytes (compiled out entirely) // - Estimated savings: ~500-600 bytes for typical project // // ============================================================================== // Testing Strategy // ============================================================================== // // Always test both modes before committing: // // 1. Normal build (error checking enabled): // cmake -S . -B build // cmake --build build && cd build && ctest // // 2. FINAL_STRIP build (error checking stripped): // cmake -S . -B build_final -DDEMO_FINAL_STRIP=ON // cmake --build build_final // // Or use convenience commands: // make final (CMake target) // ./scripts/build_final.sh (Script) //