Simplify working with labels

TLDR: no need to call either of:

program.emit_insn_with_label_dependency() -> just call program.emit_insn()
program.defer_label_resolution() -> just call program.resolve_label()

Changes:

- make BranchOffset an explicit enum (Label, Offset, Placeholder)
- remove program.emit_insn_with_label_dependency() - label dependency is automatically detected
- for label to offset mapping, use a hashmap from label(negative i32) to offset (positive u32)
- resolve all labels in program.build()
- remove program.defer_label_resolution() - all labels are resolved in build()
This commit is contained in:
Jussi Saurio 2025-01-07 12:46:01 +02:00
parent a369329988
commit 731ff1480f
13 changed files with 637 additions and 833 deletions

View file

@ -104,12 +104,9 @@ fn prologue<'a>(
let mut program = ProgramBuilder::new();
let init_label = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::Init {
target_pc: init_label,
},
init_label,
);
program.emit_insn(Insn::Init {
target_pc: init_label,
});
let start_offset = program.offset();
@ -151,8 +148,6 @@ fn epilogue(
target_pc: start_offset,
});
program.resolve_deferred_labels();
Ok(())
}
@ -218,12 +213,9 @@ pub fn emit_query<'a>(
let after_main_loop_label = program.allocate_label();
t_ctx.label_main_loop_end = Some(after_main_loop_label);
if plan.contains_constant_false_condition {
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: after_main_loop_label,
},
after_main_loop_label,
);
program.emit_insn(Insn::Goto {
target_pc: after_main_loop_label,
});
}
// Allocate registers for result columns
@ -281,12 +273,9 @@ fn emit_program_for_delete(
// No rows will be read from source table loops if there is a constant false condition eg. WHERE 0
let after_main_loop_label = program.allocate_label();
if plan.contains_constant_false_condition {
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: after_main_loop_label,
},
after_main_loop_label,
);
program.emit_insn(Insn::Goto {
target_pc: after_main_loop_label,
});
}
// Initialize cursors and other resources needed for query execution
@ -356,13 +345,10 @@ fn emit_delete_insns<'a>(
dest: limit_reg,
});
program.mark_last_insn_constant();
program.emit_insn_with_label_dependency(
Insn::DecrJumpZero {
reg: limit_reg,
target_pc: t_ctx.label_main_loop_end.unwrap(),
},
t_ctx.label_main_loop_end.unwrap(),
)
program.emit_insn(Insn::DecrJumpZero {
reg: limit_reg,
target_pc: t_ctx.label_main_loop_end.unwrap(),
})
}
Ok(())

View file

