Debugging Workflow
April 25, 2026 · View on GitHub
Step-by-step debugging for common failures in the Beamtalk compiler and runtime.
Compiler Crashes
# 1. Enable panic backtraces
RUST_BACKTRACE=1 beamtalk build failing.bt
# 2. Identify which phase failed
# Lexer error: "unexpected character at line X, column Y"
# Parser error: "expected X, found Y"
# Codegen error: "failed to generate code for ..."
# 3. Create minimal repro case
echo "minimal failing code" > test.bt
beamtalk build test.bt
# 4. Add debug output in relevant layer
# For parser: add dbg!(&ast) in crates/beamtalk-core/src/source_analysis/parser/mod.rs
# For codegen: add dbg!(&expr) in crates/beamtalk-core/src/codegen/core_erlang/
Runtime Errors
# 1. Inspect generated Core Erlang
cat build/module_name.core | less
# Look for:
# - Function definitions ('functionName'/Arity)
# - Pattern matches (case ... of)
# - Error calls (call 'erlang':'error')
# 2. Test generated BEAM in Erlang shell
# Single-file build outputs to ./build with bt@ prefix
erl -pa build
1> 'bt@module_name':function_name(Args).
# 3. Enable Erlang debug traces
2> dbg:tracer().
3> dbg:p(all, c).
4> dbg:tpl('bt@module_name', '_', []).
5> 'bt@module_name':function_name(Args).
Test Failures
# 1. Run single test with output
cargo test test_name -- --nocapture
# 2. Check what the test expects
# - Snapshot test: see tests/snapshots/*.snap
# - REPL-protocol test: see tests/repl-protocol/cases/*.btscript
# - Unit test: read test source
# 3. Update snapshots if intentional
cargo test test_name
# Review changes in git diff
cargo insta accept
# 4. Run all tests in module
cargo test --test module_name
REPL-Protocol Test Failures
# 1. Check REPL daemon logs
just test-repl-protocol 2>&1 | tee repl-protocol.log
grep "ERROR\|Warning\|failed" repl-protocol.log
# 2. Test fixture manually
cd tests/repl-protocol/fixtures
../../target/debug/beamtalk build counter.bt
cat build/counter.core
# 3. Run REPL interactively
beamtalk repl
> :load tests/repl-protocol/fixtures/counter.bt
> Counter spawn
> c increment
# 4. Check expected output in test file
cat tests/repl-protocol/cases/actors.btscript
# Look for // => expected output comments
Codegen Debugging
# 1. Generate and inspect Core Erlang
beamtalk build failing.bt
cat build/failing.core
# 2. Look for suspicious patterns:
# - Missing State/Self parameters
# - Unbound variables (StateX, State1, etc.)
# - Wrong function arities
# - Call to undefined functions
# 3. Compare with working example
beamtalk build examples/counter.bt
diff build/counter.core build/failing.core
# 4. Add codegen debug output
# Edit crates/beamtalk-core/src/codegen/core_erlang/expressions.rs
dbg!(&expr);
// Rebuild and check output
Runtime/REPL Debugging
# 1. Check if modules loaded
beamtalk repl
> Beamtalk allClasses
> Beamtalk classNamed: #Counter
# 2. Enable CLI diagnostics for the REPL
RUST_LOG=beamtalk=debug beamtalk repl
# 3. Run the node in the foreground (no daemonization)
beamtalk repl --foreground
# 4. Check actor state
> c := Counter spawn
> c class
> c respondsTo: #increment
# 5. Inspect Erlang process state
# In separate terminal:
erl -name debug@127.0.0.1 -setcookie beamtalk
(debug@127.0.0.1)1> nodes().
(debug@127.0.0.1)2> observer:start().
# Find beamtalk_repl process, inspect state
Performance Debugging
# 1. Profile compilation
time beamtalk build large_file.bt
# 2. Profile runtime (connect Erlang shell to running node)
# Start REPL in one terminal, then in another:
erl -remsh beamtalk@localhost -name profiler@localhost
1> timer:tc(fun() -> 'bt@counter':spawn() end).
{TimeInMicroseconds, Result}
# 3. Check memory usage (Observer in Erlang shell)
1> observer:start().
% Use the Memory tab to see allocation by process
# 4. Flame graphs (advanced)
# Enable Erlang profiling
erl -pa build
1> fprof:apply(Module, Function, Args).
2> fprof:profile().
3> fprof:analyse().
Codegen Diagnostics (BT-1343)
The compiler can emit detailed diagnostics about code generation decisions. These are off by default (too noisy for normal use) and gated behind environment variables.
Environment Variables
| Variable | Effect |
|---|---|
BEAMTALK_CODEGEN_DIAGNOSTICS=1 | Enable all codegen diagnostics (info-level hints) |
BEAMTALK_WARN_STATEACC=1 | Promote StateAcc fallback diagnostics to warning level (requires BEAMTALK_CODEGEN_DIAGNOSTICS=1) |
# See all codegen decisions
BEAMTALK_CODEGEN_DIAGNOSTICS=1 beamtalk build myfile.bt
# Highlight StateAcc fallbacks as warnings
BEAMTALK_CODEGEN_DIAGNOSTICS=1 BEAMTALK_WARN_STATEACC=1 beamtalk build myfile.bt
Diagnostic Categories
1. Block calling convention chosen
Reports which optimization mode was selected for each stateful loop:
direct-params— pure locals, no field mutations (BT-1275)tuple-acc— local mutations in foldl list ops (BT-1276)hybrid— locals + field reads/mutations as direct params (BT-1326)StateAcc— fallback map-based threading
Example: Loop at line 42: using direct-params (3 locals, 0 field mutations)
2. StateAcc fallback reason
When falling back to StateAcc, includes the specific reason:
self-send in loop bodynested list op with cross-scope mutationtier-2 value call on threaded localinline conditional writing to threaded localcondition has state effectscontrol-flow sub-expression with mutations
Example: Loop at line 15: StateAcc fallback — self-send in loop body
3. Non-local return (^) in block
Emitted when ^ inside a block generates throw/catch, which can prevent BEAM JIT
from optimizing the enclosing function.
Example: Non-local return at line 15: compiled via throw/catch, may inhibit JIT optimization
4. Synchronous self-send in loop
Flags deadlock risk when a loop body sends a message to self.
Example: Self-send 'self bar' inside loop at line 30: synchronous call to own mailbox, potential deadlock
5. Dynamic dispatch fallback
When a message send can't be statically resolved and uses runtime dispatch
via beamtalk_message_dispatch:send/3.
Example: Send 'foo:' at line 23: dynamic dispatch (receiver type unknown)
6. Large extracted arity
Informational when a loop extracts >8 parameters as direct fun arguments.
Example: Loop at line 10: 14 extracted params
When All Else Fails
- Simplify — Remove code until it works, then add back
- Compare — Find similar working code, diff against it
- Ask — Share error + what you tried, get fresh eyes
- Rubber duck — Explain the problem out loud to yourself
- Sleep — Come back tomorrow with fresh perspective