Files
mercury/compiler/use_local_vars.m
Zoltan Somogyi 9c58f97e3b Add a new optimization, --use-local-vars, to the LLDS backend.
Estimated hours taken: 20
Branches: main

Add a new optimization, --use-local-vars, to the LLDS backend. This
optimization is intended to replace references to fake registers and stack
slots with references to temporary variables in C code, since accessing these
should be cheaper.

With this optimization and one for delaying construction unifications,
the eager code generator should generate code at least good as that produced by
the old value numbering pass. This should make it possible to get rid of value
numbering, which is much harder to maintain.

compiler/use_local_vars.m:
	New module containing the optimization.

compiler/notes/compiler_design.html:
	Mention the new module.

compiler/exprn_aux.m:
	Add new utility predicates for use by use_local_vars.

	If --debug-opt is specified, do not dump instruction sequences to
	standard output. Instead, put them in separate files, where they can be
	compared more easily.

compiler/options.m:
	Add the --use-local-vars option to control whether the use_local_vars
	pass gets run.

compiler/llds.m:
	Add liveness information to the c_code and pragma_foreign_code LLDS
	instructions, in order to allow use_local_vars to work in the presence
	of automatically-generated C code (e.g. by debugging).

compiler/livemap.m:
	Use the new liveness information to generate useful livemap information
	even in the presence of automatically generated C code.

compiler/code_gen.m:
compiler/code_info.m:
compiler/dupelim.m:
compiler/frameopt.m:
compiler/llds_common.m:
compiler/llds_out.m:
compiler/middle_rec.m:
compiler/opt_debug.m:
compiler/opt_util.m:
compiler/pragma_c_gen.m:
compiler/trace.m:
compiler/vn_block.m:
compiler/vn_cost.m:
compiler/vn_filter.m:
compiler/vn_verify.m:
	Provide and/or ignore this additional liveness information.

compiler/wrap_block.m:
	The post_value_number pass wraps LLDS instruction sequences
	using temporaries in a block instruction which actually declares
	those temporaries. It used to be used only by value numbering;
	it is now also used by use_local_vars. It has therefore been renamed
	and put in its own file.

compiler/optimize.m:
	Invoke use_local_vars if required, and call wrap_blocks instead of
	post_value_number.

compiler/value_number.m:
	Since the value numbering pass still cannot handle automatically
	generated C code, check for it explicitly now that livemap carries out
	only a weaker check.

compiler/basic_block.m:
	Add a module qualification.

library/set.m:
library/set_bbbtree.m:
library/set_ordlist.m:
library/set_unordlist.m:
	Add a new predicate, union_list, to each implementation of sets,
	for use by some of the new code above.

tests/general/array_test.m:
	Print out the result of each operation as soon as it is done, so that
	if you get a seg fault, you know which operations have completed and
	which haven't.
2001-04-24 03:59:13 +00:00

457 lines
14 KiB
Mathematica

