Merge pull request #206 from penberg/cleanup-vdbe

Cleanup VDBE module
This commit is contained in:
Pekka Enberg
2024-07-23 14:59:24 +03:00
committed by GitHub
8 changed files with 827 additions and 821 deletions

View File

@@ -5,7 +5,6 @@ mod io;
mod pager;
mod pseudo;
mod schema;
mod sorter;
mod sqlite3_ondisk;
mod storage;
mod translate;

View File

@@ -6,7 +6,7 @@ use crate::{
schema::{Schema, Table, Type},
translate::select::{ColumnInfo, Select, SrcTable},
util::normalize_ident,
vdbe::{BranchOffset, Insn, ProgramBuilder},
vdbe::{BranchOffset, Insn, builder::ProgramBuilder},
};
pub fn build_select<'a>(schema: &Schema, select: &'a ast::Select) -> Result<Select<'a>> {

View File

@@ -15,7 +15,7 @@ use crate::translate::where_clause::{
};
use crate::types::{OwnedRecord, OwnedValue};
use crate::util::normalize_ident;
use crate::vdbe::{BranchOffset, Insn, Program, ProgramBuilder};
use crate::vdbe::{BranchOffset, Insn, Program, builder::ProgramBuilder};
use anyhow::Result;
use expr::{build_select, maybe_apply_affinity, translate_expr};
use sqlite3_parser::ast::{self, Literal};

View File

@@ -5,7 +5,7 @@ use crate::{
translate::expr::{resolve_ident_qualified, resolve_ident_table, translate_expr},
function::SingleRowFunc,
translate::select::Select,
vdbe::{BranchOffset, Insn, ProgramBuilder},
vdbe::{BranchOffset, Insn, builder::ProgramBuilder},
};
const HARDCODED_CURSOR_LEFT_TABLE: usize = 0;

295
core/vdbe/builder.rs Normal file
View File

