/* Copyright (C) 2021-2026 Free Software Foundation, Inc.
   Contributed by Oracle.

   This file is part of GNU Binutils.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include "config.h"
#include <stdlib.h>
#include <stdarg.h>

#include "util.h"
#include "Application.h"
#include "CallStack.h"
#include "Experiment.h"
#include "Exp_Layout.h"
#include "DataObject.h"
#include "DbeSession.h"
#include "MetricList.h"
#include "Function.h"
#include "Module.h"
#include "MemObject.h"
#include "DbeView.h"
#include "Metric.h"
#include "DataSpace.h"
#include "LoadObject.h"

#include "debug.h"
#include "ABS.h"

//char *DOBJ_UNSPECIFIED  = STXT("(Not identified by the compiler as a memory-referencing instruction)");
char *DOBJ_UNSPECIFIED  = STXT("(No type information)");
char *DOBJ_UNIDENTIFIED = STXT("(No identifying descriptor provided by the compiler)");
char *DOBJ_UNDETERMINED = STXT("(Not determined from the symbolic information provided by the compiler)");
char *DOBJ_ANON         = STXT("(Padding in structure)");

// run-time codes
//  ABS_UNSUPPORTED = 0x01, /* inappropriate HWC event type */
//  ABS_BLOCKED     = 0x02, /* runtime backtrack blocker reached */
//  ABS_INCOMPLETE  = 0x03, /* runtime backtrack limit reached */
//  ABS_REG_LOSS    = 0x04, /* address register contaminated */
//  ABS_INVALID_EA  = 0x05, /* invalid effective address value */

const char *ABS_RT_CODES[NUM_ABS_RT_CODES] = {
  "(OK)",
  "(Dataspace data not requested during data collection)",
  "(Backtracking was prevented by a jump or call instruction)",
  "(Backtracking did not find trigger PC)",
  "(Could not determine VA because registers changed after trigger instruction)",
  "(Memory-referencing instruction did not specify a valid VA)",
  "(UNKNOWN)"
};

// post-processing codes
//  ABS_NO_CTI_INFO = 0x10, /* no AnalyzerInfo for validation */
//  ABS_INFO_FAILED = 0x20, /* info failed to validate backtrack */
//  ABS_CTI_TARGET  = 0x30, /* CTI target invalidated backtrack */
char *DOBJ_UNASCERTAINABLE = STXT("(Module with trigger PC not compiled with -xhwcprof)");
char *DOBJ_UNVERIFIABLE    = STXT("(Backtracking failed to find a valid branch target)");
char *DOBJ_UNRESOLVABLE    = STXT("(Backtracking traversed a branch target)");

char *ABS_PP_CODES[NUM_ABS_PP_CODES] = {
  STXT ("(OK)"),
  DOBJ_UNASCERTAINABLE,
  DOBJ_UNVERIFIABLE,
  DOBJ_UNRESOLVABLE,
  STXT ("(<INTERNAL ERROR DURING POST-PROCESSING>)")
};

DataSpace::DataSpace (DbeView *_dbev, int /* _picked */)
{
  dbev = _dbev;
}

DataSpace::~DataSpace () { }

void
DataSpace::reset () { }

char *
DataSpace::status_str ()
{
  return NULL;
}

