<commit_before>#include "types.h"
#include "kernel.hh"
#include "spinlock.h"
#include "condvar.h"
#include "atomic.hh"
#include "proc.hh"
#include "cpu.hh"
#include "cpputil.hh"
#include "percpu.hh"
#include "gc.hh"
#include "major.h"
#include "uk/gcstat.h"

using std::atomic;

// A simple RCU implementation, but general:
// - processes can call sleep in an epoch
// - processes can migrate during an epoch
//
// The GC scheme is adopted from Fraser's epoch-based scheme:
// a machine has a global_epoch
// a process add to its core global_epoch's delayed freelist on delayed_free()
// a core maintains an epoch, min_epoch (>= global_epoch)
//   min_epoch is the lowest epoch # a process on that core is in
//   a process always ends an epoch on the core it started (even if the
//   process is migrated)
// one gc thread and state (e.g., NEPOCH delaylist) per core
// a gcc thread performs two jobs:
// 1. in parallel gc threads free the elements on the delayed-free lists
//   (costs linear in the number of elements to be freed, but a local operation)
// 2a (global scheme). one gcc thread perform step 1: updates global_epoch
//   (costs linear in the number of cores, but a global operation)
// 2a (local scheme). each core reads the global_min and cur_epoch from its
//   neighbor. it updates its global_min by computing the min(global_min read from
//   neighbor and its min_epoch). it sets its cur_epoch to the
//   neighbor's cur_epoch.  core 0 is the ring leader: it increases epoch,
//   if global_min from last core >= cur_epoch-2.  If local communication scales,
//   this gc scheme will scale, but the delay to increase cur_epoch is linear
//   in the number of cores.
//
// To limit the number of delayed free lists per core, each core also has a
// variable nexttofree_epoch, which <= min_epoch. cur_epoch isn't increased
// unless nexttofree_epoch >= cur_epoch-2, implicitly also ensuring the
// constraint that cur_epoch is only increases when min_epoch >= cur_epoch-2.

enum { gc_debug = 0, gc_global = 0 };

// Head of a delayed free list. Always read and updated while holding lock_
struct headinfo {
  rcu_freed* head;
  u64 epoch;
};

// nexttofree_epoch << min_epoch << cur_epoch (or global_epoch)
struct gc_state {
  atomic<u64> nexttofree_epoch; // the lowest epoch # to free on this core
  atomic<u64> min_epoch;        // the lowest epoch # a process on this core is in
  atomic<u64> cur_epoch;        // the current epoch this core is running in
  atomic<u64> global_min;       // used to compute global_min over nexttofree
  struct spinlock lock_ __mpalign__;
  struct condvar cv;
  headinfo delayed[NEPOCH];     // NEPOCH delayed-free lists
  gc_handle proclist;           // list of process in an epoch on this core
public:
  gc_state();
  void dequeue(gc_handle *h);
  void enqueue(gc_handle *h);
  int gc_free(rcu_freed *r, u64 epoch);
  void do_gc(void);
  void inc_cur_epoch(void);
};

percpu<gc_state, percpu_safety::internal> gc_states;
percpu<gc_stat, percpu_safety::internal> stat;
int ngc_cpu;
int gc_batchsize;

// Increment of global_epoch acquires gc_lock, but processes cannot read it
// without locks
static struct gc_lock {
  struct spinlock l __mpalign__;
  gc_lock() : l("gc", LOCKSTAT_GC) { }
} gc_lock;
atomic<u64> global_epoch __mpalign__;

// Increment global_epoch if (1) each core has freed all epochs <= global-2
// and (2) each core has no processes in an epoch <= global - 2
// This operation is the only global operation.
static void
gc_inc_global_epoch(void)
{
  int r = tryacquire(&gc_lock.l);
  if (r == 0) return;
  assert(r == 1);
  u64 t0 = rdtsc();
  u64 global = global_epoch;  // make "local" copy
  u64 minfree = global;
  u64 minepoch = global;
  for (int c = 0; c < ngc_cpu; c++) {
    // is reading nexttofree_epoch and min_epoch a single cache miss?
    if (gc_states[c].nexttofree_epoch < minfree) {
      minfree = gc_states[c].nexttofree_epoch;
    }
    if (gc_states[c].min_epoch < minepoch) {
      minepoch = gc_states[c].min_epoch;
    }
  }
  if ((minfree < global-2) || (minepoch < global-2))
    goto done;
  if (minepoch > global-2) {
    if (gc_debug) cprintf("update global_epoch to: %lu\n", minepoch+1);
    global_epoch = global + 1;
  }
done:
  release(&gc_lock.l);
  u64 t1 = rdtsc();
  stat[mycpu()->id].ncycles += (t1-t0);
  stat[mycpu()->id].nop++;
}

// Scalable way of computing global_min by arranging nodes in a virtual ring
// XXX ring could follow physical proximity
void
gc_state::inc_cur_epoch(void)
{
  u64 t0 = rdtsc();
  if (mycpu()->id == 0) {
    u64 min = gc_states[ncpu-1].global_min;
    if (min >= cur_epoch - 2) {
      if (gc_debug) cprintf("%d: update my epoch to: %lu\n",
                          mycpu()->id, cur_epoch+1);
      cur_epoch += 1;
    }
    global_min = nexttofree_epoch.load();
  } else {
    int neighbor = mycpu()->id - 1;
    u64 neighbor_global_min = gc_states[neighbor].global_min;
    u64 e = gc_states[neighbor].cur_epoch.load();
    assert(cur_epoch <= e);
    cur_epoch = e;
    global_min = neighbor_global_min > nexttofree_epoch ?
      global_min = nexttofree_epoch.load() :  global_min = neighbor_global_min;
  }
  u64 t1 = rdtsc();
  stat[mycpu()->id].ncycles += (t1-t0);
  stat[mycpu()->id].nop++;
}

gc_state::gc_state() :
  lock_("gc_state", LOCKSTAT_GC), cv(condvar("gc_cv"))
{
  proclist.next = &proclist;
  proclist.prev = &proclist;
  for (int i = 0; i < NEPOCH; i++) {
    delayed[i].epoch = i;
  }
  cur_epoch = NEPOCH-2;
}

// caller should hold lock_
void
gc_state::enqueue(gc_handle *h)
{
  h->next = &this->proclist;
  h->prev = this->proclist.prev;
  this->proclist.prev->next = h;
  this->proclist.prev = h;
}

// caller should hold lock_
void
gc_state::dequeue(gc_handle *h)
{
  u64 m = gc_global ? global_epoch : cur_epoch;
  for (gc_handle* entry = this->proclist.next; entry != &this->proclist;
       entry = entry->next) {
    if (entry == h) {
      entry->next->prev = entry->prev;
      entry->prev->next = entry->next;
    } else {
      if ((entry->epoch >> 8) < m)
        m = (entry->epoch >> 8);
    }
  }
  if (this->min_epoch != m) {
    this->min_epoch = m;
  }
}

// free the elements in delayed-free list r (from epoch epoch).
// Runs without holding _lock
int
gc_state::gc_free(rcu_freed *r, u64 epoch)
{
  int nfree = 0;
  rcu_freed *nr;
  for (; r; r = nr) {
    if (r->_rcu_epoch > epoch) {
      cprintf("gc_free: r->epoch %ld > epoch %ld\n", r->_rcu_epoch, epoch);
#if RCU_TYPE_DEBUG
      cprintf("gc_free: name %s\n", r->_rcu_type);
#endif
      assert(0);
    }
    nr = r->_rcu_next;
    r->do_gc();
    nfree++;
  }
  return nfree;
}

// Caller must hold lock_.  This function cannot be called recursively,
// but it won't if only gc_worker() runs do_gc.
void
gc_state::do_gc(void)
{
  u64 i;

  stat->nrun++;

  // free all delayed-free lists until min_epoch
  for (i = nexttofree_epoch; i < min_epoch; i++) {
    rcu_freed *head = delayed[i%NEPOCH].head;

    // give up lock during free; gc_free() may call gc_begin/end_epoch
    release(&lock_);

    int nfree = gc_free(head,i);

    acquire(&lock_);
    delayed[i%NEPOCH].head = nullptr;
    delayed[i%NEPOCH].epoch += NEPOCH;
    stat->nfree += nfree;
    if (gc_debug && nfree > 0) {
      cprintf("%d: epoch %lu freed %d\n", mycpu()->id, i, nfree);
    }

  }
  nexttofree_epoch = i;

  // try to increment global_epoch
  if (gc_global) gc_inc_global_epoch();
  else inc_cur_epoch();
}

static void
gc_worker(void *x)
{
  if (VERBOSE)
    cprintf("gc_worker: %d\n", mycpu()->id);

  acquire(&gc_states->lock_);
  for (;;) {
    gc_states->cv.sleep_to(&gc_states->lock_,
                          nsectime() + ((u64)GCINTERVAL)*1000000ull);

    // if no processes are running on this core, update min_epoch
    if (gc_states->proclist.next == &gc_states->proclist) {
      gc_states->min_epoch = gc_global ? global_epoch.load() :
        gc_states->cur_epoch.load();
    }

    gc_states->do_gc();

  }
}

static int
readstat(struct inode *inode, char *dst, u32 off, u32 n)
{
  size_t sz = sizeof(gc_stat);
  int i = off / sz;

  if (n != sz)
    return -1;

  if (i >= NCPU) {
    for (int n = 0; n < NCPU; n++)
      memset((void *) &stat[n], 0, sz);
    return 0;
  }

  memcpy(dst, &stat[i], sz);

  return n;
}

static int
writectrl(struct inode *inode, const char *buf, u32 off, u32 n)
{
  int op;
  if (n != 3*sizeof(int))
    return -1;
  memcpy(&ngc_cpu, buf, sizeof(int));
  memcpy(&gc_batchsize, buf + sizeof(int), sizeof(int));
  memcpy(&op, buf + 2 * sizeof(int), sizeof(int));
  return n;
}

//
// Public interface:
//

void
initgc(void)
{
  ngc_cpu = NCPU;
  global_epoch = NEPOCH-2;
  gc_batchsize = 100000000;

  devsw[MAJ_GC].write = writectrl;
  devsw[MAJ_GC].read = readstat;

  for (int c = 0; c < ncpu; c++) {
    char namebuf[32];
    snprintf(namebuf, sizeof(namebuf), "gc_%u", c);
    threadpin(gc_worker, 0, namebuf, c);
  }
}

void
gc_delayed(rcu_freed *e)
{
  int c =  mycpu()->id;
  struct gc_state *gs = &gc_states[c];

  scoped_acquire x(&gs->lock_);

  u64 epoch = gc_global ? global_epoch : gs->cur_epoch;

  if (gc_debug)
    cprintf("(%d, %d): gc_delayed: %lu ndelayed %lu\n", c, myproc()->pid,
            epoch, stat[c].ndelay);

  if (epoch != gs->delayed[epoch % NEPOCH].epoch) {
    cprintf("%d: epoch %lu minepoch %lu my freeto %lu my min %lu\n", c, epoch,
            gs->delayed[epoch % NEPOCH].epoch, gs->nexttofree_epoch.load(),
            gs->min_epoch.load());
    if (c >= ngc_cpu) return;
    panic("gc_delayed");
  }
  stat[c].ndelay++;
  e->_rcu_epoch = epoch;
  e->_rcu_next = gs->delayed[epoch % NEPOCH].head;
  gs->delayed[epoch % NEPOCH].head = e;
}

