/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s): 
 */

#include "Fundamentals.h"
#include "ControlGraph.h"
#include "Primitives.h"
#include "GraphBuilder.h"
#include "Pool.h"
#include "cstubs.h"
#include "Parser.h"

ControlGraph& GraphBuilder::
parse(char *filename)
{
  if (freopen(filename, "r", stdin) == NULL)
	{
	  perror("");
	  exit(EXIT_FAILURE);
	}

  YYSTYPE yylval;
  int yychar;
  uint32 argCount = 0;
  while ((yychar = yylex(&yylval)) > 0)
	{
	  if ((yychar == PRIMITIVE) && (yylval.yyPrimitiveOperation == poArg_I))
		argCount++;
	}

  freopen(filename, "r", stdin);

  ValueKind* kinds = new(envPool) ValueKind[argCount];
  for (uint32 i = 0; i < argCount; i++)
	kinds[i] = vkInt;

  graph = new(envPool) ControlGraph(envPool, argCount, kinds, 1);

  predecessorEdge = 0;
  lastCondition = 0;
  currentNode = &graph->newControlNode();

  yyparse(this);

  // link to the end node.
  finishCurrentNode("__end__");
  currentNode->remove();

  if (!labelManager.used("__end__"))
	yyerror("no end to this method");

  // link the begin node to the start label.
  labelManager.resolve("start", graph->getBeginExtra().getSuccessorEdge());
  labelManager.define("__end__", graph->getEndNode());

  // Initialize the finalMemory in the EndNode.
  graph->getEndExtra().finalMemory.getInput().
	setVar(graph->getBeginExtra().initialMemory.getOutgoingEdge());

  // check for link errors
  if (varManager.hasUndefinedSymbols())
	undefinedSymbolError(varManager.undefinedSymbols());
  if (labelManager.hasUndefinedSymbols())
	undefinedSymbolError(labelManager.undefinedSymbols());

  return *graph;
}

void GraphBuilder::
aliasVariable(char *var, char* alias)
{
  assert(varManager.defined(var));
  varManager.define(alias, varManager.get(var));
}

char *GraphBuilder::
getPhiVariable(Vector<ConsumerOrConstant *>& vars, char *label)
{
  uint32 str_size = 0;
  char *phi_variable, *ptr;
  bool restore = false;
  ControlNode* saved_node;

  if (label)
	if (labelManager.defined(label))
	  {
		saved_node = currentNode;
		currentNode = &labelManager.get(label);
		restore = true;
	  }
	else
	  {
		fprintf(stderr, "error: Label %s is not yet defined.", label);
		exit(EXIT_FAILURE);
	  }

  for (uint32 i = 0; i < vars.size(); i++)
	{
	  checkInputArgument(vars, i, akVariable);
	  str_size += (strlen(vars[i]->getName()) + 1);
	}
  str_size += 3 /* phi */ + 1 /* '\0' */;
  phi_variable = (char *) malloc(str_size);
  ptr = phi_variable;
  sprintf(ptr, "phi"); ptr += 3;
  for (uint32 j = 0; j < vars.size(); j++)
	{
	  sprintf(ptr, "_%s", vars[j]->getName()); ptr += strlen(ptr);
	}

  if (!varManager.defined(phi_variable))
	{
	  buildPhiNode(vars, vkInt, phi_variable);
	}
  else
	{
	  DataNode& phi_producer = varManager.get(phi_variable);
	  DoublyLinkedList<PhiNode>& phiNodes = currentNode->getPhiNodes();
	  bool found = false;

	  for (DoublyLinkedList<PhiNode>::iterator it = phiNodes.begin(); !phiNodes.done(it);
		   it = phiNodes.advance(it))
		if (&phiNodes.get(it).getOutgoingEdge() == &phi_producer)
		  {
			found = true;
			break;
		  }
	  if (!found)
		{
		  fprintf(stderr, "error: the phi node at line %d was defined in another block.\n"
				  "       Try phi(arg, ...)@label\n", yylineno);
		  exit(EXIT_FAILURE);
		}
	}

  if (restore)
	currentNode = saved_node;

  return phi_variable;
}

void GraphBuilder::
undefinedSymbolError(Vector<char *>& symbols)
{
  uint32 count = symbols.size();
  fprintf(stderr, "error: undefined symbol%s: ", count > 1 ? "s" : "");
  for (uint32 i=0; i < count; i++)
	fprintf(stderr, "%s ", symbols[i]);
  fprintf(stderr, "\n");
  exit(EXIT_FAILURE);
}

