Files
mercury/tests/warnings/bug311.m
2023-09-16 02:07:56 +10:00

216 lines
7.3 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ts=4 sw=4 et ft=mercury
%---------------------------------------------------------------------------%
%
% Author: original version written by Pedro Mariano; reformatted for our
% standard style, cut down to minimal size and the problem diagnosed by zs.
%
% Since the two predicates lattice_good and lattice_bad have the same
% semantics, invoking solutions.solutions on them should yield the same
% results. The bug is that the two calls to solutions yield different results:
% calling solutions on lattice_good returns both expected results, while
% calling solutions on lattice_bad returns only one.
%
% The source of the problem is that the C code we generate for lattice_bad
%
% 1. creates a memory cell for G and fills in the xsize and ysize fields;
% 2. creates a choice point for the disjunction;
% 3a. fills in the boundary field with `torus' in the first arm of the
% disjunction, and
% 3b. fills in the boundary field with `closed' in the second arm of the
% disjunction; and then
% 4. the code after the disjunction returns G.
%
% When you print the result after each exit from lattice_bad, you get the
% the right answer. The problem is that the code in the second arm of the
% disjunction CLOBBERS a field in the memory cell returned by the first arm.
% EVERY ANSWER returned by lattice_bad specifies the same memory address for G.
% When the solutions predicate sorts the set of solutions, all the solutions
% it sorts look exactly alike, because they are all stored at the same address.
% At that time, that cell will contain the last solution returned.
%
% In a sense, neither the code generated for lattice_bad nor the solutions
% predicate are to blame; they are both doing what they are supposed to.
% The actual problem is that the code of solutions makes an assumption
% (that the memory cells containing the solutions are static and won't change)
% that is incorrect in the presence of code that fills in some parts of a
% memory cell *before* a disjunction (creating the cell, and thus establishing
% its address), and some other parts *inside* the disjunction.
%
% We could fix the problem in several ways.
%
% The quickest fix would be to make solutions deep copy all answers. Due to
% the potentially horrendous runtime cost, I don't think we should do this.
%
% We could generate an error message for disjunctions that have variables
% that are bound but not ground at the start of the disjunction and become
% more instantiated (maybe ground, maybe not) during the disjunction.
%
% The most complete solution would be to look for such variables, but instead
% of generating an error for them, we could modify the HLDS to eliminate the
% condition that causes the problem. We can do this by introducing a proxy
% for each such variable, a proxy that is set entirely inside the disjuncts.
%
% The idea is to replace the original HLDS
%
% bug311.lattice_bad(X, Y, G) :-
% (
% G = bug311.lattice(X, V_9, V_10),
% G = bug311.lattice(V_11, Y, V_12),
% (
% V_8 = bug311.torus,
% G = bug311.lattice(V_13, V_14, V_8)
% ;
% V_7 = bug311.closed,
% G = bug311.lattice(V_15, V_16, V_7)
% )
% ).
%
% with
%
% bug311.lattice_bad(X, Y, G) :-
% (
% G_prime1 = bug311.lattice(X, V_9, V_10),
% G_prime1 = bug311.lattice(V_11, Y, V_12),
% (
% G_prime1 = bug311.lattice(G_prime1_f1, G_prime1_f2, _),
% V_8 = bug311.torus,
% G = bug311.lattice(G_prime1_f1, G_prime1_f2, V_8)
% ;
% G_prime1 = bug311.lattice(G_prime1_f1, G_prime1_f2, _),
% V_7 = bug311.closed,
% G = bug311.lattice(G_prime1_f1, G_prime1_f2, V_7)
% )
% ).
%
% Note that in the original test case, G has TWO of its fields filled out
% by two SEPARATE disjunctions, so we would have to create TWO separate
% proxy variables for it.
%
% This change would have to be done by a new pass in the compiler, but
% this pass would have to be invoked only for predicates that (a) contain
% disjunctions, and (b) some of these disjunctions further instantiate
% already-partially-filled-in memory cells. We could have the pre-codegen
% simplify pass look for such situations, and record the set of variables
% involved (G in this case); the new pass would have to be invoked
% only if the set of these variables is not empty. Since this problem
% has existed for a long time and does not seem to have been noticed before,
% this definitely will be a rare occurrence.
%
% Note that the problem exists in both for low and high level C grades, and
% probably exists for the other MLDS grades as well.
%
% Note also that although this solution will fix the bug by making solutions
% generate the right set of solutions, the copying involved will slow down
% the generated code. The more fields filled in piecemeal, the more the cell
% will be copied, and the slower the code will be.
%
% Therefore for the original submitter, I propose replacing lattice_bad
% (which exhibits the bug) with lattice_bad_fixed (which does not).
%
%---------------------------------------------------------------------------%
:- module bug311.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
:- implementation.
:- import_module list.
:- import_module solutions.
:- type geometry
---> wellmixed
; lattice(
xsize :: int,
ysize :: int,
boundary :: boundary
) .
:- type boundary
---> torus
; closed.
%---------------------------------------------------------------------------%
main(!IO) :-
X = 2,
Y = 3,
solutions.solutions(lattice_good(X, Y), SG),
io.print_line(SG, !IO),
solutions.solutions(lattice_bad(X, Y), SB),
io.print_line(SB, !IO),
solutions.solutions(lattice_bad_fixed(X, Y), SBF),
io.print_line(SBF, !IO),
solutions.solutions(lattice_bad_3(X, Y), SB3),
io.print_line(SB3, !IO).
%---------------------------------------------------------------------------%
:- pred lattice_good(int::in, int::in, geometry::out) is multi.
lattice_good(X, Y, lattice(X, Y, torus)).
lattice_good(X, Y, lattice(X, Y, closed)).
:- pred lattice_bad(int::in, int::in, geometry::out) is multi.
lattice_bad(X, Y, G) :-
G ^ xsize = X,
G ^ ysize = Y,
(
G ^ boundary = torus
;
G ^ boundary = closed
).
:- pred lattice_bad_3(int::in, int::in, {geometry, geometry, geometry}::out)
is multi.
lattice_bad_3(X, Y, Tuple) :-
G1 ^ xsize = X,
G1 ^ ysize = Y,
G2 ^ xsize = X,
G2 ^ ysize = Y,
G3 ^ xsize = X,
G3 ^ ysize = Y,
(
G1 ^ boundary = torus,
G2 ^ boundary = torus,
G3 ^ boundary = torus
;
G1 ^ boundary = closed,
G2 ^ boundary = closed,
G3 ^ boundary = closed
),
Tuple = {G1, G2, G3}.
:- pred lattice_bad_lambda(int::in, int::in, geometry::out) is multi.
lattice_bad_lambda(X, Y, G) :-
P =
( pred(YY::in, GG::out) is multi :-
GG ^ xsize = X,
GG ^ ysize = YY,
(
GG ^ boundary = torus
;
GG ^ boundary = closed
)
),
P(Y, G).
:- pred lattice_bad_fixed(int::in, int::in, geometry::out) is multi.
lattice_bad_fixed(X, Y, G) :-
(
B = torus
;
B = closed
),
G = lattice(X, Y, B).