void
gc_begin_epoch(void)
{
  if (myproc() == nullptr) return;
  u64 v = myproc()->gc->epoch++;
  if (v & 0xff) return;

  int c =  mycpu()->id;
  struct gc_state *gs = &gc_states[c];

  scoped_acquire x(&gs->lock_);
  u64 epoch = gc_global ? global_epoch : gs->cur_epoch;
  myproc()->gc->core = c;
  cmpxch(&myproc()->gc->epoch, v+1, (epoch<<8)+1);
  // We effectively need an mfence here, and cmpxch provides one
  // by virtue of being a LOCK instuction.
  gs->enqueue(myproc()->gc);
}

void
gc_end_epoch(void)
{
  if (myproc() == nullptr) return;
  u64 e = --myproc()->gc->epoch;
  if ((e & 0xff) != 0)
    return;

  int c = myproc()->gc->core;
  assert (c != -1);
  assert (c >= 0 && c < ncpu);
  struct gc_state *gs = &gc_states[c];

  scoped_acquire x(&gs->lock_);

  gc_states[c].dequeue(myproc()->gc);
  myproc()->gc->core = -1;

  if (stat[c].ndelay % gc_batchsize == 0) {
    // calling gs->do_gc() works for gcbench, because gcbench threads are pinned
    // to a core.  do_gc is correct when it uses one core's gc_state, so better
    // to wakeup this core's gc thread, and yield the core to it.
    gs->cv.wake_all(true);
  }
}

void
gc_wakeup(void)
{
  for (int i = 0; i < NCPU; i++) {
    gc_states[i].cv.wake_all();
  }
}
<commit_msg>for measuring gc with fewer cores than machine has<commit_after>#include "types.h"
#include "kernel.hh"
#include "spinlock.h"
#include "condvar.h"
#include "atomic.hh"
#include "proc.hh"
#include "cpu.hh"
#include "cpputil.hh"
#include "percpu.hh"
#include "gc.hh"
#include "major.h"
#include "uk/gcstat.h"

using std::atomic;

// A simple RCU implementation, but general:
// - processes can call sleep in an epoch
// - processes can migrate during an epoch
//
// The GC scheme is adopted from Fraser's epoch-based scheme:
// a machine has a global_epoch
// a process add to its core global_epoch's delayed freelist on delayed_free()
// a core maintains an epoch, min_epoch (>= global_epoch)
//   min_epoch is the lowest epoch # a process on that core is in
//   a process always ends an epoch on the core it started (even if the
//   process is migrated)
// one gc thread and state (e.g., NEPOCH delaylist) per core
// a gcc thread performs two jobs:
// 1. in parallel gc threads free the elements on the delayed-free lists
//   (costs linear in the number of elements to be freed, but a local operation)
// 2a (global scheme). one gcc thread perform step 1: updates global_epoch
//   (costs linear in the number of cores, but a global operation)
// 2b (local scheme). each core reads the global_min and cur_epoch from its
//   neighbor. it updates its global_min by computing the min(global_min read from
//   neighbor and its min_epoch). it sets its cur_epoch to the
//   neighbor's cur_epoch.  core 0 is the ring leader: it increases epoch,
//   if global_min from last core >= cur_epoch-2.  If local communication scales,
//   this gc scheme will scale, but the delay to increase cur_epoch is linear
//   in the number of cores.
//
// To limit the number of delayed free lists per core, each core also has a
// variable nexttofree_epoch, which <= min_epoch. cur_epoch isn't increased
// unless nexttofree_epoch >= cur_epoch-2, implicitly also ensuring the
// constraint that cur_epoch is only increases when min_epoch >= cur_epoch-2.

enum { gc_debug = 0, gc_global = 0 };

// Head of a delayed free list. Always read and updated while holding lock_
struct headinfo {
  rcu_freed* head;
  u64 epoch;
};

// nexttofree_epoch << min_epoch << global_epoch (or cur_epoch)
struct gc_state {
  atomic<u64> nexttofree_epoch; // the lowest epoch # to free on this core
  atomic<u64> min_epoch;        // the lowest epoch # a process on this core is in
  atomic<u64> cur_epoch;        // the current epoch this core is running in
  atomic<u64> global_min;       // used to compute global_min over nexttofree
  struct spinlock lock_ __mpalign__;
  struct condvar cv;
  headinfo delayed[NEPOCH];     // NEPOCH delayed-free lists
  gc_handle proclist;           // list of process in an epoch on this core
public:
  gc_state();
  void dequeue(gc_handle *h);
  void enqueue(gc_handle *h);
  int gc_free(rcu_freed *r, u64 epoch);
  void do_gc(void);
  void inc_cur_epoch(void);
};

percpu<gc_state, percpu_safety::internal> gc_states;
percpu<gc_stat, percpu_safety::internal> stat;
int ngc_cpu;
int gc_batchsize;

// Increment of global_epoch acquires gc_lock, but processes cannot read it
// without locks
static struct gc_lock {
  struct spinlock l __mpalign__;
  gc_lock() : l("gc", LOCKSTAT_GC) { }
} gc_lock;
atomic<u64> global_epoch __mpalign__;

// Sceheme 2a: Increment global_epoch if (1) each core has freed all epochs <=
// global-2 and (2) each core has no processes in an epoch <= global - 2 This
// operation is the only global operation.
static void
gc_inc_global_epoch(void)
{
  int r = tryacquire(&gc_lock.l);
  if (r == 0) return;
  assert(r == 1);
  u64 t0 = rdtsc();
  u64 global = global_epoch;  // make "local" copy
  u64 minfree = global;
  u64 minepoch = global;
  for (int c = 0; c < ngc_cpu; c++) {
    // is reading nexttofree_epoch and min_epoch a single cache miss?
    if (gc_states[c].nexttofree_epoch < minfree) {
      minfree = gc_states[c].nexttofree_epoch;
    }
    if (gc_states[c].min_epoch < minepoch) {
      minepoch = gc_states[c].min_epoch;
    }
  }
  if ((minfree < global-2) || (minepoch < global-2))
    goto done;
  if (minepoch > global-2) {
    if (gc_debug) cprintf("update global_epoch to: %lu\n", minepoch+1);
    global_epoch = global + 1;
  }
done:
  release(&gc_lock.l);
  u64 t1 = rdtsc();
  stat[mycpu()->id].ncycles += (t1-t0);
  stat[mycpu()->id].nop++;
}

// Scheme 2b: Scalable way of computing global_min by arranging nodes in a
// virtual ring.
// XXX ring could follow physical proximity
void
gc_state::inc_cur_epoch(void)
{
  u64 t0 = rdtsc();
  if (mycpu()->id == 0) {
    u64 min = gc_states[ngc_cpu-1].global_min;
    if (min >= cur_epoch - 2) {
      if (gc_debug) cprintf("%d: update my epoch to: %lu\n",
                          mycpu()->id, cur_epoch+1);
      cur_epoch += 1;
    }
    global_min = nexttofree_epoch.load();
  } else {
    int neighbor = mycpu()->id - 1;
    u64 neighbor_global_min = gc_states[neighbor].global_min;
    u64 e = gc_states[neighbor].cur_epoch.load();
    assert(cur_epoch <= e);
    cur_epoch = e;
    global_min = neighbor_global_min > nexttofree_epoch ?
      global_min = nexttofree_epoch.load() :  global_min = neighbor_global_min;
  }
  u64 t1 = rdtsc();
  stat[mycpu()->id].ncycles += (t1-t0);
  stat[mycpu()->id].nop++;
}

gc_state::gc_state() :
  lock_("gc_state", LOCKSTAT_GC), cv(condvar("gc_cv"))
{
  proclist.next = &proclist;
  proclist.prev = &proclist;
  for (int i = 0; i < NEPOCH; i++) {
    delayed[i].epoch = i;
  }
  cur_epoch = NEPOCH-2;
}

// caller should hold lock_
void
gc_state::enqueue(gc_handle *h)
{
  h->next = &this->proclist;
  h->prev = this->proclist.prev;
  this->proclist.prev->next = h;
  this->proclist.prev = h;
}

// caller should hold lock_
void
gc_state::dequeue(gc_handle *h)
{
  u64 m = gc_global ? global_epoch : cur_epoch;
  for (gc_handle* entry = this->proclist.next; entry != &this->proclist;
       entry = entry->next) {
    if (entry == h) {
      entry->next->prev = entry->prev;
      entry->prev->next = entry->next;
    } else {
      if ((entry->epoch >> 8) < m)
        m = (entry->epoch >> 8);
    }
  }
  if (this->min_epoch != m) {
    this->min_epoch = m;
  }
}

// Free the elements in delayed-free list r (from epoch epoch).
// Runs without holding _lock
int
gc_state::gc_free(rcu_freed *r, u64 epoch)
{
  int nfree = 0;
  rcu_freed *nr;
  for (; r; r = nr) {
    if (r->_rcu_epoch > epoch) {
      cprintf("gc_free: r->epoch %ld > epoch %ld\n", r->_rcu_epoch, epoch);
#if RCU_TYPE_DEBUG
      cprintf("gc_free: name %s\n", r->_rcu_type);
#endif
      assert(0);
    }
    nr = r->_rcu_next;
    r->do_gc();
    nfree++;
  }
  return nfree;
}

// Caller must hold lock_.  This function cannot be called recursively,
// but it won't if only gc_worker() runs do_gc.
void
gc_state::do_gc(void)
{
  u64 i;

  stat->nrun++;

  // free all delayed-free lists until min_epoch
  for (i = nexttofree_epoch; i < min_epoch; i++) {
    rcu_freed *head = delayed[i%NEPOCH].head;

    // give up lock during free; gc_free() may call gc_begin/end_epoch
    release(&lock_);

    int nfree = gc_free(head,i);

    acquire(&lock_);
    delayed[i%NEPOCH].head = nullptr;
    delayed[i%NEPOCH].epoch += NEPOCH;
    stat->nfree += nfree;
    if (gc_debug && nfree > 0) {
      cprintf("%d: epoch %lu freed %d\n", mycpu()->id, i, nfree);
    }

  }
  nexttofree_epoch = i;

  // try to increment global_epoch
  if (gc_global) gc_inc_global_epoch();
  else inc_cur_epoch();
}

static void
gc_worker(void *x)
{
  if (VERBOSE)
    cprintf("gc_worker: %d\n", mycpu()->id);

  acquire(&gc_states->lock_);
  for (;;) {
    gc_states->cv.sleep_to(&gc_states->lock_,
                          nsectime() + ((u64)GCINTERVAL)*1000000ull);

    // if no processes are running on this core, update min_epoch
    if (gc_states->proclist.next == &gc_states->proclist) {
      gc_states->min_epoch = gc_global ? global_epoch.load() :
        gc_states->cur_epoch.load();
    }

    gc_states->do_gc();

  }
}

static int
readstat(struct inode *inode, char *dst, u32 off, u32 n)
{
  size_t sz = sizeof(gc_stat);
  int i = off / sz;

  if (n != sz)
    return -1;

  if (i >= NCPU) {
    for (int n = 0; n < NCPU; n++)
      memset((void *) &stat[n], 0, sz);
    return 0;
  }

  memcpy(dst, &stat[i], sz);

  return n;
}

