Using prolog_walk_code/1 to identify meta predicate calls?

This is some example code that reveals a transformation of a call graph made by using meta_predicate/1.

This is based on the blog Using SWI-Prolog’s modules.

prolog_walk_code/1 is used to create the call graph as calls/2 facts. (Example)

Directory used for example: C:\Users\Groot\Documents

File: add.pl

:- module('add',[
        add_all/2,
        add_some/3
    ]).

% :- meta_predicate add_some(-,1,-).

add_all(List,Sum) :-
    sum_list(List,Sum).

add_some(List,Goal,Sum) :-
    add_some_(List,Goal,Sum).

add_some_([],_,[]).
add_some_([H0|T0],Goal,R) :-
    (
        call(Goal,H0)
    ->
        R = [H0|T]
    ;
        R = T
    ),
    add_some_(T0,Goal,T).

File: filters.pl

:- module('filters',[
        less_than_three/1
    ]).


less_than_three(N) :-
    integer(N),
    N < 3

File: broken.pl

:- module('broken',[
]).

:- use_module(add).
:- use_module(filters).

go :-
    add_some([1,2,3,4,5], less_than_three, S),
    writeln(S).

File: call_graph.pl

:- module('call_graph',[
        assert_call_graph/0
    ]).

:- dynamic
        calls/2.

assert_call_graph :-
        retractall(calls(_, _)),
        prolog_walk_code([ trace_reference(_),
                           on_trace(assert_edge),
                           source(true),
                           infer_meta_predicates(all),
                           verbose(true)
                         ]),
        predicate_property(calls(_,_), number_of_clauses(N)),
        format('Got ~D edges~n', [N]).

assert_edge(Callee, Caller, _Where) :-
        calls(Caller, Callee), !.
assert_edge(Callee, Caller, _Where) :-
        assertz(calls(Caller, Callee)).

Verify the code is broke

C:\Users\Groot>cd "C:\Users\Groot\Documents"
C:\Users\Groot\Documents>swipl -q -t broken:go,halt -f broken.pl
ERROR: add:add_some_/3: Unknown procedure: add:less_than_three/1
   Exception: (7) add:less_than_three(1) ? 

Load the code and use assert_call_graph/0.

?- working_directory(_,'C:/Users/Groot/Documents').
true.

?- [call_graph,add,filters,broken].
true.

?- assert_call_graph.
% Found new meta-predicates in iteration 1 (5.406 sec)
% :- meta_predicate add:add_some_(*,1,*).
% :- meta_predicate prolog:generated_predicate(:).
% :- meta_predicate user:prolog_exception_hook(*,*,*,:).
% Restarting analysis ...
% Found new meta-predicates in iteration 2 (5.484 sec)
% :- meta_predicate add:add_some(*,1,*).
% Restarting analysis ...
Got 8,703 edges
true.

Save the calls/2 to file calls (no meta).txt

?- tell('./calls (no meta).txt'), listing(call_graph:calls), told.
?- halt.

Thanks Jan W. (ref)

File: calls (no meta).txt

...

calls(add:add_some(A, B, C), add:add_some_(A, B, C)).
calls(add:add_some_([A|_], B, _), add:call(B, A)).
calls(add:add_some_([A|_], _, B), add:(B=[A|_])).
calls(add:add_some_([_|A], B, _), add:add_some_(A, B, _)).
calls(add:add_all(A, B), add:sum_list(A, B)).

...

calls(filters:less_than_three(A), filters:integer(A)).
calls(filters:less_than_three(A), filters:(A<3)).

...

calls(broken:go, broken:add_some([1, 2, 3, 4, 5], less_than_three, _)).
calls(broken:go, broken:writeln(_)).

...

Add meta_predicate to add module.

:- meta_predicate add_some(-,1,-).

Don’t forget to save the change.

Verify the code works

C:\Users\Groot>cd "C:\Users\Groot\Documents"
C:\Users\Groot\Documents>swipl -q -t broken:go,halt -f broken.pl
[1,2]

Load the code and use assert_call_graph/0.

?- working_directory(_,'C:/Users/Groot/Documents').
true.

?- [call_graph,add,filters,broken].
true.

?- assert_call_graph.
% Found new meta-predicates in iteration 1 (6.234 sec)
% :- meta_predicate add:add_some_(*,1,*).
% :- meta_predicate prolog:generated_predicate(:).
% :- meta_predicate user:prolog_exception_hook(*,*,*,:).
% Restarting analysis ...
Got 7,991 edges
true.

Save the calls/2 to file calls (meta).txt

?- tell('./calls (meta).txt'), listing(call_graph:calls), told.

File: calls (meta).txt

...

calls(add:add_some(A, B, C), add:add_some_(A, B, C)).
calls(add:add_some_([A|_], B, _), add:call(B, A)).
calls(add:add_some_([A|_], _, B), add:(B=[A|_])).
calls(add:add_some_([_|A], B, _), add:add_some_(A, B, _)).
calls(add:add_all(A, B), add:sum_list(A, B)).

...

calls(filters:less_than_three(A), filters:integer(A)).
calls(filters:less_than_three(A), filters:(A<3)).

...

calls(broken:go, broken:add_some([1, 2, 3, 4, 5], less_than_three, _)).
calls(broken:go, broken:less_than_three(_)).
calls(broken:go, broken:writeln(_)).

...

NB

So adding

:- meta_predicate add_some(-,1,-).

to the module add the following call at the binary level (think meta predicate at the source level) is created

calls(broken:go, broken:less_than_three(_))..


Was hoping gxref/0 could be used for finding such calls created via meta_predicate, but I don’t know if it is possible or just did not find how it is done with gxref/0.


EDIT

trace/0 and gtrace/0 are aware of meta_predicate/1

File: library.pl

:- module(library, [my_call/1]).

:- meta_predicate(my_call(0)).

my_call(Goal) :-
    write('Calling: '),
    writeq(Goal), nl,
    call(Goal).

me(library).

File: client.pl

:- module(client, [
        test_1/1
    ]).

:- use_module(library, [my_call/1]).

me(client).

% Me = client.
test_1(Me) :-
    my_call(me(Me)).
[trace]  ?- client:test_1(Me).
   Call: (10) client:test_1(_23106)
   Unify: (10) client:test_1(_23106)
^  Call: (11) library:my_call(me(_23106))
^  Unify: (11) library:my_call(client:me(_23106))
   Call: (12) write('Calling: ')
Calling: 
   Exit: (12) write('Calling: ')
   Call: (12) writeq(client:me(_23106))
client:me(_23106)
   Exit: (12) writeq(client:me(_23106))
   Call: (12) nl

   Exit: (12) nl
   Call: (12) client:me(_23106)
   Unify: (12) client:me(client)
   Exit: (12) client:me(client)
^  Exit: (11) library:my_call(client:me(client))
   Exit: (10) client:test_1(client)
Me = client.

The lines marked ^ indicate calls to transparent predicates, AKA meta predicates.

image

See personal note in this post for table of icons used in Call Stack panel.

1 Like

Or, a bit simpler and not spamming your console:

?- tell('./calls (meta).txt'), listing(call_graph:calls), told.

gxref/0 works differently. It runs on top of library(prolog_xref), which does source analysis. The code walking library examines the binary and thus examines all code while the source analysis can be incomplete because there are many ways how the actually loaded code may differ from the source (term/goal expansion, generated dynamic code, etc.)

1 Like