Histable *
DataSpace::get_hist_obj (Histable::Type type, DataView *dview, long i)
{
  DataObject *dobj = NULL;
  char *errcode = NTXT ("<internal error>");
  switch (type)
    {
    case Histable::DOBJECT:
      dobj = (DataObject*) dview->getObjValue (PROP_HWCDOBJ, i);
      if (dobj == NULL)
	{
	  Vaddr leafVA = (Vaddr) dview->getLongValue (PROP_VADDR, i);
	  unsigned rt_code = (unsigned) ABS_GET_RT_CODE (leafVA);
	  unsigned pp_code = (unsigned) ABS_GET_PP_CODE (leafVA);
	  if (leafVA < ABS_CODE_RANGE
	      && (pp_code || (rt_code && rt_code != ABS_REG_LOSS)))
	    {
	      if (rt_code >= NUM_ABS_RT_CODES)
		rt_code = NUM_ABS_RT_CODES - 1;
	      if (pp_code >= NUM_ABS_PP_CODES)
		pp_code = NUM_ABS_PP_CODES - 1;
	      if (rt_code)
		errcode = PTXT (ABS_RT_CODES[rt_code]);
	      else
		errcode = PTXT (ABS_PP_CODES[pp_code]);
	    }
	  else
	    {
	      // associate dataobject with event
	      int index;

	      // search for memop in Module infoList
	      void *cstack = dview->getObjValue (PROP_MSTACK, i);
	      Histable *leafPCObj = CallStack::getStackPC (cstack, 0);
	      DbeInstr *leafPC = NULL;
	      if (leafPCObj->get_type () == Histable::INSTR)
		leafPC = (DbeInstr*) leafPCObj;
	      else  // DBELINE
		leafPC = (DbeInstr*) leafPCObj->convertto (Histable::INSTR);
	      Function *func = leafPC->func;
	      uint64_t leafPC_offset = func->img_offset + leafPC->addr;
	      Module *mod = func->module;
	      uint32_t dtype_id = 0;
	      inst_info_t *info = NULL;
	      Vec_loop (inst_info_t*, mod->infoList, index, info)
	      {
		if (info->offset == leafPC_offset)
		  {
		    dtype_id = info->memop->datatype_id;
		    break;
		  }
	      }
	      dobj = mod->get_dobj (dtype_id);
	      if (dobj == NULL)
		{
		  // ensure dobj is determined
		  if (dtype_id == DataObject::UNSPECIFIED_ID)
		    errcode = PTXT (DOBJ_UNSPECIFIED);
		  else
		    errcode = PTXT (DOBJ_UNIDENTIFIED);
		}
	      else
		{
		  // determine associated master dataobject
		  if (!dobj->master && dobj->scope)
		    dobj->master = dbeSession->createMasterDataObject (dobj);
		  if (dobj->scope)
		    dobj = dobj->master;  // use associated master
		}
	    }
	  if (!dobj)
	    {
	      // if dobj is not set yet, supply a dobj for errcode
	      // search for a dobj with the same name
	      dobj = dbeSession->find_dobj_by_name (errcode);
	      if (dobj == NULL)
		{
		  // create new DataObject for unknown code
		  dobj = (DataObject*) dbeSession->createHistObject (Histable::DOBJECT);
		  dobj->size = 0;
		  dobj->offset = -1;
		  dobj->parent = dbeSession->get_Unknown_DataObject ();
		  dobj->set_dobjname (errcode, NULL); // dobj->parent must already be set
		}
	    }
	  dview->setObjValue (PROP_HWCDOBJ, i, dobj);
	}
      break;
    default:
      break;
    }
  return dobj;
}

