/* ###
 * IP: GHIDRA
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "unionresolve.hh"
#include "funcdata.hh"

namespace ghidra {

/// The original parent must either be a union, a partial union, a structure with a single field,
/// an array with a single element, or a pointer to one of these data-types.
/// The object is set up initially to resolve to the parent.
/// \param parent is the original parent data-type
ResolvedUnion::ResolvedUnion(Datatype *parent)

{
  baseType = parent;
  if (baseType->getMetatype() == TYPE_PTR)
    baseType = ((TypePointer *)baseType)->getPtrTo();
  resolve = parent;
  fieldNum = -1;
  lock = false;
}

/// The original parent must be a union or structure.
/// \param parent is the original parent
/// \param fldNum is the index of the particular field to resolve to (or -1 to resolve to parent)
/// \param typegrp is a TypeFactory used to construct the resolved data-type of the field
ResolvedUnion::ResolvedUnion(Datatype *parent,int4 fldNum,TypeFactory &typegrp)

{
  if (parent->getMetatype() == TYPE_PARTIALUNION)
    parent = ((TypePartialUnion *)parent)->getParentUnion();
  baseType = parent;
  fieldNum = fldNum;
  lock = false;
  if (fldNum < 0)
    resolve = parent;
  else {
    if (parent->getMetatype() == TYPE_PTR) {
      TypePointer *pointer = (TypePointer *)parent;
      Datatype *field = pointer->getPtrTo()->getDepend(fldNum);
      resolve = typegrp.getTypePointer(parent->getSize(),field,pointer->getWordSize());
    }
    else
      resolve = parent->getDepend(fldNum);
  }
}

/// \param parent is a parent data-type that needs to be resolved
/// \param op is the PcodeOp reading/writing the \b parent data-type
/// \param slot is the slot (>=0 for input, -1 for output) accessing the \b parent
ResolveEdge::ResolveEdge(const Datatype *parent,const PcodeOp *op,int4 slot)

{
  opTime = op->getTime();
  encoding = slot;
  if (parent->getMetatype() == TYPE_PTR) {
    typeId = ((TypePointer *)parent)->getPtrTo()->getId();	// Strip pointer
    encoding += 0x1000;		// Encode the fact that a pointer is getting accessed
  }
  else if (parent->getMetatype() == TYPE_PARTIALUNION)
    typeId = ((TypePartialUnion *)parent)->getParentUnion()->getId();
  else
    typeId = parent->getId();
}

const int4 ScoreUnionFields::threshold = 256;
const int4 ScoreUnionFields::maxPasses = 6;
const int4 ScoreUnionFields::maxTrials = 1024;

/// If the \b op is adding a constant size or a multiple of a constant size to the given input slot, where the
/// size is at least as large as the union, return \b true.
/// \param op is the given PcodeOp
/// \param inslot is given input slot
/// \return \b true if \b op is doing array arithmetic with elements at least as large as the union
bool ScoreUnionFields::testArrayArithmetic(PcodeOp *op,int4 inslot)

{
  if (op->code() == CPUI_INT_ADD) {
    Varnode *vn = op->getIn(1 - inslot);
    if (vn->isConstant()) {
      if (vn->getOffset() >= result.baseType->getSize())
	return true;		// Array with union elements
    }
    else if (vn->isWritten()) {
      PcodeOp *multOp = vn->getDef();
      if (multOp->code() == CPUI_INT_MULT) {
	Varnode *vn2 = multOp->getIn(1);
	if (vn2->isConstant() && vn2->getOffset() >= result.baseType->getSize())
	  return true;// Array with union elements
      }
    }
  }
  else if (op->code() == CPUI_PTRADD) {
    Varnode *vn = op->getIn(2);
    if (vn->getOffset() >= result.baseType->getSize())
      return true;
  }
  return false;
}

/// Identify cases where we know the union shouldn't be resolved to a field.
/// \param op is the PcodeOp manipulating the union variable
/// \param inslot is -1 if the union is the output, >=0 if the union is an input to the op
/// \param parent is the parent union or pointer to union
/// \return \b true if the union should \e not be resolved to a field
bool ScoreUnionFields::testSimpleCases(PcodeOp *op,int4 inslot,Datatype *parent)

{
  if (op->isMarker())
    return true;		// Propagate raw union across MULTIEQUAL and INDIRECT
  if (parent->getMetatype() == TYPE_PTR) {
    if (inslot < 0)
      return true;		// Don't resolve pointers "up", there's only 1 possibility for assignment
    if (testArrayArithmetic(op, inslot))
      return true;
  }
  if (op->code() != CPUI_COPY)
    return false;		// A more complicated case
  if (inslot < 0)
    return false;		// Generally we don't want to propagate union backward thru COPY
  if (op->getOut()->isTypeLock())
    return false;		// Do the full scoring
  return true;			// Assume we don't have to extract a field if copying
}

/// A trial that encounters a locked data-type does not propagate through it but scores
/// the trial data-type against the locked data-type.
/// \param ct is the trial data-type
/// \param lockType is the locked data-type
/// \return the score
int4 ScoreUnionFields::scoreLockedType(Datatype *ct,Datatype *lockType)

{
  int score = 0;

  if (lockType == ct)
    score += 5;		// Perfect match

  while(ct->getMetatype() == TYPE_PTR) {
    if (lockType->getMetatype() != TYPE_PTR) break;
    score += 5;
    ct = ((TypePointer *)ct)->getPtrTo();
    lockType = ((TypePointer *)lockType)->getPtrTo();
  }

  type_metatype ctMeta = ct->getMetatype();
  type_metatype vnMeta = lockType->getMetatype();
  if (ctMeta == vnMeta) {
    if (ctMeta == TYPE_STRUCT || ctMeta == TYPE_UNION || ctMeta == TYPE_ARRAY || ctMeta == TYPE_CODE)
      score += 10;
    else
      score += 3;
  }
  else {
    if ((ctMeta == TYPE_INT && vnMeta == TYPE_UINT)||(ctMeta == TYPE_UINT && vnMeta == TYPE_INT))
      score -= 1;
    else
      score -= 5;
    if (ct->getSize() != lockType->getSize())
      score -= 2;
  }
  return score;
}

/// Look up the call-specs for the given CALL.  If the inputs are locked, find the corresponding
/// parameter and score the trial data-type against it.
/// \param ct is the trial data-type
/// \param callOp is the CALL
/// \param paramSlot is the input slot of the trial data-type
/// \return the score
int4 ScoreUnionFields::scoreParameter(Datatype *ct,const PcodeOp *callOp,int4 paramSlot)

{
  const Funcdata *fd = callOp->getParent()->getFuncdata();

  FuncCallSpecs *fc = fd->getCallSpecs(callOp);
  if (fc != (FuncCallSpecs *)0 && fc->isInputLocked() && fc->numParams() > paramSlot) {
    return scoreLockedType(ct,fc->getParam(paramSlot)->getType());
  }
  type_metatype meta = ct->getMetatype();
  if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE)
    return -1;		// Vaguely unlikely thing to pass as a param
  return 0;
}

/// Look up the call-specs for the given CALL.  If the output is locked,
/// score the trial data-type against it.
/// \param ct is the trial data-type
/// \param callOp is the CALL
/// \return the score
int4 ScoreUnionFields::scoreReturnType(Datatype *ct,const PcodeOp *callOp)

{
  const Funcdata *fd = callOp->getParent()->getFuncdata();

  FuncCallSpecs *fc = fd->getCallSpecs(callOp);
  if (fc != (FuncCallSpecs *)0 && fc->isOutputLocked()) {
    return scoreLockedType(ct,fc->getOutputType());
  }
  type_metatype meta = ct->getMetatype();
  if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE)
    return -1;		// Vaguely unlikely thing to return from a function
  return 0;
}

/// Test if the data-type is a pointer and if the pointed-to data-type is
/// compatible with the size of the value being loaded or stored. A \b score is
/// passed back for how closely the data-type fits this scenario, and if it
/// does we return the data-type of the pointer value.
/// \param ct is the trial data-type
/// \param vn is the Varnode holding the value being loaded or stored
/// \param score is used to pass back the score
/// \return the data-type of the value or null
Datatype *ScoreUnionFields::derefPointer(Datatype *ct,Varnode *vn,int4 &score)

{
  Datatype *resType = (Datatype *)0;
  score = 0;
  if (ct->getMetatype() == TYPE_PTR) {
    Datatype *ptrto = ((TypePointer *)ct)->getPtrTo();
    while(ptrto != (Datatype *)0 && ptrto->getSize() > vn->getSize()) {
      int8 newoff;
      ptrto = ptrto->getSubType(0, &newoff);
    }
    if (ptrto != (Datatype *)0 && ptrto->getSize() == vn->getSize()) {
      score = 10;
      resType = ptrto;
    }
  }
  else
    score = -10;
  return resType;
}

/// If the Varnode has already been visited, no new trials are created
/// \param vn is the given Varnode
/// \param ct is the data-type to associate with the trial
/// \param scoreIndex is the field index to score the trial against
/// \param isArray is \b true if the data-type to fit is a pointer to an array
void ScoreUnionFields::newTrialsDown(Varnode *vn,Datatype *ct,int4 scoreIndex,bool isArray)

{
  VisitMark mark(vn,scoreIndex);
  if (!visited.insert(mark).second)
    return;				// Already visited this Varnode
  if (vn->isTypeLock()) {
    scores[scoreIndex] += scoreLockedType(ct, vn->getType());
    return;				// Don't propagate through locked Varnode
  }
  list<PcodeOp*>::const_iterator piter;
  for(piter = vn->beginDescend();piter != vn->endDescend();++piter) {
    PcodeOp *op = *piter;
    trialNext.emplace_back(op,op->getSlot(vn),ct,scoreIndex,isArray);
  }
}

/// If the input slot is a Varnode that has already been visited, no new trial is created
/// \param op is the PcodeOp with the given slot
/// \param slot is the index of the given input slot
/// \param ct is the data-type to associate with the trial
/// \param scoreIndex is the field index to score the trial against
/// \param isArray is \b true if the data-type to fit is a pointer to an array
void ScoreUnionFields::newTrials(PcodeOp *op,int4 slot,Datatype *ct,int4 scoreIndex,bool isArray)

{
  Varnode *vn = op->getIn(slot);
  VisitMark mark(vn,scoreIndex);
  if (!visited.insert(mark).second)
    return;				// Already visited this Varnode
  if (vn->isTypeLock()) {
    scores[scoreIndex] += scoreLockedType(ct, vn->getType());
    return;				// Don't propagate through locked Varnode
  }
  trialNext.emplace_back(vn,ct,scoreIndex,isArray);	// Try to fit up
  list<PcodeOp *>::const_iterator iter;
  for(iter=vn->beginDescend();iter!=vn->endDescend();++iter) {
    PcodeOp *readOp = *iter;
    int4 inslot = readOp->getSlot(vn);
    if (readOp == op && inslot == slot)
      continue;			// Don't go down PcodeOp we came from
    trialNext.emplace_back(readOp,inslot,ct, scoreIndex,isArray);
  }
}

/// The trial's data-type is fitted to its PcodeOp as the incoming Varnode and a
/// score is computed and added to the score for the trial's union field.  The fitting may
/// produce a new data-type which indicates scoring for the trial recurses into the output.
/// This method builds trials for any new data-type unless \b lastLevel is \b true
/// Varnode of its PcodeOp.
/// \param trial is the given trial
/// \param lastLevel is \b true if the method should skip building new trials
void ScoreUnionFields::scoreTrialDown(const Trial &trial,bool lastLevel)

{
  if (trial.direction == Trial::fit_up)
    return;				// Trial doesn't push in this direction
  Datatype *resType = (Datatype *)0;	// Assume by default we don't propagate
  type_metatype meta = trial.fitType->getMetatype();
  int4 score = 0;
  switch(trial.op->code()) {
    case CPUI_COPY:
    case CPUI_MULTIEQUAL:
    case CPUI_INDIRECT:
      resType = trial.fitType;		// No score, but we can propagate
      break;
    case CPUI_LOAD:
      resType = derefPointer(trial.fitType, trial.op->getOut(), score);
      break;
    case CPUI_STORE:
      if (trial.inslot == 1) {
	Datatype *ptrto = derefPointer(trial.fitType,trial.op->getIn(2),score);
	if (ptrto != (Datatype*)0) {
	  if (!lastLevel)
	    newTrials(trial.op,2,ptrto,trial.scoreIndex,trial.array);	// Propagate to value being STOREd
	}
      }
      else if (trial.inslot == 2) {
	if (meta == TYPE_CODE)
	  score = -5;
	else
	  score = 1;
      }
     break;
    case CPUI_CBRANCH:
      if (meta == TYPE_BOOL)
	score = 10;
      else
	score = -10;
      break;
    case CPUI_BRANCHIND:
      if (meta == TYPE_PTR || meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION ||
	  meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else
	score = 1;
      break;
    case CPUI_CALL:
    case CPUI_CALLOTHER:
      if (trial.inslot > 0)
	score = scoreParameter(trial.fitType, trial.op, trial.inslot-1);
      break;
    case CPUI_CALLIND:
      if (trial.inslot == 0) {
	if (meta == TYPE_PTR) {
	  Datatype *ptrto = ((TypePointer*)trial.fitType)->getPtrTo();
	  if (ptrto->getMetatype() == TYPE_CODE) {
	    score = 10;
	  }
	  else {
	    score = -10;
	  }
	}
      }
      else {
	score = scoreParameter(trial.fitType, trial.op, trial.inslot-1);
      }
      break;
    case CPUI_RETURN:
      // We could check for locked return data-type
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE)
      	score = -1;
      break;
    case CPUI_INT_EQUAL:
    case CPUI_INT_NOTEQUAL:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
      	score = -1;
      else
	score = 1;
      break;
    case CPUI_INT_SLESS:
    case CPUI_INT_SLESSEQUAL:
    case CPUI_INT_SCARRY:
    case CPUI_INT_SBORROW:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else if (meta == TYPE_PTR || meta == TYPE_UNKNOWN || meta == TYPE_UINT || meta == TYPE_BOOL)
	score = -1;
      else
	score = 5;
      break;
    case CPUI_INT_LESS:
    case CPUI_INT_LESSEQUAL:
    case CPUI_INT_CARRY:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else if (meta == TYPE_PTR || meta == TYPE_UNKNOWN || meta == TYPE_UINT)
	score = 5;
      else if (meta == TYPE_INT)
	score = -5;
      break;
    case CPUI_INT_ZEXT:
      if (meta == TYPE_UINT)
	score = 2;
      else if (meta == TYPE_INT || meta == TYPE_BOOL)
	score = 1;
      else if (meta == TYPE_UNKNOWN)
	score = 0;
      else	// struct,union,ptr,array,code,float
	score = -5;
      break;
    case CPUI_INT_SEXT:
      if (meta == TYPE_INT)
	score = 2;
      else if (meta == TYPE_UINT || meta == TYPE_BOOL)
	score = 1;
      else if (meta == TYPE_UNKNOWN)
	score = 0;
      else	// struct,union,ptr,array,code,float
	score = -5;
      break;
    case CPUI_INT_ADD:
    case CPUI_INT_SUB:
    case CPUI_PTRSUB:
      if (meta == TYPE_PTR) {
	if (trial.inslot >= 0) {
	  Varnode *vn = trial.op->getIn(1-trial.inslot);
	  if (vn->isConstant()) {
	    TypePointer *baseType = (TypePointer *)trial.fitType;
	    int8 off = vn->getOffset();
	    int8 parOff;
	    TypePointer *par;
	    resType = baseType->downChain(off,par,parOff,trial.array,typegrp);
	    if (resType != (Datatype*)0)
	      score = 5;
	  }
	  else {
	    if (trial.array) {
	      score = 1;
	      int4 elSize = 1;
	      if (vn->isWritten()) {
		PcodeOp *multOp = vn->getDef();
		if (multOp->code() == CPUI_INT_MULT) {
		  Varnode *multVn = multOp->getIn(1);
		  if (multVn->isConstant())
		    elSize = (int4)multVn->getOffset();
		}
	      }
	      TypePointer *baseType = (TypePointer *)trial.fitType;
	      if (baseType->getPtrTo()->getAlignSize() == elSize) {
		score = 5;
		resType = trial.fitType;
	      }
	    }
	    else
	      score = 5;	// Indexing into something that is not an array
	  }
	}
      }
      else if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else
	score = 1;
      break;
    case CPUI_INT_2COMP:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else if (meta == TYPE_PTR || meta == TYPE_UNKNOWN || meta == TYPE_BOOL)
	score = -1;
      else if (meta == TYPE_INT)
	score = 5;
      break;
    case CPUI_INT_NEGATE:
    case CPUI_INT_XOR:
    case CPUI_INT_AND:
    case CPUI_INT_OR:
    case CPUI_POPCOUNT:
    case CPUI_LZCOUNT:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else if (meta == TYPE_PTR || meta == TYPE_BOOL)
	score = -1;
      else if (meta == TYPE_UINT || meta == TYPE_UNKNOWN)
	score = 2;
      break;
    case CPUI_INT_LEFT:
    case CPUI_INT_RIGHT:
      if (trial.inslot == 0) {
	if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	  score = -5;
	else if (meta == TYPE_PTR || meta == TYPE_BOOL)
	  score = -1;
	else if (meta == TYPE_UINT || meta == TYPE_UNKNOWN)
	  score = 2;
      }
      else {
	if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE ||
	    meta == TYPE_FLOAT || meta == TYPE_PTR)
	  score = -5;
	else
	  score = 1;
      }
      break;
    case CPUI_INT_SRIGHT:
      if (trial.inslot == 0) {
	if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	  score = -5;
	else if (meta == TYPE_PTR || meta == TYPE_BOOL || meta == TYPE_UINT || meta == TYPE_UNKNOWN)
	  score = -1;
	else
	  score = 2;
      }
      else {
	if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE ||
	    meta == TYPE_FLOAT || meta == TYPE_PTR)
	  score = -5;
	else
	  score = 1;
      }
      break;
    case CPUI_INT_MULT:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -10;
      else if (meta == TYPE_PTR || meta == TYPE_BOOL)
	score = -2;
      else
	score = 5;
      break;
    case CPUI_INT_DIV:
    case CPUI_INT_REM:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -10;
      else if (meta == TYPE_PTR || meta == TYPE_BOOL)
	score = -2;
      else if (meta == TYPE_UINT || meta == TYPE_UNKNOWN)
	score = 5;
      break;
    case CPUI_INT_SDIV:
    case CPUI_INT_SREM:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -10;
      else if (meta == TYPE_PTR || meta == TYPE_BOOL)
	score = -2;
      else if (meta == TYPE_INT)
	score = 5;
      break;
    case CPUI_BOOL_NEGATE:
    case CPUI_BOOL_AND:
    case CPUI_BOOL_XOR:
    case CPUI_BOOL_OR:
      if (meta == TYPE_BOOL)
	score = 10;
      else if (meta == TYPE_INT || meta == TYPE_UINT || meta == TYPE_UNKNOWN)
	score = -1;
      else
	score = -10;
      break;
    case CPUI_FLOAT_EQUAL:
    case CPUI_FLOAT_NOTEQUAL:
    case CPUI_FLOAT_LESS:
    case CPUI_FLOAT_LESSEQUAL:
    case CPUI_FLOAT_NAN:
    case CPUI_FLOAT_ADD:
    case CPUI_FLOAT_DIV:
    case CPUI_FLOAT_MULT:
    case CPUI_FLOAT_SUB:
    case CPUI_FLOAT_NEG:
    case CPUI_FLOAT_ABS:
    case CPUI_FLOAT_SQRT:
    case CPUI_FLOAT_FLOAT2FLOAT:
    case CPUI_FLOAT_TRUNC:
    case CPUI_FLOAT_CEIL:
    case CPUI_FLOAT_FLOOR:
    case CPUI_FLOAT_ROUND:
      if (meta == TYPE_FLOAT)
	score = 10;
      else
	score = -10;
      break;
    case CPUI_FLOAT_INT2FLOAT:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -10;
      else if (meta == TYPE_PTR)
	score = -5;
      else if (meta == TYPE_INT)
	score = 5;
      break;
    case CPUI_PIECE:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      break;
    case CPUI_SUBPIECE:
    {
      int4 offset = TypeOpSubpiece::computeByteOffsetForComposite(trial.op);
      resType = scoreTruncation(trial.fitType, trial.op->getOut(), offset, trial.scoreIndex);
      break;
    }
    case CPUI_PTRADD:
      if (meta == TYPE_PTR) {
	if (trial.inslot == 0) {
	  Datatype *ptrto = ((TypePointer *)trial.fitType)->getPtrTo();
	  if (ptrto->getAlignSize() == trial.op->getIn(2)->getOffset()) {
	    score = 10;
	    resType = trial.fitType;
	  }
	}
	else {
	  score = -10;
	}
      }
      else if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else
	score = 1;
      break;
    case CPUI_SEGMENTOP:
      if (trial.inslot == 2) {
	if (meta == TYPE_PTR)
	  score = 5;
	else if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	  score = -5;
	else
	  score = -1;
      }
      else {
	if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT ||
	    meta == TYPE_PTR)
	  score = -2;
      }
      break;
    default:
      score = -10;		// Doesn't fit
      break;
  }
  scores[trial.scoreIndex] += score;
  if (resType != (Datatype *)0 && !lastLevel)
    newTrialsDown(trial.op->getOut(), resType, trial.scoreIndex, trial.array);
}

void ScoreUnionFields::scoreTrialUp(const Trial &trial,bool lastLevel)

{
  if (trial.direction == Trial::fit_down)
    return;				// Trial doesn't push in this direction
  int score = 0;
  if (!trial.vn->isWritten()) {
    if (trial.vn->isConstant())
      scoreConstantFit(trial);
    return;		// Nothing to propagate up through
  }
  Datatype *resType = (Datatype *)0;	// Assume by default we don't propagate
  int4 newslot = 0;
  type_metatype meta = trial.fitType->getMetatype();
  PcodeOp *def = trial.vn->getDef();
  switch(def->code()) {
    case CPUI_COPY:
    case CPUI_MULTIEQUAL:
    case CPUI_INDIRECT:
      resType = trial.fitType;		// No score, but we can propagate
      newslot = 0;
      break;
    case CPUI_LOAD:
      resType = typegrp.getTypePointer(def->getIn(1)->getSize(),trial.fitType,1);
      newslot = 1;	// No score, but we can propagate
      break;
    case CPUI_CALL:
    case CPUI_CALLOTHER:
    case CPUI_CALLIND:
      score = scoreReturnType(trial.fitType, def);
      break;
    case CPUI_INT_EQUAL:
    case CPUI_INT_NOTEQUAL:
    case CPUI_INT_SLESS:
    case CPUI_INT_SLESSEQUAL:
    case CPUI_INT_SCARRY:
    case CPUI_INT_SBORROW:
    case CPUI_INT_LESS:
    case CPUI_INT_LESSEQUAL:
    case CPUI_INT_CARRY:
    case CPUI_BOOL_NEGATE:
    case CPUI_BOOL_AND:
    case CPUI_BOOL_XOR:
    case CPUI_BOOL_OR:
    case CPUI_FLOAT_EQUAL:
    case CPUI_FLOAT_NOTEQUAL:
    case CPUI_FLOAT_LESS:
    case CPUI_FLOAT_LESSEQUAL:
    case CPUI_FLOAT_NAN:
      if (meta == TYPE_BOOL)
	score = 10;
      else if (trial.fitType->getSize() == 1)
	score = 1;
      else
	score = -10;
      break;
    case CPUI_INT_ADD:
    case CPUI_INT_SUB:
    case CPUI_PTRSUB:
      if (meta == TYPE_PTR) {
	score = 5;	// Don't try to back up further
      }
      else if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else
	score = 1;
      break;
    case CPUI_INT_2COMP:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else if (meta == TYPE_PTR || meta == TYPE_UNKNOWN || meta == TYPE_BOOL)
	score = -1;
      else if (meta == TYPE_INT)
	score = 5;
      break;
    case CPUI_INT_NEGATE:
    case CPUI_INT_XOR:
    case CPUI_INT_AND:
    case CPUI_INT_OR:
    case CPUI_POPCOUNT:
    case CPUI_LZCOUNT:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else if (meta == TYPE_PTR || meta == TYPE_BOOL)
	score = -1;
      else if (meta == TYPE_UINT || meta == TYPE_UNKNOWN)
	score = 2;
      break;
    case CPUI_INT_LEFT:
    case CPUI_INT_RIGHT:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else if (meta == TYPE_PTR || meta == TYPE_BOOL)
	score = -1;
      else if (meta == TYPE_UINT || meta == TYPE_UNKNOWN)
	score = 2;
      break;
    case CPUI_INT_SRIGHT:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else if (meta == TYPE_PTR || meta == TYPE_BOOL || meta == TYPE_UINT || meta == TYPE_UNKNOWN)
	score = -1;
      else
	score = 2;
      break;
    case CPUI_INT_MULT:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -10;
      else if (meta == TYPE_PTR || meta == TYPE_BOOL)
	score = -2;
      else
	score = 5;
      break;
    case CPUI_INT_DIV:
    case CPUI_INT_REM:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -10;
      else if (meta == TYPE_PTR || meta == TYPE_BOOL)
	score = -2;
      else if (meta == TYPE_UINT || meta == TYPE_UNKNOWN)
	score = 5;
      break;
    case CPUI_INT_SDIV:
    case CPUI_INT_SREM:
      if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -10;
      else if (meta == TYPE_PTR || meta == TYPE_BOOL)
	score = -2;
      else if (meta == TYPE_INT)
	score = 5;
      break;
    case CPUI_FLOAT_ADD:
    case CPUI_FLOAT_DIV:
    case CPUI_FLOAT_MULT:
    case CPUI_FLOAT_SUB:
    case CPUI_FLOAT_NEG:
    case CPUI_FLOAT_ABS:
    case CPUI_FLOAT_SQRT:
    case CPUI_FLOAT_FLOAT2FLOAT:
    case CPUI_FLOAT_CEIL:
    case CPUI_FLOAT_FLOOR:
    case CPUI_FLOAT_ROUND:
    case CPUI_FLOAT_INT2FLOAT:
      if (meta == TYPE_FLOAT)
	score = 10;
      else
	score = -10;
      break;
    case CPUI_FLOAT_TRUNC:
      if (meta == TYPE_INT || meta == TYPE_UINT)
	score = 2;
      else
	score = -2;
      break;
    case CPUI_PIECE:
      if (meta == TYPE_FLOAT || meta == TYPE_BOOL)
	score = -5;
      else if (meta == TYPE_CODE || meta == TYPE_PTR)
	score = -2;
      break;
    case CPUI_SUBPIECE:
      if (meta == TYPE_INT || meta == TYPE_UINT || meta == TYPE_BOOL) {
	if (def->getIn(1)->getOffset() == 0)
	  score = 3;		// Likely truncation
	else
	  score = 1;
      }
      else
	score = -5;
      break;
    case CPUI_PTRADD:
      if (meta == TYPE_PTR) {
	Datatype *ptrto = ((TypePointer *)trial.fitType)->getPtrTo();
	if (ptrto->getAlignSize() == def->getIn(2)->getOffset())
	  score = 10;
	else
	  score = 2;
      }
      else if (meta == TYPE_ARRAY || meta == TYPE_STRUCT || meta == TYPE_UNION || meta == TYPE_CODE || meta == TYPE_FLOAT)
	score = -5;
      else
	score = 1;
      break;
    default:
      score = -10;	// Datatype doesn't fit
      break;
  }
  scores[trial.scoreIndex] += score;
  if (resType != (Datatype *)0 && !lastLevel) {
    newTrials(def, newslot, resType, trial.scoreIndex, trial.array);
  }
}

/// The truncation may be an explicit CPUI_SUBPIECE, or it may be implied.
/// A score is computed for fitting a given data-type to the truncation, and a possible
/// data-type to recurse is also computed.
/// \param ct is the given data-type to truncate
/// \param vn is the Varnode the truncation will fit into
/// \param offset is the number of bytes truncated off the start of the data-type
/// \param scoreIndex is the field being scored
/// \return the data-type to recurse or null
Datatype *ScoreUnionFields::scoreTruncation(Datatype *ct,Varnode *vn,int4 offset,int4 scoreIndex)

{
  int4 score;
  if (ct->getMetatype() == TYPE_UNION) {
    TypeUnion *unionDt = (TypeUnion *)ct;
    ct = (Datatype *)0;			// Don't recurse a data-type from truncation of a union
    score = -10;			// Negative score if the union has no field matching the size
    int4 num = unionDt->numDepend();
    for(int4 i=0;i<num;++i) {
      const TypeField *field = unionDt->getField(i);
      if (field->offset == offset && field->type->getSize() == vn->getSize()) {
	score = 10;
	if (result.getBase() == unionDt)
	  score += 5;
	break;
      }
    }
  }
  else {
    score = 10;		// If we can find a size match for the truncation
    int8 curOff = offset;
    while(ct != (Datatype*)0 && (curOff != 0 || ct->getSize() != vn->getSize())) {
      if (ct->getMetatype() == TYPE_INT || ct->getMetatype() == TYPE_UINT) {
	if (ct->getSize() >= vn->getSize() + curOff) {
	  score = 1;	// Size doesn't match, but still possibly a reasonable operation
	  break;
	}
      }
      ct = ct->getSubType(curOff,&curOff);
    }
    if (ct == (Datatype *)0)
      score = -10;
  }
  scores[scoreIndex] += score;
  return ct;
 }

/// Assume the constant has no data-type of its own to match against.
/// Evaluate if the constant looks like an integer or pointer etc. and score the trial data-type against that.
/// \param trial is the trial of the constant Varnode
void ScoreUnionFields::scoreConstantFit(const Trial &trial)

{
  int4 size = trial.vn->getSize();
  uintb val = trial.vn->getOffset();
  type_metatype meta = trial.fitType->getMetatype();
  int4 score = 0;
  if (meta == TYPE_BOOL) {
    score = (size == 1 && val < 2) ? 2 : -2;
  }
  else if (meta == TYPE_FLOAT) {
    score = -1;
    const FloatFormat *format = typegrp.getArch()->translate->getFloatFormat(size);
    if (format != (const FloatFormat *)0) {
      int4 exp = format->extractExponentCode(val);
      if (exp < 7 && exp > -4)		// Check for common exponent range
	score = 2;
    }
  }
  else if (meta == TYPE_INT || meta == TYPE_UINT || meta == TYPE_PTR) {
    if (val == 0) {
      score = 2;	// Zero is equally valid as pointer or integer
    }
    else {
      AddrSpace *spc = typegrp.getArch()->getDefaultDataSpace();
      bool looksLikePointer = false;
      if (val >= spc->getPointerLowerBound() && val <= spc->getPointerUpperBound()) {
	if (bit_transitions(val,size) >= 3) {
	  looksLikePointer = true;
	}
      }
      if (meta == TYPE_PTR) {
	score = looksLikePointer ? 2 : -2;
      }
      else {
	score = looksLikePointer ? 1 : 2;
      }
    }
  }
  else
    score = -2;
  scores[trial.scoreIndex] += score;
}

/// Run through each trial in the current list and compute a score.  If the trial recurses and this is
/// \e not the final pass, build new trials for the recursion.
/// \param lastPass is \b true if this is the last pass
void ScoreUnionFields::runOneLevel(bool lastPass)

{
  list<Trial>::const_iterator iter;
  for(iter=trialCurrent.begin();iter!=trialCurrent.end();++iter) {
    trialCount += 1;
    if (trialCount > maxTrials)
      return;				// Absolute number of trials reached
    const Trial &trial(*iter);
    scoreTrialDown(trial,lastPass);
    scoreTrialUp(trial,lastPass);
  }
}

void ScoreUnionFields::computeBestIndex(void)

{
  int4 bestScore = scores[0];
  int4 bestIndex = 0;
  for(int4 i=1;i<scores.size();++i) {
    if (scores[i] > bestScore) {
      bestScore = scores[i];
      bestIndex = i;
    }
  }
  result.fieldNum = bestIndex - 1;	// Renormalize score index to field index
  result.resolve = fields[bestIndex];
}

/// Try to fit each possible field over multiple levels of the data-flow.
/// Return the index of the highest scoring field or -1 if the union data-type
/// itself is the best fit.
void ScoreUnionFields::run(void)

{
  trialCount = 0;
  for(int4 pass=0;pass < maxPasses;++pass) {
    if (trialCurrent.empty())
      break;
    if (trialCount > threshold)
      break;				// Threshold reached, don't score any more trials
    if (pass + 1 == maxPasses)
      runOneLevel(true);
    else {
      runOneLevel(false);
      trialCurrent.swap(trialNext);
      trialNext.clear();
    }
  }
}

/// \brief Score a given data-type involving a union against data-flow
///
/// The data-type must either be a union or a pointer to union.
/// Set up the initial set of trials based on the given data-flow edge (PcodeOp and slot).
/// \param tgrp is the TypeFactory owning the data-types
/// \param parentType is the given data-type to score
/// \param op is PcodeOp of the given data-flow edge
/// \param slot is slot of the given data-flow edge
ScoreUnionFields::ScoreUnionFields(TypeFactory &tgrp,Datatype *parentType,PcodeOp *op,int4 slot)
  : typegrp(tgrp), result(parentType)
{
  if (testSimpleCases(op, slot, parentType))
    return;
  int4 wordSize = (parentType->getMetatype() == TYPE_PTR) ? ((TypePointer *)parentType)->getWordSize() : 0;
  int4 numFields = result.baseType->numDepend();
  scores.resize(numFields + 1,0);
  fields.resize(numFields + 1,(Datatype *)0);
  Varnode *vn;
  if (slot < 0) {
    vn = op->getOut();
    if (vn->getSize() != parentType->getSize())
      scores[0] -= 10;		// Data-type does not even match size of Varnode
    else
      trialCurrent.emplace_back(vn,parentType,0,false);
  }
  else {
    vn = op->getIn(slot);
    if (vn->getSize() != parentType->getSize())
      scores[0] -= 10;
    else
      trialCurrent.emplace_back(op,slot,parentType,0,false);
  }
  fields[0] = parentType;
  visited.insert(VisitMark(vn,0));
  for(int4 i=0;i<numFields;++i) {
    Datatype *fieldType = result.baseType->getDepend(i);
    bool isArray = false;
    if (wordSize != 0) {
      if (fieldType->getMetatype() == TYPE_ARRAY)
	isArray = true;
      fieldType = typegrp.getTypePointerStripArray(parentType->getSize(),fieldType,wordSize);
    }
    if (vn->getSize() != fieldType->getSize())
      scores[i+1] -= 10;	// Data-type does not even match size of Varnode, don't create trial
    else if (slot < 0) {
      trialCurrent.emplace_back(vn,fieldType,i+1,isArray);
    }
    else {
      trialCurrent.emplace_back(op,slot,fieldType,i+1,isArray);
    }
    fields[i+1] = fieldType;
    visited.insert(VisitMark(vn,i+1));
  }
  run();
  computeBestIndex();
}

/// \brief Score a union data-type against data-flow, where there is a SUBPIECE
///
/// A truncation is fit to each union field before doing the fit against data-flow.
/// Only fields that match the offset and the truncation size (of the SUBPIECE) are scored further.
/// If there is a good fit, the scoring for that field recurses into the given data-flow edge.
/// This is only used where there is a SUBPIECE and the base scoring indicates the whole union is
/// the best match for the input.
/// \param tgrp is the TypeFactory owning the data-types
/// \param unionType is the data-type to score, which must be a TypeUnion
/// \param offset is the given starting offset of the truncation
/// \param op is the SUBPIECE op
ScoreUnionFields::ScoreUnionFields(TypeFactory &tgrp,TypeUnion *unionType,int4 offset,PcodeOp *op)
  :typegrp(tgrp), result(unionType)
{
  Varnode *vn = op->getOut();
  int numFields = unionType->numDepend();
  scores.resize(numFields + 1, 0);
  fields.resize(numFields + 1, (Datatype *)0);
  fields[0] = unionType;
  scores[0] = -10;
  for(int4 i=0;i<numFields;++i) {
    const TypeField *unionField = unionType->getField(i);
    fields[i+1] = unionField->type;
    if (unionField->type->getSize() != vn->getSize() || unionField->offset != offset) {
      scores[i+1] = -10;
      continue;
    }
    newTrialsDown(vn, unionField->type, i+1, false);
  }
  trialCurrent.swap(trialNext);
  if (trialCurrent.size() > 1)
    run();
  computeBestIndex();
}

/// \brief Score a union data-type against data-flow, where there is an implied truncation
///
/// A truncation is fit to each union field before doing the fit against data-flow, starting with
/// the given PcodeOp and input slot.
/// \param tgrp is the TypeFactory owning the data-types
/// \param unionType is the data-type to score, which must be a TypeUnion
/// \param offset is the given starting offset of the truncation
/// \param op is the PcodeOp initially reading/writing the union
/// \param slot is the -1 if the op is writing, >= 0 if reading
ScoreUnionFields::ScoreUnionFields(TypeFactory &tgrp,TypeUnion *unionType,int4 offset,PcodeOp *op,int4 slot)
  :typegrp(tgrp), result(unionType)
{
  Varnode *vn = (slot < 0) ? op->getOut() : op->getIn(slot);
  int numFields = unionType->numDepend();
  scores.resize(numFields + 1, 0);
  fields.resize(numFields + 1, (Datatype *)0);
  fields[0] = unionType;
  scores[0] = -10;		// Assume the untruncated entire union is not a good fit
  for(int4 i=0;i<numFields;++i) {
    const TypeField *unionField = unionType->getField(i);
    fields[i+1] = unionField->type;
    // Score the implied truncation
    Datatype *ct = scoreTruncation(unionField->type,vn,offset-unionField->offset,i+1);
    if (ct != (Datatype *)0) {
      if (slot < 0)
	trialCurrent.emplace_back(vn,ct,i+1,false);		// Try to flow backward
      else
	trialCurrent.emplace_back(op,slot,ct,i+1,false);	// Flow downward
      visited.insert(VisitMark(vn,i+1));
    }
  }
  if (trialCurrent.size() > 1)
    run();
  computeBestIndex();
}

} // End namespace ghidra
