add tabling library, update README, make Cargo.toml non-local

This commit is contained in:
Mark Thom
2020-02-02 22:24:48 -07:00
parent 740bd528c4
commit d83c5cf5f1
10 changed files with 1585 additions and 8 deletions

View File

@@ -24,7 +24,7 @@ libc = "0.2.62"
nix = "0.15.0"
num-rug-adapter = { optional = true, version = "0.1.1" }
ordered-float = "0.5.0"
prolog_parser = { version = "0.8.38", path = "../prolog_parser", default-features = false }
prolog_parser = { version = "0.8.38", default-features = false }
ref_thread_local = "0.0.0"
rug = { version = "1.4.0", optional = true }
rustyline = "5.0.3"

View File

@@ -30,10 +30,8 @@ Extend Scryer Prolog to include the following, among other features:
- [x] Built-in predicates for list processing and top-level declarative
control (`setup_call_cleanup/3`, `call_with_inference_limit/3`,
etc.)
- [x] Default representation of strings as list of chars, using a packed
internal representation.
- A representation of 'partial strings' as difference lists
of characters.
- [x] ~~Default representation of strings as list of chars, using a packed
internal representation.~~
- [x] `term_expansion/2` and `goal_expansion/2`.
- [x] Definite Clause Grammars.
- [x] Attributed variables using the SICStus Prolog interface and
@@ -52,9 +50,10 @@ Extend Scryer Prolog to include the following, among other features:
(backtrackable).
- [x] Delimited continuations based on reset/3, shift/1 (documented in
"Delimited Continuations for Prolog").
- [ ] Add opt-in tabling library based on delimited continuations
(documented in "Tabling as a Library with Delimited Control") (_in
progress_).
- [x] Add opt-in tabling library based on delimited continuations
(documented in "Tabling as a Library with Delimited Control").
- [ ] A _redone_ representation of strings as difference list of
chars, using a packed internal representation (_in progress_).
- [ ] clp(B) and clp() as builtin libraries (_in progress_).
- [ ] Streams and predicates for stream control (_in progress_).
- [ ] An incremental compacting garbage collector satisfying the five

224
src/prolog/lib/tabling.pl Normal file
View File

@@ -0,0 +1,224 @@
:- module(tabling,
[ start_tabling/2, % +Wrapper, :Worker.
abolish_all_tables/0,
% (table)/1, % +PI ...
op(1150, fx, table)
]).
:- use_module('tabling/double_linked_list').
:- use_module('tabling/table_data_structure').
:- use_module('tabling/batched_worklist').
:- use_module('tabling/wrapper').
:- use_module('tabling/global_worklist').
:- use_module('tabling/table_link_manager').
:- use_module(library(cont)).
:- use_module(library(lists)).
%:- use_module(library(debug)).
:- use_module(library(non_iso)).
%% :- meta_predicate
%% start_tabling(+, 0).
%% user:exception(+Exception, +Var, -Action)
%
% Realises lazy initialization of table variables.
%% user:exception(undefined_global_variable, Var, retry) :-
%% ( table_gvar(Var)
%% -> true
%% ; format('Creating global var ~q~n', [Var]),
%% nb_setval(Var, [])
%% ).
/*
table_gvar(trie_table_link) :-
table_datastructure_initialize.
table_gvar(newly_created_table_identifiers) :-
table_datastructure_initialize.
table_gvar(table_global_worklist) :-
bb_put(table_global_worklist, []).
table_gvar(table_leader) :-
bb_put(table_leader, []).
*/
%% abolish_all_tables
%
% Remove all tables. Should not be called when tabling is in
% progress.
%
% @bug Check whether tabling is in progress
abolish_all_tables :-
bb_put(trie_table_link, []),
bb_put(newly_created_table_identifiers, []),
bb_put(table_global_worklist,[]),
bb_put(table_leader, []).
% Find table and status for the given call variant.
%
table_and_status_for_variant(V,T,S) :-
% Order of the two calls really important: first create, then get status
table_for_variant(V,T),
tbd_table_status(T,S).
start_tabling(Wrapper,Worker) :-
put_new_trie_table_link,
put_new_global_worklist,
put_new_table_identifiers,
table_and_status_for_variant(Wrapper,T,S),
( S == complete ->
get_answer(T,Wrapper)
;
( exists_scheduling_component ->
run_leader(Wrapper,Worker,T),
% Now answer the original query!
get_answer(T,Wrapper)
;
run_follower(S,Wrapper,Worker,T)
)
).
run_follower(fresh,Wrapper,Worker,T) :-
activate(Wrapper,Worker,T),
shift(call_info(Wrapper,T)).
run_follower(active,Wrapper,_Worker,T) :-
shift(call_info(Wrapper,T)).
run_leader(Wrapper,Worker,T) :-
create_scheduling_component,
activate(Wrapper,Worker,T),
completion,
unset_scheduling_component.
exists_scheduling_component :-
bb_get(table_leader, Leader),
Leader == [].
create_scheduling_component :-
bb_b_put(table_leader, leaderCreated).
unset_scheduling_component :-
bb_put(table_leader, []).
set_all_complete :-
get_newly_created_table_identifiers(Ts, _),
set_all_complete_(Ts).
set_all_complete_([]).
set_all_complete_([T|Ts]) :-
set_complete_status(T),
set_all_complete_(Ts).
cleanup_all_complete :-
get_newly_created_table_identifiers(Ts,_),
cleanup_all_complete_(Ts).
cleanup_all_complete_([]).
cleanup_all_complete_([T|Ts]) :-
cleanup_after_complete(T),
cleanup_all_complete_(Ts).
activate(Wrapper,Worker,T) :-
set_active_status(T),
(
delim(Wrapper,Worker,T),
fail
;
true
).
delim(Wrapper,Worker,Table) :-
% debug(tabling, 'ACT: ~p on ~p', [Wrapper, Table]),
reset(Worker,SourceCall,Continuation),
( Continuation == none, var(SourceCall) ->
( add_answer(Table,Wrapper)
-> true %debug(tabling, 'ADD: ~p', [Wrapper])
; %debug(tabling, 'DUP: ~p', [Wrapper]),
fail
)
;
( Continuation = cont(Cont) ->
true
; Continuation = none ->
Cont = true
),
SourceCall = call_info(_,SourceTable),
TargetCall = call_info(Wrapper,Table),
Dependency = dependency(SourceCall,Cont,TargetCall),
%debug(tabling, 'DEP: ~p: ~p', [SourceTable,Dependency]),
store_dependency(SourceTable,Dependency)
).
completion :-
( worklist_empty ->
set_all_complete,
cleanup_all_complete,
% The place of the call to reset is really important: it must happen after the completion. If you do it before, you will wrongly remove yourself from the list of newly created table identifiers. On starting hProlog there are no newly created table identifiers, and nb_getval gives [] which is the perfect value.
reset_newly_created_table_identifiers
;
pop_worklist(Table),
completion_step(Table),
completion
).
completion_step(SourceTableID) :-
bb_get(SourceTableID, Table),
get_nb_identifiers(Table, NBWorklistID, _),
(
table_get_work(NBWorklistID,Answer,dependency(Source,Continuation,Target)),
Source = call_info(Answer,_),
Target = call_info(Wrapper,TargetTable),
delim(Wrapper,Continuation,TargetTable),
fail
;
true
).
table_get_work(NBWorklistID,Answer,Dependency) :-
% get_worklist(Table, Worklist),
% NOT IN PAPER (could be part of the definition of pop_worklist):
bb_get(NBWorklistID, table_nb_worklist(Worklist)),
unset_global_worklist_presence_flag(Worklist),
set_flag_executing_all_work(Worklist),
bb_put(NBWorklistID, table_nb_worklist(Worklist)),
table_get_work_(NBWorklistID,Answer,Dependency).
table_get_work_(NBWorklistID,Answer,Dependency) :-
worklist_do_all_work(NBWorklistID,Answer,Dependency0), % This will eventually fail
copy_term(Dependency0,Dependency).
table_get_work_(NBWorklistID,_Answer,_Dependency) :-
bb_get(NBWorklistID, table_nb_worklist(Worklist)),
unset_flag_executing_all_work(Worklist),
bb_put(NBWorklistID, table_nb_worklist(Worklist)),
fail.
worklist_do_all_work(NBWorklistID,Answer,Dependency) :-
( bb_get(NBWorklistID, table_nb_worklist(Worklist)),
wkl_worklist_work_done(Worklist) ->
fail
;
worklist_do_step(NBWorklistID,Answer,Dependency)
;
worklist_do_all_work(NBWorklistID,Answer,Dependency)
).
worklist_do_step(NBWorklistID,Answer,Dependency) :-
bb_get(NBWorklistID, table_nb_worklist(Worklist)),
wkl_p_get_rightmost_inner_answer_cluster_pointer(Worklist,ACP),
wkl_p_swap_answer_continuation(Worklist,ACP,SCP),
dll_get_data(ACP,wkl_answer_cluster(AListFlag)),
dll_get_data(SCP,wkl_suspension_cluster(SListFlag)),
get_atts(AListFlag, batched_worklist, wkl_answer_cluster(AList)),
get_atts(SListFlag, batched_worklist, wkl_suspension_cluster(SList)),
bb_put(NBWorklistID, table_nb_worklist(Worklist)),
member(Answer,AList),
member(Dependency,SList).
:- initialization(bb_put(table_leader, [])).

