Merged
Conversation
- Rename debug.rs → debug_info.rs in silverscript-lang (types-only module) - Create debugger-session library crate (session runtime, presentation) - Create cli-debugger binary crate (renamed from sil-debug) - Migrate debug session tests and CLI smoke test to new crates - Update all imports across workspace
feat(debugger): port debugger onto izio spanned-ast core
There was a problem hiding this comment.
Pull request overview
This PR implements a comprehensive source-level debugger infrastructure for SilverScript, enabling developers to step through contract execution, inspect variables, and set breakpoints.
Changes:
- Adds
DebugInfoartifact system to compilation with source mappings, variable tracking, and inline call metadata - Implements
DebugSessionruntime for stepping (into/over/out), breakpoints, and variable inspection via shadow VM evaluation - Introduces CLI debugger (
sil-debug) as a REPL-based interface
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
silverscript-lang/src/debug_info.rs |
Core debug metadata structures (mappings, variable updates, constants) |
silverscript-lang/src/compiler/debug_recording.rs |
Debug event recording during compilation with inline call support |
silverscript-lang/src/compiler.rs |
Integration of debug recording into compilation pipeline |
debugger/session/src/session.rs |
Debug session runtime with stepping and variable inspection |
debugger/session/src/presentation.rs |
Value formatting and source context presentation |
debugger/cli/src/main.rs |
CLI REPL interface for interactive debugging |
Cargo.toml / Cargo.lock |
Workspace and dependency updates |
Comments suppressed due to low confidence (3)
debugger/session/src/presentation.rs:129
- The
decode_i64function is duplicated betweensession.rsandpresentation.rs. This code duplication should be eliminated by extracting the function into a shared utility module or making one implementation call the other.
fn decode_i64(bytes: &[u8]) -> Result<i64, String> {
if bytes.is_empty() {
return Ok(0);
}
if bytes.len() > 8 {
return Err("numeric value is longer than 8 bytes".to_string());
}
let msb = bytes[bytes.len() - 1];
let sign = 1 - 2 * ((msb >> 7) as i64);
let first_byte = (msb & 0x7f) as i64;
let mut value = first_byte;
for byte in bytes[..bytes.len() - 1].iter().rev() {
value = (value << 8) + (*byte as i64);
}
Ok(value * sign)
}
debugger/session/src/session.rs:890
- Using
Box::leakin tests causes intentional memory leaks. While this is acceptable for tests that run once and exit, consider documenting why this approach is necessary (likely for lifetime constraints with 'static references), or alternatively use an approach that doesn't permanently leak memory, such as scoped threads or once_cell.
let sig_cache = Box::leak(Box::new(Cache::new(10_000)));
let reused_values: &'static SigHashReusedValuesUnsync = Box::leak(Box::new(SigHashReusedValuesUnsync::new()));
silverscript-lang/src/compiler.rs:1834
- The inline call parameter handling creates synthetic
__arg_variables that are inserted into both the callee's environment and the caller's environment. If the function name itself contains an underscore or if parameter names could collide with this naming pattern, there could be unexpected behavior. Consider using a more unique namespace for synthetic variables (e.g.,__internal_arg_{name}_{index}_{unique_id}) or verifying that user-defined names cannot start with__arg_.
for (index, (param, arg)) in function.params.iter().zip(args.iter()).enumerate() {
let resolved = resolve_expr(arg.clone(), caller_env, &mut HashSet::new())?;
let temp_name = format!("__arg_{name}_{index}");
let param_type_name = type_name_from_ref(¶m.type_ref);
env.insert(temp_name.clone(), resolved.clone());
types.insert(temp_name.clone(), param_type_name.clone());
env.insert(param.name.clone(), Expr::new(ExprKind::Identifier(temp_name.clone()), span::Span::default()));
caller_env.insert(temp_name.clone(), resolved);
caller_types.insert(temp_name, param_type_name);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Bad merge resolution had reverted PR kaspanet#44 changes: - Restore serialize_i64 instead of to_le_bytes+OpBin2Num for int fields - Restore Ok(9) field chunk size (was incorrectly Ok(10)) - Remove spurious OpBin2Num injection in validate_output_state - Update test expectations to match corrected encoding Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove constructor/constant names from env when they collide with function param names (prioritizing function parameters). - Propagate caller's __arg_ bindings and params map into inline calls, allowing nested synthetic argument chains to resolve correctly. - Extract magic string into pub const SYNTHETIC_ARG_PREFIX. - Add regression tests for both fixes.
someone235
approved these changes
Mar 5, 2026
Contributor
someone235
left a comment
There was a problem hiding this comment.
Approved. Waiting for #46 to be merged before merging.
# Conflicts: # silverscript-lang/src/compiler.rs
someone235
approved these changes
Mar 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AI usage disclosure
This code was written by a coding agent. I mentored the implementation and reviewed the code.
Source-Level Debugger
Adds source-level stepping debugger infrastructure for SilverScript.
The main challenges:
varswhen locals are purely a compile-time abstraction.We solve this by adding an optional
DebugInfoartifact to compilation, and aDebugSessionthat consumes it to drive stepping and compute variable values via a shadow Script engine.The CLI (
sil-debug) is a thin demo client on top ofDebugInfo+DebugSession.End-to-End Flow
DebugInfooutput (opt-in viaCompileOptions::record_debug_infos).DebugSession(Kaspa's TxScriptEngine +DebugInfo).Changes Overview
DebugInfo: Optional Compilation Output
When debug recording is enabled, compilation emits a
DebugInfoartifact containing:DebugMapping- the ordered source-step timelineDebugVariableUpdate- variable updates attached to that timelineEach
DebugMappingdescribes one source-level moment:span- the source region to highlightbytecode_start/bytecode_end- where it maps in the emitted script (can be zero-width)kind- what type of mapping:Statement- emitted bytecodeVirtual- zero bytecode (declarations, assignments, etc.)InlineCallEnter/InlineCallExit- structural markers around inline callsSynthetic- compiler-only regions with no user source (dispatcher logic, etc.)sequence- global canonical step ordering (the stable step id)call_depth- inline nesting depth (0 = entrypoint, 1+ inside inline calls)frame_id- unique id per inline invocation (isolates locals)Since multiple source steps can share the same bytecode offset (virtual steps), the debugger steps by
sequence, not by offset.Variable Updates
Getting
varsto work at each source step requires bridging compile-time semantics to runtime evaluation.The approach that stayed clean after a few iterations:
DebugVariableUpdaterecords:name,type_name,expr(pre-resolved for debugger eval),sequence, andframe_id.This keeps the debugger honest - real compiler, real engine - and sets us up for future context-aware features (covenant values, TX-aware opcodes, etc.).
Recording During Compilation
All recording lives in
compiler/debug_recording.rs, split into two layers:FunctionDebugRecorder(per-function) - assigns localsequenceids, records a mapping per statement (StatementorVirtual), and attaches variable updates at statement boundaries.DebugSink(global) - merges per-function recordings into a singleDebugInfo, applies bytecode offsets during final script placement, and remaps sequences so they're globally unique and properly ordered.Inline Calls
Inline calls compile by inlining callee bytecode into the caller. We make stepping work by:
InlineCallEnter/InlineCallExitmarkers at the call sitecall_depthframe_idper invocation (so locals don't leak across calls)DebugSession (
debugger/session/)DebugSessionwraps Kaspa's TxScriptEngine +DebugInfoand exposes source-level stepping and inspection.It builds a step list sorted by
sequence. Stepping is depth-aware:step_into()- next mapping regardless of depthstep_over()- next mapping wherecall_depth <= currentstep_out()- next mapping wherecall_depth < currentVirtual steps advance the visible source position without consuming opcodes.
Variable Listing
Variable visibility at any step is based on
(sequence, frame_id):The shadow engine compiles the recorded expression with
compile_debug_expr, runs it on a fresh engine seeded with entrypoint params from the main engine, and decodes the result. If anything fails, the variable stays visible but showsUnknownwith a reason.Debugger CLI (
debugger/cli/)The CLI REPL (
sil-debug) is a thin wrapper overDebugSession— just enough to test the infrastructure interactively.Crate Layout
silverscript-lang/src/compiler/debug_recording.rs— recording layer (FunctionDebugRecorder+DebugSink)silverscript-lang/src/debug_info.rs—DebugInfodata model (DebugMapping,DebugVariableUpdate, etc.)debugger/session/—DebugSessionruntime (stepping, variable inspection)debugger/cli/—sil-debugCLI REPLTests
debugger/session/tests/debug_session_tests.rs— stepping, virtual steps, inline step into/over/out, breakpoints, expression evaluationdebugger/cli/tests/cli_tests.rs— CLI smoke test