Merge 'VDBE with indirect function dispatch' from Pere Diaz Bou

This PR is unapologetically stolen from @vmg's implementation in Vitess
implemented here https://github.com/vitessio/vitess/pull/12369. If you
want a more in depth explanation of how this works you can read the
[blog post he carefully
wrote](https://planetscale.com/blog/faster-interpreters-in-go-catching-
up-with-cpp).
In limbo we have a huge problem with [register
spilling](https://en.wikipedia.org/wiki/Register_allocation), this can
be easily observed with the prolog of `Program::step` before:
```llvm
start:
    %e.i.i304.i = alloca [0 x i8], align 8
    %formatter.i305.i = alloca [64 x i8], align 8
    %buf.i306.i = alloca [24 x i8], align 8
    %formatter.i259.i = alloca [64 x i8], align 8
    ..................... these are repeated for hundreds of lines
.....................
    %formatter.i52.i = alloca [64 x i8], align 8
    %buf.i53.i = alloca [24 x i8], align 8
    %formatter.i.i = alloca [64 x i8], align 8
    %buf.i.i = alloca [24 x i8], align 8
    %_87.i = alloca [48 x i8], align 8
    %_82.i = alloca [24 x i8], align 8
    %_73.i = alloca [24 x i8], align 8
    %_66.i8446 = alloca [24 x i8], align 8
    %_57.i = alloca [24 x i8], align 8
    %_48.i = alloca [24 x i8], align 8
```
After these changes we completely remove the need of register spilling
(yes that is the complete prolog):
```llvm
start:
    %self1 = alloca [80 x i8], align 8
    %pager = alloca [8 x i8], align 8
    %mv_store = alloca [8 x i8], align 8
    store ptr %0, ptr %mv_store, align 8
    store ptr %1, ptr %pager, align 8
    %2 = getelementptr inbounds i8, ptr %state, i64 580
    %3 = getelementptr inbounds i8, ptr %state, i64 576
    %4 = getelementptr inbounds i8, ptr %self, i64 16
    %5 = getelementptr inbounds i8, ptr %self, i64 8
    %6 = getelementptr inbounds i8, ptr %self1, i64 8
    br label %bb1, !dbg !286780
```
When it comes to branch prediction, we don't really fix a lot because
thankfully rust already compiles `match` expressions to a jump table:
```llvm
%insn = getelementptr inbounds [0 x %"vdbe::insn::Insn"], ptr %self657,
i64 0, i64 %index, !dbg !249527
%332 = load i8, ptr %insn, align 8, !dbg !249528, !range !38291,
!noundef !14
switch i8 %332, label %default.unreachable26674 [
    i8 0, label %bb111
    i8 1, label %bb101
    i8 2, label %bb100
    i8 3, label %bb110
    ...
    i8 104, label %bb5
    i8 105, label %bb16
    i8 106, label %bb14
], !dbg !249530
```
Some results
----
```
function dispatch:
Execute `SELECT 1`/limbo_execute_select_1
                        time:   [29.498 ns 29.548 ns 29.601 ns]
                        change: [-3.6125% -3.3592% -3.0804%] (p = 0.00 <
0.05)

main:
Execute `SELECT 1`/limbo_execute_select_1
                        time:   [33.789 ns 33.832 ns 33.878 ns]
```

Closes #1233
This commit is contained in:
Pekka Enberg
2025-04-02 17:01:58 +03:00
4 changed files with 6126 additions and 4998 deletions

View File

@@ -16,14 +16,14 @@ use crate::{
Connection, VirtualTable,
};
use super::{BranchOffset, CursorID, Insn, InsnReference, Program};
use super::{BranchOffset, CursorID, Insn, InsnFunction, InsnReference, Program};
#[allow(dead_code)]
pub struct ProgramBuilder {
next_free_register: usize,
next_free_cursor_id: usize,
insns: Vec<Insn>,
insns: Vec<(Insn, InsnFunction)>,
// for temporarily storing instructions that will be put after Transaction opcode
constant_insns: Vec<Insn>,
constant_insns: Vec<(Insn, InsnFunction)>,
// Vector of labels which must be assigned to next emitted instruction
next_insn_labels: Vec<BranchOffset>,
// Cursors that are referenced by the program. Indexed by CursorID.
@@ -127,7 +127,8 @@ impl ProgramBuilder {
self.label_to_resolved_offset[label.to_label_value() as usize] =
Some(self.insns.len() as InsnReference);
}
self.insns.push(insn);
let function = insn.to_function();
self.insns.push((insn, function));
}
pub fn emit_string8(&mut self, value: String, dest: usize) {
@@ -253,7 +254,7 @@ impl ProgramBuilder {
);
}
};
for insn in self.insns.iter_mut() {
for (insn, _) in self.insns.iter_mut() {
match insn {
Insn::Init { target_pc } => {
resolve(target_pc, "Init");

5926
core/vdbe/execute.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,8 @@
use std::num::NonZero;
use super::{cast_text_to_numeric, AggFunc, BranchOffset, CursorID, FuncCtx, PageIdx};
use super::{
cast_text_to_numeric, execute, AggFunc, BranchOffset, CursorID, FuncCtx, InsnFunction, PageIdx,
};
use crate::storage::wal::CheckpointMode;
use crate::types::{OwnedValue, Record};
use limbo_macros::Description;
@@ -1140,6 +1142,171 @@ pub fn exec_or(lhs: &OwnedValue, rhs: &OwnedValue) -> OwnedValue {
}
}
impl Insn {
pub fn to_function(&self) -> InsnFunction {
match self {
Insn::Init { .. } => execute::execute_insn_init,
Insn::Null { .. } => execute::execute_insn_null,
Insn::NullRow { .. } => execute::execute_insn_null_row,
Insn::Add { .. } => execute::execute_insn_add,
Insn::Subtract { .. } => execute::execute_insn_subtract,
Insn::Multiply { .. } => execute::execute_insn_multiply,
Insn::Divide { .. } => execute::execute_insn_divide,
Insn::Compare { .. } => execute::execute_insn_compare,
Insn::BitAnd { .. } => execute::execute_insn_bit_and,
Insn::BitOr { .. } => execute::execute_insn_bit_or,
Insn::BitNot { .. } => execute::execute_insn_bit_not,
Insn::Checkpoint { .. } => execute::execute_insn_checkpoint,
Insn::Remainder { .. } => execute::execute_insn_remainder,
Insn::Jump { .. } => execute::execute_insn_jump,
Insn::Move { .. } => execute::execute_insn_move,
Insn::IfPos { .. } => execute::execute_insn_if_pos,
Insn::NotNull { .. } => execute::execute_insn_not_null,
Insn::Eq { .. } => execute::execute_insn_eq,
Insn::Ne { .. } => execute::execute_insn_ne,
Insn::Lt { .. } => execute::execute_insn_lt,
Insn::Le { .. } => execute::execute_insn_le,
Insn::Gt { .. } => execute::execute_insn_gt,
Insn::Ge { .. } => execute::execute_insn_ge,
Insn::If { .. } => execute::execute_insn_if,
Insn::IfNot { .. } => execute::execute_insn_if_not,
Insn::OpenReadAsync { .. } => execute::execute_insn_open_read_async,
Insn::OpenReadAwait => execute::execute_insn_open_read_await,
Insn::VOpenAsync { .. } => execute::execute_insn_vopen_async,
Insn::VOpenAwait => execute::execute_insn_vopen_await,
Insn::VCreate { .. } => execute::execute_insn_vcreate,
Insn::VFilter { .. } => execute::execute_insn_vfilter,
Insn::VColumn { .. } => execute::execute_insn_vcolumn,
Insn::VUpdate { .. } => execute::execute_insn_vupdate,
Insn::VNext { .. } => execute::execute_insn_vnext,
Insn::OpenPseudo { .. } => execute::execute_insn_open_pseudo,
Insn::RewindAsync { .. } => execute::execute_insn_rewind_async,
Insn::RewindAwait { .. } => execute::execute_insn_rewind_await,
Insn::LastAsync { .. } => execute::execute_insn_last_async,
Insn::LastAwait { .. } => execute::execute_insn_last_await,
Insn::Column { .. } => execute::execute_insn_column,
Insn::MakeRecord { .. } => execute::execute_insn_make_record,
Insn::ResultRow { .. } => execute::execute_insn_result_row,
Insn::NextAsync { .. } => execute::execute_insn_next_async,
Insn::NextAwait { .. } => execute::execute_insn_next_await,
Insn::PrevAsync { .. } => execute::execute_insn_prev_async,
Insn::PrevAwait { .. } => execute::execute_insn_prev_await,
Insn::Halt { .. } => execute::execute_insn_halt,
Insn::Transaction { .. } => execute::execute_insn_transaction,
Insn::AutoCommit { .. } => execute::execute_insn_auto_commit,
Insn::Goto { .. } => execute::execute_insn_goto,
Insn::Gosub { .. } => execute::execute_insn_gosub,
Insn::Return { .. } => execute::execute_insn_return,
Insn::Integer { .. } => execute::execute_insn_integer,
Insn::Real { .. } => execute::execute_insn_real,
Insn::RealAffinity { .. } => execute::execute_insn_real_affinity,
Insn::String8 { .. } => execute::execute_insn_string8,
Insn::Blob { .. } => execute::execute_insn_blob,
Insn::RowId { .. } => execute::execute_insn_row_id,
Insn::SeekRowid { .. } => execute::execute_insn_seek_rowid,
Insn::DeferredSeek { .. } => execute::execute_insn_deferred_seek,
Insn::SeekGE { .. } => execute::execute_insn_seek_ge,
Insn::SeekGT { .. } => execute::execute_insn_seek_gt,
Insn::IdxGE { .. } => execute::execute_insn_idx_ge,
Insn::IdxGT { .. } => execute::execute_insn_idx_gt,
Insn::IdxLE { .. } => execute::execute_insn_idx_le,
Insn::IdxLT { .. } => execute::execute_insn_idx_lt,
Insn::DecrJumpZero { .. } => execute::execute_insn_decr_jump_zero,
Insn::AggStep { .. } => execute::execute_insn_agg_step,
Insn::AggFinal { .. } => execute::execute_insn_agg_final,
Insn::SorterOpen { .. } => execute::execute_insn_sorter_open,
Insn::SorterInsert { .. } => execute::execute_insn_sorter_insert,
Insn::SorterSort { .. } => execute::execute_insn_sorter_sort,
Insn::SorterData { .. } => execute::execute_insn_sorter_data,
Insn::SorterNext { .. } => execute::execute_insn_sorter_next,
Insn::Function { .. } => execute::execute_insn_function,
Insn::InitCoroutine { .. } => execute::execute_insn_init_coroutine,
Insn::EndCoroutine { .. } => execute::execute_insn_end_coroutine,
Insn::Yield { .. } => execute::execute_insn_yield,
Insn::InsertAsync { .. } => execute::execute_insn_insert_async,
Insn::InsertAwait { .. } => execute::execute_insn_insert_await,
Insn::DeleteAsync { .. } => execute::execute_insn_delete_async,
Insn::DeleteAwait { .. } => execute::execute_insn_delete_await,
Insn::NewRowid { .. } => execute::execute_insn_new_rowid,
Insn::MustBeInt { .. } => execute::execute_insn_must_be_int,
Insn::SoftNull { .. } => execute::execute_insn_soft_null,
Insn::NotExists { .. } => execute::execute_insn_not_exists,
Insn::OffsetLimit { .. } => execute::execute_insn_offset_limit,
Insn::OpenWriteAsync { .. } => execute::execute_insn_open_write_async,
Insn::OpenWriteAwait { .. } => execute::execute_insn_open_write_await,
Insn::Copy { .. } => execute::execute_insn_copy,
Insn::CreateBtree { .. } => execute::execute_insn_create_btree,
Insn::Destroy { .. } => execute::execute_insn_destroy,
Insn::DropTable { .. } => execute::execute_insn_drop_table,
Insn::Close { .. } => execute::execute_insn_close,
Insn::IsNull { .. } => execute::execute_insn_is_null,
Insn::ParseSchema { .. } => execute::execute_insn_parse_schema,
Insn::ShiftRight { .. } => execute::execute_insn_shift_right,
Insn::ShiftLeft { .. } => execute::execute_insn_shift_left,
Insn::Variable { .. } => execute::execute_insn_variable,
Insn::ZeroOrNull { .. } => execute::execute_insn_zero_or_null,
Insn::Not { .. } => execute::execute_insn_not,
Insn::Concat { .. } => execute::execute_insn_concat,
Insn::And { .. } => execute::execute_insn_and,
Insn::Or { .. } => execute::execute_insn_or,
Insn::Noop => execute::execute_insn_noop,
Insn::PageCount { .. } => execute::execute_insn_page_count,
Insn::ReadCookie { .. } => execute::execute_insn_read_cookie,
}
}
}
#[cfg(test)]
mod tests {
use crate::{

File diff suppressed because it is too large Load Diff