Runtime Profiles
BHC supports runtime profiles: the same source language, different compilation and runtime contracts. Profiles are explicit and localizable (package/module).
How Profiles Work
- Profiles do not “rename the language”
- Profiles are explicit: you choose them; nothing silently changes
- Profile differences are documented and testable
- You can select profiles per package or per module
Available Profiles
default
General-purpose Haskell compilation. Focuses on correctness and broad compatibility.
bhc Main.hs
# or explicitly:
bhc --profile=default Main.hsBest for:
- Library code
- Applications prioritizing compatibility
- Code that needs to work with both BHC and GHC
server
Structured concurrency, predictable latency, observability hooks.
bhc --profile=server Main.hsFeatures:
- Cancellation and deadlines for concurrent operations
- Scoped resource management
- Tracing and observability hooks
- Predictable scheduling behavior
Best for:
- Web services
- API servers
- Long-running daemons
- Applications needing observability
numeric
Strict-by-default hot paths, unboxed-first numerics, tensor/array lowering, fusion guarantees, SIMD + parallel loops.
bhc --profile=numeric Main.hsFeatures:
- Strict-by-default evaluation in hot paths (fewer thunks, fewer surprises)
- Unboxed numerics and flat arrays by default
- Array/tensor IR stage that understands shapes/strides/layouts
- Fusion as a contract for core patterns (map/zip/reduce)
- SIMD and parallel lowering when legal and profitable
- Pinned/non-moving buffers for FFI and large arrays
Best for:
- Scientific computing
- Data processing pipelines
- Machine learning workloads
- Performance-critical numeric code
See Numeric Performance for details.
edge
Smaller runtime footprint and restricted environment assumptions.
bhc --profile=edge Main.hsFeatures:
- Minimal runtime size
- Works within WASI constraints
- Reduced memory overhead
- Suitable for sandboxed execution
Best for:
- WebAssembly targets
- Serverless functions
- Size-constrained deployments
realtime
Bounded GC pauses and predictable latency for time-sensitive applications.
bhc --profile=realtime Main.hsFeatures:
- Bounded GC pauses (configurable, default <1ms)
- Arena allocators for per-frame allocation
- Incremental GC with latency guarantees
- Predictable scheduling behavior
- No GC during critical sections
Best for:
- Game engines and game logic
- Audio processing and synthesis
- Robotics and control systems
- VR/AR applications
- Any soft real-time system
embedded
No garbage collector, static allocation only, bare-metal execution.
bhc --profile=embedded --target=thumbv7m-none-eabi Main.hsFeatures:
- No GC (compile-time error if GC would be needed)
- Static memory allocation only
- Stack-based computation
- Direct hardware access primitives
- Minimal or zero runtime
- Interrupt-safe code generation
Best for:
- Microcontrollers (ARM Cortex-M, RISC-V)
- Safety-critical systems
- Bootloaders and firmware
- Bare-metal applications
- Memory-constrained devices (<64KB RAM)
Selecting Profiles
Command line
bhc --profile=numeric Main.hsPer-package (in cabal file)
-- In your .cabal file
ghc-options: -profile=serverPer-module
{-# OPTIONS_BHC -profile=numeric #-}
module HotPath where
-- | This module handles the performance-critical inner loop of our application.
-- By specifying the numeric profile at the module level, we get:
-- - Strict evaluation by default (no thunk buildup)
-- - Unboxed numeric types (no pointer indirection)
-- - Guaranteed fusion for map/filter/fold chains
--
-- The rest of our application can use the default profile for flexibility,
-- while this module gets the specialized numeric treatment.
import qualified Data.Vector.Unboxed as V
import Data.Vector.Unboxed (Vector)
-- | Process sensor readings with guaranteed performance characteristics.
-- In numeric profile, this entire pipeline fuses into a single loop.
processSensorData :: Vector Double -> (Double, Double, Int)
processSensorData readings = (avg, peak, outlierCount)
where
-- Remove invalid readings (negative values are sensor errors)
valid = V.filter (>= 0) readings
-- Compute statistics in a single pass
n = V.length valid
avg = V.sum valid / fromIntegral n
peak = V.maximum valid
-- Count outliers (values more than 3 standard deviations from mean)
sumSq = V.foldl' (\acc x -> acc + x*x) 0 valid
variance = (sumSq / fromIntegral n) - avg * avg
stdDev = sqrt variance
outlierCount = V.length $ V.filter (\x -> abs (x - avg) > 3 * stdDev) validProfile Combinations
Profiles are mutually exclusive at the module level. A module compiles with exactly one profile. Different modules in the same package can use different profiles.
Compatibility
All profiles compile the same Haskell source language. The differences are:
- Compilation strategy (strictness, unboxing decisions)
- Runtime behavior (scheduling, allocation)
- Available runtime features (tracing, concurrency primitives)
Code compiled with different profiles can interoperate, subject to documented constraints.