plotnik_lib/bytecode/
ir.rs

1//! Instruction IR with symbolic labels.
2//!
3//! Pre-layout instructions use `Label` for symbolic references.
4//! After layout, labels are resolved to step addresses (u16) for serialization.
5//! Member indices use deferred resolution via `MemberRef`.
6
7use std::collections::BTreeMap;
8use std::num::NonZeroU16;
9
10use super::effects::{EffectOp, EffectOpcode};
11use super::instructions::{
12    Call, Opcode, Return, StepAddr, StepId, Trampoline, select_match_opcode,
13};
14use super::nav::Nav;
15use crate::analyze::type_check::TypeId;
16
17/// Node type constraint for Match instructions.
18///
19/// Distinguishes between named nodes (`(identifier)`), anonymous nodes (`"text"`),
20/// and wildcards (`_`, `(_)`). Encoded in bytecode header byte bits 5-4.
21///
22/// | `node_kind` | Value | Meaning      | `node_type=0`       | `node_type>0`     |
23/// | ----------- | ----- | ------------ | ------------------- | ----------------- |
24/// | `00`        | Any   | `_` pattern  | No check            | (invalid)         |
25/// | `01`        | Named | `(_)`/`(t)`  | Check `is_named()`  | Check `kind_id()` |
26/// | `10`        | Anon  | `"text"`     | Check `!is_named()` | Check `kind_id()` |
27/// | `11`        | -     | Reserved     | Error               | Error             |
28#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
29pub enum NodeTypeIR {
30    /// Any node (`_` pattern) - no type check performed.
31    #[default]
32    Any,
33    /// Named node constraint (`(_)` or `(identifier)`).
34    /// - `None` = any named node (check `is_named()`)
35    /// - `Some(id)` = specific named type (check `kind_id()`)
36    Named(Option<NonZeroU16>),
37    /// Anonymous node constraint (`"text"` literals).
38    /// - `None` = any anonymous node (check `!is_named()`)
39    /// - `Some(id)` = specific anonymous type (check `kind_id()`)
40    Anonymous(Option<NonZeroU16>),
41}
42
43impl NodeTypeIR {
44    /// Encode to bytecode: returns (node_kind bits, node_type value).
45    ///
46    /// `node_kind` is 2 bits for header byte bits 5-4.
47    /// `node_type` is u16 for bytes 2-3.
48    pub fn to_bytes(self) -> (u8, u16) {
49        match self {
50            Self::Any => (0b00, 0),
51            Self::Named(opt) => (0b01, opt.map(|n| n.get()).unwrap_or(0)),
52            Self::Anonymous(opt) => (0b10, opt.map(|n| n.get()).unwrap_or(0)),
53        }
54    }
55
56    /// Decode from bytecode: node_kind bits (2 bits) and node_type value (u16).
57    pub fn from_bytes(node_kind: u8, node_type: u16) -> Self {
58        match node_kind {
59            0b00 => Self::Any,
60            0b01 => Self::Named(NonZeroU16::new(node_type)),
61            0b10 => Self::Anonymous(NonZeroU16::new(node_type)),
62            _ => panic!("invalid node_kind: {node_kind}"),
63        }
64    }
65
66    /// Check if this represents a specific type ID (not a wildcard).
67    pub fn type_id(&self) -> Option<NonZeroU16> {
68        match self {
69            Self::Any => None,
70            Self::Named(opt) | Self::Anonymous(opt) => *opt,
71        }
72    }
73
74    /// Check if this is the Any wildcard.
75    pub fn is_any(&self) -> bool {
76        matches!(self, Self::Any)
77    }
78
79    /// Check if this is a Named constraint (wildcard or specific).
80    pub fn is_named(&self) -> bool {
81        matches!(self, Self::Named(_))
82    }
83
84    /// Check if this is an Anonymous constraint (wildcard or specific).
85    pub fn is_anonymous(&self) -> bool {
86        matches!(self, Self::Anonymous(_))
87    }
88}
89
90/// Symbolic reference, resolved to step address at layout time.
91#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
92pub struct Label(pub u32);
93
94impl Label {
95    /// Resolve this label to a step address using the layout mapping.
96    #[inline]
97    pub fn resolve(self, map: &BTreeMap<Label, StepAddr>) -> StepAddr {
98        *map.get(&self).expect("label not in layout")
99    }
100}
101
102/// Symbolic reference to a struct field or enum variant.
103/// Resolved to absolute member index during bytecode emission.
104///
105/// Struct field indices are deduplicated globally: same (name, type) pair → same index.
106/// This enables call-site scoping where uncaptured refs share the caller's scope.
107///
108/// Enum variant indices use the traditional (parent_type, relative_index) approach
109/// since enum variants don't bubble between scopes.
110#[derive(Clone, Copy, Debug)]
111pub enum MemberRef {
112    /// Already resolved to absolute index (for cases where it's known).
113    Absolute(u16),
114    /// Deferred resolution by field identity (for struct fields).
115    /// The same (field_name, field_type) pair resolves to the same member index
116    /// regardless of which struct type contains it.
117    Deferred {
118        /// The Symbol of the field name (from query interner).
119        field_name: plotnik_core::Symbol,
120        /// The TypeId of the field's value type (from query TypeContext).
121        field_type: TypeId,
122    },
123    /// Deferred resolution by parent type + relative index (for enum variants).
124    /// Uses the parent enum's member_base + relative_index.
125    DeferredByIndex {
126        /// The TypeId of the parent enum type.
127        parent_type: TypeId,
128        /// Relative index within the parent type's members.
129        relative_index: u16,
130    },
131}
132
133impl MemberRef {
134    /// Create an absolute reference.
135    pub fn absolute(index: u16) -> Self {
136        Self::Absolute(index)
137    }
138
139    /// Create a deferred reference by field identity (for struct fields).
140    pub fn deferred(field_name: plotnik_core::Symbol, field_type: TypeId) -> Self {
141        Self::Deferred {
142            field_name,
143            field_type,
144        }
145    }
146
147    /// Create a deferred reference by parent type + index (for enum variants).
148    pub fn deferred_by_index(parent_type: TypeId, relative_index: u16) -> Self {
149        Self::DeferredByIndex {
150            parent_type,
151            relative_index,
152        }
153    }
154
155    /// Resolve this reference using lookup functions.
156    ///
157    /// - `lookup_member`: maps (field_name Symbol, field_type TypeId) to member index
158    /// - `get_member_base`: maps parent TypeId to member base index
159    pub fn resolve<F, G>(self, lookup_member: F, get_member_base: G) -> u16
160    where
161        F: Fn(plotnik_core::Symbol, TypeId) -> Option<u16>,
162        G: Fn(TypeId) -> Option<u16>,
163    {
164        match self {
165            Self::Absolute(n) => n,
166            Self::Deferred {
167                field_name,
168                field_type,
169            } => lookup_member(field_name, field_type).unwrap_or(0),
170            Self::DeferredByIndex {
171                parent_type,
172                relative_index,
173            } => get_member_base(parent_type).unwrap_or(0) + relative_index,
174        }
175    }
176}
177
178/// Effect operation with symbolic member references.
179/// Used during compilation; resolved to EffectOp during emission.
180#[derive(Clone, Debug)]
181pub struct EffectIR {
182    pub opcode: EffectOpcode,
183    /// Payload for effects that don't use member indices.
184    pub payload: usize,
185    /// Member reference for Set/E effects (None for other effects).
186    pub member_ref: Option<MemberRef>,
187}
188
189impl EffectIR {
190    /// Create a simple effect without member reference.
191    pub fn simple(opcode: EffectOpcode, payload: usize) -> Self {
192        Self {
193            opcode,
194            payload,
195            member_ref: None,
196        }
197    }
198
199    /// Create an effect with a member reference.
200    pub fn with_member(opcode: EffectOpcode, member_ref: MemberRef) -> Self {
201        Self {
202            opcode,
203            payload: 0,
204            member_ref: Some(member_ref),
205        }
206    }
207
208    /// Capture current node value.
209    pub fn node() -> Self {
210        Self::simple(EffectOpcode::Node, 0)
211    }
212
213    /// Capture current node text.
214    pub fn text() -> Self {
215        Self::simple(EffectOpcode::Text, 0)
216    }
217
218    /// Push null value.
219    pub fn null() -> Self {
220        Self::simple(EffectOpcode::Null, 0)
221    }
222
223    /// Push accumulated value to array.
224    pub fn push() -> Self {
225        Self::simple(EffectOpcode::Push, 0)
226    }
227
228    /// Begin array scope.
229    pub fn start_arr() -> Self {
230        Self::simple(EffectOpcode::Arr, 0)
231    }
232
233    /// End array scope.
234    pub fn end_arr() -> Self {
235        Self::simple(EffectOpcode::EndArr, 0)
236    }
237
238    /// Begin object scope.
239    pub fn start_obj() -> Self {
240        Self::simple(EffectOpcode::Obj, 0)
241    }
242
243    /// End object scope.
244    pub fn end_obj() -> Self {
245        Self::simple(EffectOpcode::EndObj, 0)
246    }
247
248    /// Begin enum scope.
249    pub fn start_enum() -> Self {
250        Self::simple(EffectOpcode::Enum, 0)
251    }
252
253    /// End enum scope.
254    pub fn end_enum() -> Self {
255        Self::simple(EffectOpcode::EndEnum, 0)
256    }
257
258    /// Begin suppression (suppress effects within).
259    pub fn suppress_begin() -> Self {
260        Self::simple(EffectOpcode::SuppressBegin, 0)
261    }
262
263    /// End suppression.
264    pub fn suppress_end() -> Self {
265        Self::simple(EffectOpcode::SuppressEnd, 0)
266    }
267
268    /// Resolve this IR effect to a concrete EffectOp.
269    ///
270    /// - `lookup_member`: maps (field_name Symbol, field_type TypeId) to member index
271    /// - `get_member_base`: maps parent TypeId to member base index
272    pub fn resolve<F, G>(&self, lookup_member: F, get_member_base: G) -> EffectOp
273    where
274        F: Fn(plotnik_core::Symbol, TypeId) -> Option<u16>,
275        G: Fn(TypeId) -> Option<u16>,
276    {
277        let payload = if let Some(member_ref) = self.member_ref {
278            member_ref.resolve(&lookup_member, &get_member_base) as usize
279        } else {
280            self.payload
281        };
282        EffectOp::new(self.opcode, payload)
283    }
284}
285
286/// Pre-layout instruction with symbolic references.
287#[derive(Clone, Debug)]
288pub enum InstructionIR {
289    Match(MatchIR),
290    Call(CallIR),
291    Return(ReturnIR),
292    Trampoline(TrampolineIR),
293}
294
295impl InstructionIR {
296    /// Get the label where this instruction lives.
297    #[inline]
298    pub fn label(&self) -> Label {
299        match self {
300            Self::Match(m) => m.label,
301            Self::Call(c) => c.label,
302            Self::Return(r) => r.label,
303            Self::Trampoline(t) => t.label,
304        }
305    }
306
307    /// Compute instruction size in bytes (8, 16, 24, 32, 48, or 64).
308    pub fn size(&self) -> usize {
309        match self {
310            Self::Match(m) => m.size(),
311            Self::Call(_) | Self::Return(_) | Self::Trampoline(_) => 8,
312        }
313    }
314
315    /// Get all successor labels (for graph building).
316    pub fn successors(&self) -> Vec<Label> {
317        match self {
318            Self::Match(m) => m.successors.clone(),
319            Self::Call(c) => vec![c.next],
320            Self::Return(_) => vec![],
321            Self::Trampoline(t) => vec![t.next],
322        }
323    }
324
325    /// Resolve labels and serialize to bytecode bytes.
326    ///
327    /// - `lookup_member`: maps (field_name Symbol, field_type TypeId) to member index
328    /// - `get_member_base`: maps parent TypeId to member base index
329    pub fn resolve<F, G>(
330        &self,
331        map: &BTreeMap<Label, StepAddr>,
332        lookup_member: F,
333        get_member_base: G,
334    ) -> Vec<u8>
335    where
336        F: Fn(plotnik_core::Symbol, TypeId) -> Option<u16>,
337        G: Fn(TypeId) -> Option<u16>,
338    {
339        match self {
340            Self::Match(m) => m.resolve(map, lookup_member, get_member_base),
341            Self::Call(c) => c.resolve(map).to_vec(),
342            Self::Return(r) => r.resolve().to_vec(),
343            Self::Trampoline(t) => t.resolve(map).to_vec(),
344        }
345    }
346}
347
348/// Match instruction IR with symbolic successors.
349#[derive(Clone, Debug)]
350pub struct MatchIR {
351    /// Where this instruction lives.
352    pub label: Label,
353    /// Navigation command. `Epsilon` means pure control flow (no node check).
354    pub nav: Nav,
355    /// Node type constraint (Any = wildcard, Named/Anonymous for specific checks).
356    pub node_type: NodeTypeIR,
357    /// Field constraint (None = wildcard).
358    pub node_field: Option<NonZeroU16>,
359    /// Effects to execute before match attempt.
360    pub pre_effects: Vec<EffectIR>,
361    /// Fields that must NOT be present on the node.
362    pub neg_fields: Vec<u16>,
363    /// Effects to execute after successful match.
364    pub post_effects: Vec<EffectIR>,
365    /// Successor labels (empty = accept, 1 = linear, 2+ = branch).
366    pub successors: Vec<Label>,
367}
368
369impl MatchIR {
370    /// Create a terminal/accept state (empty successors).
371    pub fn terminal(label: Label) -> Self {
372        Self {
373            label,
374            nav: Nav::Epsilon,
375            node_type: NodeTypeIR::Any,
376            node_field: None,
377            pre_effects: vec![],
378            neg_fields: vec![],
379            post_effects: vec![],
380            successors: vec![],
381        }
382    }
383
384    /// Start building a match instruction at the given label.
385    pub fn at(label: Label) -> Self {
386        Self::terminal(label)
387    }
388
389    /// Create an epsilon transition (no node interaction) to a single successor.
390    pub fn epsilon(label: Label, next: Label) -> Self {
391        Self::at(label).next(next)
392    }
393
394    /// Set the navigation command.
395    pub fn nav(mut self, nav: Nav) -> Self {
396        self.nav = nav;
397        self
398    }
399
400    /// Set the node type constraint.
401    pub fn node_type(mut self, t: NodeTypeIR) -> Self {
402        self.node_type = t;
403        self
404    }
405
406    /// Set the field constraint.
407    pub fn node_field(mut self, f: impl Into<Option<NonZeroU16>>) -> Self {
408        self.node_field = f.into();
409        self
410    }
411
412    /// Add a negated field constraint.
413    pub fn neg_field(mut self, f: u16) -> Self {
414        self.neg_fields.push(f);
415        self
416    }
417
418    /// Add a pre-match effect.
419    pub fn pre_effect(mut self, e: EffectIR) -> Self {
420        self.pre_effects.push(e);
421        self
422    }
423
424    /// Add a post-match effect.
425    pub fn post_effect(mut self, e: EffectIR) -> Self {
426        self.post_effects.push(e);
427        self
428    }
429
430    /// Add multiple negated field constraints.
431    pub fn neg_fields(mut self, fields: impl IntoIterator<Item = u16>) -> Self {
432        self.neg_fields.extend(fields);
433        self
434    }
435
436    /// Add multiple pre-match effects.
437    pub fn pre_effects(mut self, effects: impl IntoIterator<Item = EffectIR>) -> Self {
438        self.pre_effects.extend(effects);
439        self
440    }
441
442    /// Add multiple post-match effects.
443    pub fn post_effects(mut self, effects: impl IntoIterator<Item = EffectIR>) -> Self {
444        self.post_effects.extend(effects);
445        self
446    }
447
448    /// Set a single successor.
449    pub fn next(mut self, s: Label) -> Self {
450        self.successors = vec![s];
451        self
452    }
453
454    /// Set multiple successors (for branches).
455    pub fn next_many(mut self, s: Vec<Label>) -> Self {
456        self.successors = s;
457        self
458    }
459
460    /// Compute instruction size in bytes.
461    pub fn size(&self) -> usize {
462        // Match8 can be used if: no effects, no neg_fields, and at most 1 successor
463        let can_use_match8 = self.pre_effects.is_empty()
464            && self.neg_fields.is_empty()
465            && self.post_effects.is_empty()
466            && self.successors.len() <= 1;
467
468        if can_use_match8 {
469            return 8;
470        }
471
472        // Extended match: count all payload slots
473        let slots = self.pre_effects.len()
474            + self.neg_fields.len()
475            + self.post_effects.len()
476            + self.successors.len();
477
478        select_match_opcode(slots).map(|op| op.size()).unwrap_or(64)
479    }
480
481    /// Resolve labels and serialize to bytecode bytes.
482    ///
483    /// - `lookup_member`: maps (field_name Symbol, field_type TypeId) to member index
484    /// - `get_member_base`: maps parent TypeId to member base index
485    pub fn resolve<F, G>(
486        &self,
487        map: &BTreeMap<Label, StepAddr>,
488        lookup_member: F,
489        get_member_base: G,
490    ) -> Vec<u8>
491    where
492        F: Fn(plotnik_core::Symbol, TypeId) -> Option<u16>,
493        G: Fn(TypeId) -> Option<u16>,
494    {
495        let can_use_match8 = self.pre_effects.is_empty()
496            && self.neg_fields.is_empty()
497            && self.post_effects.is_empty()
498            && self.successors.len() <= 1;
499
500        let opcode = if can_use_match8 {
501            Opcode::Match8
502        } else {
503            let slots_needed = self.pre_effects.len()
504                + self.neg_fields.len()
505                + self.post_effects.len()
506                + self.successors.len();
507            select_match_opcode(slots_needed).expect("instruction too large")
508        };
509
510        let size = opcode.size();
511        let mut bytes = vec![0u8; size];
512
513        // Header byte layout: segment(2) | node_kind(2) | opcode(4)
514        let (node_kind, node_type_val) = self.node_type.to_bytes();
515        bytes[0] = (node_kind << 4) | (opcode as u8); // segment 0
516        bytes[1] = self.nav.to_byte();
517        bytes[2..4].copy_from_slice(&node_type_val.to_le_bytes());
518        let node_field_val = self.node_field.map(|n| n.get()).unwrap_or(0);
519        bytes[4..6].copy_from_slice(&node_field_val.to_le_bytes());
520
521        if opcode == Opcode::Match8 {
522            let next = self
523                .successors
524                .first()
525                .map(|&l| l.resolve(map))
526                .unwrap_or(0);
527            bytes[6..8].copy_from_slice(&next.to_le_bytes());
528        } else {
529            let pre_count = self.pre_effects.len();
530            let neg_count = self.neg_fields.len();
531            let post_count = self.post_effects.len();
532            let succ_count = self.successors.len();
533
534            // Validate bit-packed field limits (3 bits for counts, 6 bits for successors)
535            assert!(
536                pre_count <= 7,
537                "pre_effects overflow: {pre_count} > 7 (use emit_match_with_cascade)"
538            );
539            assert!(neg_count <= 7, "neg_fields overflow: {neg_count} > 7");
540            assert!(post_count <= 7, "post_effects overflow: {post_count} > 7");
541            assert!(succ_count <= 63, "successors overflow: {succ_count} > 63");
542
543            let counts = ((pre_count as u16) << 13)
544                | ((neg_count as u16) << 10)
545                | ((post_count as u16) << 7)
546                | ((succ_count as u16) << 1);
547            bytes[6..8].copy_from_slice(&counts.to_le_bytes());
548
549            let mut offset = 8;
550            for effect in &self.pre_effects {
551                let resolved = effect.resolve(&lookup_member, &get_member_base);
552                bytes[offset..offset + 2].copy_from_slice(&resolved.to_bytes());
553                offset += 2;
554            }
555            for &field in &self.neg_fields {
556                bytes[offset..offset + 2].copy_from_slice(&field.to_le_bytes());
557                offset += 2;
558            }
559            for effect in &self.post_effects {
560                let resolved = effect.resolve(&lookup_member, &get_member_base);
561                bytes[offset..offset + 2].copy_from_slice(&resolved.to_bytes());
562                offset += 2;
563            }
564            for &label in &self.successors {
565                let addr = label.resolve(map);
566                bytes[offset..offset + 2].copy_from_slice(&addr.to_le_bytes());
567                offset += 2;
568            }
569        }
570
571        bytes
572    }
573
574    /// Check if this is an epsilon transition (no node interaction).
575    #[inline]
576    pub fn is_epsilon(&self) -> bool {
577        self.nav == Nav::Epsilon
578    }
579}
580
581impl From<MatchIR> for InstructionIR {
582    fn from(m: MatchIR) -> Self {
583        Self::Match(m)
584    }
585}
586
587/// Call instruction IR with symbolic target.
588#[derive(Clone, Debug)]
589pub struct CallIR {
590    /// Where this instruction lives.
591    pub label: Label,
592    /// Navigation to apply before jumping to target.
593    pub nav: Nav,
594    /// Field constraint (None = no constraint).
595    pub node_field: Option<NonZeroU16>,
596    /// Return address (where to continue after callee returns).
597    pub next: Label,
598    /// Callee entry point.
599    pub target: Label,
600}
601
602impl CallIR {
603    /// Create a call instruction with default nav (Stay) and no field constraint.
604    pub fn new(label: Label, target: Label, next: Label) -> Self {
605        Self {
606            label,
607            nav: Nav::Stay,
608            node_field: None,
609            next,
610            target,
611        }
612    }
613
614    /// Set the navigation command.
615    pub fn nav(mut self, nav: Nav) -> Self {
616        self.nav = nav;
617        self
618    }
619
620    /// Set the field constraint.
621    pub fn node_field(mut self, f: impl Into<Option<NonZeroU16>>) -> Self {
622        self.node_field = f.into();
623        self
624    }
625
626    /// Resolve labels and serialize to bytecode bytes.
627    pub fn resolve(&self, map: &BTreeMap<Label, StepAddr>) -> [u8; 8] {
628        Call::new(
629            self.nav,
630            self.node_field,
631            StepId::new(self.next.resolve(map)),
632            StepId::new(self.target.resolve(map)),
633        )
634        .to_bytes()
635    }
636}
637
638impl From<CallIR> for InstructionIR {
639    fn from(c: CallIR) -> Self {
640        Self::Call(c)
641    }
642}
643
644/// Return instruction IR.
645#[derive(Clone, Debug)]
646pub struct ReturnIR {
647    /// Where this instruction lives.
648    pub label: Label,
649}
650
651impl ReturnIR {
652    /// Create a return instruction at the given label.
653    pub fn new(label: Label) -> Self {
654        Self { label }
655    }
656
657    /// Serialize to bytecode bytes (no labels to resolve).
658    pub fn resolve(&self) -> [u8; 8] {
659        Return::new().to_bytes()
660    }
661}
662
663impl From<ReturnIR> for InstructionIR {
664    fn from(r: ReturnIR) -> Self {
665        Self::Return(r)
666    }
667}
668
669/// Trampoline instruction IR with symbolic return address.
670///
671/// Trampoline is like Call, but the target comes from VM context (external parameter)
672/// rather than being encoded in the instruction. Used for universal entry preamble.
673#[derive(Clone, Debug)]
674pub struct TrampolineIR {
675    /// Where this instruction lives.
676    pub label: Label,
677    /// Return address (where to continue after entrypoint returns).
678    pub next: Label,
679}
680
681impl TrampolineIR {
682    /// Create a trampoline instruction.
683    pub fn new(label: Label, next: Label) -> Self {
684        Self { label, next }
685    }
686
687    /// Resolve labels and serialize to bytecode bytes.
688    pub fn resolve(&self, map: &BTreeMap<Label, StepAddr>) -> [u8; 8] {
689        Trampoline::new(StepId::new(self.next.resolve(map))).to_bytes()
690    }
691}
692
693impl From<TrampolineIR> for InstructionIR {
694    fn from(t: TrampolineIR) -> Self {
695        Self::Trampoline(t)
696    }
697}
698
699/// Result of layout: maps labels to step addresses.
700#[derive(Clone, Debug)]
701pub struct LayoutResult {
702    /// Mapping from symbolic labels to concrete step addresses (raw u16).
703    pub(crate) label_to_step: BTreeMap<Label, StepAddr>,
704    /// Total number of steps (for header).
705    pub(crate) total_steps: u16,
706}
707
708impl LayoutResult {
709    /// Create a new layout result.
710    pub fn new(label_to_step: BTreeMap<Label, StepAddr>, total_steps: u16) -> Self {
711        Self {
712            label_to_step,
713            total_steps,
714        }
715    }
716
717    /// Create an empty layout result.
718    pub fn empty() -> Self {
719        Self {
720            label_to_step: BTreeMap::new(),
721            total_steps: 0,
722        }
723    }
724
725    pub fn label_to_step(&self) -> &BTreeMap<Label, StepAddr> {
726        &self.label_to_step
727    }
728    pub fn total_steps(&self) -> u16 {
729        self.total_steps
730    }
731}