@ -13,7 +13,7 @@ use crate::Result;
use super::emitter::Resolver;
use super::plan::{TableReference, TableReferenceType};
#[derive(Default, Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy)]
pub struct ConditionMetadata {
pub jump_if_condition_is_true: bool,
pub jump_target_when_true: BranchOffset,
@ -87,128 +87,92 @@ pub fn translate_condition_expr(
match op {
ast::Operator::Greater => {
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::Gt {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
},
condition_metadata.jump_target_when_true,
)
program.emit_insn(Insn::Gt {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
})
} else {
program.emit_insn_with_label_dependency(
Insn::Le {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
},
condition_metadata.jump_target_when_false,
)
program.emit_insn(Insn::Le {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
})
}
}
ast::Operator::GreaterEquals => {
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::Ge {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
},
condition_metadata.jump_target_when_true,
)
program.emit_insn(Insn::Ge {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
})
} else {
program.emit_insn_with_label_dependency(
Insn::Lt {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
},
condition_metadata.jump_target_when_false,
)
program.emit_insn(Insn::Lt {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
})
}
}
ast::Operator::Less => {
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::Lt {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
},
condition_metadata.jump_target_when_true,
)
program.emit_insn(Insn::Lt {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
})
} else {
program.emit_insn_with_label_dependency(
Insn::Ge {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
},
condition_metadata.jump_target_when_false,
)
program.emit_insn(Insn::Ge {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
})
}
}
ast::Operator::LessEquals => {
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::Le {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
},
condition_metadata.jump_target_when_true,
)
program.emit_insn(Insn::Le {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
})
} else {
program.emit_insn_with_label_dependency(
Insn::Gt {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
},
condition_metadata.jump_target_when_false,
)
program.emit_insn(Insn::Gt {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
})
}
}
ast::Operator::Equals => {
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::Eq {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
},
condition_metadata.jump_target_when_true,
)
program.emit_insn(Insn::Eq {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
})
} else {
program.emit_insn_with_label_dependency(
Insn::Ne {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
},
condition_metadata.jump_target_when_false,
)
program.emit_insn(Insn::Ne {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
})
}
}
ast::Operator::NotEquals => {
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::Ne {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
},
condition_metadata.jump_target_when_true,
)
program.emit_insn(Insn::Ne {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_true,
})
} else {
program.emit_insn_with_label_dependency(
Insn::Eq {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
},
condition_metadata.jump_target_when_false,
)
program.emit_insn(Insn::Eq {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
})
}
}
ast::Operator::Is => todo!(),
@ -228,23 +192,17 @@ pub fn translate_condition_expr(
dest: reg,
});
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::If {
reg,
target_pc: condition_metadata.jump_target_when_true,
null_reg: reg,
},
condition_metadata.jump_target_when_true,
)
program.emit_insn(Insn::If {
reg,
target_pc: condition_metadata.jump_target_when_true,
null_reg: reg,
})
} else {
program.emit_insn_with_label_dependency(
Insn::IfNot {
reg,
target_pc: condition_metadata.jump_target_when_false,
null_reg: reg,
},
condition_metadata.jump_target_when_false,
)
program.emit_insn(Insn::IfNot {
reg,
target_pc: condition_metadata.jump_target_when_false,
null_reg: reg,
})
}
} else {
crate::bail_parse_error!("unsupported literal type in condition");
@ -257,23 +215,17 @@ pub fn translate_condition_expr(
dest: reg,
});
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::If {
reg,
target_pc: condition_metadata.jump_target_when_true,
null_reg: reg,
},
condition_metadata.jump_target_when_true,
)
program.emit_insn(Insn::If {
reg,
target_pc: condition_metadata.jump_target_when_true,
null_reg: reg,
})
} else {
program.emit_insn_with_label_dependency(
Insn::IfNot {
reg,
target_pc: condition_metadata.jump_target_when_false,
null_reg: reg,
},
condition_metadata.jump_target_when_false,
)
program.emit_insn(Insn::IfNot {
reg,
target_pc: condition_metadata.jump_target_when_false,
null_reg: reg,
})
}
}
unimpl => todo!("literal {:?} not implemented", unimpl),
@ -302,20 +254,14 @@ pub fn translate_condition_expr(
// Note that we are already breaking up our WHERE clauses into a series of terms at "AND" boundaries, so right now we won't be running into cases where jumping on true would be incorrect,
// but once we have e.g. parenthesization and more complex conditions, not having this 'if' here would introduce a bug.
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: condition_metadata.jump_target_when_true,
},
condition_metadata.jump_target_when_true,
);
program.emit_insn(Insn::Goto {
target_pc: condition_metadata.jump_target_when_true,
});
}
} else {
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: condition_metadata.jump_target_when_false,
},
condition_metadata.jump_target_when_false,
);
program.emit_insn(Insn::Goto {
target_pc: condition_metadata.jump_target_when_false,
});
}
return Ok(());
}
@ -349,35 +295,26 @@ pub fn translate_condition_expr(
translate_expr(program, Some(referenced_tables), expr, rhs_reg, resolver)?;
// If this is not the last condition, we need to jump to the 'jump_target_when_true' label if the condition is true.
if !last_condition {
program.emit_insn_with_label_dependency(
Insn::Eq {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: jump_target_when_true,
},
jump_target_when_true,
);
program.emit_insn(Insn::Eq {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: jump_target_when_true,
});
} else {
// If this is the last condition, we need to jump to the 'jump_target_when_false' label if there is no match.
program.emit_insn_with_label_dependency(
Insn::Ne {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
},
condition_metadata.jump_target_when_false,
);
program.emit_insn(Insn::Ne {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
});
}
}
// If we got here, then the last condition was a match, so we jump to the 'jump_target_when_true' label if 'jump_if_condition_is_true'.
// If not, we can just fall through without emitting an unnecessary instruction.
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: condition_metadata.jump_target_when_true,
},
condition_metadata.jump_target_when_true,
);
program.emit_insn(Insn::Goto {
target_pc: condition_metadata.jump_target_when_true,
});
}
} else {
// If it's a NOT IN expression, we need to jump to the 'jump_target_when_false' label if any of the conditions are true.
@ -385,24 +322,18 @@ pub fn translate_condition_expr(
let rhs_reg = program.alloc_register();
let _ =
translate_expr(program, Some(referenced_tables), expr, rhs_reg, resolver)?;
program.emit_insn_with_label_dependency(
Insn::Eq {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
},
condition_metadata.jump_target_when_false,
);
program.emit_insn(Insn::Eq {
lhs: lhs_reg,
rhs: rhs_reg,
target_pc: condition_metadata.jump_target_when_false,
});
}
// If we got here, then none of the conditions were a match, so we jump to the 'jump_target_when_true' label if 'jump_if_condition_is_true'.
// If not, we can just fall through without emitting an unnecessary instruction.
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: condition_metadata.jump_target_when_true,
},
condition_metadata.jump_target_when_true,
);
program.emit_insn(Insn::Goto {
target_pc: condition_metadata.jump_target_when_true,
});
}
}
@ -464,42 +395,30 @@ pub fn translate_condition_expr(
}
if !*not {
if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::If {
reg: cur_reg,
target_pc: condition_metadata.jump_target_when_true,
null_reg: cur_reg,
},
condition_metadata.jump_target_when_true,
);
} else {
program.emit_insn_with_label_dependency(
Insn::IfNot {
reg: cur_reg,
target_pc: condition_metadata.jump_target_when_false,
null_reg: cur_reg,
},
condition_metadata.jump_target_when_false,
);
}
} else if condition_metadata.jump_if_condition_is_true {
program.emit_insn_with_label_dependency(
Insn::IfNot {
program.emit_insn(Insn::If {
reg: cur_reg,
target_pc: condition_metadata.jump_target_when_true,
null_reg: cur_reg,
},
condition_metadata.jump_target_when_true,
);
} else {
program.emit_insn_with_label_dependency(
Insn::If {
});
} else {
program.emit_insn(Insn::IfNot {
reg: cur_reg,
target_pc: condition_metadata.jump_target_when_false,
null_reg: cur_reg,
},
condition_metadata.jump_target_when_false,
);
});
}
} else if condition_metadata.jump_if_condition_is_true {
program.emit_insn(Insn::IfNot {
reg: cur_reg,
target_pc: condition_metadata.jump_target_when_true,
null_reg: cur_reg,
});
} else {
program.emit_insn(Insn::If {
reg: cur_reg,
target_pc: condition_metadata.jump_target_when_false,
null_reg: cur_reg,
});
}
}
ast::Expr::Parenthesized(exprs) => {
@ -707,23 +626,17 @@ pub fn translate_expr(
translate_expr(program, referenced_tables, when_expr, expr_reg, resolver)?;
match base_reg {
// CASE 1 WHEN 0 THEN 0 ELSE 1 becomes 1==0, Ne branch to next clause
Some(base_reg) => program.emit_insn_with_label_dependency(
Insn::Ne {
lhs: base_reg,
rhs: expr_reg,
target_pc: next_case_label,
},
next_case_label,
),
Some(base_reg) => program.emit_insn(Insn::Ne {
lhs: base_reg,
rhs: expr_reg,
target_pc: next_case_label,
}),
// CASE WHEN 0 THEN 0 ELSE 1 becomes ifnot 0 branch to next clause
None => program.emit_insn_with_label_dependency(
Insn::IfNot {
reg: expr_reg,
target_pc: next_case_label,
null_reg: 1,
},
next_case_label,
),
None => program.emit_insn(Insn::IfNot {
reg: expr_reg,
target_pc: next_case_label,
null_reg: 1,
}),
};
// THEN...
translate_expr(
@ -733,12 +646,9 @@ pub fn translate_expr(
target_register,
resolver,
)?;
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: return_label,
},
return_label,
);
program.emit_insn(Insn::Goto {
target_pc: return_label,
});
// This becomes either the next WHEN, or in the last WHEN/THEN, we're
// assured to have at least one instruction corresponding to the ELSE immediately follow.
program.preassign_label_to_next_insn(next_case_label);
@ -984,13 +894,10 @@ pub fn translate_expr(
resolver,
)?;
if index < args.len() - 1 {
program.emit_insn_with_label_dependency(
Insn::NotNull {
reg,
target_pc: label_coalesce_end,
},
label_coalesce_end,
);
program.emit_insn(Insn::NotNull {
reg,
target_pc: label_coalesce_end,
});
}
}
program.preassign_label_to_next_insn(label_coalesce_end);
@ -1085,7 +992,7 @@ pub fn translate_expr(
)?;
program.emit_insn(Insn::NotNull {
reg: temp_reg,
target_pc: program.offset() + 2,
target_pc: program.offset().add(2u32),
});
translate_expr(
@ -1120,14 +1027,11 @@ pub fn translate_expr(
resolver,
)?;
let jump_target_when_false = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::IfNot {
reg: temp_reg,
target_pc: jump_target_when_false,
null_reg: 1,
},
jump_target_when_false,
);
program.emit_insn(Insn::IfNot {
reg: temp_reg,
target_pc: jump_target_when_false,
null_reg: 1,
});
translate_expr(
program,
referenced_tables,
@ -1136,12 +1040,9 @@ pub fn translate_expr(
resolver,
)?;
let jump_target_result = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: jump_target_result,
},
jump_target_result,
);
program.emit_insn(Insn::Goto {
target_pc: jump_target_result,
});
program.resolve_label(jump_target_when_false, program.offset());
translate_expr(
program,
@ -2033,7 +1934,7 @@ fn wrap_eval_jump_expr(
value: 1, // emit True by default
dest: target_register,
});
program.emit_insn_with_label_dependency(insn, if_true_label);
program.emit_insn(insn);
program.emit_insn(Insn::Integer {
value: 0, // emit False if we reach this point (no jump)
dest: target_register,

View file

@ -93,13 +93,10 @@ pub fn init_group_by(
program.add_comment(program.offset(), "go to clear accumulator subroutine");
let reg_subrtn_acc_clear_return_offset = program.alloc_register();
program.emit_insn_with_label_dependency(
Insn::Gosub {
target_pc: label_subrtn_acc_clear,
return_reg: reg_subrtn_acc_clear_return_offset,
},
label_subrtn_acc_clear,
);
program.emit_insn(Insn::Gosub {
target_pc: label_subrtn_acc_clear,
return_reg: reg_subrtn_acc_clear_return_offset,
});
t_ctx.reg_agg_start = Some(reg_agg_exprs_start);
@ -187,15 +184,12 @@ pub fn emit_group_by<'a>(
});
// Sort the sorter based on the group by columns
program.emit_insn_with_label_dependency(
Insn::SorterSort {
cursor_id: sort_cursor,
pc_if_empty: label_grouping_loop_end,
},
label_grouping_loop_end,
);
program.emit_insn(Insn::SorterSort {
cursor_id: sort_cursor,
pc_if_empty: label_grouping_loop_end,
});
program.defer_label_resolution(label_grouping_loop_start, program.offset() as usize);
program.resolve_label(label_grouping_loop_start, program.offset());
// Read a row from the sorted data in the sorter into the pseudo cursor
program.emit_insn(Insn::SorterData {
cursor_id: sort_cursor,
@ -229,14 +223,11 @@ pub fn emit_group_by<'a>(
"start new group if comparison is not equal",
);
// If we are at a new group, continue. If we are at the same group, jump to the aggregation step (i.e. accumulate more values into the aggregations)
program.emit_insn_with_label_dependency(
Insn::Jump {
target_pc_lt: program.offset() + 1,
target_pc_eq: agg_step_label,
target_pc_gt: program.offset() + 1,
},
agg_step_label,
);
program.emit_insn(Insn::Jump {
target_pc_lt: program.offset().add(1u32),
target_pc_eq: agg_step_label,
target_pc_gt: program.offset().add(1u32),
});
// New group, move current group by columns into the comparison register
program.emit_insn(Insn::Move {
@ -249,32 +240,23 @@ pub fn emit_group_by<'a>(
program.offset(),
"check if ended group had data, and output if so",
);
program.emit_insn_with_label_dependency(
Insn::Gosub {
target_pc: label_subrtn_acc_output,
return_reg: reg_subrtn_acc_output_return_offset,
},
label_subrtn_acc_output,
);
program.emit_insn(Insn::Gosub {
target_pc: label_subrtn_acc_output,
return_reg: reg_subrtn_acc_output_return_offset,
});
program.add_comment(program.offset(), "check abort flag");
program.emit_insn_with_label_dependency(
Insn::IfPos {
reg: reg_abort_flag,
target_pc: label_group_by_end,
decrement_by: 0,
},
label_group_by_end,
);
program.emit_insn(Insn::IfPos {
reg: reg_abort_flag,
target_pc: label_group_by_end,
decrement_by: 0,
});
program.add_comment(program.offset(), "goto clear accumulator subroutine");
program.emit_insn_with_label_dependency(
Insn::Gosub {
target_pc: label_subrtn_acc_clear,
return_reg: reg_subrtn_acc_clear_return_offset,
},
label_subrtn_acc_clear,
);
program.emit_insn(Insn::Gosub {
target_pc: label_subrtn_acc_clear,
return_reg: reg_subrtn_acc_clear_return_offset,
});
// Accumulate the values into the aggregations
program.resolve_label(agg_step_label, program.offset());
@ -299,14 +281,11 @@ pub fn emit_group_by<'a>(
program.offset(),
"don't emit group columns if continuing existing group",
);
program.emit_insn_with_label_dependency(
Insn::If {
target_pc: label_acc_indicator_set_flag_true,
reg: reg_data_in_acc_flag,
null_reg: 0, // unused in this case
},
label_acc_indicator_set_flag_true,
);
program.emit_insn(Insn::If {
target_pc: label_acc_indicator_set_flag_true,
reg: reg_data_in_acc_flag,
null_reg: 0, // unused in this case
});
// Read the group by columns for a finished group
for i in 0..group_by.exprs.len() {
@ -326,32 +305,23 @@ pub fn emit_group_by<'a>(
dest: reg_data_in_acc_flag,
});
program.emit_insn_with_label_dependency(
Insn::SorterNext {
cursor_id: sort_cursor,
pc_if_next: label_grouping_loop_start,
},
label_grouping_loop_start,
);
program.emit_insn(Insn::SorterNext {
cursor_id: sort_cursor,
pc_if_next: label_grouping_loop_start,
});
program.resolve_label(label_grouping_loop_end, program.offset());
program.add_comment(program.offset(), "emit row for final group");
program.emit_insn_with_label_dependency(
Insn::Gosub {
target_pc: label_subrtn_acc_output,
return_reg: reg_subrtn_acc_output_return_offset,
},
label_subrtn_acc_output,
);
program.emit_insn(Insn::Gosub {
target_pc: label_subrtn_acc_output,
return_reg: reg_subrtn_acc_output_return_offset,
});
program.add_comment(program.offset(), "group by finished");
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: label_group_by_end,
},
label_group_by_end,
);
program.emit_insn(Insn::Goto {
target_pc: label_group_by_end,
});
program.emit_insn(Insn::Integer {
value: 1,
dest: reg_abort_flag,
@ -363,19 +333,13 @@ pub fn emit_group_by<'a>(
program.resolve_label(label_subrtn_acc_output, program.offset());
program.add_comment(program.offset(), "output group by row subroutine start");
program.emit_insn_with_label_dependency(
Insn::IfPos {
reg: reg_data_in_acc_flag,
target_pc: label_agg_final,
decrement_by: 0,
},
label_agg_final,
);
program.emit_insn(Insn::IfPos {
reg: reg_data_in_acc_flag,
target_pc: label_agg_final,
decrement_by: 0,
});
let group_by_end_without_emitting_row_label = program.allocate_label();
program.defer_label_resolution(
group_by_end_without_emitting_row_label,
program.offset() as usize,
);
program.resolve_label(group_by_end_without_emitting_row_label, program.offset());
program.emit_insn(Insn::Return {
return_reg: reg_subrtn_acc_output_return_offset,
});
@ -417,7 +381,7 @@ pub fn emit_group_by<'a>(
ConditionMetadata {
jump_if_condition_is_true: false,
jump_target_when_false: group_by_end_without_emitting_row_label,
jump_target_when_true: i64::MAX, // unused
jump_target_when_true: BranchOffset::Placeholder, // not used. FIXME: this is a bug. HAVING can have e.g. HAVING a OR b.
},
&t_ctx.resolver,
)?;

View file

@ -7,6 +7,7 @@ use sqlite3_parser::ast::{
use crate::error::SQLITE_CONSTRAINT_PRIMARYKEY;
use crate::util::normalize_ident;
use crate::vdbe::BranchOffset;
use crate::{
schema::{Column, Schema, Table},
storage::sqlite3_ondisk::DatabaseHeader,
@ -40,12 +41,9 @@ pub fn translate_insert(
let mut program = ProgramBuilder::new();
let resolver = Resolver::new(syms);
let init_label = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::Init {
target_pc: init_label,
},
init_label,
);
program.emit_insn(Insn::Init {
target_pc: init_label,
});
let start_offset = program.offset();
// open table
@ -104,7 +102,7 @@ pub fn translate_insert(
let record_register = program.alloc_register();
let halt_label = program.allocate_label();
let mut loop_start_offset = 0;
let mut loop_start_offset = BranchOffset::Offset(0);
let inserting_multiple_rows = values.len() > 1;
@ -112,14 +110,11 @@ pub fn translate_insert(
if inserting_multiple_rows {
let yield_reg = program.alloc_register();
let jump_on_definition_label = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::InitCoroutine {
yield_reg,
jump_on_definition: jump_on_definition_label,
start_offset: program.offset() + 1,
},
jump_on_definition_label,
);
program.emit_insn(Insn::InitCoroutine {
yield_reg,
jump_on_definition: jump_on_definition_label,
start_offset: program.offset().add(1u32),
});
for value in values {
populate_column_registers(
@ -133,7 +128,7 @@ pub fn translate_insert(
)?;
program.emit_insn(Insn::Yield {
yield_reg,
end_offset: 0,
end_offset: halt_label,
});
}
program.emit_insn(Insn::EndCoroutine { yield_reg });
@ -149,13 +144,10 @@ pub fn translate_insert(
// FIXME: rollback is not implemented. E.g. if you insert 2 rows and one fails to unique constraint violation,
// the other row will still be inserted.
loop_start_offset = program.offset();
program.emit_insn_with_label_dependency(
Insn::Yield {
yield_reg,
end_offset: halt_label,
},
halt_label,
);
program.emit_insn(Insn::Yield {
yield_reg,
end_offset: halt_label,
});
} else {
// Single row - populate registers directly
program.emit_insn(Insn::OpenWriteAsync {
@ -194,13 +186,10 @@ pub fn translate_insert(
program.emit_insn(Insn::SoftNull { reg });
}
// the user provided rowid value might itself be NULL. If it is, we create a new rowid on the next instruction.
program.emit_insn_with_label_dependency(
Insn::NotNull {
reg: rowid_reg,
target_pc: check_rowid_is_integer_label.unwrap(),
},
check_rowid_is_integer_label.unwrap(),
);
program.emit_insn(Insn::NotNull {
reg: rowid_reg,
target_pc: check_rowid_is_integer_label.unwrap(),
});
}
// Create new rowid if a) not provided by user or b) provided by user but is NULL
@ -220,14 +209,11 @@ pub fn translate_insert(
// When the DB allocates it there are no need for separate uniqueness checks.
if has_user_provided_rowid {
let make_record_label = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::NotExists {
cursor: cursor_id,
rowid_reg,
target_pc: make_record_label,
},
make_record_label,
);
program.emit_insn(Insn::NotExists {
cursor: cursor_id,
rowid_reg,
target_pc: make_record_label,
});
let rowid_column_name = if let Some(index) = rowid_alias_index {
table.column_index_to_name(index).unwrap()
} else {
@ -276,7 +262,6 @@ pub fn translate_insert(
program.emit_insn(Insn::Goto {
target_pc: start_offset,
});
program.resolve_deferred_labels();
Ok(program.build(database_header, connection))
}

View file

@ -194,7 +194,7 @@ pub fn open_loop(
// In case the subquery is an inner loop, it needs to be reinitialized on each iteration of the outer loop.
program.emit_insn(Insn::InitCoroutine {
yield_reg,
jump_on_definition: 0,
jump_on_definition: BranchOffset::Offset(0),
start_offset: coroutine_implementation_start,
});
let LoopLabels {
@ -205,18 +205,15 @@ pub fn open_loop(
.labels_main_loop
.get(id)
.expect("subquery has no loop labels");
program.defer_label_resolution(loop_start, program.offset() as usize);
program.resolve_label(loop_start, program.offset());
// A subquery within the main loop of a parent query has no cursor, so instead of advancing the cursor,
// it emits a Yield which jumps back to the main loop of the subquery itself to retrieve the next row.
// When the subquery coroutine completes, this instruction jumps to the label at the top of the termination_label_stack,
// which in this case is the end of the Yield-Goto loop in the parent query.
program.emit_insn_with_label_dependency(
Insn::Yield {
yield_reg,
end_offset: loop_end,
},
loop_end,
);
program.emit_insn(Insn::Yield {
yield_reg,
end_offset: loop_end,
});
// These are predicates evaluated outside of the subquery,
// so they are translated here.
@ -291,10 +288,7 @@ pub fn open_loop(
if *outer {
let lj_meta = t_ctx.meta_left_joins.get(id).unwrap();
program.defer_label_resolution(
lj_meta.label_match_flag_set_true,
program.offset() as usize,
);
program.resolve_label(lj_meta.label_match_flag_set_true, program.offset());
program.emit_insn(Insn::Integer {
value: 1,
dest: lj_meta.reg_match_flag,
@ -326,7 +320,7 @@ pub fn open_loop(
.labels_main_loop
.get(id)
.expect("scan has no loop labels");
program.emit_insn_with_label_dependency(
program.emit_insn(
if iter_dir
.as_ref()
.is_some_and(|dir| *dir == IterationDirection::Backwards)
@ -341,9 +335,8 @@ pub fn open_loop(
pc_if_empty: loop_end,
}
},
loop_end,
);
program.defer_label_resolution(loop_start, program.offset() as usize);
program.resolve_label(loop_start, program.offset());
if let Some(preds) = predicates {
for expr in preds {
@ -420,28 +413,25 @@ pub fn open_loop(
_ => unreachable!(),
}
// If we try to seek to a key that is not present in the table/index, we exit the loop entirely.
program.emit_insn_with_label_dependency(
match cmp_op {
ast::Operator::Equals | ast::Operator::GreaterEquals => Insn::SeekGE {
is_index: index_cursor_id.is_some(),
cursor_id: index_cursor_id.unwrap_or(table_cursor_id),
start_reg: cmp_reg,
num_regs: 1,
target_pc: loop_end,
},
ast::Operator::Greater
| ast::Operator::Less
| ast::Operator::LessEquals => Insn::SeekGT {
is_index: index_cursor_id.is_some(),
cursor_id: index_cursor_id.unwrap_or(table_cursor_id),
start_reg: cmp_reg,
num_regs: 1,
target_pc: loop_end,
},
_ => unreachable!(),
program.emit_insn(match cmp_op {
ast::Operator::Equals | ast::Operator::GreaterEquals => Insn::SeekGE {
is_index: index_cursor_id.is_some(),
cursor_id: index_cursor_id.unwrap_or(table_cursor_id),
start_reg: cmp_reg,
num_regs: 1,
target_pc: loop_end,
},
loop_end,
);
ast::Operator::Greater | ast::Operator::Less | ast::Operator::LessEquals => {
Insn::SeekGT {
is_index: index_cursor_id.is_some(),
cursor_id: index_cursor_id.unwrap_or(table_cursor_id),
start_reg: cmp_reg,
num_regs: 1,
target_pc: loop_end,
}
}
_ => unreachable!(),
});
if *cmp_op == ast::Operator::Less || *cmp_op == ast::Operator::LessEquals {
translate_expr(
program,
@ -452,7 +442,7 @@ pub fn open_loop(
)?;
}
program.defer_label_resolution(loop_start, program.offset() as usize);
program.resolve_label(loop_start, program.offset());
// TODO: We are currently only handling ascending indexes.
// For conditions like index_key > 10, we have already seeked to the first key greater than 10, and can just scan forward.
// For conditions like index_key < 10, we are at the beginning of the index, and will scan forward and emit IdxGE(10) with a conditional jump to the end.
@ -466,56 +456,44 @@ pub fn open_loop(
match cmp_op {
ast::Operator::Equals | ast::Operator::LessEquals => {
if let Some(index_cursor_id) = index_cursor_id {
program.emit_insn_with_label_dependency(
Insn::IdxGT {
cursor_id: index_cursor_id,
start_reg: cmp_reg,
num_regs: 1,
target_pc: loop_end,
},
loop_end,
);
program.emit_insn(Insn::IdxGT {
cursor_id: index_cursor_id,
start_reg: cmp_reg,
num_regs: 1,
target_pc: loop_end,
});
} else {
let rowid_reg = program.alloc_register();
program.emit_insn(Insn::RowId {
cursor_id: table_cursor_id,
dest: rowid_reg,
});
program.emit_insn_with_label_dependency(
Insn::Gt {
lhs: rowid_reg,
rhs: cmp_reg,
target_pc: loop_end,
},
loop_end,
);
program.emit_insn(Insn::Gt {
lhs: rowid_reg,
rhs: cmp_reg,
target_pc: loop_end,
});
}
}
ast::Operator::Less => {
if let Some(index_cursor_id) = index_cursor_id {
program.emit_insn_with_label_dependency(
Insn::IdxGE {
cursor_id: index_cursor_id,
start_reg: cmp_reg,
num_regs: 1,
target_pc: loop_end,
},
loop_end,
);
program.emit_insn(Insn::IdxGE {
cursor_id: index_cursor_id,
start_reg: cmp_reg,
num_regs: 1,
target_pc: loop_end,
});
} else {
let rowid_reg = program.alloc_register();
program.emit_insn(Insn::RowId {
cursor_id: table_cursor_id,
dest: rowid_reg,
});
program.emit_insn_with_label_dependency(
Insn::Ge {
lhs: rowid_reg,
rhs: cmp_reg,
target_pc: loop_end,
},
loop_end,
);
program.emit_insn(Insn::Ge {
lhs: rowid_reg,
rhs: cmp_reg,
target_pc: loop_end,
});
}
}
_ => {}
@ -538,14 +516,11 @@ pub fn open_loop(
src_reg,
&t_ctx.resolver,
)?;
program.emit_insn_with_label_dependency(
Insn::SeekRowid {
cursor_id: table_cursor_id,
src_reg,
target_pc: next,
},
next,
);
program.emit_insn(Insn::SeekRowid {
cursor_id: table_cursor_id,
src_reg,
target_pc: next,
});
}
if let Some(predicates) = predicates {
for predicate in predicates.iter() {
@ -748,12 +723,9 @@ pub fn close_loop(
// A subquery has no cursor to call NextAsync on, so it just emits a Goto
// to the Yield instruction, which in turn jumps back to the main loop of the subquery,
// so that the next row from the subquery can be read.
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: loop_labels.loop_start,
},
loop_labels.loop_start,
);
program.emit_insn(Insn::Goto {
target_pc: loop_labels.loop_start,
});
}
SourceOperator::Join {
id,
@ -771,7 +743,7 @@ pub fn close_loop(
// If the left join match flag has been set to 1, we jump to the next row on the outer table,
// i.e. continue to the next row of t1 in our example.
program.resolve_label(lj_meta.label_match_flag_check_value, program.offset());
let jump_offset = program.offset() + 3;
let jump_offset = program.offset().add(3u32);
program.emit_insn(Insn::IfPos {
reg: lj_meta.reg_match_flag,
target_pc: jump_offset,
@ -799,12 +771,9 @@ pub fn close_loop(
// and we will end up back in the IfPos instruction above, which will then
// check the match flag again, and since it is now 1, we will jump to the
// next row in the left table.
program.emit_insn_with_label_dependency(
Insn::Goto {
target_pc: lj_meta.label_match_flag_set_true,
},
lj_meta.label_match_flag_set_true,
);
program.emit_insn(Insn::Goto {
target_pc: lj_meta.label_match_flag_set_true,
});
assert!(program.offset() == jump_offset);
}
@ -830,21 +799,15 @@ pub fn close_loop(
.as_ref()
.is_some_and(|dir| *dir == IterationDirection::Backwards)
{
program.emit_insn_with_label_dependency(
Insn::PrevAwait {
cursor_id,
pc_if_next: loop_labels.loop_start,
},
loop_labels.loop_start,
);
program.emit_insn(Insn::PrevAwait {
cursor_id,
pc_if_next: loop_labels.loop_start,
});
} else {
program.emit_insn_with_label_dependency(
Insn::NextAwait {
cursor_id,
pc_if_next: loop_labels.loop_start,
},
loop_labels.loop_start,
);
program.emit_insn(Insn::NextAwait {
cursor_id,
pc_if_next: loop_labels.loop_start,
});
}
}
SourceOperator::Search {
@ -866,13 +829,10 @@ pub fn close_loop(
};
program.emit_insn(Insn::NextAsync { cursor_id });
program.emit_insn_with_label_dependency(
Insn::NextAwait {
cursor_id,
pc_if_next: loop_labels.loop_start,
},
loop_labels.loop_start,
);
program.emit_insn(Insn::NextAwait {
cursor_id,
pc_if_next: loop_labels.loop_start,
});
}
SourceOperator::Nothing { .. } => {}
};

View file

@ -388,12 +388,9 @@ fn translate_create_table(
if schema.get_table(tbl_name.name.0.as_str()).is_some() {
if if_not_exists {
let init_label = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::Init {
target_pc: init_label,
},
init_label,
);
program.emit_insn(Insn::Init {
target_pc: init_label,
});
let start_offset = program.offset();
program.emit_insn(Insn::Halt {
err_code: 0,
@ -414,12 +411,9 @@ fn translate_create_table(
let parse_schema_label = program.allocate_label();
let init_label = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::Init {
target_pc: init_label,
},
init_label,
);
program.emit_insn(Insn::Init {
target_pc: init_label,
});
let start_offset = program.offset();
// TODO: ReadCookie
// TODO: If
@ -544,12 +538,9 @@ fn translate_pragma(
) -> Result<Program> {
let mut program = ProgramBuilder::new();
let init_label = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::Init {
target_pc: init_label,
},
init_label,
);
program.emit_insn(Insn::Init {
target_pc: init_label,
});
let start_offset = program.offset();
let mut write = false;
match body {
@ -581,7 +572,6 @@ fn translate_pragma(
program.emit_insn(Insn::Goto {
target_pc: start_offset,
});
program.resolve_deferred_labels();
Ok(program.build(database_header, connection))
}

View file

@ -110,15 +110,12 @@ pub fn emit_order_by(
num_fields: num_columns_in_sorter,
});
program.emit_insn_with_label_dependency(
Insn::SorterSort {
cursor_id: sort_cursor,
pc_if_empty: sort_loop_end_label,
},
sort_loop_end_label,
);
program.emit_insn(Insn::SorterSort {
cursor_id: sort_cursor,
pc_if_empty: sort_loop_end_label,
});
program.defer_label_resolution(sort_loop_start_label, program.offset() as usize);
program.resolve_label(sort_loop_start_label, program.offset());
program.emit_insn(Insn::SorterData {
cursor_id: sort_cursor,
dest_reg: reg_sorter_data,
@ -140,13 +137,10 @@ pub fn emit_order_by(
emit_result_row_and_limit(program, t_ctx, plan, start_reg, Some(sort_loop_end_label))?;
program.emit_insn_with_label_dependency(
Insn::SorterNext {
cursor_id: sort_cursor,
pc_if_next: sort_loop_start_label,
},
sort_loop_start_label,
);
program.emit_insn(Insn::SorterNext {
cursor_id: sort_cursor,
pc_if_next: sort_loop_start_label,
});
program.resolve_label(sort_loop_end_label, program.offset());

View file

@ -294,7 +294,7 @@ fn parse_from_clause_table(
};
subplan.query_type = SelectQueryType::Subquery {
yield_reg: usize::MAX, // will be set later in bytecode emission
coroutine_implementation_start: BranchOffset::MAX, // will be set later in bytecode emission
coroutine_implementation_start: BranchOffset::Placeholder, // will be set later in bytecode emission
};
let identifier = maybe_alias
.map(|a| match a {

View file

@ -54,7 +54,7 @@ pub fn emit_result_row_and_limit(
SelectQueryType::Subquery { yield_reg, .. } => {
program.emit_insn(Insn::Yield {
yield_reg: *yield_reg,
end_offset: 0,
end_offset: BranchOffset::Offset(0),
});
}
}
@ -71,13 +71,10 @@ pub fn emit_result_row_and_limit(
dest: t_ctx.reg_limit.unwrap(),
});
program.mark_last_insn_constant();
program.emit_insn_with_label_dependency(
Insn::DecrJumpZero {
reg: t_ctx.reg_limit.unwrap(),
target_pc: label_on_limit_reached.unwrap(),
},
label_on_limit_reached.unwrap(),
);
program.emit_insn(Insn::DecrJumpZero {
reg: t_ctx.reg_limit.unwrap(),
target_pc: label_on_limit_reached.unwrap(),
});
}
Ok(())
}

View file

@ -72,7 +72,7 @@ pub fn emit_subquery<'a>(
t_ctx: &mut TranslateCtx<'a>,
) -> Result<usize> {
let yield_reg = program.alloc_register();
let coroutine_implementation_start_offset = program.offset() + 1;
let coroutine_implementation_start_offset = program.offset().add(1u32);
match &mut plan.query_type {
SelectQueryType::Subquery {
yield_reg: y,
@ -100,14 +100,11 @@ pub fn emit_subquery<'a>(
resolver: Resolver::new(t_ctx.resolver.symbol_table),
};
let subquery_body_end_label = program.allocate_label();
program.emit_insn_with_label_dependency(
Insn::InitCoroutine {
yield_reg,
jump_on_definition: subquery_body_end_label,
start_offset: coroutine_implementation_start_offset,
},
subquery_body_end_label,
);
program.emit_insn(Insn::InitCoroutine {
yield_reg,
jump_on_definition: subquery_body_end_label,
start_offset: coroutine_implementation_start_offset,
});
// Normally we mark each LIMIT value as a constant insn that is emitted only once, but in the case of a subquery,
// we need to initialize it every time the subquery is run; otherwise subsequent runs of the subquery will already
// have the LIMIT counter at 0, and will never return rows.

View file

@ -11,23 +11,20 @@ use super::{BranchOffset, CursorID, Insn, InsnReference, Program, Table};
#[allow(dead_code)]
pub struct ProgramBuilder {
next_free_register: usize,
next_free_label: BranchOffset,
next_free_label: i32,
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)>,
// Hashmap of label to insn reference. Resolved in build().
label_to_resolved_offset: HashMap<i32, u32>,
// Bitmask of cursors that have emitted a SeekRowid instruction.
seekrowid_emitted_bitmask: u64,
// map of instruction index to manual comment (used in EXPLAIN)
comments: HashMap<BranchOffset, &'static str>,
comments: HashMap<InsnReference, &'static str>,
}
impl ProgramBuilder {
@ -37,11 +34,10 @@ impl ProgramBuilder {
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(),
label_to_resolved_offset: HashMap::new(),
seekrowid_emitted_bitmask: 0,
comments: HashMap::new(),
}
@ -71,20 +67,17 @@ impl ProgramBuilder {
cursor
}
fn _emit_insn(&mut self, insn: Insn) {
pub fn emit_insn(&mut self, insn: Insn) {
if let Some(label) = self.next_insn_label {
self.label_to_resolved_offset
.insert(label.to_label_value(), self.insns.len() as InsnReference);
self.next_insn_label = None;
}
self.insns.push(insn);
}
pub fn emit_insn(&mut self, insn: Insn) {
self._emit_insn(insn);
if let Some(label) = self.next_insn_label {
self.next_insn_label = None;
self.resolve_label(label, (self.insns.len() - 1) as BranchOffset);
}
}
pub fn add_comment(&mut self, insn_index: BranchOffset, comment: &'static str) {
self.comments.insert(insn_index, comment);
self.comments.insert(insn_index.to_offset_int(), comment);
}
// Emit an instruction that will be put at the end of the program (after Transaction statement).
@ -99,19 +92,13 @@ impl ProgramBuilder {
self.insns.append(&mut self.constant_insns);
}
pub fn emit_insn_with_label_dependency(&mut self, insn: Insn, label: BranchOffset) {
self._emit_insn(insn);
self.add_label_dependency(label, (self.insns.len() - 1) as BranchOffset);
}
pub fn offset(&self) -> BranchOffset {
self.insns.len() as BranchOffset
BranchOffset::Offset(self.insns.len() as InsnReference)
}
pub fn allocate_label(&mut self) -> BranchOffset {
self.next_free_label -= 1;
self.unresolved_labels.push(Vec::new());
self.next_free_label
BranchOffset::Label(self.next_free_label)
}
// Effectively a GOTO <next insn> without the need to emit an explicit GOTO instruction.
@ -121,232 +108,187 @@ impl ProgramBuilder {
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!(matches!(label, BranchOffset::Label(_)));
assert!(matches!(to_offset, BranchOffset::Offset(_)));
self.label_to_resolved_offset
.insert(label.to_label_value(), to_offset.to_offset_int());
}
/// Resolve unresolved labels to a specific offset in the instruction list.
///
/// This function updates all instructions that reference the given label
/// to point to the specified offset. It ensures that the label and offset
/// are valid and updates the target program counter (PC) of each instruction
/// that references the label.
///
/// # Arguments
///
/// * `label` - The label to resolve.
/// * `to_offset` - The offset to which the labeled instructions should be resolved to.
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];
/// This function scans all instructions and resolves any labels to their corresponding offsets.
/// It ensures that all labels are resolved correctly and updates the target program counter (PC)
/// of each instruction that references a label.
pub fn resolve_labels(&mut self) {
let resolve = |pc: &mut BranchOffset, insn_name: &str| {
if let BranchOffset::Label(label) = pc {
let to_offset = *self.label_to_resolved_offset.get(label).unwrap_or_else(|| {
panic!("Reference to undefined label in {}: {}", insn_name, label)
});
*pc = BranchOffset::Offset(to_offset);
}
};
for insn in self.insns.iter_mut() {
match insn {
Insn::Init { target_pc } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "Init");
}
Insn::Eq {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "Eq");
}
Insn::Ne {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "Ne");
}
Insn::Lt {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "Lt");
}
Insn::Le {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "Le");
}
Insn::Gt {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "Gt");
}
Insn::Ge {
lhs: _lhs,
rhs: _rhs,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "Ge");
}
Insn::If {
reg: _reg,
target_pc,
null_reg: _,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "If");
}
Insn::IfNot {
reg: _reg,
target_pc,
null_reg: _,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "IfNot");
}
Insn::RewindAwait {
cursor_id: _cursor_id,
pc_if_empty,
} => {
assert!(*pc_if_empty < 0);
*pc_if_empty = to_offset;
resolve(pc_if_empty, "RewindAwait");
}
Insn::LastAwait {
cursor_id: _cursor_id,
pc_if_empty,
} => {
assert!(*pc_if_empty < 0);
*pc_if_empty = to_offset;
resolve(pc_if_empty, "LastAwait");
}
Insn::Goto { target_pc } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "Goto");
}
Insn::DecrJumpZero {
reg: _reg,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "DecrJumpZero");
}
Insn::SorterNext {
cursor_id: _cursor_id,
pc_if_next,
} => {
assert!(*pc_if_next < 0);
*pc_if_next = to_offset;
resolve(pc_if_next, "SorterNext");
}
Insn::SorterSort { pc_if_empty, .. } => {
assert!(*pc_if_empty < 0);
*pc_if_empty = to_offset;
resolve(pc_if_empty, "SorterSort");
}
Insn::NotNull {
reg: _reg,
target_pc,
} => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "NotNull");
}
Insn::IfPos { target_pc, .. } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "IfPos");
}
Insn::NextAwait { pc_if_next, .. } => {
assert!(*pc_if_next < 0);
*pc_if_next = to_offset;
resolve(pc_if_next, "NextAwait");
}
Insn::PrevAwait { pc_if_next, .. } => {
assert!(*pc_if_next < 0);
*pc_if_next = to_offset;
resolve(pc_if_next, "PrevAwait");
}
Insn::InitCoroutine {
yield_reg: _,
jump_on_definition,
start_offset: _,
} => {
*jump_on_definition = to_offset;
resolve(jump_on_definition, "InitCoroutine");
}
Insn::NotExists {
cursor: _,
rowid_reg: _,
target_pc,
} => {
*target_pc = to_offset;
resolve(target_pc, "NotExists");
}
Insn::Yield {
yield_reg: _,
end_offset,
} => {
*end_offset = to_offset;
resolve(end_offset, "Yield");
}
Insn::SeekRowid { target_pc, .. } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "SeekRowid");
}
Insn::Gosub { target_pc, .. } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "Gosub");
}
Insn::Jump { target_pc_eq, .. } => {
// FIXME: this current implementation doesnt scale for insns that
// have potentially multiple label dependencies.
assert!(*target_pc_eq < 0);
*target_pc_eq = to_offset;
Insn::Jump {
target_pc_eq,
target_pc_lt,
target_pc_gt,
} => {
resolve(target_pc_eq, "Jump");
resolve(target_pc_lt, "Jump");
resolve(target_pc_gt, "Jump");
}
Insn::SeekGE { target_pc, .. } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "SeekGE");
}
Insn::SeekGT { target_pc, .. } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "SeekGT");
}
Insn::IdxGE { target_pc, .. } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "IdxGE");
}
Insn::IdxGT { target_pc, .. } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
resolve(target_pc, "IdxGT");
}
Insn::IsNull { src: _, target_pc } => {
assert!(*target_pc < 0);
*target_pc = to_offset;
}
_ => {
todo!("missing resolve_label for {:?}", insn);
resolve(target_pc, "IsNull");
}
_ => continue,
}
}
label_references.clear();
self.label_to_resolved_offset.clear();
}
// translate table to cursor id
@ -361,23 +303,12 @@ impl ProgramBuilder {
.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,
mut self,
database_header: Rc<RefCell<DatabaseHeader>>,
connection: Weak<Connection>,
) -> 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()?"
);
self.resolve_labels();
assert!(
self.constant_insns.is_empty(),
"constant_insns is not empty when build() is called, did you forget to call emit_constant_insns()?"

View file

@ -13,11 +13,11 @@ pub fn insn_to_str(
Insn::Init { target_pc } => (
"Init",
0,
*target_pc as i32,
target_pc.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("Start at {}", target_pc),
format!("Start at {}", target_pc.to_debug_int()),
),
Insn::Add { lhs, rhs, dest } => (
"Add",
@ -114,11 +114,11 @@ pub fn insn_to_str(
Insn::NotNull { reg, target_pc } => (
"NotNull",
*reg as i32,
*target_pc as i32,
target_pc.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("r[{}]!=NULL -> goto {}", reg, target_pc),
format!("r[{}]!=NULL -> goto {}", reg, target_pc.to_debug_int()),
),
Insn::Compare {
start_reg_a,
@ -145,9 +145,9 @@ pub fn insn_to_str(
target_pc_gt,
} => (
"Jump",
*target_pc_lt as i32,
*target_pc_eq as i32,
*target_pc_gt as i32,
target_pc_lt.to_debug_int(),
target_pc_eq.to_debug_int(),
target_pc_gt.to_debug_int(),
OwnedValue::build_text(Rc::new("".to_string())),
0,
"".to_string(),
@ -178,13 +178,16 @@ pub fn insn_to_str(
} => (
"IfPos",
*reg as i32,
*target_pc as i32,
target_pc.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!(
"r[{}]>0 -> r[{}]-={}, goto {}",
reg, reg, decrement_by, target_pc
reg,
reg,
decrement_by,
target_pc.to_debug_int()
),
),
Insn::Eq {
@ -195,10 +198,15 @@ pub fn insn_to_str(
"Eq",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
target_pc.to_debug_int(),
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("if r[{}]==r[{}] goto {}", lhs, rhs, target_pc),
format!(
"if r[{}]==r[{}] goto {}",
lhs,
rhs,
target_pc.to_debug_int()
),
),
Insn::Ne {
lhs,
@ -208,10 +216,15 @@ pub fn insn_to_str(
"Ne",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
target_pc.to_debug_int(),
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("if r[{}]!=r[{}] goto {}", lhs, rhs, target_pc),
format!(
"if r[{}]!=r[{}] goto {}",
lhs,
rhs,
target_pc.to_debug_int()
),
),
Insn::Lt {
lhs,
@ -221,10 +234,10 @@ pub fn insn_to_str(
"Lt",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
target_pc.to_debug_int(),
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("if r[{}]<r[{}] goto {}", lhs, rhs, target_pc),
format!("if r[{}]<r[{}] goto {}", lhs, rhs, target_pc.to_debug_int()),
),
Insn::Le {
lhs,
@ -234,10 +247,15 @@ pub fn insn_to_str(
"Le",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
target_pc.to_debug_int(),
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("if r[{}]<=r[{}] goto {}", lhs, rhs, target_pc),
format!(
"if r[{}]<=r[{}] goto {}",
lhs,
rhs,
target_pc.to_debug_int()
),
),
Insn::Gt {
lhs,
@ -247,10 +265,10 @@ pub fn insn_to_str(
"Gt",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
target_pc.to_debug_int(),
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("if r[{}]>r[{}] goto {}", lhs, rhs, target_pc),
format!("if r[{}]>r[{}] goto {}", lhs, rhs, target_pc.to_debug_int()),
),
Insn::Ge {
lhs,
@ -260,10 +278,15 @@ pub fn insn_to_str(
"Ge",
*lhs as i32,
*rhs as i32,
*target_pc as i32,
target_pc.to_debug_int(),
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("if r[{}]>=r[{}] goto {}", lhs, rhs, target_pc),
format!(
"if r[{}]>=r[{}] goto {}",
lhs,
rhs,
target_pc.to_debug_int()
),
),
Insn::If {
reg,
@ -272,11 +295,11 @@ pub fn insn_to_str(
} => (
"If",
*reg as i32,
*target_pc as i32,
target_pc.to_debug_int(),
*null_reg as i32,
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("if r[{}] goto {}", reg, target_pc),
format!("if r[{}] goto {}", reg, target_pc.to_debug_int()),
),
Insn::IfNot {
reg,
@ -285,11 +308,11 @@ pub fn insn_to_str(
} => (
"IfNot",
*reg as i32,
*target_pc as i32,
target_pc.to_debug_int(),
*null_reg as i32,
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("if !r[{}] goto {}", reg, target_pc),
format!("if !r[{}] goto {}", reg, target_pc.to_debug_int()),
),
Insn::OpenReadAsync {
cursor_id,
@ -347,7 +370,7 @@ pub fn insn_to_str(
} => (
"RewindAwait",
*cursor_id as i32,
*pc_if_empty as i32,
pc_if_empty.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -431,7 +454,7 @@ pub fn insn_to_str(
} => (
"NextAwait",
*cursor_id as i32,
*pc_if_next as i32,
pc_if_next.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -461,7 +484,7 @@ pub fn insn_to_str(
Insn::Goto { target_pc } => (
"Goto",
0,
*target_pc as i32,
target_pc.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -473,7 +496,7 @@ pub fn insn_to_str(
} => (
"Gosub",
*return_reg as i32,
*target_pc as i32,
target_pc.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -562,7 +585,7 @@ pub fn insn_to_str(
"SeekRowid",
*cursor_id as i32,
*src_reg as i32,
*target_pc as i32,
target_pc.to_debug_int(),
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!(
@ -572,7 +595,7 @@ pub fn insn_to_str(
.0
.as_ref()
.unwrap_or(&format!("cursor {}", cursor_id)),
target_pc
target_pc.to_debug_int()
),
),
Insn::DeferredSeek {
@ -596,7 +619,7 @@ pub fn insn_to_str(
} => (
"SeekGT",
*cursor_id as i32,
*target_pc as i32,
target_pc.to_debug_int(),
*start_reg as i32,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -611,7 +634,7 @@ pub fn insn_to_str(
} => (
"SeekGE",
*cursor_id as i32,
*target_pc as i32,
target_pc.to_debug_int(),
*start_reg as i32,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -625,7 +648,7 @@ pub fn insn_to_str(
} => (
"IdxGT",
*cursor_id as i32,
*target_pc as i32,
target_pc.to_debug_int(),
*start_reg as i32,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -639,7 +662,7 @@ pub fn insn_to_str(
} => (
"IdxGE",
*cursor_id as i32,
*target_pc as i32,
target_pc.to_debug_int(),
*start_reg as i32,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -648,11 +671,11 @@ pub fn insn_to_str(
Insn::DecrJumpZero { reg, target_pc } => (
"DecrJumpZero",
*reg as i32,
*target_pc as i32,
target_pc.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("if (--r[{}]==0) goto {}", reg, target_pc),
format!("if (--r[{}]==0) goto {}", reg, target_pc.to_debug_int()),
),
Insn::AggStep {
func,
@ -742,7 +765,7 @@ pub fn insn_to_str(
} => (
"SorterSort",
*cursor_id as i32,
*pc_if_empty as i32,
pc_if_empty.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -754,7 +777,7 @@ pub fn insn_to_str(
} => (
"SorterNext",
*cursor_id as i32,
*pc_if_next as i32,
pc_if_next.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -792,8 +815,8 @@ pub fn insn_to_str(
} => (
"InitCoroutine",
*yield_reg as i32,
*jump_on_definition as i32,
*start_offset as i32,
jump_on_definition.to_debug_int(),
start_offset.to_debug_int(),
OwnedValue::build_text(Rc::new("".to_string())),
0,
"".to_string(),
@ -813,7 +836,7 @@ pub fn insn_to_str(
} => (
"Yield",
*yield_reg as i32,
*end_offset as i32,
end_offset.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -898,7 +921,7 @@ pub fn insn_to_str(
} => (
"NotExists",
*cursor as i32,
*target_pc as i32,
target_pc.to_debug_int(),
*rowid_reg as i32,
OwnedValue::build_text(Rc::new("".to_string())),
0,
@ -968,11 +991,11 @@ pub fn insn_to_str(
Insn::IsNull { src, target_pc } => (
"IsNull",
*src as i32,
*target_pc as i32,
target_pc.to_debug_int(),
0,
OwnedValue::build_text(Rc::new("".to_string())),
0,
format!("if (r[{}]==NULL) goto {}", src, target_pc),
format!("if (r[{}]==NULL) goto {}", src, target_pc.to_debug_int()),
),
Insn::ParseSchema { db, where_clause } => (
"ParseSchema",

View file

@ -58,13 +58,75 @@ use std::cell::RefCell;
use std::collections::{BTreeMap, HashMap};
use std::rc::{Rc, Weak};
pub type BranchOffset = i64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
/// Represents a target for a jump instruction.
/// Stores 32-bit ints to keep the enum word-sized.
pub enum BranchOffset {
/// A label is a named location in the program.
/// If there are references to it, it must always be resolved to an Offset
/// via program.resolve_label().
Label(i32),
/// An offset is a direct index into the instruction list.
Offset(InsnReference),
/// A placeholder is a temporary value to satisfy the compiler.
/// It must be set later.
Placeholder,
}
impl BranchOffset {
/// Returns true if the branch offset is a label.
pub fn is_label(&self) -> bool {
matches!(self, BranchOffset::Label(_))
}
/// Returns true if the branch offset is an offset.
pub fn is_offset(&self) -> bool {
matches!(self, BranchOffset::Offset(_))
}
/// Returns the offset value. Panics if the branch offset is a label or placeholder.
pub fn to_offset_int(&self) -> InsnReference {
match self {
BranchOffset::Label(v) => unreachable!("Unresolved label: {}", v),
BranchOffset::Offset(v) => *v,
BranchOffset::Placeholder => unreachable!("Unresolved placeholder"),
}
}
/// Returns the label value. Panics if the branch offset is an offset or placeholder.
pub fn to_label_value(&self) -> i32 {
match self {
BranchOffset::Label(v) => *v,
BranchOffset::Offset(_) => unreachable!("Offset cannot be converted to label value"),
BranchOffset::Placeholder => unreachable!("Unresolved placeholder"),
}
}
/// Returns the branch offset as a signed integer.
/// Used in explain output, where we don't want to panic in case we have an unresolved
/// label or placeholder.
pub fn to_debug_int(&self) -> i32 {
match self {
BranchOffset::Label(v) => *v,
BranchOffset::Offset(v) => *v as i32,
BranchOffset::Placeholder => i32::MAX,
}
}
/// Adds an integer value to the branch offset.
/// Returns a new branch offset.
/// Panics if the branch offset is a label or placeholder.
pub fn add<N: Into<u32>>(self, n: N) -> BranchOffset {
BranchOffset::Offset(self.to_offset_int() + n.into())
}
}
pub type CursorID = usize;
pub type PageIdx = usize;
// Index of insn in list of insns
type InsnReference = usize;
type InsnReference = u32;
pub enum StepResult<'a> {
Done,
@ -101,7 +163,7 @@ impl RegexCache {
/// The program state describes the environment in which the program executes.
pub struct ProgramState {
pub pc: BranchOffset,
pub pc: InsnReference,
cursors: RefCell<BTreeMap<CursorID, Box<dyn Cursor>>>,
registers: Vec<OwnedValue>,
last_compare: Option<std::cmp::Ordering>,
@ -151,7 +213,7 @@ pub struct Program {
pub insns: Vec<Insn>,
pub cursor_ref: Vec<(Option<String>, Option<Table>)>,
pub database_header: Rc<RefCell<DatabaseHeader>>,
pub comments: HashMap<BranchOffset, &'static str>,
pub comments: HashMap<InsnReference, &'static str>,
pub connection: Weak<Connection>,
pub auto_commit: bool,
}
@ -189,8 +251,8 @@ impl Program {
let mut cursors = state.cursors.borrow_mut();
match insn {
Insn::Init { target_pc } => {
assert!(*target_pc >= 0);
state.pc = *target_pc;
assert!(target_pc.is_offset());
state.pc = target_pc.to_offset_int();
}
Insn::Add { lhs, rhs, dest } => {
state.registers[*dest] =
@ -278,6 +340,9 @@ impl Program {
target_pc_eq,
target_pc_gt,
} => {
assert!(target_pc_lt.is_offset());
assert!(target_pc_eq.is_offset());
assert!(target_pc_gt.is_offset());
let cmp = state.last_compare.take();
if cmp.is_none() {
return Err(LimboError::InternalError(
@ -289,8 +354,7 @@ impl Program {
std::cmp::Ordering::Equal => *target_pc_eq,
std::cmp::Ordering::Greater => *target_pc_gt,
};
assert!(target_pc >= 0);
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
}
Insn::Move {
source_reg,
@ -313,12 +377,12 @@ impl Program {
target_pc,
decrement_by,
} => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
let reg = *reg;
let target_pc = *target_pc;
match &state.registers[reg] {
OwnedValue::Integer(n) if *n > 0 => {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
state.registers[reg] = OwnedValue::Integer(*n - *decrement_by as i64);
}
OwnedValue::Integer(_) => {
@ -332,7 +396,7 @@ impl Program {
}
}
Insn::NotNull { reg, target_pc } => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
let reg = *reg;
let target_pc = *target_pc;
match &state.registers[reg] {
@ -340,7 +404,7 @@ impl Program {
state.pc += 1;
}
_ => {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
}
}
}
@ -350,17 +414,17 @@ impl Program {
rhs,
target_pc,
} => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
let lhs = *lhs;
let rhs = *rhs;
let target_pc = *target_pc;
match (&state.registers[lhs], &state.registers[rhs]) {
(_, OwnedValue::Null) | (OwnedValue::Null, _) => {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
}
_ => {
if state.registers[lhs] == state.registers[rhs] {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -372,17 +436,17 @@ impl Program {
rhs,
target_pc,
} => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
let lhs = *lhs;
let rhs = *rhs;
let target_pc = *target_pc;
match (&state.registers[lhs], &state.registers[rhs]) {
(_, OwnedValue::Null) | (OwnedValue::Null, _) => {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
}
_ => {
if state.registers[lhs] != state.registers[rhs] {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -394,17 +458,17 @@ impl Program {
rhs,
target_pc,
} => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
let lhs = *lhs;
let rhs = *rhs;
let target_pc = *target_pc;
match (&state.registers[lhs], &state.registers[rhs]) {
(_, OwnedValue::Null) | (OwnedValue::Null, _) => {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
}
_ => {
if state.registers[lhs] < state.registers[rhs] {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -416,17 +480,17 @@ impl Program {
rhs,
target_pc,
} => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
let lhs = *lhs;
let rhs = *rhs;
let target_pc = *target_pc;
match (&state.registers[lhs], &state.registers[rhs]) {
(_, OwnedValue::Null) | (OwnedValue::Null, _) => {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
}
_ => {
if state.registers[lhs] <= state.registers[rhs] {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -438,17 +502,17 @@ impl Program {
rhs,
target_pc,
} => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
let lhs = *lhs;
let rhs = *rhs;
let target_pc = *target_pc;
match (&state.registers[lhs], &state.registers[rhs]) {
(_, OwnedValue::Null) | (OwnedValue::Null, _) => {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
}
_ => {
if state.registers[lhs] > state.registers[rhs] {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -460,17 +524,17 @@ impl Program {
rhs,
target_pc,
} => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
let lhs = *lhs;
let rhs = *rhs;
let target_pc = *target_pc;
match (&state.registers[lhs], &state.registers[rhs]) {
(_, OwnedValue::Null) | (OwnedValue::Null, _) => {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
}
_ => {
if state.registers[lhs] >= state.registers[rhs] {
state.pc = target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -482,9 +546,9 @@ impl Program {
target_pc,
null_reg,
} => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
if exec_if(&state.registers[*reg], &state.registers[*null_reg], false) {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -494,9 +558,9 @@ impl Program {
target_pc,
null_reg,
} => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
if exec_if(&state.registers[*reg], &state.registers[*null_reg], true) {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -539,10 +603,11 @@ impl Program {
cursor_id,
pc_if_empty,
} => {
assert!(pc_if_empty.is_offset());
let cursor = cursors.get_mut(cursor_id).unwrap();
cursor.wait_for_completion()?;
if cursor.is_empty() {
state.pc = *pc_if_empty;
state.pc = pc_if_empty.to_offset_int();
} else {
state.pc += 1;
}
@ -551,10 +616,11 @@ impl Program {
cursor_id,
pc_if_empty,
} => {
assert!(pc_if_empty.is_offset());
let cursor = cursors.get_mut(cursor_id).unwrap();
cursor.wait_for_completion()?;
if cursor.is_empty() {
state.pc = *pc_if_empty;
state.pc = pc_if_empty.to_offset_int();
} else {
state.pc += 1;
}
@ -620,11 +686,11 @@ impl Program {
cursor_id,
pc_if_next,
} => {
assert!(*pc_if_next >= 0);
assert!(pc_if_next.is_offset());
let cursor = cursors.get_mut(cursor_id).unwrap();
cursor.wait_for_completion()?;
if !cursor.is_empty() {
state.pc = *pc_if_next;
state.pc = pc_if_next.to_offset_int();
} else {
state.pc += 1;
}
@ -633,11 +699,11 @@ impl Program {
cursor_id,
pc_if_next,
} => {
assert!(*pc_if_next >= 0);
assert!(pc_if_next.is_offset());
let cursor = cursors.get_mut(cursor_id).unwrap();
cursor.wait_for_completion()?;
if !cursor.is_empty() {
state.pc = *pc_if_next;
state.pc = pc_if_next.to_offset_int();
} else {
state.pc += 1;
}
@ -705,24 +771,22 @@ impl Program {
state.pc += 1;
}
Insn::Goto { target_pc } => {
assert!(*target_pc >= 0);
state.pc = *target_pc;
assert!(target_pc.is_offset());
state.pc = target_pc.to_offset_int();
}
Insn::Gosub {
target_pc,
return_reg,
} => {
assert!(*target_pc >= 0);
state.registers[*return_reg] = OwnedValue::Integer(state.pc + 1);
state.pc = *target_pc;
assert!(target_pc.is_offset());
state.registers[*return_reg] = OwnedValue::Integer((state.pc + 1) as i64);
state.pc = target_pc.to_offset_int();
}
Insn::Return { return_reg } => {
if let OwnedValue::Integer(pc) = state.registers[*return_reg] {
if pc < 0 {
return Err(LimboError::InternalError(
"Return register is negative".to_string(),
));
}
let pc: u32 = pc
.try_into()
.unwrap_or_else(|_| panic!("Return register is negative: {}", pc));
state.pc = pc;
} else {
return Err(LimboError::InternalError(
@ -779,11 +843,12 @@ impl Program {
src_reg,
target_pc,
} => {
assert!(target_pc.is_offset());
let cursor = cursors.get_mut(cursor_id).unwrap();
let rowid = match &state.registers[*src_reg] {
OwnedValue::Integer(rowid) => *rowid as u64,
OwnedValue::Null => {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
continue;
}
other => {
@ -794,7 +859,7 @@ impl Program {
};
let found = return_if_io!(cursor.seek(SeekKey::TableRowId(rowid), SeekOp::EQ));
if !found {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -813,6 +878,7 @@ impl Program {
target_pc,
is_index,
} => {
assert!(target_pc.is_offset());
if *is_index {
let cursor = cursors.get_mut(cursor_id).unwrap();
let record_from_regs: OwnedRecord =
@ -821,7 +887,7 @@ impl Program {
cursor.seek(SeekKey::IndexKey(&record_from_regs), SeekOp::GE)
);
if !found {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -844,7 +910,7 @@ impl Program {
let found =
return_if_io!(cursor.seek(SeekKey::TableRowId(rowid), SeekOp::GE));
if !found {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -857,6 +923,7 @@ impl Program {
target_pc,
is_index,
} => {
assert!(target_pc.is_offset());
if *is_index {
let cursor = cursors.get_mut(cursor_id).unwrap();
let record_from_regs: OwnedRecord =
@ -865,7 +932,7 @@ impl Program {
cursor.seek(SeekKey::IndexKey(&record_from_regs), SeekOp::GT)
);
if !found {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -888,7 +955,7 @@ impl Program {
let found =
return_if_io!(cursor.seek(SeekKey::TableRowId(rowid), SeekOp::GT));
if !found {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -900,7 +967,7 @@ impl Program {
num_regs,
target_pc,
} => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
let cursor = cursors.get_mut(cursor_id).unwrap();
let record_from_regs: OwnedRecord =
make_owned_record(&state.registers, start_reg, num_regs);
@ -909,12 +976,12 @@ impl Program {
if idx_record.values[..idx_record.values.len() - 1]
>= *record_from_regs.values
{
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
} else {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
}
}
Insn::IdxGT {
@ -923,6 +990,7 @@ impl Program {
num_regs,
target_pc,
} => {
assert!(target_pc.is_offset());
let cursor = cursors.get_mut(cursor_id).unwrap();
let record_from_regs: OwnedRecord =
make_owned_record(&state.registers, start_reg, num_regs);
@ -931,21 +999,21 @@ impl Program {
if idx_record.values[..idx_record.values.len() - 1]
> *record_from_regs.values
{
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
} else {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
}
}
Insn::DecrJumpZero { reg, target_pc } => {
assert!(*target_pc >= 0);
assert!(target_pc.is_offset());
match state.registers[*reg] {
OwnedValue::Integer(n) => {
let n = n - 1;
if n == 0 {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.registers[*reg] = OwnedValue::Integer(n);
state.pc += 1;
@ -1250,18 +1318,18 @@ impl Program {
cursor.rewind()?;
state.pc += 1;
} else {
state.pc = *pc_if_empty;
state.pc = pc_if_empty.to_offset_int();
}
}
Insn::SorterNext {
cursor_id,
pc_if_next,
} => {
assert!(*pc_if_next >= 0);
assert!(pc_if_next.is_offset());
let cursor = cursors.get_mut(cursor_id).unwrap();
return_if_io!(cursor.next());
if !cursor.is_empty() {
state.pc = *pc_if_next;
state.pc = pc_if_next.to_offset_int();
} else {
state.pc += 1;
}
@ -1726,18 +1794,23 @@ impl Program {
jump_on_definition,
start_offset,
} => {
assert!(*jump_on_definition >= 0);
state.registers[*yield_reg] = OwnedValue::Integer(*start_offset);
assert!(jump_on_definition.is_offset());
let start_offset = start_offset.to_offset_int();
state.registers[*yield_reg] = OwnedValue::Integer(start_offset as i64);
state.ended_coroutine.insert(*yield_reg, false);
state.pc = if *jump_on_definition == 0 {
let jump_on_definition = jump_on_definition.to_offset_int();
state.pc = if jump_on_definition == 0 {
state.pc + 1
} else {
*jump_on_definition
jump_on_definition
};
}
Insn::EndCoroutine { yield_reg } => {
if let OwnedValue::Integer(pc) = state.registers[*yield_reg] {
state.ended_coroutine.insert(*yield_reg, true);
let pc: u32 = pc
.try_into()
.unwrap_or_else(|_| panic!("EndCoroutine: pc overflow: {}", pc));
state.pc = pc - 1; // yield jump is always next to yield. Here we substract 1 to go back to yield instruction
} else {
unreachable!();
@ -1753,12 +1826,15 @@ impl Program {
.get(yield_reg)
.expect("coroutine not initialized")
{
state.pc = *end_offset;
state.pc = end_offset.to_offset_int();
} else {
let pc: u32 = pc
.try_into()
.unwrap_or_else(|_| panic!("Yield: pc overflow: {}", pc));
// swap the program counter with the value in the yield register
// this is the mechanism that allows jumping back and forth between the coroutine and the caller
(state.pc, state.registers[*yield_reg]) =
(pc, OwnedValue::Integer(state.pc + 1));
(pc, OwnedValue::Integer((state.pc + 1) as i64));
}
} else {
unreachable!(
@ -1842,7 +1918,7 @@ impl Program {
if exists {
state.pc += 1;
} else {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
}
}
// this cursor may be reused for next insert
@ -1894,7 +1970,7 @@ impl Program {
}
Insn::IsNull { src, target_pc } => {
if matches!(state.registers[*src], OwnedValue::Null) {
state.pc = *target_pc;
state.pc = target_pc.to_offset_int();
} else {
state.pc += 1;
}
@ -1976,7 +2052,7 @@ fn trace_insn(program: &Program, addr: InsnReference, insn: &Insn) {
addr,
insn,
String::new(),
program.comments.get(&(addr as BranchOffset)).copied()
program.comments.get(&(addr as u32)).copied()
)
);
}
@ -1987,7 +2063,7 @@ fn print_insn(program: &Program, addr: InsnReference, insn: &Insn, indent: Strin
addr,
insn,
indent,
program.comments.get(&(addr as BranchOffset)).copied(),
program.comments.get(&(addr as u32)).copied(),
);
println!("{}", s);
}