/* Main simulator loop for CGEN-based simulators.
   Copyright (C) 1998-2024 Free Software Foundation, Inc.
   Contributed by Cygnus Solutions.

This file is part of GDB, the GNU debugger.

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 of the License, 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, see <http://www.gnu.org/licenses/>.  */

/* ??? These are old notes, kept around for now.
   Collecting profile data and tracing slow us down so we don't do them in
   "fast mode".
   There are 6 possibilities on 2 axes:
   - no-scaching, insn-scaching, basic-block-scaching
   - run with full features or run fast
   Supporting all six possibilities in one executable is a bit much but
   supporting full/fast seems reasonable.
   If the scache is configured in it is always used.
   If pbb-scaching is configured in it is always used.
   ??? Sometimes supporting more than one set of semantic functions will make
   the simulator too large - this should be configurable.  Blah blah blah.
   ??? Supporting full/fast can be more modular, blah blah blah.
   When the framework is more modular, this can be.
*/

/* This must come before any other includes.  */
#include "defs.h"

#include "sim-main.h"
#include "sim-assert.h"
#include "sim-signal.h"

#ifndef SIM_ENGINE_PREFIX_HOOK
#define SIM_ENGINE_PREFIX_HOOK(sd)
#endif
#ifndef SIM_ENGINE_POSTFIX_HOOK
#define SIM_ENGINE_POSTFIX_HOOK(sd)
#endif

static sim_event_handler has_stepped;
static void prime_cpu (SIM_CPU *, int);
static void engine_run_1 (SIM_DESC, int, int);
static void engine_run_n (SIM_DESC, int, int, int, int);

/* If no profiling or tracing has been enabled, run in fast mode.  */
static int
cgen_get_fast_p (SIM_DESC sd)
{
  int c;

  for (c = 0; c < MAX_NR_PROCESSORS; ++c)
    {
      SIM_CPU *cpu = STATE_CPU (sd, c);

      if (PROFILE_ANY_P (cpu) || TRACE_ANY_P (cpu))
	return 0;
    }

  return 1;
}

/* sim_resume for cgen */

void
sim_resume (SIM_DESC sd, int step, int siggnal)
{
  sim_engine *engine = STATE_ENGINE (sd);
  jmp_buf buf;
  int jmpval;
  static int fast_p = -1;

  ASSERT (STATE_MAGIC (sd) == SIM_MAGIC_NUMBER);

  if (fast_p == -1)
    fast_p = cgen_get_fast_p (sd);

  /* we only want to be single stepping the simulator once */
  if (engine->stepper != NULL)
    {
      sim_events_deschedule (sd, engine->stepper);
      engine->stepper = NULL;
    }
  if (step)
    engine->stepper = sim_events_schedule (sd, 1, has_stepped, sd);

  sim_module_resume (sd);

#if WITH_SCACHE
  if (USING_SCACHE_P (sd))
    scache_flush (sd);
#endif

  /* run/resume the simulator */

  sim_engine_set_run_state (sd, sim_running, 0);

  engine->jmpbuf = &buf;
  jmpval = setjmp (buf);
  if (jmpval == sim_engine_start_jmpval
      || jmpval == sim_engine_restart_jmpval)
    {
      int last_cpu_nr = sim_engine_last_cpu_nr (sd);
      int next_cpu_nr = sim_engine_next_cpu_nr (sd);
      int nr_cpus = sim_engine_nr_cpus (sd);
      /* ??? Setting max_insns to 0 allows pbb/jit code to run wild and is
	 useful if all one wants to do is run a benchmark.  Need some better
	 way to identify this case.  */
      int max_insns = (step
		       ? 1
		       : (nr_cpus == 1
			  /*&& wip:no-events*/
			  /* Don't do this if running under gdb, need to
			     poll ui for events.  */
			  && STATE_OPEN_KIND (sd) == SIM_OPEN_STANDALONE)
		       ? 0
		       : 8); /*FIXME: magic number*/

      sim_events_preprocess (sd, last_cpu_nr >= nr_cpus, next_cpu_nr >= nr_cpus);
      if (next_cpu_nr >= nr_cpus)
	next_cpu_nr = 0;
      if (nr_cpus == 1)
	engine_run_1 (sd, max_insns, fast_p);
      else
	engine_run_n (sd, next_cpu_nr, nr_cpus, max_insns, fast_p);
    }
#if 1 /*wip*/
  else
    {
      /* Account for the last insn executed.  */
      SIM_CPU *cpu = STATE_CPU (sd, sim_engine_last_cpu_nr (sd));
      ++ CPU_INSN_COUNT (cpu);
      CGEN_TRACE_INSN_FINI (cpu, NULL, 1);
    }
#endif

  engine->jmpbuf = NULL;

  {
    int i;
    int nr_cpus = sim_engine_nr_cpus (sd);

#if 0 /*wip,ignore*/
    /* If the loop exits, either we single-stepped or @cpu@_engine_stop
       was called.  */
    if (step)
      sim_engine_set_run_state (sd, sim_stopped, SIM_SIGTRAP);
    else
      sim_engine_set_run_state (sd, pending_reason, pending_sigrc);
#endif

    for (i = 0; i < nr_cpus; ++i)
      {
	SIM_CPU *cpu = STATE_CPU (sd, i);

	PROFILE_TOTAL_INSN_COUNT (CPU_PROFILE_DATA (cpu)) += CPU_INSN_COUNT (cpu);
      }
  }

  sim_module_suspend (sd);
}

