Introspection Tools

RepliBuild includes a comprehensive introspection toolkit located in RepliBuild.Introspect. This module provides a unified interface for analyzing every stage of the compilation pipeline—from binary artifacts and DWARF debug info to Julia's lowered code and LLVM IR.

Binary Analysis

These tools allow you to inspect the compiled C++ artifacts directly.

RepliBuild.Introspect.symbolsFunction
symbols(binary_path::String; filter=:all, demangled=true)

Extract symbols from a binary using nm.

Wraps the existing Compiler.extract_symbols_from_binary() and returns structured SymbolInfo objects.

Arguments

  • binary_path::String - Path to binary file
  • filter::Symbol - Filter symbols (:all, :functions, :data, :weak)
  • demangled::Bool - Return demangled names (default: true)

Returns

Vector{SymbolInfo}

Examples

# Get all function symbols
syms = symbols("lib", filter=:functions)

# Get all symbols with mangling
syms = symbols("lib", demangled=false)

# Export to CSV
export_csv(syms, "symbols.csv")
source
RepliBuild.Introspect.dwarf_infoFunction
dwarf_info(binary_path::String)

Extract complete DWARF debug information from a binary.

Wraps the existing Compiler.extract_dwarf_return_types() and returns a structured DWARFInfo object.

Arguments

  • binary_path::String - Path to binary file

Returns

DWARFInfo

Examples

# Extract DWARF info
dwarf = dwarf_info("lib")

# Access structs
matrix = dwarf.structs["Matrix3x3"]
println("Size: $(matrix.size) bytes")

# Access functions
func = dwarf.functions["compute"]
println(func.return_type)

# Export as dataset
export_dataset(dwarf, "training_data/")
source
RepliBuild.Introspect.dwarf_dumpFunction
dwarf_dump(binary_path::String; section=:info)

Dump DWARF debug information using llvm-dwarfdump.

Arguments

  • binary_path::String - Path to binary file
  • section::Symbol - DWARF section (:info, :types, :line, :frame, default: :info)

Returns

String - Raw DWARF dump output

Examples

# Dump DWARF info section
info = dwarf_dump(lib, section=:info)

# Dump type information
types = dwarf_dump(lib, section=:types)

# Save to file
open("dwarf.txt", "w") do io
    write(io, info)
end
source
RepliBuild.Introspect.disassembleFunction
disassemble(binary_path::String, symbol=nothing; syntax=:att)

Disassemble binary or specific symbol using llvm-objdump.

Arguments

  • binary_path::String - Path to binary file
  • symbol - Optional symbol name to disassemble (default: entire binary)
  • syntax::Symbol - Assembly syntax (:att or :intel, default: :att)

Returns

String - Disassembled code

Examples

# Disassemble entire binary
asm = disassemble("lib")

# Disassemble specific function
asm = disassemble("lib", "compute_fft", syntax=:intel)

# Save to file
open("disasm.s", "w") do io
    write(io, asm)
end
source
RepliBuild.Introspect.headersFunction
headers(binary_path::String)

Extract binary headers and sections using readelf/llvm-readelf.

Arguments

  • binary_path::String - Path to binary file

Returns

HeaderInfo

Examples

# Get header info
header = headers("lib")
println("Architecture: $(header.architecture)")
println("Sections: $(length(header.sections))")
source

Julia Introspection

Analyze how Julia compiles your wrapper code. These functions wrap standard Julia introspection tools but provide more structured output suitable for analysis.

RepliBuild.Introspect.code_loweredFunction
code_lowered(func, types::Tuple)

Get lowered Julia IR before type inference.

Wraps @code_lowered macro and returns structured CodeLoweredInfo.

Arguments

  • func - Function to inspect
  • types::Tuple - Argument types tuple

Returns

CodeLoweredInfo

Examples

# Inspect lowered code
info = code_lowered(sort, (Vector{Int},))
println("Slots: $(info.slot_names)")
println("Instructions: $(length(info.code))")
source
RepliBuild.Introspect.code_typedFunction
code_typed(func, types::Tuple; optimized=true)

Get type-inferred Julia IR.

Wraps @code_typed macro and returns structured CodeTypedInfo.

Arguments

  • func - Function to inspect
  • types::Tuple - Argument types tuple
  • optimized::Bool - Apply optimizations (default: true)

Returns

CodeTypedInfo

