Setarg/3 circular bug?

This test predicate:

test(A) :-
    A = v(L),
    L1 = [3|L],
    setarg(1, A, L1).

… produces the surprisingly circular:

?- test(A).
A = v(_S1), % where
    _S1 = [3|_S1].

Changing the setarg to nb_setarg produces the expected:

?- test(A).
A = v([3|_]).

When running the equivalent code in the console, setarg seems to behave:

?- A = v(L), L1 = [3|L], setarg(1, A, L1).
A = v([3|L]),
L1 = [3|L].

This is reproducible in swi-prolog 8.4.3 and 8.5.13

This seems simply a bug, and hope this “bug”
not to threaten the possible user on the wonderful setarg/3 native SWI-Prolog in the future.

This is because if the argument that is set is a variable it uses normal unification. I thought we could easily fix this, but this is really nasty. This is what happens:

  • allocate v(L).
  • allocate [3|L], now the tail of this list becomes a reference pointer to the L in v(L).
  • Now we set the 1st argument of v() to the list. The tail of the list is still a reference pointer to the 1st argument of v() and thus effectively a reference pointer to the list itself, creating a cyclic term.

If you swap the two unifications (and add some call before them to avoid inlining the A = v(L) into the head which makes it the first again) the modified version produced the result you expect.

This cannot really be fixed (I think). A proper fix would (I think) require a complete scan of the stacks for reference pointers to the (variable) argument, allocate a new variable and make all these reference pointers point at the newly allocated variable.

So, I think it is better to document the fact that if the argument is unbound, normal unification takes place. This makes the behavior consistent regardless of the ordering.

1 Like

On the second rule, I always think in using setarg/3, allocate [3|L], and the tail of this list simply a "reference pointer to the variable cell for L (independent of the structure v(L)). I assumed something like argument place of the structure v(arg<1>). This is only a comment without considering any implementation of Prolog basics.

This would imply also to change current behavior of setarg/3:

?- A = v(B), setarg(1, A, 3), write(B).
A = v(3).

Thanks. This is a result of the toplevel which creates v(B) in the end using a separately allocated variable and a reference pointer. Pushed 1a4ad739811148e68b14b2160cc3cc4708299114 to get consistent behavior (writing “3”).

It seems a bad idea to use setarg/3 when the designated location is a variable unless this is the result of using functor/3 to create a term with only variables and these arguments are only accessed using setarg/3. The docs already claim setarg/3 is a can of worms. I recall it has been discussed a couple of times in the ISO standard group with no conclusion. SICStus has mutable terms, which implements a safe interface for mutable data. It is part of the SICStus emulation for SWI-Prolog, implemented on top of setarg/3. I think the SICStus implementation comes with a considerable (space) overhead for implementing what I see as one of the main use cases: (large) arrays with mutable elements.

Isn’t better to document that setarg/3 with a variable as the target compound argument behaves just like arg/3 with the same arguments?

1 Like

Thanks. Clever remark. Added to the docs.

Why does nb_setarg not seem to have this circular ref problem?

Because it copies the value, so the variables in the value are renamed.

I prefer much this behavior.

I don’t like this behavior, because it seems to me a bug. Perhaps I don’t understand fully Jan’s explanation on special role of toplevel interface of Prolog. Sorry for this.

The behavior of the query differs depending on subtle ways of how the involved terms are created. That is highly undesirable. Now the behavior no longer depends on such details and is sufficiently simple that it can be documented. Bottom line, if the target argument is unbound, make sure it is not bound to a variable that is also used elsewhere. So, using functor/3 to create a large array and setarg/3 on this term is fine.

I agree with @brebs that the behavior he expected is preferable, but it is simply impossible to implement without either doubling the space required to represent a term with variables (while slowing down access) or a complete scan of the runtime checks inside setarg/3. Both are unacceptable, so we better make the behavior consistent and document what it does.

It sounds reasonable. In particular, as a user of setarg/3, I feel at ease about functor examples. However, to be honest, I still don’t understand why setarg argument on toplevel seemingly variable (possible instantiated) should be unified. In other words, what wrong if the unification is not perormed. I appreciate if some one gives an example which explains that toplevel unfication is necessary for setarg/3.

Basically we cannot avoid the unification in some scenarios, so we better do it always rather than sometimes yes, sometimes no under conditions that are impossible to explain without explaining a lot of low-level details on how Prolog stores its data on the stacks.