summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt34
-rw-r--r--doc/CONTRIBUTING.md76
-rw-r--r--doc/HOWTO.md24
-rwxr-xr-xscripts/build_final.sh71
-rw-r--r--src/util/fatal_error.h180
5 files changed, 384 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ef072ec..7000d0a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,9 +4,10 @@ project(demo64k LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-#-- - Configuration Options -- -
+#-- - Configuration Options -- -
option(DEMO_SIZE_OPT "Enable size optimization flags" OFF)
option(DEMO_STRIP_ALL "Strip all unnecessary code for final build" OFF)
+option(DEMO_FINAL_STRIP "Strip ALL error checking for final-final build" OFF)
option(DEMO_BUILD_TESTS "Build tests" OFF)
option(DEMO_BUILD_TOOLS "Build tools" OFF)
option(DEMO_ENABLE_COVERAGE "Enable code coverage generation (macOS only)" OFF)
@@ -18,6 +19,15 @@ if (DEMO_ALL_OPTIONS)
set(DEMO_STRIP_ALL ON)
set(DEMO_BUILD_TESTS ON)
set(DEMO_BUILD_TOOLS ON)
+ # NOTE: DEMO_FINAL_STRIP is NOT included here (too dangerous for testing)
+endif()
+
+# FINAL_STRIP: Most aggressive stripping (removes ALL error checking)
+# Implies STRIP_ALL (stricter superset)
+if (DEMO_FINAL_STRIP)
+ add_definitions(-DFINAL_STRIP)
+ set(DEMO_STRIP_ALL ON)
+ message(STATUS "FINAL_STRIP enabled - all error checking will be removed")
endif()
if (DEMO_STRIP_ALL)
@@ -308,6 +318,28 @@ if (DEMO_SIZE_OPT)
endif()
# ==============================================================================
+# Final Build Target (make final)
+# ==============================================================================
+# Convenience target for final production build with all stripping enabled
+# Usage: make final
+# This reconfigures CMake with FINAL_STRIP and rebuilds demo64k
+# Note: Only create this target if we're NOT already in FINAL_STRIP mode
+
+if (NOT DEMO_FINAL_STRIP)
+ add_custom_target(final
+ COMMAND ${CMAKE_COMMAND} -E echo "Building FINAL production binary..."
+ COMMAND ${CMAKE_COMMAND} ${CMAKE_SOURCE_DIR} -DDEMO_FINAL_STRIP=ON -DDEMO_SIZE_OPT=ON
+ COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target demo64k -j8
+ COMMAND ${CMAKE_COMMAND} -E echo ""
+ COMMAND ${CMAKE_COMMAND} -E echo "Final build complete!"
+ COMMAND ${CMAKE_COMMAND} -E echo "Binary: ${CMAKE_BINARY_DIR}/demo64k"
+ COMMAND ls -lh ${CMAKE_BINARY_DIR}/demo64k
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ COMMENT "Building final production binary with FINAL_STRIP"
+ )
+endif()
+
+# ==============================================================================
# Test Demo (Audio/Visual Sync Tool)
# ==============================================================================
diff --git a/doc/CONTRIBUTING.md b/doc/CONTRIBUTING.md
index ae8c35a..d7ea448 100644
--- a/doc/CONTRIBUTING.md
+++ b/doc/CONTRIBUTING.md
@@ -340,6 +340,82 @@ For tests that only need synth or tracker (not both), you can still call `synth_
These are performance-critical APIs and should be called directly, not wrapped by AudioEngine.
+### Fatal Error Checking
+
+The project uses fatal error checking macros that can be stripped for final builds. These are defined in `src/util/fatal_error.h`.
+
+**When to use fatal error checks:**
+
+Use fatal error checking for conditions that indicate programming errors or corrupt state that cannot be recovered from:
+- Bounds checking (array/buffer overflows)
+- Null pointer checks
+- Invariant violations
+- Invalid state transitions
+
+**DO NOT use for:**
+- Expected runtime errors (file not found, network errors)
+- Recoverable conditions
+- Input validation (use return codes instead)
+
+**Macro Reference:**
+
+```cpp
+#include "util/fatal_error.h"
+
+// Conditional check (most common - 90% of cases)
+FATAL_CHECK(write_pos >= capacity,
+ "write_pos out of bounds: %d >= %d\n",
+ write_pos, capacity);
+
+// Unconditional error (should-never-happen)
+FATAL_ERROR("Invalid state: %d\n", state);
+
+// Unreachable code marker (switch defaults)
+switch (type) {
+ case TYPE_A: return handle_a();
+ case TYPE_B: return handle_b();
+ default: FATAL_UNREACHABLE();
+}
+
+// Assertion-style (documenting invariants)
+FATAL_ASSERT(buffer != nullptr);
+FATAL_ASSERT(count > 0 && count < MAX_COUNT);
+
+// Complex validation blocks
+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
+```
+
+**Build modes:**
+- **Debug/STRIP_ALL**: All checks enabled, full error messages with file:line info
+- **FINAL_STRIP**: All checks compiled out (zero runtime cost, ~500-600 bytes saved)
+
+**Testing requirement:**
+
+Before committing code with fatal error checks, verify both modes compile:
+
+```bash
+# Normal build (checks enabled)
+cmake -S . -B build
+cmake --build build && cd build && ctest
+
+# FINAL_STRIP build (checks stripped)
+cmake -S . -B build_final -DDEMO_FINAL_STRIP=ON
+cmake --build build_final
+```
+
+Or use the convenience script:
+```bash
+./scripts/build_final.sh
+```
+
+**Important:** Never use `FATAL_CHECK` for conditions that can legitimately occur during normal operation. These are for programming errors only.
+
### Script Maintenance After Hierarchy Changes
After any major source hierarchy change (moving, renaming, or reorganizing files), you **must** review and update all scripts in the `scripts/` directory to ensure they remain functional.
diff --git a/doc/HOWTO.md b/doc/HOWTO.md
index 36216fa..3a1aee2 100644
--- a/doc/HOWTO.md
+++ b/doc/HOWTO.md
@@ -46,6 +46,30 @@ cmake --build build
```
In this mode, the demo will always start in fullscreen.
+### Final-Final Build (Maximum Stripping)
+
+For the absolute smallest binary (removing ALL error checking), use the `DEMO_FINAL_STRIP` option:
+
+```bash
+# Method 1: CMake target (from normal build directory)
+cd build
+make final
+
+# Method 2: Convenience script (creates build_final/ directory)
+./scripts/build_final.sh
+
+# Method 3: Manual CMake configuration
+cmake -S . -B build_final -DDEMO_FINAL_STRIP=ON
+cmake --build build_final
+```
+
+**⚠️ WARNING:** FINAL_STRIP removes ALL error checking (bounds checks, null checks, assertions). This saves ~500-600 bytes but means crashes will have no error messages. Use ONLY for final release builds, never for development or testing.
+
+**Build Mode Hierarchy:**
+- **Debug**: Full error checking + debug features
+- **STRIP_ALL**: Full error checking, no debug features (~64k target)
+- **FINAL_STRIP**: No error checking, no debug features (absolute minimum size)
+
### Developer Build (All Options)
To enable all features at once (tests, tools, size optimizations, and stripping) for a comprehensive check:
diff --git a/scripts/build_final.sh b/scripts/build_final.sh
new file mode 100755
index 0000000..ae85030
--- /dev/null
+++ b/scripts/build_final.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+# This file is part of the 64k demo project.
+# Builds the final production binary with all stripping enabled.
+# Usage: ./scripts/build_final.sh
+
+set -e # Exit on error
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+BUILD_DIR="$PROJECT_ROOT/build_final"
+
+echo "========================================"
+echo " Building FINAL Production Binary"
+echo "========================================"
+echo ""
+echo "This build uses:"
+echo " - DEMO_FINAL_STRIP=ON (removes ALL error checking)"
+echo " - DEMO_STRIP_ALL=ON (removes debug features)"
+echo " - DEMO_SIZE_OPT=ON (maximum size optimization)"
+echo ""
+
+# Clean previous final build
+if [ -d "$BUILD_DIR" ]; then
+ echo "Cleaning previous final build..."
+ rm -rf "$BUILD_DIR"
+fi
+
+# Create fresh build directory
+mkdir -p "$BUILD_DIR"
+cd "$BUILD_DIR"
+
+# Configure with FINAL_STRIP
+echo "Configuring with FINAL_STRIP..."
+cmake "$PROJECT_ROOT" \
+ -DDEMO_FINAL_STRIP=ON \
+ -DDEMO_SIZE_OPT=ON \
+ -DCMAKE_BUILD_TYPE=Release
+
+# Build demo64k
+echo ""
+echo "Building demo64k..."
+cmake --build . --target demo64k -j8
+
+# Show result
+echo ""
+echo "========================================"
+echo " Final Build Complete!"
+echo "========================================"
+echo ""
+echo "Binary location:"
+ls -lh "$BUILD_DIR/demo64k"
+echo ""
+
+# Optional: Compare with stripped build (if exists)
+STRIP_BUILD="$PROJECT_ROOT/build_strip"
+if [ -f "$STRIP_BUILD/demo64k" ]; then
+ FINAL_SIZE=$(stat -f%z "$BUILD_DIR/demo64k" 2>/dev/null || stat -c%s "$BUILD_DIR/demo64k")
+ STRIP_SIZE=$(stat -f%z "$STRIP_BUILD/demo64k" 2>/dev/null || stat -c%s "$STRIP_BUILD/demo64k")
+ SAVED=$((STRIP_SIZE - FINAL_SIZE))
+
+ echo "Size comparison:"
+ echo " STRIP_ALL build: $STRIP_SIZE bytes"
+ echo " FINAL_STRIP build: $FINAL_SIZE bytes"
+ echo " Saved: $SAVED bytes"
+ echo ""
+fi
+
+echo "To run: $BUILD_DIR/demo64k"
+echo ""
+echo "⚠️ WARNING: This build has NO error checking!"
+echo " Use only for final release, not for development/testing."
diff --git a/src/util/fatal_error.h b/src/util/fatal_error.h
new file mode 100644
index 0000000..ea4d168
--- /dev/null
+++ b/src/util/fatal_error.h
@@ -0,0 +1,180 @@
+// 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 <cstdio>
+#include <cstdlib>
+
+// 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: <message> [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: <condition> [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)
+//