Merge pull request #732 from notoria/indexing

Implemented a different way to index clauses
This commit is contained in:
Mark Thom
2020-12-22 14:51:10 -07:00
committed by GitHub
7 changed files with 164 additions and 50 deletions

View File

@@ -226,6 +226,37 @@ arithmetic operators with the usual precedences,
New operators can be defined using the `op` declaration.
### First instantiated argument indexing
Scryer Prolog indexes on the leftmost argument that is not a variable
in all clauses of a predicate's definition. We call this strategy
first *instantiated* argument indexing.
A key motivation for first instantiated argument indexing is to enable
indexing for meta-predicates such as `maplist/N` and `foldl/N`, where
the first argument is a goal or partial goal that is a variable in the
definition of these predicates and therefore cannot be used for
indexing.
For example, a natural definiton of `maplist/2` reads:
```
maplist(_, []).
maplist(Goal, [L|Ls]) :-
call(Goal, L),
maplist(Goal, Ls).
```
In this case, first instantianted argument indexing automatically uses
the *second* argument for indexing, and thus prevents choicepoints for
calls with lists of fixed lengths (and deterministic goals).
Conveniently, no auxiliary predicates with reorderd arguments are
needed to benefit from indexing in such cases.
Conventional first argument indexing naturally arises as a
special case of this strategy, if the first argument is instantiated
in any clause of a predicate's definition.
### Strings and partial strings
In Scryer Prolog, the default value of the Prolog flag `double_quotes`

View File

