BoundaryML/baml · critical

exception table: handler block {handler:?} has no PC address

Error message

exception table: handler block {handler:?} has no PC address — catch region was emitted but its handler block was dropped

What it means

Panic (via unreachable!) while building the exception table: a MIR catch region's handler block, after jump-target resolution, has no entry in block_addresses. The catch region was emitted (so the compiler believes a handler is needed) but the handler block itself was dropped during emission. Every emitted catch region must have a handler block with a concrete PC.

Source

Thrown at baml_language/crates/baml_compiler2_emit/src/emit.rs:2830

    /// anchor it), and both escaped their `catch` before this was made exact.
    ///
    /// Nested regions overlap: the protected PC set of an inner region is a
    /// subset of every enclosing region's (inner windows nest inside outer
    /// windows at lowering). The VM picks the innermost covering entry —
    /// largest `start_pc`, then smallest `end_pc`, then latest table order —
    /// which subset-nesting makes unambiguous: the inner region's coalesced
    /// range around any PC is contained in the outer's, and for byte-identical
    /// ranges the stable sort below preserves `catch_regions` creation order
    /// (always outer before inner), so the last matching entry is the inner
    /// handler.
    fn build_exception_table(&mut self, mir: &MirFunctionBody<'ctx>) {
        use bex_vm_types::bytecode::{ExceptionTableEntry, HandlerContextEntry};

        for region in &mir.catch_regions {
            let handler = self.analysis.resolve_jump_target(region.handler);

            let &handler_pc = self.block_addresses.get(&handler).unwrap_or_else(|| {
                unreachable!(
                    "exception table: handler block {handler:?} has no PC address — \
                     catch region was emitted but its handler block was dropped"
                )
            });
            // If the error local was optimized away (e.g. an inline
            // `throw X catch ...` that the MIR lowers as a direct jump),
            // the catch region doesn't need a VM-level exception table entry.
            let Some(&error_slot) = self.local_slots.get(&region.error_local) else {
                log::debug!(
                    "exception table: error local {:?} has no slot (optimized away)",
                    region.error_local,
                );
                continue;
            };

            let stack_trace_slot = region
                .stack_trace_local
                .and_then(|local| self.local_slots.get(&local).copied())

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Keep the handler block alive whenever a catch region referencing it is emitted, or drop both together.
  2. Record a redirect address for eliminated handler blocks so the exception table can point at the surviving equivalent.
  3. Reproduce with the try/catch BAML source and file a compiler bug including the MIR dump.
  4. Use a compiler build without the offending block-elimination change.
Defensive patterns

Strategy: fallback

Try / catch

// compiler panic — not catchable in-process; guard the build
if !baml_cli_compile(src).is_ok() {
    report_compiler_bug(src);
}

Prevention

When it happens

Trigger: Finalizing a function with mir.catch_regions non-empty where the resolved handler block was never emitted, e.g. the handler block was eliminated by an optimization but its catch region survived.

Common situations: Compiler development around try/catch lowering or dead-block elimination in BAML's MIR-to-bytecode pipeline; users see it as a compiler crash on some try/catch programs.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/8940b042c47203d7. Report an issue: GitHub.