void GraphBuilder::
checkInputArgument(Vector<ConsumerOrConstant *>& in, uint32 pos, uint8 kind)
{
  if (pos >= in.size())
	yyerror("wrong input argument count");
  else if (!((in[pos]->isConstant() ? akConstant : akVariable) & kind))
	yyerror("wrong argument kind");
}

void GraphBuilder::
checkOutputArgument(char *name)
{
  if (!name)
	yyerror("need an output argument");
}

void GraphBuilder::
connectConsumer(char *name, DataConsumer& consumer)
{
  varManager.resolve(name, consumer);
}

void GraphBuilder::
registerProducer(char *name, DataNode& producer)
{
  if (varManager.defined(name))
	{
	  char fmt[] = "duplicate variable definition %s";
	  char* error = (char *) malloc(strlen(fmt) - 2 + strlen(name) + 1);
	  sprintf(error, fmt, name);
	  yyerror(error);
	}
  varManager.define(name, producer);
}

void GraphBuilder::
buildConstPrimitive(Value& value, ValueKind constantKind, char *producer)
{
  PrimConst& primConst = *new(envPool) PrimConst(constantKind, value);
  registerProducer(producer, primConst.getOutgoingEdge());
  currentNode->addPrimitive(primConst);
}

void GraphBuilder::
buildArgPrimitive(ValueKind outputKind, ConsumerOrConstant& arg0, char *producer)
{
  ValueKind v = outputKind; v = vkInt;
  uint32 argNum = arg0.getName()[strlen(arg0.getName())-1] - '0';

  PrimArg& primArg = graph->getBeginExtra().arguments[argNum];
  char buffer[] = "arg0";
  buffer[3] = '0' + argNum;
  registerProducer(buffer, primArg.getOutgoingEdge());
  registerProducer(producer, primArg.getOutgoingEdge());
}

void GraphBuilder::
buildResultPrimitive(ValueKind kind, ConsumerOrConstant& arg0)
{
  ValueKind* kinds = new(envPool) ValueKind[1];
  kinds[0] = kind;
  ControlReturn& block = *new(envPool) ControlReturn(*currentNode, 1, kinds, envPool);
  connectConsumer(arg0.getName(), block.results[0].getInput());
  if (predecessorEdge)
	currentNode->addPredecessor(*predecessorEdge);
  predecessorEdge = &block.getSuccessorEdge();

  currentNode = &graph->newControlNode();
}

void GraphBuilder::
buildStorePrimitive(ValueKind /*outputKind*/, ConsumerOrConstant& /*arg0*/, ConsumerOrConstant& /*arg1*/)
{
}

void GraphBuilder::
buildGenericBinaryPrimitive(PrimitiveOperation op, ValueKind outputKind, 
							ConsumerOrConstant& arg0, ConsumerOrConstant& arg1, char *producer)
{
  PrimBinaryGeneric& prim = *new(envPool) PrimBinaryGeneric(op, outputKind, true);
  if (arg0.isConstant())
	prim.getInputs()[0].setConst(arg0.getValue().i);
  else
	connectConsumer(arg0.getName(), prim.getInputs()[0]);

  if (arg1.isConstant())
	prim.getInputs()[1].setConst(arg1.getValue().i);
  else
	connectConsumer(arg1.getName(), prim.getInputs()[1]);

  registerProducer(producer, prim.getOutgoingEdge());
  currentNode->addPrimitive(prim);
}

void GraphBuilder::
buildIfPrimitive(PrimitiveOperation op, DataNode& cond, char *branch_to_if_true)
{
  // finish the Block
  ControlIf& block = *new(envPool) ControlIf(*currentNode, Condition2(condLt + op - poIfLt), cond);
  if (predecessorEdge)
	currentNode->addPredecessor(*predecessorEdge);
  predecessorEdge = &block.getFalseIfEdge();
  labelManager.resolve(branch_to_if_true, block.getTrueIfEdge());

  currentNode = &graph->newControlNode();
}

