mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-17 23:05:21 +00:00
compiler/simplify_goal_call.m:
If the --warn-suspicious-recursion option is set, and if the warning
isn't disabled, generate warnings for two different kinds of suspicious
recursion. They are both related to, but separate from, the warning
we have long generated for infinite recursion, which occurs when
the input args of a recursive call are the same as the corresponding
args in the clause head.
Both kinds suspicious recursion look at the input args of a recursive call
that are NOT the same as the corresponding args in the clause head.
Both require these args to have non-unique modes. (If they have unique
modes, then the depth of the recursion may be controlled by state outside
the view of the Mercury compiler, which means that a warning would be
likely to be misleading.)
The first kind is when all these args use state variable notation.
Most of the time, we use state var notation to denote the data structures
updated by the recursive code; having variables using such notation
*controlling* the recursion is much less common, and much more likely
to be unintended. The motivation for the new option was this infinitely
looping code, which resulted from neglecting to s/[X | Xs]/Xs/ after
cutting-and-pasting the clause head to the recursive call.
p([X | Xs], !S) :-
...,
p([X | Xs], !S).
The other kind of suspicious recursive call we warn about involve
input arguments where the base names of the input arguments (the part
before any numeric suffixes) seem be switched between the clause head
and the recursive call, as here:
q(As0, Bs0, ...) :-
...,
q(Bs1, As, ...).
compiler/mercury_compile_main.m:
Disable style warnings when invoked with --make-optimization-interface
or its transitive variant. Without this, warnings about suspicious
recursion would get reported in such invocations.
Move a test from a callee to a caller to allow the callee to be
less indented.
compiler/options.m:
Export functionality to mercury_compile_main.m to make the above possible.
library/string.m:
Add a predicate to convert a string to *reverse* char list directly.
Note a discrepancy between the documentation and the implementation
of the old predicate the new one is based on (which converts a string
to a forward char list).
NEWS:
Note the new predicate in string.m.
compiler/cse_detection.m:
compiler/ctgc.selector.m:
compiler/dead_proc_elim.m:
compiler/deforest.m:
compiler/dep_par_conj.m:
compiler/det_analysis.m:
compiler/distance_granularity.m:
compiler/frameopt.m:
compiler/inst_util.m:
compiler/lp_rational.m:
compiler/matching.m:
compiler/modes.m:
compiler/old_type_constraints.m:
compiler/rbmm.points_to_analysis.m:
compiler/rbmm.region_arguments.m:
compiler/recompilation.usage.m:
compiler/stratify.m:
compiler/structure_reuse.direct.choose_reuse.m:
compiler/structure_reuse.indirect.m:
compiler/typeclasses.m:
compiler/use_local_vars.m:
deep_profiler/callgraph.m:
deep_profiler/canonical.m:
library/bit_buffer.read.m:
library/bit_buffer.write.m:
library/calendar.m:
library/diet.m:
library/lexer.m:
library/parser.m:
library/parsing_utils.m:
library/ranges.m:
library/set_ctree234.m:
library/set_tree234.m:
library/string.parse_util.m:
library/tree234.m:
library/varset.m:
mdbcomp/program_representation.m:
mdbcomp/rtti_access.m:
profiler/demangle.m:
Avoid warnings for suspicious recursion. In most cases, do this by
wrapping disable_warning scopes around the affected recursive calls;
in a few cases, do this by changing the code.
tests/warnings/suspicious_recursion.{m,exp}:
A test case for the new warnings.
tests/warnings/Mercury.options:
tests/warnings/Mmakefile:
Enable the new test case.
909 lines
34 KiB
Mathematica
909 lines
34 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2001-2011 The University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% File: use_local_vars.m
|
|
% Author: zs.
|
|
%
|
|
% This module implements an LLDS->LLDS transformation that optimizes the
|
|
% sequence of instructions in a procedure body by replacing references to
|
|
% relatively expensive locations: fake registers (Mercury abstract machine
|
|
% registers that are not mapped to machine registers) or stack slots with
|
|
% references to cheaper locations: local variables in C blocks, which should
|
|
% be mapped to machine registers by the C compiler. The C blocks should be
|
|
% introduced later by wrap_blocks.m, possibly after the LLDS code has been
|
|
% transformed further. Wrap_blocks will know what local variables to declare
|
|
% in each block by looking for the temp(_, _) lvals that represent those local
|
|
% variables.
|
|
%
|
|
% This module looks for three patterns. The first is
|
|
%
|
|
% <instruction that defines a fake register>
|
|
% <instructions that use and possibly define the fake register>
|
|
% <end of basic block, at which the fake register is not live>
|
|
%
|
|
% When it finds an occurrence of that pattern, it replaces all references to
|
|
% the fake register with a local variable.
|
|
%
|
|
% If the basic block jumps to a code address which is not a label (e.g.
|
|
% do_redo, do_fail), we consider all registers to be live at the end of the
|
|
% basic block. This is because livemap.m, which computes liveness information
|
|
% for us, does not know about liveness requirements introduced by backtracking.
|
|
% This is a conservative approximation. The union of the livenesses of all the
|
|
% labels that represent resume points is a better approximation, but it would
|
|
% be tedious to compute and is unlikely to yield significantly better code.
|
|
%
|
|
% The second pattern we look for is simply an instruction that defines a fake
|
|
% register or stack slot, followed by some uses of that register or stack slot
|
|
% before code that redefines the register or stack slot. When we find this
|
|
% pattern, we again replace all references to the fake register or stack slot
|
|
% with a local variable, but since this time we cannot be sure that the
|
|
% original lval will not be referred to, we assign the local variable to the
|
|
% lval as well. This is a win because the cost of the assignment is less than
|
|
% the savings from replacing the fake register or stack slot references with
|
|
% local variable references.
|
|
%
|
|
% The third pattern we look for consists of a sequence of instructions in which
|
|
% a false register or stack slot is used several times, including at least once
|
|
% in the first instruction as a part of a path to a memory location, before
|
|
% being redefined or maybe aliased. This typically occurs when the code
|
|
% generator fills in the fields of a structure or extracts the fields of a
|
|
% structure. Again, we replace the false register or stack slot with a
|
|
% temporary after assigning the value in the false register or stack slot to
|
|
% the temporary.
|
|
%
|
|
% If we cannot find out what registers are live at each label, we still look
|
|
% for the second and third patterns.
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module ll_backend.use_local_vars.
|
|
:- interface.
|
|
|
|
:- import_module ll_backend.llds.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.prim_data.
|
|
|
|
:- import_module bool.
|
|
:- import_module counter.
|
|
:- import_module list.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred use_local_vars_proc(list(instruction)::in, list(instruction)::out,
|
|
int::in, int::in, bool::in, proc_label::in, counter::in, counter::out)
|
|
is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module hlds.
|
|
:- import_module hlds.hlds_llds.
|
|
:- import_module ll_backend.basic_block.
|
|
:- import_module ll_backend.code_util.
|
|
:- import_module ll_backend.exprn_aux.
|
|
:- import_module ll_backend.livemap.
|
|
:- import_module ll_backend.opt_debug.
|
|
:- import_module ll_backend.opt_util.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.prog_data_foreign.
|
|
|
|
:- import_module int.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module require.
|
|
:- import_module set.
|
|
:- import_module string.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
use_local_vars_proc(Instrs0, Instrs, NumRealRRegs, AccessThreshold,
|
|
AutoComments, ProcLabel, !C) :-
|
|
create_basic_blocks(Instrs0, Comments0, ProcLabel, !C, NewLabels,
|
|
LabelSeq, BlockMap0),
|
|
flatten_basic_blocks(LabelSeq, BlockMap0, TentativeInstrs,
|
|
NumTentativeInstrs),
|
|
% If the number of instructions in the procedure is too big,
|
|
% then building MaybeLiveMap will probably take too long.
|
|
( if NumTentativeInstrs < local_vars_proc_size_limit then
|
|
build_livemap(TentativeInstrs, MaybeLiveMap),
|
|
extend_basic_blocks(LabelSeq, EBBLabelSeq, BlockMap0, EBBBlockMap0,
|
|
NewLabels),
|
|
list.foldl(use_local_vars_block(MaybeLiveMap, NumRealRRegs,
|
|
AccessThreshold), EBBLabelSeq, EBBBlockMap0, EBBBlockMap),
|
|
flatten_basic_blocks(EBBLabelSeq, EBBBlockMap, Instrs1, _),
|
|
|
|
( if
|
|
MaybeLiveMap = yes(LiveMap),
|
|
AutoComments = yes
|
|
then
|
|
NewComment = "\n" ++ dump_livemap(yes(ProcLabel), LiveMap),
|
|
NewCommentInstr = llds_instr(comment(NewComment), ""),
|
|
Comments = Comments0 ++ [NewCommentInstr]
|
|
else
|
|
Comments = Comments0
|
|
),
|
|
Instrs = Comments ++ Instrs1
|
|
else
|
|
Instrs = Instrs0
|
|
).
|
|
|
|
:- pred use_local_vars_block(maybe(livemap)::in, int::in, int::in, label::in,
|
|
block_map::in, block_map::out) is det.
|
|
|
|
use_local_vars_block(MaybeLiveMap, NumRealRRegs, AccessThreshold, Label,
|
|
!BlockMap) :-
|
|
map.lookup(!.BlockMap, Label, BlockInfo0),
|
|
BlockInfo0 = block_info(BlockLabel, LabelInstr, RestInstrs0, BlockSize,
|
|
FallInto, JumpLabels, MaybeFallThrough),
|
|
% The algorithm we use is half-quadratic, so using it on long instruction
|
|
% lists is not a good idea. The correct fix of course would be to reduce
|
|
% the complexity of the algorithm, but that is not trivial to do.
|
|
( if BlockSize < local_vars_block_size_limit then
|
|
counter.init(1, TempCounter0),
|
|
use_local_vars_instrs(RestInstrs0, RestInstrs,
|
|
TempCounter0, TempCounter, NumRealRRegs,
|
|
AccessThreshold, MaybeLiveMap, MaybeFallThrough),
|
|
( if TempCounter = TempCounter0 then
|
|
true
|
|
else
|
|
BlockInfo = block_info(BlockLabel, LabelInstr, RestInstrs,
|
|
BlockSize, FallInto, JumpLabels, MaybeFallThrough),
|
|
map.det_update(Label, BlockInfo, !BlockMap)
|
|
)
|
|
else
|
|
true
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred use_local_vars_instrs(list(instruction)::in, list(instruction)::out,
|
|
counter::in, counter::out, int::in, int::in, maybe(livemap)::in,
|
|
maybe(label)::in) is det.
|
|
|
|
use_local_vars_instrs(!RestInstrs, !TempCounter,
|
|
NumRealRRegs, AccessThreshold, MaybeLiveMap, MaybeFallThrough) :-
|
|
opt_assign(!RestInstrs, !TempCounter, NumRealRRegs, [], MaybeLiveMap,
|
|
MaybeFallThrough),
|
|
( if AccessThreshold >= 1 then
|
|
opt_access(!RestInstrs, !TempCounter, NumRealRRegs,
|
|
set.init, AccessThreshold)
|
|
else
|
|
true
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred opt_assign(list(instruction)::in, list(instruction)::out,
|
|
counter::in, counter::out, int::in, list(lval)::in,
|
|
maybe(livemap)::in, maybe(label)::in) is det.
|
|
|
|
opt_assign([], [], !TempCounter, _, _, _, _).
|
|
opt_assign([Instr0 | TailInstrs0], Instrs, !TempCounter, NumRealRRegs,
|
|
!.AvoidLvals, MaybeLiveMap, MaybeFallThrough) :-
|
|
Instr0 = llds_instr(Uinstr0, _Comment0),
|
|
( if
|
|
(
|
|
% We don't optimize keep_assign instructions.
|
|
(
|
|
Uinstr0 = assign(ToLval, _FromRval)
|
|
;
|
|
Uinstr0 = incr_hp(ToLval, _MaybeTag, _SizeRval, _MO, _Type,
|
|
_Atomic, _, _)
|
|
),
|
|
base_lval_worth_replacing(NumRealRRegs, ToLval),
|
|
MaybeMore = no
|
|
;
|
|
Uinstr0 = foreign_proc_code(_D, Comps, _MCM, _FNL, _FL, _FOL, _NF,
|
|
_MDL, _S, _MD),
|
|
opt_assign_find_output_in_components(Comps, NumRealRRegs,
|
|
!.AvoidLvals, ToLval),
|
|
MaybeMore = yes
|
|
)
|
|
then
|
|
( if
|
|
ToLval = reg(_, _),
|
|
find_compulsory_lvals(TailInstrs0, MaybeLiveMap, MaybeFallThrough,
|
|
no, MaybeCompulsoryLvals),
|
|
MaybeCompulsoryLvals = known(CompulsoryLvals),
|
|
not set.member(ToLval, CompulsoryLvals)
|
|
then
|
|
counter.allocate(TempNum, !TempCounter),
|
|
NewLval = temp(reg_type_for_lval(ToLval), TempNum),
|
|
substitute_lval_in_defn(ToLval, NewLval, Instr0, Instr),
|
|
list.map_foldl(
|
|
exprn_aux.substitute_lval_in_instr(ToLval, NewLval),
|
|
TailInstrs0, TailInstrs1, 0, _),
|
|
(
|
|
MaybeMore = no,
|
|
opt_assign(TailInstrs1, TailInstrs, !TempCounter, NumRealRRegs,
|
|
[], MaybeLiveMap, MaybeFallThrough),
|
|
Instrs = [Instr | TailInstrs]
|
|
;
|
|
MaybeMore = yes,
|
|
!:AvoidLvals = [ToLval | !.AvoidLvals],
|
|
Instrs1 = [Instr | TailInstrs1],
|
|
opt_assign(Instrs1, Instrs, !TempCounter, NumRealRRegs,
|
|
!.AvoidLvals, MaybeLiveMap, MaybeFallThrough)
|
|
)
|
|
else if
|
|
counter.allocate(TempNum, !TempCounter),
|
|
NewLval = temp(reg_type_for_lval(ToLval), TempNum),
|
|
substitute_lval_in_instr_until_defn(ToLval, NewLval,
|
|
TailInstrs0, TailInstrs1, 0, NumSubst),
|
|
NumSubst > 1
|
|
then
|
|
substitute_lval_in_defn(ToLval, NewLval, Instr0, Instr),
|
|
CopyInstr = llds_instr(assign(ToLval, lval(NewLval)), ""),
|
|
(
|
|
MaybeMore = no,
|
|
opt_assign(TailInstrs1, TailInstrs, !TempCounter, NumRealRRegs,
|
|
[], MaybeLiveMap, MaybeFallThrough),
|
|
Instrs = [Instr, CopyInstr | TailInstrs]
|
|
;
|
|
MaybeMore = yes,
|
|
!:AvoidLvals = [ToLval | !.AvoidLvals],
|
|
Instrs1 = [Instr, CopyInstr | TailInstrs1],
|
|
opt_assign(Instrs1, Instrs, !TempCounter, NumRealRRegs,
|
|
!.AvoidLvals, MaybeLiveMap, MaybeFallThrough)
|
|
)
|
|
else
|
|
(
|
|
MaybeMore = no,
|
|
opt_assign(TailInstrs0, TailInstrs, !TempCounter, NumRealRRegs,
|
|
[], MaybeLiveMap, MaybeFallThrough),
|
|
Instrs = [Instr0 | TailInstrs]
|
|
;
|
|
MaybeMore = yes,
|
|
!:AvoidLvals = [ToLval | !.AvoidLvals],
|
|
Instrs1 = [Instr0 | TailInstrs0],
|
|
disable_warning [suspicious_recursion] (
|
|
opt_assign(Instrs1, Instrs, !TempCounter, NumRealRRegs,
|
|
!.AvoidLvals, MaybeLiveMap, MaybeFallThrough)
|
|
)
|
|
)
|
|
)
|
|
else
|
|
opt_assign(TailInstrs0, TailInstrs, !TempCounter, NumRealRRegs,
|
|
[], MaybeLiveMap, MaybeFallThrough),
|
|
Instrs = [Instr0 | TailInstrs]
|
|
).
|
|
|
|
:- pred opt_assign_find_output_in_components(list(foreign_proc_component)::in,
|
|
int::in, list(lval)::in, lval::out) is semidet.
|
|
|
|
opt_assign_find_output_in_components([Comp | Comps], NumRealRRegs, AvoidLvals,
|
|
ToLval) :-
|
|
( if
|
|
Comp = foreign_proc_outputs(Outputs),
|
|
opt_assign_find_output_in_outputs(Outputs, NumRealRRegs, AvoidLvals,
|
|
ToLvalPrime)
|
|
then
|
|
ToLval = ToLvalPrime
|
|
else
|
|
opt_assign_find_output_in_components(Comps, NumRealRRegs, AvoidLvals,
|
|
ToLval)
|
|
).
|
|
|
|
:- pred opt_assign_find_output_in_outputs(list(foreign_proc_output)::in,
|
|
int::in, list(lval)::in, lval::out) is semidet.
|
|
|
|
opt_assign_find_output_in_outputs([Output | Outputs], NumRealRRegs, AvoidLvals,
|
|
ToLval) :-
|
|
Output = foreign_proc_output(Dest, _Type, _IsDummy, _VarName,
|
|
_OrigType, _MaybeForeignType, _BoxPolicy),
|
|
( if
|
|
base_lval_worth_replacing(NumRealRRegs, Dest),
|
|
not list.member(Dest, AvoidLvals)
|
|
then
|
|
ToLval = Dest
|
|
else
|
|
opt_assign_find_output_in_outputs(Outputs, NumRealRRegs, AvoidLvals,
|
|
ToLval)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type maybe_compulsory_lvals
|
|
---> known(lvalset)
|
|
; unknown_must_assume_all.
|
|
|
|
:- pred find_compulsory_lvals(list(instruction)::in, maybe(livemap)::in,
|
|
maybe(label)::in, bool::in, maybe_compulsory_lvals::out) is det.
|
|
|
|
find_compulsory_lvals([], MaybeLiveMap, MaybeFallThrough, _PrevLivevals,
|
|
MaybeCompulsoryLvals) :-
|
|
(
|
|
MaybeFallThrough = yes(FallThrough),
|
|
(
|
|
MaybeLiveMap = yes(LiveMap),
|
|
map.lookup(LiveMap, FallThrough, CompulsoryLvals),
|
|
MaybeCompulsoryLvals = known(CompulsoryLvals)
|
|
;
|
|
MaybeLiveMap = no,
|
|
MaybeCompulsoryLvals = unknown_must_assume_all
|
|
)
|
|
;
|
|
MaybeFallThrough = no,
|
|
MaybeCompulsoryLvals = unknown_must_assume_all
|
|
).
|
|
find_compulsory_lvals([Instr | Instrs], MaybeLiveMap, MaybeFallThrough,
|
|
PrevLivevals, !:MaybeCompulsoryLvals) :-
|
|
Instr = llds_instr(Uinstr, _),
|
|
( if
|
|
Uinstr = livevals(LiveLvals)
|
|
then
|
|
find_compulsory_lvals(Instrs, MaybeLiveMap, MaybeFallThrough,
|
|
yes, !:MaybeCompulsoryLvals),
|
|
union_maybe_compulsory_lvals(LiveLvals, !MaybeCompulsoryLvals)
|
|
else if
|
|
Uinstr = llcall(_, _, _, _, _, _)
|
|
then
|
|
expect(unify(PrevLivevals, yes), $pred, "call without livevals"),
|
|
% The livevals instruction will include all the live lvals
|
|
% in MaybeCompulsoryLvals after we return.
|
|
!:MaybeCompulsoryLvals = known(set.init)
|
|
else if
|
|
Uinstr = goto(_Target),
|
|
PrevLivevals = yes
|
|
then
|
|
% The livevals instruction will include all the live lvals
|
|
% in MaybeCompulsoryLvals after we return.
|
|
!:MaybeCompulsoryLvals = known(set.init)
|
|
else
|
|
possible_targets(Uinstr, Labels, NonLabelCodeAddrs),
|
|
(
|
|
NonLabelCodeAddrs = [],
|
|
(
|
|
Labels = [],
|
|
% Optimize the common case
|
|
find_compulsory_lvals(Instrs, MaybeLiveMap, MaybeFallThrough,
|
|
no, !:MaybeCompulsoryLvals)
|
|
;
|
|
Labels = [_ | _],
|
|
(
|
|
MaybeLiveMap = yes(LiveMap),
|
|
list.map(map.lookup(LiveMap), Labels, LabelsLiveLvals),
|
|
AllLabelsLiveLvals = set.union_list(LabelsLiveLvals),
|
|
find_compulsory_lvals(Instrs, MaybeLiveMap,
|
|
MaybeFallThrough, no, !:MaybeCompulsoryLvals),
|
|
union_maybe_compulsory_lvals(AllLabelsLiveLvals,
|
|
!MaybeCompulsoryLvals)
|
|
;
|
|
MaybeLiveMap = no,
|
|
!:MaybeCompulsoryLvals = unknown_must_assume_all
|
|
)
|
|
)
|
|
;
|
|
NonLabelCodeAddrs = [_ | _],
|
|
!:MaybeCompulsoryLvals = unknown_must_assume_all
|
|
)
|
|
).
|
|
|
|
:- pred union_maybe_compulsory_lvals(lvalset::in,
|
|
maybe_compulsory_lvals::in, maybe_compulsory_lvals::out) is det.
|
|
|
|
union_maybe_compulsory_lvals(New, !MaybeCompulsoryLvals) :-
|
|
(
|
|
!.MaybeCompulsoryLvals = known(OldCompulsoryLvals),
|
|
set.union(New, OldCompulsoryLvals, AllCompulsoryLvals),
|
|
!:MaybeCompulsoryLvals = known(AllCompulsoryLvals)
|
|
;
|
|
!.MaybeCompulsoryLvals = unknown_must_assume_all
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred opt_access(list(instruction)::in, list(instruction)::out,
|
|
counter::in, counter::out, int::in, lvalset::in, int::in) is det.
|
|
|
|
opt_access([], [], !TempCounter, _, _, _).
|
|
opt_access([Instr0 | TailInstrs0], Instrs, !TempCounter, NumRealRRegs,
|
|
AlreadyTried0, AccessThreshold) :-
|
|
Instr0 = llds_instr(Uinstr0, _Comment0),
|
|
( if
|
|
Uinstr0 = assign(ToLval, FromRval),
|
|
SubLvals = lvals_in_lval(ToLval) ++ lvals_in_rval(FromRval),
|
|
list.filter(
|
|
base_lval_worth_replacing_not_tried(AlreadyTried0, NumRealRRegs),
|
|
SubLvals, ReplaceableSubLvals),
|
|
ReplaceableSubLvals = [ChosenLval | ChooseableRvals]
|
|
then
|
|
OrigTempCounter = !.TempCounter,
|
|
counter.allocate(TempNum, !TempCounter),
|
|
TempLval = temp(reg_type_for_lval(ChosenLval), TempNum),
|
|
SubChosenLvals = lvals_in_lval(ChosenLval),
|
|
expect(unify(SubChosenLvals, []), $pred,
|
|
"nonempty SubChosenLvals"),
|
|
substitute_lval_in_instr_until_defn(ChosenLval, TempLval,
|
|
[Instr0 | TailInstrs0], Instrs1, 0, NumReplacements),
|
|
set.insert(ChosenLval, AlreadyTried0, AlreadyTried1),
|
|
( if NumReplacements >= AccessThreshold then
|
|
TempAssign = llds_instr(assign(TempLval, lval(ChosenLval)),
|
|
"factor out common sub lval"),
|
|
Instrs2 = [TempAssign | Instrs1],
|
|
opt_access(Instrs2, Instrs, !TempCounter, NumRealRRegs,
|
|
AlreadyTried1, AccessThreshold)
|
|
else
|
|
(
|
|
ChooseableRvals = [_ | _],
|
|
!:TempCounter = OrigTempCounter,
|
|
opt_access([Instr0 | TailInstrs0], Instrs, !TempCounter,
|
|
NumRealRRegs, AlreadyTried1, AccessThreshold)
|
|
;
|
|
ChooseableRvals = [],
|
|
!:TempCounter = OrigTempCounter,
|
|
opt_access(TailInstrs0, TailInstrs, !TempCounter, NumRealRRegs,
|
|
set.init, AccessThreshold),
|
|
Instrs = [Instr0 | TailInstrs]
|
|
)
|
|
)
|
|
else
|
|
opt_access(TailInstrs0, TailInstrs, !TempCounter, NumRealRRegs,
|
|
set.init, AccessThreshold),
|
|
Instrs = [Instr0 | TailInstrs]
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred base_lval_worth_replacing(int::in, lval::in) is semidet.
|
|
|
|
base_lval_worth_replacing(NumRealRRegs, Lval) :-
|
|
(
|
|
Lval = reg(reg_r, RegNum),
|
|
RegNum > NumRealRRegs
|
|
;
|
|
Lval = reg(reg_f, _)
|
|
;
|
|
Lval = stackvar(_)
|
|
;
|
|
Lval = framevar(_)
|
|
;
|
|
Lval = double_stackvar(_, _)
|
|
).
|
|
|
|
:- pred base_lval_worth_replacing_not_tried(lvalset::in, int::in, lval::in)
|
|
is semidet.
|
|
|
|
base_lval_worth_replacing_not_tried(AlreadyTried, NumRealRRegs, Lval) :-
|
|
\+ set.member(Lval, AlreadyTried),
|
|
base_lval_worth_replacing(NumRealRRegs, Lval).
|
|
|
|
:- func reg_type_for_lval(lval) = reg_type.
|
|
|
|
reg_type_for_lval(Lval) = RegType :-
|
|
(
|
|
Lval = reg(RegType, _)
|
|
;
|
|
Lval = temp(RegType, _)
|
|
;
|
|
Lval = double_stackvar(_, _),
|
|
RegType = reg_f
|
|
;
|
|
( Lval = succip
|
|
; Lval = maxfr
|
|
; Lval = curfr
|
|
; Lval = hp
|
|
; Lval = sp
|
|
; Lval = parent_sp
|
|
; Lval = stackvar(_)
|
|
; Lval = parent_stackvar(_)
|
|
; Lval = framevar(_)
|
|
; Lval = succip_slot(_)
|
|
; Lval = succfr_slot(_)
|
|
; Lval = redoip_slot(_)
|
|
; Lval = redofr_slot(_)
|
|
; Lval = prevfr_slot(_)
|
|
; Lval = field(_, _, _)
|
|
; Lval = mem_ref(_)
|
|
; Lval = global_var_ref(_)
|
|
; Lval = lvar(_)
|
|
),
|
|
RegType = reg_r
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% When processing substituting e.g. tempr1 for e.g. r2 in the instruction
|
|
% that defines r2, we must be careful to leave intact the value being
|
|
% assigned. Given the instruction
|
|
%
|
|
% r2 = field(0, r2, 5)
|
|
%
|
|
% we must generate
|
|
%
|
|
% tempr1 = field(0, r2, 5)
|
|
%
|
|
% Generating
|
|
%
|
|
% tempr1 = field(0, tempr1, 5)
|
|
%
|
|
% would introduce a bug, since the right hand side now refers to
|
|
% an as yet undefined variable.
|
|
%
|
|
:- pred substitute_lval_in_defn(lval::in, lval::in,
|
|
instruction::in, instruction::out) is det.
|
|
|
|
substitute_lval_in_defn(OldLval, NewLval, Instr0, Instr) :-
|
|
Instr0 = llds_instr(Uinstr0, Comment),
|
|
( if
|
|
Uinstr0 = assign(ToLval, FromRval)
|
|
then
|
|
expect(unify(ToLval, OldLval), $pred, "mismatch in assign"),
|
|
Uinstr = assign(NewLval, FromRval)
|
|
else if
|
|
Uinstr0 = incr_hp(ToLval, MaybeTag, SizeRval, MO, Type,
|
|
MayUseAtomic, MaybeRegionRval, MaybeReuse)
|
|
then
|
|
expect(unify(ToLval, OldLval), $pred, "mismatch in incr_hp"),
|
|
Uinstr = incr_hp(NewLval, MaybeTag, SizeRval, MO, Type,
|
|
MayUseAtomic, MaybeRegionRval, MaybeReuse)
|
|
else if
|
|
Uinstr0 = foreign_proc_code(D, Comps0, MCM, FNL, FL, FOL, NF, MDL,
|
|
S, MD)
|
|
then
|
|
substitute_lval_in_defn_components(OldLval, NewLval, Comps0, Comps,
|
|
0, NumSubsts),
|
|
expect(unify(NumSubsts, 1), $pred, "mismatch in foreign_proc_code"),
|
|
Uinstr = foreign_proc_code(D, Comps, MCM, FNL, FL, FOL, NF, MDL, S, MD)
|
|
else
|
|
unexpected($pred, "unexpected instruction")
|
|
),
|
|
Instr = llds_instr(Uinstr, Comment).
|
|
|
|
:- pred substitute_lval_in_defn_components(lval::in, lval::in,
|
|
list(foreign_proc_component)::in, list(foreign_proc_component)::out,
|
|
int::in, int::out) is det.
|
|
|
|
substitute_lval_in_defn_components(_OldLval, _NewLval, [], [], !NumSubsts).
|
|
substitute_lval_in_defn_components(OldLval, NewLval,
|
|
[Comp0 | Comps0], [Comp | Comps], !NumSubsts) :-
|
|
(
|
|
Comp0 = foreign_proc_outputs(Outputs0),
|
|
substitute_lval_in_defn_outputs(OldLval, NewLval,
|
|
Outputs0, Outputs, !NumSubsts),
|
|
Comp = foreign_proc_outputs(Outputs)
|
|
;
|
|
( Comp0 = foreign_proc_inputs(_)
|
|
; Comp0 = foreign_proc_user_code(_, _, _)
|
|
; Comp0 = foreign_proc_raw_code(_, _, _, _)
|
|
; Comp0 = foreign_proc_fail_to(_)
|
|
; Comp0 = foreign_proc_alloc_id(_)
|
|
; Comp0 = foreign_proc_noop
|
|
),
|
|
Comp = Comp0
|
|
),
|
|
substitute_lval_in_defn_components(OldLval, NewLval, Comps0, Comps,
|
|
!NumSubsts).
|
|
|
|
:- pred substitute_lval_in_defn_outputs(lval::in, lval::in,
|
|
list(foreign_proc_output)::in, list(foreign_proc_output)::out,
|
|
int::in, int::out) is det.
|
|
|
|
substitute_lval_in_defn_outputs(_OldLval, _NewLval, [], [], !NumSubsts).
|
|
substitute_lval_in_defn_outputs(OldLval, NewLval,
|
|
[Output0 | Outputs0], [Output | Outputs], !NumSubsts) :-
|
|
Output0 = foreign_proc_output(Dest0, Type, IsDummy, VarName,
|
|
OrigType, MaybeForeignType, BoxPolicy),
|
|
( if Dest0 = OldLval then
|
|
Output = foreign_proc_output(NewLval, Type, IsDummy, VarName,
|
|
OrigType, MaybeForeignType, BoxPolicy),
|
|
!:NumSubsts = !.NumSubsts + 1
|
|
else
|
|
Output = Output0
|
|
),
|
|
substitute_lval_in_defn_outputs(OldLval, NewLval, Outputs0, Outputs,
|
|
!NumSubsts).
|
|
|
|
% Substitute NewLval for OldLval in an instruction sequence
|
|
% until we come an instruction that may define OldLval.
|
|
% We don't worry about instructions that define a variable that
|
|
% occurs in the access path to OldLval (and which therefore indirectly
|
|
% modifies the value that OldLval refers to), because our caller will
|
|
% call us only with OldLvals (and NewLvals for that matter) that have
|
|
% no lvals in their access path. The NewLvals will be temporaries,
|
|
% representing local variables in C blocks.
|
|
%
|
|
% When control leaves this instruction sequence via a if_val, goto or
|
|
% call, the local variables of the block in which this instruction
|
|
% sequence will go out of scope, so we must stop using them. At points
|
|
% at which control can enter this instruction sequence, i.e. at labels,
|
|
% the C block ends, so again we must stop using its local variables.
|
|
% (Livevals pseudo-instructions occur only immediately before
|
|
% instructions that cause control transfer, so we stop at them too.)
|
|
%
|
|
% Our caller ensures that we can also so stop at any point. By doing so
|
|
% we may fail to exploit an optimization opportunity, but the code we
|
|
% generate will still be correct. At the moment we stop at instructions
|
|
% whose correct handling would be non-trivial and which rarely if ever
|
|
% appear between the definition and a use of a location we want to
|
|
% substitute. These include instructions that manipulate stack frames,
|
|
% the heap, the trail and synchronization data.
|
|
%
|
|
:- pred substitute_lval_in_instr_until_defn(lval::in, lval::in,
|
|
list(instruction)::in, list(instruction)::out, int::in, int::out)
|
|
is det.
|
|
|
|
substitute_lval_in_instr_until_defn(_, _, [], [], !N).
|
|
substitute_lval_in_instr_until_defn(OldLval, NewLval,
|
|
[Instr0 | Instrs0], [Instr | Instrs], !N) :-
|
|
substitute_lval_in_instr_until_defn_2(OldLval, NewLval,
|
|
Instr0, Instr, Instrs0, Instrs, !N).
|
|
|
|
:- pred substitute_lval_in_instr_until_defn_2(lval::in, lval::in,
|
|
instruction::in, instruction::out,
|
|
list(instruction)::in, list(instruction)::out, int::in, int::out) is det.
|
|
:- pragma inline(substitute_lval_in_instr_until_defn_2/8).
|
|
|
|
substitute_lval_in_instr_until_defn_2(OldLval, NewLval, !Instr, !Instrs, !N) :-
|
|
!.Instr = llds_instr(Uinstr0, Comment),
|
|
(
|
|
Uinstr0 = block(_, _, _),
|
|
unexpected($pred, "block")
|
|
;
|
|
Uinstr0 = assign(Lval, Rval0),
|
|
Updates = assignment_updates_oldlval(Lval, OldLval),
|
|
(
|
|
Updates = yes,
|
|
% If we alter any lval that occurs in OldLval, we must stop
|
|
% the substitutions.
|
|
exprn_aux.substitute_lval_in_rval(OldLval, NewLval, Rval0, Rval),
|
|
Uinstr = assign(Lval, Rval),
|
|
!:Instr = llds_instr(Uinstr, Comment)
|
|
;
|
|
Updates = no,
|
|
exprn_aux.substitute_lval_in_instr(OldLval, NewLval, !Instr, !N),
|
|
substitute_lval_in_instr_until_defn(OldLval, NewLval, !Instrs, !N)
|
|
)
|
|
;
|
|
Uinstr0 = keep_assign(Lval, Rval0),
|
|
Updates = assignment_updates_oldlval(Lval, OldLval),
|
|
(
|
|
Updates = yes,
|
|
% If we alter any lval that occurs in OldLval, we must stop
|
|
% the substitutions.
|
|
exprn_aux.substitute_lval_in_rval(OldLval, NewLval, Rval0, Rval),
|
|
Uinstr = keep_assign(Lval, Rval),
|
|
!:Instr = llds_instr(Uinstr, Comment)
|
|
;
|
|
Updates = no,
|
|
exprn_aux.substitute_lval_in_instr(OldLval, NewLval, !Instr, !N),
|
|
substitute_lval_in_instr_until_defn(OldLval, NewLval, !Instrs, !N)
|
|
)
|
|
;
|
|
Uinstr0 = lc_create_loop_control(_NumSlots, Lval),
|
|
Updates = assignment_updates_oldlval(Lval, OldLval),
|
|
(
|
|
Updates = yes
|
|
% If we alter any lval that occurs in OldLval, we must stop
|
|
% the substitutions.
|
|
;
|
|
Updates = no,
|
|
exprn_aux.substitute_lval_in_instr(OldLval, NewLval, !Instr, !N),
|
|
substitute_lval_in_instr_until_defn(OldLval, NewLval, !Instrs, !N)
|
|
)
|
|
;
|
|
Uinstr0 = lc_wait_free_slot(Rval0, Lval, Label),
|
|
Updates = assignment_updates_oldlval(Lval, OldLval),
|
|
(
|
|
Updates = yes,
|
|
% If we alter any lval that occurs in OldLval, we must stop
|
|
% the substitutions.
|
|
exprn_aux.substitute_lval_in_rval(OldLval, NewLval, Rval0, Rval),
|
|
Uinstr = lc_wait_free_slot(Rval, Lval, Label),
|
|
!:Instr = llds_instr(Uinstr, Comment)
|
|
;
|
|
Updates = no,
|
|
exprn_aux.substitute_lval_in_instr(OldLval, NewLval, !Instr, !N),
|
|
substitute_lval_in_instr_until_defn(OldLval, NewLval, !Instrs, !N)
|
|
)
|
|
;
|
|
( Uinstr0 = incr_hp(Lval, _, _, _, _, _, _, _)
|
|
; Uinstr0 = save_maxfr(Lval)
|
|
; Uinstr0 = mark_hp(Lval)
|
|
),
|
|
Updates = assignment_updates_oldlval(Lval, OldLval),
|
|
(
|
|
Updates = yes
|
|
% If we alter any lval that occurs in OldLval, we must stop
|
|
% the substitutions.
|
|
;
|
|
Updates = no,
|
|
exprn_aux.substitute_lval_in_instr(OldLval, NewLval, !Instr, !N),
|
|
substitute_lval_in_instr_until_defn(OldLval, NewLval, !Instrs, !N)
|
|
)
|
|
;
|
|
Uinstr0 = region_fill_frame(_, _, _, NumLval, AddrLval),
|
|
( if
|
|
( assignment_updates_oldlval(NumLval, OldLval) = yes
|
|
; assignment_updates_oldlval(AddrLval, OldLval) = yes
|
|
)
|
|
then
|
|
% If we alter any lval that occurs in NumLval or AddrLval,
|
|
% we must stop the substitutions.
|
|
true
|
|
else
|
|
exprn_aux.substitute_lval_in_instr(OldLval, NewLval, !Instr, !N),
|
|
substitute_lval_in_instr_until_defn(OldLval, NewLval, !Instrs, !N)
|
|
)
|
|
;
|
|
( Uinstr0 = restore_maxfr(_)
|
|
; Uinstr0 = restore_hp(_)
|
|
; Uinstr0 = push_region_frame(_, _)
|
|
; Uinstr0 = region_set_fixed_slot(_, _, _)
|
|
; Uinstr0 = use_and_maybe_pop_region_frame(_, _)
|
|
; Uinstr0 = lc_spawn_off(_, _, _)
|
|
; Uinstr0 = lc_join_and_terminate(_, _)
|
|
),
|
|
exprn_aux.substitute_lval_in_instr(OldLval, NewLval, !Instr, !N),
|
|
substitute_lval_in_instr_until_defn(OldLval, NewLval, !Instrs, !N)
|
|
;
|
|
Uinstr0 = foreign_proc_code(_, Components, _, _, _, _, _, _, _, _),
|
|
AffectsLiveness = components_affect_liveness(Components),
|
|
(
|
|
AffectsLiveness = no,
|
|
Updates = components_update_oldlval(Components, OldLval),
|
|
(
|
|
Updates = yes
|
|
% If we alter any lval that occurs in OldLval, we must stop
|
|
% the substitutions.
|
|
;
|
|
Updates = no,
|
|
exprn_aux.substitute_lval_in_instr(OldLval, NewLval,
|
|
!Instr, !N),
|
|
substitute_lval_in_instr_until_defn(OldLval, NewLval,
|
|
!Instrs, !N)
|
|
)
|
|
;
|
|
AffectsLiveness = yes
|
|
)
|
|
;
|
|
Uinstr0 = comment(_),
|
|
substitute_lval_in_instr_until_defn(OldLval, NewLval, !Instrs, !N)
|
|
;
|
|
Uinstr0 = if_val(_, _),
|
|
exprn_aux.substitute_lval_in_instr(OldLval, NewLval, !Instr, !N),
|
|
substitute_lval_in_instr_until_defn(OldLval, NewLval, !Instrs, !N)
|
|
;
|
|
Uinstr0 = computed_goto(_, _),
|
|
exprn_aux.substitute_lval_in_instr(OldLval, NewLval, !Instr, !N)
|
|
;
|
|
( Uinstr0 = label(_)
|
|
; Uinstr0 = livevals(_)
|
|
; Uinstr0 = llcall(_, _, _, _, _, _)
|
|
; Uinstr0 = mkframe(_, _)
|
|
; Uinstr0 = goto(_)
|
|
; Uinstr0 = free_heap(_)
|
|
; Uinstr0 = store_ticket(_)
|
|
; Uinstr0 = reset_ticket(_, _)
|
|
; Uinstr0 = discard_ticket
|
|
; Uinstr0 = prune_ticket
|
|
; Uinstr0 = mark_ticket_stack(_)
|
|
; Uinstr0 = prune_tickets_to(_)
|
|
; Uinstr0 = incr_sp(_, _, _)
|
|
; Uinstr0 = decr_sp(_)
|
|
; Uinstr0 = decr_sp_and_return(_)
|
|
; Uinstr0 = init_sync_term(_, _, _)
|
|
; Uinstr0 = fork_new_child(_, _)
|
|
; Uinstr0 = join_and_continue(_, _)
|
|
; Uinstr0 = arbitrary_c_code(_, _, _)
|
|
)
|
|
).
|
|
|
|
:- func assignment_updates_oldlval(lval, lval) = bool.
|
|
|
|
assignment_updates_oldlval(Lval, OldLval) =
|
|
( if Lval = OldLval then
|
|
% If we alter any lval that occurs in OldLval, we must stop the
|
|
% substitutions. At the moment, the only lval OldLval can contain
|
|
% is itself.
|
|
yes
|
|
else
|
|
no
|
|
).
|
|
|
|
:- func components_update_oldlval(list(foreign_proc_component), lval) = bool.
|
|
|
|
components_update_oldlval([], _Lval) = no.
|
|
components_update_oldlval([Component | Components], Lval) = Updates :-
|
|
ComponentUpdates = component_updates_oldlval(Component, Lval),
|
|
(
|
|
ComponentUpdates = yes,
|
|
Updates = yes
|
|
;
|
|
ComponentUpdates = no,
|
|
Updates = components_update_oldlval(Components, Lval)
|
|
).
|
|
|
|
:- func component_updates_oldlval(foreign_proc_component, lval) = bool.
|
|
|
|
component_updates_oldlval(Component, Lval) = Updates :-
|
|
(
|
|
Component = foreign_proc_outputs(Outputs),
|
|
( if
|
|
some [Output] (
|
|
list.member(Output, Outputs),
|
|
Output ^ out_arg_dest = Lval
|
|
)
|
|
then
|
|
Updates = yes
|
|
else
|
|
Updates = no
|
|
)
|
|
;
|
|
( Component = foreign_proc_inputs(_)
|
|
; Component = foreign_proc_fail_to(_)
|
|
; Component = foreign_proc_alloc_id(_)
|
|
; Component = foreign_proc_noop
|
|
; Component = foreign_proc_user_code(_, _, _)
|
|
; Component = foreign_proc_raw_code(_, _, _, _)
|
|
),
|
|
Updates = no
|
|
).
|
|
|
|
:- func components_affect_liveness(list(foreign_proc_component))
|
|
= bool.
|
|
|
|
components_affect_liveness([]) = no.
|
|
components_affect_liveness([Component | Components]) = Updates :-
|
|
ComponentUpdates = component_affects_liveness(Component),
|
|
(
|
|
ComponentUpdates = yes,
|
|
Updates = yes
|
|
;
|
|
ComponentUpdates = no,
|
|
Updates = components_affect_liveness(Components)
|
|
).
|
|
|
|
:- func component_affects_liveness(foreign_proc_component) = bool.
|
|
|
|
component_affects_liveness(Component) = Affects :-
|
|
(
|
|
( Component = foreign_proc_inputs(_)
|
|
; Component = foreign_proc_outputs(_)
|
|
; Component = foreign_proc_fail_to(_)
|
|
; Component = foreign_proc_alloc_id(_)
|
|
; Component = foreign_proc_noop
|
|
),
|
|
Affects = no
|
|
;
|
|
( Component = foreign_proc_user_code(_, AffectsLiveness, Code)
|
|
; Component = foreign_proc_raw_code(_, AffectsLiveness, _, Code)
|
|
),
|
|
(
|
|
AffectsLiveness = proc_affects_liveness,
|
|
Affects = yes
|
|
;
|
|
AffectsLiveness = proc_does_not_affect_liveness,
|
|
Affects = no
|
|
;
|
|
AffectsLiveness = proc_default_affects_liveness,
|
|
( if Code = "" then
|
|
Affects = no
|
|
else
|
|
Affects = yes
|
|
)
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- func local_vars_proc_size_limit = int.
|
|
|
|
local_vars_proc_size_limit = 10000.
|
|
|
|
:- func local_vars_block_size_limit = int.
|
|
|
|
local_vars_block_size_limit = 200.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module ll_backend.use_local_vars.
|
|
%-----------------------------------------------------------------------------%
|