/* Halt the simulator after just one instruction.  */

static void
has_stepped (SIM_DESC sd, void *data)
{
  ASSERT (STATE_MAGIC (sd) == SIM_MAGIC_NUMBER);
  sim_engine_halt (sd, NULL, NULL, NULL_CIA, sim_stopped, SIM_SIGTRAP);
}

/* Prepare a cpu for running.
   MAX_INSNS is the number of insns to execute per time slice.
   If 0 it means the cpu can run as long as it wants (e.g. until the
   program completes).
   ??? Perhaps this should be an argument to the engine_fn.  */

static void
prime_cpu (SIM_CPU *cpu, int max_insns)
{
  CPU_MAX_SLICE_INSNS (cpu) = max_insns;
  CPU_INSN_COUNT (cpu) = 0;

  /* Initialize the insn descriptor table.
     This has to be done after all initialization so we just defer it to
     here.  */

  if (MACH_PREPARE_RUN (CPU_MACH (cpu)))
    (* MACH_PREPARE_RUN (CPU_MACH (cpu))) (cpu);
}

/* Main loop, for 1 cpu.  */

static void
engine_run_1 (SIM_DESC sd, int max_insns, int fast_p)
{
  sim_cpu *cpu = STATE_CPU (sd, 0);
  ENGINE_FN *fn = fast_p ? CPU_FAST_ENGINE_FN (cpu) : CPU_FULL_ENGINE_FN (cpu);

  prime_cpu (cpu, max_insns);

  while (1)
    {
      SIM_ENGINE_PREFIX_HOOK (sd);

      (*fn) (cpu);

      SIM_ENGINE_POSTFIX_HOOK (sd);

      /* process any events */
      if (sim_events_tick (sd))
	sim_events_process (sd);
    }
}

/* Main loop, for multiple cpus.  */

static void
engine_run_n (SIM_DESC sd, int next_cpu_nr, int nr_cpus, int max_insns, int fast_p)
{
  int i;
  /* Ensure that engine_fns is fully initialized, this silences a compiler
     warning when engine_fns is used below.  */
  ENGINE_FN *engine_fns[MAX_NR_PROCESSORS] = {};

  SIM_ASSERT (nr_cpus <= MAX_NR_PROCESSORS);
  SIM_ASSERT (next_cpu_nr >= 0 && next_cpu_nr < nr_cpus);

  for (i = 0; i < nr_cpus; ++i)
    {
      SIM_CPU *cpu = STATE_CPU (sd, i);

      engine_fns[i] = fast_p ? CPU_FAST_ENGINE_FN (cpu) : CPU_FULL_ENGINE_FN (cpu);
      prime_cpu (cpu, max_insns);
    }

  while (1)
    {
      SIM_ENGINE_PREFIX_HOOK (sd);

      /* FIXME: proper cycling of all of them, blah blah blah.  */
      while (next_cpu_nr < nr_cpus)
	{
	  SIM_CPU *cpu = STATE_CPU (sd, next_cpu_nr);

	  (* engine_fns[next_cpu_nr]) (cpu);
	  ++next_cpu_nr;
	}

      SIM_ENGINE_POSTFIX_HOOK (sd);

      /* process any events */
      if (sim_events_tick (sd))
	sim_events_process (sd);
    }
}