View File

@@ -0,0 +1,389 @@
/* Part of SWI-Prolog
Author: Benoit Desouter <Benoit.Desouter@UGent.be>
Jan Wielemaker (SWI-Prolog port)
Copyright (c) 2016, Benoit Desouter
All rights reserved.
Ported to Scryer Prolog by Mark Thom (2019/2020).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
:- module(batched_worklist,
[ wkl_add_answer/2, % +WorkList, +Answer
wkl_add_suspension/2, % +Worklist, +Suspension
wkl_new_worklist/2, % +TableID, -WorkList
unset_flag_executing_all_work/1, % +WorkList
unset_global_worklist_presence_flag/1, % +WorkList
set_flag_executing_all_work/1, % +WorkList
wkl_p_get_rightmost_inner_answer_cluster_pointer/2, % +WorkList, -Cluster
wkl_p_swap_answer_continuation/3, % +WorkList, +Cluster1, +Cluster2
wkl_worklist_work_done/1 % +WorkList
]).
:- use_module(global_worklist).
:- use_module(double_linked_list).
:- use_module(library(atts)).
:- use_module(library(lists)).
:- attribute executing_all_work/1, worklist_presence/1, wkl_answer_cluster/1, wkl_suspension_cluster/1, wkl_answer_cluster_pointer_flag/1.
/** <module> Tabling Worklist management
A batched worklist: a worklist that clusters suspensions and answers as
much as possible. The idea is to minimize the number of swaps. This
should be more efficient than the worklist implementation without
clustering.
Argument positions for nb_setarg:
1. double linked list
2. pointer to the list entry of the rightmost inner answer cluster
3. flag indicating the execution of wkl_unfolded_do_all_work
4. flag indicating whether the table identifier associated with this
worklist is already in the global worklist. This is because more
than one answer can be added due to the execution of other
worklists. 5: table identifier for the table this worklist belongs
to
Contents of a batched worklist:
- wkl_answer_cluster([Answer|RestAnswers]).
- wkl_suspension([Suspension|RestSuspension]).
The difficulty is that you should not add new entries to a cluster once
you started its execution. Probably the simplest way to do so is by
swapping the answer cluster AC and suspension cluster SC before you take
the cartesian product of all answers in AC with all suspensions in SC.
Illustration why you may need a complex procedure for finding the future
rightmost inner answer cluster.
Assume all clusters have 2 entries.
1. AA1 CC1
2. AA2 CC1 AA1 CC2 (swapped AA1 and CC1)
3. AA2 CC1 CC2 AA1 (swapped AA1 and CC2)
4. AA3 CC1 AA2 CC2 AA1 CC3 (swapped AA2 and CC1)
Now AA1 is the RIAC, but AA2 is the future RIAC.
Can you find the future RIAC smarter than by walking back? If you don't,
then it doesn't make sense to use a future RIAC at all. You could use a
stack, which should not grow too large because you use batches. But
walking back also should not take too long, since you use batches.
So let's not use a future RIAC in the first place, and just walk back
when we need a new RIAC. This is easy to implement, hence we can test
more quickly.
Abbreviations:
- RIAC = rightmost inner answer cluster
- FUTRIAC = future rightmost inner answer cluster
*/
%% wkl_new_worklist(+TableID, -WorkList) is det.
%
% Create a new worklist for TableID and add it to the global
% worklist list (global variable `table_global_worklist`.
wkl_new_worklist(TableIdentifier, wkl_worklist(List,AnswerClusterPointerFlag,ExecutingAllWork,WorklistPresence,TableIdentifier)) :-
dll_new_double_linked_list(List),
put_atts(AnswerClusterPointerFlag, wkl_answer_cluster_pointer_flag(List)),
% We set the RIAC to the dummy element at the start of the double linked list, which is List.
% Don't set all the rest for now.
put_atts(ExecutingAllWork, executing_all_work(false)),
put_atts(WorklistPresence, worklist_presence(true)),
add_to_global_worklist(TableIdentifier).
%% wkl_worklist_work_done(+WorkList) is semidet.
%
% The work is done if the RIAC pointer points to the unused cell
% at the beginning. The work is also done if the RIAC pointer
% points to the sole answer cluster in a list dll_start -
% wkl_answer_cluster, because in that case there are no
% suspensions to swap with. This is a special case, which we only
% discovered by testing. You can detect it by checking whether the
% NEXT-pointer of the RIAC is the dummy pointer.
wkl_worklist_work_done(Worklist) :-
wkl_p_get_rightmost_inner_answer_cluster_pointer(Worklist,RiacPointer),
( wkl_is_dummy_pointer(Worklist,RiacPointer) ->
true
;
dll_get_pointer_to_next(RiacPointer,NextPointer),
wkl_is_dummy_pointer(Worklist,NextPointer)
).
set_flag_executing_all_work(wkl_worklist(_,_,ExecutingAllWork,_,_)) :-
put_atts(ExecutingAllWork, executing_all_work(true)).
unset_flag_executing_all_work(wkl_worklist(_,_,ExecutingAllWork,_,_)) :-
put_atts(ExecutingAllWork, executing_all_work(false)).
% Swap answer cluster and the adjacent continuation cluster.
% Mode: + + -
wkl_p_swap_answer_continuation(Worklist,InnerAnswerClusterPointer,SuspensionClusterPointer) :-
% You can have a worklist containing only an answer cluster, but no continuations.
% In that case SuspensionClusterPointer will be dll_start. We must take our precautions elsewhere.
% Do not forget that the list of answers and the list of suspensions is wrapped in a predicate!
dll_get_pointer_to_next(InnerAnswerClusterPointer,SuspensionClusterPointer),
% For reasons of speed we don't use dll_swap: we only swap adjacent elements and we can be sure that they are in the order A,B.
% Therefore we can use dll_p_swap_adjacent_elements_
dll_p_swap_adjacent_elements_(InnerAnswerClusterPointer,SuspensionClusterPointer),
% Update the necessary pointers
wkl_p_update_righmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer).
% Update the pointer if the answer cluster it points to is no longer the rightmost inner answer cluster.
wkl_p_update_righmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer) :-
( wkl_p_answer_cluster_currently_moved_completely(Worklist,InnerAnswerClusterPointer) ->
wkl_p_find_new_rightmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer,NewRiacPointer),
wkl_p_set_rightmost_inner_answer_cluster_pointer(Worklist,NewRiacPointer)
;
true
).
% Rationale for this implementation: see the top of the file.
% Unify NewRiacPointer to the first pointer satisfying the following conditions:
% - left of StartPointer (when viewing the list as DUMMY-ELEM POINTER POINTER POINTER START-POINTER)
% - either an anwer pointer or the dummy element
% When StartPointer is the dummy element, NewRiacPointer is also the dummy element. We never look "in front of" the dummy element.
wkl_p_find_new_rightmost_inner_answer_cluster_pointer(Worklist,StartPointer,NewRiacPointer) :-
( wkl_is_dummy_pointer(Worklist,StartPointer) ->
NewRiacPointer = StartPointer
;
dll_get_pointer_to_previous(StartPointer,FirstCandidatePointer),
wkl_p_find_new_riac_helper(Worklist,FirstCandidatePointer,NewRiacPointer)
).
wkl_p_find_new_riac_helper(Worklist,CandidatePointer,NewRiacPointer) :-
( is_answer_cluster_or_dummy_pointer(Worklist,CandidatePointer) ->
NewRiacPointer = CandidatePointer
;
dll_get_pointer_to_previous(CandidatePointer,NewCandidate),
wkl_p_find_new_riac_helper(Worklist,NewCandidate,NewRiacPointer)
).
is_answer_cluster_or_dummy_pointer(Worklist,Pointer) :-
( wkl_is_dummy_pointer(Worklist,Pointer) ->
true
;
wkl_p_dereference_pointer(Worklist,Pointer,A),
wkl_p_is_answer_cluster(A)
).
% Failure-driven loop
wkl_clusters_cartesian_product(AnswerCluster,SuspensionCluster) :-
( member(Answer,AnswerCluster),
member(Suspension,SuspensionCluster),
% The meat
run_worklist_helper(Suspension,Answer),
% Trigger loop
fail
;
% Loop base case
true
).
run_worklist_helper(_Suspension, _Answer) :- % FIXME: just silense
throw('not implemented').
wkl_both_flags_unset(wkl_worklist(_Dll,_Riac,ExecutingAllWork,WorklistPresence,_TableIdentifier)) :-
put_atts(ExecutingAllWork, executing_all_work(false)),
put_atts(WorklistPresence, worklist_presence(false)).
set_global_worklist_presence_flag(wkl_worklist(_,_,_,WorklistPresence,_)) :-
put_atts(WorklistPresence, worklist_presence(true)).
unset_global_worklist_presence_flag(wkl_worklist(_,_,_,WorklistPresence,_)) :-
put_atts(WorklistPresence, worklist_presence(false)).
potentially_add_to_global_worklist(Worklist) :-
( wkl_both_flags_unset(Worklist) ->
% Set the flag for presence in the metaworklist
set_global_worklist_presence_flag(Worklist),
% Should add to the metaworklist
arg(5,Worklist,TableIdentifier),
add_to_global_worklist(TableIdentifier)
;
% Nothing to do.
true
).
wkl_add_answer(Worklist,Answer) :-
% Add to global worklist if not executing during wkl_unfolded_do_all_work and not there yet as well.
potentially_add_to_global_worklist(Worklist),
( wkl_p_leftmost_cluster_is_answer_cluster(Worklist) ->
wkl_add_to_existing_answer_cluster(Worklist,Answer)
% If you add to an existing cluster, then obviously you should not change the RIAC.
;
wkl_add_to_new_answer_cluster(Worklist,Answer,AnswerClusterPointer),
% If the RIAC is the dummy pointer, we need to change that.
wkl_p_update_rightmost_inner_answer_cluster_pointer(Worklist,AnswerClusterPointer)
).
wkl_p_update_rightmost_inner_answer_cluster_pointer(Worklist,NewAnswerClusterPointer) :-
wkl_p_get_rightmost_inner_answer_cluster_pointer(Worklist,CurrentRiac),
( wkl_is_dummy_pointer(Worklist,CurrentRiac) -> %% <- debugging this.
wkl_p_set_rightmost_inner_answer_cluster_pointer(Worklist,NewAnswerClusterPointer)
;
% Nothing to do.
true
).
wkl_add_suspension(Worklist,Suspension) :-
% Add to global worklist if not executing during wkl_unfolded_do_all_work and not there yet as well.
potentially_add_to_global_worklist(Worklist),
( wkl_p_rightmost_cluster_is_suspension_cluster(Worklist) ->
wkl_add_to_existing_suspension_cluster(Worklist,Suspension)
;
wkl_add_to_new_suspension_cluster(Worklist,Suspension,SuspensionClusterPointer),
% If added to a new suspension cluster, we may need to change the righmost inner answer pointer
wkl_p_potential_rias_update_add_contin(Worklist,SuspensionClusterPointer)
).
% This predicate should not fail.
wkl_p_potential_rias_update_add_contin(Worklist,SuspensionClusterPointer) :-
% Look back one entry of the freshly inserted SuspensionClusterPointer
dll_get_pointer_to_previous(SuspensionClusterPointer,PotentialNewRiacPointer),
( wkl_p_is_answer_cluster_pointer(Worklist,PotentialNewRiacPointer) ->
% We must indeed update the rightmost inner answer cluster pointer.
wkl_p_set_rightmost_inner_answer_cluster_pointer(Worklist,PotentialNewRiacPointer)
;
% Nothing to do, but we should not fail.
true
).
wkl_add_to_existing_answer_cluster(Worklist, Answer) :-
arg(1,Worklist,Dll),
dll_get_pointer_to_next(Dll,AnswerClusterPointer),
wkl_p_dereference_pointer(Worklist,AnswerClusterPointer,AnswerCluster),
AnswerCluster = wkl_answer_cluster(AnswersFlag),
get_atts(AnswersFlag, wkl_answer_cluster(AnswersAlreadyInCluster)),
put_atts(AnswersFlag, wkl_answer_cluster([Answer|AnswersAlreadyInCluster])).
wkl_add_to_new_answer_cluster(
wkl_worklist(Dll,_Ria,_FlagExecutingWork,_AlreadyInMetaworklist,_TableIdentifier),
Answer,AnswerClusterPointer
) :-
dll_append_left(Dll,wkl_answer_cluster(AnswerFlag),AnswerClusterPointer),
put_atts(AnswerFlag, wkl_answer_cluster([Answer])).
wkl_add_to_existing_suspension_cluster(Worklist, Suspension) :-
arg(1,Worklist,Dll),
dll_get_pointer_to_previous(Dll,SuspensionClusterPointer),
wkl_p_dereference_pointer(Worklist,SuspensionClusterPointer,SuspensionCluster),
SuspensionCluster = wkl_suspension_cluster(SuspensionsFlag),
get_atts(SuspensionsFlag, wkl_suspension_cluster(SuspensionsAlreadyInCluster)),
put_atts(SuspensionsFlag, wkl_suspension_cluster([Suspension|SuspensionsAlreadyInCluster])).
%% nb_linkarg(1,SuspensionCluster,[Suspension|SuspensionsAlreadyInCluster]).
wkl_add_to_new_suspension_cluster(
wkl_worklist(Dll,_Ria,_FlagExecutingWork,_AlreadyInMetaworklist,_TableIdentifier),
Suspension,
SuspensionClusterPointer
) :-
put_atts(SuspensionFlag, wkl_suspension_cluster([Suspension])),
dll_append_right(Dll,wkl_suspension_cluster(SuspensionFlag),SuspensionClusterPointer).
wkl_p_is_answer_cluster(CandidateAnswerCluster) :-
nonvar(CandidateAnswerCluster),
CandidateAnswerCluster = wkl_answer_cluster(_).
wkl_p_is_suspension_cluster(CandidateSuspensionCluster) :-
nonvar(CandidateSuspensionCluster),
CandidateSuspensionCluster = wkl_suspension_cluster(_).
wkl_p_leftmost_cluster_is_answer_cluster(Worklist) :-
arg(1,Worklist,Dll),
dll_get_pointer_to_next(Dll,CandidateAnswerClusterPointer),
wkl_p_is_answer_cluster_pointer(Worklist,CandidateAnswerClusterPointer).
wkl_p_rightmost_cluster_is_suspension_cluster(Worklist) :-
arg(1,Worklist,Dll),
dll_get_pointer_to_previous(Dll,CandidateSuspensionClusterPointer),
wkl_p_is_suspension_cluster_pointer(Worklist,CandidateSuspensionClusterPointer).
wkl_p_get_rightmost_inner_answer_cluster_pointer(wkl_worklist(_Dll,InnerAnswerClusterPointerFlag,_FlagExecutingWork,_AlreadyInMetaworklist,_TableIdentifier), InnerAnswerClusterPointer) :-
get_atts(InnerAnswerClusterPointerFlag, wkl_answer_cluster_pointer_flag(InnerAnswerClusterPointer)).
% Succeed if there are currently no more continuation clusters on the right of the given position:
% Why 'currently' in the name? Another continuation can be added.
wkl_p_answer_cluster_currently_moved_completely(Worklist,AnswerClusterPointer) :-
( wkl_p_at_right(Worklist,AnswerClusterPointer) ->
true
;
wkl_p_answer_cluster_on_right(Worklist,AnswerClusterPointer)
).
% Succeeds if the given pointer points to the last element in the list. That is, if its next pointer is the dummy element in the double linked list.
wkl_p_at_right(Worklist,Pointer) :-
dll_get_pointer_to_next(Pointer,NextPointer),
wkl_is_dummy_pointer(Worklist,NextPointer).
wkl_p_answer_cluster_on_right(Worklist,Pointer) :-
dll_get_pointer_to_next(Pointer,NextPointer),
wkl_p_is_answer_cluster_pointer(Worklist,NextPointer).
wkl_is_dummy_pointer(Worklist,Pointer) :-
wkl_p_get_double_linked_list(Worklist,Dll),
dll_is_dummy_pointer(Dll,Pointer).
wkl_p_is_answer_cluster_pointer(Worklist,PointerCandidateAnswerCluster) :-
( wkl_is_dummy_pointer(Worklist,PointerCandidateAnswerCluster) ->
% Certainly not an answer cluster, should not dereference this
fail
;
wkl_p_dereference_pointer(Worklist,PointerCandidateAnswerCluster,CandidateAnswerCluster),
wkl_p_is_answer_cluster(CandidateAnswerCluster)
).
wkl_p_is_suspension_cluster_pointer(Worklist,PointerCandidateSuspensionCluster) :-
( wkl_is_dummy_pointer(Worklist,PointerCandidateSuspensionCluster) ->
% Certainly not an answer cluster, should not dereference this
fail
;
wkl_p_dereference_pointer(Worklist,PointerCandidateSuspensionCluster,CandidateSuspensionCluster),
wkl_p_is_suspension_cluster(CandidateSuspensionCluster)
).
wkl_p_get_double_linked_list(Worklist,Dll) :-
arg(1,Worklist,Dll).
% One should not attempt to dereference the dummy pointer in the double linked list.
wkl_p_dereference_pointer(_Worklist,Pointer,Data) :-
dll_get_data(Pointer,Data).
% SETTING POINTERS
%%%%%%%%%%%%%%%%%%
wkl_p_set_rightmost_inner_answer_cluster_pointer(Worklist,AnswerClusterPointer) :-
arg(2, Worklist, AnswerClusterPointerFlag),
put_atts(AnswerClusterPointerFlag, wkl_answer_cluster_pointer_flag(AnswerClusterPointer)).