static int
writectrl(struct inode *inode, const char *buf, u32 off, u32 n)
{
  int op;
  if (n != 3*sizeof(int))
    return -1;
  memcpy(&ngc_cpu, buf, sizeof(int));
  memcpy(&gc_batchsize, buf + sizeof(int), sizeof(int));
  memcpy(&op, buf + 2 * sizeof(int), sizeof(int));
  return n;
}

//
// Public interface:
//

void
initgc(void)
{
  ngc_cpu = NCPU;
  global_epoch = NEPOCH-2;
  gc_batchsize = 100000000;

  devsw[MAJ_GC].write = writectrl;
  devsw[MAJ_GC].read = readstat;

  for (int c = 0; c < ncpu; c++) {
    char namebuf[32];
    snprintf(namebuf, sizeof(namebuf), "gc_%u", c);
    threadpin(gc_worker, 0, namebuf, c);
  }
}

void
gc_delayed(rcu_freed *e)
{
  int c =  mycpu()->id;
  struct gc_state *gs = &gc_states[c];

  scoped_acquire x(&gs->lock_);

  u64 epoch = gc_global ? global_epoch : gs->cur_epoch;

  if (gc_debug)
    cprintf("(%d, %d): gc_delayed: %lu ndelayed %lu\n", c, myproc()->pid,
            epoch, stat[c].ndelay);

  if (epoch != gs->delayed[epoch % NEPOCH].epoch) {
    cprintf("%d: epoch %lu minepoch %lu my freeto %lu my min %lu\n", c, epoch,
            gs->delayed[epoch % NEPOCH].epoch, gs->nexttofree_epoch.load(),
            gs->min_epoch.load());
    if (c >= ngc_cpu) return;
    panic("gc_delayed");
  }
  stat[c].ndelay++;
  e->_rcu_epoch = epoch;
  e->_rcu_next = gs->delayed[epoch % NEPOCH].head;
  gs->delayed[epoch % NEPOCH].head = e;
}

void
gc_begin_epoch(void)
{
  if (myproc() == nullptr) return;
  u64 v = myproc()->gc->epoch++;
  if (v & 0xff) return;

  int c =  mycpu()->id;
  struct gc_state *gs = &gc_states[c];

  scoped_acquire x(&gs->lock_);
  u64 epoch = gc_global ? global_epoch : gs->cur_epoch;
  myproc()->gc->core = c;
  cmpxch(&myproc()->gc->epoch, v+1, (epoch<<8)+1);
  // We effectively need an mfence here, and cmpxch provides one
  // by virtue of being a LOCK instuction.
  gs->enqueue(myproc()->gc);
}

void
gc_end_epoch(void)
{
  if (myproc() == nullptr) return;
  u64 e = --myproc()->gc->epoch;
  if ((e & 0xff) != 0)
    return;

  int c = myproc()->gc->core;
  assert (c != -1);
  assert (c >= 0 && c < ncpu);
  struct gc_state *gs = &gc_states[c];

  scoped_acquire x(&gs->lock_);

  gc_states[c].dequeue(myproc()->gc);
  myproc()->gc->core = -1;

  if (stat[c].ndelay % gc_batchsize == 0) {
    // calling gs->do_gc() works for gcbench, because gcbench threads are pinned
    // to a core.  do_gc is correct when it uses one core's gc_state, so better
    // to wakeup this core's gc thread, and yield the core to it.
    gs->cv.wake_all(true);
  }
}

void
gc_wakeup(void)
{
  for (int i = 0; i < NCPU; i++) {
    gc_states[i].cv.wake_all();
  }
}
<|endoftext|>


<commit_before>#include <imap.hpp>

#include "helpers.hpp"

#include <vector>
#include <string>
#include <iterator>

#include "catch.hpp"

using iter::imap;
using Vec = const std::vector<int>;

namespace {
    int plusone(int i) {
        return i + 1;
    }

    class PlusOner {
        public:
            int operator()(int i) {
                return i + 1;
            }
    };

    int power(int b, int e) {
        int acc = 1;
        for (int i = 0; i < e; ++i) {
            acc *= b;
        }
        return acc;
    }
}

TEST_CASE("imap: works with lambda, callable, and function", "[imap]") {
    std::vector<int> ns = {10, 20, 30};
    SECTION("with lambda") {
        auto im = imap([](int i) { return i + 1; }, ns);
        Vec v(std::begin(im), std::end(im));
        Vec vc = {11, 21, 31};
        REQUIRE( v == vc );
    }

    SECTION("with function") {
        auto im = imap(plusone, ns);
        Vec v(std::begin(im), std::end(im));
        Vec vc = {11, 21, 31};
        REQUIRE( v == vc );
    }

    SECTION("with function") {
        auto im = imap(PlusOner{}, ns);
        Vec v(std::begin(im), std::end(im));
        Vec vc = {11, 21, 31};
        REQUIRE( v == vc );
    }

}

TEST_CASE("imap: works with multiple sequences", "[imap]") {
    Vec bases = {0, 1, 2, 3};
    Vec exps = {1, 2, 3, 4};

    auto im = imap(power, bases, exps);
    Vec v(std::begin(im), std::end(im));
    Vec vc = {0, 1, 8, 81};

    REQUIRE( v == vc );
}
<commit_msg>tests imap with uneven sequences<commit_after>#include <imap.hpp>

#include "helpers.hpp"

#include <vector>
#include <string>
#include <iterator>

#include "catch.hpp"

using iter::imap;
using Vec = const std::vector<int>;

namespace {
    int plusone(int i) {
        return i + 1;
    }

    class PlusOner {
        public:
            int operator()(int i) {
                return i + 1;
            }
    };

    int power(int b, int e) {
        int acc = 1;
        for (int i = 0; i < e; ++i) {
            acc *= b;
        }
        return acc;
    }
}

TEST_CASE("imap: works with lambda, callable, and function", "[imap]") {
    std::vector<int> ns = {10, 20, 30};
    SECTION("with lambda") {
        auto im = imap([](int i) { return i + 1; }, ns);
        Vec v(std::begin(im), std::end(im));
        Vec vc = {11, 21, 31};
        REQUIRE( v == vc );
    }

    SECTION("with function") {
        auto im = imap(plusone, ns);
        Vec v(std::begin(im), std::end(im));
        Vec vc = {11, 21, 31};
        REQUIRE( v == vc );
    }

    SECTION("with function") {
        auto im = imap(PlusOner{}, ns);
        Vec v(std::begin(im), std::end(im));
        Vec vc = {11, 21, 31};
        REQUIRE( v == vc );
    }

}

TEST_CASE("imap: works with multiple sequences", "[imap]") {
    Vec bases = {0, 1, 2, 3};
    Vec exps = {1, 2, 3, 4};

    auto im = imap(power, bases, exps);
    Vec v(std::begin(im), std::end(im));
    Vec vc = {0, 1, 8, 81};

    REQUIRE( v == vc );
}

