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}