Fix a bug in LLDS codegen's handling of builtins.

compiler/call_gen.m:
    When a builtin generates an output that the rest of the computation
    ignores, don't update the state of the output variable(s), because
    the presence of a state for a variable that shouldn't exist
    can cause a compiler abort later.

compiler/code_gen.m:
    Pass the needed info to call_gen.m.

tests/valid/dead_get_io_state.m:
    A new test case for this bug, whose module comment explains
    the chain of events leading to the compiler abort.

tests/valid/Mmakefile:
    Enable the new test case.
This commit is contained in:
Zoltan Somogyi
2023-10-06 10:25:57 +11:00
parent 625ec287f1
commit c8aabea968
4 changed files with 91 additions and 9 deletions

View File

@@ -56,7 +56,7 @@
known_call_variant::out) is det.
:- pred generate_builtin(code_model::in, pred_id::in, proc_id::in,
list(prog_var)::in, llds_code::out,
list(prog_var)::in, hlds_goal_info::in, llds_code::out,
code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.
:- pred input_arg_locs(assoc_list(prog_var, arg_info)::in,
@@ -687,7 +687,7 @@ rebuild_registers([Var - arg_info(ArgLoc, Mode) | Args], Liveness,
%---------------------------------------------------------------------------%
generate_builtin(CodeModel, PredId, ProcId, Args, Code, !CI, !CLD) :-
generate_builtin(CodeModel, PredId, ProcId, Args, GoalInfo, Code, !CI, !CLD) :-
get_module_info(!.CI, ModuleInfo),
ModuleName = predicate_module(ModuleInfo, PredId),
PredName = predicate_name(ModuleInfo, PredId),
@@ -697,7 +697,13 @@ generate_builtin(CodeModel, PredId, ProcId, Args, Code, !CI, !CLD) :-
CodeModel = model_det,
(
SimpleCode = assign(Var, AssignExpr),
generate_assign_builtin(Var, AssignExpr, Code, !CLD)
NonLocals = goal_info_get_nonlocals(GoalInfo),
( if set_of_var.contains(NonLocals, Var) then
generate_assign_builtin(Var, AssignExpr, Code, !CLD)
else
% The output of this builtin is unused.
Code = empty
)
;
SimpleCode = ref_assign(AddrVar, ValueVar),
produce_variable(AddrVar, AddrVarCode, AddrRval, !CLD),
@@ -710,7 +716,10 @@ generate_builtin(CodeModel, PredId, ProcId, Args, Code, !CI, !CLD) :-
unexpected($pred, "malformed model_det builtin predicate")
;
SimpleCode = noop(DefinedVars),
list.foldl(magically_put_var_in_unused_reg, DefinedVars, !CLD),
NonLocals = goal_info_get_nonlocals(GoalInfo),
list.filter(set_of_var.contains(NonLocals),
DefinedVars, UsedDefinedVars),
list.foldl(magically_put_var_in_unused_reg, UsedDefinedVars, !CLD),
Code = empty
)
;

View File

@@ -292,15 +292,15 @@ generate_goal_expr(GoalExpr, GoalInfo, CodeModel, ForwardLiveVarsBeforeGoal,
call_gen.generate_generic_call(CodeModel, GenericCall, Args,
Modes, MaybeRegTypes, Det, GoalInfo, Code, !CI, !CLD)
;
GoalExpr = plain_call(PredId, ProcId, Args, BuiltinState, _, _),
GoalExpr = plain_call(PredId, ProcId, ArgVars, BuiltinState, _, _),
(
BuiltinState = not_builtin,
call_gen.generate_call(CodeModel, PredId, ProcId, Args, GoalInfo,
Code, !CI, !CLD)
call_gen.generate_call(CodeModel, PredId, ProcId, ArgVars,
GoalInfo, Code, !CI, !CLD)
;
BuiltinState = inline_builtin,
call_gen.generate_builtin(CodeModel, PredId, ProcId, Args,
Code, !CI, !CLD)
call_gen.generate_builtin(CodeModel, PredId, ProcId, ArgVars,
GoalInfo, Code, !CI, !CLD)
)
;
GoalExpr = call_foreign_proc(Attributes, PredId, ProcId,

View File

@@ -156,6 +156,7 @@ OTHER_PROGS = \
constructor_arg_names \
cse_unique \
dcg_test \
dead_get_io_state \
deforest_bug \
deforest_loop \
deforest_rerun_det \

View File

@@ -0,0 +1,72 @@
%---------------------------------------------------------------------------%
% vim: ts=4 sw=4 et ft=mercury
%---------------------------------------------------------------------------%
%
% Before the fix committed on 2023 oct 6, this code resulted in
% this compiler abort in LLDS grades:
%
% Uncaught Mercury exception:
% Software Error: predicate
% `ll_backend.var_locn.clobber_lval_in_var_state_map'/6:
% Unexpected: empty state
%
% The chain of events that lead to this abort was the following.
%
% - The trace goal transformation in goal_expr_to_goal.m adds
%
% - a call to unsafe_get_io_state just before the call to string.format and
% - a call to unsafe_set_io_state just after the call to unexpected.
%
% Since the goal inside the trace goal's scope does not refer to !IO,
% the variable returned by unsafe_get_io_state as the initial I/O state
% is also the variable passed back to unsafe_set_io_state.
%
% At this point, this variable is a nonlocal variable in both of these calls.
%
% - Mode checking, noticing that the call to unexpected cannot succeed,
% deletes the call to unsafe_set_io_state. This makes the variable
% returned by unsafe_get_io_state *local* to that call.
%
% - Simplification declines to delete the call to unsafe_get_io_state,
% because goal_can_loop_or_throw_imaf returns can_loop_or_throw for this
% call, even though this builtin cannot do either of those things.
%
% - The LLDS code generator recognizes that unsafe_get_io_state is a builtin,
% and invokes generate_builtin to generate code for. generate_builtin
% applied the builtin's translation by magically binding the variable
% representing the I/O state WITHOUT CHECKING WHETHER THIS VARIABLE IS LIVE.
%
% - The call to the string.format (or rather, the calls that format_call.m
% expands the call to string.format into) require flushing live variables
% to the stack, since the call may clobber all the registers. The variable
% bound by unsafe_get_io_state had no stack slot (values of dummy types never
% do) and was not known to be a constant, so having the call clobbering
% all the registers would have destroyed its state. This is why the code
% generator raised the exception shown above.
%
% The fix was to make generate_builtin ignore bindings if they are made to
% variables that are ignored outside the builtin.
%
:- module dead_get_io_state.
:- interface.
:- pred test(int::in, int::in, int::out) is det.
:- implementation.
:- import_module int.
:- import_module list.
:- import_module require.
:- import_module string.
test(A, B, Z) :-
( if A < B then
trace [io(!IO)] (
string.format("A = %d, B = %d\n", [i(A), i(B)], Msg),
unexpected($pred, Msg)
),
Z = A
else
Z = B
).