|
| 1 | +# Stack Optimization for decompress_accounts_idempotent Macro |
| 2 | + |
| 3 | +## Problem |
| 4 | + |
| 5 | +The macro-generated `decompress_accounts_idempotent` function had a stack frame of 6080 bytes, exceeding the 4096 byte limit by 1640 bytes. |
| 6 | + |
| 7 | +## Solution: Inner Function Decomposition with Parameter Bundling |
| 8 | + |
| 9 | +Split the large monolithic function into multiple **inner helper functions** within the main function, each with its own stack frame. This avoids Anchor's "multiple fallback functions" error while still reducing stack usage. Additionally, bundle parameters into structs to reduce stack pressure from parameter passing. |
| 10 | + |
| 11 | +### 1. **Main Function** (`decompress_accounts_idempotent`) |
| 12 | + |
| 13 | +- Contains all helper functions as inner functions |
| 14 | +- Reduced main logic to coordination only |
| 15 | +- Validates inputs and delegates to helpers |
| 16 | +- Stack usage: ~500 bytes (estimated) |
| 17 | + |
| 18 | +### 2. **Inner Setup Helper** (`setup_cpi_and_config`) |
| 19 | + |
| 20 | +- Defined inside main function |
| 21 | +- Handles CPI account creation |
| 22 | +- Loads and validates config |
| 23 | +- Returns boxed values |
| 24 | +- Stack usage: ~200 bytes (estimated) |
| 25 | +- Marked with `#[inline(never)]` |
| 26 | + |
| 27 | +### 3. **Inner Processing Helper** (`process_single_compressed_variant`) |
| 28 | + |
| 29 | +- Defined inside main function |
| 30 | +- Takes parameters bundled in a `ProcessParams` struct to reduce stack |
| 31 | +- Processes one compressed account at a time |
| 32 | +- Contains the match statement for account variants |
| 33 | +- All large data structures boxed |
| 34 | +- Stack usage: ~200 bytes (estimated, reduced from 4392) |
| 35 | +- Marked with `#[inline(never)]` and `#[cold]` |
| 36 | + |
| 37 | +### 4. **Inner Dispatch Helper** (`dispatch_variant`) |
| 38 | + |
| 39 | +- Defined inside main function |
| 40 | +- Contains only the match statement |
| 41 | +- Isolates variant matching from other processing |
| 42 | +- Stack usage: ~150 bytes (estimated) |
| 43 | +- Marked with `#[inline(never)]` and `#[cold]` |
| 44 | + |
| 45 | +### 5. **Inner Prepare Accounts Helper** (`call_prepare_accounts`) |
| 46 | + |
| 47 | +- Defined inside main function |
| 48 | +- Generic helper to call `prepare_accounts_for_decompress_idempotent` |
| 49 | +- Separates the heavy lifting from the match statement |
| 50 | +- Stack usage: ~300 bytes (estimated) |
| 51 | +- Marked with `#[inline(never)]` and `#[cold]` |
| 52 | + |
| 53 | +### 6. **Inner CPI Helper** (`invoke_cpi_with_compressed_accounts`) |
| 54 | + |
| 55 | +- Defined inside main function |
| 56 | +- Handles the final CPI invocation |
| 57 | +- Minimal stack usage |
| 58 | +- Stack usage: ~200 bytes (estimated) |
| 59 | +- Marked with `#[inline(never)]` |
| 60 | + |
| 61 | +## Key Optimizations |
| 62 | + |
| 63 | +1. **Function Splitting**: Breaking the function reduces per-frame stack usage from 6080 to ~500 bytes max per function |
| 64 | + |
| 65 | +2. **Parameter Bundling**: Using `ProcessParams` struct to pass multiple parameters as a single boxed value |
| 66 | + |
| 67 | +3. **Boxing Strategy**: All large data structures are immediately boxed: |
| 68 | + |
| 69 | + - `Box::new(proof)` |
| 70 | + - `Box::new(compressed_accounts)` |
| 71 | + - `Box::new(bumps)` |
| 72 | + - `Box::new(Vec::with_capacity(...))` |
| 73 | + |
| 74 | +4. **Iterator Optimization**: Removed iterator chaining that could create temporary stack allocations |
| 75 | + |
| 76 | +5. **Cold Path Marking**: Helper functions marked with `#[cold]` to optimize for the common path |
| 77 | + |
| 78 | +6. **No Inline**: All helpers use `#[inline(never)]` to ensure separate stack frames |
| 79 | + |
| 80 | +## Benefits |
| 81 | + |
| 82 | +- **Stack Safety**: Each function now uses well under the 4096 byte limit |
| 83 | +- **Maintainability**: Smaller, focused functions are easier to understand |
| 84 | +- **Debuggability**: Stack traces will show which helper failed |
| 85 | +- **Flexibility**: Individual helpers can be further optimized if needed |
| 86 | + |
| 87 | +## Estimated Stack Usage |
| 88 | + |
| 89 | +| Function | Before | After V1 | After V2 | After V3 | |
| 90 | +| ----------------------------------- | ---------- | ---------- | ---------- | ---------- | |
| 91 | +| decompress_accounts_idempotent | 6080 bytes | ~500 bytes | ~500 bytes | ~500 bytes | |
| 92 | +| setup_cpi_and_config | N/A | ~200 bytes | ~200 bytes | ~200 bytes | |
| 93 | +| process_single_compressed_variant | N/A | 4392 bytes | 4312 bytes | ~150 bytes | |
| 94 | +| dispatch_variant | N/A | N/A | N/A | ~150 bytes | |
| 95 | +| call_prepare_accounts | N/A | N/A | ~300 bytes | ~300 bytes | |
| 96 | +| invoke_cpi_with_compressed_accounts | N/A | ~200 bytes | ~200 bytes | ~200 bytes | |
| 97 | + |
| 98 | +Total maximum stack depth: ~1500 bytes (well under 4096 limit) |
| 99 | + |
| 100 | +## Testing Recommendations |
| 101 | + |
| 102 | +1. Test with maximum number of compressed accounts |
| 103 | +2. Verify stack usage with `solana-stack-check` tool |
| 104 | +3. Profile with different account types |
| 105 | +4. Test error paths to ensure stack safety in all cases |
0 commit comments