%-----------------------------------------------------------------------------%
% Copyright (C) 1998 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: rl_loop.m
% Main author: stayl
%
% Detect and move loop invariant instructions.
% As described in the dragon book -
% 	Aho, Sethi and Ullman,
% 	Compilers: principles, techniques and tools,
%	Addison-Wesley, 1986.
%-----------------------------------------------------------------------------%

:- module rl_loop.

:- interface.

:- import_module rl_block.

	% Given the flow graph for a procedure, return a new flow 
	% graph with loop invariant instructions moved out of loops.
:- pred rl_loop__shift_invariants(rl_opt_info::in, rl_opt_info::out) is det.

%-----------------------------------------------------------------------------%
:- implementation.

:- import_module rl.
:- import_module assoc_list, bimap, bool, int, list, map, queue, relation.
:- import_module require, set, std_util.

rl_loop__shift_invariants -->
	rl_opt_info_get_loops(Loops0),
	% Do the outer loops first, so that we move instructions as far
	% as they can go in one jump. If a variable is an invariant of
	% an outer loop, it must be invariant in any inner loops of that
	% outer loop.
	{ CompareLoops = 
	    lambda([Loop1::in, Loop2::in, Compare::out] is det, (
	    	Loop1 = loop(_, Nodes1),
		Loop2 = loop(_, Nodes2),
		set__to_sorted_list(Nodes1, Nodes1List),
		list__length(Nodes1List, Size1),
		set__to_sorted_list(Nodes2, Nodes2List),
		list__length(Nodes2List, Size2),
		% Sort in descending order on size.
		compare(Compare, Size2, Size1)
	    )) },
	{ list__sort(CompareLoops, Loops0, SortedLoops) },
	rl_loop__shift_invariants_2(SortedLoops, [], Loops),
	rl_opt_info_set_loops(Loops).

:- pred rl_loop__shift_invariants_2(list(loop)::in, list(loop)::in,
		list(loop)::out, rl_opt_info::in, rl_opt_info::out) is det.

rl_loop__shift_invariants_2([], Loops, Loops) --> [].
rl_loop__shift_invariants_2([Loop0 | LoopsToProcess0], 
		ProcessedLoops0, ProcessedLoops) --> 
	{ Loop0 = loop(EntryNode, Nodes) },

		% We can only remove invariant computation if it is performed
		% on every execution of the loop, i.e. the block containing
		% it dominates all the exit points.
	rl_opt_info_get_flow_graph(FlowGraph),
	{ IsExitPoint = 
		lambda([Node::in] is semidet, (
			relation__lookup_element(FlowGraph, Node, NodeKey),
			relation__lookup_from(FlowGraph, NodeKey,
				CalledNodeKeys),
			set__member(CalledNodeKey, CalledNodeKeys),
			relation__lookup_key(FlowGraph,
				CalledNodeKey, CalledNode),
			\+ set__member(CalledNode, Nodes)
		)) },
	{ set__to_sorted_list(Nodes, NodeList) },
	{ list__filter(IsExitPoint, NodeList, ExitNodes) },
	rl_opt_info_get_dominator_info(DominatorInfo), 
	{
		ExitNodes = [ExitNode | ExitNodes1],
		map__lookup(DominatorInfo, ExitNode, ExitDominatingNodes0),
		IntersectDominators =
		    lambda([N::in, Inter0::in, Inter::out] is det, (
		    	map__lookup(DominatorInfo, N, DominatingNodes1),
			set__intersect(Inter0, DominatingNodes1, Inter)
		    )),
		list__foldl(IntersectDominators, ExitNodes1, 
			ExitDominatingNodes0, ExitDominatingNodes)
	;
		ExitNodes = [],
		error("rl_loop__shift_invariants: loop has no exit nodes")
	},

	{ set__init(OneBlockRels0) },
	{ set__init(ManyBlockRels0) },

	% Pick out relations which are only used in one block within the
	% loop. Since rl_gen.m only generates loops with one block,
	% this isn't a problem.
	rl_loop__get_nonlocal_rels(NodeList, OneBlockRels0, 
		_OneBlockRels, ManyBlockRels0, ManyBlockRels),

	rl_loop__shift_invariants_loop(NodeList, NodeList, ExitDominatingNodes,
		ManyBlockRels, _, no, MaybeHeader),
	rl_loop__maybe_add_header(MaybeHeader, Nodes, EntryNode, 
		ProcessedLoops0, ProcessedLoops1, 
		LoopsToProcess0, LoopsToProcess),
	rl_loop__shift_invariants_2(LoopsToProcess, 
		[Loop0 | ProcessedLoops1], ProcessedLoops).

