Technical details
Doesn’t have to be closures, either; this has the same problem.
(program entry point)
(temp 1)
(temp 1)
(show [ [1] [2] [3] ])
(show $X) $X
[[[[2] [3]]] [2] [3]]
The argument to (temp $) has to be the same as one of the elements in the lists:
(program entry point)
(temp @apple)
(temp @apple)
(show [ [apple] [banana] [cantaloupe] ])
(show $X) $X
[[[[banana] [cantaloupe]]] [banana] [cantaloupe]]
(program entry point)
(temp @banana)
(temp @banana)
(show [ [apple] [banana] [cantaloupe] ])
(show $X) $X
[[apple] [[[cantaloupe]]] [cantaloupe]]
And it has to be a literal. This is fine:
(program entry point)
(temp @banana)
(temp $)
(show [ [apple] [banana] [cantaloupe] ])
(show $X) $X
[[apple] [banana] [cantaloupe]]
How bizarre!
This seems to be the minimal example:
(program entry point)
(temp 1)
(temp 1)
(show [ [1] [2] ])
(show $X) $X
The broken IR:
Intermediate code for (temp $): 0 -1
R0: (group leader) (1 incoming) clause 65535
JUMP R1 - -
R1: (part of group R0) (1 incoming) clause 65535
ALLOCATE 0 1 -
UNIFY A0 1 -
TRACEPOINT 0 closurebug.dg:4
MAKE_PAIR_VV X0 2 []
MAKE_PAIR_VV A0 X0 []
MAKE_PAIR_VV X1 A0 []
MAKE_PAIR_VV A0 X1 A0
TRACEPOINT 1 closurebug.dg:5
SET_CONT R2 - -
INVOKE_ONCE (show $)
R2: (group leader) (1 incoming) clause 65535
TRACEPOINT 3 closurebug.dg:5
DEALLOCATE 1 - - -
PROCEED 0 - - -
Working IR (with 3 instead of 1):
R0: (group leader) (1 incoming) clause 65535
JUMP R1 - -
R1: (part of group R0) (1 incoming) clause 65535
ALLOCATE 0 1 -
UNIFY A0 1 -
TRACEPOINT 0 closurebug.dg:4
MAKE_PAIR_VV X0 2 []
MAKE_PAIR_VV A0 X0 []
MAKE_PAIR_VV X1 3 []
MAKE_PAIR_VV A0 X1 A0
TRACEPOINT 1 closurebug.dg:5
SET_CONT R2 - -
INVOKE_ONCE (show $)
R2: (group leader) (1 incoming) clause 65535
TRACEPOINT 3 closurebug.dg:5
DEALLOCATE 1 - - -
PROCEED 0 - - -
So the working code does this:
X = [2]
A = [X] = [[2]]
Y = [3]
A = [X|A] = [[3] [2]]
And the broken code does this:
X <- [2]
A <- [X] = [[2]]
Y <- [A] = [[[2]]]
A <- [X|A] = [[2] [[2]]]
It looks like it’s using [A] instead of [1] when building the list, even though A has already been given a new value and is no longer 1.
The only place a MAKE_PAIR_VV instruction is emitted is in comp_value_into in compile.c. To make each of its arguments, it calls comp_value(cl, an->children[i], seen, known_args). That routine checks if the value it’s compiling is equivalent to any of the known_args, and if so, it uses that argument’s value to cut down on temporary variables.
So the problem is that the slot in known_args isn’t getting cleared out when the argument gets reassigned!
if(dest.tag == OPER_ARG) { // See issue #204
known_args[dest.value] = NULL;
}
Problem solved. Haha!