mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 17:33:38 +00:00
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:
@@ -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
|
||||
)
|
||||
;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -156,6 +156,7 @@ OTHER_PROGS = \
|
||||
constructor_arg_names \
|
||||
cse_unique \
|
||||
dcg_test \
|
||||
dead_get_io_state \
|
||||
deforest_bug \
|
||||
deforest_loop \
|
||||
deforest_rerun_det \
|
||||
|
||||
72
tests/valid/dead_get_io_state.m
Normal file
72
tests/valid/dead_get_io_state.m
Normal 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
|
||||
).
|
||||
Reference in New Issue
Block a user