%-----------------------------------------------------------------------------%

	% Given a list of blocks, pick out the relations that occur
	% in more than one of the blocks.
:- pred rl_loop__get_nonlocal_rels(list(block_id)::in,
	set(relation_id)::in, set(relation_id)::out, set(relation_id)::in,
	set(relation_id)::out, rl_opt_info::in, rl_opt_info::out) is det.

rl_loop__get_nonlocal_rels([], OneBlock, OneBlock,
		ManyBlock, ManyBlock) --> [].
rl_loop__get_nonlocal_rels([Block | Blocks], OneBlock0, OneBlock, 
		ManyBlock0, ManyBlock) -->
	rl_opt_info_get_block(Block, block(_, Instrs, _, _)),
	{ GetInstrRelations = 
		lambda([Instr::in, Rels0::in, Rels::out] is det, (
			rl__instr_relations(Instr, Inputs, Outputs),
			set__insert_list(Rels0, Inputs, Rels1),
			set__insert_list(Rels1, Outputs, Rels)
		)) },
	{ set__init(BlockRels0) },
	{ list__foldl(GetInstrRelations, Instrs, BlockRels0, BlockRels1) },

	{ Update =
	    lambda([Rel::in, Changed0::in, Changed::out] is det, (
		Changed0 = One0 - Many0,
		( set__member(Rel, Many0) ->
			Many = Many0,
			One = One0
		; set__member(Rel, One0) ->
			set__delete(One0, Rel, One),
			set__insert(Many0, Rel, Many)
		;
			set__insert(One0, Rel, One),
			Many = Many0
		),
		Changed = One - Many
	    )) },
	{ set__to_sorted_list(BlockRels1, BlockRels) },
	{ list__foldl(Update, BlockRels,
		OneBlock0 - ManyBlock0, OneBlock1 - ManyBlock1) },
	rl_loop__get_nonlocal_rels(Blocks, OneBlock1, OneBlock, 
		ManyBlock1, ManyBlock).

%-----------------------------------------------------------------------------%

:- pred rl_loop__shift_invariants_loop(list(block_id)::in, list(block_id)::in,
	set(block_id)::in, set(relation_id)::in, set(relation_id)::out,
	maybe(block)::in, maybe(block)::out, rl_opt_info::in,
	rl_opt_info::out) is det.

rl_loop__shift_invariants_loop([], _, _, N, N, H, H) --> [].
rl_loop__shift_invariants_loop([LoopNode | LoopNodes], AllLoopNodes,
		ExitDominatingNodes, NonLocalRels0, NonLocalRels,
		MaybeHeader0, MaybeHeader) -->
	rl_opt_info_get_block(LoopNode, Block0),
	{ Block0 = block(Label, Instrs0, Branch, BlockInfo) },
	{ GetChangedRels = 
		lambda([Instr::in, Rels0::in, Rels::out] is det, (
			rl__instr_relations(Instr, _, Outputs),
			set__insert_list(Rels0, Outputs, Rels)
		)) },
	{ set__init(BlockChangedRels0) },
	{ list__foldl(GetChangedRels, Instrs0, 
		BlockChangedRels0, BlockChangedRels) },
	(
		% Can't shift invariants if this block doesn't dominate
		% all the exits to the loop (i.e. isn't executed on every
		% pass through the loop).
		{ set__member(LoopNode, ExitDominatingNodes) },

		% Don't bother processing the block if it doesn't contain
		% any relations which are changed in only one block within
		% the loop.

		{ set__difference(BlockChangedRels, NonLocalRels0, 
			ThisBlockRels) },
		{ \+ set__empty(ThisBlockRels) }
	->
		{ set__init(SeenRels) },
		{ set__init(ShiftedRels0) },
		rl_loop__shift_invariants_block(Instrs0, Instrs, LoopNode,
			AllLoopNodes, ExitDominatingNodes, BlockChangedRels,
			NonLocalRels0, NonLocalRels1, SeenRels, 
			ShiftedRels0, _ShiftedRels, MaybeHeader0, MaybeHeader1),
		rl_opt_info_set_block(LoopNode,
			block(Label, Instrs, Branch, BlockInfo))
	;
		{ MaybeHeader1 = MaybeHeader0 },
		{ NonLocalRels1 = NonLocalRels0 }
	),
	rl_loop__shift_invariants_loop(LoopNodes, AllLoopNodes,
		ExitDominatingNodes, NonLocalRels1, NonLocalRels,
		MaybeHeader1, MaybeHeader).

