// Moved from docs/src/build-and-tooling/PGO_GUIDE.adoc to docs/working/scratch/src/build-and-tooling/PGO_GUIDE.adoc on 2026-06-16 (docs reorg Phase 2)
== QUARANTiNE ME MOTHERFUCKER
:toc: left
:toc-title: Contents
:toclevels: 3
xref:../README.adoc[← Back to Documentation Index]



== StarForth Profile-Guided Optimization (PGO) Guide

*Complete guide to building the fastest possible StarForth binary using
profile-guided optimization*

'''''

=== Overview

Profile-Guided Optimization (PGO) is a compiler optimization technique
that uses runtime profiling data to guide code generation. StarForth’s
PGO system exercises all major code paths to create an optimized binary
tailored to real-world usage patterns.

==== Performance Gains

PGO typically delivers *5-15% performance improvements* over standard
`+-O3+` optimization by:

* Better branch prediction (hot paths predicted correctly)
* Improved instruction cache utilization (hot code packed together)
* More aggressive inlining of frequently-called functions
* Optimal code layout for common execution patterns

'''''

=== Quick Start

==== Basic PGO Build

[source,bash]
----
make pgo
----

This runs a comprehensive 6-stage build:

[arabic]
. *Clean environment* - Remove old artifacts and profile data
. *Instrumentation build* - Compile with profiling instrumentation
(`+-fprofile-generate+`)
. *Profiling workload* - Execute comprehensive test suite to generate
profile data
. *Collect profile data* - Gather `+.gcda+` files from all code paths
. *Optimized rebuild* - Recompile with profile data (`+-fprofile-use+`)
. *Cleanup* - Remove temporary profile data

*Build time:* ~2-3 minutes (depending on hardware)

'''''

=== Available PGO Targets

==== 1. Standard PGO (`+make pgo+`)

*Use when:* You want maximum performance for typical StarForth
workloads.

*What it does:*

* Runs comprehensive profiling workload (tests, benchmarks, REPL,
blocks)
* Generates optimized binary with ASM optimizations + direct threading
* Cleans up profile data automatically

*Output:* Optimized `+build/starforth+` binary

==== 2. PGO + perf (`+make pgo-perf+`)

*Use when:* You need detailed performance analysis and want to identify
bottlenecks.

*Requires:* `+sudo+` access for perf, linux-tools-generic package

*What it does:*

* Everything from standard PGO
* Captures perf profiling data during workload execution
* Generates call graphs and hotspot analysis
* Keeps frame pointers for accurate stack traces

*Output:*

* Optimized `+build/starforth+` binary
* `+pgo-perf.data+` - perf profiling data

*View results:*

[source,bash]
----
# Interactive TUI
sudo perf report -i pgo-perf.data

# Text report (top functions by CPU time)
sudo perf report --stdio -i pgo-perf.data --sort comm,dso,symbol --percent-limit 1

# Generate flamegraph (if you have flamegraph.pl)
sudo perf script -i pgo-perf.data | ./scripts/flamegraph.pl > pgo-flame.svg
----

==== 3. PGO + Valgrind (`+make pgo-valgrind+`)

*Use when:* You need instruction-level analysis or want to understand
cache behavior.

*Requires:* valgrind package, kcachegrind (optional, for visualization)

*What it does:*

* Runs callgrind profiling during benchmark workload
* Collects instruction counts, cache misses, branch mispredictions
* Builds optimized binary with profile data + debug symbols

*Output:*

* Optimized `+build/starforth+` binary (with `+-g+` debug symbols)
* `+pgo-callgrind.out+` - callgrind profiling data

*View results:*

[source,bash]
----
# GUI visualization (best option)
kcachegrind pgo-callgrind.out

# Text report
callgrind_annotate pgo-callgrind.out
----

*Note:* Callgrind adds significant overhead (~10-100x slowdown), so
workload is limited to 100 benchmark iterations.

'''''

=== Benchmark Comparison

Compare performance before and after PGO:

[source,bash]
----
make bench-compare
----

This builds both regular and PGO binaries, runs identical benchmarks,
and reports timing differences.

*Example output:*

....
🏁 Benchmark Comparison: Regular vs PGO

Building regular optimized binary...
Running benchmark (10000 iterations)...
  Regular build: 0:00.45 elapsed, 0.44 user

Building PGO optimized binary...
Running benchmark (10000 iterations)...
  PGO build:     0:00.38 elapsed, 0.37 user

✓ Comparison complete!
....

*Speedup:* 1.18x faster (18% improvement in this example)