Hist_data *
DataSpace::compute_metrics (MetricList *mlist, Histable::Type type,
			    Hist_data::Mode mode, Histable *sel_obj)
{
  int nmetrics = mlist->get_items ()->size ();
  int sort_ind = -1;
  Hist_data::HistItem *hi;
  int index;

  // reset event_data count for all datatypes
  Vector<LoadObject*> *lobjs = dbeSession->get_text_segments ();
  for (int i = 0, sz = lobjs ? lobjs->size () : -1; i < sz; i++)
    {
      LoadObject *lo = lobjs->fetch (i);
      Vector<Module*> *modules = lo->seg_modules;
      for (int j = 0, msize = modules ? modules->size () : -1; j < msize; j++)
	{
	  Module *mod = modules->fetch (j);
	  mod->reset_datatypes ();
	}
    }
  Hist_data *hist_data = new Hist_data (mlist, type, mode);

  // add each experiment, skipping disabled and broken experiments
  for (index = 0; index < dbeSession->nexps (); index++)
    {
      Experiment *exp = dbeSession->get_exp (index);
      if (exp->broken)
	continue;

      Collection_params *params = exp->get_params ();
      if (!params->xhw_mode)
	continue;

      char *expt_name = exp->get_expt_name ();
      char *base_name = strrchr (expt_name, '/');
      base_name = base_name ? base_name + 1 : expt_name;

      // Determine mapping of experiment HWC metrics to hist_data metric list
      int *xlate = new int[MAX_HWCOUNT];
      for (unsigned i = 0; i < MAX_HWCOUNT; i++)
	{
	  xlate[i] = -1;
	  if (params->hw_interval[i] > 0)
	    {
	      const char *ctr_name = params->hw_aux_name[i];
	      int mindex;
	      Metric *met;
	      Vec_loop (Metric*, mlist->get_items (), mindex, met)
	      {
		if (dbe_strcmp (met->get_cmd (), ctr_name) == 0)
		  xlate[i] = mindex;
	      }
	    }
	}

      //
      // Process hardware profiling data
      //
      DataView *dview = dbev->get_filtered_events (index, DATA_HWC);
      if (dview)
	{
	  DataDescriptor *ddscr = dview ->getDataDescriptor ();
	  if (ddscr->getProp (PROP_HWCDOBJ) == NULL)
	    {
	      PropDescr *prop = new PropDescr (PROP_HWCDOBJ, NTXT ("HWCDOBJ"));
	      prop->uname = NULL;
	      prop->vtype = TYPE_OBJ;
	      ddscr->addProperty (prop);
	    }
	}
      if (dview && dview->getSize () != 0)
	{
	  char *msg = NULL;
	  for (long i = 0; i < dview->getSize (); i++)
	    {
	      if (i % 5000 == 0)
		{
		  int percent = (int) (100.0 * i / dview->getSize ());
		  if (percent == 0 && msg == NULL)
		    msg = dbe_sprintf (GTXT ("Filtering HW Profile Address Data: %s"), base_name);
		  theApplication->set_progress (percent, (percent != 0) ? NULL : msg);
		}

	      uint32_t tag = dview->getIntValue (PROP_HWCTAG, i);
	      if (tag < 0 || tag >= MAX_HWCOUNT)
		continue;  // invalid HWC tag in the record; ignore it
	      int mHwcntr_idx = xlate[tag];
	      if (mHwcntr_idx < 0)
		continue;

	      Vaddr leafVA = (Vaddr) dview->getLongValue (PROP_VADDR, i);
	      if (leafVA == ABS_UNSUPPORTED)
		continue; // skip this record
	      Histable *obj = get_hist_obj (type, dview, i);
	      if (obj == NULL)
		continue;
	      uint64_t interval = dview->getLongValue (PROP_HWCINT, i);
	      if (HWCVAL_HAS_ERR (interval))
		continue;
	      if (mode == Hist_data::ALL)
		{ // data_objects
		  hi = hist_data->append_hist_item (obj);
		  hi->value[mHwcntr_idx].ll += interval;
		  for (DataObject *dobj = ((DataObject *) obj)->parent; dobj; dobj = dobj->parent)
		    {
		      hi = hist_data->append_hist_item (dobj);
		      hi->value[mHwcntr_idx].ll += interval;
		    }
		}
	      else if (mode == Hist_data::LAYOUT || mode == Hist_data::DETAIL)
		{ // data_single
		  {
		    // for data layout, insert elements that have no metrics yet
		    DataObject *tmpParent = ((DataObject *) obj)->parent;
		    if (tmpParent && tmpParent->get_typename ())
		      {
			// dobj is an aggregate element
			if (!hist_data->find_hist_item (tmpParent))
			  {
			    // parent not yet a member of hist_data
			    // supply parent's children with 0 values for layout
			    Vector<DataObject*> *elements = dbeSession->get_dobj_elements (tmpParent);
			    for (long eli = 0, sz = elements->size (); eli < sz; eli++)
			      {
				DataObject* element = elements->fetch (eli);
				assert (!hist_data->find_hist_item (element));
				hi = hist_data->append_hist_item (element);
			      }
			  }
		      }
		  }

		  // Same as for mode == Hist_data::ALL:
		  hi = hist_data->append_hist_item (obj);
		  hi->value[mHwcntr_idx].ll += interval;
		  for (DataObject *dobj = ((DataObject *) obj)->parent; dobj; dobj = dobj->parent)
		    {
		      hi = hist_data->append_hist_item (dobj);
		      hi->value[mHwcntr_idx].ll += interval;
		    }
		}
	      else if (mode == Hist_data::SELF)
		{ // used by dbeGetSummary()
		  if (obj == sel_obj)
		    {
		      hi = hist_data->append_hist_item (obj);
		      hi->value[mHwcntr_idx].ll += interval;
		    }
		  else
		    {
		      for (DataObject *dobj = ((DataObject *) obj)->parent; dobj; dobj = dobj->parent)
			{
			  if ((Histable*) dobj == sel_obj)
			    {
			      hi = hist_data->append_hist_item (dobj);
			      hi->value[mHwcntr_idx].ll += interval;
			      break;
			    }
			}
		    }
		}
	      // Update total
	      hist_data->total->value[mHwcntr_idx].ll += interval;
	    }
	  free (msg);
	  theApplication->set_progress (0, NTXT (""));
	}
      delete[] xlate;
    }

  // include a regular HistItem for <Total> -- for all DataObjects, and MemObjects
  DataObject *dtot = dbeSession->get_Total_DataObject ();
  if (mode == Hist_data::ALL || mode == Hist_data::DETAIL
      || mode == Hist_data::LAYOUT ||
      sel_obj == dtot)
    {
      hi = hist_data->append_hist_item (dtot);
      for (int mind = 0; mind < nmetrics; mind++)
	hi->value[mind] = hist_data->total->value[mind];
    }
  if (hist_data->get_status () != Hist_data::SUCCESS)
    return hist_data;
  theApplication->set_progress (0, GTXT ("Constructing Metrics"));

  // Determine by which metric to sort if any
  bool rev_sort = mlist->get_sort_rev ();

  // Compute static metrics: SIZES, ADDRESS.
  for (int mind = 0; mind < nmetrics; mind++)
    {
      Metric *mtr = mlist->get_items ()->fetch (mind);
      if (mlist->get_sort_ref_index () == mind)
	sort_ind = mind;
      else if (!mtr->is_visible () && !mtr->is_tvisible ()
	       && !mtr->is_pvisible ())
	continue;
      Metric::Type mtype = mtr->get_type ();
      if (mtype == Metric::SIZES)
	{
	  Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi)
	  {
	    Histable *h = mtr->get_comparable_obj (hi->obj);
	    hi->value[mind].tag = VT_LLONG;
	    hi->value[mind].ll = h ? h->get_size () : 0;
	  }
	}
      else if (mtype == Metric::ONAME
	       && (mode == Hist_data::SELF
		   || ((DataObject*) sel_obj == dbeSession->get_Total_DataObject ())))
	{
	  Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi)
	  {
	    hi->value[mind].tag = VT_OFFSET; // offset labels
	  }
	}
      else if (mtype == Metric::ADDRESS)
	{ // pseudo-address
	  Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi)
	  {
	    hi->value[mind].tag = VT_ADDRESS;
	    Histable *h = mtr->get_comparable_obj (hi->obj);
	    hi->value[mind].ll = h ? h->get_addr () : 0;
	  }
	  // force sort by offset // XXXX should visibility also be set?
	  if (mode == Hist_data::SELF)
	    { // used by dbeGetSummary()
	      sort_ind = mind;
	      //hist_data->metrics->fetch(mind)->set_visible(T);
	    }
	}
      else
	{
	  ValueTag vtype = mtr->get_vtype ();
	  switch (vtype)
	    {
	    case VT_ULLONG: // most Data-derived HWC metrics are VT_ULLONG
	      hist_data->total->value[mind].tag = vtype;
	      Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi)
	      {
		hi->value[mind].tag = vtype;
	      }
	      break;
	    case VT_DOUBLE:
	      {
		double prec = mtr->get_precision ();
		hist_data->total->value[mind].tag = vtype;
		hist_data->total->value[mind].d = hist_data->total->value[mind].ll / prec;
		Vec_loop (Hist_data::HistItem *, hist_data->hist_items, index, hi)
		{
		  hi->value[mind].tag = vtype;
		  hi->value[mind].d = hi->value[mind].ll / prec;
		}
		break;
	      }
	    default:
	      if (mtr->get_subtype () != Metric::STATIC)
		abort ();
	      break;
	    }
	}
    }
  hist_data->sort (sort_ind, rev_sort);
  hist_data->compute_minmax ();
  theApplication->set_progress (0, NTXT (""));
  return hist_data;
}