@@ -1,3 +1,4 @@
/// Code generation to WAM-like instructions.
use crate::prolog_parser::ast::*;
use crate::allocator::*;
@@ -857,21 +858,63 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<TermMarker> {
Ok(code)
}
fn split_predicate(clauses: &Vec<PredicateClause>) -> Vec<(usize, usize)> {
/// Returns the index of the first instantiated argument.
fn first_instantiated_index(clauses: &[PredicateClause]) -> Option<usize> {
let mut optimal_index = None;
let has_args = match clauses.first() {
Some(clause) => match clause.args() {
Some(args) => !args.is_empty(),
None => false,
},
None => false,
};
if !has_args {
return optimal_index;
}
for clause in clauses.iter() {
let args = clause.args().unwrap();
for (i, arg) in args.iter().enumerate() {
if let Some(optimal_index) = optimal_index {
if i >= optimal_index {
break;
}
}
match **arg {
Term::AnonVar | Term::Var(..) => (),
_ => {
match optimal_index {
Some(ref mut optimal_i) => *optimal_i = i,
None => optimal_index = Some(i),
}
break;
}
}
}
}
optimal_index
}
fn split_predicate(clauses: &[PredicateClause], optimal_index: usize) -> Vec<(usize, usize)> {
let mut subseqs = Vec::new();
let mut left_index = 0;
for (right_index, clause) in clauses.iter().enumerate() {
match clause.first_arg() {
Some(&Term::Var(_, _)) | Some(&Term::AnonVar) => {
if left_index < right_index {
subseqs.push((left_index, right_index));
}
if clauses.first().unwrap().args().is_some() {
for (right_index, clause) in clauses.iter().enumerate() {
// Can unwrap safely.
if let Some(arg) = clause.args().unwrap().iter().nth(optimal_index) {
match **arg {
Term::Var(..) | Term::AnonVar => {
if left_index < right_index {
subseqs.push((left_index, right_index));
}
subseqs.push((right_index, right_index + 1));
left_index = right_index + 1;
subseqs.push((right_index, right_index + 1));
left_index = right_index + 1;
}
_ => (),
}
}
_ => {}
}
}
@@ -901,6 +944,7 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<TermMarker> {
fn compile_pred_subseq<'b: 'a>(
&mut self,
clauses: &'b [PredicateClause],
optimal_index: usize,
) -> Result<Code, ParserError> {
let mut code_body = Vec::new();
let mut code_offsets = CodeOffsets::new();
@@ -910,9 +954,9 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<TermMarker> {
for (i, clause) in clauses.iter().enumerate() {
self.marker.reset();
let mut clause_code = match clause {
&PredicateClause::Fact(ref fact, ..) => self.compile_fact(fact),
&PredicateClause::Rule(ref rule, ..) => self.compile_rule(rule)?,
let mut clause_code = match *clause {
PredicateClause::Fact(ref fact, ..) => self.compile_fact(fact),
PredicateClause::Rule(ref rule, ..) => self.compile_rule(rule)?,
};
if num_clauses > 1 {
@@ -925,17 +969,24 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<TermMarker> {
code_body.push(Line::Choice(choice));
}
clause.first_arg().map(|arg| {
let arg = match clause.args() {
Some(args) => match args.iter().nth(optimal_index) {
Some(term) => Some(term),
None => None,
},
None => None,
};
if let Some(arg) = arg {
let index = code_body.len();
code_offsets.index_term(arg, index);
});
}
code_body.append(&mut clause_code);
}
let mut code = Vec::new();
code_offsets.add_indices(&mut code, code_body, optimal_index + 1);
code_offsets.add_indices(&mut code, code_body);
Ok(code)
}
@@ -944,11 +995,16 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<TermMarker> {
clauses: &'b Vec<PredicateClause>,
) -> Result<Code, ParserError> {
let mut code = Vec::new();
let split_pred = Self::split_predicate(&clauses);
let optimal_index = match Self::first_instantiated_index(&clauses) {
Some(index) => index,
None => 0, // Default to first argument indexing.
};
let split_pred = Self::split_predicate(&clauses, optimal_index);
let multi_seq = split_pred.len() > 1;
for (l, r) in split_pred {
let mut code_segment = self.compile_pred_subseq(&clauses[l..r])?;
let mut code_segment =
self.compile_pred_subseq(&clauses[l..r], optimal_index)?;
if multi_seq {
let choice = match l {

View File

@@ -332,10 +332,16 @@ pub enum PredicateClause {
}
impl PredicateClause {
pub fn first_arg(&self) -> Option<&Term> {
match self {
&PredicateClause::Fact(ref term, ..) => term.first_arg(),
&PredicateClause::Rule(ref rule, ..) => rule.head.1.first().map(|bt| bt.as_ref()),
// TODO: add this to `Term` in `prolog_parser` like `first_arg`.
pub fn args(&self) -> Option<&[Box<Term>]> {
match *self {
PredicateClause::Fact(ref term, ..) => {
match term {
Term::Clause(_, _, args, _) => Some(&args),
_ => None,
}
},
PredicateClause::Rule(ref rule, ..) => Some(&rule.head.1),
}
}

View File

@@ -143,8 +143,8 @@ impl CodeOffsets {
}
}
pub fn index_term(&mut self, first_arg: &Term, index: usize) {
match first_arg {
pub fn index_term(&mut self, optimal_arg: &Term, index: usize) {
match optimal_arg {
&Term::Clause(_, ref name, ref terms, _) => {
let code = self
.structures
@@ -237,12 +237,17 @@ impl CodeOffsets {
fn switch_on_constant(
con_ind: IndexMap<Constant, ThirdLevelIndex>,
prelude: &mut CodeDeque,
optimal_index: usize,
) -> IntIndex {
let con_ind = Self::second_level_index(con_ind, prelude);
if con_ind.len() > 1 {
let index = Self::flatten_index(con_ind, prelude.len());
let instr = IndexingInstruction::SwitchOnConstant(index.len(), index);
let instr = IndexingInstruction::SwitchOnConstant(
optimal_index,
index.len(),
index
);
prelude.push_front(Line::from(instr));
@@ -259,12 +264,17 @@ impl CodeOffsets {
fn switch_on_structure(
str_ind: IndexMap<(ClauseName, usize), ThirdLevelIndex>,
prelude: &mut CodeDeque,
optimal_index: usize,
) -> IntIndex {
let str_ind = Self::second_level_index(str_ind, prelude);
if str_ind.len() > 1 {
let index = Self::flatten_index(str_ind, prelude.len());
let instr = IndexingInstruction::SwitchOnStructure(index.len(), index);
let instr = IndexingInstruction::SwitchOnStructure(
optimal_index,
index.len(),
index
);
prelude.push_front(Line::from(instr));
@@ -325,7 +335,7 @@ impl CodeOffsets {
}
}
pub fn add_indices(self, code: &mut Code, mut code_body: Code) {
pub fn add_indices(self, code: &mut Code, mut code_body: Code, optimal_index: usize) {
if self.no_indices() {
*code = code_body;
return;
@@ -334,8 +344,10 @@ impl CodeOffsets {
let mut prelude = VecDeque::new();
let lst_loc = Self::switch_on_list(self.lists, &mut prelude);
let str_loc = Self::switch_on_structure(self.structures, &mut prelude);
let con_loc = Self::switch_on_constant(self.constants, &mut prelude);
let str_loc =
Self::switch_on_structure(self.structures, &mut prelude, optimal_index);
let con_loc =
Self::switch_on_constant(self.constants, &mut prelude, optimal_index);
let prelude_length = prelude.len();
@@ -355,8 +367,13 @@ impl CodeOffsets {
let con_loc = Self::switch_on_con_offset_from(con_loc, prelude.len());
let lst_loc = Self::switch_on_lst_offset_from(lst_loc, prelude.len());
let switch_instr =
IndexingInstruction::SwitchOnTerm(prelude.len() + 1, con_loc, lst_loc, str_loc);
let switch_instr = IndexingInstruction::SwitchOnTerm(
optimal_index,
prelude.len() + 1,
con_loc,
lst_loc,
str_loc
);
prelude.push_front(Line::from(switch_instr));

View File

@@ -143,6 +143,7 @@ impl IndexedChoiceInstruction {
}
}
/// A `Line` is an instruction (cf. page 98 of wambook).
#[derive(Debug)]
pub enum Line {
Arithmetic(ArithmeticInstruction),
@@ -424,11 +425,13 @@ impl ControlInstruction {
}
}
/// `IndexingInstruction` cf. page 110 of wambook.
#[derive(Debug)]
pub enum IndexingInstruction {
SwitchOnTerm(usize, usize, usize, usize),
SwitchOnConstant(usize, IndexMap<Constant, usize>),
SwitchOnStructure(usize, IndexMap<(ClauseName, usize), usize>),
// The first index is the optimal argument being indexed.
SwitchOnTerm(usize, usize, usize, usize, usize),
SwitchOnConstant(usize, usize, IndexMap<Constant, usize>),
SwitchOnStructure(usize, usize, IndexMap<(ClauseName, usize), usize>),
}
impl From<IndexingInstruction> for Line {
@@ -440,25 +443,26 @@ impl From<IndexingInstruction> for Line {
impl IndexingInstruction {
pub fn to_functor(&self) -> MachineStub {
match self {
&IndexingInstruction::SwitchOnTerm(vars, constants, lists, structures) => {
&IndexingInstruction::SwitchOnTerm(arg, vars, constants, lists, structures) => {
functor!(
"switch_on_term",
[integer(vars),
[integer(arg),
integer(vars),
integer(constants),
integer(lists),
integer(structures)]
)
}
&IndexingInstruction::SwitchOnConstant(constants, _) => {
&IndexingInstruction::SwitchOnConstant(arg, constants, _) => {
functor!(
"switch_on_constant",
[integer(constants)]
[integer(arg), integer(constants)]
)
}
&IndexingInstruction::SwitchOnStructure(structures, _) => {
&IndexingInstruction::SwitchOnStructure(arg, structures, _) => {
functor!(
"switch_on_structure",
[integer(structures)]
[integer(arg), integer(structures)]
)
}
}

View File

@@ -1392,8 +1392,8 @@ impl MachineState {
pub(super)
fn execute_indexing_instr(&mut self, instr: &IndexingInstruction) {
match instr {
&IndexingInstruction::SwitchOnTerm(v, c, l, s) => {
let addr = self[temp_v!(1)];
&IndexingInstruction::SwitchOnTerm(arg, v, c, l, s) => {
let addr = self[temp_v!(arg)];
let addr = self.store(self.deref(addr));
let offset = match addr {
@@ -1423,8 +1423,8 @@ impl MachineState {
o => self.p += o,
};
}
&IndexingInstruction::SwitchOnConstant(_, ref hm) => {
let addr = self[temp_v!(1)];
&IndexingInstruction::SwitchOnConstant(arg, _, ref hm) => {
let addr = self[temp_v!(arg)];
let addr = self.store(self.deref(addr));
let offset =
@@ -1445,8 +1445,8 @@ impl MachineState {
o => self.p += o,
};
}
&IndexingInstruction::SwitchOnStructure(_, ref hm) => {
let a1 = self.registers[1];
&IndexingInstruction::SwitchOnStructure(arg, _, ref hm) => {
let a1 = self.registers[arg];
let addr = self.store(self.deref(a1));
let offset = match addr {

View File

@@ -276,13 +276,13 @@ impl fmt::Display for ChoiceInstruction {
impl fmt::Display for IndexingInstruction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&IndexingInstruction::SwitchOnTerm(v, c, l, s) => {
write!(f, "switch_on_term {}, {}, {}, {}", v, c, l, s)
&IndexingInstruction::SwitchOnTerm(a, v, c, l, s) => {
write!(f, "switch_on_term {}, {}, {}, {}, {}", a, v, c, l, s)
}
&IndexingInstruction::SwitchOnConstant(num_cs, _) => {
&IndexingInstruction::SwitchOnConstant(_, num_cs, _) => {
write!(f, "switch_on_constant {}", num_cs)
}
&IndexingInstruction::SwitchOnStructure(num_ss, _) => {
&IndexingInstruction::SwitchOnStructure(_, num_ss, _) => {
write!(f, "switch_on_structure {}", num_ss)
}
}