%-----------------------------------------------------------------------------%

:- pred rl_loop__shift_invariants_block(list(rl_instruction)::in, 
	list(rl_instruction)::out, block_id::in, list(block_id)::in,
	set(block_id)::in, set(relation_id)::in, set(relation_id)::in, 
	set(relation_id)::out, set(relation_id)::in, 
	set(relation_id)::in, set(relation_id)::out, maybe(block)::in,
	maybe(block)::out, rl_opt_info::in, rl_opt_info::out) is det.

rl_loop__shift_invariants_block([], [], _, _, _, _, NL, NL, _, 
		S, S, H, H) --> [].
rl_loop__shift_invariants_block([Instr | Instrs0], Instrs, LoopNode,
		AllLoopNodes, ExitDominatingNodes, BlockChangedRels,
		NonLocalRels0, NonLocalRels, SeenRels0, 
		ShiftedRels0, ShiftedRels, MaybeHeader0, MaybeHeader) -->
	{ rl__instr_relations(Instr, InputRels, OutputRels) },
	(
		%
		% Check if this instruction is invariant.
		%

		{ \+ rl_loop__unmovable(Instr) },

		% Are all the inputs produced outside the loop.
		{ \+ (
			list__member(InputRel, InputRels),
			\+ set__member(InputRel, ShiftedRels0),
			( set__member(InputRel, BlockChangedRels)
			; set__member(InputRel, NonLocalRels0)
			; set__member(InputRel, SeenRels0)
			)
		) },

		% Is there no use of one of the output relations within
		% the block which could be reached by some other definition 
		% of the output.
		{ \+ (
			list__member(OutputRel, OutputRels),
			set__member(OutputRel, SeenRels0)
		) },
	
		% Check that the outputs of this instruction are the only 
		% values of those output relations used by other instructions
		% within this block.
		{ set__list_to_set(OutputRels, OutputRelSet) },
		{ rl_loop__check_later_uses_in_block(OutputRelSet, 
			Instrs0, Instrs1) }
	->
		{ rl_loop__add_instruction_to_pre_header(MaybeHeader0, 
			Instr, MaybeHeader1) },
		{ set__insert_list(SeenRels0, InputRels, SeenRels1) },
		{ set__insert_list(SeenRels1, OutputRels, SeenRels) },
		{ set__insert_list(ShiftedRels0, InputRels, ShiftedRels1) },
		{ set__union(ShiftedRels1, OutputRelSet, ShiftedRels2) }, 
		rl_loop__shift_invariants_block(Instrs1, Instrs, LoopNode, 
			AllLoopNodes, ExitDominatingNodes, BlockChangedRels,
			NonLocalRels0, NonLocalRels, SeenRels, 
			ShiftedRels2, ShiftedRels, MaybeHeader1, MaybeHeader)
	;
		{ set__insert_list(SeenRels0, InputRels, SeenRels1) },
		{ set__insert_list(SeenRels1, OutputRels, SeenRels) },
		rl_loop__shift_invariants_block(Instrs0, Instrs1, LoopNode, 
			AllLoopNodes, ExitDominatingNodes, BlockChangedRels,
			NonLocalRels0, NonLocalRels, SeenRels, 
			ShiftedRels0, ShiftedRels, MaybeHeader0, MaybeHeader),
		{ Instrs = [Instr | Instrs1] }
	).

%-----------------------------------------------------------------------------%

:- pred rl_loop__unmovable(rl_instruction::in) is semidet.

rl_loop__unmovable(label(_) - _).
rl_loop__unmovable(goto(_) - _).
rl_loop__unmovable(conditional_goto(_, _) - _).
rl_loop__unmovable(clear(_) - _).
rl_loop__unmovable(unset(_) - _).

