diff --git a/compiler/call_gen.m b/compiler/call_gen.m index 992dedf35..53f39f7fd 100644 --- a/compiler/call_gen.m +++ b/compiler/call_gen.m @@ -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 ) ; diff --git a/compiler/code_gen.m b/compiler/code_gen.m index 985fae1a3..f7452a3b4 100644 --- a/compiler/code_gen.m +++ b/compiler/code_gen.m @@ -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, diff --git a/tests/valid/Mmakefile b/tests/valid/Mmakefile index 121c9fcec..1e794d7d8 100644 --- a/tests/valid/Mmakefile +++ b/tests/valid/Mmakefile @@ -156,6 +156,7 @@ OTHER_PROGS = \ constructor_arg_names \ cse_unique \ dcg_test \ + dead_get_io_state \ deforest_bug \ deforest_loop \ deforest_rerun_det \ diff --git a/tests/valid/dead_get_io_state.m b/tests/valid/dead_get_io_state.m new file mode 100644 index 000000000..551a5c4d5 --- /dev/null +++ b/tests/valid/dead_get_io_state.m @@ -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 + ).