View File

@@ -0,0 +1,211 @@
/* Part of SWI-Prolog
Author: Benoit Desouter <Benoit.Desouter@UGent.be>
Jan Wielemaker (SWI-Prolog port)
Copyright (c) 2016, Benoit Desouter
All rights reserved.
Ported to Scryer Prolog by Mark Thom (2019/2020).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
:- module(double_linked_list,
[ dll_new_double_linked_list/1, % -List
dll_append_right/2, % !List, +Element
dll_append_left/2, % !List, +Element
dll_append_right/3, % !List, +Element, -Pointer
dll_append_left/3, % !List, +Element, -Pointer
dll_get_data/2, % +List, -Head
dll_get_pointer_to_next/2, % +List, -Pointer
dll_get_pointer_to_previous/2, % +List, -Pointer
dll_is_dummy_pointer/2, % +List, +Pointer
dll_p_swap_adjacent_elements_/2, % +Pointer1, +Pointer2
dll_get_contents/2,
dll_get_reverse_contents/2
]).
:- use_module(library(atts)).
:- attribute dll_element/1, dll_next/1, dll_prev/1.
% A circular double linked list
% =============================
% Always have a unused-cell at the beginning.
% I do not always inline unifications because the head is then more readable for users who don't need to know the details.
% Due to lack of modules in hProlog, the following predicate names should not be used elsewhere:
% - the heads of all following rules (starting with dll_, I reserve "the namespace"!)
% dll_cell(Element,Next,Previous)
% The following is perhaps odd:
%
% Next link = more to the front (the left)
% Previous link = more to the back (the right)
%
% List structure
% --------------
% front-of-the-list | ... | back-of-the-list
dll_new_double_linked_list(List) :-
% Nonused cell dll_start at the beginning, points to itself (this is easy when adding elements).
List = dll_cell(Start),
put_atts(Start, [dll_next(List), dll_prev(List), dll_element(dll_start)]).
dll_append_right(List, Element) :-
dll_append_right(List, Element, _).
dll_append_left(List, Element) :-
dll_append_left(List, Element, _).
% Append at the back of the list
% Mode: + + -
dll_append_right(List, Element, Pointer) :-
% Get pointer to cell currently at the back. Done by taking the previous element from the unused element representing the list.
dll_get_pointer_to_previous(List, OldBack),
% Make the new cell point to OldBack as predecessor
% Make the new cell point to the unused cell as successor.
Pointer = dll_cell(NewCell),
put_atts(NewCell, [dll_element(Element), dll_next(List), dll_prev(OldBack)]),
% Make OldBack point to the new cell as successor
dll_p_set_next_pointer(OldBack, Pointer),
% Make the unused cell point to the new cell as predecessor
dll_p_set_previous_pointer(List, Pointer).
% Add to the front of the list
% Mode: + + -
dll_append_left(List, Element, Pointer) :-
% Get pointer to cell currently at the front. Done by taking the next element from the unused element representing the list.
dll_get_pointer_to_next(List, OldFront),
% Make the new cell point to OldFront as successor
% Make the new cell point to the unused cell as predecessor
Pointer = dll_cell(NewCell),
put_atts(NewCell, [dll_element(Element), dll_prev(List), dll_next(OldFront)]),
% Make OldFront point to the new cell as predecessor
dll_p_set_previous_pointer(OldFront, Pointer),
% Make the unused cell point to the new cell as successor
dll_p_set_next_pointer(List, Pointer).
% get_next_cell?
dll_get_pointer_to_next(dll_cell(Cell), PointerNext) :-
get_atts(Cell, dll_next(PointerNext)).
% get_previous_cell?
dll_get_pointer_to_previous(dll_cell(Cell), PointerPrevious) :-
get_atts(Cell, dll_prev(PointerPrevious)).
% Will happily give you the "data" from the unused cell at the beginning. (We use this odd behaviour below, f.e. in dll_p_foreach_element_/2.)
dll_get_data(dll_cell(Cell), Element) :-
get_atts(Cell, dll_element(Element)).
dll_is_dummy_pointer(List, Pointer) :-
dll_get_contents(List, ListContents),
dll_get_contents(Pointer, PointerContents),
\+ PointerContents \= ListContents.
% Special case of swapping - used in dll_swap/2.
% This is also the case used for swapping a freshly created list with itself.
%
% Sketch: APrevious <-> PointerA <-> PointerB <-> BNext etc.
dll_p_swap_adjacent_elements(PointerA, PointerB) :-
% Order B A?
( dll_get_pointer_to_next(PointerB, PointerA) ->
dll_p_swap_adjacent_elements_(PointerB, PointerA)
;
% Order A B!
dll_p_swap_adjacent_elements_(PointerA, PointerB)
).
% Assumes the order A B.
dll_p_swap_adjacent_elements_(PointerA, PointerB) :-
% Get A's previous and B's next
dll_get_pointer_to_previous(PointerA, PointerAPrevious),
dll_get_pointer_to_next(PointerB, PointerBNext),
% Set A's previous to B
dll_p_set_previous_pointer(PointerA, PointerB),
% Set B's next to A
dll_p_set_next_pointer(PointerB, PointerA),
% Set A's next to BNext
dll_p_set_next_pointer(PointerA, PointerBNext),
% Set B's previous to APrevious
dll_p_set_previous_pointer(PointerB, PointerAPrevious),
% Set APrevious' next to B !!
dll_p_set_next_pointer(PointerAPrevious, PointerB),
% Set BNext's previous to A !!
dll_p_set_previous_pointer(PointerBNext, PointerA).
% Private
% Careful: make sure this is called on the actual cell, and not some copy.
% Mode: + +
dll_p_set_previous_pointer(dll_cell(Cell), PointerToNewPrevious) :-
put_atts(Cell, dll_prev(PointerToNewPrevious)).
% Private
% Careful: make sure this is called on the actual cell, and not some copy.
% Mode: + +
dll_p_set_next_pointer(dll_cell(Cell), PointerToNewNext) :-
put_atts(Cell, dll_next(PointerToNewNext)).
dll_extract_element(ElementFlag, Element) :-
( ElementFlag = wkl_suspension_cluster(SuspensionClusterFlag) ->
get_atts(SuspensionClusterFlag, batched_worklist, wkl_suspension_cluster(SuspensionCluster)),
Element = wkl_suspension_cluster(SuspensionCluster)
; ElementFlag = wkl_answer_cluster(AnswerClusterFlag) ->
get_atts(AnswerClusterFlag, batched_worklist, wkl_answer_cluster(AnswerCluster)),
Element = wkl_answer_cluster(AnswerCluster)
).
dll_get_contents(List, Contents) :-
dll_get_pointer_to_next(List, Next),
dll_get_contents_(Next, Contents).
dll_get_contents_(List, Contents) :-
dll_get_data(List, ElementFlag),
( ElementFlag == dll_start ->
Contents = []
; dll_extract_element(ElementFlag, Element),
Contents = [Element | Rest],
dll_get_pointer_to_next(List, Next),
dll_get_contents_(Next, Rest)
).
dll_get_reverse_contents(List, Contents) :-
dll_get_pointer_to_previous(List, Prev),
dll_get_reverse_contents_(Prev, Contents).
dll_get_reverse_contents_(List, Contents) :-
dll_get_data(List, ElementFlag),
( ElementFlag == dll_start ->
Contents = []
; dll_extract_element(ElementFlag, Element),
Contents = [Element | Rest],
dll_get_pointer_to_previous(List, Prev),
dll_get_reverse_contents_(Prev, Rest)
).

View File

@@ -0,0 +1,40 @@
/* Ported to Scryer Prolog by Mark Thom (2019/2020).
*/
:- module(global_worklist,
[ put_new_global_worklist/0,
add_to_global_worklist/1,
worklist_empty/0,
pop_worklist/1
]).
:- use_module(library(atts)).
:- use_module(library(non_iso)).
:- attribute table_global_worklist/1.
put_new_global_worklist :-
( bb_get(table_global_worklist_initialized, _) ->
true
; put_atts(Worklist, table_global_worklist([])),
bb_put(table_global_worklist, Worklist),
bb_b_put(table_global_worklist_initialized, [])
).
add_to_global_worklist(TableIdentifier) :-
bb_get(table_global_worklist, TableGlobalWorklistFlag),
get_atts(TableGlobalWorklistFlag, table_global_worklist(L1)),
put_atts(TableGlobalWorklistFlag, table_global_worklist([TableIdentifier|L1])),
bb_put(table_global_worklist, TableGlobalWorklistFlag).
worklist_empty :-
bb_get(table_global_worklist,TableGlobalWorklistFlag),
get_atts(TableGlobalWorklistFlag, table_global_worklist(L)),
L == [].
pop_worklist(TableIdentifier) :-
bb_get(table_global_worklist,TableGlobalWorklistFlag),
get_atts(TableGlobalWorklistFlag, table_global_worklist(L1)),
L1 = [TableIdentifier|L2],
put_atts(TableGlobalWorklistFlag, table_global_worklist(L2)),
bb_put(table_global_worklist, TableGlobalWorklistFlag).