%-----------------------------------------------------------------------------%

	% Check that all instructions in the current block that 
	% use any of the outputs of the instruction we are trying to
	% move only use the version produced by that instruction.
:- pred rl_loop__check_later_uses_in_block(set(relation_id)::in, 
	list(rl_instruction)::in, list(rl_instruction)::out) is semidet.	

rl_loop__check_later_uses_in_block(_, [], []).
rl_loop__check_later_uses_in_block(OutputRels, [Instr | Instrs0], Instrs) :-
	rl__instr_relations(Instr, _, InstrOutputs),
	set__list_to_set(InstrOutputs, InstrOutputSet),
	set__intersect(OutputRels, InstrOutputSet, RedefinedRels),
	set__empty(RedefinedRels),
	rl_loop__check_later_uses_in_block(OutputRels, Instrs0, Instrs1),
	Instrs = [Instr | Instrs1].

%-----------------------------------------------------------------------------%

:- pred rl_loop__add_instruction_to_pre_header(maybe(block)::in, 
		rl_instruction::in, maybe(block)::out) is det.

rl_loop__add_instruction_to_pre_header(no, Instr, yes(Block)) :-
	block_info_init(BlockInfo),
	Block = block(no, [Instr], no, BlockInfo).
rl_loop__add_instruction_to_pre_header(yes(Block0), Instr, yes(Block)) :-
	Block0 = block(Label, RevInstrs0, Branch, BlockInfo),
	RevInstrs = [Instr | RevInstrs0],
	Block = block(Label, RevInstrs, Branch, BlockInfo).
	
%-----------------------------------------------------------------------------%

:- pred rl_loop__maybe_add_header(maybe(block)::in, set(block_id)::in,
	block_id::in, list(loop)::in, list(loop)::out, list(loop)::in,
	list(loop)::out, rl_opt_info::in, rl_opt_info::out) is det.

rl_loop__maybe_add_header(no, _, _, P, P, L, L) --> [].
rl_loop__maybe_add_header(yes(HeaderBlock), LoopNodes, EntryNode, 
		ProcessedLoops0, ProcessedLoops, 
		LoopsToProcess0, LoopsToProcess) -->
	rl_opt_info_get_new_label(HeaderLabel, HeaderBlockId),

	% Add a new block.
	{ HeaderBlock = block(Label, RevInstrs, Branch, BlockInfo) },
	{ list__reverse(RevInstrs, Instrs) },
	{ HeaderBlock1 = block(Label, Instrs, Branch, BlockInfo) },
	rl_opt_info_set_block(HeaderBlockId, HeaderBlock1),
	rl_opt_info_get_block(EntryNode, EntryBlock),

	% Add the new block to the ordering.
	rl_opt_info_get_rev_block_order(RevOrder0),
	{ rl_loop__insert_into_order(RevOrder0, HeaderBlockId, 
		EntryNode, RevOrder) },
	rl_opt_info_set_rev_block_order(RevOrder),

	% Fix up the flow graph.
	rl_opt_info_get_flow_graph(FlowGraph0),
	{ relation__lookup_element(FlowGraph0, EntryNode, EntryKey) },
	{ relation__add_element(FlowGraph0, HeaderBlockId, 
		HeaderKey, FlowGraph1) },
	{ relation__add(FlowGraph1, HeaderKey, EntryKey, FlowGraph) },
	rl_opt_info_set_flow_graph(FlowGraph),

	% Add the new block to all loops containing the old header.
	( { EntryBlock = block(yes(EntryLabel), _, _, _) } ->
		{ UpdateLoop = 
		    lambda([L0::in, L::out] is det, (
			L0 = loop(LEntry0, LNodes0),
			( set__member(EntryNode, LNodes0) ->
				set__insert(LNodes0, HeaderBlockId, LNodes)
			;
				LNodes = LNodes0
			),
			L = loop(LEntry0, LNodes)
		    )) },
		{ list__map(UpdateLoop, ProcessedLoops0, ProcessedLoops) },
		{ list__map(UpdateLoop, LoopsToProcess0, LoopsToProcess) },
		rl_loop__update_gotos(LoopNodes, EntryLabel, HeaderLabel, 
			EntryNode, HeaderBlockId)
	;
		{ error("rl_loop__maybe_add_header: loop entry does not have a label") }
	).