Examples

# Get typed IR
info = code_typed(sum, (Vector{Float64},), optimized=true)
println("Return type: $(info.return_type)")
source
RepliBuild.Introspect.code_llvmFunction
code_llvm(func, types::Tuple; optimized=true, raw=false, debuginfo=:none)

Get LLVM IR for Julia function.

Wraps @code_llvm macro and returns structured LLVMIRInfo.

Arguments

  • func - Function to inspect
  • types::Tuple - Argument types tuple
  • optimized::Bool - Apply LLVM optimizations (default: true)
  • raw::Bool - Show raw unoptimized IR (default: false)
  • debuginfo::Symbol - Debug info level (:none, :source, :default)

Returns

LLVMIRInfo

Examples

# Get optimized LLVM IR
info = code_llvm(sort, (Vector{Int},), optimized=true)
println(info.ir)

# Get unoptimized IR
info = code_llvm(sort, (Vector{Int},), optimized=false)
source
RepliBuild.Introspect.code_nativeFunction
code_native(func, types::Tuple; syntax=:att, debuginfo=:none)

Get native assembly for Julia function.

Wraps @code_native macro and returns structured AssemblyInfo.

Arguments

  • func - Function to inspect
  • types::Tuple - Argument types tuple
  • syntax::Symbol - Assembly syntax (:att or :intel, default: :att)
  • debuginfo::Symbol - Debug info level (:none, :source, :default)

Returns

AssemblyInfo

Examples

# Get AT&T syntax assembly
info = code_native(sum, (Vector{Float64},))
println(info.assembly)

# Get Intel syntax assembly
info = code_native(sum, (Vector{Float64},), syntax=:intel)
source
RepliBuild.Introspect.code_warntypeFunction
code_warntype(func, types::Tuple)

Get type inference warnings (wrapper for @code_warntype).

Arguments

  • func - Function to inspect
  • types::Tuple - Argument types tuple

Returns

String - Warning output

Examples

# Check for type instabilities
warnings = code_warntype(my_func, (Int, Vector))
source
RepliBuild.Introspect.analyze_type_stabilityFunction
analyze_type_stability(func, types::Tuple)

Analyze type stability using @code_warntype.

Parses @code_warntype output to identify type instabilities.

Arguments

  • func - Function to analyze
  • types::Tuple - Argument types tuple

Returns

TypeStabilityAnalysis

Examples

# Check type stability
analysis = analyze_type_stability(my_func, (Int, Vector{Float64}))
if !analysis.is_stable
    println("Type unstable!")
    for var in analysis.unstable_variables
        println("  $(var.variable): $(var.inferred_type)")
    end
end
source
RepliBuild.Introspect.analyze_simdFunction
analyze_simd(func, types::Tuple)

Analyze SIMD vectorization by parsing LLVM IR.

Searches for vector instructions and identifies vectorized loops.

Arguments

  • func - Function to analyze
  • types::Tuple - Argument types tuple

Returns

SIMDAnalysis

Examples

# Check SIMD vectorization
analysis = analyze_simd(my_loop_func, (Vector{Float64},))
println("Vectorized loops: $(length(analysis.vectorized_loops))")
println("Vector instructions: $(analysis.vector_instructions)")
source
RepliBuild.Introspect.analyze_allocationsFunction
analyze_allocations(func, types::Tuple)

Analyze memory allocations by parsing LLVM IR.

Searches for Julia allocation function calls in LLVM IR.

Arguments

  • func - Function to analyze
  • types::Tuple - Argument types tuple

Returns

AllocationAnalysis

Examples

# Check allocations
analysis = analyze_allocations(my_func, (Vector{Float64},))
println("Total allocations: $(length(analysis.allocations))")
println("Total bytes: $(analysis.total_bytes)")
source
RepliBuild.Introspect.analyze_inliningFunction
analyze_inlining(func, types::Tuple)

Analyze inlining by comparing unoptimized vs optimized typed IR.

Arguments

  • func - Function to analyze
  • types::Tuple - Argument types tuple

Returns

Dict with :unoptimized and :optimized CodeTypedInfo

Examples

# Check inlining
analysis = analyze_inlining(my_func, (Int, Float64))
unopt = analysis[:unoptimized]
opt = analysis[:optimized]
println("Inlining reduced code by $(unopt.code.code.codelocs - opt.code.code.codelocs) locations")
source
RepliBuild.Introspect.compilation_pipelineFunction
compilation_pipeline(func, types::Tuple)