TEST_CASE("imap: terminates on shortest squence", "[imap]") {
    Vec ns1 = {1, 2, 3, 4};
    Vec ns2 = {2, 4, 6, 8, 10};
    Vec vc = {3, 6, 9, 12};
    SECTION("shortest sequence first") {
        auto im = imap([](int a, int b){ return a + b; }, ns1, ns2);
        Vec v(std::begin(im), std::end(im));
        REQUIRE( v == vc );
    }
    SECTION("shortest sequence second") {
        auto im = imap([](int a, int b){ return a + b; }, ns2, ns1);
        Vec v(std::begin(im), std::end(im));
        REQUIRE( v == vc );
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#include "RemoveNullcheckStringArg.h"

#include "CFGMutation.h"
#include "Creators.h"
#include "DexClass.h"
#include "DexUtil.h"
#include "KotlinNullCheckMethods.h"
#include "LiveRange.h"
#include "PassManager.h"
#include "ReachingDefinitions.h"
#include "Show.h"
#include "Trace.h"
#include "Walkers.h"

using namespace kotlin_nullcheck_wrapper;

void RemoveNullcheckStringArg::run_pass(DexStoresVector& stores,
                                        ConfigFiles& /*conf*/,
                                        PassManager& mgr) {
  TransferMap transfer_map;

  std::unordered_set<DexMethod*> new_methods;
  if (!setup(transfer_map, new_methods)) {
    TRACE(NULLCHECK, 2, "RemoveNullcheckStringArgPass: setup failed");
    return;
  }

  Scope scope = build_class_scope(stores);
  Stats stats = walk::parallel::methods<Stats>(scope, [&](DexMethod* method) {
    auto code = method->get_code();
    if (method->rstate.no_optimizations() || code == nullptr ||
        new_methods.count(method)) {
      return Stats();
    }
    code->build_cfg();
    auto local_stats =
        change_in_cfg(code->cfg(), transfer_map, method->is_virtual());
    code->clear_cfg();
    return local_stats;
  });

  stats.report(mgr);
}

bool RemoveNullcheckStringArg::setup(
    TransferMap& transfer_map, std::unordered_set<DexMethod*>& new_methods) {

  DexMethodRef* builtin_param_V1_3 =
      DexMethod::get_method(CHECK_PARAM_NULL_SIGNATURE_V1_3);
  DexMethodRef* builtin_param_V1_4 =
      DexMethod::get_method(CHECK_PARAM_NULL_SIGNATURE_V1_4);
  DexMethodRef* builtin_expr_V1_3 =
      DexMethod::get_method(CHECK_EXPR_NULL_SIGNATURE_V1_3);
  DexMethodRef* builtin_expr_V1_4 =
      DexMethod::get_method(CHECK_EXPR_NULL_SIGNATURE_V1_4);

  if (builtin_param_V1_3) {
    auto new_check_param_method = get_wrapper_method_with_int_index(
        NEW_CHECK_PARAM_NULL_SIGNATURE_V1_3,
        WRAPPER_CHECK_PARAM_NULL_METHOD_V1_3, builtin_param_V1_3);
    if (new_check_param_method) {
      transfer_map[builtin_param_V1_3] =
          std::make_pair(new_check_param_method, true);
      new_methods.insert(new_check_param_method);
    }
  }
  if (builtin_param_V1_4) {
    auto new_check_param_method = get_wrapper_method_with_int_index(
        NEW_CHECK_PARAM_NULL_SIGNATURE_V1_4,
        WRAPPER_CHECK_PARAM_NULL_METHOD_V1_4, builtin_param_V1_4);
    if (new_check_param_method) {
      transfer_map[builtin_param_V1_4] =
          std::make_pair(new_check_param_method, true);
      new_methods.insert(new_check_param_method);
    }
  }

  if (builtin_expr_V1_3) {
    auto new_check_expr_method = get_wrapper_method(
        NEW_CHECK_EXPR_NULL_SIGNATURE_V1_3, WRAPPER_CHECK_EXPR_NULL_METHOD_V1_3,
        builtin_expr_V1_3);
    if (new_check_expr_method) {
      transfer_map[builtin_expr_V1_3] =
          std::make_pair(new_check_expr_method, false);
      new_methods.insert(new_check_expr_method);
    }
  }

  if (builtin_expr_V1_4) {
    auto new_check_expr_method = get_wrapper_method(
        NEW_CHECK_EXPR_NULL_SIGNATURE_V1_4, WRAPPER_CHECK_EXPR_NULL_METHOD_V1_4,
        builtin_expr_V1_4);
    if (new_check_expr_method) {
      transfer_map[builtin_expr_V1_4] =
          std::make_pair(new_check_expr_method, false);
      new_methods.insert(new_check_expr_method);
    }
  }

  /* If we could not generate suitable wrapper method, giveup. */
  if (new_methods.empty()) {
    return false;
  }

  return true;
}

DexMethod* RemoveNullcheckStringArg::get_wrapper_method(
    const char* wrapper_signature,
    const char* wrapper_name,
    DexMethodRef* builtin) {

  if (DexMethod::get_method(wrapper_signature)) {
    /* Wrapper method already exist. */
    return nullptr;
  }

  auto* host_cls = type_class(builtin->get_class());
  if (!host_cls || host_cls->is_external()) {
    return nullptr;
  }

  DexTypeList* arg_signature =
      DexTypeList::make_type_list({type::java_lang_Object()});
  const auto proto = DexProto::make_proto(type::_void(), arg_signature);
  MethodCreator method_creator(host_cls->get_type(),
                               DexString::make_string(wrapper_name),
                               proto,
                               ACC_PUBLIC | ACC_STATIC);
  auto obj_arg = method_creator.get_local(0);

  auto main_block = method_creator.get_main_block();
  auto if_block = main_block->if_testz(OPCODE_IF_NEZ, obj_arg);
  auto str_type = DexType::get_type("Ljava/lang/String;");
  if (!str_type) {
    return nullptr;
  }

  auto str_const = method_creator.make_local(str_type);

  // const-string v2, "UNKNOWN"
  if_block->load_const(str_const, DexString::make_string("UNKNOWN"));

  if_block->invoke(OPCODE_INVOKE_STATIC, builtin, {obj_arg, str_const});
  if_block->ret_void();
  main_block->ret_void();

  auto new_method = method_creator.create();
  TRACE(NULLCHECK, 5, "Created Method : %s", SHOW(new_method->get_code()));
  host_cls->add_method(new_method);
  return new_method;
}

DexMethod* RemoveNullcheckStringArg::get_wrapper_method_with_int_index(
    const char* wrapper_signature,
    const char* wrapper_name,
    DexMethodRef* builtin) {

  if (DexMethod::get_method(wrapper_signature)) {
    /* Wrapper method already exist. */
    return nullptr;
  }

  auto host_cls = type_class(builtin->get_class());
  if (!host_cls) {
    return nullptr;
  }

  DexTypeList* arg_signature =
      DexTypeList::make_type_list({type::java_lang_Object(), type::_int()});
  const auto proto = DexProto::make_proto(type::_void(), arg_signature);
  MethodCreator method_creator(host_cls->get_type(),
                               DexString::make_string(wrapper_name),
                               proto,
                               ACC_PUBLIC | ACC_STATIC);
  auto obj_arg = method_creator.get_local(0);

  // If the wrapper is going to print the index of the param as a string, we
  // will have to construct a string from the index with additional
  // information as part of the wrapper method.
  auto main_block = method_creator.get_main_block();
  auto int_ind = method_creator.get_local(1);
  auto str_type = DexType::get_type("Ljava/lang/String;");
  auto str_builder_type = DexType::get_type("Ljava/lang/StringBuilder;");
  if (!str_type || !str_builder_type) {
    return nullptr;
  }

  auto to_str_method = DexMethod::get_method(
      "Ljava/lang/Integer;.toString:(I)Ljava/lang/String;");
  auto str_builder_init_method =
      DexMethod::get_method("Ljava/lang/StringBuilder;.<init>:()V");
  auto append_method = DexMethod::get_method(
      "Ljava/lang/StringBuilder;.append:(Ljava/lang/"
      "String;)Ljava/lang/StringBuilder;");
  auto str_builder_to_str_method = DexMethod::get_method(
      "Ljava/lang/StringBuilder;.toString:()Ljava/lang/String;");

  if (!to_str_method || !append_method || !str_builder_to_str_method) {
    return nullptr;
  }
  auto str_ind = method_creator.make_local(str_type);
  auto str_builder = method_creator.make_local(str_builder_type);
  auto str_const = method_creator.make_local(str_type);
  auto str_res = method_creator.make_local(str_type);
  auto if_block = main_block->if_testz(OPCODE_IF_NEZ, obj_arg);

  // invoke-static {v3}, Ljava/lang/Integer;.toString:(I)Ljava/lang/String;
  if_block->invoke(OPCODE_INVOKE_STATIC, to_str_method, {int_ind});
  // move-result-object v3
  if_block->move_result(str_ind, str_type);
  // new-instance v1, Ljava/lang/StringBuilder;
  if_block->new_instance(str_builder_type, str_builder);
  // invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V
  if_block->invoke(OPCODE_INVOKE_DIRECT, str_builder_init_method,
                   {str_builder});
  // const-string v2, "param index = "
  if_block->load_const(str_const, DexString::make_string("param at index = "));
  // invoke-virtual {v1, v2},
  // Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  if_block->invoke(OPCODE_INVOKE_VIRTUAL, append_method,
                   {str_builder, str_const});
  // invoke-virtual {v1, v3},
  // Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  if_block->invoke(OPCODE_INVOKE_VIRTUAL, append_method,
                   {str_builder, str_ind});
  // invoke-virtual {v1},
  // Ljava/lang/StringBuilder;.toString:()Ljava/lang/String;
  if_block->invoke(OPCODE_INVOKE_VIRTUAL, str_builder_to_str_method,
                   {str_builder});
  // move-result-object v3
  if_block->move_result(str_res, str_type);

  if_block->invoke(OPCODE_INVOKE_STATIC, builtin, {obj_arg, str_res});
  if_block->ret_void();
  main_block->ret_void();

  auto new_method = method_creator.create();
  TRACE(NULLCHECK, 5, "Created Method : %s", SHOW(new_method->get_code()));
  host_cls->add_method(new_method);
  return new_method;
}

RemoveNullcheckStringArg::Stats RemoveNullcheckStringArg::change_in_cfg(
    cfg::ControlFlowGraph& cfg,
    const TransferMap& transfer_map,
    bool is_virtual) {
  Stats stats{};
  cfg::CFGMutation m(cfg);
  auto params = cfg.get_param_instructions();
  std::unordered_map<size_t, uint32_t> param_index;
  uint32_t arg_index = is_virtual ? -1 : 0;

  reaching_defs::MoveAwareFixpointIterator reaching_defs_iter(cfg);
  reaching_defs_iter.run({});

  for (const auto& mie : InstructionIterable(params)) {
    auto load_insn = mie.insn;
    always_assert(opcode::is_a_load_param(load_insn->opcode()));
    param_index.insert(std::make_pair(load_insn->dest(), arg_index++));
  }

  live_range::MoveAwareChains chains(cfg);
  live_range::DefUseChains du_chains = chains.get_def_use_chains();

  for (cfg::Block* block : cfg.blocks()) {
    auto env = reaching_defs_iter.get_entry_state_at(block);
    if (env.is_bottom()) {
      continue;
    }
    auto ii = InstructionIterable(block);
    for (auto it = ii.begin(); it != ii.end();
         reaching_defs_iter.analyze_instruction(it++->insn, &env)) {
      auto insn = it->insn;
      if (insn->opcode() != OPCODE_INVOKE_STATIC) {
        continue;
      }
      auto iter = transfer_map.find(insn->get_method());
      if (iter == transfer_map.end()) {
        continue;
      }
      IRInstruction* new_insn = new IRInstruction(OPCODE_INVOKE_STATIC);
      if (iter->second.second) {
        // We could have params copied via intermediate registers.
        auto defs = env.get(insn->src(0));
        always_assert(!defs.is_bottom() && !defs.is_top());
        IRInstruction* param_load_insn = nullptr;
        for (auto* def : defs.elements()) {
          if (!opcode::is_a_load_param(def->opcode())) {
            continue;
          }
          if (param_load_insn) {
            /* Multiple param load insns. Should never happen.*/
            param_load_insn = nullptr;
            break;
          }
          param_load_insn = def;
        }
        if (!param_load_insn) {
          /* No param load insn. Should never happen. In any case, skipping this
           * insn is OK. */
          continue;
        }
        auto param_iter = param_index.find(param_load_insn->dest());
        always_assert(param_iter != param_index.end());

        auto use_set = du_chains[param_load_insn];
        if (use_set.size() == 1) {
          for (const auto& p : use_set) {
            always_assert(p.insn == insn);
          }
          // If we have single use which is null check, remove null check.
          m.remove(cfg.find_insn(insn));
          continue;
        }

        auto index = param_iter->second;
        auto tmp_reg = cfg.allocate_temp();
        IRInstruction* cst_insn = new IRInstruction(OPCODE_CONST);
        cst_insn->set_literal(index)->set_dest(tmp_reg);
        new_insn->set_method(iter->second.first)
            ->set_srcs_size(2)
            ->set_src(0, insn->src(0))
            ->set_src(1, tmp_reg);
        m.replace(cfg.find_insn(insn), {cst_insn, new_insn});
      } else {
        new_insn->set_method(iter->second.first)
            ->set_srcs_size(1)
            ->set_src(0, insn->src(0));
        m.replace(cfg.find_insn(insn), {new_insn});
      }
      stats.null_check_insns_changed++;
    }
  }

  m.flush();
  return stats;
}

void RemoveNullcheckStringArg::Stats::report(PassManager& mgr) const {
  mgr.incr_metric("null_check_insns_changed", null_check_insns_changed);
  TRACE(NULLCHECK, 2, "RemoveNullcheckStringArgPass Stats:");
  TRACE(NULLCHECK,
        2,
        "RemoveNullcheckStringArgPass insns changed = %zu",
        null_check_insns_changed);
}

// Computes set of uninstantiable types, also looking at the type system to
// find non-external (and non-native)...
static RemoveNullcheckStringArg s_pass;
<commit_msg>Fix leak<commit_after>/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#include "RemoveNullcheckStringArg.h"

#include "CFGMutation.h"
#include "Creators.h"
#include "DexClass.h"
#include "DexUtil.h"
#include "KotlinNullCheckMethods.h"
#include "LiveRange.h"
#include "PassManager.h"
#include "ReachingDefinitions.h"
#include "Show.h"
#include "Trace.h"
#include "Walkers.h"

using namespace kotlin_nullcheck_wrapper;

void RemoveNullcheckStringArg::run_pass(DexStoresVector& stores,
                                        ConfigFiles& /*conf*/,
                                        PassManager& mgr) {
  TransferMap transfer_map;

  std::unordered_set<DexMethod*> new_methods;
  if (!setup(transfer_map, new_methods)) {
    TRACE(NULLCHECK, 2, "RemoveNullcheckStringArgPass: setup failed");
    return;
  }

  Scope scope = build_class_scope(stores);
  Stats stats = walk::parallel::methods<Stats>(scope, [&](DexMethod* method) {
    auto code = method->get_code();
    if (method->rstate.no_optimizations() || code == nullptr ||
        new_methods.count(method)) {
      return Stats();
    }
    code->build_cfg();
    auto local_stats =
        change_in_cfg(code->cfg(), transfer_map, method->is_virtual());
    code->clear_cfg();
    return local_stats;
  });

  stats.report(mgr);
}