void GraphBuilder::
finishCurrentNode(char *successor)
{
  if (!currentNode->getPrimitives().empty())
	{
	  ControlBlock& cb = *new(envPool) ControlBlock(*currentNode);
	  if (predecessorEdge)
		currentNode->addPredecessor(*predecessorEdge);
	  predecessorEdge = 0;
	  labelManager.resolve(successor, cb.getSuccessorEdge());
	  currentNode = &graph->newControlNode();
	}
  else
	{
	  if (predecessorEdge)
		labelManager.resolve(successor, *predecessorEdge);
	  predecessorEdge = 0;
	}
}

char *GraphBuilder::
getNewConditionName()
{
  static uint32 index = 0;
  static char buffer[4 + 3 + 1];
  sprintf(buffer, "cond%3.3d", index++);
  return buffer;
}

void GraphBuilder::
buildPhiNode(Vector<ConsumerOrConstant *>& in, ValueKind kind, char *producer)
{
  uint32 count = in.size();
  PhiNode& phiNode = *new(envPool) PhiNode(count, kind, envPool);
  DataConsumer& fakeConsumer = *new(envPool) DataConsumer();
  fakeConsumer.setNode(&phiNode);

  for (uint32 i = 0; i < count; i++)
	{
	  checkInputArgument(in, i, akVariable);
	  connectConsumer(in[i]->getName(), fakeConsumer);
	}
  registerProducer(producer, phiNode.getOutgoingEdge());
  currentNode->addPhiNode(phiNode);
}

void GraphBuilder::
buildPrimitive(PrimitiveOperation op, Vector<ConsumerOrConstant *>& in, char *out)
{
  switch(op)
	{
	case poConst_I:
	  checkInputArgument(in, 0, akConstant);
	  checkOutputArgument(out);
	  buildConstPrimitive(in[0]->getValue(), vkInt, out);
	  break;

	case poArg_I:
	  checkInputArgument(in, 0, akVariable);
	  checkOutputArgument(out);
	  buildArgPrimitive(vkInt, *in[0], out);
	  break;

	case poResult_I:
	  checkInputArgument(in, 0, akVariable);
	  buildResultPrimitive(vkInt, *in[0]);
	  break;

	case poSt_I:
	  checkInputArgument(in, 0, akVariable);
	  checkInputArgument(in, 1, akVariable);
	  buildStorePrimitive(vkInt, *in[0], *in[1]);
	  break;

	case poAdd_I:
	case poSub_I:
	case poMul_I:
	case poAnd_I:
	case poOr_I:
	case poXor_I:
	  checkInputArgument(in, 0, akAny);
	  checkInputArgument(in, 1, akAny);
	  checkOutputArgument(out);

	  buildGenericBinaryPrimitive(op, vkInt, *in[0], *in[1], out);
	  break;

	case poCmp_I:
	  checkInputArgument(in, 0, akAny);
	  checkInputArgument(in, 1, akAny);
	  lastCondition = (out) ? out : getNewConditionName();
	  buildGenericBinaryPrimitive(poCmp_I, vkCond, *in[0], *in[1], lastCondition);
	  break;

	case poIfLt:
	case poIfEq:
	case poIfLe:
	case poIfGt:
	case poIfNe:
	case poIfGe:
	  checkInputArgument(in, 0, akVariable); // label name or cond.
	  if (in.size() == 1)
		{
		  if (!varManager.exists(lastCondition))
			yyerror("no condition set for this control");
		  buildIfPrimitive(op, varManager.get(lastCondition), in[0]->getName());
		}
	  else
		{
		  checkInputArgument(in, 1, akVariable); // label name.
		  lastCondition = in[0]->getName();
		  buildIfPrimitive(op, varManager.get(lastCondition), in[1]->getName());		  
		}
	  break;

	case poPhi_I:
	  checkOutputArgument(out);
	  buildPhiNode(in, vkInt, out);
	  break;

	case nPrimitiveOperations: // bra
	  checkInputArgument(in, 0, akVariable); // label name.
	  finishCurrentNode(in[0]->getName());
	  break;

	default:
	  yyerror("unhandled primitive operation");
	  break;
	}
}

void GraphBuilder::
defineLabel(char *name)
{
  if (!currentNode->getPrimitives().empty())
	{
	  // there is an incoming edge
	  ControlBlock& cb = *new(envPool) ControlBlock(*currentNode);
	  if (predecessorEdge)
		currentNode->addPredecessor(*predecessorEdge);
	  predecessorEdge = &cb.getSuccessorEdge();
	  currentNode = &graph->newControlNode();
	}
  labelManager.define(name, *currentNode);
}