%-----------------------------------------------------------------------------%

:- pred rl_loop__insert_into_order(list(block_id)::in, block_id::in,
		block_id::in, list(block_id)::out) is det.

rl_loop__insert_into_order([], _, _, _) :-
	error("rl_loop__insert_into_order").
rl_loop__insert_into_order([Block | RevOrder0], 
		HeaderBlockId, EntryNode, RevOrder) :-
	( Block = EntryNode ->
		RevOrder = [EntryNode, HeaderBlockId | RevOrder0]
	;
		rl_loop__insert_into_order(RevOrder0, HeaderBlockId, 
			EntryNode, RevOrder1),
		RevOrder = [Block | RevOrder1]
	).

%-----------------------------------------------------------------------------%

:- pred rl_loop__update_gotos(set(block_id)::in, label_id::in, label_id::in, 
	block_id::in, block_id::in, rl_opt_info::in, rl_opt_info::out) is det.

rl_loop__update_gotos(LoopNodes, OldLabel, NewLabel,
		OldEntryBlock, NewEntryBlock) -->

	% 
	% Fix up gotos to the loop entry from outside the loop.
	%
	rl_opt_info_get_block_map(BlockMap0),
	{ map__to_assoc_list(BlockMap0, BlockAL0) },
	{ UpdateBlock =
	   lambda([BlockIdAndBlock0::in, BlockIdAndBlock::out] is det, (
	   	BlockIdAndBlock0 = BlockId - block(A, B, MaybeBranch0, D),
		( set__member(BlockId, LoopNodes) ->
		    MaybeBranch = MaybeBranch0
		;
		    ( 
		    	MaybeBranch0 = yes(goto(OldLabel) - Comment) 
		    ->
			MaybeBranch = yes(goto(NewLabel) - Comment)
		    ; 
		    	MaybeBranch0 = 
				yes(conditional_goto(Cond, OldLabel) - Comment)
		    ->
			MaybeBranch = 
				yes(conditional_goto(Cond, NewLabel) - Comment)
		    ;
		        MaybeBranch = MaybeBranch0
		    )
		),
	   	BlockIdAndBlock = BlockId - block(A, B, MaybeBranch, D)
	    )) },
	{ list__map(UpdateBlock, BlockAL0, BlockAL) },
	{ map__from_assoc_list(BlockAL, BlockMap) },
	rl_opt_info_set_block_map(BlockMap),

	% 
	% Fix up the flow graph.
	%
	rl_opt_info_get_flow_graph(FlowGraph0),
	{ relation__lookup_element(FlowGraph0,
		OldEntryBlock, OldEntryBlockKey) },
	{ relation__lookup_element(FlowGraph0, 
		NewEntryBlock, NewEntryBlockKey) },
	{ relation__lookup_to(FlowGraph0, OldEntryBlockKey, 
		CallingBlockKeys) },
	{ set__to_sorted_list(LoopNodes, NodeList) },
	{ list__map(relation__lookup_element(FlowGraph0), 
		NodeList, NodeKeys) },
	{ set__delete_list(CallingBlockKeys, NodeKeys,
		OutsideLoopCallingBlocks) },
	{ set__to_sorted_list(OutsideLoopCallingBlocks, OutsideLoopBlocks) },
	{ list__length(OutsideLoopBlocks, NumBlocks) },
	{ list__duplicate(NumBlocks, OldEntryBlockKey, OldEntryList) },
	{ assoc_list__from_corresponding_lists(OutsideLoopBlocks, OldEntryList, 
		OldEntryAssocList) },	
	{ relation__remove_assoc_list(FlowGraph0, 
		OldEntryAssocList, FlowGraph1) },
	{ list__duplicate(NumBlocks, NewEntryBlockKey, NewEntryList) },
	{ assoc_list__from_corresponding_lists(OutsideLoopBlocks, NewEntryList, 
		NewEntryAssocList) },	
	{ relation__add_assoc_list(FlowGraph1, 
		NewEntryAssocList, FlowGraph) },
	rl_opt_info_set_flow_graph(FlowGraph).

%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