bool RemoveNullcheckStringArg::setup(
    TransferMap& transfer_map, std::unordered_set<DexMethod*>& new_methods) {

  DexMethodRef* builtin_param_V1_3 =
      DexMethod::get_method(CHECK_PARAM_NULL_SIGNATURE_V1_3);
  DexMethodRef* builtin_param_V1_4 =
      DexMethod::get_method(CHECK_PARAM_NULL_SIGNATURE_V1_4);
  DexMethodRef* builtin_expr_V1_3 =
      DexMethod::get_method(CHECK_EXPR_NULL_SIGNATURE_V1_3);
  DexMethodRef* builtin_expr_V1_4 =
      DexMethod::get_method(CHECK_EXPR_NULL_SIGNATURE_V1_4);

  if (builtin_param_V1_3) {
    auto new_check_param_method = get_wrapper_method_with_int_index(
        NEW_CHECK_PARAM_NULL_SIGNATURE_V1_3,
        WRAPPER_CHECK_PARAM_NULL_METHOD_V1_3, builtin_param_V1_3);
    if (new_check_param_method) {
      transfer_map[builtin_param_V1_3] =
          std::make_pair(new_check_param_method, true);
      new_methods.insert(new_check_param_method);
    }
  }
  if (builtin_param_V1_4) {
    auto new_check_param_method = get_wrapper_method_with_int_index(
        NEW_CHECK_PARAM_NULL_SIGNATURE_V1_4,
        WRAPPER_CHECK_PARAM_NULL_METHOD_V1_4, builtin_param_V1_4);
    if (new_check_param_method) {
      transfer_map[builtin_param_V1_4] =
          std::make_pair(new_check_param_method, true);
      new_methods.insert(new_check_param_method);
    }
  }

  if (builtin_expr_V1_3) {
    auto new_check_expr_method = get_wrapper_method(
        NEW_CHECK_EXPR_NULL_SIGNATURE_V1_3, WRAPPER_CHECK_EXPR_NULL_METHOD_V1_3,
        builtin_expr_V1_3);
    if (new_check_expr_method) {
      transfer_map[builtin_expr_V1_3] =
          std::make_pair(new_check_expr_method, false);
      new_methods.insert(new_check_expr_method);
    }
  }

  if (builtin_expr_V1_4) {
    auto new_check_expr_method = get_wrapper_method(
        NEW_CHECK_EXPR_NULL_SIGNATURE_V1_4, WRAPPER_CHECK_EXPR_NULL_METHOD_V1_4,
        builtin_expr_V1_4);
    if (new_check_expr_method) {
      transfer_map[builtin_expr_V1_4] =
          std::make_pair(new_check_expr_method, false);
      new_methods.insert(new_check_expr_method);
    }
  }

  /* If we could not generate suitable wrapper method, giveup. */
  if (new_methods.empty()) {
    return false;
  }

  return true;
}

DexMethod* RemoveNullcheckStringArg::get_wrapper_method(
    const char* wrapper_signature,
    const char* wrapper_name,
    DexMethodRef* builtin) {

  if (DexMethod::get_method(wrapper_signature)) {
    /* Wrapper method already exist. */
    return nullptr;
  }

  auto* host_cls = type_class(builtin->get_class());
  if (!host_cls || host_cls->is_external()) {
    return nullptr;
  }

  DexTypeList* arg_signature =
      DexTypeList::make_type_list({type::java_lang_Object()});
  const auto proto = DexProto::make_proto(type::_void(), arg_signature);
  MethodCreator method_creator(host_cls->get_type(),
                               DexString::make_string(wrapper_name),
                               proto,
                               ACC_PUBLIC | ACC_STATIC);
  auto obj_arg = method_creator.get_local(0);

  auto main_block = method_creator.get_main_block();
  auto if_block = main_block->if_testz(OPCODE_IF_NEZ, obj_arg);
  auto str_type = DexType::get_type("Ljava/lang/String;");
  if (!str_type) {
    return nullptr;
  }

  auto str_const = method_creator.make_local(str_type);

  // const-string v2, "UNKNOWN"
  if_block->load_const(str_const, DexString::make_string("UNKNOWN"));

  if_block->invoke(OPCODE_INVOKE_STATIC, builtin, {obj_arg, str_const});
  if_block->ret_void();
  main_block->ret_void();

  auto new_method = method_creator.create();
  TRACE(NULLCHECK, 5, "Created Method : %s", SHOW(new_method->get_code()));
  host_cls->add_method(new_method);
  return new_method;
}

DexMethod* RemoveNullcheckStringArg::get_wrapper_method_with_int_index(
    const char* wrapper_signature,
    const char* wrapper_name,
    DexMethodRef* builtin) {

  if (DexMethod::get_method(wrapper_signature)) {
    /* Wrapper method already exist. */
    return nullptr;
  }

  auto host_cls = type_class(builtin->get_class());
  if (!host_cls) {
    return nullptr;
  }

  DexTypeList* arg_signature =
      DexTypeList::make_type_list({type::java_lang_Object(), type::_int()});
  const auto proto = DexProto::make_proto(type::_void(), arg_signature);
  MethodCreator method_creator(host_cls->get_type(),
                               DexString::make_string(wrapper_name),
                               proto,
                               ACC_PUBLIC | ACC_STATIC);
  auto obj_arg = method_creator.get_local(0);

  // If the wrapper is going to print the index of the param as a string, we
  // will have to construct a string from the index with additional
  // information as part of the wrapper method.
  auto main_block = method_creator.get_main_block();
  auto int_ind = method_creator.get_local(1);
  auto str_type = DexType::get_type("Ljava/lang/String;");
  auto str_builder_type = DexType::get_type("Ljava/lang/StringBuilder;");
  if (!str_type || !str_builder_type) {
    return nullptr;
  }

  auto to_str_method = DexMethod::get_method(
      "Ljava/lang/Integer;.toString:(I)Ljava/lang/String;");
  auto str_builder_init_method =
      DexMethod::get_method("Ljava/lang/StringBuilder;.<init>:()V");
  auto append_method = DexMethod::get_method(
      "Ljava/lang/StringBuilder;.append:(Ljava/lang/"
      "String;)Ljava/lang/StringBuilder;");
  auto str_builder_to_str_method = DexMethod::get_method(
      "Ljava/lang/StringBuilder;.toString:()Ljava/lang/String;");

  if (!to_str_method || !append_method || !str_builder_to_str_method) {
    return nullptr;
  }
  auto str_ind = method_creator.make_local(str_type);
  auto str_builder = method_creator.make_local(str_builder_type);
  auto str_const = method_creator.make_local(str_type);
  auto str_res = method_creator.make_local(str_type);
  auto if_block = main_block->if_testz(OPCODE_IF_NEZ, obj_arg);

  // invoke-static {v3}, Ljava/lang/Integer;.toString:(I)Ljava/lang/String;
  if_block->invoke(OPCODE_INVOKE_STATIC, to_str_method, {int_ind});
  // move-result-object v3
  if_block->move_result(str_ind, str_type);
  // new-instance v1, Ljava/lang/StringBuilder;
  if_block->new_instance(str_builder_type, str_builder);
  // invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V
  if_block->invoke(OPCODE_INVOKE_DIRECT, str_builder_init_method,
                   {str_builder});
  // const-string v2, "param index = "
  if_block->load_const(str_const, DexString::make_string("param at index = "));
  // invoke-virtual {v1, v2},
  // Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  if_block->invoke(OPCODE_INVOKE_VIRTUAL, append_method,
                   {str_builder, str_const});
  // invoke-virtual {v1, v3},
  // Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  if_block->invoke(OPCODE_INVOKE_VIRTUAL, append_method,
                   {str_builder, str_ind});
  // invoke-virtual {v1},
  // Ljava/lang/StringBuilder;.toString:()Ljava/lang/String;
  if_block->invoke(OPCODE_INVOKE_VIRTUAL, str_builder_to_str_method,
                   {str_builder});
  // move-result-object v3
  if_block->move_result(str_res, str_type);

  if_block->invoke(OPCODE_INVOKE_STATIC, builtin, {obj_arg, str_res});
  if_block->ret_void();
  main_block->ret_void();

  auto new_method = method_creator.create();
  TRACE(NULLCHECK, 5, "Created Method : %s", SHOW(new_method->get_code()));
  host_cls->add_method(new_method);
  return new_method;
}

RemoveNullcheckStringArg::Stats RemoveNullcheckStringArg::change_in_cfg(
    cfg::ControlFlowGraph& cfg,
    const TransferMap& transfer_map,
    bool is_virtual) {
  Stats stats{};
  cfg::CFGMutation m(cfg);
  auto params = cfg.get_param_instructions();
  std::unordered_map<size_t, uint32_t> param_index;
  uint32_t arg_index = is_virtual ? -1 : 0;

  reaching_defs::MoveAwareFixpointIterator reaching_defs_iter(cfg);
  reaching_defs_iter.run({});

  for (const auto& mie : InstructionIterable(params)) {
    auto load_insn = mie.insn;
    always_assert(opcode::is_a_load_param(load_insn->opcode()));
    param_index.insert(std::make_pair(load_insn->dest(), arg_index++));
  }

  live_range::MoveAwareChains chains(cfg);
  live_range::DefUseChains du_chains = chains.get_def_use_chains();

  for (cfg::Block* block : cfg.blocks()) {
    auto env = reaching_defs_iter.get_entry_state_at(block);
    if (env.is_bottom()) {
      continue;
    }
    auto ii = InstructionIterable(block);
    for (auto it = ii.begin(); it != ii.end();
         reaching_defs_iter.analyze_instruction(it++->insn, &env)) {
      auto insn = it->insn;
      if (insn->opcode() != OPCODE_INVOKE_STATIC) {
        continue;
      }
      auto iter = transfer_map.find(insn->get_method());
      if (iter == transfer_map.end()) {
        continue;
      }
      auto new_insn = std::make_unique<IRInstruction>(OPCODE_INVOKE_STATIC);
      if (iter->second.second) {
        // We could have params copied via intermediate registers.
        auto defs = env.get(insn->src(0));
        always_assert(!defs.is_bottom() && !defs.is_top());
        IRInstruction* param_load_insn = nullptr;
        for (auto* def : defs.elements()) {
          if (!opcode::is_a_load_param(def->opcode())) {
            continue;
          }
          if (param_load_insn) {
            /* Multiple param load insns. Should never happen.*/
            param_load_insn = nullptr;
            break;
          }
          param_load_insn = def;
        }
        if (!param_load_insn) {
          /* No param load insn. Should never happen. In any case, skipping this
           * insn is OK. */
          continue;
        }
        auto param_iter = param_index.find(param_load_insn->dest());
        always_assert(param_iter != param_index.end());

        auto use_set = du_chains[param_load_insn];
        if (use_set.size() == 1) {
          for (const auto& p : use_set) {
            always_assert(p.insn == insn);
          }
          // If we have single use which is null check, remove null check.
          m.remove(cfg.find_insn(insn));
          continue;
        }

        auto index = param_iter->second;
        auto tmp_reg = cfg.allocate_temp();
        IRInstruction* cst_insn = new IRInstruction(OPCODE_CONST);
        cst_insn->set_literal(index)->set_dest(tmp_reg);
        new_insn->set_method(iter->second.first)
            ->set_srcs_size(2)
            ->set_src(0, insn->src(0))
            ->set_src(1, tmp_reg);
        m.replace(cfg.find_insn(insn), {cst_insn, new_insn.release()});
      } else {
        new_insn->set_method(iter->second.first)
            ->set_srcs_size(1)
            ->set_src(0, insn->src(0));
        m.replace(cfg.find_insn(insn), {new_insn.release()});
      }
      stats.null_check_insns_changed++;
    }
  }

  m.flush();
  return stats;
}