%-----------------------------------------------------------------------------%
% Copyright (C) 2001 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 two 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.
%-----------------------------------------------------------------------------%
:- module use_local_vars.
:- interface.
:- import_module llds.
:- import_module list, counter.
:- pred use_local_vars__main(list(instruction)::in, list(instruction)::out,
proc_label::in, int::in, counter::in, counter::out) is det.
:- implementation.
:- import_module basic_block, livemap, exprn_aux, opt_util.
:- import_module int, set, map, counter, std_util, require.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
use_local_vars__main(Instrs0, Instrs, ProcLabel, NumRealRRegs, C0, C) :-
create_basic_blocks(Instrs0, Comments, ProcLabel, C0, C1,
LabelSeq, BlockMap0),
flatten_basic_blocks(LabelSeq, BlockMap0, TentativeInstrs),
livemap__build(TentativeInstrs, MaybeLiveMap),
(
% Instrs0 must have contained C code which cannot be analyzed
MaybeLiveMap = no,
Instrs = Instrs0,
C = C0
;
MaybeLiveMap = yes(LiveMap),
list__foldl(use_local_vars_block(LiveMap, NumRealRRegs),
LabelSeq, BlockMap0, BlockMap),
flatten_basic_blocks(LabelSeq, BlockMap, Instrs1),
list__append(Comments, Instrs1, Instrs),
C = C1
).
:- pred use_local_vars_block(livemap::in, int::in, label::in, block_map::in,
block_map::out) is det.
use_local_vars_block(LiveMap, NumRealRRegs, Label, BlockMap0, BlockMap) :-
map__lookup(BlockMap0, Label, BlockInfo0),
BlockInfo0 = block_info(BlockLabel, LabelInstr, RestInstrs0,
JumpLabels, MaybeFallThrough),
( can_branch_to_unknown_label(RestInstrs0) ->
MaybeEndLiveLvals = no
;
(
MaybeFallThrough = yes(FallThrough),
EndLabels = [FallThrough | JumpLabels]
;
MaybeFallThrough = no,
EndLabels = JumpLabels
),
list__foldl(find_live_lvals_at_end_labels(LiveMap), EndLabels,
set__init, EndLiveLvals0),
list__foldl(find_live_lvals_in_annotations, RestInstrs0,
EndLiveLvals0, EndLiveLvals),
MaybeEndLiveLvals = yes(EndLiveLvals)
),
counter__init(1, TempCounter0),
use_local_vars_instrs(RestInstrs0, RestInstrs,
TempCounter0, TempCounter, NumRealRRegs, MaybeEndLiveLvals),
( TempCounter = TempCounter0 ->
BlockMap = BlockMap0
;
BlockInfo = block_info(BlockLabel, LabelInstr,
RestInstrs, JumpLabels, MaybeFallThrough),
map__det_update(BlockMap0, Label, BlockInfo, BlockMap)
).
:- pred can_branch_to_unknown_label(list(instruction)::in) is semidet.
can_branch_to_unknown_label([Uinstr - _ | Instrs]) :-
(
opt_util__instr_labels(Uinstr, _, CodeAddrs),
some_code_addr_is_not_label(CodeAddrs)
;
can_branch_to_unknown_label(Instrs)
).
:- pred some_code_addr_is_not_label(list(code_addr)::in) is semidet.
some_code_addr_is_not_label([CodeAddr | CodeAddrs]) :-
(
CodeAddr \= label(_Label)
;
some_code_addr_is_not_label(CodeAddrs)
).
:- pred find_live_lvals_at_end_labels(livemap::in, label::in,
lvalset::in, lvalset::out) is det.
find_live_lvals_at_end_labels(LiveMap, Label, LiveLvals0, LiveLvals) :-
( map__search(LiveMap, Label, LabelLiveLvals) ->
set__union(LiveLvals0, LabelLiveLvals, LiveLvals)
; Label = local(_, _) ->
error("find_live_lvals_at_end_labels: local label not found")
;
% Non-local labels can be found only through call instructions,
% which must be preceded by livevals instructions. The
% variables live at the label will be included when we process
% the livevals instruction.
LiveLvals = LiveLvals0
).
:- pred find_live_lvals_in_annotations(instruction::in,
lvalset::in, lvalset::out) is det.
find_live_lvals_in_annotations(Uinstr - _, LiveLvals0, LiveLvals) :-
( Uinstr = livevals(InstrLiveLvals) ->
set__union(LiveLvals0, InstrLiveLvals, LiveLvals)
;
LiveLvals = LiveLvals0
).
:- pred use_local_vars_instrs(list(instruction)::in, list(instruction)::out,
counter::in, counter::out, int::in, maybe(lvalset)::in) is det.
use_local_vars_instrs([], [], TempCounter, TempCounter, _, _).
use_local_vars_instrs([Instr0 | TailInstrs0], Instrs,
TempCounter0, TempCounter, NumRealRRegs, MaybeEndLiveLvals) :-
Instr0 = Uinstr0 - _Comment0,
(
( Uinstr0 = assign(ToLval, _FromRval)
; Uinstr0 = incr_hp(ToLval, _MaybeTag, _SizeRval, _Type)
),
(
ToLval = reg(r, RegNum),
RegNum > NumRealRRegs
;
ToLval = stackvar(_)
;
ToLval = framevar(_)
)
->
counter__allocate(TempNum, TempCounter0, TempCounter1),
NewLval = temp(r, TempNum),
(
ToLval = reg(_, _),
MaybeEndLiveLvals = yes(EndLiveLvals),
not set__member(ToLval, EndLiveLvals)
->
substitute_lval_in_defn(ToLval, NewLval,
Instr0, Instr),
list__map_foldl(exprn_aux__substitute_lval_in_instr(
ToLval, NewLval),
TailInstrs0, TailInstrs1, 0, _),
use_local_vars_instrs(TailInstrs1, TailInstrs,
TempCounter1, TempCounter,
NumRealRRegs, MaybeEndLiveLvals),
Instrs = [Instr | TailInstrs]
;
substitute_lval_in_instr_until_defn(ToLval, NewLval,
TailInstrs0, TailInstrs1, 0, NumSubst),
NumSubst > 1
->
substitute_lval_in_defn(ToLval, NewLval,
Instr0, Instr),
CopyInstr = assign(ToLval, lval(NewLval)) - "",
use_local_vars_instrs(TailInstrs1, TailInstrs,
TempCounter1, TempCounter,
NumRealRRegs, MaybeEndLiveLvals),
Instrs = [Instr, CopyInstr | TailInstrs]
;
use_local_vars_instrs(TailInstrs0, TailInstrs,
TempCounter0, TempCounter,
NumRealRRegs, MaybeEndLiveLvals),
Instrs = [Instr0 | TailInstrs]
)
;
use_local_vars_instrs(TailInstrs0, TailInstrs,
TempCounter0, TempCounter,
NumRealRRegs, MaybeEndLiveLvals),
Instrs = [Instr0 | TailInstrs]
).
% 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 = Uinstr0 - Comment,
( Uinstr0 = assign(ToLval, FromRval) ->
require(unify(ToLval, OldLval),
"substitute_lval_in_defn: mismatch in assign"),
Uinstr = assign(NewLval, FromRval)
; Uinstr0 = incr_hp(ToLval, MaybeTag, SizeRval, Type) ->
require(unify(ToLval, OldLval),
"substitute_lval_in_defn: mismatch in incr_hp"),
Uinstr = incr_hp(NewLval, MaybeTag, SizeRval, Type)
;
error("substitute_lval_in_defn: unexpected instruction")
),
Instr = Uinstr - Comment.
% 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, N).
substitute_lval_in_instr_until_defn(OldLval, NewLval,
[Instr0 | Instrs0], [Instr | Instrs], N0, N) :-
Instr0 = Uinstr0 - _,
(
Uinstr0 = comment(_),
Instr = Instr0,
substitute_lval_in_instr_until_defn(OldLval, NewLval,
Instrs0, Instrs, N0, N)
;
Uinstr0 = livevals(_),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = block(_, _, _),
error("substitute_lval_in_instr_until_defn: found block")
;
Uinstr0 = assign(Lval, _),
( Lval = OldLval ->
% If we alter any lval that occurs in OldLval,
% we must stop the substitutions. At the
% moment, the only lval OldLval contains is
% itself.
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
exprn_aux__substitute_lval_in_instr(OldLval, NewLval,
Instr0, Instr, N0, N1),
substitute_lval_in_instr_until_defn(OldLval, NewLval,
Instrs0, Instrs, N1, N)
)
;
Uinstr0 = call(_, _, _, _, _, _),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = mkframe(_, _),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = label(_),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = goto(_),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = computed_goto(_, _),
exprn_aux__substitute_lval_in_instr(OldLval, NewLval,
Instr0, Instr, N0, N),
Instrs = Instrs0
;
Uinstr0 = if_val(_, _),
exprn_aux__substitute_lval_in_instr(OldLval, NewLval,
Instr0, Instr, N0, N),
Instrs = Instrs0
;
Uinstr0 = incr_hp(Lval, _, _, _),
( Lval = OldLval ->
% If we alter any lval that occurs in OldLval,
% we must stop the substitutions. At the
% moment, the only lval OldLval contains is
% itself.
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
exprn_aux__substitute_lval_in_instr(OldLval, NewLval,
Instr0, Instr, N0, N1),
substitute_lval_in_instr_until_defn(OldLval, NewLval,
Instrs0, Instrs, N1, N)
)
;
Uinstr0 = mark_hp(_),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = restore_hp(_),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = free_heap(_),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = store_ticket(_),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = reset_ticket(_, _),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = discard_ticket,
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = prune_ticket,
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = mark_ticket_stack(_),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = prune_tickets_to(_),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = incr_sp(_, _),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = decr_sp(_),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = init_sync_term(_, _),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = fork(_, _, _),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = join_and_terminate(_),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = join_and_continue(_, _),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = c_code(_, _),
Instr = Instr0,
Instrs = Instrs0,
N = N0
;
Uinstr0 = pragma_c(_, _, _, _, _, _, _, _),
Instr = Instr0,
Instrs = Instrs0,
N = N0
).