/* NSC -- new scala compiler
 * Copyright 2005 LAMP/EPFL
 * @author  Martin Odersky
 */

// $Id: Linearizers.scala 18387 2009-07-24 15:28:37Z odersky $

package scala.tools.nsc
package backend
package icode;

import scala.tools.nsc.ast._;
import scala.collection.mutable.{Stack, HashSet, BitSet};

trait Linearizers { self: ICodes =>
  import opcodes._;

  abstract class Linearizer {
    def linearize(c: IMethod): List[BasicBlock];
    def linearizeAt(c: IMethod, start: BasicBlock): List[BasicBlock]
  }

  /** 
   * A simple linearizer which predicts all branches to 
   * take the 'success' branch and tries to schedule those
   * blocks immediately after the test. This is in sync with
   * how 'while' statements are translated (if the test is 
   * 'true', the loop continues).
   */
  class NormalLinearizer extends Linearizer with WorklistAlgorithm {
    type Elem = BasicBlock;

    val worklist: WList = new Stack();

    var blocks: List[BasicBlock] = Nil;
    
    def linearize(m: IMethod): List[BasicBlock] = {
      val b = m.code.startBlock;
      blocks = Nil;

      run {
        worklist pushAll (m.exh map (_.startBlock));
        worklist.push(b); 
      }

      blocks.reverse;
    }
    
    def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = {
      blocks = Nil
      worklist.clear()
      linearize(start)
    }

    /** Linearize another subtree and append it to the existing blocks. */
    def linearize(startBlock: BasicBlock): List[BasicBlock] = {
      //blocks = startBlock :: Nil;
      run( { worklist.push(startBlock); } );
      blocks.reverse;
    }

    def processElement(b: BasicBlock) = 
      if (b.size > 0) {
        add(b);
        b.lastInstruction match {
          case JUMP(whereto) => 
            add(whereto);
          case CJUMP(success, failure, _, _) =>
            add(success);
            add(failure);
          case CZJUMP(success, failure, _, _) =>
            add(success);
            add(failure);
          case SWITCH(_, labels) =>
            add(labels);
          case RETURN(_) => ();
          case THROW() =>   ();
        }
      }

    def dequeue: Elem = worklist.pop;

    /** 
     * Prepend b to the list, if not already scheduled. 
     * TODO: use better test than linear search
     */
    def add(b: BasicBlock) {
      if (blocks.contains(b))
        ()
      else {
        blocks = b :: blocks;
        worklist push b;
      }
    }

    def add(bs: List[BasicBlock]): Unit = bs foreach add;
  }

  /**
   * Linearize code using a depth first traversal.
   */
  class DepthFirstLinerizer extends Linearizer {
    var blocks: List[BasicBlock] = Nil;
    
    def linearize(m: IMethod): List[BasicBlock] = {
      blocks = Nil;

      dfs(m.code.startBlock);
      m.exh foreach (b => dfs(b.startBlock));

      blocks.reverse
    }
    
    def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = {
      blocks = Nil
      dfs(start)
      blocks.reverse
    }

    def dfs(b: BasicBlock): Unit = 
      if (b.size > 0 && add(b))
        b.successors foreach dfs;

    /** 
     * Prepend b to the list, if not already scheduled. 
     * TODO: use better test than linear search
     * @return Returns true if the block was added.
     */
    def add(b: BasicBlock): Boolean = 
      if (blocks.contains(b))
        false
      else {
        blocks = b :: blocks;
        true
      }
  }

  /**
   * Linearize code in reverse post order. In fact, it does
   * a post order traversal, prepending visited nodes to the list.
   * This way, it is constructed already in reverse post order.
   */
  class ReversePostOrderLinearizer extends Linearizer {
    var blocks: List[BasicBlock] = Nil;
    var visited: HashSet[BasicBlock] = new HashSet;
    val added: BitSet = new BitSet
    
    def linearize(m: IMethod): List[BasicBlock] = {
      blocks = Nil;
      visited.clear;
      added.clear;

      m.exh foreach (b => rpo(b.startBlock));
      rpo(m.code.startBlock);
      
      // if the start block has predecessors, it won't be the first one 
      // in the linearization, so we need to enforce it here
      if (m.code.startBlock.predecessors eq Nil)
        blocks
      else
        m.code.startBlock :: (blocks.filterNot(_ == m.code.startBlock))
    }

    def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = {
      blocks = Nil
      visited.clear
      added.clear
      
      rpo(start)
      blocks
    }
    
    def rpo(b: BasicBlock): Unit = 
      if (b.size > 0 && !(visited contains b)) {
        visited += b;
        b.successors foreach rpo;
        add(b);
      }

    /** 
     * Prepend b to the list, if not already scheduled. 
     * @return Returns true if the block was added.
     */
    def add(b: BasicBlock) = 
      if (!added(b.label)) {
        added += b.label
        blocks = b :: blocks;
      }
  }

  /** A 'dump' of the blocks in this method, which does not
   *  require any well-formedness of the basic blocks (like
   *  the last instruction being a jump).
   */
  class DumpLinearizer extends Linearizer {
    def linearize(m: IMethod): List[BasicBlock] =
      m.code.blocks.toList;
    
    def linearizeAt(m: IMethod, start: BasicBlock): List[BasicBlock] = {
      error("not implemented")
    }
  }
  
}