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.symbols — Function
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 filefilter::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")RepliBuild.Introspect.dwarf_info — Function
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/")RepliBuild.Introspect.dwarf_dump — Function
dwarf_dump(binary_path::String; section=:info)Dump DWARF debug information using llvm-dwarfdump.
Arguments
binary_path::String- Path to binary filesection::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)
endRepliBuild.Introspect.disassemble — Function
disassemble(binary_path::String, symbol=nothing; syntax=:att)Disassemble binary or specific symbol using llvm-objdump.
Arguments
binary_path::String- Path to binary filesymbol- 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)
endRepliBuild.Introspect.headers — Function
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))")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_lowered — Function
code_lowered(func, types::Tuple)Get lowered Julia IR before type inference.
Wraps @code_lowered macro and returns structured CodeLoweredInfo.
Arguments
func- Function to inspecttypes::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))")RepliBuild.Introspect.code_typed — Function
code_typed(func, types::Tuple; optimized=true)Get type-inferred Julia IR.
Wraps @code_typed macro and returns structured CodeTypedInfo.
Arguments
func- Function to inspecttypes::Tuple- Argument types tupleoptimized::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)")RepliBuild.Introspect.code_llvm — Function
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 inspecttypes::Tuple- Argument types tupleoptimized::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)RepliBuild.Introspect.code_native — Function
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 inspecttypes::Tuple- Argument types tuplesyntax::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)RepliBuild.Introspect.code_warntype — Function
code_warntype(func, types::Tuple)Get type inference warnings (wrapper for @code_warntype).
Arguments
func- Function to inspecttypes::Tuple- Argument types tuple
Returns
String - Warning output
Examples
# Check for type instabilities
warnings = code_warntype(my_func, (Int, Vector))RepliBuild.Introspect.analyze_type_stability — Function
analyze_type_stability(func, types::Tuple)Analyze type stability using @code_warntype.
Parses @code_warntype output to identify type instabilities.
Arguments
func- Function to analyzetypes::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
endRepliBuild.Introspect.analyze_simd — Function
analyze_simd(func, types::Tuple)Analyze SIMD vectorization by parsing LLVM IR.
Searches for vector instructions and identifies vectorized loops.
Arguments
func- Function to analyzetypes::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)")RepliBuild.Introspect.analyze_allocations — Function
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 analyzetypes::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)")RepliBuild.Introspect.analyze_inlining — Function
analyze_inlining(func, types::Tuple)Analyze inlining by comparing unoptimized vs optimized typed IR.
Arguments
func- Function to analyzetypes::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")RepliBuild.Introspect.compilation_pipeline — Function
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 analyzetypes::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")LLVM Tooling
Work directly with LLVM IR to understand optimization passes and code generation.
RepliBuild.Introspect.llvm_ir — Function
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)")RepliBuild.Introspect.optimize_ir — Function
optimize_ir(ir_path::String, opt_level::String; passes=nothing)Optimize LLVM IR using opt tool.
Arguments
ir_path::String- Path to .ll IR fileopt_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"])RepliBuild.Introspect.compare_optimization — Function
compare_optimization(ir_path::String, levels::Vector{String})Compare multiple optimization levels.
Arguments
ir_path::String- Path to .ll IR filelevels::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")
endRepliBuild.Introspect.run_passes — Function
run_passes(ir_path::String, passes::Vector{String})Run specific LLVM passes and return optimized IR.
Arguments
ir_path::String- Path to .ll IR filepasses::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"])RepliBuild.Introspect.compile_to_asm — Function
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 fileopt_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)
endBenchmarking
Performance analysis tools designed to compare C++ native performance against Julia wrappers.
RepliBuild.Introspect.benchmark — Function
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 benchmarkargs...- Arguments to pass to functionsamples::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")RepliBuild.Introspect.benchmark_suite — Function
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 functionssamples::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/")RepliBuild.Introspect.track_allocations — Function
track_allocations(func, args...)Detailed allocation tracking for a function call.
Uses @timed to track allocations and returns detailed information.
Arguments
func- Function to trackargs...- 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")Data Export
Export your findings for external analysis or reporting.
RepliBuild.Introspect.export_json — Function
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 pathpretty::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")RepliBuild.Introspect.export_csv — Function
export_csv(data::Vector, path::String)Export vector of structs to CSV format.
Arguments
data::Vector- Vector of structured datapath::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")RepliBuild.Introspect.export_dataset — Function
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 directoryformats::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])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")