Files
mercury/tests/hard_coded/gh133.m
Zoltan Somogyi 792ee5e89c Fix compiler abort for undeleted dead code.
This fixes Github issue #133.

The bug occurred after jumpopt.m transformed code of the form

    %       if (Cond) L1
    %       r1 = MR_FALSE

when followed immediately by

    %       <epilog>
    %       ...
    %     L1:
    %       r1 = MR_TRUE
    %       <epilog>

into code of the form

    %       r1 = Cond
    %       <epilog>

The transformation left both epilogs in the instruction sequence,
even though at least the first, and probably both, were now being dead code.
The problem arose because the compiler did not clean up either dead epilogue,
leading to failed assertion in a later code optimization pass.

compiler/optimize.m:
    Fix the bug by forcing the execution of the passes that clean up dead code
    if jumpopt makes any changes to the instruction sequence.

compiler/jumpopt.m:
    When replacing an if_val instruction whose target and fallthrough
    lead to two different semidet epilogues (one assigning 0 to r1 and
    one assigning 1) with a single epilogue assigning to r1 the value
    of a bool expression and then returning, delete all the instructions
    following the if_val up to but not including the next label.
    This is because replacing the if_val by by the new epilogue makes
    those instructions unreachable.

The diffs for the next two files are not part of the fix, but I noticed
the need for them while hunting the bug.

compiler/opt_debug.m:
    Give a predicate a more descriptive name, and simplify its code.

    Make the dumps of computed goto instructions more readable, both
    by printing each target on its own line, and by printing the value
    corresponding to each target.

compiler/hlds_out_type_table.m:
    Format the description of arguments' offsets in heap cells
    in a way that is more readable. Print just one offset for an argument
    in the usual case where it is not preceded by any nonarguments such as
    type_infos/typeclass_infos (i.e. if its arg-only offset is the same
    as its offset from the start of the heap cell). If those two offsets
    differ, then include a description of each offset with its value.

    Move the description of each du type's kind (i.e. whether it is an
    enum type, notag type etc) to *before* the description of its
    function symbols.

tests/hard_coded/gh133.{m,exp}:
    A test case for this bug, derived from the one in github issue #133.

tests/hard_coded/Mmakefile:
    Enable the new test case.
2024-07-11 11:08:15 +02:00

124 lines
3.5 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ts=4 sw=4 et ft=mercury
%---------------------------------------------------------------------------%
%
% Test case contributed by C4Cypher.
%
% This is a test case for a rare bug in the LLDS optimizer.
% The symptom was a compiler abort when processing the is_bar predicate,
% which is a switch that cover all the barN function symbols of the foo type.
%
% The bug happened because of the following sequence of events.
%
% - The code generator generates a computed goto for on the primary tag
% of the one input arg, with one of the labels leading to another computed
% goto on the remote secondary tag.
%
% - The early optimization phases convert this structure to a simple test:
% if the primary tag is zero, go to a label that assigns 0 to r1 and returns,
% and otherwise, go to a label that assigns 1 to r1 and returns.
%
% - The second invocation of jump optimization converts this structure
% to code that assigns the value of the C expression "tag(r1) != 0" to r1,
% and returns. Crucially, it left behind two separate kinds of dead code.
% The first of the original semidet epilogs got left as code behind an
% unconditional jump, while the second was left as code after a label
% that had no more jumps targeting it.
%
% - Everything above was perfectly acceptable. What was not acceptable,
% and what caused the bug, was that the optimize_middle predicate in
% optimize.m, after invoking jump optimization for the second time,
% did not call the passes that clean up such dead code. This dead code
% then caused the compiler abort by triggering a sanity check during
% the use_local_vars pass.
%
%---------------------------------------------------------------------------%
:- module gh133.
:- interface.
:- import_module io.
:- pred main(io::di, io::uo) is det.
%---------------------%
:- type foo
---> some [T] exist_foo(T)
; bar01(int)
; bar02(int)
; bar03(int)
; bar04(int)
; bar05(int)
; bar06(int)
; bar07(int)
; bar08(int)
; bar09(int)
; bar10(int)
; bar11(int)
; bar12(int).
:- type bar =< foo
---> bar01(int)
; bar02(int)
; bar03(int)
; bar04(int)
; bar05(int)
; bar06(int)
; bar07(int)
; bar08(int)
; bar09(int)
; bar10(int)
; bar11(int)
; bar12(int).
:- inst bar
---> bar01(ground)
; bar02(ground)
; bar03(ground)
; bar04(ground)
; bar05(ground)
; bar06(ground)
; bar07(ground)
; bar08(ground)
; bar09(ground)
; bar10(ground)
; bar11(ground)
; bar12(ground).
:- pred is_bar(foo::(ground >> bar)) is semidet.
%---------------------------------------------------------------------------%
:- implementation.
:- import_module list.
:- import_module string.
main(!IO) :-
( if
X:foo = bar01(5),
is_bar(X),
Y:bar = coerce(X)
then
io.format("Test succeeded. Y = %s\n", [s(string(Y))], !IO)
else
io.write_string("Test failed.\n", !IO)
).
is_bar(T) :-
( T = bar01(_)
; T = bar02(_)
; T = bar03(_)
; T = bar04(_)
; T = bar05(_)
; T = bar06(_)
; T = bar07(_)
; T = bar08(_)
; T = bar09(_)
; T = bar10(_)
; T = bar11(_)
; T = bar12(_)
).