Thank you for considering a contribution to the Universal Memory Runtime. This guide covers everything you need to get from zero to a merged pull request.
- Getting started
- Repository layout
- Build and test
- Coding standards
- Submitting changes
- Issue templates
- Design decisions
# Fork, then clone
git clone https://github.com/your-fork/UMR.git
cd UMR
# Configure a debug build (recommended for development)
cmake -S . -B build-dev -DCMAKE_BUILD_TYPE=Debug \
-DUMR_ENABLE_DEBUG=ON \
-DUMR_BUILD_TESTS=ON \
-DUMR_BUILD_BENCHMARKS=ON \
-DUMR_BUILD_CLI=ON
cmake --build build-dev
# Run the full test suite
ctest --test-dir build-dev --output-on-failureAll tests must pass before you submit a PR.
core/ Runtime lifecycle, policy presets, TLS heap
allocator/ Engine implementations (arena, pool, slab, buddy, general)
fragment/ Fragment engine (split, merge, manager)
heap/ Global heap singleton, metadata, size classes
platform/ OS memory provider + mutex (linux/windows/macos)
profiler/ Tracker, leak detector, heap visualizer, snapshot
include/
umr.h Public API (stable — ABI freeze post v1)
umr/internal/ Private headers (no ABI guarantee)
tests/ Unit test files (one per module)
benchmarks/ Microbenchmarks
examples/ Getting-started programs (each self-contained)
tools/umr-cli/ In-process CLI tool
docs/ Markdown documentation
cmake/ CMake config + pkg-config templates
| Tool | Version |
|---|---|
| CMake | 3.20+ |
| C compiler | GCC 12+, Clang 15+, or MSVC 2022 |
| C standard | C11 minimum; C23 preferred |
Always develop against a debug build (-DUMR_ENABLE_DEBUG=ON). This enables
magic-value checks, abort-on-bad-free, and source location tracking in block
headers.
The test runner is a single binary that runs all suites in order. To focus on one suite, add a printf-grep:
./build-dev/umr_tests 2>&1 | grep -A 20 "\[fragment\]"cmake --build build-dev --target umr_bench_suite
./build-dev/umr_bench_suiteCompare output before and after your change. A regression of > 5 % on any benchmark in the suite needs a justification comment in the PR.
- Write C23 where possible; fall back to C11 when a feature is unavailable.
- No C++ (the project is a pure C library).
- No external dependencies (stdlib + OS syscalls only).
| Kind | Convention | Example |
|---|---|---|
| Public API functions | umr_verb_noun() |
umr_arena_create() |
| Internal functions | module_verb_noun() |
frag_block_merge() |
| Types | umr_noun_t |
umr_fragment_heap_t |
| Constants / macros | UMR_UPPER_SNAKE |
UMR_FRAG_MIN_BLOCK |
| Static file-local | k_ prefix for constants |
k_size_classes[] |
| Global singletons | g_ prefix |
g_tracker, g_heap |
Every .c file should open with a Doxygen file comment, then includes in
this order:
"umr.h"or the owning internal header- Other internal UMR headers
- Standard library headers
No #include of one .c from another. All cross-module communication goes
through the internal headers in include/umr/internal/.
- Functions that can fail return
int(0 = success, -1 = error) or a pointer (NULL = failure). Neverabort()in release builds except viaUMR_ASSERT. UMR_ASSERT(cond)is a debug-only abort. In release builds it compiles to((void)0).
- Never call
malloc/free/new/deleteinside the library. Useumr_os_alloc/umr_os_freefor control structures that must be independent of the tracked heap. - Zero newly allocated control structures with
memset.
- The global heap (
umr_heap_t) is protected byheap->lock. - Phase 2 engine control structs that are shared need their own mutex.
- Read the mutex discipline comment at the top of the file you are modifying.
Guard all diagnostic paths with #if defined(UMR_DEBUG) && UMR_DEBUG.
Release builds must compile cleanly with -O2 -DNDEBUG and no warnings.
subsystem: short imperative description (≤ 72 chars)
Optional longer explanation of why, not what.
Reference issues with: Fixes #N
Examples:
fragment: fix boundary-tag mismatch on zero-payload split
arena: add aligned allocation path for SIMD buffers
docs: update feature.md with Phase 4 strategy selection guide
Before opening a PR:
- All tests pass (
ctest --test-dir build-dev --output-on-failure) - Debug build compiles cleanly (
-DUMR_ENABLE_DEBUG=ON, no warnings) - Release build compiles cleanly (no warnings on GCC/Clang/MSVC)
- New functionality has tests in
tests/test_<module>.c - Public API additions are documented in
include/umr.h(Doxygen comment) -
docs/feature.mdupdated if a feature contract changed - Benchmark impact noted in PR description (run
bench_suitebefore/after) - No new external dependencies introduced
## Summary
One paragraph explaining what this changes and why.
## Changes
- `module/file.c`: what changed and why
- `include/umr.h`: new API surface (if any)
## Test coverage
Which test cases cover the new/changed behaviour.
## Benchmark impact
Paste bench_suite output before and after (or "no measurable impact").
## Notes
Any design trade-offs, known limitations, or follow-up items.**Describe the bug**
A clear description of what went wrong.
**To reproduce**
Minimal C code that triggers the bug.
**Expected behaviour**
What should have happened.
**Environment**
- OS and version:
- Compiler and version:
- UMR version / commit:
- Build type (Debug/Release, UMR_ENABLE_DEBUG):
**Additional context**
Stack trace, address sanitiser output, or core dump if available.**Problem**
What allocation problem or use case is not addressed today?
**Proposed solution**
Sketch of the API or behaviour you have in mind.
**Alternatives considered**
Other approaches and why they are less suitable.
**Phase**
Which phase does this fit into? (2 = specialized engines, 3 = observability,
4 = fragment, 5 = ecosystem, or new phase?)A few standing rules to keep the project coherent:
No heap inside the library. Control structures (tracker table, pool
chunks, arena chunks, fragment heap) are all allocated with umr_os_alloc
so they never recurse into the heap they manage.
Public API is stable after v1. include/umr.h is the ABI boundary.
Internal headers under include/umr/internal/ have no ABI guarantee and
can change between minor versions.
Phase boundaries are real. Each phase must be fully implemented and passing tests before the next begins. Do not mix Phase N+1 code into a Phase N branch.
Feature flags over forks. Observability (tracker, leak detector) is always compiled in but zero-cost when disabled. Do not create separate "debug" and "release" builds of core algorithms.
Portability is not optional. Every change must compile and pass tests
on Linux (x86-64), Windows (x86-64), and macOS (ARM64). Platform-specific
code lives only in platform/.