BoundaryML/baml · critical

virtual local without definition

Error message

virtual local {local} without definition

What it means

Panic while inlining a virtual (def-use optimized) local at its use site: the analysis recorded the local as having a use, but def_use[&local].def is None, so there is no rvalue to inline. A virtual local exists precisely to have its single definition inlined; a use without a definition breaks that invariant.

Solutions

  1. Ensure every virtual local has its definition rvalue recorded in def_use before emission of its uses.
  2. Re-materialize locals as normal stack locals when their definition cannot be inlined.
  3. Verify the pass that promotes locals to virtual ones checks for a present definition.
  4. Report the triggering BAML source as a compiler bug.
Defensive patterns

Strategy: validation

Validate before calling

// only treat locals as virtual when a definition exists
fn can_be_virtual(local: &Local, def_use: &DefUse) -> bool {
    def_use.uses(local) == 1 && def_use.def(local).is_some()
}

Try / catch

// compiler panic — guard the compile entry point
match baml_compile(source) {
    Err(e) => log_internal_bug(e),
    Ok(b) => use_bytecode(b),
}

Prevention

When it happens

Trigger: Emitting a use of a virtual local whose def-use entry has no recorded definition rvalue — e.g. the definition was dropped by a pass but the use remained, or the local was misclassified as virtual.

Common situations: Compiler development around the def-use/virtual-local optimization; users see a compiler crash rather than a source-level error.

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/cdcba348e9ba6314. Report an issue: GitHub.

Appendix: source

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

    fn pull_constant(&mut self, constant: &Constant<'ctx>) -> Result<(), Self::Error> {
        self.emit_constant(constant);
        Ok(())
    }

    fn pull_local(&mut self, local: Local) -> Result<LocalPullAction<'ctx>, Self::Error> {
        let classification = self.analysis.classifications[&local];

        let action = match classification {
            LocalClassification::Virtual => {
                // Attribute inlined virtual loads to their defining statement.
                self.set_debug_span(self.def_span_for_local(local), false);
                // Inline the definition rvalue at use site.
                let rvalue = self.analysis.def_use[&local]
                    .def
                    .as_ref()
                    .map(|def| def.rvalue.clone())
                    .unwrap_or_else(|| panic!("virtual local {local} without definition"));
                // MakeClosure must be handled specially: its captures need to load
                // cell pointers (LoadVar) not cell values (LoadDeref). We intercept
                // here so that `emit_rvalue_pull` (which sets loading_for_closure_capture)
                // is called rather than the generic `walk_rvalue_pull` inlining path.
                // MakeBoundMethod / MakeVirtualBoundMethod / VirtualFieldAccess must
                // also be handled specially: none is handled by `walk_rvalue_pull`
                // (which panics on them), so route through `emit_rvalue_pull`.
                // BinaryOp must be routed through `emit_rvalue_pull` so that the
                // type-aware specialization in `try_specialize_binary_op` can fire
                // (e.g. emitting `CmpBigintOp` instead of the generic `CmpOp`).
                // Class aggregates may use emitter-only spread helpers, so they
                // also need to flow through `emit_rvalue_pull` when inlined.
                if matches!(
                    rvalue,
                    Rvalue::MakeClosure { .. }
                        | Rvalue::MakeBoundMethod { .. }
                        | Rvalue::MakeVirtualBoundMethod { .. }
                        | Rvalue::MakeVirtualFunction { .. }

View on GitHub (pinned to bd85ce9dee)