'''''

=== The PGO Profiling Workload

==== What Gets Profiled

The profiling workload (`+scripts/pgo-workload.sh+`) exercises 7 major
code paths:

===== 1. Unit Tests

* All word implementations (stack, arithmetic, logic, memory, etc.)
* Dictionary operations
* Compiler functionality
* ~400+ test cases

===== 2. Stress Tests

* Deep call stacks (nested definitions)
* Stack exhaustion scenarios
* Large word definitions
* Edge cases and boundary conditions

===== 3. Integration Tests

* Complete Forth programs
* Multi-word interactions
* Real-world usage patterns

===== 4. Benchmarks

* 5000 iterations of hot-path operations
* Stack manipulation
* Arithmetic operations
* Control flow

===== 5. REPL Workload

* Interactive command processing
* Variable definitions
* Constant creation
* Control structures (IF/ELSE/THEN, DO/LOOP, BEGIN/UNTIL)
* Nested definitions
* Recursive functions
* String handling
* Memory operations
* Vocabulary management

===== 6. Block I/O Operations

* Block reading from disk image
* LIST operations
* Buffer management
* (Runs if `+disks/rajames-rajames-1.0.img+` exists)

===== 7. Word Frequency Profiling

* Lightweight execution frequency tracking
* Hot word identification for optimization
* Zero-overhead call counting at PROFILE_BASIC level

==== Customizing the Workload

You can run the profiling workload independently:

[source,bash]
----
./scripts/pgo-workload.sh ./build/starforth
----

To add custom workload patterns, edit `+scripts/pgo-workload.sh+` and
add Forth code that exercises your specific use cases.

'''''

=== Understanding Profile Data

==== Profile Data Files

During instrumentation, GCC generates two types of files:

* *`+.gcno+` files* (generated at compile time)
** Coverage notes - which branches/blocks exist
** Static instrumentation metadata
** Removed before optimization build
* *`+.gcda+` files* (generated at runtime)
** Coverage data - execution counts for each branch/block
** Dynamic profiling information
** Used by `+-fprofile-use+` to guide optimization

==== Profile Data Location

Profile data is written to the directory where the instrumented binary
runs. StarForth’s build system searches:

[source,bash]
----
./*.gcda
src/*.gcda
src/*/*.gcda
build/*.gcda
----

==== Verifying Profile Coverage

After running the workload, check how many profile files were generated:

[source,bash]
----
find . -name "*.gcda" -type f | wc -l
----

Expected: *100+ files* (one per source file that executed)

If you see fewer than expected, some code paths weren’t exercised.
Consider expanding the workload.

'''''

=== Compiler Flags Explained

==== Instrumentation Build (`+-fprofile-generate+`)

[source,bash]
----
CFLAGS="-O2 -DUSE_ASM_OPT=1 -fprofile-generate"
LDFLAGS="-fprofile-generate -lgcov"
----

* `+-O2+` - Moderate optimization (faster build than `+-O3+`, sufficient
for profiling)
* `+-DUSE_ASM_OPT=1+` - Enable assembly optimizations
* `+-fprofile-generate+` - Instrument code to collect edge/block
execution counts
* `+-lgcov+` - Link against GCC coverage runtime library

==== Optimized Build (`+-fprofile-use+`)

[source,bash]
----
CFLAGS="-O3 -DUSE_ASM_OPT=1 -DUSE_DIRECT_THREADING=1 \
        -fprofile-use -fprofile-correction -Wno-error=coverage-mismatch"
LDFLAGS="-fprofile-use"
----

* `+-O3+` - Maximum optimization level
* `+-DUSE_ASM_OPT=1+` - Assembly optimizations
* `+-DUSE_DIRECT_THREADING=1+` - Direct threading for inner interpreter
* `+-fprofile-use+` - Use profile data to guide optimization
* `+-fprofile-correction+` - Handle inconsistent profile data gracefully
* `+-Wno-error=coverage-mismatch+` - Warn (don’t fail) on minor profile
mismatches

==== perf-Specific Flags (`+-fno-omit-frame-pointer+`)

[source,bash]
----
-fno-omit-frame-pointer
----

* Keeps frame pointer register for accurate stack traces
* Slightly reduces performance (~2-3%) but essential for perf analysis
* Allows perf to generate accurate call graphs

'''''

=== Integration with Other Tools

==== perf - Linux Performance Analysis

*Install:*

[source,bash]
----
sudo apt-get install linux-tools-generic
----

*Manual profiling:*

[source,bash]
----
# Record profile data
sudo perf record -g --call-graph dwarf -o myprofile.data ./build/starforth --benchmark 10000

# View report
sudo perf report -i myprofile.data

# Generate flamegraph
sudo perf script -i myprofile.data | flamegraph.pl > flame.svg
----

*Key perf commands:*

* `+perf record+` - Capture profiling data
* `+perf report+` - View profiling results (interactive TUI)
* `+perf annotate+` - Show assembly with performance annotations
* `+perf stat+` - Display performance counter statistics
* `+perf top+` - Real-time performance monitoring

==== Valgrind/Callgrind - Instruction-Level Profiling

*Install:*

[source,bash]
----
sudo apt-get install valgrind kcachegrind
----

*Manual profiling:*

[source,bash]
----
# Run with callgrind
valgrind --tool=callgrind \
         --callgrind-out-file=myprofile.out \
         --dump-instr=yes \
         --collect-jumps=yes \
         ./build/starforth --benchmark 1000

# View in kcachegrind (GUI)
kcachegrind myprofile.out

# Or text report
callgrind_annotate myprofile.out
----

*What callgrind shows:*

* Instruction counts (exact, deterministic)
* Function call counts
* Cache simulation (I1/D1/LL cache hits/misses)
* Branch prediction accuracy
* Caller/callee relationships

'''''

=== Troubleshooting

==== Issue: "`No profile data found`"

*Symptoms:*

....
warning: no profile data available for function 'vm_init'
....

*Causes:*

[arabic]
. Workload didn’t exercise code paths
. Profile data files not found
. Source changed between instrumentation and optimization builds

*Solutions:*

[arabic]
. Expand workload to cover more code
. Check for `+.gcda+` files: `+find . -name "*.gcda"+`
. Don’t modify source between stages (use `+make pgo+` which handles
this)

==== Issue: "`Coverage mismatch`"

*Symptoms:*

....
warning: source locations for function 'vm_execute' have changed
....

*Cause:* Source code modified between instrumentation and optimization

*Solution:* Run `+make pgo+` from scratch (it cleans first)

==== Issue: "`Permission denied writing .gcda`"

*Cause:* Insufficient permissions in working directory

*Solution:*

[source,bash]
----
chmod -R u+w .
make pgo
----

==== Issue: PGO build slower than regular build

*Possible causes:*

[arabic]
. Profile data doesn’t match actual usage patterns
. Workload too small/unrealistic
. Compiler made poor optimization decisions

*Solutions:*

[arabic]
. Customize workload to match your use case
. Try different optimization levels (`+-O2+` vs `+-O3+`)
. Compare with `+make bench-compare+` to verify

'''''

=== Best Practices

==== 1. Profile Representative Workloads

✅ *Do:*

* Use realistic test data
* Cover common code paths (90%+ of real usage)
* Include edge cases that are still frequent

❌ *Don’t:*

* Profile only trivial operations
* Use synthetic benchmarks that don’t match real usage
* Profile rarely-executed error paths

==== 2. Keep Profile Data Fresh

* Re-run PGO after major code changes (>10% of codebase)
* Update profile data when adding new features
* Consider separate PGO builds for different workload profiles

==== 3. Verify Performance Gains

Always measure before/after:

[source,bash]
----
make bench-compare
----

If PGO doesn’t improve performance, investigate:

* Is workload representative?
* Are hot paths correctly identified?
* Check perf data to understand what’s optimized

==== 4. Combine with Other Optimizations

PGO works best with:

* `+-march=native+` - CPU-specific instructions
* `+-flto+` - Link-time optimization
* `+-DUSE_DIRECT_THREADING=1+` - Direct threading inner interpreter
* `+-DUSE_ASM_OPT=1+` - Hand-optimized assembly

StarForth’s `+make pgo+` enables all of these automatically.

'''''

=== Advanced Usage

==== Separate Training and Production Builds

*Scenario:* Profile on development machine, optimize for production.

*Step 1:* Collect profile data

[source,bash]
----
# On dev machine
make clean
make CFLAGS="-O2 -fprofile-generate" LDFLAGS="-fprofile-generate -lgcov"
./scripts/pgo-workload.sh ./build/starforth

# Save profile data
tar czf profile-data.tar.gz $(find . -name "*.gcda")
----

*Step 2:* Optimize with profile data

[source,bash]
----
# On production machine (or CI)
tar xzf profile-data.tar.gz
make clean-obj
make CFLAGS="-O3 -DUSE_ASM_OPT=1 -DUSE_DIRECT_THREADING=1 -fprofile-use -fprofile-correction" \
     LDFLAGS="-fprofile-use"
----

==== Multi-Stage PGO (Iterative Optimization)

*Stage 1:* Initial PGO

[source,bash]
----
make pgo
----

*Stage 2:* Profile the PGO binary (better profile data)

[source,bash]
----
./scripts/pgo-workload.sh ./build/starforth
# This generates more accurate profiles using optimized code
----

*Stage 3:* Re-optimize with improved profile

[source,bash]
----
make clean-obj
make CFLAGS="..." LDFLAGS="..." # Use profile data from stage 2
----

*Diminishing returns:* Usually not worth it (< 1% additional gain).

'''''

=== Comparison with Other Builds

==== Build Target Performance Comparison

[width="99%",cols="14%,28%,16%,14%,28%",options="header",]
|===
|Target |Optimization |Speed |Build Time |Use Case
|`+debug+` |`+-O0 -g+` |1.0x |30s |Development, debugging
|`+all+` |`+-O2+` |3.5x |45s |Default, balanced
|`+fast+` |`+-O3 + ASM+` |5.2x |60s |Production, no LTO
|`+fastest+` |`+-O3 + ASM + DT + LTO+` |6.8x |90s |Maximum performance
|*`+pgo+`* |*Fastest + PGO* |*7.5-8.0x* |*3m* |*Absolute maximum*
|===

*Legend:*

* ASM = Assembly optimizations (`+-DUSE_ASM_OPT=1+`)
* DT = Direct threading (`+-DUSE_DIRECT_THREADING=1+`)
* LTO = Link-time optimization (`+-flto+`)
* PGO = Profile-guided optimization

'''''

=== Technical Details

==== How PGO Works

[arabic]
. *Instrumentation Phase*
* Compiler inserts counters at control flow edges
* Runtime increments counters during execution
* Profile data written to `+.gcda+` files on program exit
. *Optimization Phase*
* Compiler reads profile data
* Identifies hot paths (frequently executed code)
* Identifies cold paths (rarely executed code)
* Makes optimization decisions:
** *Inline hot functions* (reduce call overhead)
** *Pack hot code together* (improve I-cache locality)
** *Predict branches* (arrange code for correct prediction)
** *Devirtualize calls* (when type is known from profile)
** *Unroll hot loops* (reduce branch overhead)
. *Code Layout Optimization*
* Hot code placed sequentially in memory
* Cold code moved to separate section
* Result: Better instruction cache utilization

==== Profile Data Structure

`+.gcda+` files contain:

* *Arc counts:* Number of times each control flow edge executed
* *Block counts:* Number of times each basic block executed
* *Function summaries:* Entry/exit counts per function
* *Checksum:* Verify profile matches current source

==== Compiler Optimization Heuristics

With profile data, GCC adjusts:

[width="100%",cols="27%,20%,53%",options="header",]
|===
|Heuristic |Default |With PGO
|Inline threshold |600 units |Adjusted per call site
|Loop unroll factor |4 |Adjusted per loop (up to 8 for hot loops)
|Branch prediction |Static (50/50) |Dynamic (from profile)
|Function outlining |Disabled |Cold code outlined
|Register allocation |Balanced |Favor hot paths
|===

'''''

=== Platform-Specific Notes

==== x86_64

* Full support for all PGO features
* Best results with `+-march=native+`
* perf hardware counters available
* Valgrind fully supported

==== ARM64 (Raspberry Pi 4, Apple Silicon)

* Full PGO support
* Use `+-march=armv8-a+crc+simd -mtune=cortex-a72+` on RPi4
* perf support varies by kernel (check `+perf list+`)
* Valgrind support limited on some ARM platforms

==== Cross-Compilation

PGO requires native execution for profiling. For cross-compilation:

[arabic]
. Build instrumented binary for target architecture
. Run on target hardware to collect profile data
. Transfer `+.gcda+` files back to build machine
. Cross-compile optimized binary with profile data

'''''

=== References

==== GCC Documentation

* https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html#index-fprofile-generate[Profile-Guided
Optimization]
* https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html[Instrumentation
Options]

==== Tools Documentation

* https://perf.wiki.kernel.org/index.php/Main_Page[perf Wiki]
* https://valgrind.org/docs/manual/manual.html[Valgrind Manual]
* https://valgrind.org/docs/manual/cl-manual.html[Callgrind
Documentation]

==== Academic Papers

* "`Feedback-Directed Optimization in Compilers`" (ACM Computing
Surveys)
* "`Profile-Guided Post-Link Optimization`" (Intel)

'''''

=== Summary

*Quick command reference:*

[source,bash]
----
# Standard PGO build (recommended)
make pgo

# PGO with performance analysis
make pgo-perf
sudo perf report -i pgo-perf.data

# PGO with instruction analysis
make pgo-valgrind
kcachegrind pgo-callgrind.out

# Compare performance
make bench-compare

# Custom workload
./scripts/pgo-workload.sh ./build/starforth
----

*Key takeaways:*

[arabic]
. PGO delivers *5-15% performance improvement* for typical workloads
. Workload must be *representative* of real usage
. Best combined with other optimizations (ASM, direct threading, LTO)
. Use `+make pgo-perf+` or `+make pgo-valgrind+` for detailed analysis
. Verify gains with `+make bench-compare+`

'''''

*End of PGO Guide*