Run complete compilation pipeline: lowered → typed → llvm → native.

Returns all intermediate representations in a single result.

Arguments

  • func - Function to analyze
  • types::Tuple - Argument types tuple

Returns

CompilationPipelineResult

Examples

# Get full pipeline
pipeline = compilation_pipeline(sort, (Vector{Int},))
println(pipeline.lowered)
println(pipeline.typed)
println(pipeline.llvm_ir)
println(pipeline.native)

# Export to JSON
export_json(pipeline, "pipeline.json")
source

LLVM Tooling

Work directly with LLVM IR to understand optimization passes and code generation.

RepliBuild.Introspect.llvm_irFunction
llvm_ir(bitcode_path::String)

Disassemble LLVM bitcode to readable IR using llvm-dis.

Arguments

  • bitcode_path::String - Path to .bc bitcode file

Returns

LLVMIRInfo

Examples

# Disassemble bitcode
ir = llvm_ir("code.bc")
println(ir.ir)
println("Instructions: $(ir.instruction_count)")
source
RepliBuild.Introspect.optimize_irFunction
optimize_ir(ir_path::String, opt_level::String; passes=nothing)

Optimize LLVM IR using opt tool.

Arguments

  • ir_path::String - Path to .ll IR file
  • opt_level::String - Optimization level ("0", "1", "2", "3")
  • passes - Optional specific passes to run (Vector{String})

Returns

OptimizationResult

Examples

# Optimize at O2
result = optimize_ir("code.ll", "2")
println("Reduction: $(result.metrics.reduction_percentage)%")

# Run specific passes
result = optimize_ir("code.ll", "2", passes=["mem2reg", "inline"])
source
RepliBuild.Introspect.compare_optimizationFunction
compare_optimization(ir_path::String, levels::Vector{String})

Compare multiple optimization levels.

Arguments

  • ir_path::String - Path to .ll IR file
  • levels::Vector{String} - Optimization levels to compare (e.g., ["0", "2", "3"])

Returns

Dict{String, OptimizationResult}

Examples

# Compare O0, O2, O3
results = compare_optimization("code.ll", ["0", "2", "3"])
for (level, result) in results
    println("O$level: $(result.metrics.instructions_after) instructions")
end
source
RepliBuild.Introspect.run_passesFunction
run_passes(ir_path::String, passes::Vector{String})

Run specific LLVM passes and return optimized IR.

Arguments

  • ir_path::String - Path to .ll IR file
  • passes::Vector{String} - List of pass names

Returns

OptimizationResult

Examples

# Run mem2reg and inline passes
result = run_passes("code.ll", ["mem2reg", "inline"])

# Run loop optimization passes
result = run_passes("code.ll", ["loop-simplify", "loop-unroll"])
source
RepliBuild.Introspect.compile_to_asmFunction
compile_to_asm(ir_path::String; opt_level="2", target=nothing)

Compile LLVM IR to native assembly using llc.

Arguments

  • ir_path::String - Path to .ll IR file
  • opt_level::String - Optimization level (default: "2")
  • target - Target architecture (default: native)

Returns

String - Generated assembly code

Examples

# Compile to native assembly
asm = compile_to_asm("code.ll")

# Compile with O3
asm = compile_to_asm("code.ll", opt_level="3")

# Compile for specific target
asm = compile_to_asm("code.ll", target="x86_64-unknown-linux-gnu")

# Save to file
open("code.s", "w") do io
    write(io, asm)
end
source

Benchmarking

Performance analysis tools designed to compare C++ native performance against Julia wrappers.

RepliBuild.Introspect.benchmarkFunction
benchmark(func, args...; samples=1000, warmup=10)

Benchmark a function with given arguments.

Performs warmup runs, then measures timing, allocations, and GC time across multiple samples.

Arguments

  • func - Function to benchmark
  • args... - Arguments to pass to function
  • samples::Int - Number of samples to collect (default: 1000)
  • warmup::Int - Number of warmup runs (default: 10)

Returns

BenchmarkResult

Examples

# Benchmark a function
result = benchmark(sort, rand(10000))
println("Median: $(format_time(result.median_time))")

# Benchmark with more samples
result = benchmark(my_func, data, samples=5000, warmup=50)