void RemoveNullcheckStringArg::Stats::report(PassManager& mgr) const {
  mgr.incr_metric("null_check_insns_changed", null_check_insns_changed);
  TRACE(NULLCHECK, 2, "RemoveNullcheckStringArgPass Stats:");
  TRACE(NULLCHECK,
        2,
        "RemoveNullcheckStringArgPass insns changed = %zu",
        null_check_insns_changed);
}

// Computes set of uninstantiable types, also looking at the type system to
// find non-external (and non-native)...
static RemoveNullcheckStringArg s_pass;
<|endoftext|>


<commit_before>/*
 * manager-external.cpp
 *
 * Created on: 21/06/2016
 *
 * This executable starts a manager which can monitor applications not written
 * with the Nornir framework.
 *
 * =========================================================================
 *  Copyright (C) 2015-, Daniele De Sensi (d.desensi.software@gmail.com)
 *
 *  This file is part of nornir.
 *
 *  nornir is free software: you can redistribute it and/or
 *  modify it under the terms of the Lesser GNU General Public
 *  License as published by the Free Software Foundation, either
 *  version 3 of the License, or (at your option) any later version.

 *  nornir 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
 *  Lesser GNU General Public License for more details.
 *
 *  You should have received a copy of the Lesser GNU General Public
 *  License along with nornir.
 *  If not, see <http://www.gnu.org/licenses/>.
 *
 * =========================================================================
 */

#include <vector>
#include <iostream>
#include <fstream>
#include <sys/mman.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <stdlib.h>
#include "../src/manager.hpp"
#include "../src/manager-multi.hpp"
#include "../src/external/mammut/mammut/external/papi-5.5.1/src/papi.h"
#include <tclap/CmdLine.h>

using namespace nornir;

#undef DEBUG
#undef DEBUGB

#define DEBUG_MBB

#ifdef DEBUG_MBB
#define DEBUG(x) do { cerr << "[BlackBox Manager] " << x << endl; } while (0)
#define DEBUGB(x) do {x;} while(0)
#else
#define DEBUG(x)
#define DEBUGB(x)
#endif

struct ScheduledProgram;

typedef struct ScheduledProgram{
    friend std::ostream& operator <<(std::ostream& os, const ScheduledProgram& instance);
    double start;
    double minPerfRequired;
    std::vector<std::string> program;
    pid_t pid;
}ScheduledProgram;

std::ostream& operator <<(std::ostream& os, const ScheduledProgram& instance){
    os << "Starts at: [" << instance.start << "] ";
    os << "Minimum performance required: [" << instance.minPerfRequired << "] ";
    os << "Program: [";
    for(size_t i = 0; i < instance.program.size(); i++){
        os << instance.program[i] << " ";
    }
    os << "]";
    return os;
}

void initializeParameters(Parameters& p){
    p.contractType = CONTRACT_PERF_MAX;
    p.samplingIntervalCalibration = 100;
    p.samplingIntervalSteady = 1000;
    p.strategySelection = STRATEGY_SELECTION_LEARNING;
    p.strategyPredictionPerformance = STRATEGY_PREDICTION_PERFORMANCE_USLP;
    p.knobMappingEnabled = false;
    p.knobHyperthreadingEnabled = false;
}

static std::string pidToString(pid_t pid){
    stringstream out;
    out << pid;
    return out.str();
}