View File

@@ -0,0 +1,253 @@
:- module(table_datastructure,
[ get_answer/2, % +TableID, -Answer
add_answer/2, % +TableID, +Answer
get_call_variant/2, % +TableID, -CallVariant
set_complete_status/1, % +TableID
set_active_status/1, % +TableID
tbd_table_status/2, % +TableID, -Status
table_for_variant/2, % +Variant, -TableID
store_dependency/2, % +TableID, +Suspension
cleanup_after_complete/1, % +TableID
get_newly_created_table_identifiers/2, % NewlyCreatedTableIDs, NumIDs
reset_newly_created_table_identifiers/0,
answers_for_variant/2, % +Variant, -Answers
put_new_table_identifiers/0,
get_nb_identifiers/3 % +Table, -NbWorklistID, -NbAnswerTreeID
]).
:- use_module(table_link_manager).
:- use_module(trie).
/* Part of SWI-Prolog
Author: Benoit Desouter <Benoit.Desouter@UGent.be>
Jan Wielemaker (SWI-Prolog port)
Copyright (c) 2016, Benoit Desouter
All rights reserved.
Ported to Scryer Prolog by Mark Thom (2019/2020).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
:- use_module(batched_worklist).
:- use_module(library(atts)).
:- use_module(library(gensym)).
:- use_module(library(non_iso)).
:- attribute table_status/1, newly_created_table_identifiers/1.
% This file defines the table datastructure.
%
% The table datastructure contains the following sub-structures:
% - the answer trie
% - the worklist
%
% Structure for tables:
% table(CallVariant,Status,AnswerTrie,Worklist) or complete_table(CallVariant,AnswerTrie).
% where AnswerTrie contains a trie of unique answers
%
% Remember that a table may also be nonexistent!
% nb_getval(nonexistent,X) then gives [].
put_new_table_identifiers :-
( bb_get(newly_created_table_identifiers_initialized, _) ->
true
; put_atts(NewlyCreatedFlag, newly_created_table_identifiers([]-0)),
bb_b_put(newly_created_table_identifiers, NewlyCreatedFlag),
bb_b_put(newly_created_table_identifiers_initialized, [])
).
% Returns a list of newly created table identifiers since the last call to reset_newly_created_table_identifiers/0, as well as the length of the list.
get_newly_created_table_identifiers(NewlyCreatedTableIdentifiers,NumIdentifiers) :-
bb_get(newly_created_table_identifiers, NewlyCreatedFlag),
get_atts(NewlyCreatedFlag, newly_created_table_identifiers(NewlyCreatedTableIdentifiers-NumIdentifiers)).
reset_newly_created_table_identifiers :-
bb_get(newly_created_table_identifiers, NewlyCreatedFlag),
put_atts(NewlyCreatedFlag, newly_created_table_identifiers([]-0)).
add_to_newly_created_table_identifiers(TableIdentifier) :-
bb_get(newly_created_table_identifiers, NewlyCreatedFlag),
get_atts(NewlyCreatedFlag, newly_created_table_identifiers(L1-Num1)),
Num2 is Num1 + 1,
put_atts(NewlyCreatedFlag, newly_created_table_identifiers([TableIdentifier|L1]-Num2)).
% PRIVATE
% Mode: + -
%
% Created in the fresh status.
p_create_table(CallVariant,TableIdentifier) :-
% We use a copy_term here so that we can be sure not to corrupt our table if CallVariant is "changed" afterwards.
copy_term(CallVariant,CallVariant2),
% Generate a table identifier, create the table and do bookkeeping.
gensym(table,TableIdentifier),
% Create a trie and a worklist.
trie_new(EmptyTrie),
wkl_new_worklist(TableIdentifier,NewWorklist),
put_atts(StatusFlag, table_status(fresh)),
%% this is important! we don't want to copy the incomplete table every time we refer to it,
%% which would occur if we used bb_put here.
%% note that the complete_table variant is written to the blackboard using bb_get.
atom_concat(TableIdentifier, nb_worklist, NbWorklistID),
atom_concat(TableIdentifier, nb_answer_trie, NbAnswerTrieID),
bb_put(TableIdentifier, table(CallVariant2,StatusFlag,NbWorklistID,NbAnswerTrieID)),
bb_put(NbWorklistID, table_nb_worklist(NewWorklist)),
bb_put(NbAnswerTrieID, table_nb_answer_trie(EmptyTrie)),
p_link_variant_identifier(CallVariant2,TableIdentifier),
add_to_newly_created_table_identifiers(TableIdentifier).
% Get the Status for table TableIdentifier
% Throws exception if this table does not exist.
tbd_table_status(TableIdentifier,Status) :-
p_get_table_for_identifier(TableIdentifier,Table),
tbd_table_status_(Table,Status).
% Is also used in other predicates than tbd_table_status.
tbd_table_status_(table(_CallVariant,StatusFlag,_NbWorklistID, _NbAnswerTrieID),Status) :-
get_atts(StatusFlag, table_status(Status)).
tbd_table_status_(complete_table(_,_,_),complete).
% PRIVATE
% Table must already exist.
p_get_table_for_identifier(TableIdentifier,Table) :-
bb_get(TableIdentifier,Table).
% Get the table identifier (!!) for call variant V, creating a new one if necessary.
%
% More costly than directly passing the table identifier for already existing tables.
%
% Since this creates a new table, this predicate is NOT meant for users who should get access to existing tables - f.e. benchmark shortest_path.P
%
table_for_variant(V,TableIdentifier) :-
( p_existing_table(V,TableIdentifier) ->
true
;
p_create_table(V,TableIdentifier)
).
% Get call variant for this table
get_call_variant(TableIdentifier,CallVariant) :-
p_get_table_for_identifier(TableIdentifier,Table),
get_call_variant_(Table,CallVariant).
get_call_variant_(table(CallVariant,_Status,_NbWorklistID,_NbAnswerTrieID),CallVariant).
get_call_variant_(complete_table(CallVariant,_NbWorklistID,_NbAnswerTrieID),CallVariant).
add_answer(TableIdentifier,A) :-
p_get_table_for_identifier(TableIdentifier,Table),
% arg(1,Table,CallVariant),
arg(3,Table,NbWorklistID),
arg(4,Table,NbAnswerTrieID),
bb_get(NbWorklistID,table_nb_worklist(Worklist)),
bb_get(NbAnswerTrieID,table_nb_answer_trie(AnswerTrie)),
copy_term(A,A2),
% This predicate succeeds if the answer was new, otherwise it fails.
trie_insert(AnswerTrie,A2,A2), % Use answer both as key and as value. Having it as value uses memory, but greatly simplifies getting all the answers.
% We got here, so trie_insert added a new answer.
% We must also insert this answer in the worklist
wkl_add_answer(Worklist,A2),
bb_put(NbWorklistID, table_nb_worklist(Worklist)),
bb_put(NbAnswerTrieID, table_nb_answer_trie(AnswerTrie)).
get_answer(TableIdentifier,A) :-
p_get_table_for_identifier(TableIdentifier,Table),
get_answer_trie_(Table,AnswerTrie),
% The trick is that we have stored the answers as values of the trie and that there is a method to get all the values.
trie_get_all_values(AnswerTrie,A).
% get_answer_trie_(TableOrCompleteTable,AnswerTrie).
% First argument is not a TableIdentifier.
get_answer_trie_(table(_CallVariant,_Status,_NbWorklistID, NbAnswerTrieID),AnswerTrie) :-
bb_get(NbAnswerTrieID, table_nb_answer_trie(AnswerTrie)).
get_answer_trie_(complete_table(_CallVariant,_NbWorklistID, NbAnswerTrieID),AnswerTrie) :-
bb_get(NbAnswerTrieID, table_nb_answer_trie(AnswerTrie)).
get_nb_identifiers(table(_CallVariant, _Status, NbWorklistID, NbAnswerTrieID), NbWorklistID, NbAnswerTrieID).
get_nb_identifiers(complete_table(_CallVariant, NbWorklistID, NbAnswerTrieID), NbWorklistID, NbAnswerTrieID).
% Get a list of answers for the given call variant.
% Used in compare_expected_for_variant/3 in testlib.pl
% IMPORTANT: table must be filled already, this is not done in this predicate! Therefore can be called during execution.
% V = variant
% LA = list of answers.
%
% More costly operation than directly giving the table identifier.
answers_for_variant(V,LA) :-
table_for_variant(V,TableIdentifier),
p_get_table_for_identifier(TableIdentifier,Table),
get_answer_trie_(Table,AnswerTrie),
findall(Value,trie_get_all_values(AnswerTrie,Value),LA).
% Set status of table TableIdentifier to active
set_active_status(TableIdentifier) :-
tbd_status_transition(TableIdentifier,active,fresh,'set_active_status').
cleanup_after_complete(TableIdentifier) :-
p_get_table_for_identifier(TableIdentifier,Table),
cleanup_after_complete_(Table,TableIdentifier).
% Clause for a (noncomplete) table.
cleanup_after_complete_(
table(CallVariant,_ActualOldStatus, NbWorklistID, NbAnswerTrieID),
TableIdentifier
) :-
bb_put(TableIdentifier,complete_table(CallVariant, NbWorklistID, NbAnswerTrieID)).
% If necessary for debugging add second clause for complete_table.
% Set status of table TableIdentifier to complete.
set_complete_status(TableIdentifier) :-
% The transition must be active to complete, otherwise we have an invalid status transition.
% Preexisting tables should have been cleaned-up, thus not have the form table/5 anymore, thus complete -> complete is not possible there.
p_get_table_for_identifier(TableIdentifier,Table),
set_complete_status_(Table,TableIdentifier).
% set_complete_status_(Table,TableIdentifier).
set_complete_status_(table(_CallVariant,_OldStatus,_NbWorklistID, _NbAnswerTrieID),TableIdentifier) :-
tbd_status_transition(TableIdentifier,complete,active,'set_complete_status').
tbd_status_transition_no_check(TableIdentifier,NewStatus) :-
p_get_table_for_identifier(TableIdentifier,Table),
tbd_status_transition_no_check_(TableIdentifier,Table,NewStatus).
tbd_status_transition_no_check_(TableIdentifier,Table,NewStatus) :-
Table = table(_,StatusFlag,_,_),
put_atts(StatusFlag, table_status(NewStatus)),
bb_put(TableIdentifier, Table).
% Set Table's status to NewStatus if current status is RequiredOldStatus, otherwise throw an exception mentioning CallerAsString: attempt to set NewStatus for table TableIdentifier, but current status was ActualOldStatus instead of RequiredOldStatus
tbd_status_transition(TableIdentifier,NewStatus,_RequiredOldStatus,_CallerAsString) :-
p_get_table_for_identifier(TableIdentifier,Table),
tbd_status_transition_no_check_(TableIdentifier,Table,NewStatus).
store_dependency(TableIdentifier,Suspension) :-
p_get_table_for_identifier(TableIdentifier, Table),
get_nb_identifiers(Table, NbWorklistID, _NbAnswerTrieID),
copy_term(Suspension, SuspensionCopy),
bb_get(NbWorklistID, table_nb_worklist(Worklist)),
wkl_add_suspension(Worklist, SuspensionCopy),
bb_put(NbWorklistID, table_nb_worklist(Worklist)).

View File

@@ -0,0 +1,121 @@
/* Part of SWI-Prolog
Author: Benoit Desouter <Benoit.Desouter@UGent.be>
Jan Wielemaker (SWI-Prolog port)
Copyright (c) 2016, Benoit Desouter
All rights reserved.
Ported to Scryer Prolog by Mark Thom (2019/2020).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
:- module(table_link_manager,
[ get_existing_tables/1, % -Tables
p_existing_table/2, % +Variant, -TableID
p_link_variant_identifier/2, % +Variant, -TableID
num_tables/1, % -Count
get_trie_table_link/1, % -Trie
put_new_trie_table_link/0
]).
:- use_module(library(atts)).
:- use_module(library(lists)).
:- use_module(library(non_iso)).
:- use_module(library(terms)).
:- use_module(trie).
:- attribute trie_table_link/1.
% This file defines a call pattern trie.
%
% This data structure keeps the relation between a variant and the
% corresponding table identifier using a trie. The trick is to make a
% canonical representation of a given variant using the numbervars/3
% predicate. The trie uses this canonical representation as key, and
% the table identifier as value.
% Uses the (private) global variable trie_table_link
% This predicate should be called exactly once.
% It throws an exception if it is called more than once.
%% table_link_manager_initialize
%
% Initializes the global variables `trie_table_link`. Normally
% called from table_datastructure_initialize/0.
put_new_trie_table_link :-
( bb_get(trie_table_link_initialized, _) ->
true
; trie_new(Trie),
bb_b_put(trie_table_link, TrieFlag),
bb_b_put(trie_table_link_initialized, []),
put_atts(TrieFlag, trie_table_link(Trie))
).
get_trie_table_link(Trie) :-
bb_get(trie_table_link, TrieFlag),
get_atts(TrieFlag, trie_table_link(Trie)).
% PRIVATE
% mode: + -
% Variant is not modified
variant_canonical_representation(Variant, CanonicalRepresentation) :-
copy_term(Variant, CanonicalRepresentation),
numbervars(CanonicalRepresentation, 0 ,_).
% Succeeds if there is a table TableIdentifier in existance for the
% given call variant Variant.
p_existing_table(Variant, TableIdentifier) :-
get_trie_table_link(Trie),
variant_canonical_representation(Variant, CanonicalRepresentation),
trie_lookup(Trie, CanonicalRepresentation, TableIdentifier).
% Important remark: we cannot use an out-of-the-box association list,
% because we need a lookup based on variant checking, which is not
% available for such lists. Converting the association list to a
% regular list => why would you use an association list in the first
% place...
p_link_variant_identifier(Variant, TableIdentifier) :-
get_trie_table_link(Trie),
variant_canonical_representation(Variant, CanonicalRepresentation),
trie_insert_succeed(Trie, CanonicalRepresentation, TableIdentifier).
% Returns a list of existing table identifiers.
% Rather costly.
get_existing_tables(Ts) :-
get_trie_table_link(Trie),
findall(T, trie_get_all_values(Trie, T), Ts).
% A very unefficient way of implementing this predicate. But it is
% only used for unit testing, so it doesn't really matter. Also, it
% doesn't require any additional bookkeeping during the actual
% execution.
num_tables(N) :-
get_existing_tables(Ts),
length(Ts, N).

View File

@@ -0,0 +1,220 @@
/* Part of SWI-Prolog
Author: Benoit Desouter <Benoit.Desouter@UGent.be>
Jan Wielemaker (SWI-Prolog port)
Copyright (c) 2016, Benoit Desouter
All rights reserved.
Ported to Scryer Prolog by Mark Thom (2019/2020).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
:- module(trie,
[ trie_new/1, % -Trie
trie_insert/3, % !Trie, +Key, +Value
trie_insert_succeed/3,
trie_lookup/3, % +Trie, +Key, -Value
trie_get_all_values/2 % +Trie, -Value
]).
:- use_module(library(assoc)).
:- use_module(library(atts)).
:- use_module(library(lists)).
:- attribute maybe_just/1, children/1.
% Implementation of a prefix tree, a.k.a. trie %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Desired complexity for lookup and insert: linear in the length of the key.
% ATTENTION: do not use the term functor_data/2; this is used internally here.
% Inspiration from http://en.wikipedia.org/wiki/Trie
% Structure of tries:
% trie_inner_node(MaybeValue,Children).
% where Children is an association list of nonvars to tries.
% and where MaybeValue is maybe_none/0 or maybe_just(Value).
% PRIVATE
% For a term of the form p(a,q(b)), "returns" functor_data(p,2) and [a,q(b)].
% p_trie_arity_univ(+Term,-FunctorData,-ArgumentsList).
p_trie_arity_univ(Term,functor_data(Name,Arity),Arguments) :-
Term =.. [Name|Arguments],
functor(Term,_,Arity).
% Returns a new empty trie.
trie_new(Trie) :-
empty_assoc(Assoc),
put_atts(A, children(Assoc)),
Trie = trie_inner_node(_,A).
% Succeeds if given trie does not contain any key-value pair.
% trie_is_empty(+Trie)
trie_is_empty(trie_inner_node(X,A)) :-
get_atts(X, -maybe_just(_)),
get_atts(A, children(Assoc)),
empty_assoc(Assoc).
% For internal use.
% For now, Children is an association list that can be manipulated using the assoc_ predicates.
trie_get_children(trie_inner_node(_,ChildNode),Children) :-
get_atts(ChildNode, children(Children)).
% For internal use.
trie_get_maybe_value(trie_inner_node(MaybeNode,_),MaybeValue) :-
get_atts(MaybeNode, maybe_just(MaybeValue)).
% Destructive update of the association list Children.
% For internal use.
trie_set_children(trie_inner_node(_,ChildNode),Children) :-
put_atts(ChildNode, children(Children)).
trie_set_maybe_value(trie_inner_node(MaybeNode, _),MaybeValue) :-
put_atts(MaybeNode, MaybeValue).
trie_insert_succeed(Trie,Key,Value) :-
( trie_insert(Trie,Key,Value) ->
true
;
true
).
% Succeeds if the term was not present, fails if the term was present.
% The term will be present now, whatever the outcome.
% We don't use an extra argument to indicate earlier presence, as this increases the trail size.
trie_insert(Trie,Key,Value) :-
p_trie_arity_univ(Key,FunctorData,KeyList),
trie_insert_1(KeyList,FunctorData,Trie,Value).
trie_insert_1([],FunctorData,Trie,Value) :-
trie_get_children(Trie,Assoc),
% You need Assoc twice: once to traverse through it, once keeping it as a whole for insertion using put_assoc/4.
trie_insert_a(Assoc,Assoc,FunctorData,Trie,Value).
% Inline the failure and success continuation to avoid a growing trail stack.
trie_insert_1([First|Rest],FunctorData,Trie,Value) :-
trie_get_children(Trie,Assoc),
% You need Assoc twice: once to traverse through it, once keeping it as a whole for insertion using put_assoc/4.
trie_insert_1_1(Assoc,Assoc,FunctorData,Trie,First,Rest,Value).
% Else part, base case: empty assoc list.
trie_insert_a(t,Assoc,FunctorData,Trie,Value) :-
trie_new(Subtrie),
trie_set_maybe_value(Subtrie,maybe_just(Value)),
put_assoc(FunctorData,Assoc,Subtrie,NewAssoc),
trie_set_children(Trie,NewAssoc).
% Then part, nonempty assoc tree.
trie_insert_a(t(K,V,_,L,R),Assoc,FunctorData,Trie,Value) :-
compare(Rel,FunctorData,K),
trie_insert_b(Rel,V,L,R,Assoc,FunctorData,Trie,Value).
% Recursively look in the left part of the assoc tree.
trie_insert_b(<,_V,L,_R,Assoc,FunctorData,Trie,Value) :-
trie_insert_a(L,Assoc,FunctorData,Trie,Value).
% Recursively look in the right part of the assoc tree.
trie_insert_b(>,_V,_L,R,Assoc,FunctorData,Trie,Value) :-
trie_insert_a(R,Assoc,FunctorData,Trie,Value).
trie_insert_b(=,V,_L,_R,_Assoc,_FunctorData,_Trie,Value) :-
trie_get_maybe_value(V,MaybeValue), % V is the Subtrie
( MaybeValue == maybe_none ->
trie_set_maybe_value(V,maybe_just(Value))
% Use true to indicate that the answer was new.
;
MaybeValue = maybe_just(JustValue),
( JustValue == Value ->
% Fail to indicate earlier presence
fail
;
throw('trie: attempt to update the value for a key')
)
).
% Else part, base case: empty assoc list
trie_insert_1_1(t,Assoc,FunctorData,Trie,First,Rest,Value) :-
% Assoc = t, % t is the empty assoc tree
trie_new(Subtrie),
put_assoc(FunctorData,Assoc,Subtrie,NewAssoc),
trie_set_children(Trie,NewAssoc),
trie_insert_2(First,Rest,Subtrie,Value).
% Then part, lookup in assoc list.
trie_insert_1_1(t(K,V,_,L,R),Assoc,FunctorData,Trie,First,Rest,Value) :-
compare(Rel,FunctorData,K),
trie_insert_1_1_1(Rel,V,L,R,Assoc,FunctorData,Trie,First,Rest,Value).
trie_insert_1_1_1(=,V,_L,_R,_Assoc,_FunctorData,_Trie,First,Rest,Value) :-
trie_insert_2(First,Rest,V,Value). % V is the Subtrie
trie_insert_1_1_1(<,_V,L,_R,Assoc,FunctorData,Trie,First,Rest,Value) :-
% Look in the left part of the assoc tree.
trie_insert_1_1(L,Assoc,FunctorData,Trie,First,Rest,Value).
trie_insert_1_1_1(>,_V,_L,R,Assoc,FunctorData,Trie,First,Rest,Value) :-
% Look in the right part of the assoc tree.
trie_insert_1_1(R,Assoc,FunctorData,Trie,First,Rest,Value).
trie_insert_2(RegularTerm,Rest,Trie,Value) :-
p_trie_arity_univ(RegularTerm,FunctorData,KList),
append(KList,Rest,KList2),
trie_insert_1(KList2,FunctorData,Trie,Value).
trie_lookup(Trie,Key,Value) :-
p_trie_arity_univ(Key,FunctorData,KeyList),
trie_lookup_1(FunctorData,KeyList,Trie,Value).
trie_lookup_1(FunctorData,Rest,Trie,Value) :-
% Select right subtree, fail if it isn't there, and do recursive call.
trie_get_children(Trie,Assoc),
get_assoc(FunctorData,Assoc,Subtrie), % Fails if not present
trie_lookup_2(Rest,Subtrie,Value).
trie_lookup_2([],Trie,Value) :-
% If the value at this trie is maybe_just(X), then X is our Value.
% Otherwise, there is no value for this key, so we fail...
trie_get_maybe_value(Trie,Value).
% Regular term at the head, like p or p(a). Not functor_data/2.
trie_lookup_2([RegularTerm|Rest],Trie,Value) :-
% split RegularTerm
p_trie_arity_univ(RegularTerm,FunctorData,KList),
% Make a recursive call on KList ++ Rest.
% Since we cannot implement p_trie_arity_univ so that "its result", KList, has a free variable at the end, without resorting to techniques that require linear time, we need a call to append/3. However, since KList will in general be rather short, I don't expect this to be a large problem in practice.
append(KList,Rest,KList2),
trie_lookup_1(FunctorData,KList2,Trie,Value).
% Returns all values in the trie by backtracking - we don't provide any information about the associated key.
trie_get_all_values(Trie,Value) :-
trie_get_maybe_value(Trie,Value).
trie_get_all_values(Trie,Value) :-
trie_get_children(Trie,Children),
gen_assoc(_Key, Children, ChildTrie),
trie_get_all_values(ChildTrie,Value).

View File

@@ -0,0 +1,120 @@
/* Part of SWI-Prolog
Author: Jan Wielemaker
Copyright (c) 2016, VU University Amsterdam
All rights reserved.
Ported to Scryer Prolog by Mark Thom (2019/2020).
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
:- module(table_wrapper,
[ %(table)/1, % +Predicates
op(1150, fx, table)
]).
:- use_module(library(dcgs)).
:- use_module(library(error)).
%%:- multifile
%% system:term_expansion/2,
%% tabled/2.
%%:- dynamic
%% system:term_expansion/2.
%% table(+PredicateIndicators)
%
% Prepare the given PredicateIndicators for tabling. Can only
% be used as a directive.
%% table(PIList) :-
%% throw(error(context_error(nodirective, table(PIList)), _)).
instantiation_error(Var) :-
throw(error(instantiation_error(Var), _)).
wrappers(Var) -->
{ var(Var), !,
instantiation_error(Var)
}.
wrappers((A,B)) --> !,
wrappers(A),
wrappers(B).
wrappers(Name//Arity) -->
{ atom(Name), integer(Arity), Arity >= 0, !,
Arity1 is Arity+2
},
wrappers(Name/Arity1).
wrappers(Name/Arity) -->
{ atom(Name), integer(Arity), Arity >= 0, !,
functor(Head, Name, Arity),
atom_concat(Name, ' tabled', WrapName),
Head =.. [Name|Args],
WrappedHead =.. [WrapName|Args],
'$module_of'(Module, Name) %prolog_load_context(module, Module)
},
[ ( Head :-
start_tabling(Module:Head, WrappedHead)
),
table_wrapper:tabled(Head, Module)
].
rename(M:Term0, M:Term, _) :-
atom(M), !,
rename(Term0, Term, M).
rename((Head :- Body), (NewHead :- Body), Module) :- !,
rename(Head, NewHead, Module).
rename((Head --> Body), (NewHead --> Body), Module) :- !,
functor(Head, Name, Arity),
PlainArity is Arity+1,
functor(PlainHead, Name, PlainArity),
table_wrapper:tabled(PlainHead, Module),
rename_term(Head, NewHead).
rename(Head, NewHead, Module) :-
table_wrapper:tabled(Head, Module), !,
rename_term(Head, NewHead).
rename_term(Compound0, Compound) :-
compound(Compound0), !,
Compound0 =.. [Name|Args],
atom_concat(Name, ' tabled', WrapName),
Compound =.. [WrapName|Args].
rename_term(Name, WrapName) :-
atom_concat(Name, ' tabled', WrapName).
user:term_expansion(Term0,
[ (:- multifile(table_wrapper:tabled/2))
| Clauses
]) :-
nonvar(Term0),
Term0 = (:- table Preds),
phrase(wrappers(Preds), Clauses).
user:term_expansion(Clause, NewClause) :-
nonvar(Clause),
'$module_of'(Module, Clause),
rename(Clause, NewClause, Module).