@@ -0,0 +1,295 @@
use super::{BranchOffset, CursorID, Insn, InsnReference, Program, Table};
pub struct ProgramBuilder {
next_free_register: usize,
next_free_label: BranchOffset,
next_free_cursor_id: usize,
insns: Vec<Insn>,
// for temporarily storing instructions that will be put after Transaction opcode
constant_insns: Vec<Insn>,
// Each label has a list of InsnReferences that must
// be resolved. Lists are indexed by: label.abs() - 1
unresolved_labels: Vec<Vec<InsnReference>>,
next_insn_label: Option<BranchOffset>,
// Cursors that are referenced by the program. Indexed by CursorID.
pub cursor_ref: Vec<(Option<String>, Option<Table>)>,
// List of deferred label resolutions. Each entry is a pair of (label, insn_reference).
deferred_label_resolutions: Vec<(BranchOffset, InsnReference)>,
}
impl ProgramBuilder {
pub fn new() -> Self {
Self {
next_free_register: 1,
next_free_label: 0,
next_free_cursor_id: 0,
insns: Vec::new(),
unresolved_labels: Vec::new(),
next_insn_label: None,
cursor_ref: Vec::new(),
constant_insns: Vec::new(),
deferred_label_resolutions: Vec::new(),
}
}
pub fn alloc_register(&mut self) -> usize {
let reg = self.next_free_register;
self.next_free_register += 1;
reg
}
pub fn alloc_registers(&mut self, amount: usize) -> usize {
let reg = self.next_free_register;
self.next_free_register += amount;
reg
}
pub fn next_free_register(&self) -> usize {
self.next_free_register
}
pub fn alloc_cursor_id(
&mut self,
table_identifier: Option<String>,
table: Option<Table>,
) -> usize {
let cursor = self.next_free_cursor_id;
self.next_free_cursor_id += 1;
self.cursor_ref.push((table_identifier, table));
assert!(self.cursor_ref.len() == self.next_free_cursor_id);
cursor
}
pub fn emit_insn(&mut self, insn: Insn) {
self.insns.push(insn);
if let Some(label) = self.next_insn_label {
self.next_insn_label = None;
self.resolve_label(label, (self.insns.len() - 1) as BranchOffset);
}
}
// Emit an instruction that will be put at the end of the program (after Transaction statement).
// This is useful for instructions that otherwise will be unnecessarily repeated in a loop.
// Example: In `SELECT * from users where name='John'`, it is unnecessary to set r[1]='John' as we SCAN users table.
// We could simply set it once before the SCAN started.
pub fn mark_last_insn_constant(&mut self) {
self.constant_insns.push(self.insns.pop().unwrap());
}
pub fn emit_constant_insns(&mut self) {
self.insns.append(&mut self.constant_insns);
}
pub fn emit_insn_with_label_dependency(&mut self, insn: Insn, label: BranchOffset) {
self.insns.push(insn);
self.add_label_dependency(label, (self.insns.len() - 1) as BranchOffset);
}
pub fn offset(&self) -> BranchOffset {
self.insns.len() as BranchOffset
}
pub fn allocate_label(&mut self) -> BranchOffset {
self.next_free_label -= 1;
self.unresolved_labels.push(Vec::new());
self.next_free_label
}
// Effectively a GOTO <next insn> without the need to emit an explicit GOTO instruction.
// Useful when you know you need to jump to "the next part", but the exact offset is unknowable
// at the time of emitting the instruction.
pub fn preassign_label_to_next_insn(&mut self, label: BranchOffset) {
self.next_insn_label = Some(label);
}
fn label_to_index(&self, label: BranchOffset) -> usize {
(label.abs() - 1) as usize
}
pub fn add_label_dependency(&mut self, label: BranchOffset, insn_reference: BranchOffset) {
assert!(insn_reference >= 0);
assert!(label < 0);
let label_index = self.label_to_index(label);
assert!(label_index < self.unresolved_labels.len());
let insn_reference = insn_reference as InsnReference;
let label_references = &mut self.unresolved_labels[label_index];
label_references.push(insn_reference);
}
pub fn defer_label_resolution(&mut self, label: BranchOffset, insn_reference: InsnReference) {
self.deferred_label_resolutions
.push((label, insn_reference));
}
pub fn resolve_label(&mut self, label: BranchOffset, to_offset: BranchOffset) {
assert!(label < 0);
assert!(to_offset >= 0);
let label_index = self.label_to_index(label);
assert!(
label_index < self.unresolved_labels.len(),
"Forbidden resolve of an unexistent label!"
);
let label_references = &mut self.unresolved_labels[label_index];
for insn_reference in label_references.iter() {
let insn = &mut self.insns[*insn_reference];
match insn {
Insn::Init { target_pc } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Eq {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Ne {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Lt {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Le {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Gt {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Ge {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::If {
reg: _reg,
target_pc,
null_reg: _,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::IfNot {
reg: _reg,
target_pc,
null_reg: _,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::RewindAwait {
cursor_id: _cursor_id,
pc_if_empty,
} => {
assert!(*pc_if_empty < 0);
*pc_if_empty = to_offset;
}
Insn::Goto { target_pc } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::DecrJumpZero {
reg: _reg,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::SorterNext {
cursor_id: _cursor_id,
pc_if_next,
} => {
assert!(*pc_if_next < 0);
*pc_if_next = to_offset;
}
Insn::SorterSort { pc_if_empty, .. } => {
assert!(*pc_if_empty < 0);
*pc_if_empty = to_offset;
}
Insn::NotNull {
reg: _reg,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::IfPos { target_pc, .. } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
_ => {
todo!("missing resolve_label for {:?}", insn);
}
}
}
label_references.clear();
}
// translate table to cursor id
pub fn resolve_cursor_id(
&self,
table_identifier: &str,
cursor_hint: Option<CursorID>,
) -> CursorID {
if let Some(cursor_hint) = cursor_hint {
return cursor_hint;
}
self.cursor_ref
.iter()
.position(|(t_ident, _)| {
t_ident
.as_ref()
.is_some_and(|ident| ident == table_identifier)
})
.unwrap()
}
pub fn resolve_deferred_labels(&mut self) {
for i in 0..self.deferred_label_resolutions.len() {
let (label, insn_reference) = self.deferred_label_resolutions[i];
self.resolve_label(label, insn_reference as BranchOffset);
}
self.deferred_label_resolutions.clear();
}
pub fn build(self) -> Program {
assert!(
self.deferred_label_resolutions.is_empty(),
"deferred_label_resolutions is not empty when build() is called, did you forget to call resolve_deferred_labels()?"
);
assert!(
self.constant_insns.is_empty(),
"constant_insns is not empty when build() is called, did you forget to call emit_constant_insns()?"
);
Program {
max_registers: self.next_free_register,
insns: self.insns,
cursor_ref: self.cursor_ref,
}
}
}

522
core/vdbe/explain.rs Normal file
View File

@@ -0,0 +1,522 @@
use super::{Program, InsnReference, Insn, OwnedValue};
use std::rc::Rc;
pub fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent: String) -> String {
let (opcode, p1, p2, p3, p4, p5, comment): (&str, i32, i32, i32, OwnedValue, u16, String) =
match insn {
Insn::Init { target_pc } => (
"Init",
0,
*target_pc as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("Start at {}", target_pc),
),
Insn::Add { lhs, rhs, dest } => (
"Add",
*lhs as i32,
*rhs as i32,
*dest as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}]=r[{}]+r[{}]", dest, lhs, rhs),
),
Insn::Null { dest } => (
"Null",
*dest as i32,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}]=NULL", dest),
),
Insn::NullRow { cursor_id } => (
"NullRow",
*cursor_id as i32,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("Set cursor {} to a (pseudo) NULL row", cursor_id),
),
Insn::NotNull { reg, target_pc } => (
"NotNull",
*reg as i32,
*target_pc as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}] -> {}", reg, target_pc),
),
Insn::IfPos {
reg,
target_pc,
decrement_by,
} => (
"IfPos",
*reg as i32,
*target_pc as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!(
"r[{}]>0 -> r[{}]-={}, goto {}",
reg, reg, decrement_by, target_pc
),
),
Insn::Eq {
lhs,
rhs,
target_pc,
} => (
"Eq",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]==r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::Ne {
lhs,
rhs,
target_pc,
} => (
"Ne",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]!=r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::Lt {
lhs,
rhs,
target_pc,
} => (
"Lt",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]<r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::Le {
lhs,
rhs,
target_pc,
} => (
"Le",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]<=r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::Gt {
lhs,
rhs,
target_pc,
} => (
"Gt",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]>r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::Ge {
lhs,
rhs,
target_pc,
} => (
"Ge",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]>=r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::If {
reg,
target_pc,
null_reg,
} => (
"If",
*reg as i32,
*target_pc as i32,
*null_reg as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}] goto {}", reg, target_pc),
),
Insn::IfNot {
reg,
target_pc,
null_reg,
} => (
"IfNot",
*reg as i32,
*target_pc as i32,
*null_reg as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if !r[{}] goto {}", reg, target_pc),
),
Insn::OpenReadAsync {
cursor_id,
root_page,
} => (
"OpenReadAsync",
*cursor_id as i32,
*root_page as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("root={}", root_page),
),
Insn::OpenReadAwait => (
"OpenReadAwait",
0,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::OpenPseudo {
cursor_id,
content_reg,
num_fields,
} => (
"OpenPseudo",
*cursor_id as i32,
*content_reg as i32,
*num_fields as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("{} columns in r[{}]", num_fields, content_reg),
),
Insn::RewindAsync { cursor_id } => (
"RewindAsync",
*cursor_id as i32,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::RewindAwait {
cursor_id,
pc_if_empty,
} => (
"RewindAwait",
*cursor_id as i32,
*pc_if_empty as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Column {
cursor_id,
column,
dest,
} => {
let (_, table) = &program.cursor_ref[*cursor_id];
(
"Column",
*cursor_id as i32,
*column as i32,
*dest as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!(
"r[{}]={}.{}",
dest,
table
.as_ref()
.map(|x| x.get_name())
.unwrap_or(format!("cursor {}", cursor_id).as_str()),
table
.as_ref()
.and_then(|x| x.column_index_to_name(*column))
.unwrap_or(format!("column {}", *column).as_str())
),
)
}
Insn::MakeRecord {
start_reg,
count,
dest_reg,
} => (
"MakeRecord",
*start_reg as i32,
*count as i32,
*dest_reg as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!(
"r[{}]=mkrec(r[{}..{}])",
dest_reg,
start_reg,
start_reg + count - 1,
),
),
Insn::ResultRow { start_reg, count } => (
"ResultRow",
*start_reg as i32,
*count as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
if *count == 1 {
format!("output=r[{}]", start_reg)
} else {
format!("output=r[{}..{}]", start_reg, start_reg + count - 1)
},
),
Insn::NextAsync { cursor_id } => (
"NextAsync",
*cursor_id as i32,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::NextAwait {
cursor_id,
pc_if_next,
} => (
"NextAwait",
*cursor_id as i32,
*pc_if_next as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Halt => (
"Halt",
0,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Transaction => (
"Transaction",
0,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Goto { target_pc } => (
"Goto",
0,
*target_pc as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Integer { value, dest } => (
"Integer",
*value as i32,
*dest as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}]={}", dest, value),
),
Insn::Real { value, dest } => (
"Real",
0,
*dest as i32,
0,
OwnedValue::Float(*value),
0,
format!("r[{}]={}", dest, value),
),
Insn::RealAffinity { register } => (
"RealAffinity",
*register as i32,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::String8 { value, dest } => (
"String8",
0,
*dest as i32,
0,
OwnedValue::Text(Rc::new(value.clone())),
0,
format!("r[{}]='{}'", dest, value),
),
Insn::RowId { cursor_id, dest } => (
"RowId",
*cursor_id as i32,
*dest as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!(
"r[{}]={}.rowid",
dest,
&program.cursor_ref[*cursor_id]
.1
.as_ref()
.map(|x| x.get_name())
.unwrap_or(format!("cursor {}", cursor_id).as_str())
),
),
Insn::DecrJumpZero { reg, target_pc } => (
"DecrJumpZero",
*reg as i32,
*target_pc as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if (--r[{}]==0) goto {}", reg, target_pc),
),
Insn::AggStep {
func,
acc_reg,
delimiter: _,
col,
} => (
"AggStep",
0,
*col as i32,
*acc_reg as i32,
OwnedValue::Text(Rc::new(func.to_string().into())),
0,
format!("accum=r[{}] step(r[{}])", *acc_reg, *col),
),
Insn::AggFinal { register, func } => (
"AggFinal",
0,
*register as i32,
0,
OwnedValue::Text(Rc::new(func.to_string().into())),
0,
format!("accum=r[{}]", *register),
),
Insn::SorterOpen {
cursor_id,
columns,
order,
} => {
let _p4 = String::new();
let to_print: Vec<String> = order
.values
.iter()
.map(|v| match v {
OwnedValue::Integer(i) => {
if *i == 0 {
"B".to_string()
} else {
"-B".to_string()
}
}
_ => unreachable!(),
})
.collect();
(
"SorterOpen",
*cursor_id as i32,
*columns as i32,
0,
OwnedValue::Text(Rc::new(format!("k({},{})", columns, to_print.join(",")))),
0,
format!("cursor={}", cursor_id),
)
}
Insn::SorterData {
cursor_id,
dest_reg,
pseudo_cursor,
} => (
"SorterData",
*cursor_id as i32,
*dest_reg as i32,
*pseudo_cursor as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}]=data", dest_reg),
),
Insn::SorterInsert {
cursor_id,
record_reg,
} => (
"SorterInsert",
*cursor_id as i32,
*record_reg as i32,
0,
OwnedValue::Integer(0),
0,
format!("key=r[{}]", record_reg),
),
Insn::SorterSort {
cursor_id,
pc_if_empty,
} => (
"SorterSort",
*cursor_id as i32,
*pc_if_empty as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::SorterNext {
cursor_id,
pc_if_next,
} => (
"SorterNext",
*cursor_id as i32,
*pc_if_next as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Function {
start_reg,
dest,
func,
} => (
"Function",
1,
*start_reg as i32,
*dest as i32,
OwnedValue::Text(Rc::new(func.to_string())),
0,
format!("r[{}]=func(r[{}..])", dest, start_reg),
),
};
format!(
"{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}",
addr,
&(indent + opcode),
p1,
p2,
p3,
p4.to_string(),
p5,
comment
)
}

View File

@@ -1,3 +1,7 @@
pub mod builder;
pub mod explain;
pub mod sorter;
use crate::btree::BTreeCursor;
use crate::function::{AggFunc, SingleRowFunc};
use crate::pager::Pager;
@@ -259,300 +263,6 @@ pub enum Insn {
// Index of insn in list of insns
type InsnReference = usize;
pub struct ProgramBuilder {
next_free_register: usize,
next_free_label: BranchOffset,
next_free_cursor_id: usize,
insns: Vec<Insn>,
// for temporarily storing instructions that will be put after Transaction opcode
constant_insns: Vec<Insn>,
// Each label has a list of InsnReferences that must
// be resolved. Lists are indexed by: label.abs() - 1
unresolved_labels: Vec<Vec<InsnReference>>,
next_insn_label: Option<BranchOffset>,
// Cursors that are referenced by the program. Indexed by CursorID.
pub cursor_ref: Vec<(Option<String>, Option<Table>)>,
// List of deferred label resolutions. Each entry is a pair of (label, insn_reference).
deferred_label_resolutions: Vec<(BranchOffset, InsnReference)>,
}
impl ProgramBuilder {
pub fn new() -> Self {
Self {
next_free_register: 1,
next_free_label: 0,
next_free_cursor_id: 0,
insns: Vec::new(),
unresolved_labels: Vec::new(),
next_insn_label: None,
cursor_ref: Vec::new(),
constant_insns: Vec::new(),
deferred_label_resolutions: Vec::new(),
}
}
pub fn alloc_register(&mut self) -> usize {
let reg = self.next_free_register;
self.next_free_register += 1;
reg
}
pub fn alloc_registers(&mut self, amount: usize) -> usize {
let reg = self.next_free_register;
self.next_free_register += amount;
reg
}
pub fn next_free_register(&self) -> usize {
self.next_free_register
}
pub fn alloc_cursor_id(
&mut self,
table_identifier: Option<String>,
table: Option<Table>,
) -> usize {
let cursor = self.next_free_cursor_id;
self.next_free_cursor_id += 1;
self.cursor_ref.push((table_identifier, table));
assert!(self.cursor_ref.len() == self.next_free_cursor_id);
cursor
}
pub fn emit_insn(&mut self, insn: Insn) {
self.insns.push(insn);
if let Some(label) = self.next_insn_label {
self.next_insn_label = None;
self.resolve_label(label, (self.insns.len() - 1) as BranchOffset);
}
}
// Emit an instruction that will be put at the end of the program (after Transaction statement).
// This is useful for instructions that otherwise will be unnecessarily repeated in a loop.
// Example: In `SELECT * from users where name='John'`, it is unnecessary to set r[1]='John' as we SCAN users table.
// We could simply set it once before the SCAN started.
pub fn mark_last_insn_constant(&mut self) {
self.constant_insns.push(self.insns.pop().unwrap());
}
pub fn emit_constant_insns(&mut self) {
self.insns.append(&mut self.constant_insns);
}
pub fn emit_insn_with_label_dependency(&mut self, insn: Insn, label: BranchOffset) {
self.insns.push(insn);
self.add_label_dependency(label, (self.insns.len() - 1) as BranchOffset);
}
pub fn offset(&self) -> BranchOffset {
self.insns.len() as BranchOffset
}
pub fn allocate_label(&mut self) -> BranchOffset {
self.next_free_label -= 1;
self.unresolved_labels.push(Vec::new());
self.next_free_label
}
// Effectively a GOTO <next insn> without the need to emit an explicit GOTO instruction.
// Useful when you know you need to jump to "the next part", but the exact offset is unknowable
// at the time of emitting the instruction.
pub fn preassign_label_to_next_insn(&mut self, label: BranchOffset) {
self.next_insn_label = Some(label);
}
fn label_to_index(&self, label: BranchOffset) -> usize {
(label.abs() - 1) as usize
}
pub fn add_label_dependency(&mut self, label: BranchOffset, insn_reference: BranchOffset) {
assert!(insn_reference >= 0);
assert!(label < 0);
let label_index = self.label_to_index(label);
assert!(label_index < self.unresolved_labels.len());
let insn_reference = insn_reference as InsnReference;
let label_references = &mut self.unresolved_labels[label_index];
label_references.push(insn_reference);
}
pub fn defer_label_resolution(&mut self, label: BranchOffset, insn_reference: InsnReference) {
self.deferred_label_resolutions
.push((label, insn_reference));
}
pub fn resolve_label(&mut self, label: BranchOffset, to_offset: BranchOffset) {
assert!(label < 0);
assert!(to_offset >= 0);
let label_index = self.label_to_index(label);
assert!(
label_index < self.unresolved_labels.len(),
"Forbidden resolve of an unexistent label!"
);
let label_references = &mut self.unresolved_labels[label_index];
for insn_reference in label_references.iter() {
let insn = &mut self.insns[*insn_reference];
match insn {
Insn::Init { target_pc } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Eq {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Ne {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Lt {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Le {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Gt {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::Ge {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::If {
reg: _reg,
target_pc,
null_reg: _,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::IfNot {
reg: _reg,
target_pc,
null_reg: _,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::RewindAwait {
cursor_id: _cursor_id,
pc_if_empty,
} => {
assert!(*pc_if_empty < 0);
*pc_if_empty = to_offset;
}
Insn::Goto { target_pc } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::DecrJumpZero {
reg: _reg,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::SorterNext {
cursor_id: _cursor_id,
pc_if_next,
} => {
assert!(*pc_if_next < 0);
*pc_if_next = to_offset;
}
Insn::SorterSort { pc_if_empty, .. } => {
assert!(*pc_if_empty < 0);
*pc_if_empty = to_offset;
}
Insn::NotNull {
reg: _reg,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
Insn::IfPos { target_pc, .. } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
_ => {
todo!("missing resolve_label for {:?}", insn);
}
}
}
label_references.clear();
}
// translate table to cursor id
pub fn resolve_cursor_id(
&self,
table_identifier: &str,
cursor_hint: Option<CursorID>,
) -> CursorID {
if let Some(cursor_hint) = cursor_hint {
return cursor_hint;
}
self.cursor_ref
.iter()
.position(|(t_ident, _)| {
t_ident
.as_ref()
.is_some_and(|ident| ident == table_identifier)
})
.unwrap()
}
pub fn resolve_deferred_labels(&mut self) {
for i in 0..self.deferred_label_resolutions.len() {
let (label, insn_reference) = self.deferred_label_resolutions[i];
self.resolve_label(label, insn_reference as BranchOffset);
}
self.deferred_label_resolutions.clear();
}
pub fn build(self) -> Program {
assert!(
self.deferred_label_resolutions.is_empty(),
"deferred_label_resolutions is not empty when build() is called, did you forget to call resolve_deferred_labels()?"
);
assert!(
self.constant_insns.is_empty(),
"constant_insns is not empty when build() is called, did you forget to call emit_constant_insns()?"
);
Program {
max_registers: self.next_free_register,
insns: self.insns,
cursor_ref: self.cursor_ref,
}
}
}
pub enum StepResult<'a> {
Done,
IO,
@@ -1218,7 +928,7 @@ impl Program {
_ => unreachable!(),
})
.collect();
let cursor = Box::new(crate::sorter::Sorter::new(order));
let cursor = Box::new(sorter::Sorter::new(order));
cursors.insert(*cursor_id, cursor);
state.pc += 1;
}
@@ -1414,534 +1124,14 @@ fn trace_insn(program: &Program, addr: InsnReference, insn: &Insn) {
if !log::log_enabled!(log::Level::Trace) {
return;
}
log::trace!("{}", insn_to_str(program, addr, insn, String::new()));
log::trace!("{}", explain::insn_to_str(program, addr, insn, String::new()));
}
fn print_insn(program: &Program, addr: InsnReference, insn: &Insn, indent: String) {
let s = insn_to_str(program, addr, insn, indent);
let s = explain::insn_to_str(program, addr, insn, indent);
println!("{}", s);
}
fn insn_to_str(program: &Program, addr: InsnReference, insn: &Insn, indent: String) -> String {
let (opcode, p1, p2, p3, p4, p5, comment): (&str, i32, i32, i32, OwnedValue, u16, String) =
match insn {
Insn::Init { target_pc } => (
"Init",
0,
*target_pc as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("Start at {}", target_pc),
),
Insn::Add { lhs, rhs, dest } => (
"Add",
*lhs as i32,
*rhs as i32,
*dest as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}]=r[{}]+r[{}]", dest, lhs, rhs),
),
Insn::Null { dest } => (
"Null",
*dest as i32,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}]=NULL", dest),
),
Insn::NullRow { cursor_id } => (
"NullRow",
*cursor_id as i32,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("Set cursor {} to a (pseudo) NULL row", cursor_id),
),
Insn::NotNull { reg, target_pc } => (
"NotNull",
*reg as i32,
*target_pc as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}] -> {}", reg, target_pc),
),
Insn::IfPos {
reg,
target_pc,
decrement_by,
} => (
"IfPos",
*reg as i32,
*target_pc as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!(
"r[{}]>0 -> r[{}]-={}, goto {}",
reg, reg, decrement_by, target_pc
),
),
Insn::Eq {
lhs,
rhs,
target_pc,
} => (
"Eq",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]==r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::Ne {
lhs,
rhs,
target_pc,
} => (
"Ne",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]!=r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::Lt {
lhs,
rhs,
target_pc,
} => (
"Lt",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]<r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::Le {
lhs,
rhs,
target_pc,
} => (
"Le",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]<=r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::Gt {
lhs,
rhs,
target_pc,
} => (
"Gt",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]>r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::Ge {
lhs,
rhs,
target_pc,
} => (
"Ge",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}]>=r[{}] goto {}", lhs, rhs, target_pc),
),
Insn::If {
reg,
target_pc,
null_reg,
} => (
"If",
*reg as i32,
*target_pc as i32,
*null_reg as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if r[{}] goto {}", reg, target_pc),
),
Insn::IfNot {
reg,
target_pc,
null_reg,
} => (
"IfNot",
*reg as i32,
*target_pc as i32,
*null_reg as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if !r[{}] goto {}", reg, target_pc),
),
Insn::OpenReadAsync {
cursor_id,
root_page,
} => (
"OpenReadAsync",
*cursor_id as i32,
*root_page as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("root={}", root_page),
),
Insn::OpenReadAwait => (
"OpenReadAwait",
0,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::OpenPseudo {
cursor_id,
content_reg,
num_fields,
} => (
"OpenPseudo",
*cursor_id as i32,
*content_reg as i32,
*num_fields as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("{} columns in r[{}]", num_fields, content_reg),
),
Insn::RewindAsync { cursor_id } => (
"RewindAsync",
*cursor_id as i32,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::RewindAwait {
cursor_id,
pc_if_empty,
} => (
"RewindAwait",
*cursor_id as i32,
*pc_if_empty as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Column {
cursor_id,
column,
dest,
} => {
let (_, table) = &program.cursor_ref[*cursor_id];
(
"Column",
*cursor_id as i32,
*column as i32,
*dest as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!(
"r[{}]={}.{}",
dest,
table
.as_ref()
.map(|x| x.get_name())
.unwrap_or(format!("cursor {}", cursor_id).as_str()),
table
.as_ref()
.and_then(|x| x.column_index_to_name(*column))
.unwrap_or(format!("column {}", *column).as_str())
),
)
}
Insn::MakeRecord {
start_reg,
count,
dest_reg,
} => (
"MakeRecord",
*start_reg as i32,
*count as i32,
*dest_reg as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!(
"r[{}]=mkrec(r[{}..{}])",
dest_reg,
start_reg,
start_reg + count - 1,
),
),
Insn::ResultRow { start_reg, count } => (
"ResultRow",
*start_reg as i32,
*count as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
if *count == 1 {
format!("output=r[{}]", start_reg)
} else {
format!("output=r[{}..{}]", start_reg, start_reg + count - 1)
},
),
Insn::NextAsync { cursor_id } => (
"NextAsync",
*cursor_id as i32,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::NextAwait {
cursor_id,
pc_if_next,
} => (
"NextAwait",
*cursor_id as i32,
*pc_if_next as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Halt => (
"Halt",
0,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Transaction => (
"Transaction",
0,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Goto { target_pc } => (
"Goto",
0,
*target_pc as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Integer { value, dest } => (
"Integer",
*value as i32,
*dest as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}]={}", dest, value),
),
Insn::Real { value, dest } => (
"Real",
0,
*dest as i32,
0,
OwnedValue::Float(*value),
0,
format!("r[{}]={}", dest, value),
),
Insn::RealAffinity { register } => (
"RealAffinity",
*register as i32,
0,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::String8 { value, dest } => (
"String8",
0,
*dest as i32,
0,
OwnedValue::Text(Rc::new(value.clone())),
0,
format!("r[{}]='{}'", dest, value),
),
Insn::RowId { cursor_id, dest } => (
"RowId",
*cursor_id as i32,
*dest as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!(
"r[{}]={}.rowid",
dest,
&program.cursor_ref[*cursor_id]
.1
.as_ref()
.map(|x| x.get_name())
.unwrap_or(format!("cursor {}", cursor_id).as_str())
),
),
Insn::DecrJumpZero { reg, target_pc } => (
"DecrJumpZero",
*reg as i32,
*target_pc as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("if (--r[{}]==0) goto {}", reg, target_pc),
),
Insn::AggStep {
func,
acc_reg,
delimiter: _,
col,
} => (
"AggStep",
0,
*col as i32,
*acc_reg as i32,
OwnedValue::Text(Rc::new(func.to_string().into())),
0,
format!("accum=r[{}] step(r[{}])", *acc_reg, *col),
),
Insn::AggFinal { register, func } => (
"AggFinal",
0,
*register as i32,
0,
OwnedValue::Text(Rc::new(func.to_string().into())),
0,
format!("accum=r[{}]", *register),
),
Insn::SorterOpen {
cursor_id,
columns,
order,
} => {
let _p4 = String::new();
let to_print: Vec<String> = order
.values
.iter()
.map(|v| match v {
OwnedValue::Integer(i) => {
if *i == 0 {
"B".to_string()
} else {
"-B".to_string()
}
}
_ => unreachable!(),
})
.collect();
(
"SorterOpen",
*cursor_id as i32,
*columns as i32,
0,
OwnedValue::Text(Rc::new(format!("k({},{})", columns, to_print.join(",")))),
0,
format!("cursor={}", cursor_id),
)
}
Insn::SorterData {
cursor_id,
dest_reg,
pseudo_cursor,
} => (
"SorterData",
*cursor_id as i32,
*dest_reg as i32,
*pseudo_cursor as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}]=data", dest_reg),
),
Insn::SorterInsert {
cursor_id,
record_reg,
} => (
"SorterInsert",
*cursor_id as i32,
*record_reg as i32,
0,
OwnedValue::Integer(0),
0,
format!("key=r[{}]", record_reg),
),
Insn::SorterSort {
cursor_id,
pc_if_empty,
} => (
"SorterSort",
*cursor_id as i32,
*pc_if_empty as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::SorterNext {
cursor_id,
pc_if_next,
} => (
"SorterNext",
*cursor_id as i32,
*pc_if_next as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
"".to_string(),
),
Insn::Function {
start_reg,
dest,
func,
} => (
"Function",
1,
*start_reg as i32,
*dest as i32,
OwnedValue::Text(Rc::new(func.to_string())),
0,
format!("r[{}]=func(r[{}..])", dest, start_reg),
),
};
format!(
"{:<4} {:<17} {:<4} {:<4} {:<4} {:<13} {:<2} {}",
addr,
&(indent + opcode),
p1,
p2,
p3,
p4.to_string(),
p5,
comment
)
}
fn get_indent_count(indent_count: usize, curr_insn: &Insn, prev_insn: Option<&Insn>) -> usize {
let indent_count = if let Some(insn) = prev_insn {
match insn {