int main(int argc, char * argv[]){
    std::vector<long> pids;
    std::vector<double> pidPerfs;
    std::vector<ScheduledProgram> scheduledPrograms;
    std::string logDir;
    std::string parametersFile;
    double powerCap;
    double startTime = mammut::utils::getMillisecondsTime();
    try {
        TCLAP::CmdLine cmd("Runs Nornir on already existing processes, coordinating "
                           "their requirements. It is also possible to specify new "
                           "programs to be ran. One parameter between --pid or "
                           "--schedule must be specified.", ' ', "1.0");
        TCLAP::ValueArg<std::string> logArg("l", "logdir", "Directory where the log files will be stored. (default = ./logs)", false, "./logs/", "string", cmd);
        TCLAP::ValueArg<std::string> parametersArg("r", "parameters", "Nornir parameters file. Can be specified only when only one application needs "
                                                   "to be controlled.", false, "", "string", cmd);
        TCLAP::ValueArg<double> capArg("c", "cap", "Power cap, expressed in watts. If 0, no power cap will "
                                       "be applied and we will just maximise performance for each submitted "
                                       "application. (default = 0).", false, 0, "double", cmd);
        TCLAP::MultiArg<long> pidArg("p", "pid", "PID of a running process.", false, "long", cmd);
        TCLAP::MultiArg<double> perfArg("m", "minperf", "Minimum performance required (one value for each PID, in the same "
                                     "order. It is expressed as a percentage of the maximum achievable performance. For "
                                     "example, a value of 80 means that we would like to do not decrease the performance "
                                     "of the application below the 80% of its maximum. 0 means no specific requirement.",
                                     false, "double", cmd);
        TCLAP::ValueArg<std::string> scheduleArg("s", "schedule", "Name of the file containing the schedule of "
                                                  "the programs to be run. The file has multiple lines, each one with "
                                                  "the following syntax:\n"
                                                  "StartTime MinPerformanceRequired ProgramPath ProgramArguments\n"
                                                  "The start time is a relative offset starting from the start of this "
                                                  "executable.\n"
                                                  "MinPerformanceRequired is a value in the range [0, 100] "
                                                  "representing the minimum required performance, expressed as a percentage "
                                                  "of the maximum achievable performance. For example, a value of 80 means "
                                                  "that we would like to do not decrease the performance of the application "
                                                  "below the 80% of its maximum. 0 means no specific requirement.\n"
                                                  "The lines must be sorted increasingly by start time.",
                                                  false, "", "string", cmd);
        cmd.parse(argc, argv);

        /* Validate parameters. */
        if(!pidArg.getValue().size() &&
           !scheduleArg.getValue().compare("")){
            std::cerr << "[ERROR] One between --pid or --schedule must be specified." << std::endl;
            return -1;
        }

        if(pidArg.getValue().size() !=
           perfArg.getValue().size()){
            std::cerr << "[ERROR] You must specify one minimum performance requirement for each pid." << std::endl;
            return -1;
        }

        logDir = logArg.getValue();
        powerCap = capArg.getValue();
        pids = pidArg.getValue();
        pidPerfs = perfArg.getValue();
        parametersFile = parametersArg.getValue();

        if(pids.size()){
            std::cerr << "Sorry, --pid is still not supported. This is mainly due to difficulties in attaching to an already running "
                         "(non child) process to read the counters. Moreover, even if we could attach, we would not be able to monitor "
                         "the already created childrens/threads." << std::endl;
        }

        if(!mammut::utils::existsDirectory(logDir)){
            std::cerr << "Specified logging directory doesn't exist. Please create it before running." << std::endl;
            return -1;
        }

        /* Load schedule. */
        std::ifstream scheduleFile(scheduleArg.getValue());
        std::string line;
        while(std::getline(scheduleFile, line)){
            std::vector<std::string> fields;
            fields = mammut::utils::split(line, ' ');
            if(fields.size() < 3){
                std::cerr << "Invalid scheduled line: " << line << ". You must at least "
                             "specify the scheduled time, minimum performance required "
                             "and program name." << std::endl;
                return -1;
            }
            ScheduledProgram sp;
            sp.start = mammut::utils::stringToDouble(fields[0]);
            sp.minPerfRequired = mammut::utils::stringToDouble(fields[1]);
            sp.program = std::vector<std::string>(fields.begin() + 2, fields.end());
            DEBUG("Read schedule: " << sp);
            scheduledPrograms.push_back(sp);
        }
        scheduleFile.close();
    }catch (TCLAP::ArgException &e){
        std::cerr << "error: " << e.error() << " for arg " << e.argId() << std::endl;
        return -1;
    }

    if(parametersFile.compare("") && scheduledPrograms.size() > 1){
        std::cerr << "Parameter file can only be specified when you want to control only one application." << std::endl;
        return -1;
    }

    ManagerMultiConfiguration mmc;
    mmc.powerCap = powerCap;
    ManagerMulti mm(mmc);
    bool multiManagerNeeded = false;
    if(pids.size() + scheduledPrograms.size() > 1){
        multiManagerNeeded = true;
        mm.start();
        DEBUG("Multi manager started.");
    }

    /* First we add the already running pid. */
    for(size_t i = 0; i < pids.size(); i++){
        Parameters p;
        if(parametersFile.compare("")){
            p.load(parametersFile);
        }else{
            initializeParameters(p);
        }
        std::string logPrefix = logDir + "/" + pidToString(pids.at(i));
        p.observer = new Observer(logPrefix + "_stats.csv",
                                  logPrefix + "_calibration.csv",
                                  logPrefix + "_summary.csv",
                                  mammut::utils::getMillisecondsTime() - startTime);
        ManagerBlackBox* m = new ManagerBlackBox(pids.at(i), p);
        if(multiManagerNeeded){
            mm.addManager(m, pidPerfs.at(i));
            DEBUG("Added PID: " << pids.at(i) <<
                  " to the multimanager with minPerf " << pidPerfs.at(i));
        }else{
            m->start();
            DEBUG("Started PID: " << pids.at(i));
            m->join();
            delete m;
            delete p.observer;
        }
    }

    /* Then we start following the schedule. */
    volatile bool* started = (bool*) mmap(NULL, sizeof(volatile bool), PROT_READ | PROT_WRITE,
                           MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    volatile bool* handlerCreated = (bool*) mmap(NULL, sizeof(volatile bool), PROT_READ | PROT_WRITE,
                                  MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    if(started == MAP_FAILED || handlerCreated == MAP_FAILED){
        std::cerr << "mmap failed."<< std::endl;
        return -1;
    }

    *started = false;
    *handlerCreated = false;
    double lastStart = 0;

    for(size_t i = 0; i < scheduledPrograms.size(); i++){
        ScheduledProgram& sp = scheduledPrograms[i];
        sleep(sp.start - lastStart);
        lastStart = sp.start;

        void* mmem = (void*) mmap(NULL, sizeof(ManagerBlackBox), PROT_READ | PROT_WRITE,
                                  MAP_SHARED | MAP_ANONYMOUS, -1, 0);
        if(mmem == MAP_FAILED){
            std::cerr << "mmap failed." << std::endl;
            return -1;
        }

        pid_t pid = fork();
        if(pid == -1){
            std::cerr << "Fork failed." << std::endl;
            return -1;
        }else if(pid){
            /* Father - Manager. */
            sp.pid = pid;
            Parameters p;
            if(parametersFile.compare("")){
                p.load(parametersFile);
            }else{
                initializeParameters(p);
            }
            stringstream out;
            out << sp.start;
            std::string logPrefix = logDir + "/" + out.str() + "_" + mammut::utils::split(sp.program.at(0), '/').back();
            p.observer = new Observer(logPrefix + "_stats.csv",
                                      logPrefix + "_calibration.csv",
                                      logPrefix + "_summary.csv",
                                      mammut::utils::getMillisecondsTime() - startTime);
            while(!*started){;}

            ManagerBlackBox* m = new (mmem) ManagerBlackBox(pid, p);
            *handlerCreated = true;
            DEBUG("Started scheduled: " << sp.program[0] << " with pid " << pid << " on manager " << m);

            if(multiManagerNeeded){
                mm.addManager(m, sp.minPerfRequired);
                // Wait for the child to reset the flags.
                while(*handlerCreated){;}
            }else{
                m->start();
                m->join();
                waitpid(pid, NULL, 0);
                m->~ManagerBlackBox(); // Because created with placement new
                delete p.observer;
            }
        }else{
            /* Child - Application */
            *started = true;
            // If the handler is not created, we would not catch the counters of
            // the threads/processes created by this process
            while(!*handlerCreated){;}

            extern char** environ;
            char** arguments = new char*[sp.program.size() + 1];
            for(size_t i = 0; i < sp.program.size(); i++){
                arguments[i] = new char[sp.program[i].size() + 1];
                strcpy(arguments[i], sp.program[i].c_str());
            }
            arguments[sp.program.size()] = NULL;
            DEBUG("Running program " << sp.program[0]);
            /* Resets flag for next scheduled application. */
            *started = false;
            *handlerCreated = false;
            if(execve(arguments[0], arguments, environ) == -1){
                std::cerr << "Impossible to run the specified executable. Terminating the manager." << std::endl;
                ((ManagerBlackBox*) mmem)->terminate();
                return -1;
            }
            return -1; // execve never returns (except in the error case above).
        }
    }

    DEBUG("All specified programs have been added to the monitoring system. Waiting for their termination...");
    /* All programs scheduled, now wait for termination and clean. */
    if(multiManagerNeeded){
        for(size_t i = 0; i < scheduledPrograms.size(); i++){
            waitpid(scheduledPrograms[i].pid, NULL, 0);
            DEBUG(scheduledPrograms[i].pid << " PID terminated (" << scheduledPrograms[i].program << ")");
        }

        for(size_t i = 0; i < pids.size() + scheduledPrograms.size(); i++){
            ManagerBlackBox* m;
            while((m = (ManagerBlackBox*) mm.getTerminatedManager()) == NULL){
                sleep(1);
            }
            DEBUG("Manager (" << m << ") terminated.");
            if(mammut::utils::contains(pids, (long) m->getPid())){
                delete m;
            }else{
                m->~ManagerBlackBox(); // Because we used placement new
            }
            //delete m->_p.observer;
        }
    }
}

<commit_msg>[FIX] Termination bug on manager-blackbox.<commit_after>/*
 * manager-external.cpp
 *
 * Created on: 21/06/2016
 *
 * This executable starts a manager which can monitor applications not written
 * with the Nornir framework.
 *
 * =========================================================================
 *  Copyright (C) 2015-, Daniele De Sensi (d.desensi.software@gmail.com)
 *
 *  This file is part of nornir.
 *
 *  nornir is free software: you can redistribute it and/or
 *  modify it under the terms of the Lesser GNU General Public
 *  License as published by the Free Software Foundation, either
 *  version 3 of the License, or (at your option) any later version.

 *  nornir 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
 *  Lesser GNU General Public License for more details.
 *
 *  You should have received a copy of the Lesser GNU General Public
 *  License along with nornir.
 *  If not, see <http://www.gnu.org/licenses/>.
 *
 * =========================================================================
 */

#include <vector>
#include <iostream>
#include <fstream>
#include <sys/mman.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <stdlib.h>
#include "../src/manager.hpp"
#include "../src/manager-multi.hpp"
#include "../src/external/mammut/mammut/external/papi-5.5.1/src/papi.h"
#include <tclap/CmdLine.h>

using namespace nornir;

#undef DEBUG
#undef DEBUGB

#define DEBUG_MBB

#ifdef DEBUG_MBB
#define DEBUG(x) do { cerr << "[BlackBox Manager] " << x << endl; } while (0)
#define DEBUGB(x) do {x;} while(0)
#else
#define DEBUG(x)
#define DEBUGB(x)
#endif

struct ScheduledProgram;

typedef struct ScheduledProgram{
    friend std::ostream& operator <<(std::ostream& os, const ScheduledProgram& instance);
    double start;
    double minPerfRequired;
    std::vector<std::string> program;
    pid_t pid;
}ScheduledProgram;

std::ostream& operator <<(std::ostream& os, const ScheduledProgram& instance){
    os << "Starts at: [" << instance.start << "] ";
    os << "Minimum performance required: [" << instance.minPerfRequired << "] ";
    os << "Program: [";
    for(size_t i = 0; i < instance.program.size(); i++){
        os << instance.program[i] << " ";
    }
    os << "]";
    return os;
}

void initializeParameters(Parameters& p){
    p.contractType = CONTRACT_PERF_MAX;
    p.samplingIntervalCalibration = 100;
    p.samplingIntervalSteady = 1000;
    p.strategySelection = STRATEGY_SELECTION_LEARNING;
    p.strategyPredictionPerformance = STRATEGY_PREDICTION_PERFORMANCE_USLP;
    p.knobMappingEnabled = false;
    p.knobHyperthreadingEnabled = false;
}

static std::string pidToString(pid_t pid){
    stringstream out;
    out << pid;
    return out.str();
}

int main(int argc, char * argv[]){
    std::vector<long> pids;
    std::vector<double> pidPerfs;
    std::vector<ScheduledProgram> scheduledPrograms;
    std::string logDir;
    std::string parametersFile;
    double powerCap;
    double startTime = mammut::utils::getMillisecondsTime();
    try {
        TCLAP::CmdLine cmd("Runs Nornir on already existing processes, coordinating "
                           "their requirements. It is also possible to specify new "
                           "programs to be ran. One parameter between --pid or "
                           "--schedule must be specified.", ' ', "1.0");
        TCLAP::ValueArg<std::string> logArg("l", "logdir", "Directory where the log files will be stored. (default = ./logs)", false, "./logs/", "string", cmd);
        TCLAP::ValueArg<std::string> parametersArg("r", "parameters", "Nornir parameters file. Can be specified only when only one application needs "
                                                   "to be controlled.", false, "", "string", cmd);
        TCLAP::ValueArg<double> capArg("c", "cap", "Power cap, expressed in watts. If 0, no power cap will "
                                       "be applied and we will just maximise performance for each submitted "
                                       "application. (default = 0).", false, 0, "double", cmd);
        TCLAP::MultiArg<long> pidArg("p", "pid", "PID of a running process.", false, "long", cmd);
        TCLAP::MultiArg<double> perfArg("m", "minperf", "Minimum performance required (one value for each PID, in the same "
                                     "order. It is expressed as a percentage of the maximum achievable performance. For "
                                     "example, a value of 80 means that we would like to do not decrease the performance "
                                     "of the application below the 80% of its maximum. 0 means no specific requirement.",
                                     false, "double", cmd);
        TCLAP::ValueArg<std::string> scheduleArg("s", "schedule", "Name of the file containing the schedule of "
                                                  "the programs to be run. The file has multiple lines, each one with "
                                                  "the following syntax:\n"
                                                  "StartTime MinPerformanceRequired ProgramPath ProgramArguments\n"
                                                  "The start time is a relative offset starting from the start of this "
                                                  "executable.\n"
                                                  "MinPerformanceRequired is a value in the range [0, 100] "
                                                  "representing the minimum required performance, expressed as a percentage "
                                                  "of the maximum achievable performance. For example, a value of 80 means "
                                                  "that we would like to do not decrease the performance of the application "
                                                  "below the 80% of its maximum. 0 means no specific requirement.\n"
                                                  "The lines must be sorted increasingly by start time.",
                                                  false, "", "string", cmd);
        cmd.parse(argc, argv);

        /* Validate parameters. */
        if(!pidArg.getValue().size() &&
           !scheduleArg.getValue().compare("")){
            std::cerr << "[ERROR] One between --pid or --schedule must be specified." << std::endl;
            return -1;
        }

        if(pidArg.getValue().size() !=
           perfArg.getValue().size()){
            std::cerr << "[ERROR] You must specify one minimum performance requirement for each pid." << std::endl;
            return -1;
        }

        logDir = logArg.getValue();
        powerCap = capArg.getValue();
        pids = pidArg.getValue();
        pidPerfs = perfArg.getValue();
        parametersFile = parametersArg.getValue();

        if(pids.size()){
            std::cerr << "Sorry, --pid is still not supported. This is mainly due to difficulties in attaching to an already running "
                         "(non child) process to read the counters. Moreover, even if we could attach, we would not be able to monitor "
                         "the already created childrens/threads." << std::endl;
        }

        if(!mammut::utils::existsDirectory(logDir)){
            std::cerr << "Specified logging directory doesn't exist. Please create it before running." << std::endl;
            return -1;
        }

        /* Load schedule. */
        std::ifstream scheduleFile(scheduleArg.getValue());
        std::string line;
        while(std::getline(scheduleFile, line)){
            std::vector<std::string> fields;
            fields = mammut::utils::split(line, ' ');
            if(fields.size() < 3){
                std::cerr << "Invalid scheduled line: " << line << ". You must at least "
                             "specify the scheduled time, minimum performance required "
                             "and program name." << std::endl;
                return -1;
            }
            ScheduledProgram sp;
            sp.start = mammut::utils::stringToDouble(fields[0]);
            sp.minPerfRequired = mammut::utils::stringToDouble(fields[1]);
            sp.program = std::vector<std::string>(fields.begin() + 2, fields.end());
            DEBUG("Read schedule: " << sp);
            scheduledPrograms.push_back(sp);
        }
        scheduleFile.close();
    }catch (TCLAP::ArgException &e){
        std::cerr << "error: " << e.error() << " for arg " << e.argId() << std::endl;
        return -1;
    }

    if(parametersFile.compare("") && scheduledPrograms.size() > 1){
        std::cerr << "Parameter file can only be specified when you want to control only one application." << std::endl;
        return -1;
    }

    ManagerMultiConfiguration mmc;
    mmc.powerCap = powerCap;
    ManagerMulti mm(mmc);
    bool multiManagerNeeded = false;
    if(pids.size() + scheduledPrograms.size() > 1){
        multiManagerNeeded = true;
        mm.start();
        DEBUG("Multi manager started.");
    }

    /* First we add the already running pid. */
    for(size_t i = 0; i < pids.size(); i++){
        Parameters p;
        if(parametersFile.compare("")){
            p.load(parametersFile);
        }else{
            initializeParameters(p);
        }
        std::string logPrefix = logDir + "/" + pidToString(pids.at(i));
        p.observer = new Observer(logPrefix + "_stats.csv",
                                  logPrefix + "_calibration.csv",
                                  logPrefix + "_summary.csv",
                                  mammut::utils::getMillisecondsTime() - startTime);
        ManagerBlackBox* m = new ManagerBlackBox(pids.at(i), p);
        if(multiManagerNeeded){
            mm.addManager(m, pidPerfs.at(i));
            DEBUG("Added PID: " << pids.at(i) <<
                  " to the multimanager with minPerf " << pidPerfs.at(i));
        }else{
            m->start();
            DEBUG("Started PID: " << pids.at(i));
            m->join();
            delete m;
            delete p.observer;
        }
    }

    /* Then we start following the schedule. */
    volatile bool* started = (bool*) mmap(NULL, sizeof(volatile bool), PROT_READ | PROT_WRITE,
                           MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    volatile bool* handlerCreated = (bool*) mmap(NULL, sizeof(volatile bool), PROT_READ | PROT_WRITE,
                                  MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    if(started == MAP_FAILED || handlerCreated == MAP_FAILED){
        std::cerr << "mmap failed."<< std::endl;
        return -1;
    }

    *started = false;
    *handlerCreated = false;
    double lastStart = 0;

    for(size_t i = 0; i < scheduledPrograms.size(); i++){
        ScheduledProgram& sp = scheduledPrograms[i];
        sleep(sp.start - lastStart);
        lastStart = sp.start;

        void* mmem = (void*) mmap(NULL, sizeof(ManagerBlackBox), PROT_READ | PROT_WRITE,
                                  MAP_SHARED | MAP_ANONYMOUS, -1, 0);
        if(mmem == MAP_FAILED){
            std::cerr << "mmap failed." << std::endl;
            return -1;
        }

        pid_t pid = fork();
        if(pid == -1){
            std::cerr << "Fork failed." << std::endl;
            return -1;
        }else if(pid){
            /* Father - Manager. */
            sp.pid = pid;
            Parameters p;
            if(parametersFile.compare("")){
                p.load(parametersFile);
            }else{
                initializeParameters(p);
            }
            stringstream out;
            out << sp.start;
            std::string logPrefix = logDir + "/" + out.str() + "_" + mammut::utils::split(sp.program.at(0), '/').back();
            p.observer = new Observer(logPrefix + "_stats.csv",
                                      logPrefix + "_calibration.csv",
                                      logPrefix + "_summary.csv",
                                      mammut::utils::getMillisecondsTime() - startTime);
            while(!*started){;}

            ManagerBlackBox* m = new (mmem) ManagerBlackBox(pid, p);
            *handlerCreated = true;
            DEBUG("Started scheduled: " << sp.program[0] << " with pid " << pid << " on manager " << m);

            if(multiManagerNeeded){
                mm.addManager(m, sp.minPerfRequired);
                // Wait for the child to reset the flags.
                while(*handlerCreated){;}
            }else{
                m->start();
                waitpid(pid, NULL, 0);
                m->join();
                m->~ManagerBlackBox(); // Because created with placement new
                delete p.observer;
            }
        }else{
            /* Child - Application */
            *started = true;
            // If the handler is not created, we would not catch the counters of
            // the threads/processes created by this process
            while(!*handlerCreated){;}

            extern char** environ;
            char** arguments = new char*[sp.program.size() + 1];
            for(size_t i = 0; i < sp.program.size(); i++){
                arguments[i] = new char[sp.program[i].size() + 1];
                strcpy(arguments[i], sp.program[i].c_str());
            }
            arguments[sp.program.size()] = NULL;
            DEBUG("Running program " << sp.program[0]);
            /* Resets flag for next scheduled application. */
            *started = false;
            *handlerCreated = false;
            if(execve(arguments[0], arguments, environ) == -1){
                std::cerr << "Impossible to run the specified executable. Terminating the manager." << std::endl;
                ((ManagerBlackBox*) mmem)->terminate();
                return -1;
            }
            return -1; // execve never returns (except in the error case above).
        }
    }

    DEBUG("All specified programs have been added to the monitoring system. Waiting for their termination...");
    /* All programs scheduled, now wait for termination and clean. */
    if(multiManagerNeeded){
        for(size_t i = 0; i < scheduledPrograms.size(); i++){
            waitpid(scheduledPrograms[i].pid, NULL, 0);
            DEBUG(scheduledPrograms[i].pid << " PID terminated (" << scheduledPrograms[i].program << ")");
        }

        for(size_t i = 0; i < pids.size() + scheduledPrograms.size(); i++){
            ManagerBlackBox* m;
            while((m = (ManagerBlackBox*) mm.getTerminatedManager()) == NULL){
                sleep(1);
            }
            DEBUG("Manager (" << m << ") terminated.");
            if(mammut::utils::contains(pids, (long) m->getPid())){
                delete m;
            }else{
                m->~ManagerBlackBox(); // Because we used placement new
            }
            //delete m->_p.observer;
        }
    }
}

<|endoftext|>


<commit_before>// ------------------------------------------------------------------------
// textdebug.cpp: Implementation of console logging subsystem.
// Copyright (C) 1999-2002,2004 Kai Vehmanen
//
// Attributes:
//     eca-style-version: 2
//
// 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 2 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, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
// ------------------------------------------------------------------------

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <string>
#include <vector>

#include <eca-logger-interface.h>
#include <kvu_utils.h>

#include "textdebug.h"

#ifdef ECA_USE_NCURSES_H
#include <ncurses.h>
#include <term.h> /* for setupterm() */
#elif ECA_USE_NCURSES_NCURSES_H
#include <ncurses/ncurses.h>
#include <ncurses/term.h> /* for setupterm() */
#elif ECA_USE_CURSES_H
#include <curses.h>
#include <term.h> /* for setupterm() */
#endif

using namespace std;

/**
 * Set terminal width used in pretty-printing ecasound console output.
 *
 * Value of 79 guarantees that output is readable even in 80x25 terminal mode.
 */
const static int tb_terminal_width = 74;

/**
 * Wraps text 'msg' by adding <newline> + "... " breaks so that none
 * of the lines exceed 'width' characteds.
 */
static string tb_wrap(const string& msg, int width, int offset)
{
  int counter = offset;
  vector<string> vec = kvu_string_to_vector(msg, ' ');
  string result;
  for(vector<string>::const_iterator p = vec.begin(); p != vec.end(); ) {
    /* check if adding the next token would go over the limit */
    if ((counter + static_cast<int>(p->size())) > width) {
      result += "\n... ";
      counter = 0;
    }
    result += *p;
    /* check if line already contains a newline */
    if (find(p->begin(), p->end(), '\n') != p->end()) {
      counter = 0;
    }
    else {
      counter += p->size();
    }
    ++p;
    if (p != vec.end()) {
      result += " ";
    }
  }
  return result;
}

void TEXTDEBUG::stream(std::ostream* dos)
{
  dostream_repp = dos;
}

std::ostream* TEXTDEBUG::stream(void)
{
  return dostream_repp;
}

void TEXTDEBUG::do_flush(void)
{
  dostream_repp->flush();
}

void TEXTDEBUG::do_msg(ECA_LOGGER::Msg_level_t level, const std::string& module_name, const std::string& log_message)
{
  if (is_log_level_set(level) == true) {
    int offset = 0;

    if (level == ECA_LOGGER::subsystems) {
#if defined(ECA_USE_NCURSES_H) || defined(ECA_USE_NCURSES_NCURSES_H) || defined(ECA_USE_CURSES_H)
      *dostream_repp << "- [ ";
      putp(tigetstr("bold"));
      offset += 4;
#endif
    }
    else if (is_log_level_set(ECA_LOGGER::module_names) == true &&
	     level != ECA_LOGGER::eiam_return_values) {
      *dostream_repp << "("
		     << std::string(module_name.begin(),
				    find(module_name.begin(), module_name.end(), '.'))
		     << ") ";
      offset += module_name.size() + 2;
    }

    *dostream_repp << tb_wrap(log_message, tb_terminal_width, offset);

    if (level == ECA_LOGGER::subsystems) {
#if defined(ECA_USE_NCURSES_H) || defined(ECA_USE_NCURSES_NCURSES_H) || defined(ECA_USE_CURSES_H)
      putp(tigetstr("sgr0"));
      *dostream_repp << " ] ";
#else
      *dostream_repp << " ] ";
#endif
      if (log_message.size() < static_cast<int>(tb_terminal_width)) {
	for (unsigned char n = 0; n < (tb_terminal_width - log_message.size() - 1); n++) *dostream_repp << "-";
      }
    }

    *dostream_repp << endl;
  }
}

TEXTDEBUG::TEXTDEBUG(void)
{
  dostream_repp = &std::cout;
}

TEXTDEBUG::~TEXTDEBUG(void)
{
  flush();
}
<commit_msg>Fix compiliation with egcs-2.91.66.<commit_after>// ------------------------------------------------------------------------
// textdebug.cpp: Implementation of console logging subsystem.
// Copyright (C) 1999-2002,2004-2005 Kai Vehmanen
//
// Attributes:
//     eca-style-version: 2
//
// 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 2 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, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
// ------------------------------------------------------------------------

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

#include <eca-logger-interface.h>
#include <kvu_utils.h>

#include "textdebug.h"

#ifdef ECA_USE_NCURSES_H
#include <ncurses.h>
#include <term.h> /* for setupterm() */
#elif ECA_USE_NCURSES_NCURSES_H
#include <ncurses/ncurses.h>
#include <ncurses/term.h> /* for setupterm() */
#elif ECA_USE_CURSES_H
#include <curses.h>
#include <term.h> /* for setupterm() */
#endif

using namespace std;

/**
 * Set terminal width used in pretty-printing ecasound console output.
 *
 * Value of 79 guarantees that output is readable even in 80x25 terminal mode.
 */
const static int tb_terminal_width = 74;

/**
 * Wraps text 'msg' by adding <newline> + "... " breaks so that none
 * of the lines exceed 'width' characteds.
 */
static string tb_wrap(const string& msg, int width, int offset)
{
  int counter = offset;
  vector<string> vec = kvu_string_to_vector(msg, ' ');
  string result;
  for(vector<string>::const_iterator p = vec.begin(); p != vec.end(); ) {
    /* check if adding the next token would go over the limit */
    if ((counter + static_cast<int>(p->size())) > width) {
      result += "\n... ";
      counter = 0;
    }
    result += *p;
    /* check if line already contains a newline */
    if (find(p->begin(), p->end(), '\n') != p->end()) {
      counter = 0;
    }
    else {
      counter += p->size();
    }
    ++p;
    if (p != vec.end()) {
      result += " ";
    }
  }
  return result;
}

void TEXTDEBUG::stream(std::ostream* dos)
{
  dostream_repp = dos;
}

std::ostream* TEXTDEBUG::stream(void)
{
  return dostream_repp;
}

void TEXTDEBUG::do_flush(void)
{
  dostream_repp->flush();
}

void TEXTDEBUG::do_msg(ECA_LOGGER::Msg_level_t level, const std::string& module_name, const std::string& log_message)
{
  if (is_log_level_set(level) == true) {
    int offset = 0;

    if (level == ECA_LOGGER::subsystems) {
#if defined(ECA_USE_NCURSES_H) || defined(ECA_USE_NCURSES_NCURSES_H) || defined(ECA_USE_CURSES_H)
      *dostream_repp << "- [ ";
      putp(tigetstr("bold"));
      offset += 4;
#endif
    }
    else if (is_log_level_set(ECA_LOGGER::module_names) == true &&
	     level != ECA_LOGGER::eiam_return_values) {
      *dostream_repp << "("
		     << std::string(module_name.begin(),
				    find(module_name.begin(), module_name.end(), '.'))
		     << ") ";
      offset += module_name.size() + 2;
    }

    *dostream_repp << tb_wrap(log_message, tb_terminal_width, offset);

    if (level == ECA_LOGGER::subsystems) {
#if defined(ECA_USE_NCURSES_H) || defined(ECA_USE_NCURSES_NCURSES_H) || defined(ECA_USE_CURSES_H)
      putp(tigetstr("sgr0"));
      *dostream_repp << " ] ";
#else
      *dostream_repp << " ] ";
#endif
      if (log_message.size() < static_cast<int>(tb_terminal_width)) {
	for (unsigned char n = 0; n < (tb_terminal_width - log_message.size() - 1); n++) *dostream_repp << "-";
      }
    }

    *dostream_repp << endl;
  }
}

TEXTDEBUG::TEXTDEBUG(void)
{
  dostream_repp = &std::cout;
}

TEXTDEBUG::~TEXTDEBUG(void)
{
  flush();
}
<|endoftext|>