// generate annotated structure info for data_layout
// note: similar data traversal found in er_print_histogram::dump_detail()
Hist_data *
DataSpace::get_layout_data (Hist_data *sorted_data,
			    Vector<int> *marks, int /* _threshold */)
{
  Hist_data *data_items = NULL;
  Hist_data::HistItem *new_item;
  MetricList *mlist = new MetricList (sorted_data->get_metric_list ());
  int no_metrics = mlist->get_items ()->size ();
  int index, addr_index = -1, name_index = -1;
  Dprintf (DEBUG_DATAOBJ, NTXT ("DataSpace::get_layout_data(ALL)\n"));

  // Allocate a new Hist_data for the list, to be copied from the DataObect list
  data_items = new Hist_data (mlist, Histable::DOBJECT, Hist_data::MODL);
  data_items->set_status (sorted_data->get_status ());

  // suppress threshold setting
  // XXX this threshold should probably not be used
  sorted_data->set_threshold ((double) 75. / 100.0);
  TValue* all_empty = new TValue[no_metrics];
  memset (all_empty, 0, sizeof (TValue) * no_metrics);

  Metric *mitem;
  Vec_loop (Metric*, mlist->get_items (), index, mitem)
  {
    // new data items have same total as original items
    data_items->total->value[index] = sorted_data->total->value[index];
    // empty metric items need matching types
    all_empty[index].tag = mitem->get_vtype ();
    if (mitem->get_type () == Metric::ONAME) name_index = index;
    if (mitem->get_type () == Metric::ADDRESS) addr_index = index;
  }

  int64_t next_elem_offset = 0;
  for (long i = 0; i < sorted_data->size (); i++)
    {
      Hist_data::HistItem* ditem = sorted_data->fetch (i);
      DataObject* dobj = (DataObject*) (ditem->obj);
      if (!dobj->get_parent ())
	{ // doesn't have a parent; top level item
	  next_elem_offset = 0;
	  if (i > 0)
	    { // add a blank line as separator
	      // fixme xxxxx, is it really ok to create a DataObject just for this?
	      DataObject* empty = new DataObject ();
	      empty->size = 0;
	      empty->offset = 0;
	      empty->set_name (NTXT (""));
	      new_item = sorted_data->new_hist_item (empty, Module::AT_EMPTY, all_empty);
	      new_item->value[name_index].tag = VT_LABEL;
	      new_item->value[name_index].l = NULL;
	      data_items->append_hist_item (new_item);
	    }
	  // then add the aggregate
	  new_item = sorted_data->new_hist_item (dobj, Module::AT_SRC, ditem->value);
	  new_item->value[name_index].tag = VT_OFFSET;
	  new_item->value[name_index].l = dbe_strdup (dobj->get_name ());
	  data_items->append_hist_item (new_item);
	}
      else
	{ // is a child
	  if (dobj->get_parent ()->get_typename ())
	    { // typed sub-element that has offset
	      if (dobj->offset > next_elem_offset)
		{ // filler entry
		  // hole in offsets
		  // fixme xxxxx, is it really ok to create a DataObject just for this?
		  DataObject* filler = new DataObject ();
		  filler->set_name (PTXT (DOBJ_ANON));
		  filler->size = (dobj->offset - next_elem_offset);
		  filler->offset = next_elem_offset;
		  new_item = sorted_data->new_hist_item (filler, Module::AT_EMPTY, all_empty);
		  new_item->value[name_index].tag = VT_OFFSET;
		  new_item->value[name_index].l = dbe_strdup (filler->get_offset_name ());
		  if (addr_index >= 0)
		    {
		      new_item->value[addr_index].tag = VT_ADDRESS;
		      new_item->value[addr_index].ll = (dobj->get_addr () - filler->size);
		    }
		  data_items->append_hist_item (new_item);
		}
	      next_elem_offset = dobj->offset + dobj->size;
	    }
	  // then add the aggregate's subelement
	  if (marks)
	    if (sorted_data->above_threshold (ditem))
	      marks->append (data_items->size ());
	  new_item = sorted_data->new_hist_item (dobj, Module::AT_DIS, ditem->value);
	  new_item->value[name_index].tag = VT_OFFSET;
	  new_item->value[name_index].l = dbe_strdup (dobj->get_offset_name ());
	  data_items->append_hist_item (new_item);
	}
    }
  delete[] all_empty;
  return data_items;
}
