scryer-prolog icon indicating copy to clipboard operation
scryer-prolog copied to clipboard

'$call'/N does not call directly

Open triska opened this issue 2 years ago • 7 comments

From https://github.com/mthom/scryer-prolog/discussions/1513#discussioncomment-2978271, I take it that '$call'/N calls the goal directly, without any expansion.

Currently, if I define:

p(X) :-
        '$call'(g, X).

then this gets compiled to the WAM instructions:

?- wam_instructions(p/1, Is),
   maplist(portray_clause, Is).
put_constant(level(shallow),g,x(2)).
execute_n(2).

However, it seems the definition ought to be equivalent to:

p(X) :-
        g(X).

and that compiles to:

execute(g,1).

triska avatar Jun 19 '22 07:06 triska

On second thought, it seems that this would not make much sense, since code that emits '$call'(p, ...) where p is known, may just as well emit p(...) instead.

I filed this because using '$call' was suggested in https://github.com/mthom/scryer-prolog/discussions/1513#discussioncomment-2978271.

triska avatar Jun 19 '22 15:06 triska

Everything you've observed is true. The idea for meta-predicates like maplist/N is to keep their call/N calls compiled as they are but to wrap their goal arguments in the '$call' functor so that

maplist(portray_clause, Is)

is goal expanded to

maplist('$call'(portray_clause), Is)

Note that call/2 is defined as:

:- non_counted_backtracking call/2.
call(A,B) :-
   (  var(A) ->
      instantiation_error(call/2)
   ;  A = '$call'(C) ->
      '$prepare_call_clause'(D,E,C,B),
      '$call_with_inference_counting'('$call'(E:D))
   ;  '$prepare_call_clause'(D,E,A,B),
      expand_goal(call(E:D),E,call(F)),
      '$call_with_inference_counting'('$call'(F))
   ).

expand_goal/3 could still be applied to the goal arguments prior to wrapping, but I'm not totally certain on how it should apply to incomplete goals. The more aggressive inlining you proposed in the discussion doesn't pose this problem.

We can bridge the two ideas as follows. We generate an auxiliary predicate, as you suggested. The goal arguments are expanded via expand_goal/3 before the auxiliary predicate is entered where the expanded goals are invoked via $call in the call sites of the original predicate.

mthom avatar Jun 19 '22 16:06 mthom

Is there hope that also #1390 could be addressed here? The idea would be to have two cases: with expansion (and thus losing information about local variables) and as the frequent fallback without expansion and there retaining that info.

UWN avatar Jun 19 '22 18:06 UWN

where the expanded goals are invoked via $call in the call sites of the original predicate.

I have filed #1516 to clarify what I mean with the auxiliary predicate. I think '$call' is not necessary in this case, because the auxiliary predicate can be invoked directly, and so no meta-call seems to be needed.

triska avatar Jun 19 '22 19:06 triska

You're right to point out that '$call/N' isn't necessary. Two points in favor of the approach I outlined above, though:

  1. Compiling a maplist_for_p for every predicate p used in maplist would generate a lot of additional code, a problem at least until code GC is implemented, and,
  2. It would remove much of the goal expansion overhead for all predicates using call/N, not just those of library(lists) et al.

Then again, library(lists) will need an ad hoc approach either way, since it's used in the bootstrapping process and so its compilation isn't subject to expand_goal.

mthom avatar Jun 19 '22 20:06 mthom

and so no meta-call seems to be needed.

What about calling an integer? This case needs to be recognized here somehow.

UWN avatar Jun 20 '22 06:06 UWN

I think this can be safely closed once rebis-dev is merged (see #1516), since the relevant goals are now automatically inlined.

triska avatar Jul 23 '22 20:07 triska

Thank you a lot!

triska avatar Nov 10 '22 22:11 triska