From 4854da4805688f6aac2cd0e466b9d10a27a46076 Mon Sep 17 00:00:00 2001 From: brightly-salty Date: Tue, 15 Dec 2020 06:59:26 -0600 Subject: [PATCH 01/11] Replace dirs with dirs-next --- Cargo.lock | 35 +++++++++++++++++++++++------------ Cargo.toml | 2 +- src/machine/mod.rs | 2 +- src/read.rs | 6 +++--- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4ee6d4c..0b2ca779 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,16 +265,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "dirs" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" -dependencies = [ - "cfg-if 0.1.10", - "dirs-sys", -] - [[package]] name = "dirs" version = "3.0.1" @@ -284,6 +274,16 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + [[package]] name = "dirs-sys" version = "0.3.5" @@ -295,6 +295,17 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.8", +] + [[package]] name = "divrem" version = "0.1.0" @@ -1220,7 +1231,7 @@ checksum = "1a5f54deba50e65ee4cf786dbc37e8b3c63bdccccbcf9d3a8a9fd0c1bb7e1984" dependencies = [ "bitflags", "cfg-if 1.0.0", - "dirs 3.0.1", + "dirs", "fs2", "libc", "log", @@ -1264,7 +1275,7 @@ dependencies = [ "chrono", "cpu-time", "crossterm", - "dirs 2.0.2", + "dirs-next", "divrem", "downcast", "git-version", diff --git a/Cargo.toml b/Cargo.toml index 2b834096..6d10a3ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ num = ["num-rug-adapter", "prolog_parser/num"] [dependencies] cpu-time = "1.0.0" crossterm = "0.16.0" -dirs = "2.0.2" +dirs-next = "2.0.0" divrem = "0.1.0" downcast = "0.10.0" git-version = "0.3.4" diff --git a/src/machine/mod.rs b/src/machine/mod.rs index 365b201e..daaf4e81 100644 --- a/src/machine/mod.rs +++ b/src/machine/mod.rs @@ -281,7 +281,7 @@ impl Machine { } fn compile_scryerrc(&mut self) { - let mut path = match dirs::home_dir() { + let mut path = match dirs_next::home_dir() { Some(path) => path, None => return, }; diff --git a/src/read.rs b/src/read.rs index f879e511..5e972a08 100644 --- a/src/read.rs +++ b/src/read.rs @@ -47,7 +47,7 @@ pub mod readline { #[inline] pub fn new(pending_input: String) -> Self { let mut rl = Editor::<()>::new(); - if let Some(mut path) = dirs::home_dir() { + if let Some(mut path) = dirs_next::home_dir() { path.push(HISTORY_FILE); if path.exists() { if rl.load_history(&path).is_err() { @@ -55,7 +55,7 @@ pub mod readline { } } } - + rl.bind_sequence(KeyEvent::from('\t'), Cmd::Insert(1, "\t".to_string())); ReadlineStream { rl, pending_input: Cursor::new(pending_input) } } @@ -95,7 +95,7 @@ pub mod readline { } fn save_history(&mut self) { - if let Some(mut path) = dirs::home_dir() { + if let Some(mut path) = dirs_next::home_dir() { path.push(HISTORY_FILE); if path.exists() { if self.rl.append_history(&path).is_err() { From 657e4f12bbeaf3af40959cae3b88af60f59d3c7c Mon Sep 17 00:00:00 2001 From: notoria Date: Sat, 19 Dec 2020 14:52:38 +0100 Subject: [PATCH 02/11] Implemented a different way to index clauses --- src/codegen.rs | 98 ++++++++++++++++++++++++------- src/forms.rs | 13 ++++ src/indexing.rs | 31 +++++++--- src/instructions.rs | 22 ++++--- src/machine/machine_state_impl.rs | 12 ++-- src/write.rs | 8 +-- 6 files changed, 137 insertions(+), 47 deletions(-) diff --git a/src/codegen.rs b/src/codegen.rs index a1811985..56128edc 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -857,21 +857,28 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { Ok(code) } - fn split_predicate(clauses: &Vec) -> Vec<(usize, usize)> { + fn split_predicate(clauses: &Vec, optimal_index: Option) -> 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)); - } - - subseqs.push((right_index, right_index + 1)); - left_index = right_index + 1; + if let Some(optimal_i) = optimal_index { + for (right_index, clause) in clauses.iter().enumerate() { + if clause.args().is_none() { + continue; + } + if let Some(arg) = clause.args().unwrap().iter().nth(optimal_i) { + 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; + } + _ => (), + } } - _ => {} } } @@ -901,6 +908,7 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { fn compile_pred_subseq<'b: 'a>( &mut self, clauses: &'b [PredicateClause], + optimal_index: Option, ) -> Result { let mut code_body = Vec::new(); let mut code_offsets = CodeOffsets::new(); @@ -910,9 +918,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 +933,28 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { code_body.push(Line::Choice(choice)); } - clause.first_arg().map(|arg| { - let index = code_body.len(); - code_offsets.index_term(arg, index); - }); + if let Some(optimal_i) = optimal_index { + let arg = match clause.args() { + Some(args) => match args.iter().nth(optimal_i) { + 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); + if let Some(optimal_i) = optimal_index { + code_offsets.add_indices(&mut code, code_body, optimal_i + 1); + } Ok(code) } @@ -944,11 +963,48 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { clauses: &'b Vec, ) -> Result { let mut code = Vec::new(); - let split_pred = Self::split_predicate(&clauses); + 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 { + 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; + } + } + } + } + } + match optimal_index { + // No good index or no argument, default to 0. + // TODO: Why? + None => optimal_index = Some(0), + Some(_) => (), + } + 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..5a7fcd2b 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -339,6 +339,19 @@ impl PredicateClause { } } + // 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), + } + } + pub fn arity(&self) -> usize { match self { &PredicateClause::Fact(ref term, ..) => { diff --git a/src/indexing.rs b/src/indexing.rs index b93b3c93..ccd63717 100644 --- a/src/indexing.rs +++ b/src/indexing.rs @@ -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) } } From 4efbc20a4f66c195b43e433547d1c19737fd62ac Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Sat, 19 Dec 2020 18:07:43 +0100 Subject: [PATCH 03/11] ENHANCED: format_//2, format/[2,3], portray_clause/1 are now deterministic. This works as soon as #732 is merged, since then maplist/N and foldl/N are deterministic in the required cases. This also resolves the extra choicepoint of time/1 (see #378). Many thanks to @notoria for implementing better indexing in #732, which allowed me to find this opportunity for improvement! --- src/lib/format.pl | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/lib/format.pl b/src/lib/format.pl index 50ea419c..f576a0dc 100644 --- a/src/lib/format.pl +++ b/src/lib/format.pl @@ -158,7 +158,7 @@ element_gluevar(glue(_,V), N, N) --> [V]. consume whitespace in the sense of format strings. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -cells([], Args, Tab, Es, _) --> +cells([], Args, Tab, Es, _) --> !, ( { Args == [] } -> cell(Tab, Tab, Es) ; { domain_error(empty_list, Args, format_//2) } ). @@ -404,32 +404,28 @@ Cs = [cell(0,4,[glue(' ',_A),chars("a"),glue(' ',_B)])] ?- phrase(format_("hello~n~tthere~6|", []), Ls). ?- format("~ta~t~4|", []). - a true -; false. + a true. ?- format("~ta~tb~tc~10|", []). - a b c true -; false. + a b c true. ?- format("~tabc~3|", []). ?- format("~ta~t~4|", []). ?- format("~ta~t~tb~tc~20|", []). - a b c true -; false. + a b c true. ?- format("~2f~n", [3]). 3.00 - true + true. ?- format("~20f", [0.1]). -0.10000000000000000000 true % this should use higher accuracy! -; false. +0.10000000000000000000 true. ?- X is atan(2), format("~7f~n", [X]). 1.1071487 - X = 1.1071487177940906 + X = 1.1071487177940906. ?- format("~`at~50|~n", []). aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa @@ -438,10 +434,10 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ?- format("~t~N", []). ?- format("~q", [.]). -'.' true +'.' true. ?- format("~12r", [300]). -210 true +210 true. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -537,7 +533,7 @@ a :- b, c, d. - true + true. ?- portray_clause([a,b,c,d]). From ec2c5a9b2c3a98609e992eee54918cab6ea4c143 Mon Sep 17 00:00:00 2001 From: notoria Date: Sat, 19 Dec 2020 21:04:17 +0100 Subject: [PATCH 04/11] Simplified the code, removed first_arg, the Option --- src/codegen.rs | 47 ++++++++++++++++++++--------------------------- src/forms.rs | 7 ------- src/indexing.rs | 4 ++-- 3 files changed, 22 insertions(+), 36 deletions(-) diff --git a/src/codegen.rs b/src/codegen.rs index 56128edc..eb68ae8b 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -857,16 +857,14 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { Ok(code) } - fn split_predicate(clauses: &Vec, optimal_index: Option) -> Vec<(usize, usize)> { + fn split_predicate(clauses: &Vec, optimal_index: usize) -> Vec<(usize, usize)> { let mut subseqs = Vec::new(); let mut left_index = 0; - if let Some(optimal_i) = optimal_index { + if clauses.first().unwrap().args().is_some() { for (right_index, clause) in clauses.iter().enumerate() { - if clause.args().is_none() { - continue; - } - if let Some(arg) = clause.args().unwrap().iter().nth(optimal_i) { + // 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 { @@ -908,7 +906,7 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { fn compile_pred_subseq<'b: 'a>( &mut self, clauses: &'b [PredicateClause], - optimal_index: Option, + optimal_index: usize, ) -> Result { let mut code_body = Vec::new(); let mut code_offsets = CodeOffsets::new(); @@ -933,18 +931,16 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { code_body.push(Line::Choice(choice)); } - if let Some(optimal_i) = optimal_index { - let arg = match clause.args() { - Some(args) => match args.iter().nth(optimal_i) { - Some(term) => Some(term), - None => None, - }, + let arg = match clause.args() { + Some(args) => match args.iter().nth(optimal_index) { + Some(term) => Some(term), None => None, - }; - if let Some(arg) = arg { - let index = code_body.len(); - code_offsets.index_term(arg, index); - } + }, + None => None, + }; + if let Some(arg) = arg { + let index = code_body.len(); + code_offsets.index_term(arg, index); } code_body.append(&mut clause_code); @@ -952,9 +948,8 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { let mut code = Vec::new(); - if let Some(optimal_i) = optimal_index { - code_offsets.add_indices(&mut code, code_body, optimal_i + 1); - } + code_offsets.add_indices(&mut code, code_body, optimal_index + 1); + Ok(code) } @@ -993,12 +988,10 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { } } } - match optimal_index { - // No good index or no argument, default to 0. - // TODO: Why? - None => optimal_index = Some(0), - Some(_) => (), - } + let optimal_index = match optimal_index { + Some(optimal_index) => optimal_index, + None => 0, // Default to first argument indexing. + }; let split_pred = Self::split_predicate(&clauses, optimal_index); let multi_seq = split_pred.len() > 1; diff --git a/src/forms.rs b/src/forms.rs index 5a7fcd2b..be572bc4 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -332,13 +332,6 @@ 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 { diff --git a/src/indexing.rs b/src/indexing.rs index ccd63717..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 From ecc059bf8bc39d346cd1e8da873164f0f8784bdc Mon Sep 17 00:00:00 2001 From: notoria Date: Mon, 21 Dec 2020 12:19:21 +0100 Subject: [PATCH 05/11] Organized changes into find_optimal_index --- src/codegen.rs | 77 +++++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/src/codegen.rs b/src/codegen.rs index eb68ae8b..7b328e7e 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,7 +858,45 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { Ok(code) } - fn split_predicate(clauses: &Vec, optimal_index: usize) -> Vec<(usize, usize)> { + fn find_optimal_index(clauses: &[PredicateClause]) -> 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 0; + } + 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; + } + } + } + } + match optimal_index { + Some(optimal_index) => optimal_index, + None => 0, // Default to first argument indexing. + } + } + + fn split_predicate(clauses: &[PredicateClause], optimal_index: usize) -> Vec<(usize, usize)> { let mut subseqs = Vec::new(); let mut left_index = 0; @@ -947,7 +986,6 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { } let mut code = Vec::new(); - code_offsets.add_indices(&mut code, code_body, optimal_index + 1); Ok(code) @@ -958,40 +996,7 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { clauses: &'b Vec, ) -> Result { let mut code = Vec::new(); - 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 { - 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; - } - } - } - } - } - let optimal_index = match optimal_index { - Some(optimal_index) => optimal_index, - None => 0, // Default to first argument indexing. - }; + let optimal_index = Self::find_optimal_index(&clauses); let split_pred = Self::split_predicate(&clauses, optimal_index); let multi_seq = split_pred.len() > 1; From 5b9f0f45a9ada42deacb089d1b66881da8ec6e99 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Mon, 21 Dec 2020 19:55:15 +0100 Subject: [PATCH 06/11] document first instantiated argument indexing Many thanks to @notoria for this brilliant idea and implementation! --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) 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` From 706d842102b0dedf6b3aab8080dcb262e208811a Mon Sep 17 00:00:00 2001 From: notoria Date: Mon, 21 Dec 2020 20:05:45 +0100 Subject: [PATCH 07/11] Renamed find_optimal_index to first_instantiated_index --- src/codegen.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/codegen.rs b/src/codegen.rs index 7b328e7e..d260cfa0 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -858,7 +858,8 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { Ok(code) } - fn find_optimal_index(clauses: &[PredicateClause]) -> 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() { @@ -868,7 +869,7 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { None => false, }; if !has_args { - return 0; + return optimal_index; } for clause in clauses.iter() { let args = clause.args().unwrap(); @@ -890,10 +891,8 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { } } } - match optimal_index { - Some(optimal_index) => optimal_index, - None => 0, // Default to first argument indexing. - } + + optimal_index } fn split_predicate(clauses: &[PredicateClause], optimal_index: usize) -> Vec<(usize, usize)> { @@ -996,7 +995,10 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator { clauses: &'b Vec, ) -> Result { let mut code = Vec::new(); - let optimal_index = Self::find_optimal_index(&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; From 1c76c869efadc641e5f71cb6d01df1eccc667bc0 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 22 Dec 2020 21:59:11 +0100 Subject: [PATCH 08/11] small documentation improvements related to the new indexing strategy --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6688f01f..2b41ca91 100644 --- a/README.md +++ b/README.md @@ -233,24 +233,23 @@ 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. +indexing for meta-predicates such as `maplist/N` and `foldl/N`, whose +first argument is a 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). +maplist(Goal_1, [L|Ls]) :- + call(Goal_1, L), + maplist(Goal_1, 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 +Conveniently, no auxiliary predicates with reordered arguments are needed to benefit from indexing in such cases. Conventional first argument indexing naturally arises as a From 27a52ef56ad406e65a90ea50e27582f1bd2a7d2d Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Tue, 22 Dec 2020 23:55:40 +0100 Subject: [PATCH 09/11] reflect improved determinism thanks to the improvements by @notoria --- src/lib/charsio.pl | 3 +-- src/lib/crypto.pl | 10 ++++------ src/lib/files.pl | 9 +++------ src/lib/format.pl | 3 +-- src/lib/os.pl | 3 +-- src/lib/time.pl | 14 ++++++-------- 6 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/lib/charsio.pl b/src/lib/charsio.pl index 71223884..05681bc3 100644 --- a/src/lib/charsio.pl +++ b/src/lib/charsio.pl @@ -212,8 +212,7 @@ read_line_to_chars(Stream, Cs0, Cs) :- Example: ?- chars_base64("hello", Bs, []). - Bs = "aGVsbG8=" - ; false. + Bs = "aGVsbG8=". - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ chars_base64(Cs, Bs, Options) :- diff --git a/src/lib/crypto.pl b/src/lib/crypto.pl index e88113c7..0e31f3de 100644 --- a/src/lib/crypto.pl +++ b/src/lib/crypto.pl @@ -58,8 +58,7 @@ Example: ?- hex_bytes("501ACE", Bs). - Bs = [80,26,206] - ; false. + Bs = [80,26,206]. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ @@ -146,7 +145,7 @@ must_be_byte_chars(Chars, Context) :- ?- crypto_n_random_bytes(32, Bs), bytes_integer(Bs, I). Bs = [146,166,162,210,242,7,25,132,64,94|...], - I = 337420085690608915485...(56 digits omitted) + I = 337420085690608915485...(56 digits omitted). The above relation also works in the other direction, letting you translate an integer _to_ a list of bytes. In addition, you can @@ -155,7 +154,7 @@ must_be_byte_chars(Chars, Context) :- ?- crypto_n_random_bytes(12, Bs), hex_bytes(Hex, Bs). - Bs = [34,25,50,72,58,63,50,172,32,46|...], Hex = "221932483a3f32ac202 ..." + Bs = [34,25,50,72,58,63,50,172,32,46|...], Hex = "221932483a3f32ac202 ...". - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ @@ -190,8 +189,7 @@ crypto_random_byte(B) :- '$crypto_random_byte'(B). Example: ?- crypto_data_hash("abc", Hs, [algorithm(sha256)]). - Hs = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" - ; false. + Hs = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad". - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/lib/files.pl b/src/lib/files.pl index 512c757c..e88f53a2 100644 --- a/src/lib/files.pl +++ b/src/lib/files.pl @@ -166,19 +166,16 @@ file_time_(File, Which, T) :- Examples: ?- path_segments("/hello/there", Segments). - Segments = [[],"hello","there"] - ; false. + Segments = [[],"hello","there"]. ?- path_segments(Path, ["hello","there"]). - Path = "hello/there" - ; false. + Path = "hello/there". To obtain the platform-specific directory separator, you can use: ?- path_segments(Separator, ["",""]). - Separator = "/" - ; false. + Separator = "/". - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ path_segments(Path, Segments) :- diff --git a/src/lib/format.pl b/src/lib/format.pl index f576a0dc..540bdb9f 100644 --- a/src/lib/format.pl +++ b/src/lib/format.pl @@ -64,8 +64,7 @@ Example: ?- phrase(format_("~s~n~`.t~w!~12|", ["hello",there]), Cs). - %@ Cs = "hello\n......there!" - %@ ; false. + %@ Cs = "hello\n......there!". I place this code in the public domain. Use it in any way you want. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ diff --git a/src/lib/os.pl b/src/lib/os.pl index 35bfc789..b5d9608a 100644 --- a/src/lib/os.pl +++ b/src/lib/os.pl @@ -7,8 +7,7 @@ Example: ?- getenv("LANG", Ls). - Ls = "en_US.UTF-8" - ; false. + Ls = "en_US.UTF-8". Public domain code. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ diff --git a/src/lib/time.pl b/src/lib/time.pl index d6b0ebae..30ec0f45 100644 --- a/src/lib/time.pl +++ b/src/lib/time.pl @@ -34,8 +34,7 @@ Example: ?- current_time(T), phrase(format_time("%d.%m.%Y (%H:%M:%S)", T), Cs). - T = [...], Cs = "11.06.2020 (00:24:32)" - ; false. + T = [...], Cs = "11.06.2020 (00:24:32)". sleep(S) sleeps for S seconds (a floating point number). @@ -107,15 +106,14 @@ report_time(T0) :- false. :- time(use_module(library(clpz))). - % CPU time: 2.762 seconds - true -; false. + % CPU time: 0.000 seconds + % CPU time: 0.001 seconds + true. :- time(use_module(library(lists))). % CPU time: 0.000 seconds - true -; % CPU time: 0.001 seconds - false. + % CPU time: 0.001 seconds + true. ?- time(member(X, [a,b,c])). % CPU time: 0.000 seconds From 67d856e4b700e744f1ef814f9d868f42e5137639 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 23 Dec 2020 18:06:35 +0100 Subject: [PATCH 10/11] reflect determinism improvement thanks to the latest changes --- src/lib/http/http_open.pl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/http/http_open.pl b/src/lib/http/http_open.pl index fc6304c7..e28a2a70 100644 --- a/src/lib/http/http_open.pl +++ b/src/lib/http/http_open.pl @@ -17,8 +17,7 @@ Example: ?- http_open("https://github.com/mthom/scryer-prolog", S, []). - %@ S = '$stream'(0x7f86f94a6cd0) - %@ ; false. + %@ S = '$stream'(0x7fcfc9e00f00). - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ From 01f73b11d8f0070580dafdb4ac4f9ab53e1704ff Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Sat, 26 Dec 2020 22:35:28 -0700 Subject: [PATCH 11/11] fix bug in code walker causing entire clauses to be skipped (#736) --- src/machine/code_walker.rs | 61 +++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/src/machine/code_walker.rs b/src/machine/code_walker.rs index 7cc7be4f..152395c5 100644 --- a/src/machine/code_walker.rs +++ b/src/machine/code_walker.rs @@ -2,30 +2,57 @@ use crate::instructions::*; use std::collections::VecDeque; -fn scan_for_trust_me(code: &Code, jmp_offsets: &mut VecDeque, after_idx: &mut usize) { - for (idx, instr) in code[*after_idx..].iter().enumerate() { - match instr { - &Line::Choice(ChoiceInstruction::TrustMe) - | &Line::IndexedChoice(IndexedChoiceInstruction::Trust(..)) => { - *after_idx += idx; - return; +fn scan_for_trust_me( + code: &Code, + jmp_offsets: &mut VecDeque, + before_idx: usize, + after_idx: &mut usize, +) { + // record the location of the line after the TrustMe capping the + // choice instruction sequence to after_idx. + loop { + match &code[*after_idx] { + &Line::Choice(ChoiceInstruction::DefaultRetryMeElse(offset)) | + &Line::Choice(ChoiceInstruction::RetryMeElse(offset)) | + &Line::IndexedChoice(IndexedChoiceInstruction::Retry(offset)) => { + *after_idx += offset; } - &Line::Control(ControlInstruction::JmpBy(_, offset, ..)) => { - jmp_offsets.push_back(*after_idx + idx + offset) + &Line::Choice(ChoiceInstruction::DefaultTrustMe) | + &Line::Choice(ChoiceInstruction::TrustMe) | + &Line::IndexedChoice(IndexedChoiceInstruction::Trust(..)) => { + break; + } + _ => { + *after_idx += 1; } - _ => {} } } + + // search the code in the range for JmpBy instructions and record their + // offsets for future scanning. + for (idx, instr) in code[before_idx .. *after_idx].iter().enumerate() { + match instr { + &Line::Control(ControlInstruction::JmpBy(_, offset, ..)) => { + jmp_offsets.push_back(before_idx + idx + offset) + } + _ => { + } + } + } + + *after_idx += 1; } fn capture_next_range(code: &Code, queue: &mut VecDeque, last_idx: &mut usize) { loop { match &code[*last_idx] { - &Line::Choice(ChoiceInstruction::TryMeElse(..)) - | &Line::IndexedChoice(IndexedChoiceInstruction::Try(..)) => { - *last_idx += 1; - scan_for_trust_me(code, queue, last_idx); - } + &Line::Choice(ChoiceInstruction::TryMeElse(offset)) | + &Line::IndexedChoice(IndexedChoiceInstruction::Try(offset)) => { + let before_idx = *last_idx; + *last_idx += offset; + + scan_for_trust_me(code, queue, before_idx, last_idx); + } &Line::Control(ControlInstruction::JmpBy(_, offset, _, false)) => { queue.push_back(*last_idx + offset); *last_idx += 1; @@ -34,8 +61,8 @@ fn capture_next_range(code: &Code, queue: &mut VecDeque, last_idx: &mut u queue.push_back(*last_idx + offset); break; } - &Line::Control(ControlInstruction::Proceed) - | &Line::Control(ControlInstruction::CallClause(_, _, _, true, _)) => + &Line::Control(ControlInstruction::Proceed) | + &Line::Control(ControlInstruction::CallClause(_, _, _, true, _)) => break, _ => *last_idx += 1,