# Incremental tabling in compiled code not working as I expect

**URL:** <https://swi-prolog.discourse.group/t/incremental-tabling-in-compiled-code-not-working-as-i-expect/8679>\
**Category:** General\
**Tags:** bug\
**Created:** [December 18, 2024, 4:16pm UTC](https://swi-prolog.discourse.group/t/incremental-tabling-in-compiled-code-not-working-as-i-expect/8679 "2024-12-18T16:16:45Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![fabeulous](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/fabeulous/32/6900_2.png) [@fabeulous](https://swi-prolog.discourse.group/u/fabeulous)\
**Post date:** [December 18, 2024, 4:16pm UTC](https://swi-prolog.discourse.group/t/incremental-tabling-in-compiled-code-not-working-as-i-expect/8679/1 "2024-12-18T16:16:45Z")

</div>

_SPOILER ALERT_: for those solving Advent of Code the following will contain a (likely non-idiomatic) solution for part 2 of day 18.

This year, I’ve been trying to learn/relearn Prolog by solving the Advent of Code problems. On today’s problem (day 18) I’ve encountered different behavior between compiled and directly run code.

I’m (ab-)using a dynamic predicate and tabling in the solution. From reading the reference manual this seems to necessitate incremental tabling to properly invalidate the table.

My code is:

```prolog
:- dynamic([corrupted/2], [incremental(true)]).

initCorrupted(Points) :-
  retractall(corrupted(_,_)),
  foreach(member(X/Y,Points), assertz(corrupted(X,Y))).

step(X/Y, X2/Y) :-
  (X2 is X + 1; X2 is X - 1),
  0 =< X2, X2 =< 3,
  \+ corrupted(X2,Y).
step(X/Y, X/Y2) :-
  (Y2 is Y + 1; Y2 is Y - 1),
  0 =< Y2, Y2 =< 3,
  \+ corrupted(X,Y2).

:- table pathToGoal/2 as incremental.
pathToGoal(Start,Start).
pathToGoal(Start,End) :-
  step(Start,Next),
  pathToGoal(Next,End).

binarySearch(Low,Low,_, Low).
binarySearch(Low,High,Points, Res) :-
  Mid is (Low + High) // 2,
  length(Prefix, Mid),
  append(Prefix,_,Points),
  initCorrupted(Prefix),
  ( pathToGoal(0/0,3/3)
  -> Low2 is Mid + 1, binarySearch(Low2,High,Points,Res)
  ; binarySearch(Low,Mid,Points,Res)
  ).

compute2(Points, Res) :-
  length(Points,N),
  High is N - 1,
  binarySearch(0, High, Points, M),
  nth1(M, Points, Res).

run() :-
  Points = [0/3, 1/2, 2/1, 3/0, 2/2, 1/3, 0/2],
  compute2(Points, X/Y),
  format('Result: ~d,~d~n', [X,Y]).

```

Directly running this I get the expected output

```prolog
$ swipl -g run -g halt aoc18.pl
Result: 3,0

```

However, when first compiling the code, I get a different result

```prolog
$ swipl -g run -g halt -o aoc -c aoc18.pl
$ ./aoc
Result: 1,3

```

This is also the result I get when not declaring the table of `pathToGoal` as incremental. Manually abolishing the table in `initCorrupted/1` with `abolish_table_subgoals(pathToGoal(_,_))` fixes the issue in the compiled code, leading me to believe the table is not properly invalidated.

Is this behavior expected, and I am doing something wrong? Or is this a bug?

PS: I’m also happy to hear about any improvements to make my solution more idiomatic.

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [December 18, 2024, 4:56pm UTC](https://swi-prolog.discourse.group/t/incremental-tabling-in-compiled-code-not-working-as-i-expect/8679/2 "2024-12-18T16:56:16Z")

</div>

You are right. The incremental property of the dynamic predicate corrupted is lost in the compiled state. That is a bug. There are also at least three dubious things with the code:

- foreach/2 is something to be avoided if you can. In this case it is ok to backtrack over the 2nd argument and thus forall/2 is faster and has no issues.
- Commit to a tabled predicate is wrong as it looses answers. I think it is safe in this particular case as the call is ground.
- As the second clause of binarySearch/4 keeps firing, it produces an infinite sequence of the same answer.

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [December 18, 2024, 5:05pm UTC](https://swi-prolog.discourse.group/t/incremental-tabling-in-compiled-code-not-working-as-i-expect/8679/3 "2024-12-18T17:05:04Z")

</div>

Fixed with [FIXED: Preserve `incremental` property when creating a saved state. · SWI-Prolog/swipl-devel@21264b8 · GitHub](https://github.com/SWI-Prolog/swipl-devel/commit/21264b88ca183c4a0abaf1294a18b52c6ff2375e)

---

<div class="post-metadata">

**Author:** ![fabeulous](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/fabeulous/32/6900_2.png) [@fabeulous](https://swi-prolog.discourse.group/u/fabeulous)\
**Post date:** [December 18, 2024, 6:13pm UTC](https://swi-prolog.discourse.group/t/incremental-tabling-in-compiled-code-not-working-as-i-expect/8679/4 "2024-12-18T18:13:30Z")

</div>

Thank you for the quick answer, fix, and suggested improvements. I really appreciate it.

> [@jan](#):
>
> Commit to a tabled predicate is wrong as it looses answers. I think it is safe in this particular case as the call is ground.

I assume this refers to the `pathToGoal(0/0,3/3)` on the left of `->`? I see now that this is also mentioned in the documentation, however, I don’t fully understand the issue. Does the commit cause an incomplete table so that later calls to the tabled predicate miss the “unexplored” solutions? Are there any resources where I can find problematic examples of this? I can’t seem to construct one myself.

---

<div class="post-metadata">

**Author:** ![jan](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/jan/32/4_2.png) [@jan](https://swi-prolog.discourse.group/u/jan)\
**Post date:** [December 19, 2024, 9:17am UTC](https://swi-prolog.discourse.group/t/incremental-tabling-in-compiled-code-not-working-as-i-expect/8679/5 "2024-12-19T09:17:53Z")

</div>

> [@fabeulous](#):
>
> Does the commit cause an incomplete table so that later calls to the tabled predicate miss the “unexplored” solutions?

Yes. But, SWI-Prolog does _local scheduling_, which implies that it tries to complete a table before continuing the execution. To get trapped by this the predicate needs to be incomplete when the cut is used, which implies that it needs to be part of a larger _strongly connected component_, i.e., a set of mutually dependent tabled predicates. The way it works is that if, while filling the table, it hits a new tabled predicate, it extends its scope (strongly connected component) and solves the two predicates together.