# Export results
export_json(result, "benchmark.json")
source
RepliBuild.Introspect.benchmark_suiteFunction
benchmark_suite(funcs::Dict{String,Function}; samples=1000, warmup=10)

Benchmark multiple functions and return results as a dictionary.

Arguments

  • funcs::Dict{String,Function} - Dictionary mapping names to functions
  • samples::Int - Number of samples per function (default: 1000)
  • warmup::Int - Number of warmup runs per function (default: 10)

Returns

Dict{String, BenchmarkResult}

Examples

# Define benchmark suite
funcs = Dict(
    "sort_builtin" => () -> sort(rand(1000)),
    "sort_custom" => () -> my_sort(rand(1000))
)

# Run suite
results = benchmark_suite(funcs)

# Compare results
for (name, result) in results
    println("$name: $(format_time(result.median_time))")
end

# Export as dataset
export_dataset(results, "benchmark_suite/")
source
RepliBuild.Introspect.track_allocationsFunction
track_allocations(func, args...)

Detailed allocation tracking for a function call.

Uses @timed to track allocations and returns detailed information.

Arguments

  • func - Function to track
  • args... - Arguments to pass to function

Returns

Dict with allocation details

Examples

# Track allocations
alloc_info = track_allocations(my_func, data)
println("Allocated: $(alloc_info[:total_bytes]) bytes")
println("GC time: $(alloc_info[:gc_time]) seconds")
source

Data Export

Export your findings for external analysis or reporting.

RepliBuild.Introspect.export_jsonFunction
export_json(data, path::String; pretty=true)

Export any structured type to JSON format.

Arguments

  • data - Data to export (any type with compatible structure)
  • path::String - Output file path
  • pretty::Bool - Pretty-print JSON (default: true)

Examples

# Export benchmark result
result = benchmark(my_func, args)
export_json(result, "benchmark.json")

# Export DWARF info
dwarf = dwarf_info("lib")
export_json(dwarf, "dwarf_info.json")
source
RepliBuild.Introspect.export_csvFunction
export_csv(data::Vector, path::String)

Export vector of structs to CSV format.

Arguments

  • data::Vector - Vector of structured data
  • path::String - Output file path

Examples

# Export symbols to CSV
syms = symbols("lib")
export_csv(syms, "symbols.csv")

# Export benchmark results
results = [benchmark(f, args) for f in funcs]
export_csv(results, "benchmarks.csv")
source
RepliBuild.Introspect.export_datasetFunction
export_dataset(data, dir::String; formats=[:json, :csv])

Export complete dataset in multiple formats.

Organizes data by type and exports to the specified directory.

Arguments

  • data - Data to export (DWARFInfo, BenchmarkResult, etc.)
  • dir::String - Output directory
  • formats::Vector{Symbol} - Export formats (default: [:json, :csv])

Examples

# Export DWARF info as dataset
dwarf = dwarf_info("lib")
export_dataset(dwarf, "dataset/")
# Creates: dataset/functions.json, dataset/structs.json, etc.

# Export benchmark suite
results = benchmark_suite(funcs)
export_dataset(results, "benchmarks/", formats=[:json, :csv])
source

Real-World Workflow: Analyzing a Slow Function

Suppose you have a wrapped C++ function compute_physics that isn't performing as expected. Here is how you can use the introspection toolkit to diagnose the issue.

1. Benchmark

First, establish a baseline.

using RepliBuild.Introspect
using MyWrappedLib

# Run a reliable benchmark
result = benchmark(MyWrappedLib.compute_physics, (data_ptr, 1000))
println("Average time: \$(result.avg_time_ns) ns")

2. Check Type Stability

If the wrapper is type-unstable, Julia has to box values, killing performance.

# This will print a warning if return types or variables are not concrete
analyze_type_stability(MyWrappedLib.compute_physics, (data_ptr, 1000))

3. Inspect Native Code

Did the compiler vectorize the loop? Use code_native or analyze_simd.

# Look for vector instructions (e.g., vmovups, vmulpd) in the assembly
analyze_simd(MyWrappedLib.compute_physics, (data_ptr, 1000))

4. Optimize and Compare

Recompile your library with higher optimization levels using replibuild.toml (set optimization_level = "3"), then rebuild and compare.

# Compare the IR of two build variants
compare_optimization("build/O2/lib.so", "build/O3/lib.so")