Skip to main content

fsqlite_types/
opcode.rs

1/// VDBE (Virtual Database Engine) opcodes.
2///
3/// These correspond 1:1 to the upstream SQLite VDBE opcode set. Each opcode
4/// represents a single operation in the bytecode program that the VDBE
5/// executes. Opcodes are numbered sequentially; the specific numeric values
6/// match C SQLite for debugging/comparison purposes.
7///
8/// Reference: canonical upstream SQLite opcode definitions.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10#[repr(u8)]
11#[allow(clippy::enum_variant_names)]
12pub enum Opcode {
13    // === Control Flow ===
14    /// Jump to address P2.
15    Goto = 1,
16    /// Push return address, jump to P2.
17    Gosub = 2,
18    /// Pop return address, jump to it.
19    Return = 3,
20    /// Initialize coroutine. P1=coroutine reg, P2=jump on first entry.
21    InitCoroutine = 4,
22    /// End coroutine, jump to return address.
23    EndCoroutine = 5,
24    /// Yield control to/from coroutine.
25    Yield = 6,
26    /// Halt if register P3 is NULL.
27    HaltIfNull = 7,
28    /// Halt execution (with optional error).
29    Halt = 8,
30
31    // === Constants & Values ===
32    /// Set register P2 to integer value P1.
33    Integer = 9,
34    /// Set register P2 to 64-bit integer from P4.
35    Int64 = 10,
36    /// Set register P2 to real value from P4.
37    Real = 11,
38    /// Set register P2 to string P4 (zero-terminated).
39    String8 = 12,
40    /// Set register P2 to string of length P1 from P4.
41    String = 13,
42    /// Begin subroutine / set register P2 to NULL.
43    BeginSubrtn = 14,
44    /// Set registers P2..P2+P3-1 to NULL.
45    Null = 15,
46    /// Set register to soft NULL (for optimization).
47    SoftNull = 16,
48    /// Set register P2 to blob of length P1 from P4.
49    Blob = 17,
50    /// Set register P2 to the value of variable/parameter P1.
51    Variable = 18,
52
53    // === Register Operations ===
54    /// Move P3 registers from P1 to P2.
55    Move = 19,
56    /// Copy register P1 to P2 (and optionally more).
57    Copy = 20,
58    /// Shallow copy register P1 to P2.
59    SCopy = 21,
60    /// Copy integer value from P1 to P2.
61    IntCopy = 22,
62
63    // === Foreign Key ===
64    /// Check foreign key constraints.
65    FkCheck = 23,
66
67    // === Result ===
68    /// Output a row of P2 registers starting at P1.
69    ResultRow = 24,
70
71    // === Arithmetic & String ===
72    /// Concatenate P1 and P2, store in P3.
73    Concat = 25,
74    /// P3 = P2 + P1.
75    Add = 26,
76    /// P3 = P2 - P1.
77    Subtract = 27,
78    /// P3 = P2 * P1.
79    Multiply = 28,
80    /// P3 = P2 / P1.
81    Divide = 29,
82    /// P3 = P2 % P1.
83    Remainder = 30,
84
85    // === Collation ===
86    /// Set collation sequence for comparison.
87    CollSeq = 31,
88
89    // === Bitwise ===
90    /// P3 = P1 & P2.
91    BitAnd = 32,
92    /// P3 = P1 | P2.
93    BitOr = 33,
94    /// P3 = P2 << P1.
95    ShiftLeft = 34,
96    /// P3 = P2 >> P1.
97    ShiftRight = 35,
98
99    // === Type Conversion ===
100    /// Add integer P2 to register P1.
101    AddImm = 36,
102    /// Fail if P1 is not an integer; optionally jump to P2.
103    MustBeInt = 37,
104    /// If P1 is integer, convert to real in-place.
105    RealAffinity = 38,
106    /// Cast register P1 to type P2.
107    Cast = 39,
108
109    // === Comparison ===
110    /// Jump to P2 if P1 == P3.
111    Eq = 40,
112    /// Jump to P2 if P1 != P3.
113    Ne = 41,
114    /// Jump to P2 if P3 < P1.
115    Lt = 42,
116    /// Jump to P2 if P3 <= P1.
117    Le = 43,
118    /// Jump to P2 if P3 > P1.
119    Gt = 44,
120    /// Jump to P2 if P3 >= P1.
121    Ge = 45,
122    /// Jump if the previous comparison was Eq (for multi-column indexes).
123    ElseEq = 46,
124
125    // === Permutation & Compare ===
126    /// Set up permutation for subsequent Compare.
127    Permutation = 47,
128    /// Compare P1..P1+P3-1 with P2..P2+P3-1.
129    Compare = 48,
130
131    // === Branching ===
132    /// Jump to one of P1, P2, or P3 based on comparison result.
133    Jump = 49,
134    /// P3 = P1 AND P2 (three-valued logic).
135    And = 50,
136    /// P3 = P1 OR P2 (three-valued logic).
137    Or = 51,
138    /// Apply IS TRUE test.
139    IsTrue = 52,
140    /// P2 = NOT P1.
141    Not = 53,
142    /// P2 = ~P1 (bitwise not).
143    BitNot = 54,
144    /// Jump to P2 on first execution only.
145    Once = 55,
146    /// Jump to P2 if P1 is true (non-zero and non-NULL).
147    If = 56,
148    /// Jump to P2 if P1 is false (zero or NULL).
149    IfNot = 57,
150    /// Jump to P2 if P1 is NULL.
151    IsNull = 58,
152    /// Type check against P5 type mask; jump to P2 on mismatch.
153    IsType = 59,
154    /// P2 = 0 if any of P1, P2, P3 is NULL.
155    ZeroOrNull = 60,
156    /// Jump to P2 if P1 is not NULL.
157    NotNull = 61,
158    /// Jump to P2 if the current row of cursor P1 is NULL.
159    IfNullRow = 62,
160
161    // === Column Access ===
162    /// Extract byte offset of cursor.
163    Offset = 63,
164    /// Extract column P2 from cursor P1 into register P3.
165    Column = 64,
166    /// Type-check columns against declared types.
167    TypeCheck = 65,
168    /// Apply type affinity to P2 registers starting at P1.
169    Affinity = 66,
170
171    // === Record Building ===
172    /// Build a record from P1..P1+P2-1 registers into P3.
173    MakeRecord = 67,
174
175    // === Counting ===
176    /// Store the number of rows in cursor P1 into register P2.
177    Count = 68,
178
179    // === Transaction Control ===
180    /// Begin, release, or rollback a savepoint.
181    Savepoint = 69,
182    /// Set or clear auto-commit mode.
183    AutoCommit = 70,
184    /// Begin a transaction on database P1.
185    Transaction = 71,
186
187    // === Cookie Access ===
188    /// Read database cookie P3 from database P1 into register P2.
189    ReadCookie = 72,
190    /// Write P3 to database cookie P2 of database P1.
191    SetCookie = 73,
192
193    // === Cursor Operations ===
194    /// Reopen an index cursor (P1) if it's on a different root page.
195    ReopenIdx = 74,
196    /// Open a read cursor on table/index P2 in database P3.
197    OpenRead = 75,
198    /// Open a write cursor on table/index P2 in database P3.
199    OpenWrite = 76,
200    /// Open cursor P1 as a duplicate of cursor P2.
201    OpenDup = 77,
202    /// Open an ephemeral (temporary) table cursor.
203    OpenEphemeral = 78,
204    /// Open an auto-index ephemeral cursor.
205    OpenAutoindex = 79,
206    /// Open a sorter cursor.
207    SorterOpen = 80,
208    /// Test if sequence number has been used.
209    SequenceTest = 81,
210    /// Open a pseudo-table cursor (reads from a register).
211    OpenPseudo = 82,
212    /// Close cursor P1.
213    Close = 83,
214    /// Set the columns-used mask for cursor P1.
215    ColumnsUsed = 84,
216
217    // === Seek Operations ===
218    /// Seek cursor P1 to the largest entry less than P3.
219    SeekLT = 85,
220    /// Seek cursor P1 to the largest entry <= P3.
221    SeekLE = 86,
222    /// Seek cursor P1 to the smallest entry >= P3.
223    SeekGE = 87,
224    /// Seek cursor P1 to the smallest entry greater than P3.
225    SeekGT = 88,
226    /// Optimized seek-scan for small result sets.
227    SeekScan = 89,
228    /// Mark seek hit range for covering index optimization.
229    SeekHit = 90,
230    /// Jump to P2 if cursor P1 is not open.
231    IfNotOpen = 91,
232
233    // === Index Lookup ===
234    /// Like NotFound but with Bloom filter check.
235    IfNoHope = 92,
236    /// Jump to P2 if key P3 is NOT found (no conflict).
237    NoConflict = 93,
238    /// Jump to P2 if key P3 is NOT found in cursor P1.
239    NotFound = 94,
240    /// Jump to P2 if key P3 IS found in cursor P1.
241    Found = 95,
242
243    // === Rowid Seek ===
244    /// Seek cursor P1 to rowid P3; jump to P2 if not found.
245    SeekRowid = 96,
246    /// Jump to P2 if rowid P3 does NOT exist in cursor P1.
247    NotExists = 97,
248
249    // === Sequence & Rowid ===
250    /// Store next sequence value for cursor P1 into register P2.
251    Sequence = 98,
252    /// Generate a new unique rowid for cursor P1.
253    NewRowid = 99,
254
255    // === Insert & Delete ===
256    /// Insert record from P2 with rowid P3 into cursor P1.
257    Insert = 100,
258    /// Copy a cell directly from one cursor to another.
259    RowCell = 101,
260    /// Delete the current row of cursor P1.
261    Delete = 102,
262    /// Reset the change counter.
263    ResetCount = 103,
264
265    // === Sorter Operations ===
266    /// Compare sorter key.
267    SorterCompare = 104,
268    /// Read data from the sorter.
269    SorterData = 105,
270
271    // === Row Data ===
272    /// Copy the complete row data of cursor P1 into register P2.
273    RowData = 106,
274    /// Store the rowid of cursor P1 into register P2.
275    Rowid = 107,
276    /// Set cursor P1 to a NULL row.
277    NullRow = 108,
278
279    // === Cursor Navigation ===
280    /// Seek to end of table (no-op for reading, positions for append).
281    SeekEnd = 109,
282    /// Move cursor P1 to the last entry; jump to P2 if empty.
283    Last = 110,
284    /// Jump to P2 if table size is between P3 and P4.
285    IfSizeBetween = 111,
286    /// Sort (alias for SorterSort in some contexts).
287    SorterSort = 112,
288    /// Sort cursor P1.
289    Sort = 113,
290    /// Rewind cursor P1 to the first entry; jump to P2 if empty.
291    Rewind = 114,
292    /// Jump to P2 if cursor P1's table is empty.
293    IfEmpty = 115,
294
295    // === Iteration ===
296    /// Advance sorter to next entry.
297    SorterNext = 116,
298    /// Move cursor P1 to the previous entry; jump to P2 if done.
299    Prev = 117,
300    /// Move cursor P1 to the next entry; jump to P2 if done.
301    Next = 118,
302
303    // === Index Insert/Delete ===
304    /// Insert record P2 into index cursor P1.
305    IdxInsert = 119,
306    /// Insert into sorter.
307    SorterInsert = 120,
308    /// Delete from index cursor P1.
309    IdxDelete = 121,
310
311    // === Deferred Seek ===
312    /// Defer a seek on cursor P1 using the rowid from index cursor P2.
313    DeferredSeek = 122,
314    /// Extract rowid from index entry of cursor P1.
315    IdxRowid = 123,
316    /// Complete a previously deferred seek.
317    FinishSeek = 124,
318
319    // === Index Comparison ===
320    /// Jump to P2 if index key of P1 <= key.
321    IdxLE = 125,
322    /// Jump to P2 if index key of P1 > key.
323    IdxGT = 126,
324    /// Jump to P2 if index key of P1 < key.
325    IdxLT = 127,
326    /// Jump to P2 if index key of P1 >= key.
327    IdxGE = 128,
328
329    // === DDL Operations ===
330    /// Destroy (drop) a B-tree rooted at page P1.
331    Destroy = 129,
332    /// Clear (delete all rows from) a table or index.
333    Clear = 130,
334    /// Reset a sorter cursor.
335    ResetSorter = 131,
336    /// Allocate a new B-tree, store root page number in P2.
337    CreateBtree = 132,
338
339    // === Schema Operations ===
340    /// Execute an SQL statement stored in P4.
341    SqlExec = 133,
342    /// Parse the schema for database P1.
343    ParseSchema = 134,
344    /// Load analysis data for database P1.
345    LoadAnalysis = 135,
346    /// Drop a table.
347    DropTable = 136,
348    /// Drop an index.
349    DropIndex = 137,
350    /// Drop a trigger.
351    DropTrigger = 138,
352
353    // === Integrity Check ===
354    /// Run integrity check on database P1.
355    IntegrityCk = 139,
356
357    // === RowSet Operations ===
358    /// Add integer P2 to rowset P1.
359    RowSetAdd = 140,
360    /// Read next value from rowset P1 into P3; jump to P2 when empty.
361    RowSetRead = 141,
362    /// Test if P3 exists in rowset P1; jump to P2 if found.
363    RowSetTest = 142,
364
365    // === Trigger/Program ===
366    /// Call a trigger sub-program.
367    Program = 143,
368    /// Copy trigger parameter into register P2.
369    Param = 144,
370
371    // === FK Counters ===
372    /// Increment or decrement FK counter.
373    FkCounter = 145,
374    /// Jump to P2 if FK counter is zero.
375    FkIfZero = 146,
376
377    // === Memory/Counter ===
378    /// Set register P2 to max of P2 and register P1.
379    MemMax = 147,
380
381    // === Conditional Jumps ===
382    /// Jump to P2 if register P1 > 0; decrement by P3.
383    IfPos = 148,
384    /// Compute offset limit.
385    OffsetLimit = 149,
386    /// Jump to P2 if register P1 is not zero.
387    IfNotZero = 150,
388    /// Decrement P1, jump to P2 if result is zero.
389    DecrJumpZero = 151,
390
391    // === Aggregate Functions ===
392    /// Invoke aggregate inverse function.
393    AggInverse = 152,
394    /// Invoke aggregate step function.
395    AggStep = 153,
396    /// Step variant with different init semantics.
397    AggStep1 = 154,
398    /// Extract aggregate intermediate value.
399    AggValue = 155,
400    /// Finalize aggregate function.
401    AggFinal = 156,
402
403    // === WAL & Journal ===
404    /// Checkpoint the WAL for database P1.
405    Checkpoint = 157,
406    /// Set journal mode for database P1.
407    JournalMode = 158,
408
409    // === Vacuum ===
410    /// Vacuum the database.
411    Vacuum = 159,
412    /// Incremental vacuum step; jump to P2 if done.
413    IncrVacuum = 160,
414
415    // === Expiry & Locking ===
416    /// Mark prepared statement as expired.
417    Expire = 161,
418    /// Lock cursor P1.
419    CursorLock = 162,
420    /// Unlock cursor P1.
421    CursorUnlock = 163,
422    /// Lock table P2 in database P1.
423    TableLock = 164,
424
425    // === Virtual Table ===
426    /// Begin a virtual table transaction.
427    VBegin = 165,
428    /// Create a virtual table.
429    VCreate = 166,
430    /// Destroy a virtual table.
431    VDestroy = 167,
432    /// Open a virtual table cursor.
433    VOpen = 168,
434    /// Check virtual table integrity.
435    VCheck = 169,
436    /// Initialize IN constraint for virtual table.
437    VInitIn = 170,
438    /// Apply filter to virtual table cursor.
439    VFilter = 171,
440    /// Read column from virtual table cursor.
441    VColumn = 172,
442    /// Advance virtual table cursor.
443    VNext = 173,
444    /// Rename a virtual table.
445    VRename = 174,
446    /// Update/insert/delete on virtual table.
447    VUpdate = 175,
448
449    // === Page Count ===
450    /// Store database page count in register P2.
451    Pagecount = 176,
452    /// Set or read max page count.
453    MaxPgcnt = 177,
454
455    // === Functions ===
456    /// Call a pure (deterministic) function.
457    PureFunc = 178,
458    /// Call a function (possibly with side effects).
459    Function = 179,
460
461    // === Subtype Operations ===
462    /// Clear the subtype from register P1.
463    ClrSubtype = 180,
464    /// Get subtype of P1 into P2.
465    GetSubtype = 181,
466    /// Set subtype of P2 from P1.
467    SetSubtype = 182,
468
469    // === Bloom Filter ===
470    /// Add entry to Bloom filter.
471    FilterAdd = 183,
472    /// Test Bloom filter; jump to P2 if definitely not present.
473    Filter = 184,
474
475    // === Trace & Init ===
476    /// Trace/profile callback.
477    Trace = 185,
478    /// Initialize VDBE program; jump to P2.
479    Init = 186,
480
481    // === Hints & Debug ===
482    /// Provide cursor hint to storage engine.
483    CursorHint = 187,
484    /// Mark that this program can be aborted.
485    Abortable = 188,
486    /// Release register range.
487    ReleaseReg = 189,
488
489    // === Time-travel (SQL:2011 temporal queries) ===
490    /// Set time-travel snapshot on cursor P1.
491    /// P4 carries `TimeTravelCommitSeq(n)` or `TimeTravelTimestamp(ts)`.
492    /// Must immediately follow the `OpenRead` for the same cursor.
493    /// The cursor becomes read-only; DML/DDL through it returns an error.
494    SetSnapshot = 190,
495
496    // === Noop & FrankenSQLite extensions ===
497    /// No operation.
498    Noop = 191,
499    /// Evaluate a literal-pattern LIKE fast path directly against a register.
500    LikeConstFast = 192,
501    /// Count a run of equal first-column index keys, advancing the cursor.
502    CountIndexEqRun = 193,
503
504    // === Superinstructions (bd-perf V2.1) ===
505    /// Fused NewRowid + MakeRecord + Insert for sequential append.
506    ///
507    /// P1 = cursor number
508    /// P2 = first register of column values (same as MakeRecord P1)
509    /// P3 = number of columns (same as MakeRecord P2)
510    /// P5 = Insert flags (OE_* conflict mode in low nibble)
511    ///
512    /// Combines three opcodes into one dispatch:
513    /// 1. Allocate next sequential rowid (using cached last_alloc_rowid)
514    /// 2. Serialize column registers into record blob
515    /// 3. Append to B-tree via table_insert (prechecked absent, append mode)
516    ///
517    /// Guard conditions (codegen must verify before emitting):
518    /// - No secondary indexes on the table
519    /// - No triggers
520    /// - No foreign keys
521    /// - Default ABORT conflict mode (OE_ABORT = 2 in low nibble)
522    /// - No generated/stored columns
523    FusedAppendInsert = 194,
524
525    /// Fused OpenWrite + Last for cursor setup in INSERT programs.
526    /// P1 = cursor, P2 = root page number, P3 = column count, P5 = flags.
527    /// Opens a write cursor and navigates to the last entry for append.
528    FusedOpenWriteLast = 195,
529
530    /// Fused `Integer(p1=lit, p2=reg) + ResultRow(p1=reg, p2=1)` pair.
531    ///
532    /// Emits a single-column result row whose only value is the literal
533    /// integer `p1`, then clears register `p2` (matching the post-`ResultRow`
534    /// side effect of `take_reg_range`, which drains the source register).
535    ///
536    /// P1 = integer literal value
537    /// P2 = source register (written with the literal, then consumed)
538    /// P3 = unused (reserved; must be 0)
539    /// P4 = `P4::None`
540    /// P5 = 0
541    ///
542    /// Correctness contract: byte-equivalent to the unfused pair. Only the
543    /// peephole codegen pass emits this opcode; it MUST verify that the
544    /// immediately-following `ResultRow` consumes exactly the register
545    /// written by `Integer` and outputs exactly one column.
546    FusedLiteralResultRow = 196,
547
548    /// Compute `SUBSTR(column, 1, P4)` directly from a table cursor column.
549    ///
550    /// P1 = cursor number, P2 = logical column index, P3 = output register,
551    /// P4 = `Int(prefix_len)`, P5 = 0.
552    ///
553    /// The engine may fast-path storage TEXT/BLOB payload prefixes without
554    /// materializing the full column. Unsupported storage classes fall back to
555    /// the equivalent scalar `substr(value, 1, prefix_len)` behavior.
556    ColumnSubstrPrefix = 197,
557
558    /// Compute `octet_length(column)` from a table cursor record header.
559    ///
560    /// P1 = cursor number, P2 = logical column index, P3 = output register,
561    /// P4 = `None`, P5 = 0.
562    ///
563    /// TEXT and BLOB byte lengths are encoded by the record serial type, so a
564    /// storage cursor can answer this without expanding overflow payloads or
565    /// allocating the source value. Unsupported storage classes fall back to
566    /// the equivalent scalar `octet_length(value)` behavior.
567    ColumnOctetLength = 198,
568}
569
570impl Opcode {
571    /// Total number of opcodes defined.
572    pub const COUNT: usize = 199;
573
574    /// Get the opcode name as a static string slice.
575    #[allow(clippy::too_many_lines)]
576    pub const fn name(self) -> &'static str {
577        match self {
578            Self::Goto => "Goto",
579            Self::Gosub => "Gosub",
580            Self::Return => "Return",
581            Self::InitCoroutine => "InitCoroutine",
582            Self::EndCoroutine => "EndCoroutine",
583            Self::Yield => "Yield",
584            Self::HaltIfNull => "HaltIfNull",
585            Self::Halt => "Halt",
586            Self::Integer => "Integer",
587            Self::Int64 => "Int64",
588            Self::Real => "Real",
589            Self::String8 => "String8",
590            Self::String => "String",
591            Self::BeginSubrtn => "BeginSubrtn",
592            Self::Null => "Null",
593            Self::SoftNull => "SoftNull",
594            Self::Blob => "Blob",
595            Self::Variable => "Variable",
596            Self::Move => "Move",
597            Self::Copy => "Copy",
598            Self::SCopy => "SCopy",
599            Self::IntCopy => "IntCopy",
600            Self::FkCheck => "FkCheck",
601            Self::ResultRow => "ResultRow",
602            Self::Concat => "Concat",
603            Self::Add => "Add",
604            Self::Subtract => "Subtract",
605            Self::Multiply => "Multiply",
606            Self::Divide => "Divide",
607            Self::Remainder => "Remainder",
608            Self::CollSeq => "CollSeq",
609            Self::BitAnd => "BitAnd",
610            Self::BitOr => "BitOr",
611            Self::ShiftLeft => "ShiftLeft",
612            Self::ShiftRight => "ShiftRight",
613            Self::AddImm => "AddImm",
614            Self::MustBeInt => "MustBeInt",
615            Self::RealAffinity => "RealAffinity",
616            Self::Cast => "Cast",
617            Self::Eq => "Eq",
618            Self::Ne => "Ne",
619            Self::Lt => "Lt",
620            Self::Le => "Le",
621            Self::Gt => "Gt",
622            Self::Ge => "Ge",
623            Self::ElseEq => "ElseEq",
624            Self::Permutation => "Permutation",
625            Self::Compare => "Compare",
626            Self::Jump => "Jump",
627            Self::And => "And",
628            Self::Or => "Or",
629            Self::IsTrue => "IsTrue",
630            Self::Not => "Not",
631            Self::BitNot => "BitNot",
632            Self::Once => "Once",
633            Self::If => "If",
634            Self::IfNot => "IfNot",
635            Self::IsNull => "IsNull",
636            Self::IsType => "IsType",
637            Self::ZeroOrNull => "ZeroOrNull",
638            Self::NotNull => "NotNull",
639            Self::IfNullRow => "IfNullRow",
640            Self::Offset => "Offset",
641            Self::Column => "Column",
642            Self::TypeCheck => "TypeCheck",
643            Self::Affinity => "Affinity",
644            Self::MakeRecord => "MakeRecord",
645            Self::Count => "Count",
646            Self::Savepoint => "Savepoint",
647            Self::AutoCommit => "AutoCommit",
648            Self::Transaction => "Transaction",
649            Self::ReadCookie => "ReadCookie",
650            Self::SetCookie => "SetCookie",
651            Self::ReopenIdx => "ReopenIdx",
652            Self::OpenRead => "OpenRead",
653            Self::OpenWrite => "OpenWrite",
654            Self::OpenDup => "OpenDup",
655            Self::OpenEphemeral => "OpenEphemeral",
656            Self::OpenAutoindex => "OpenAutoindex",
657            Self::SorterOpen => "SorterOpen",
658            Self::SequenceTest => "SequenceTest",
659            Self::OpenPseudo => "OpenPseudo",
660            Self::Close => "Close",
661            Self::ColumnsUsed => "ColumnsUsed",
662            Self::SeekLT => "SeekLT",
663            Self::SeekLE => "SeekLE",
664            Self::SeekGE => "SeekGE",
665            Self::SeekGT => "SeekGT",
666            Self::SeekScan => "SeekScan",
667            Self::SeekHit => "SeekHit",
668            Self::IfNotOpen => "IfNotOpen",
669            Self::IfNoHope => "IfNoHope",
670            Self::NoConflict => "NoConflict",
671            Self::NotFound => "NotFound",
672            Self::Found => "Found",
673            Self::SeekRowid => "SeekRowid",
674            Self::NotExists => "NotExists",
675            Self::Sequence => "Sequence",
676            Self::NewRowid => "NewRowid",
677            Self::Insert => "Insert",
678            Self::RowCell => "RowCell",
679            Self::Delete => "Delete",
680            Self::ResetCount => "ResetCount",
681            Self::SorterCompare => "SorterCompare",
682            Self::SorterData => "SorterData",
683            Self::RowData => "RowData",
684            Self::Rowid => "Rowid",
685            Self::NullRow => "NullRow",
686            Self::SeekEnd => "SeekEnd",
687            Self::Last => "Last",
688            Self::IfSizeBetween => "IfSizeBetween",
689            Self::SorterSort => "SorterSort",
690            Self::Sort => "Sort",
691            Self::Rewind => "Rewind",
692            Self::IfEmpty => "IfEmpty",
693            Self::SorterNext => "SorterNext",
694            Self::Prev => "Prev",
695            Self::Next => "Next",
696            Self::IdxInsert => "IdxInsert",
697            Self::SorterInsert => "SorterInsert",
698            Self::IdxDelete => "IdxDelete",
699            Self::DeferredSeek => "DeferredSeek",
700            Self::IdxRowid => "IdxRowid",
701            Self::FinishSeek => "FinishSeek",
702            Self::IdxLE => "IdxLE",
703            Self::IdxGT => "IdxGT",
704            Self::IdxLT => "IdxLT",
705            Self::IdxGE => "IdxGE",
706            Self::Destroy => "Destroy",
707            Self::Clear => "Clear",
708            Self::ResetSorter => "ResetSorter",
709            Self::CreateBtree => "CreateBtree",
710            Self::SqlExec => "SqlExec",
711            Self::ParseSchema => "ParseSchema",
712            Self::LoadAnalysis => "LoadAnalysis",
713            Self::DropTable => "DropTable",
714            Self::DropIndex => "DropIndex",
715            Self::DropTrigger => "DropTrigger",
716            Self::IntegrityCk => "IntegrityCk",
717            Self::RowSetAdd => "RowSetAdd",
718            Self::RowSetRead => "RowSetRead",
719            Self::RowSetTest => "RowSetTest",
720            Self::Program => "Program",
721            Self::Param => "Param",
722            Self::FkCounter => "FkCounter",
723            Self::FkIfZero => "FkIfZero",
724            Self::MemMax => "MemMax",
725            Self::IfPos => "IfPos",
726            Self::OffsetLimit => "OffsetLimit",
727            Self::IfNotZero => "IfNotZero",
728            Self::DecrJumpZero => "DecrJumpZero",
729            Self::AggInverse => "AggInverse",
730            Self::AggStep => "AggStep",
731            Self::AggStep1 => "AggStep1",
732            Self::AggValue => "AggValue",
733            Self::AggFinal => "AggFinal",
734            Self::Checkpoint => "Checkpoint",
735            Self::JournalMode => "JournalMode",
736            Self::Vacuum => "Vacuum",
737            Self::IncrVacuum => "IncrVacuum",
738            Self::Expire => "Expire",
739            Self::CursorLock => "CursorLock",
740            Self::CursorUnlock => "CursorUnlock",
741            Self::TableLock => "TableLock",
742            Self::VBegin => "VBegin",
743            Self::VCreate => "VCreate",
744            Self::VDestroy => "VDestroy",
745            Self::VOpen => "VOpen",
746            Self::VCheck => "VCheck",
747            Self::VInitIn => "VInitIn",
748            Self::VFilter => "VFilter",
749            Self::VColumn => "VColumn",
750            Self::VNext => "VNext",
751            Self::VRename => "VRename",
752            Self::VUpdate => "VUpdate",
753            Self::Pagecount => "Pagecount",
754            Self::MaxPgcnt => "MaxPgcnt",
755            Self::PureFunc => "PureFunc",
756            Self::Function => "Function",
757            Self::ClrSubtype => "ClrSubtype",
758            Self::GetSubtype => "GetSubtype",
759            Self::SetSubtype => "SetSubtype",
760            Self::FilterAdd => "FilterAdd",
761            Self::Filter => "Filter",
762            Self::Trace => "Trace",
763            Self::Init => "Init",
764            Self::CursorHint => "CursorHint",
765            Self::Abortable => "Abortable",
766            Self::ReleaseReg => "ReleaseReg",
767            Self::SetSnapshot => "SetSnapshot",
768            Self::Noop => "Noop",
769            Self::LikeConstFast => "LikeConstFast",
770            Self::CountIndexEqRun => "CountIndexEqRun",
771            Self::FusedAppendInsert => "FusedAppendInsert",
772            Self::FusedOpenWriteLast => "FusedOpenWriteLast",
773            Self::FusedLiteralResultRow => "FusedLiteralResultRow",
774            Self::ColumnSubstrPrefix => "ColumnSubstrPrefix",
775            Self::ColumnOctetLength => "ColumnOctetLength",
776        }
777    }
778
779    /// Try to convert a u8 to an Opcode.
780    #[allow(clippy::too_many_lines)]
781    pub const fn from_byte(byte: u8) -> Option<Self> {
782        if byte == 0 || byte as usize >= Self::COUNT {
783            return None;
784        }
785        // SAFETY: All values 1..Opcode::COUNT are valid discriminants.
786        // We verified byte is in range above.
787        // Since the enum is repr(u8) with consecutive values, this is safe.
788        // However, since unsafe is forbidden, we use a match instead.
789        // For now, we accept the compile-time cost of a big match.
790        match byte {
791            1 => Some(Self::Goto),
792            2 => Some(Self::Gosub),
793            3 => Some(Self::Return),
794            4 => Some(Self::InitCoroutine),
795            5 => Some(Self::EndCoroutine),
796            6 => Some(Self::Yield),
797            7 => Some(Self::HaltIfNull),
798            8 => Some(Self::Halt),
799            9 => Some(Self::Integer),
800            10 => Some(Self::Int64),
801            11 => Some(Self::Real),
802            12 => Some(Self::String8),
803            13 => Some(Self::String),
804            14 => Some(Self::BeginSubrtn),
805            15 => Some(Self::Null),
806            16 => Some(Self::SoftNull),
807            17 => Some(Self::Blob),
808            18 => Some(Self::Variable),
809            19 => Some(Self::Move),
810            20 => Some(Self::Copy),
811            21 => Some(Self::SCopy),
812            22 => Some(Self::IntCopy),
813            23 => Some(Self::FkCheck),
814            24 => Some(Self::ResultRow),
815            25 => Some(Self::Concat),
816            26 => Some(Self::Add),
817            27 => Some(Self::Subtract),
818            28 => Some(Self::Multiply),
819            29 => Some(Self::Divide),
820            30 => Some(Self::Remainder),
821            31 => Some(Self::CollSeq),
822            32 => Some(Self::BitAnd),
823            33 => Some(Self::BitOr),
824            34 => Some(Self::ShiftLeft),
825            35 => Some(Self::ShiftRight),
826            36 => Some(Self::AddImm),
827            37 => Some(Self::MustBeInt),
828            38 => Some(Self::RealAffinity),
829            39 => Some(Self::Cast),
830            40 => Some(Self::Eq),
831            41 => Some(Self::Ne),
832            42 => Some(Self::Lt),
833            43 => Some(Self::Le),
834            44 => Some(Self::Gt),
835            45 => Some(Self::Ge),
836            46 => Some(Self::ElseEq),
837            47 => Some(Self::Permutation),
838            48 => Some(Self::Compare),
839            49 => Some(Self::Jump),
840            50 => Some(Self::And),
841            51 => Some(Self::Or),
842            52 => Some(Self::IsTrue),
843            53 => Some(Self::Not),
844            54 => Some(Self::BitNot),
845            55 => Some(Self::Once),
846            56 => Some(Self::If),
847            57 => Some(Self::IfNot),
848            58 => Some(Self::IsNull),
849            59 => Some(Self::IsType),
850            60 => Some(Self::ZeroOrNull),
851            61 => Some(Self::NotNull),
852            62 => Some(Self::IfNullRow),
853            63 => Some(Self::Offset),
854            64 => Some(Self::Column),
855            65 => Some(Self::TypeCheck),
856            66 => Some(Self::Affinity),
857            67 => Some(Self::MakeRecord),
858            68 => Some(Self::Count),
859            69 => Some(Self::Savepoint),
860            70 => Some(Self::AutoCommit),
861            71 => Some(Self::Transaction),
862            72 => Some(Self::ReadCookie),
863            73 => Some(Self::SetCookie),
864            74 => Some(Self::ReopenIdx),
865            75 => Some(Self::OpenRead),
866            76 => Some(Self::OpenWrite),
867            77 => Some(Self::OpenDup),
868            78 => Some(Self::OpenEphemeral),
869            79 => Some(Self::OpenAutoindex),
870            80 => Some(Self::SorterOpen),
871            81 => Some(Self::SequenceTest),
872            82 => Some(Self::OpenPseudo),
873            83 => Some(Self::Close),
874            84 => Some(Self::ColumnsUsed),
875            85 => Some(Self::SeekLT),
876            86 => Some(Self::SeekLE),
877            87 => Some(Self::SeekGE),
878            88 => Some(Self::SeekGT),
879            89 => Some(Self::SeekScan),
880            90 => Some(Self::SeekHit),
881            91 => Some(Self::IfNotOpen),
882            92 => Some(Self::IfNoHope),
883            93 => Some(Self::NoConflict),
884            94 => Some(Self::NotFound),
885            95 => Some(Self::Found),
886            96 => Some(Self::SeekRowid),
887            97 => Some(Self::NotExists),
888            98 => Some(Self::Sequence),
889            99 => Some(Self::NewRowid),
890            100 => Some(Self::Insert),
891            101 => Some(Self::RowCell),
892            102 => Some(Self::Delete),
893            103 => Some(Self::ResetCount),
894            104 => Some(Self::SorterCompare),
895            105 => Some(Self::SorterData),
896            106 => Some(Self::RowData),
897            107 => Some(Self::Rowid),
898            108 => Some(Self::NullRow),
899            109 => Some(Self::SeekEnd),
900            110 => Some(Self::Last),
901            111 => Some(Self::IfSizeBetween),
902            112 => Some(Self::SorterSort),
903            113 => Some(Self::Sort),
904            114 => Some(Self::Rewind),
905            115 => Some(Self::IfEmpty),
906            116 => Some(Self::SorterNext),
907            117 => Some(Self::Prev),
908            118 => Some(Self::Next),
909            119 => Some(Self::IdxInsert),
910            120 => Some(Self::SorterInsert),
911            121 => Some(Self::IdxDelete),
912            122 => Some(Self::DeferredSeek),
913            123 => Some(Self::IdxRowid),
914            124 => Some(Self::FinishSeek),
915            125 => Some(Self::IdxLE),
916            126 => Some(Self::IdxGT),
917            127 => Some(Self::IdxLT),
918            128 => Some(Self::IdxGE),
919            129 => Some(Self::Destroy),
920            130 => Some(Self::Clear),
921            131 => Some(Self::ResetSorter),
922            132 => Some(Self::CreateBtree),
923            133 => Some(Self::SqlExec),
924            134 => Some(Self::ParseSchema),
925            135 => Some(Self::LoadAnalysis),
926            136 => Some(Self::DropTable),
927            137 => Some(Self::DropIndex),
928            138 => Some(Self::DropTrigger),
929            139 => Some(Self::IntegrityCk),
930            140 => Some(Self::RowSetAdd),
931            141 => Some(Self::RowSetRead),
932            142 => Some(Self::RowSetTest),
933            143 => Some(Self::Program),
934            144 => Some(Self::Param),
935            145 => Some(Self::FkCounter),
936            146 => Some(Self::FkIfZero),
937            147 => Some(Self::MemMax),
938            148 => Some(Self::IfPos),
939            149 => Some(Self::OffsetLimit),
940            150 => Some(Self::IfNotZero),
941            151 => Some(Self::DecrJumpZero),
942            152 => Some(Self::AggInverse),
943            153 => Some(Self::AggStep),
944            154 => Some(Self::AggStep1),
945            155 => Some(Self::AggValue),
946            156 => Some(Self::AggFinal),
947            157 => Some(Self::Checkpoint),
948            158 => Some(Self::JournalMode),
949            159 => Some(Self::Vacuum),
950            160 => Some(Self::IncrVacuum),
951            161 => Some(Self::Expire),
952            162 => Some(Self::CursorLock),
953            163 => Some(Self::CursorUnlock),
954            164 => Some(Self::TableLock),
955            165 => Some(Self::VBegin),
956            166 => Some(Self::VCreate),
957            167 => Some(Self::VDestroy),
958            168 => Some(Self::VOpen),
959            169 => Some(Self::VCheck),
960            170 => Some(Self::VInitIn),
961            171 => Some(Self::VFilter),
962            172 => Some(Self::VColumn),
963            173 => Some(Self::VNext),
964            174 => Some(Self::VRename),
965            175 => Some(Self::VUpdate),
966            176 => Some(Self::Pagecount),
967            177 => Some(Self::MaxPgcnt),
968            178 => Some(Self::PureFunc),
969            179 => Some(Self::Function),
970            180 => Some(Self::ClrSubtype),
971            181 => Some(Self::GetSubtype),
972            182 => Some(Self::SetSubtype),
973            183 => Some(Self::FilterAdd),
974            184 => Some(Self::Filter),
975            185 => Some(Self::Trace),
976            186 => Some(Self::Init),
977            187 => Some(Self::CursorHint),
978            188 => Some(Self::Abortable),
979            189 => Some(Self::ReleaseReg),
980            190 => Some(Self::SetSnapshot),
981            191 => Some(Self::Noop),
982            192 => Some(Self::LikeConstFast),
983            193 => Some(Self::CountIndexEqRun),
984            194 => Some(Self::FusedAppendInsert),
985            195 => Some(Self::FusedOpenWriteLast),
986            196 => Some(Self::FusedLiteralResultRow),
987            197 => Some(Self::ColumnSubstrPrefix),
988            198 => Some(Self::ColumnOctetLength),
989            _ => None,
990        }
991    }
992
993    /// Whether this opcode is a jump instruction (has a P2 jump target).
994    pub const fn is_jump(self) -> bool {
995        matches!(
996            self,
997            Self::Goto
998                | Self::Gosub
999                | Self::InitCoroutine
1000                | Self::Yield
1001                | Self::HaltIfNull
1002                | Self::Once
1003                | Self::If
1004                | Self::IfNot
1005                | Self::IsNull
1006                | Self::IsType
1007                | Self::NotNull
1008                | Self::IfNullRow
1009                | Self::Jump
1010                | Self::Eq
1011                | Self::Ne
1012                | Self::Lt
1013                | Self::Le
1014                | Self::Gt
1015                | Self::Ge
1016                | Self::ElseEq
1017                | Self::SeekLT
1018                | Self::SeekLE
1019                | Self::SeekGE
1020                | Self::SeekGT
1021                | Self::SeekRowid
1022                | Self::NotExists
1023                | Self::IfNotOpen
1024                | Self::IfNoHope
1025                | Self::NoConflict
1026                | Self::NotFound
1027                | Self::Found
1028                | Self::Last
1029                | Self::Rewind
1030                | Self::IfEmpty
1031                | Self::IfSizeBetween
1032                | Self::Next
1033                | Self::Prev
1034                | Self::SorterNext
1035                | Self::SorterSort
1036                | Self::Sort
1037                | Self::IdxLE
1038                | Self::IdxGT
1039                | Self::IdxLT
1040                | Self::IdxGE
1041                | Self::RowSetRead
1042                | Self::RowSetTest
1043                | Self::Program
1044                | Self::FkIfZero
1045                | Self::IfPos
1046                | Self::IfNotZero
1047                | Self::DecrJumpZero
1048                | Self::IncrVacuum
1049                | Self::VFilter
1050                | Self::VNext
1051                | Self::Filter
1052                | Self::Init
1053        )
1054    }
1055}
1056
1057impl std::fmt::Display for Opcode {
1058    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1059        f.write_str(self.name())
1060    }
1061}
1062
1063/// A single VDBE instruction.
1064#[derive(Debug, Clone, PartialEq)]
1065pub struct VdbeOp {
1066    /// The opcode.
1067    pub opcode: Opcode,
1068    /// First operand (typically a register number or cursor index).
1069    pub p1: i32,
1070    /// Second operand (often a jump target address).
1071    pub p2: i32,
1072    /// Third operand.
1073    pub p3: i32,
1074    /// Fourth operand (polymorphic: string, function pointer, collation, etc.).
1075    pub p4: P4,
1076    /// Fifth operand (small flags, typically bit flags or type mask).
1077    pub p5: u16,
1078}
1079
1080/// Metadata about an index cursor for REPLACE conflict resolution.
1081///
1082/// Used by `native_replace_row` to clean up secondary index entries when
1083/// a table row is deleted due to REPLACE conflict resolution.
1084#[derive(Debug, Clone, PartialEq, Eq)]
1085pub struct IndexCursorMeta {
1086    /// Cursor ID of the index (typically table_cursor + 1, +2, ...).
1087    pub cursor_id: i32,
1088    /// Column indices (0-based positions in the table schema) that make up
1089    /// the index key. The index key is `(col[0], col[1], ..., rowid)`.
1090    pub column_indices: Vec<usize>,
1091}
1092
1093/// The P4 operand of a VDBE instruction.
1094///
1095/// P4 is a polymorphic operand that can hold different types depending on
1096/// the opcode.
1097#[derive(Debug, Clone, PartialEq)]
1098pub enum P4 {
1099    /// No P4 value.
1100    None,
1101    /// A 32-bit integer value.
1102    Int(i32),
1103    /// A 64-bit integer value.
1104    Int64(i64),
1105    /// A 64-bit float value.
1106    Real(f64),
1107    /// A string value.
1108    Str(String),
1109    /// A blob value.
1110    Blob(Vec<u8>),
1111    /// A collation sequence name.
1112    Collation(String),
1113    /// A function name (for Function/PureFunc opcodes).
1114    FuncName(String),
1115    /// A function name with an associated collation sequence for DISTINCT
1116    /// deduplication in aggregate functions (e.g. `COUNT(DISTINCT col)` where
1117    /// `col` has `COLLATE NOCASE`).
1118    FuncNameCollated(String, String),
1119    /// A table name.
1120    Table(String),
1121    /// An index name (for IdxInsert/IdxDelete opcodes).
1122    Index(String),
1123    /// An affinity string (one char per column).
1124    Affinity(String),
1125    /// A precomputed SQLite record header template for `MakeRecord`.
1126    PrecomputedHeader(crate::record::PrecomputedRecordHeader),
1127    /// Time-travel target: commit sequence for `FOR SYSTEM_TIME AS OF COMMITSEQ <n>`.
1128    TimeTravelCommitSeq(u64),
1129    /// Time-travel target: ISO-8601 timestamp for `FOR SYSTEM_TIME AS OF '<ts>'`.
1130    TimeTravelTimestamp(String),
1131}
1132
1133// ── VDBE Program Builder ────────────────────────────────────────────────────
1134//
1135// NOTE: These types intentionally live in `fsqlite-types` so that the planner
1136// (Layer 3) can generate VDBE bytecode without depending on `fsqlite-vdbe`
1137// (Layer 5). This is enforced by the workspace layering tests (bd-1wwc).
1138
1139use fsqlite_error::{FrankenError, Result};
1140use smallvec::SmallVec;
1141
1142/// An opaque handle representing a forward-reference label.
1143///
1144/// Labels allow codegen to emit jump instructions before the target address is
1145/// known. All labels MUST be resolved before execution begins; unresolved
1146/// labels are a codegen bug.
1147#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1148pub struct Label(u32);
1149
1150/// Internal tracking for label resolution.
1151#[derive(Debug)]
1152enum LabelState {
1153    /// Not yet resolved. Contains the indices of instructions whose `p2` field
1154    /// should be patched when the label is resolved.
1155    Unresolved(Vec<usize>),
1156    /// Resolved to a concrete instruction address.
1157    Resolved(i32),
1158}
1159
1160/// Sequential register allocator for the VDBE register file.
1161///
1162/// Registers are numbered starting at 1 (register 0 is reserved/unused),
1163/// matching C SQLite convention.
1164#[derive(Debug)]
1165pub struct RegisterAllocator {
1166    /// The next register number to allocate (starts at 1).
1167    next_reg: i32,
1168    /// Pool of returned temporary registers available for reuse.
1169    temp_pool: Vec<i32>,
1170}
1171
1172impl RegisterAllocator {
1173    /// Create a new allocator. First allocation returns register 1.
1174    #[must_use]
1175    pub fn new() -> Self {
1176        Self {
1177            next_reg: 1,
1178            temp_pool: Vec::new(),
1179        }
1180    }
1181
1182    /// Allocate a single persistent register.
1183    pub fn alloc_reg(&mut self) -> i32 {
1184        let reg = self.next_reg;
1185        self.next_reg += 1;
1186        reg
1187    }
1188
1189    /// Allocate a contiguous block of `n` persistent registers.
1190    ///
1191    /// Returns the first register number. The block spans `[result, result+n)`.
1192    pub fn alloc_regs(&mut self, n: i32) -> i32 {
1193        let first = self.next_reg;
1194        self.next_reg += n;
1195        first
1196    }
1197
1198    /// Allocate a temporary register (reuses from pool if available).
1199    pub fn alloc_temp(&mut self) -> i32 {
1200        self.temp_pool.pop().unwrap_or_else(|| {
1201            let reg = self.next_reg;
1202            self.next_reg += 1;
1203            reg
1204        })
1205    }
1206
1207    /// Return a temporary register to the reuse pool.
1208    pub fn free_temp(&mut self, reg: i32) {
1209        self.temp_pool.push(reg);
1210    }
1211
1212    /// The total number of registers allocated (high water mark).
1213    #[must_use]
1214    pub fn count(&self) -> i32 {
1215        self.next_reg - 1
1216    }
1217}
1218
1219impl Default for RegisterAllocator {
1220    fn default() -> Self {
1221        Self::new()
1222    }
1223}
1224
1225/// A VDBE bytecode program under construction.
1226///
1227/// Provides methods to emit instructions, create/resolve labels for forward
1228/// jumps, and allocate registers. Once construction is complete, call
1229/// [`finish`](Self::finish) to validate and extract the final instruction
1230/// sequence.
1231#[derive(Debug)]
1232pub struct ProgramBuilder {
1233    /// The instruction sequence.
1234    ops: SmallVec<[VdbeOp; 64]>,
1235    /// Label states (indexed by `Label.0`).
1236    labels: Vec<LabelState>,
1237    /// Register allocator.
1238    regs: RegisterAllocator,
1239}
1240
1241impl ProgramBuilder {
1242    /// Create a new empty program builder.
1243    #[must_use]
1244    pub fn new() -> Self {
1245        Self {
1246            ops: SmallVec::new(),
1247            labels: Vec::new(),
1248            regs: RegisterAllocator::new(),
1249        }
1250    }
1251
1252    // ── Instruction emission ────────────────────────────────────────────
1253
1254    /// Emit a single instruction and return its address (index in `ops`).
1255    pub fn emit(&mut self, op: VdbeOp) -> usize {
1256        let addr = self.ops.len();
1257        self.ops.push(op);
1258        addr
1259    }
1260
1261    /// Emit a simple instruction from parts.
1262    pub fn emit_op(&mut self, opcode: Opcode, p1: i32, p2: i32, p3: i32, p4: P4, p5: u16) -> usize {
1263        self.emit(VdbeOp {
1264            opcode,
1265            p1,
1266            p2,
1267            p3,
1268            p4,
1269            p5,
1270        })
1271    }
1272
1273    /// The current address (index of the next instruction to be emitted).
1274    #[must_use]
1275    pub fn current_addr(&self) -> usize {
1276        self.ops.len()
1277    }
1278
1279    /// Get a reference to the instruction at `addr`.
1280    #[must_use]
1281    pub fn op_at(&self, addr: usize) -> Option<&VdbeOp> {
1282        self.ops.get(addr)
1283    }
1284
1285    /// Get a mutable reference to the instruction at `addr`.
1286    #[must_use]
1287    pub fn op_at_mut(&mut self, addr: usize) -> Option<&mut VdbeOp> {
1288        self.ops.get_mut(addr)
1289    }
1290
1291    // ── Label system ────────────────────────────────────────────────────
1292
1293    /// Create a new label for forward-reference jumps.
1294    #[must_use]
1295    pub fn emit_label(&mut self) -> Label {
1296        let id = u32::try_from(self.labels.len()).expect("too many labels");
1297        self.labels.push(LabelState::Unresolved(Vec::new()));
1298        Label(id)
1299    }
1300
1301    /// Emit a jump instruction whose p2 target is a label (forward reference).
1302    ///
1303    /// The label's address will be patched into p2 when `resolve_label` is called.
1304    pub fn emit_jump_to_label(
1305        &mut self,
1306        opcode: Opcode,
1307        p1: i32,
1308        p3: i32,
1309        label: Label,
1310        p4: P4,
1311        p5: u16,
1312    ) -> usize {
1313        let addr = self.emit(VdbeOp {
1314            opcode,
1315            p1,
1316            p2: -1, // placeholder; will be patched
1317            p3,
1318            p4,
1319            p5,
1320        });
1321
1322        let state = self
1323            .labels
1324            .get_mut(usize::try_from(label.0).expect("label fits usize"))
1325            .expect("label must exist");
1326
1327        match state {
1328            LabelState::Unresolved(refs) => refs.push(addr),
1329            LabelState::Resolved(target) => {
1330                // Label already resolved; patch immediately.
1331                self.ops[addr].p2 = *target;
1332            }
1333        }
1334
1335        addr
1336    }
1337
1338    /// Resolve a label to the current address and patch all forward refs.
1339    pub fn resolve_label(&mut self, label: Label) {
1340        let addr = i32::try_from(self.current_addr()).expect("program too large");
1341        self.resolve_label_to(label, addr);
1342    }
1343
1344    /// Resolve a label to an explicit address (used for some control patterns).
1345    pub fn resolve_label_to(&mut self, label: Label, address: i32) {
1346        let idx = usize::try_from(label.0).expect("label fits usize");
1347        let state = self.labels.get_mut(idx).expect("label must exist");
1348
1349        match state {
1350            LabelState::Unresolved(refs) => {
1351                // Patch all references.
1352                for &ref_addr in refs.iter() {
1353                    self.ops[ref_addr].p2 = address;
1354                }
1355                *state = LabelState::Resolved(address);
1356            }
1357            LabelState::Resolved(_) => {
1358                // Idempotent: resolving twice is allowed as long as it's consistent.
1359                *state = LabelState::Resolved(address);
1360            }
1361        }
1362    }
1363
1364    // ── Register allocation ─────────────────────────────────────────────
1365
1366    /// Allocate a single persistent register.
1367    pub fn alloc_reg(&mut self) -> i32 {
1368        self.regs.alloc_reg()
1369    }
1370
1371    /// Allocate a contiguous block of persistent registers.
1372    pub fn alloc_regs(&mut self, n: i32) -> i32 {
1373        self.regs.alloc_regs(n)
1374    }
1375
1376    /// Allocate a temporary register (reusable).
1377    pub fn alloc_temp(&mut self) -> i32 {
1378        self.regs.alloc_temp()
1379    }
1380
1381    /// Return a temporary register to the pool.
1382    pub fn free_temp(&mut self, reg: i32) {
1383        self.regs.free_temp(reg);
1384    }
1385
1386    /// Total registers allocated (high water mark).
1387    #[must_use]
1388    pub fn register_count(&self) -> i32 {
1389        self.regs.count()
1390    }
1391
1392    // ── Peephole Passes (IMPL-13) ───────────────────────────────────────
1393
1394    /// Fuse `Integer(lit, reg) + ResultRow(reg, 1)` pairs into
1395    /// `FusedLiteralResultRow(lit, reg)` + `Noop`.
1396    ///
1397    /// Rewrites in-place so program counters, jump targets, and the label
1398    /// tables remain valid without rewiring. The `ResultRow` is replaced by a
1399    /// `Noop` rather than removed so no following instruction shifts.
1400    ///
1401    /// Conservative preconditions per fusion site:
1402    /// - The `Integer`'s target register equals the `ResultRow`'s start
1403    ///   register.
1404    /// - The `ResultRow` emits exactly one column (`p2 == 1`).
1405    /// - The `ResultRow` is NOT a resolved jump target from any prior jump
1406    ///   in this program (a mid-pair jump would otherwise skip the Integer
1407    ///   write and run `ResultRow` against an unrelated register value).
1408    /// - Neither instruction carries a non-`None` P4 payload (Integer/ResultRow
1409    ///   don't use P4 in their canonical form).
1410    /// - Both instructions carry P5 == 0 and P3 == 0.
1411    ///
1412    /// Returns the number of fusions performed.
1413    pub fn apply_fuse_literal_result_row(&mut self) -> usize {
1414        // Collect the set of resolved jump targets. Any address that is the
1415        // target of some jump instruction's `p2` is ineligible to be the
1416        // second half of a fusion pair.
1417        let mut jump_targets: std::collections::HashSet<i32> = std::collections::HashSet::new();
1418        for op in &self.ops {
1419            if op.opcode.is_jump() {
1420                jump_targets.insert(op.p2);
1421            }
1422        }
1423
1424        let mut fused = 0usize;
1425        let len = self.ops.len();
1426        let mut i = 0;
1427        while i + 1 < len {
1428            let is_int = matches!(self.ops[i].opcode, Opcode::Integer)
1429                && self.ops[i].p3 == 0
1430                && self.ops[i].p5 == 0
1431                && matches!(self.ops[i].p4, P4::None);
1432            let is_row = matches!(self.ops[i + 1].opcode, Opcode::ResultRow)
1433                && self.ops[i + 1].p2 == 1
1434                && self.ops[i + 1].p3 == 0
1435                && self.ops[i + 1].p5 == 0
1436                && matches!(self.ops[i + 1].p4, P4::None);
1437            let same_reg = is_int && is_row && self.ops[i].p2 == self.ops[i + 1].p1;
1438            let row_addr = i32::try_from(i + 1).ok();
1439            let row_is_target = row_addr.is_some_and(|a| jump_targets.contains(&a));
1440
1441            if same_reg && !row_is_target {
1442                let lit = self.ops[i].p1;
1443                let reg = self.ops[i].p2;
1444                self.ops[i] = VdbeOp {
1445                    opcode: Opcode::FusedLiteralResultRow,
1446                    p1: lit,
1447                    p2: reg,
1448                    p3: 0,
1449                    p4: P4::None,
1450                    p5: 0,
1451                };
1452                self.ops[i + 1] = VdbeOp {
1453                    opcode: Opcode::Noop,
1454                    p1: 0,
1455                    p2: 0,
1456                    p3: 0,
1457                    p4: P4::None,
1458                    p5: 0,
1459                };
1460                fused += 1;
1461                i += 2;
1462            } else {
1463                i += 1;
1464            }
1465        }
1466        fused
1467    }
1468
1469    // ── Finalization ────────────────────────────────────────────────────
1470
1471    /// Validate all labels are resolved and return the finished program.
1472    pub fn finish(self) -> Result<VdbeProgram> {
1473        // Check for unresolved labels.
1474        for (i, state) in self.labels.iter().enumerate() {
1475            if let LabelState::Unresolved(refs) = state {
1476                if !refs.is_empty() {
1477                    return Err(FrankenError::Internal(format!(
1478                        "unresolved label {i} referenced by {} instruction(s)",
1479                        refs.len()
1480                    )));
1481                }
1482            }
1483        }
1484
1485        Ok(VdbeProgram {
1486            ops: self.ops,
1487            register_count: self.regs.count(),
1488        })
1489    }
1490}
1491
1492impl Default for ProgramBuilder {
1493    fn default() -> Self {
1494        Self::new()
1495    }
1496}
1497
1498/// A finalized VDBE bytecode program ready for execution.
1499#[derive(Debug, Clone, PartialEq)]
1500pub struct VdbeProgram {
1501    /// The instruction sequence.
1502    ops: SmallVec<[VdbeOp; 64]>,
1503    /// Number of registers needed (high water mark from allocation).
1504    register_count: i32,
1505}
1506
1507impl VdbeProgram {
1508    /// The instruction sequence.
1509    #[must_use]
1510    pub fn ops(&self) -> &[VdbeOp] {
1511        &self.ops
1512    }
1513
1514    /// Number of instructions.
1515    #[must_use]
1516    pub fn len(&self) -> usize {
1517        self.ops.len()
1518    }
1519
1520    /// Whether the program is empty.
1521    #[must_use]
1522    pub fn is_empty(&self) -> bool {
1523        self.ops.is_empty()
1524    }
1525
1526    /// Number of registers required.
1527    #[must_use]
1528    pub fn register_count(&self) -> i32 {
1529        self.register_count
1530    }
1531
1532    /// Get the instruction at the given program counter.
1533    #[must_use]
1534    pub fn get(&self, pc: usize) -> Option<&VdbeOp> {
1535        self.ops.get(pc)
1536    }
1537
1538    /// Disassemble the program to a human-readable string.
1539    ///
1540    /// Output format matches SQLite's `EXPLAIN` output.
1541    #[must_use]
1542    pub fn disassemble(&self) -> String {
1543        use std::fmt::Write;
1544
1545        let mut out = std::string::String::with_capacity(self.ops.len() * 60);
1546        out.push_str("addr  opcode           p1    p2    p3    p4                 p5\n");
1547        out.push_str("----  ---------------  ----  ----  ----  -----------------  --\n");
1548
1549        for (addr, op) in self.ops.iter().enumerate() {
1550            let p4_str = match &op.p4 {
1551                P4::None => String::new(),
1552                P4::Int(v) => format!("(int){v}"),
1553                P4::Int64(v) => format!("(i64){v}"),
1554                P4::Real(v) => format!("(real){v}"),
1555                P4::Str(s) => format!("(str){s}"),
1556                P4::Blob(b) => format!("(blob)[{}B]", b.len()),
1557                P4::Collation(c) => format!("(coll){c}"),
1558                P4::FuncName(f) => format!("(func){f}"),
1559                P4::FuncNameCollated(f, c) => format!("(func){f} coll={c}"),
1560                P4::Table(t) => format!("(tbl){t}"),
1561                P4::Index(i) => format!("(idx){i}"),
1562                P4::Affinity(a) => format!("(aff){a}"),
1563                P4::PrecomputedHeader(header) => format!("(hdr)[{}B]", header.template.len()),
1564                P4::TimeTravelCommitSeq(seq) => format!("(tt-seq){seq}"),
1565                P4::TimeTravelTimestamp(ts) => format!("(tt-ts){ts}"),
1566            };
1567
1568            writeln!(
1569                &mut out,
1570                "{addr:<4}  {:<15}  {:<4}  {:<4}  {:<4}  {:<17}  {:<2}",
1571                op.opcode.name(),
1572                op.p1,
1573                op.p2,
1574                op.p3,
1575                p4_str,
1576                op.p5,
1577            )
1578            .expect("write to string");
1579        }
1580
1581        out
1582    }
1583}
1584
1585#[cfg(test)]
1586#[allow(clippy::approx_constant)]
1587mod tests {
1588    use super::*;
1589    use std::collections::HashSet;
1590
1591    #[test]
1592    fn opcode_count() {
1593        assert_eq!(Opcode::COUNT, 198);
1594    }
1595
1596    #[test]
1597    fn opcode_name_roundtrip() {
1598        // Spot check a few opcodes
1599        assert_eq!(Opcode::Goto.name(), "Goto");
1600        assert_eq!(Opcode::Halt.name(), "Halt");
1601        assert_eq!(Opcode::Insert.name(), "Insert");
1602        assert_eq!(Opcode::Delete.name(), "Delete");
1603        assert_eq!(Opcode::ResultRow.name(), "ResultRow");
1604        assert_eq!(Opcode::Noop.name(), "Noop");
1605    }
1606
1607    #[test]
1608    fn opcode_from_byte() {
1609        assert_eq!(Opcode::from_byte(0), None);
1610        assert_eq!(Opcode::from_byte(1), Some(Opcode::Goto));
1611        assert_eq!(Opcode::from_byte(8), Some(Opcode::Halt));
1612        assert_eq!(Opcode::from_byte(190), Some(Opcode::SetSnapshot));
1613        assert_eq!(Opcode::from_byte(191), Some(Opcode::Noop));
1614        assert_eq!(Opcode::from_byte(192), Some(Opcode::LikeConstFast));
1615        assert_eq!(Opcode::from_byte(196), Some(Opcode::FusedLiteralResultRow));
1616        assert_eq!(Opcode::from_byte(197), Some(Opcode::ColumnSubstrPrefix));
1617        assert_eq!(Opcode::from_byte(198), Some(Opcode::ColumnOctetLength));
1618        assert_eq!(Opcode::from_byte(199), None);
1619        assert_eq!(Opcode::from_byte(255), None);
1620    }
1621
1622    #[test]
1623    fn opcode_from_byte_exhaustive() {
1624        // Every assigned opcode byte should produce Some.
1625        for i in 1..Opcode::COUNT as u8 {
1626            assert!(
1627                Opcode::from_byte(i).is_some(),
1628                "from_byte({i}) returned None"
1629            );
1630        }
1631    }
1632
1633    #[test]
1634    fn test_opcode_distinct_u8_values() {
1635        let mut encoded = HashSet::new();
1636        for byte in 1..Opcode::COUNT as u8 {
1637            let opcode = Opcode::from_byte(byte).expect("opcode byte must decode");
1638            let inserted = encoded.insert(opcode as u8);
1639            assert!(inserted, "duplicate opcode byte value for {:?}", opcode);
1640        }
1641
1642        assert_eq!(
1643            encoded.len(),
1644            Opcode::COUNT - 1,
1645            "every opcode must map to a unique byte"
1646        );
1647    }
1648
1649    #[test]
1650    fn opcode_display() {
1651        assert_eq!(Opcode::Goto.to_string(), "Goto");
1652        assert_eq!(Opcode::Init.to_string(), "Init");
1653    }
1654
1655    #[test]
1656    fn opcode_is_jump() {
1657        assert!(Opcode::Goto.is_jump());
1658        assert!(Opcode::If.is_jump());
1659        assert!(Opcode::IfNot.is_jump());
1660        assert!(Opcode::Eq.is_jump());
1661        assert!(Opcode::Next.is_jump());
1662        assert!(Opcode::Rewind.is_jump());
1663        assert!(Opcode::Init.is_jump());
1664
1665        assert!(!Opcode::Integer.is_jump());
1666        assert!(!Opcode::Add.is_jump());
1667        assert!(!Opcode::Insert.is_jump());
1668        assert!(!Opcode::Noop.is_jump());
1669        assert!(!Opcode::ResultRow.is_jump());
1670    }
1671
1672    #[test]
1673    fn vdbe_op_basic() {
1674        let op = VdbeOp {
1675            opcode: Opcode::Integer,
1676            p1: 42,
1677            p2: 1,
1678            p3: 0,
1679            p4: P4::None,
1680            p5: 0,
1681        };
1682        assert_eq!(op.opcode, Opcode::Integer);
1683        assert_eq!(op.p1, 42);
1684    }
1685
1686    #[test]
1687    fn p4_variants() {
1688        let p4 = P4::Int(42);
1689        assert_eq!(p4, P4::Int(42));
1690
1691        let p4 = P4::Str("hello".to_owned());
1692        assert_eq!(p4, P4::Str("hello".to_owned()));
1693
1694        let p4 = P4::Real(3.14);
1695        assert_eq!(p4, P4::Real(3.14));
1696    }
1697}