diff --git a/README.md b/README.md index a0364d65..6688f01f 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/src/codegen.rs b/src/codegen.rs index a1811985..d260cfa0 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -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 { Ok(code) } - fn split_predicate(clauses: &Vec) -> Vec<(usize, usize)> { + /// Returns the index of the first instantiated argument. + fn first_instantiated_index(clauses: &[PredicateClause]) -> Option { + 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 { fn compile_pred_subseq<'b: 'a>( &mut self, clauses: &'b [PredicateClause], + optimal_index: usize, ) -> Result { let mut code_body = Vec::new(); let mut code_offsets = CodeOffsets::new(); @@ -910,9 +954,9 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { 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 { 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 { clauses: &'b Vec, ) -> Result { 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 { diff --git a/src/forms.rs b/src/forms.rs index 499ee875..be572bc4 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -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]> { + match *self { + PredicateClause::Fact(ref term, ..) => { + match term { + Term::Clause(_, _, args, _) => Some(&args), + _ => None, + } + }, + PredicateClause::Rule(ref rule, ..) => Some(&rule.head.1), } } diff --git a/src/indexing.rs b/src/indexing.rs index b93b3c93..bf5403bf 100644 --- a/src/indexing.rs +++ b/src/indexing.rs @@ -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, 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)); diff --git a/src/instructions.rs b/src/instructions.rs index 33997018..c3de3469 100644 --- a/src/instructions.rs +++ b/src/instructions.rs @@ -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), - 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), + SwitchOnStructure(usize, usize, IndexMap<(ClauseName, usize), usize>), } impl From for Line { @@ -440,25 +443,26 @@ impl From 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)] ) } } diff --git a/src/machine/machine_state_impl.rs b/src/machine/machine_state_impl.rs index 849e18f5..0e4a4e1c 100644 --- a/src/machine/machine_state_impl.rs +++ b/src/machine/machine_state_impl.rs @@ -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 { diff --git a/src/write.rs b/src/write.rs index 71289d56..cf40509d 100644 --- a/src/write.rs +++ b/src/write.rs @@ -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) } }