Skip to content

Custom ponyc Builds for Debugging

ponyc supports several build-time options that instrument the compiler and runtime for debugging. These require building ponyc from source with specific options. All commands here are for the Unix build.

Build Workflow

Every option follows the same pattern:

  1. Clone ponyc and build its dependencies (one-time):

    git clone https://github.com/ponylang/ponyc.git
    cd ponyc
    cmake -P lib/build-libs.cmake
    
  2. Configure with the desired option:

    cmake --preset release -DPONY_USES=<option>
    
  3. Build:

    cmake --build --preset release
    

The resulting ponyc binary is in build/release/ — or, when you enable options with PONY_USES, a suffixed directory like build/release-address_sanitizer that lists the enabled options. The suffix always lists them in a fixed order set by the build, independent of the order you pass them, so the directory name may not match what you typed. Use it in place of your system ponyc to compile programs with the instrumentation enabled.

To combine multiple options, separate them with commas:

cmake --preset release -DPONY_USES=address_sanitizer,undefined_behavior_sanitizer

Not all combinations are valid — see the individual sections below for compatibility notes.

For full source build instructions (including platform-specific prerequisites), see Building ponyc from Source.

Valgrind

Annotates the Pony runtime so Valgrind tools like Memcheck and Helgrind can understand Pony’s custom memory allocator.

Valgrind development headers must be installed before building. On Debian/Ubuntu:

apt-get install valgrind

Build ponyc with Valgrind support:

cmake --preset release -DPONY_USES=valgrind
cmake --build --preset release

Compile your program with the instrumented ponyc, then run it under Valgrind:

valgrind ./my-program

Not available on OpenBSD

Valgrind has no OpenBSD port, so its development headers aren’t available and valgrind can’t be built there. Configuring with -DPONY_USES=valgrind is rejected on OpenBSD with an error.

Doesn’t run on DragonFly BSD

valgrind builds on DragonFly and the Pony programs it compiles link, but DragonFly ships Valgrind 3.15, which is too old to run a Pony program; it hangs on the runtime’s memory arena. See ponyc#5435.

Address Sanitizer

AddressSanitizer (ASan) detects memory errors at runtime: buffer overflows, use-after-free, double-free, and stack buffer overflows.

cmake --preset release -DPONY_USES=address_sanitizer
cmake --build --preset release

Compile your program with the instrumented ponyc and run it normally. ASan reports errors to stderr as they occur.

Cannot be combined with thread_sanitizer.

To catch errors in the Pony runtime’s own allocations, pair it with pool_memalign, which routes every runtime allocation through posix_memalign/free so ASan can surround each one with redzones:

cmake --preset release -DPONY_USES=pool_memalign,address_sanitizer
cmake --build --preset release

The instrumented ponyc isn’t LeakSanitizer-clean, so running it — during its own build, and whenever you use it to compile a program — needs ASAN_OPTIONS set in the environment. Without it the compiler reports its own leaks at exit, and on libc++ platforms aborts on false container overflows:

  • Linux: ASAN_OPTIONS=detect_leaks=0
  • FreeBSD and macOS: ASAN_OPTIONS=detect_leaks=0:detect_container_overflow=0 — the extra flag suppresses false positives from libc++ container annotations when the instrumented compiler shares std::vector/std::string with the non-instrumented vendored LLVM.

A pool_memalign,address_sanitizer build’s ponyc lands in build/release-address_sanitizer-pool_memalign, not build/release. For example, to compile a program on Linux:

ASAN_OPTIONS=detect_leaks=0 ./build/release-address_sanitizer-pool_memalign/ponyc path/to/your/program

Both options concern ponyc itself, not the programs it compiles. A correct Pony program is LeakSanitizer-clean at exit, so run a program you compiled with LeakSanitizer left on — it will report genuine leaks in your own code, such as memory you allocate through FFI and never free.

Not available on OpenBSD or DragonFly BSD

OpenBSD’s base toolchain ships no AddressSanitizer runtime, and DragonFly BSD’s gcc13 toolchain doesn’t build one either, so address_sanitizer can’t be built on either. Configuring with -DPONY_USES=address_sanitizer is rejected on both OpenBSD and DragonFly with an error.

Thread Sanitizer

ThreadSanitizer (TSan) detects data races at runtime.

cmake --preset release -DPONY_USES=thread_sanitizer
cmake --build --preset release

Compile your program with the instrumented ponyc and run it normally. TSan reports data races to stderr.

Cannot be combined with address_sanitizer.

Not available on OpenBSD or DragonFly BSD

OpenBSD’s base toolchain ships no ThreadSanitizer runtime, and DragonFly BSD’s gcc13 toolchain doesn’t build one either, so thread_sanitizer can’t be built on either. Configuring with -DPONY_USES=thread_sanitizer is rejected on both OpenBSD and DragonFly with an error.

Undefined Behavior Sanitizer

UndefinedBehaviorSanitizer (UBSan) detects undefined behavior at runtime: signed integer overflow, null pointer dereference, misaligned memory access, and other categories.

cmake --preset release -DPONY_USES=undefined_behavior_sanitizer
cmake --build --preset release

Compile your program with the instrumented ponyc and run it normally. UBSan reports violations to stderr.

Can be combined with address_sanitizer or thread_sanitizer.

Not available on OpenBSD or DragonFly BSD

OpenBSD ships only the minimal UndefinedBehaviorSanitizer runtime, not the standalone runtime ponyc links against; DragonFly BSD’s gcc13 toolchain doesn’t build the UBSan runtime at all. Either way, undefined_behavior_sanitizer can’t be built there. Configuring with -DPONY_USES=undefined_behavior_sanitizer is rejected on both OpenBSD and DragonFly with an error.

