Skip to content

Commit

Permalink
Merge 'Add bitwise vdbe ops' from Preston Thorpe
Browse files Browse the repository at this point in the history
Love the project, been following your blog posts for quite a while now.
I asked on Discord prior to submitting this, just because I didn't see a
specific issue for this feature... but if this PR is out of scope for
contributors, feel free to close it as I just had a good time hacking on
it.
This PR adds support for `BitAnd`, `BitOr`, and `BitNot` operators in
the vdbe, as well as unary expressions applied to aggregate functions;
which was needed in order to have `BitNot` support the same tests that
the other operators had.
*Also added unary negation of function calls, because since unary ops
were added, I figured adding support for the other existing unary
operator might be in scope, but lmk if not.
Let me know if there is any more tests or documentation to add/improve.

Closes #445
  • Loading branch information
penberg committed Dec 12, 2024
2 parents e79e42b + 16595f3 commit 91764b8
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ env
dist/
.tmp/


/testing/testing.db-wal

# OS
.DS_Store
6 changes: 3 additions & 3 deletions COMPAT.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,9 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html).
| AggStep | Yes |
| And | No |
| AutoCommit | No |
| BitAnd | No |
| BitNot | No |
| BitOr | No |
| BitAnd | Yes |
| BitNot | Yes |
| BitOr | Yes |
| Blob | Yes |
| Checkpoint | No |
| Clear | No |
Expand Down
77 changes: 77 additions & 0 deletions core/translate/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,20 @@ pub fn translate_expr(
dest: target_register,
});
}
ast::Operator::BitwiseAnd => {
program.emit_insn(Insn::BitAnd {
lhs: e1_reg,
rhs: e2_reg,
dest: target_register,
});
}
ast::Operator::BitwiseOr => {
program.emit_insn(Insn::BitOr {
lhs: e1_reg,
rhs: e2_reg,
dest: target_register,
});
}
other_unimplemented => todo!("{:?}", other_unimplemented),
}
Ok(target_register)
Expand Down Expand Up @@ -1663,6 +1677,69 @@ pub fn translate_expr(
dest: target_register,
});
}
program.mark_last_insn_constant();
Ok(target_register)
}
(UnaryOperator::Negative, _) => {
let reg = program.alloc_register();
translate_expr(
program,
referenced_tables,
expr,
reg,
precomputed_exprs_to_registers,
)?;
let zero_reg = program.alloc_register();
program.emit_insn(Insn::Integer {
value: -1,
dest: zero_reg,
});
program.mark_last_insn_constant();
program.emit_insn(Insn::Multiply {
lhs: zero_reg,
rhs: reg,
dest: target_register,
});
Ok(target_register)
}
(UnaryOperator::BitwiseNot, ast::Expr::Literal(ast::Literal::Numeric(num_val))) => {
let maybe_int = num_val.parse::<i64>();
if let Ok(val) = maybe_int {
program.emit_insn(Insn::Integer {
value: !val,
dest: target_register,
});
} else {
let num_val = num_val.parse::<f64>()? as i64;
program.emit_insn(Insn::Integer {
value: !num_val,
dest: target_register,
});
}
program.mark_last_insn_constant();
Ok(target_register)
}
(UnaryOperator::BitwiseNot, ast::Expr::Literal(ast::Literal::Null)) => {
program.emit_insn(Insn::Null {
dest: target_register,
dest_end: None,
});
program.mark_last_insn_constant();
Ok(target_register)
}
(UnaryOperator::BitwiseNot, _) => {
let reg = program.alloc_register();
translate_expr(
program,
referenced_tables,
expr,
reg,
precomputed_exprs_to_registers,
)?;
program.emit_insn(Insn::BitNot {
reg,
dest: target_register,
});
Ok(target_register)
}
_ => todo!(),
Expand Down
5 changes: 5 additions & 0 deletions core/translate/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ fn resolve_aggregates(expr: &ast::Expr, aggs: &mut Vec<Aggregate>) -> bool {
contains_aggregates |= resolve_aggregates(rhs, aggs);
contains_aggregates
}
ast::Expr::Unary(_, expr) => {
let mut contains_aggregates = false;
contains_aggregates |= resolve_aggregates(expr, aggs);
contains_aggregates
}
// TODO: handle other expressions that may contain aggregates
_ => false,
}
Expand Down
27 changes: 27 additions & 0 deletions core/vdbe/explain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,33 @@ pub fn insn_to_str(
0,
format!("r[{}]=r[{}]/r[{}]", dest, lhs, rhs),
),
Insn::BitAnd { lhs, rhs, dest } => (
"BitAnd",
*lhs as i32,
*rhs as i32,
*dest as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}]=r[{}]&r[{}]", dest, lhs, rhs),
),
Insn::BitOr { lhs, rhs, dest } => (
"BitOr",
*lhs as i32,
*rhs as i32,
*dest as i32,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}]=r[{}]|r[{}]", dest, lhs, rhs),
),
Insn::BitNot { reg, dest } => (
"BitNot",
*reg as i32,
*dest as i32,
0,
OwnedValue::Text(Rc::new("".to_string())),
0,
format!("r[{}]=~r[{}]", dest, reg),
),
Insn::Null { dest, dest_end } => (
"Null",
0,
Expand Down
209 changes: 208 additions & 1 deletion core/vdbe/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,23 @@ pub enum Insn {
start_reg_b: usize,
count: usize,
},
// Place the result of rhs bitwise AND lhs in third register.
BitAnd {
lhs: usize,
rhs: usize,
dest: usize,
},
// Place the result of rhs bitwise OR lhs in third register.
BitOr {
lhs: usize,
rhs: usize,
dest: usize,
},
// Place the result of bitwise NOT register P1 in dest register.
BitNot {
reg: usize,
dest: usize,
},
// Jump to the instruction at address P1, P2, or P3 depending on whether in the most recent Compare instruction the P1 vector was less than, equal to, or greater than the P2 vector, respectively.
Jump {
target_pc_lt: BranchOffset,
Expand Down Expand Up @@ -838,7 +855,7 @@ impl Program {
}
(OwnedValue::Integer(i), OwnedValue::Float(f))
| (OwnedValue::Float(f), OwnedValue::Integer(i)) => {
state.registers[dest] = OwnedValue::Float(*i as f64 * *f as f64);
state.registers[dest] = OwnedValue::Float(*i as f64 * { *f });
}
(OwnedValue::Null, _) | (_, OwnedValue::Null) => {
state.registers[dest] = OwnedValue::Null;
Expand Down Expand Up @@ -1011,6 +1028,196 @@ impl Program {
}
state.pc += 1;
}
Insn::BitAnd { lhs, rhs, dest } => {
let lhs = *lhs;
let rhs = *rhs;
let dest = *dest;
match (&state.registers[lhs], &state.registers[rhs]) {
// handle 0 and null cases
(OwnedValue::Null, _) | (_, OwnedValue::Null) => {
state.registers[dest] = OwnedValue::Null;
}
(_, OwnedValue::Integer(0))
| (OwnedValue::Integer(0), _)
| (_, OwnedValue::Float(0.0))
| (OwnedValue::Float(0.0), _) => {
state.registers[dest] = OwnedValue::Integer(0);
}
(OwnedValue::Integer(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] = OwnedValue::Integer(lh & rh);
}
(OwnedValue::Float(lh), OwnedValue::Float(rh)) => {
state.registers[dest] = OwnedValue::Integer(*lh as i64 & *rh as i64);
}
(OwnedValue::Float(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] = OwnedValue::Integer(*lh as i64 & rh);
}
(OwnedValue::Integer(lh), OwnedValue::Float(rh)) => {
state.registers[dest] = OwnedValue::Integer(lh & *rh as i64);
}
(OwnedValue::Agg(aggctx), other) | (other, OwnedValue::Agg(aggctx)) => {
match other {
OwnedValue::Agg(aggctx2) => {
match (aggctx.final_value(), aggctx2.final_value()) {
(OwnedValue::Integer(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] = OwnedValue::Integer(lh & rh);
}
(OwnedValue::Float(lh), OwnedValue::Float(rh)) => {
state.registers[dest] =
OwnedValue::Integer(*lh as i64 & *rh as i64);
}
(OwnedValue::Integer(lh), OwnedValue::Float(rh)) => {
state.registers[dest] =
OwnedValue::Integer(lh & *rh as i64);
}
(OwnedValue::Float(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] =
OwnedValue::Integer(*lh as i64 & rh);
}
_ => {
unimplemented!(
"{:?} {:?}",
aggctx.final_value(),
aggctx2.final_value()
);
}
}
}
other => match (aggctx.final_value(), other) {
(OwnedValue::Null, _) | (_, OwnedValue::Null) => {
state.registers[dest] = OwnedValue::Null;
}
(_, OwnedValue::Integer(0))
| (OwnedValue::Integer(0), _)
| (_, OwnedValue::Float(0.0))
| (OwnedValue::Float(0.0), _) => {
state.registers[dest] = OwnedValue::Integer(0);
}
(OwnedValue::Integer(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] = OwnedValue::Integer(lh & rh);
}
(OwnedValue::Float(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] =
OwnedValue::Integer(*lh as i64 & rh);
}
(OwnedValue::Integer(lh), OwnedValue::Float(rh)) => {
state.registers[dest] =
OwnedValue::Integer(lh & *rh as i64);
}
_ => {
unimplemented!("{:?} {:?}", aggctx.final_value(), other);
}
},
}
}
_ => {
unimplemented!("{:?} {:?}", state.registers[lhs], state.registers[rhs]);
}
}
state.pc += 1;
}
Insn::BitOr { lhs, rhs, dest } => {
let lhs = *lhs;
let rhs = *rhs;
let dest = *dest;
match (&state.registers[lhs], &state.registers[rhs]) {
(OwnedValue::Null, _) | (_, OwnedValue::Null) => {
state.registers[dest] = OwnedValue::Null;
}
(OwnedValue::Integer(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] = OwnedValue::Integer(lh | rh);
}
(OwnedValue::Float(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] = OwnedValue::Integer(*lh as i64 | rh);
}
(OwnedValue::Integer(lh), OwnedValue::Float(rh)) => {
state.registers[dest] = OwnedValue::Integer(lh | *rh as i64);
}
(OwnedValue::Float(lh), OwnedValue::Float(rh)) => {
state.registers[dest] = OwnedValue::Integer(*lh as i64 | *rh as i64);
}
(OwnedValue::Agg(aggctx), other) | (other, OwnedValue::Agg(aggctx)) => {
match other {
OwnedValue::Agg(aggctx2) => {
let final_lhs = aggctx.final_value();
let final_rhs = aggctx2.final_value();
match (final_lhs, final_rhs) {
(OwnedValue::Integer(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] = OwnedValue::Integer(lh | rh);
}
(OwnedValue::Float(lh), OwnedValue::Float(rh)) => {
state.registers[dest] =
OwnedValue::Integer(*lh as i64 | *rh as i64);
}
(OwnedValue::Integer(lh), OwnedValue::Float(rh)) => {
state.registers[dest] =
OwnedValue::Integer(lh | *rh as i64);
}
(OwnedValue::Float(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] =
OwnedValue::Integer(*lh as i64 | rh);
}
_ => {
unimplemented!("{:?} {:?}", final_lhs, final_rhs);
}
}
}
other => match (aggctx.final_value(), other) {
(OwnedValue::Null, _) | (_, OwnedValue::Null) => {
state.registers[dest] = OwnedValue::Null;
}
(OwnedValue::Integer(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] = OwnedValue::Integer(lh | rh);
}
(OwnedValue::Float(lh), OwnedValue::Integer(rh)) => {
state.registers[dest] =
OwnedValue::Integer(*lh as i64 | rh);
}
(OwnedValue::Integer(lh), OwnedValue::Float(rh)) => {
state.registers[dest] =
OwnedValue::Integer(lh | *rh as i64);
}
_ => {
unimplemented!("{:?} {:?}", aggctx.final_value(), other);
}
},
}
}
_ => {
unimplemented!("{:?} {:?}", state.registers[lhs], state.registers[rhs]);
}
}
state.pc += 1;
}
Insn::BitNot { reg, dest } => {
let reg = *reg;
let dest = *dest;
match &state.registers[reg] {
OwnedValue::Integer(i) => state.registers[dest] = OwnedValue::Integer(!i),
OwnedValue::Float(f) => {
state.registers[dest] = OwnedValue::Integer(!{ *f as i64 })
}
OwnedValue::Null => {
state.registers[dest] = OwnedValue::Null;
}
OwnedValue::Agg(aggctx) => match aggctx.final_value() {
OwnedValue::Integer(i) => {
state.registers[dest] = OwnedValue::Integer(!i);
}
OwnedValue::Float(f) => {
state.registers[dest] = OwnedValue::Integer(!{ *f as i64 });
}
OwnedValue::Null => {
state.registers[dest] = OwnedValue::Null;
}
_ => unimplemented!("{:?}", aggctx),
},
_ => {
unimplemented!("{:?}", state.registers[reg]);
}
}
state.pc += 1;
}
Insn::Null { dest, dest_end } => {
if let Some(dest_end) = dest_end {
for i in *dest..=*dest_end {
Expand Down
Loading

0 comments on commit 91764b8

Please sign in to comment.