mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 01:13:30 +00:00
The new --warn-unneeded-initial-statevar option asks the compiler
to warn about code such as
pred_a(!.X, ...) :-
... code that uses !.X, but does not update it ...
In this case, the preferred fix is to just replace all occurrences
of !.X with X.
The new --warn-unneeded-final-statevar option asks the compiler
to warn about code such as
pred_a(!X, ...) :-
... code that maybe uses !.X, but does not update it ...
In this case, the preferred fix also involves replacing all occurrences
of !.X with X, but it also involves either deleting the argument
containing !:X (the best option), or, if there is some reason why
the predicate's signature must stay unchanged, to replace !:X with X as well.
And if the clause body does not actually refer to either !.X or !:X, then
*both* arguments represented by !X should be deleted.
The first option is a style warning; the second option, due to the
signature change it may call for, is a non-style warning.
Both options have a version whose name adds a "-lambda" suffix, and which
does the same warnings for the heads of lambda expressions, not clauses.
Note that several of the modules below, including some that help to implement
the warnings, also contain code changes that result from *acting* on
the new warnings, e.g. by deleting unneeded statevar arguments.
Other, similar changes will also come after this diff is committed.
compiler/options.m:
doc/user_guide.texi:
Document the new options.
compiler/state_var.m:
Gather the information needed to decide what code merits the new warnings.
Do so in three stages:
- when processing the head of a clause or of a lambda expression,
- when processing the body goal of that clause or lambda expression,
- when finishing up the processing of the clause or lambda expression.
Add a predicate to generate the warnings for lambda expressions.
Do not generate the warnings for clauses. This is because whether or not
we want to warn about state vars in some clauses depends on the properties
of *other* clauses of the same predicate, and state_var.m has access
to only one clause at a time. Instead,
- return the info needed by the warning-generating code in pre_typecheck.m
(one of the first passes we execute after adding all clauses
to the HLDS), and
- we export some functionality for use by that code.
Switch to a convention for naming the program variables corresponding
to the middle (non-initial, non-final) versions of state variables
whose output is affected by changes in the code of the clause body goal
only if they involve that specific state variable.
Give some predicates more descriptive names.
compiler/make_hlds.m:
Make state_var.m and its new functionality visible from outside
the make_hlds package.
compiler/add_clause.m:
Record the information gathered by state_var.m in each clause.
compiler/hlds_clauses.m:
Add a slot to each clause for this information.
Give some predicates more descriptive names.
compiler/headvar_names.m:
Use the contents of the new slots to detect whether any clauses
have unused state vars, and if so, return the chosen consensus names
of the head vars to the code of pre_typecheck.m, which uses this info
as part of the implementation of the new warnings.
compiler/pre_typecheck.m:
Implement the new warnings.
compiler/mercury_compile_front_end.m:
Record the warnings that pre_typecheck.m can now return.
compiler/error_spec.m:
compiler/write_error_spec.m:
Add unsigned versions of the format pieces involving ints, for use
by the new code in pre_typecheck.m, and implement them.
compiler/hlds_out_util.m:
compiler/maybe_util.m:
Move two related types from hlds_out_util.m to maybe_util.m,
in order to allow pre_typecheck.m to use one of them.
compiler/hlds_args.m:
Add a new utility function for use by the new code above.
compiler/foreign.m:
Act on the new warnings by deleting the long-unused predicates
being warned about.
compiler/post_typecheck.m:
Speed up this traversal. (I originally thought to implement
the new warnings in this pass.)
compiler/add_foreign_proc.m:
compiler/add_pragma.m:
compiler/add_pragma_tabling.m:
compiler/add_pragma_type_spec.m:
compiler/add_pred.m:
compiler/add_type.m:
compiler/build_mode_constraints.m:
compiler/call_gen.m:
compiler/check_typeclass.m:
compiler/clause_to_proc.m:
compiler/code_loc_dep.m:
compiler/delay_info.m:
compiler/delay_partial_inst.m:
compiler/dense_switch.m:
compiler/det_check_goal.m:
compiler/det_infer_goal.m:
compiler/disj_gen.m:
compiler/du_type_layout.m:
compiler/format_call.m:
compiler/goal_expr_to_goal.m:
compiler/hlds_dependency_graph.m:
compiler/hlds_out_pred.m:
compiler/hlds_pred.m:
compiler/hlds_rtti.m:
compiler/inst_merge.m:
compiler/instance_method_clauses.m:
compiler/intermod.m:
compiler/interval.m:
compiler/ite_gen.m:
compiler/lookup_switch.m:
compiler/make_hlds_passes.m:
compiler/mark_tail_calls.m:
compiler/mercury_compile_llds_back_end.m:
compiler/mode_errors.m:
compiler/parse_string_format.m:
compiler/passes_aux.m:
compiler/polymorphism.m:
compiler/polymorphism_info.m:
compiler/polymorphism_type_info.m:
compiler/pragma_c_gen.m:
compiler/prop_mode_constraints.m:
compiler/purity.m:
compiler/quantification.m:
compiler/simplify_goal_call.m:
compiler/simplify_goal_conj.m:
compiler/string_switch.m:
compiler/superhomogeneous.m:
compiler/switch_gen.m:
compiler/tag_switch.m:
compiler/type_constraints.m:
compiler/typecheck.m:
compiler/typecheck_clauses.m:
compiler/typecheck_coerce.m:
compiler/typecheck_error_unify.m:
compiler/unify_gen_deconstruct.m:
compiler/unify_proc.m:
compiler/var_origins.m:
Conform to the changes above, and/or act on the new warnings.
browser/diff.m:
library/bit_buffer.m:
library/getopt.m:
library/getopt_io.m:
library/io.error_util.m:
library/io.file.m:
library/mercury_term_lexer.m:
library/parsing_utils.m:
library/pretty_printer.m:
library/robdd.m:
library/rtti_implementation.m:
library/string.builder.m:
library/string.parse_runtime.m:
mdbcomp/feedback.m:
Act on the new warnings.
tests/hard_coded/sv_nested_closures.m:
Change this test's code to avoid the new warnings, since
(if --halt-at-warn is ever enabled) the warnings would interfere
with its job .
tests/invalid/bug197.err_exp:
tests/invalid/bug487.err_exp:
tests/invalid/nullary_ho_func_error.err_exp:
tests/invalid/try_detism.err_exp:
tests/warnings/singleton_test_state_var.err_exp:
Expect variable names for the middle versions of state vars
using the new naming scheme.
854 lines
33 KiB
Mathematica
854 lines
33 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 1994-2012 The University of Melbourne.
|
|
% Copyright (C) 2013-2025 The Mercury team.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% File: call_gen.m.
|
|
% Authors: conway, zs.
|
|
%
|
|
% This module provides predicates for generating procedure calls,
|
|
% including calls to higher-order pred variables.
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module ll_backend.call_gen.
|
|
:- interface.
|
|
|
|
:- import_module hlds.
|
|
:- import_module hlds.code_model.
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module hlds.hlds_pred.
|
|
:- import_module libs.
|
|
:- import_module libs.globals.
|
|
:- import_module ll_backend.code_info.
|
|
:- import_module ll_backend.code_loc_dep.
|
|
:- import_module ll_backend.llds.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.prog_data.
|
|
|
|
:- import_module assoc_list.
|
|
:- import_module list.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred generate_call(code_model::in, pred_id::in, proc_id::in,
|
|
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 generate_generic_call(code_model::in, generic_call::in,
|
|
list(prog_var)::in, list(mer_mode)::in, arg_reg_type_info::in,
|
|
determinism::in, hlds_goal_info::in, llds_code::out,
|
|
code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.
|
|
|
|
:- type known_call_variant
|
|
---> ho_call_known_num
|
|
; ho_call_unknown.
|
|
|
|
% generic_call_info(Globals, GenericCall, NumImmediateInputArgsR,
|
|
% NumImmediateInputArgsF, CodeAddr, SpecifierArgInfos,
|
|
% FirstImmediateInputReg, HoCallVariant).
|
|
%
|
|
:- pred generic_call_info(globals::in, generic_call::in, int::in, int::in,
|
|
code_addr::out, assoc_list(prog_var, arg_info)::out, int::out,
|
|
known_call_variant::out) is det.
|
|
|
|
:- pred generate_builtin(code_model::in, pred_id::in, proc_id::in,
|
|
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,
|
|
assoc_list(prog_var, arg_loc)::out) is det.
|
|
|
|
:- pred output_arg_locs(assoc_list(prog_var, arg_info)::in,
|
|
assoc_list(prog_var, arg_loc)::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module backend_libs.
|
|
:- import_module backend_libs.builtin_ops.
|
|
:- import_module hlds.arg_info.
|
|
:- import_module hlds.hlds_class.
|
|
:- import_module hlds.hlds_llds.
|
|
:- import_module hlds.hlds_markers.
|
|
:- import_module hlds.hlds_module.
|
|
:- import_module hlds.instmap.
|
|
:- import_module libs.options.
|
|
:- import_module ll_backend.code_util.
|
|
:- import_module ll_backend.continuation_info.
|
|
:- import_module ll_backend.trace_gen.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.goal_path.
|
|
:- import_module parse_tree.prog_data_event.
|
|
:- import_module parse_tree.prog_event.
|
|
:- import_module parse_tree.prog_type.
|
|
:- import_module parse_tree.set_of_var.
|
|
:- import_module parse_tree.var_table.
|
|
|
|
:- import_module bool.
|
|
:- import_module cord.
|
|
:- import_module int.
|
|
:- import_module maybe.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module set.
|
|
:- import_module string.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
generate_call(CodeModel, PredId, ProcId, ArgVars, GoalInfo, Code, !CI, !CLD) :-
|
|
% Find out which arguments are input and which are output.
|
|
ArgInfo = get_pred_proc_arginfo(!.CI, PredId, ProcId),
|
|
assoc_list.from_corresponding_lists(ArgVars, ArgInfo, ArgsInfos),
|
|
|
|
% Save the necessary vars on the stack and move the input args
|
|
% to their registers.
|
|
setup_call(GoalInfo, ArgsInfos, LiveVals, SetupCode, !.CI, !CLD),
|
|
kill_dead_input_vars(ArgsInfos, GoalInfo, NonLiveOutputs, !CLD),
|
|
|
|
% Figure out what the call model is.
|
|
prepare_for_call(CodeModel, GoalInfo, CallModel, TraceResetCode,
|
|
!CI, !CLD),
|
|
|
|
% Make the call. Note that the construction of CallCode will be moved
|
|
% *after* the code that computes ReturnLiveLvalues.
|
|
get_module_info(!.CI, ModuleInfo),
|
|
Address = make_proc_entry_label(!.CI, ModuleInfo, PredId, ProcId,
|
|
for_immediate_call),
|
|
get_next_label(ReturnLabel, !CI),
|
|
call_gen.call_comment(!.CI, PredId, CodeModel, CallComment),
|
|
Context = goal_info_get_context(GoalInfo),
|
|
GoalId = goal_info_get_goal_id(GoalInfo),
|
|
get_maybe_containing_goal_map(!.CI, MaybeContainingGoalMap),
|
|
(
|
|
MaybeContainingGoalMap = yes(ContainingGoalMap),
|
|
GoalPath = goal_id_to_forward_path(ContainingGoalMap, GoalId),
|
|
MaybeGoalPath = yes(GoalPath)
|
|
;
|
|
MaybeContainingGoalMap = no,
|
|
MaybeGoalPath = no
|
|
),
|
|
CallCode = from_list([
|
|
llds_instr(livevals(LiveVals), ""),
|
|
llds_instr(llcall(Address, code_label(ReturnLabel), ReturnLiveLvalues,
|
|
Context, MaybeGoalPath, CallModel), CallComment),
|
|
llds_instr(label(ReturnLabel), "continuation label")
|
|
]),
|
|
|
|
% Figure out what variables will be live at the return point, and where,
|
|
% for use in the accurate garbage collector, and in the debugger.
|
|
get_instmap(!.CLD, InstMap),
|
|
InstMapDelta = goal_info_get_instmap_delta(GoalInfo),
|
|
apply_instmap_delta(InstMapDelta, InstMap, ReturnInstMap),
|
|
|
|
% Update the code generator state to reflect the situation after the call.
|
|
handle_return(ArgsInfos, GoalInfo, NonLiveOutputs,
|
|
ReturnInstMap, ReturnLiveLvalues, !.CI, !CLD),
|
|
|
|
% If the call can fail, generate code to check for and handle the failure.
|
|
remember_position(!.CLD, AfterReturn),
|
|
handle_call_failure(CodeModel, GoalInfo, FailHandlingCode, !CI, !.CLD),
|
|
reset_to_position(AfterReturn, !.CI, !:CLD),
|
|
|
|
( if
|
|
goal_info_has_feature(GoalInfo, feature_debug_self_tail_rec_call),
|
|
get_maybe_trace_info(!.CI, MaybeTraceInfo),
|
|
MaybeTraceInfo = yes(TraceInfo)
|
|
then
|
|
generate_tailrec_event_code(TraceInfo, ArgsInfos, GoalId, Context,
|
|
TraceTailRecResetAndEventCode, TailRecLabel, !CI, !CLD),
|
|
JumpCode = from_list([
|
|
llds_instr(livevals(LiveVals), ""),
|
|
llds_instr(goto(code_label(TailRecLabel)),
|
|
"tail recursive jump")
|
|
]),
|
|
Code = SetupCode ++ TraceTailRecResetAndEventCode ++ JumpCode
|
|
else
|
|
Code = SetupCode ++ TraceResetCode ++ CallCode ++ FailHandlingCode
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
generate_generic_call(OuterCodeModel, GenericCall, ArgVars, Modes,
|
|
MaybeRegTypes, Det, GoalInfo, Code, !CI, !CLD) :-
|
|
% For a generic_call, we split the arguments into inputs and outputs,
|
|
% put the inputs in the locations expected by mercury.do_call_closure in
|
|
% runtime/mercury_ho_call.c, generate the call to that code, and pick up
|
|
% the outputs from the locations that we know the runtime system leaves
|
|
% them in.
|
|
|
|
% `cast' differs from the other generic call types in that there is no
|
|
% address. Also, live_vars.m assumes that casts do not require live
|
|
% variables to be saved to the stack.
|
|
(
|
|
( GenericCall = higher_order(_, _, _, _, _)
|
|
; GenericCall = class_method(_, _, _, _)
|
|
),
|
|
generate_main_generic_call(OuterCodeModel, GenericCall, ArgVars, Modes,
|
|
MaybeRegTypes, Det, GoalInfo, Code, !CI, !CLD)
|
|
;
|
|
GenericCall = event_call(EventName),
|
|
generate_event_call(EventName, ArgVars, GoalInfo, Code, !CI, !CLD)
|
|
;
|
|
GenericCall = cast(_),
|
|
( if ArgVars = [InputArgVar, OutputArgVar] then
|
|
get_var_table(!.CI, VarTable),
|
|
lookup_var_entry(VarTable, InputArgVar, InputArgEntry),
|
|
InputArgVarIsDummy = InputArgEntry ^ vte_is_dummy,
|
|
(
|
|
InputArgVarIsDummy = is_dummy_type,
|
|
% Dummy types don't actually have values, which is
|
|
% normally harmless. However, using the constant zero means
|
|
% that we don't need to allocate space for an existentially
|
|
% typed version of a dummy type. Using the constant zero
|
|
% also avoids keeping pointers to memory that could be freed.
|
|
Assigned = assign_const(int_const(0))
|
|
;
|
|
InputArgVarIsDummy = is_not_dummy_type,
|
|
Assigned = assign_copy(InputArgVar)
|
|
),
|
|
generate_assign_builtin(OutputArgVar, Assigned, Code, !CLD)
|
|
else
|
|
unexpected($pred, "invalid type/inst cast call")
|
|
)
|
|
).
|
|
|
|
:- pred generate_main_generic_call(code_model::in, generic_call::in,
|
|
list(prog_var)::in, list(mer_mode)::in, arg_reg_type_info::in,
|
|
determinism::in, hlds_goal_info::in, llds_code::out,
|
|
code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.
|
|
|
|
generate_main_generic_call(_OuterCodeModel, GenericCall, ArgVars, Modes,
|
|
MaybeRegTypes, Det, GoalInfo, Code, !CI, !CLD) :-
|
|
get_module_info(!.CI, ModuleInfo),
|
|
arg_info.generic_call_arg_reg_types(ModuleInfo, GenericCall,
|
|
ArgVars, MaybeRegTypes, ArgRegTypes),
|
|
get_var_table(!.CI, VarTable),
|
|
arg_info.compute_in_and_out_vars_sep_regs(ModuleInfo, VarTable,
|
|
ArgVars, Modes, ArgRegTypes, InVarsR, InVarsF, OutVarsR, OutVarsF),
|
|
module_info_get_globals(ModuleInfo, Globals),
|
|
generic_call_info(Globals, GenericCall, length(InVarsR), length(InVarsF),
|
|
CodeAddr, SpecifierArgInfos, FirstImmInputR, HoCallVariant),
|
|
FirstImmInputF = 1,
|
|
determinism_to_code_model(Det, CodeModel),
|
|
(
|
|
CodeModel = model_semi,
|
|
FirstOutputR = 2
|
|
;
|
|
( CodeModel = model_det
|
|
; CodeModel = model_non
|
|
),
|
|
FirstOutputR = 1
|
|
),
|
|
FirstOutputF = 1,
|
|
|
|
give_vars_consecutive_arg_infos(InVarsR, reg_r, FirstImmInputR, top_in,
|
|
InVarArgInfosR),
|
|
give_vars_consecutive_arg_infos(InVarsF, reg_f, FirstImmInputF, top_in,
|
|
InVarArgInfosF),
|
|
give_vars_consecutive_arg_infos(OutVarsR, reg_r, FirstOutputR, top_out,
|
|
OutArgsInfosR),
|
|
give_vars_consecutive_arg_infos(OutVarsF, reg_f, FirstOutputF, top_out,
|
|
OutArgsInfosF),
|
|
ArgInfos = list.condense([SpecifierArgInfos,
|
|
InVarArgInfosR, InVarArgInfosF, OutArgsInfosR, OutArgsInfosF]),
|
|
|
|
% Save the necessary vars on the stack and move the input args defined
|
|
% by variables to their registers.
|
|
setup_call(GoalInfo, ArgInfos, LiveVals0, SetupCode, !.CI, !CLD),
|
|
kill_dead_input_vars(ArgInfos, GoalInfo, NonLiveOutputs, !CLD),
|
|
|
|
% Move the input args not defined by variables to their registers.
|
|
% Setting up these arguments last results in slightly more efficient code,
|
|
% since we can use their registers when placing the variables.
|
|
generic_call_nonvar_setup(GenericCall, HoCallVariant, InVarsR, InVarsF,
|
|
NonVarCode, !CLD),
|
|
|
|
extra_livevals(FirstImmInputR, ExtraLiveVals),
|
|
set.insert_list(ExtraLiveVals, LiveVals0, LiveVals),
|
|
|
|
prepare_for_call(CodeModel, GoalInfo, CallModel, TraceCode, !CI, !CLD),
|
|
|
|
% Make the call.
|
|
get_next_label(ReturnLabel, !CI),
|
|
Context = goal_info_get_context(GoalInfo),
|
|
GoalId = goal_info_get_goal_id(GoalInfo),
|
|
|
|
% Figure out what variables will be live at the return point, and where,
|
|
% for use in the accurate garbage collector, and in the debugger.
|
|
get_instmap(!.CLD, InstMap),
|
|
InstMapDelta = goal_info_get_instmap_delta(GoalInfo),
|
|
apply_instmap_delta(InstMapDelta, InstMap, ReturnInstMap),
|
|
|
|
% Update the code generator state to reflect the situation after the call.
|
|
OutArgsInfos = OutArgsInfosR ++ OutArgsInfosF,
|
|
handle_return(OutArgsInfos, GoalInfo, NonLiveOutputs,
|
|
ReturnInstMap, ReturnLiveLvalues, !.CI, !CLD),
|
|
|
|
get_maybe_containing_goal_map(!.CI, MaybeContainingGoalMap),
|
|
(
|
|
MaybeContainingGoalMap = yes(ContainingGoalMap),
|
|
GoalPath = goal_id_to_forward_path(ContainingGoalMap, GoalId),
|
|
MaybeGoalPath = yes(GoalPath)
|
|
;
|
|
MaybeContainingGoalMap = no,
|
|
MaybeGoalPath = no
|
|
),
|
|
CallCode = from_list([
|
|
llds_instr(livevals(LiveVals), ""),
|
|
llds_instr(llcall(CodeAddr, code_label(ReturnLabel), ReturnLiveLvalues,
|
|
Context, MaybeGoalPath, CallModel), "Setup and call"),
|
|
llds_instr(label(ReturnLabel), "Continuation label")
|
|
]),
|
|
|
|
% If the call can fail, generate code to check for and handle the failure.
|
|
remember_position(!.CLD, AfterReturn),
|
|
handle_call_failure(CodeModel, GoalInfo, FailHandlingCode, !CI, !.CLD),
|
|
reset_to_position(AfterReturn, !.CI, !:CLD),
|
|
|
|
Code = SetupCode ++ NonVarCode ++ TraceCode ++ CallCode ++
|
|
FailHandlingCode.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred generate_event_call(string::in, 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.
|
|
|
|
generate_event_call(EventName, Args, GoalInfo, Code, !CI, !CLD) :-
|
|
get_module_info(!.CI, ModuleInfo),
|
|
module_info_get_event_set(ModuleInfo, EventSet),
|
|
EventSpecMap = EventSet ^ event_set_spec_map,
|
|
( if
|
|
event_attributes(EventSpecMap, EventName, Attributes),
|
|
event_number(EventSpecMap, EventName, EventNumber)
|
|
then
|
|
generate_event_attributes(Attributes, Args, MaybeUserAttributes,
|
|
AttrCodes, !.CI, !CLD),
|
|
UserEventInfo = user_event_info(EventNumber, MaybeUserAttributes),
|
|
generate_user_event_code(UserEventInfo, GoalInfo, EventCode,
|
|
!CI, !CLD),
|
|
Code = cord_list_to_cord(AttrCodes) ++ EventCode
|
|
else
|
|
unexpected($pred, "bad event name")
|
|
).
|
|
|
|
:- pred generate_event_attributes(list(event_attribute)::in,
|
|
list(prog_var)::in, list(maybe(user_attribute))::out, list(llds_code)::out,
|
|
code_info::in, code_loc_dep::in, code_loc_dep::out) is det.
|
|
|
|
generate_event_attributes([], !.Vars, [], [], _CI, !CLD) :-
|
|
(
|
|
!.Vars = [_ | _],
|
|
unexpected($pred, "var")
|
|
;
|
|
!.Vars = []
|
|
).
|
|
generate_event_attributes([Attribute | Attributes], !.Vars,
|
|
[MaybeUserAttr | MaybeUserAttrs], [Code | Codes], CI, !CLD) :-
|
|
SynthCall = Attribute ^ attr_maybe_synth_call,
|
|
(
|
|
SynthCall = no,
|
|
(
|
|
!.Vars = [Var | !:Vars],
|
|
produce_variable(Var, Code, Rval, !CLD),
|
|
UserAttr = user_attribute(Rval, Var),
|
|
MaybeUserAttr = yes(UserAttr)
|
|
;
|
|
!.Vars = [],
|
|
unexpected($pred, "no var")
|
|
)
|
|
;
|
|
SynthCall = yes(_),
|
|
MaybeUserAttr = no,
|
|
Code = empty
|
|
),
|
|
generate_event_attributes(Attributes, !.Vars, MaybeUserAttrs, Codes,
|
|
CI, !CLD).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% The registers before the first reg_r input argument are all live.
|
|
%
|
|
:- pred extra_livevals(int::in, list(lval)::out) is det.
|
|
|
|
extra_livevals(FirstInput, ExtraLiveVals) :-
|
|
extra_livevals_from(1, FirstInput, ExtraLiveVals).
|
|
|
|
:- pred extra_livevals_from(int::in, int::in, list(lval)::out) is det.
|
|
|
|
extra_livevals_from(Reg, FirstInput, ExtraLiveVals) :-
|
|
( if Reg < FirstInput then
|
|
ExtraLiveVals = [reg(reg_r, Reg) | ExtraLiveVals1],
|
|
NextReg = Reg + 1,
|
|
extra_livevals_from(NextReg, FirstInput, ExtraLiveVals1)
|
|
else
|
|
ExtraLiveVals = []
|
|
).
|
|
|
|
generic_call_info(Globals, GenericCall, NumInputArgsR, NumInputArgsF,
|
|
CodeAddr, SpecifierArgInfos, FirstImmediateInputReg, HoCallVariant) :-
|
|
(
|
|
GenericCall = higher_order(PredVar, _, _, _, _),
|
|
Reg = reg(reg_r, 1),
|
|
SpecifierArgInfos = [PredVar - arg_info(Reg, top_in)],
|
|
globals.lookup_int_option(Globals,
|
|
max_specialized_do_call_closure, MaxSpec),
|
|
( if
|
|
MaxSpec >= 0,
|
|
NumInputArgsR =< MaxSpec,
|
|
NumInputArgsF = 0
|
|
then
|
|
CodeAddr = do_call_closure(specialized_known(NumInputArgsR)),
|
|
HoCallVariant = ho_call_known_num,
|
|
FirstImmediateInputReg = 2
|
|
else
|
|
CodeAddr = do_call_closure(generic),
|
|
HoCallVariant = ho_call_unknown,
|
|
FirstImmediateInputReg = 3
|
|
)
|
|
;
|
|
GenericCall = class_method(TCVar, _, _, _),
|
|
Reg = reg(reg_r, 1),
|
|
SpecifierArgInfos = [TCVar - arg_info(Reg, top_in)],
|
|
% XXX we do not use float registers for method calls yet
|
|
( if NumInputArgsF = 0 then
|
|
globals.lookup_int_option(Globals,
|
|
max_specialized_do_call_class_method, MaxSpec),
|
|
( if
|
|
MaxSpec >= 0,
|
|
NumInputArgsR =< MaxSpec
|
|
then
|
|
CodeAddr = do_call_class_method(
|
|
specialized_known(NumInputArgsR)),
|
|
HoCallVariant = ho_call_known_num,
|
|
FirstImmediateInputReg = 3
|
|
else
|
|
CodeAddr = do_call_class_method(generic),
|
|
HoCallVariant = ho_call_unknown,
|
|
FirstImmediateInputReg = 4
|
|
)
|
|
else
|
|
sorry($pred, "float reg inputs")
|
|
)
|
|
;
|
|
% Events and casts are generated inline.
|
|
( GenericCall = event_call(_)
|
|
; GenericCall = cast(_)
|
|
),
|
|
CodeAddr = do_not_reached,
|
|
SpecifierArgInfos = [],
|
|
FirstImmediateInputReg = 1,
|
|
HoCallVariant = ho_call_unknown % dummy; not used
|
|
).
|
|
|
|
% Some of the values that generic call passes to the dispatch routine
|
|
% to specify what code is being indirectly called come from HLDS
|
|
% variables, while the others come from constants. The ones that come
|
|
% from variables (the closure for a higher order call, the
|
|
% typeclass_info for a method call) are set up together with the
|
|
% arguments being passed the indirectly called code, since with eager
|
|
% code generation this ensures that each target register is reserved
|
|
% for the variable destined for it. This is set up by generic_call_info.
|
|
% generic_call_nonvar_setup generates code to pass to the dispatch routine
|
|
% the parts of the indirectly called code's identifier that come from
|
|
% constants.
|
|
%
|
|
:- pred generic_call_nonvar_setup(generic_call::in, known_call_variant::in,
|
|
list(prog_var)::in, list(prog_var)::in,
|
|
llds_code::out, code_loc_dep::in, code_loc_dep::out) is det.
|
|
|
|
generic_call_nonvar_setup(higher_order(_, _, _, _, _), HoCallVariant,
|
|
InVarsR, InVarsF, Code, !CLD) :-
|
|
(
|
|
HoCallVariant = ho_call_known_num,
|
|
Code = empty
|
|
;
|
|
HoCallVariant = ho_call_unknown,
|
|
clobber_reg(reg(reg_r, 2), !CLD),
|
|
list.length(InVarsR, NumInVarsR),
|
|
list.length(InVarsF, NumInVarsF),
|
|
NumInVars = encode_num_generic_call_vars(NumInVarsR, NumInVarsF),
|
|
Code = singleton(
|
|
llds_instr(assign(reg(reg_r, 2), const(llconst_int(NumInVars))),
|
|
"Assign number of immediate input arguments")
|
|
)
|
|
).
|
|
generic_call_nonvar_setup(class_method(_, Method, _, _), HoCallVariant,
|
|
InVarsR, InVarsF, Code, !CLD) :-
|
|
(
|
|
InVarsF = []
|
|
;
|
|
InVarsF = [_ | _],
|
|
sorry($pred, "float input reg")
|
|
),
|
|
Method = method_proc_num(MethodNum),
|
|
MethodNumConst = const(llconst_int(MethodNum)),
|
|
(
|
|
HoCallVariant = ho_call_known_num,
|
|
clobber_reg(reg(reg_r, 2), !CLD),
|
|
Code = singleton(
|
|
llds_instr(assign(reg(reg_r, 2), MethodNumConst),
|
|
"Index of class method in typeclass info")
|
|
)
|
|
;
|
|
HoCallVariant = ho_call_unknown,
|
|
clobber_regs([reg(reg_r, 2), reg(reg_r, 3)], !CLD),
|
|
list.length(InVarsR, NumInVarsR),
|
|
% Currently we do not use float registers for method calls.
|
|
NumInVarsF = 0,
|
|
NumInVars = encode_num_generic_call_vars(NumInVarsR, NumInVarsF),
|
|
Code = from_list([
|
|
llds_instr(assign(reg(reg_r, 2), MethodNumConst),
|
|
"Index of class method in typeclass info"),
|
|
llds_instr(assign(reg(reg_r, 3), const(llconst_int(NumInVars))),
|
|
"Assign number of immediate regular input arguments")
|
|
])
|
|
).
|
|
generic_call_nonvar_setup(event_call(_), _, _, _, _, !CLD) :-
|
|
unexpected($pred, "event_call").
|
|
generic_call_nonvar_setup(cast(_), _, _, _, _, !CLD) :-
|
|
unexpected($pred, "cast").
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred prepare_for_call(code_model::in, hlds_goal_info::in, call_model::out,
|
|
llds_code::out,
|
|
code_info::in, code_info::out, code_loc_dep::in, code_loc_dep::out) is det.
|
|
|
|
prepare_for_call(CodeModel, GoalInfo, CallModel, TraceCode, !CI, !CLD) :-
|
|
succip_is_used(!CI),
|
|
( if goal_info_has_feature(GoalInfo, feature_do_not_tailcall) then
|
|
AllowLCO = do_not_allow_lco
|
|
else
|
|
AllowLCO = allow_lco
|
|
),
|
|
(
|
|
CodeModel = model_det,
|
|
CallModel = call_model_det(AllowLCO)
|
|
;
|
|
CodeModel = model_semi,
|
|
CallModel = call_model_semidet(AllowLCO)
|
|
;
|
|
CodeModel = model_non,
|
|
may_use_nondet_tailcall(!.CLD, TailCallStatus),
|
|
CallModel = call_model_nondet(TailCallStatus),
|
|
set_resume_point_and_frame_to_unknown(!CLD)
|
|
),
|
|
trace_prepare_for_call(!.CI, TraceCode).
|
|
|
|
:- pred handle_call_failure(code_model::in, hlds_goal_info::in, llds_code::out,
|
|
code_info::in, code_info::out, code_loc_dep::in) is det.
|
|
|
|
handle_call_failure(CodeModel, GoalInfo, FailHandlingCode, !CI, CLD) :-
|
|
(
|
|
CodeModel = model_semi,
|
|
Detism = goal_info_get_determinism(GoalInfo),
|
|
( if Detism = detism_failure then
|
|
generate_failure(FailHandlingCode, !.CI, CLD)
|
|
else
|
|
get_next_label(ContLab, !CI),
|
|
FailTestCode = singleton(
|
|
llds_instr(if_val(lval(reg(reg_r, 1)), code_label(ContLab)),
|
|
"test for success")
|
|
),
|
|
generate_failure(FailCode, !.CI, CLD),
|
|
ContLabelCode = singleton(
|
|
llds_instr(label(ContLab), "")
|
|
),
|
|
FailHandlingCode = FailTestCode ++ FailCode ++ ContLabelCode
|
|
)
|
|
;
|
|
( CodeModel = model_det
|
|
; CodeModel = model_non
|
|
),
|
|
FailHandlingCode = empty
|
|
).
|
|
|
|
:- pred call_comment(code_info::in, pred_id::in, code_model::in, string::out)
|
|
is det.
|
|
|
|
call_comment(CI, PredId, CodeModel, Msg) :-
|
|
(
|
|
CodeModel = model_det,
|
|
BaseMsg = "branch to det procedure"
|
|
;
|
|
CodeModel = model_semi,
|
|
BaseMsg = "branch to semidet procedure"
|
|
;
|
|
CodeModel = model_non,
|
|
BaseMsg = "branch to nondet procedure"
|
|
),
|
|
get_auto_comments(CI, AutoComments),
|
|
(
|
|
AutoComments = yes,
|
|
get_module_info(CI, ModuleInfo),
|
|
module_info_pred_info(ModuleInfo, PredId, PredInfo),
|
|
PredName = pred_info_name(PredInfo),
|
|
Msg = BaseMsg ++ " " ++ PredName
|
|
;
|
|
AutoComments = no,
|
|
Msg = BaseMsg
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% After we have placed all the input variables in their registers,
|
|
% we will want to clear all the registers so we can start updating
|
|
% the code generator state to reflect their contents after the call.
|
|
% (In the case of higher order calls, we may place some constant
|
|
% input arguments in registers before clearing them.) The register
|
|
% clearing code complains if it is asked to dispose of the last copy
|
|
% of a still live variable, so before we clear the registers, we must
|
|
% make forward-dead all the variables that are in this goal's
|
|
% post-death set. However, a variable may be in this set even if it
|
|
% is not live before the call, if it is bound by the call. (This can
|
|
% happen when the caller ignores some of the output arguments of the
|
|
% called procedure.) We handle such variables not by making them
|
|
% forward-dead but by simply never making them forward-live in the
|
|
% first place.
|
|
%
|
|
% ArgsInfos should list all the output arguments of the call.
|
|
% It may contain the input arguments as well; kill_dead_input_vars
|
|
% and handle_return ignore them.
|
|
%
|
|
:- pred kill_dead_input_vars(assoc_list(prog_var, arg_info)::in,
|
|
hlds_goal_info::in, set_of_progvar::out,
|
|
code_loc_dep::in, code_loc_dep::out) is det.
|
|
|
|
kill_dead_input_vars(ArgsInfos, GoalInfo, NonLiveOutputs, !CLD) :-
|
|
get_forward_live_vars(!.CLD, Liveness),
|
|
find_nonlive_outputs(ArgsInfos, Liveness, set_of_var.init, NonLiveOutputs),
|
|
goal_info_get_post_deaths(GoalInfo, PostDeaths),
|
|
set_of_var.difference(PostDeaths, NonLiveOutputs, ImmediatePostDeaths),
|
|
make_vars_forward_dead(ImmediatePostDeaths, !CLD).
|
|
|
|
:- pred handle_return(assoc_list(prog_var, arg_info)::in,
|
|
hlds_goal_info::in, set_of_progvar::in, instmap::in, list(liveinfo)::out,
|
|
code_info::in, code_loc_dep::in, code_loc_dep::out) is det.
|
|
|
|
handle_return(ArgsInfos, GoalInfo, _NonLiveOutputs, ReturnInstMap,
|
|
ReturnLiveLvalues, CI, !CLD) :-
|
|
InstMapDelta = goal_info_get_instmap_delta(GoalInfo),
|
|
( if instmap_delta_is_reachable(InstMapDelta) then
|
|
OkToDeleteAny = no
|
|
else
|
|
OkToDeleteAny = yes
|
|
),
|
|
clear_all_registers(OkToDeleteAny, !CLD),
|
|
get_forward_live_vars(!.CLD, Liveness),
|
|
rebuild_registers(ArgsInfos, Liveness, OutputArgLocs, !CLD),
|
|
generate_return_live_lvalues(CI, !.CLD, OutputArgLocs,
|
|
ReturnInstMap, OkToDeleteAny, ReturnLiveLvalues).
|
|
|
|
:- pred find_nonlive_outputs(assoc_list(prog_var, arg_info)::in,
|
|
set_of_progvar::in, set_of_progvar::in, set_of_progvar::out) is det.
|
|
|
|
find_nonlive_outputs([], _, !NonLiveOutputs).
|
|
find_nonlive_outputs([Var - arg_info(_ArgLoc, Mode) | Args],
|
|
Liveness, !NonLiveOutputs) :-
|
|
(
|
|
Mode = top_out,
|
|
( if set_of_var.member(Liveness, Var) then
|
|
true
|
|
else
|
|
set_of_var.insert(Var, !NonLiveOutputs)
|
|
)
|
|
;
|
|
( Mode = top_in
|
|
; Mode = top_unused
|
|
)
|
|
),
|
|
find_nonlive_outputs(Args, Liveness, !NonLiveOutputs).
|
|
|
|
:- pred rebuild_registers(assoc_list(prog_var, arg_info)::in,
|
|
set_of_progvar::in, assoc_list(prog_var, arg_loc)::out,
|
|
code_loc_dep::in, code_loc_dep::out) is det.
|
|
|
|
rebuild_registers([], _, [], !CLD).
|
|
rebuild_registers([Var - arg_info(ArgLoc, Mode) | Args], Liveness,
|
|
OutputArgLocs, !CLD) :-
|
|
rebuild_registers(Args, Liveness, OutputArgLocs1, !CLD),
|
|
( if
|
|
Mode = top_out,
|
|
set_of_var.member(Liveness, Var)
|
|
then
|
|
code_util.arg_loc_to_register(ArgLoc, Register),
|
|
set_var_location(Var, Register, !CLD),
|
|
OutputArgLocs = [Var - ArgLoc | OutputArgLocs1]
|
|
else
|
|
OutputArgLocs = OutputArgLocs1
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
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),
|
|
builtin_ops.translate_builtin(ModuleName, PredName, ProcId, Args,
|
|
SimpleCode),
|
|
(
|
|
CodeModel = model_det,
|
|
(
|
|
SimpleCode = assign(Var, AssignExpr),
|
|
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),
|
|
produce_variable(ValueVar, ValueVarCode, ValueRval, !CLD),
|
|
StoreInstr = llds_instr(assign(mem_ref(AddrRval), ValueRval), ""),
|
|
StoreCode = singleton(StoreInstr),
|
|
Code = AddrVarCode ++ ValueVarCode ++ StoreCode
|
|
;
|
|
SimpleCode = test(_),
|
|
unexpected($pred, "malformed model_det builtin predicate")
|
|
;
|
|
SimpleCode = noop(DefinedVars),
|
|
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
|
|
)
|
|
;
|
|
CodeModel = model_semi,
|
|
(
|
|
SimpleCode = test(TestExpr),
|
|
generate_simple_test(TestExpr, Rval, ArgCode, !CLD),
|
|
fail_if_rval_is_false(Rval, TestCode, !CI, !CLD),
|
|
Code = ArgCode ++ TestCode
|
|
;
|
|
SimpleCode = assign(_, _),
|
|
unexpected($pred, "malformed model_semi builtin predicate")
|
|
;
|
|
SimpleCode = ref_assign(_, _),
|
|
unexpected($pred, "malformed model_semi builtin predicate")
|
|
;
|
|
SimpleCode = noop(_),
|
|
unexpected($pred, "malformed model_semi builtin predicate")
|
|
)
|
|
;
|
|
CodeModel = model_non,
|
|
unexpected($pred, "model_non builtin predicate")
|
|
).
|
|
|
|
:- pred generate_assign_builtin(prog_var::in,
|
|
simple_assigned_expr(prog_var)::in, llds_code::out,
|
|
code_loc_dep::in, code_loc_dep::out) is det.
|
|
|
|
generate_assign_builtin(Var, AssignedExpr, Code, !CLD) :-
|
|
( if variable_is_forward_live(!.CLD, Var) then
|
|
Rval = convert_simple_assigned_expr(AssignedExpr),
|
|
assign_expr_to_var(Var, Rval, Code, !CLD)
|
|
else
|
|
Code = empty
|
|
).
|
|
|
|
:- func convert_simple_assigned_expr(simple_assigned_expr(prog_var)) = rval.
|
|
|
|
convert_simple_assigned_expr(AssignedExpr) = Rval :-
|
|
(
|
|
AssignedExpr = assign_copy(Var),
|
|
Rval = var(Var)
|
|
;
|
|
AssignedExpr = assign_const(Const),
|
|
Rval = convert_simple_const(Const)
|
|
;
|
|
AssignedExpr = assign_binary(BinOp, X, Y),
|
|
Rval = binop(BinOp, var(X), var(Y))
|
|
;
|
|
AssignedExpr = assign_binary_lc(BinOp, Const, Y),
|
|
Rval = binop(BinOp, convert_simple_const(Const), var(Y))
|
|
;
|
|
AssignedExpr = assign_unary(UnOp, X),
|
|
Rval = unop(UnOp, var(X))
|
|
).
|
|
|
|
% This function is needed because simple_const is defined in
|
|
% backend_libs.builtin_ops.m, which is not supposed to know about
|
|
% backend-specific types such as rval.
|
|
%
|
|
:- func convert_simple_const(simple_const) = rval.
|
|
|
|
convert_simple_const(int_const(Int)) = const(llconst_int(Int)).
|
|
convert_simple_const(int8_const(Int8)) = const(llconst_int8(Int8)).
|
|
convert_simple_const(int16_const(Int16)) = const(llconst_int16(Int16)).
|
|
convert_simple_const(int32_const(Int32)) = const(llconst_int32(Int32)).
|
|
convert_simple_const(int64_const(Int64)) = const(llconst_int64(Int64)).
|
|
convert_simple_const(uint_const(UInt)) = const(llconst_uint(UInt)).
|
|
convert_simple_const(uint8_const(UInt8)) = const(llconst_uint8(UInt8)).
|
|
convert_simple_const(uint16_const(UInt16)) = const(llconst_uint16(UInt16)).
|
|
convert_simple_const(uint32_const(UInt32)) = const(llconst_uint32(UInt32)).
|
|
convert_simple_const(uint64_const(UInt64)) = const(llconst_uint64(UInt64)).
|
|
convert_simple_const(float_const(Float)) = const(llconst_float(Float)).
|
|
|
|
:- pred generate_simple_test(simple_test_expr(prog_var)::in, rval::out,
|
|
llds_code::out, code_loc_dep::in, code_loc_dep::out) is det.
|
|
|
|
generate_simple_test(TestExpr, Rval, ArgCode, !CLD) :-
|
|
TestExpr = binary_test(BinOp, VarX, VarY),
|
|
produce_variable(VarX, CodeX, RvalX, !CLD),
|
|
produce_variable(VarY, CodeY, RvalY, !CLD),
|
|
Rval = binop(BinOp, RvalX, RvalY),
|
|
ArgCode = CodeX ++ CodeY.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
input_arg_locs([], []).
|
|
input_arg_locs([Var - arg_info(Loc, Mode) | Args], Vs) :-
|
|
input_arg_locs(Args, Vs0),
|
|
(
|
|
Mode = top_in,
|
|
Vs = [Var - Loc | Vs0]
|
|
;
|
|
( Mode = top_out
|
|
; Mode = top_unused
|
|
),
|
|
Vs = Vs0
|
|
).
|
|
|
|
output_arg_locs([], []).
|
|
output_arg_locs([Var - arg_info(Loc, Mode) | Args], Vs) :-
|
|
output_arg_locs(Args, Vs0),
|
|
(
|
|
Mode = top_out,
|
|
Vs = [Var - Loc | Vs0]
|
|
;
|
|
( Mode = top_in
|
|
; Mode = top_unused
|
|
),
|
|
Vs = Vs0
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred give_vars_consecutive_arg_infos(list(prog_var)::in,
|
|
reg_type::in, int::in, top_functor_mode::in,
|
|
assoc_list(prog_var, arg_info)::out) is det.
|
|
|
|
give_vars_consecutive_arg_infos([], _RegType, _N, _TopFunctorMode, []).
|
|
give_vars_consecutive_arg_infos([Var | Vars], RegType, N, TopFunctorMode,
|
|
[Var - ArgInfo | ArgInfos]) :-
|
|
ArgInfo = arg_info(reg(RegType, N), TopFunctorMode),
|
|
give_vars_consecutive_arg_infos(Vars, RegType, N + 1, TopFunctorMode,
|
|
ArgInfos).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
:- end_module ll_backend.call_gen.
|
|
%---------------------------------------------------------------------------%
|