Coverage

Instruments ponyc and the Pony runtime for source-level code coverage. It’s built for working on ponyc itself: run a coverage-instrumented ponyc and its test suite to see which parts of the compiler and runtime C code each run exercises. To measure coverage of your own Pony code instead, you don’t need a custom build — see Coverage Reports.

cmake --preset release -DPONY_USES=coverage
cmake --build --preset release

The instrumentation comes from the host toolchain’s coverage support — -fprofile-instr-generate -fcoverage-mapping under Clang, -fprofile-arcs -ftest-coverage under GCC. A Clang build writes an LLVM profile (default.profraw) that you turn into a report with llvm-profdata and llvm-cov; a GCC build writes .gcda files for gcov or lcov. These report tools aren’t part of the ponyc build — install them separately. For the full workflow, see Clang’s source-based coverage guide or the gcov documentation.

Not available on OpenBSD

OpenBSD’s base toolchain ships an incomplete profiling runtime, so a coverage-instrumented ponyc can’t be linked there. Configuring with -DPONY_USES=coverage is rejected on OpenBSD with an error.

DTrace / SystemTap

The Pony runtime includes USDT (Userland Statically Defined Tracing) probes. They’re consumed by SystemTap on Linux and by DTrace on FreeBSD. No other platform is supported: macOS removed DTrace, and neither DragonFly BSD nor OpenBSD ships a DTrace-compatible probe-generation tool (OpenBSD’s btrace is a separate tracer with no USDT support). On DragonFly BSD and OpenBSD, configuring with -DPONY_USES=dtrace is rejected with an error rather than failing later in the build.

Building requires a dtrace-compatible tool on your PATH:

cmake --preset release -DPONY_USES=dtrace
cmake --build --preset release

The probes are defined under the pony provider and cover:

  • Actor lifecycle — creation, scheduling, descheduling
  • Message passing — actor-to-actor and thread message send/receive/push/pop
  • Backpressure — overload, mute/unmute, pressure state changes
  • Garbage collection — GC start/end, send/receive phases, threshold changes
  • Memory — heap allocation
  • Runtime lifecycle — init, start, end
  • Work stealing — steal success/failure
  • Thread state — suspend, resume, nanosleep

For the full list of probes with parameter types and descriptions, see src/common/dtrace_probes.d in the ponyc source.

Static linking on FreeBSD

Statically linked programs (ponyc --static) build and run but do not expose their probes to DTrace. FreeBSD registers USDT probes through the runtime linker, which a static binary doesn’t use; trace a dynamically linked build instead.

Runtime bitcode is incompatible with DTrace

A ponyc built with dtrace cannot compile programs with --runtimebc, and rejects the combination with an error. Probe generation operates on native object files, not LLVM bitcode, so the bitcode runtime carries no probes — a --runtimebc binary would have none. Use the default static runtime to trace your program.

Runtime Statistics

Runtime statistics tracking instruments the Pony runtime to report memory usage per actor and per scheduler thread. This is useful for understanding where memory is going in a running program.

Two options are available:

  • runtimestats — enables basic runtime statistics
  • runtimestats_messages — adds per-message tracking on top of runtimestats

For most debugging scenarios, enable both:

cmake --preset release -DPONY_USES=runtimestats,runtimestats_messages
cmake --build --preset release

For details on the available tracking functions and how to call them from Pony code, see Tracking Memory Usage at Runtime.

Runtime Tracing

Runtime tracing records events from the Pony scheduler, actor lifecycle, and message passing. Events can be written to a trace file in the background, or stored in in-memory circular buffers that dump to stderr when the program crashes — a fatal signal such as SIGSEGV or SIGILL on Linux and macOS, or a fault such as an access violation on Windows. Trace files use Chromium JSON format and can be viewed with Perfetto.

cmake --preset release -DPONY_USES=runtime_tracing
cmake --build --preset release

For details on tracing options and usage, see Tracing Pony Programs.

Systematic Testing

Systematic testing replaces the Pony scheduler with a deterministic, single-threaded scheduler that explores different actor interleaving orders. It’s a tool for working on the runtime itself: when a runtime change has a bug that only shows up under some scheduler interleaving, systematic testing lets you replay that interleaving from a seed.

A program run under systematic testing has to be deterministic, and keeping it that way is on you. The runtime removes its own source of nondeterminism: it turns off the ASIO thread entirely, the thread that does asynchronous I/O whose timing a seed can’t control. So any asynchronous I/O — a socket, standard input, a spawned process, a signal handler, a timer — aborts the program. Ordinary output and synchronous file I/O still work. But that’s only the runtime’s nondeterminism. Your own code can still make a run you can’t replay — reading the wall clock, say — and unlike I/O, that won’t abort.

Systematic testing requires two options together:

cmake --preset release -DPONY_USES=scheduler_scaling_pthreads,systematic_testing
cmake --build --preset release

The scheduler_scaling_pthreads option is required because systematic testing needs pthread-based scheduler scaling rather than the default implementation.

Seed-Based Replay

When a systematic testing build runs, it uses a random seed to determine the interleaving order. The runtime prints the seed and a rerun command to stderr at the start of every run. When a run triggers a bug, replay that seed to reproduce the exact same interleaving:

./my-program --ponysystematictestingseed 12345

A typical workflow:

  1. Run the program repeatedly with different seeds until a bug manifests.
  2. Note the seed from the failing run.
  3. Replay with --ponysystematictestingseed to reproduce the bug deterministically.
  4. Fix the bug and verify with the same seed that the fix holds.