/*===============================================================*/
/*                                                               */
/*                            sgd.h                              */
/*                                                               */
/*          Top-level hardware function declaration              */
/*                                                               */
/*===============================================================*/

#include "../host/typedefs.h"

// top-level function declaration
#pragma SDS data mem_attribute(data:NON_CACHEABLE|PHYSICAL_CONTIGUOUS, label:CACHEABLE|PHYSICAL_CONTIGUOUS, theta:CACHEABLE|PHYSICAL_CONTIGUOUS)
#pragma SDS data data_mover(data:AXIDMA_SG, label:AXIFIFO, theta:AXIDMA_SG)
#pragma SDS data sys_port(data:AFI, label:AFI, theta:AFI)
#pragma SDS data zero_copy(theta[0:NUM_FEATURES / F_VECTOR_SIZE])
#pragma SDS data copy(data[0:NUM_FEATURES * NUM_TRAINING/D_VECTOR_SIZE])
#pragma SDS data zero_copy(label[0:NUM_TRAINING/L_VECTOR_SIZE])
#pragma SDS data access_pattern(data:SEQUENTIAL, label:SEQUENTIAL, theta:SEQUENTIAL)

void SgdLR( VectorDataType    data[NUM_FEATURES * NUM_TRAINING / D_VECTOR_SIZE],
            VectorLabelType   label[NUM_TRAINING / L_VECTOR_SIZE],
            VectorFeatureType theta[NUM_FEATURES / F_VECTOR_SIZE],
            bool readLabels,
            bool writeOutput);


#include "AccelSchedule.h"
#include "AccelTest.h"
#include "Timer.h"

static Timer timers[N_LAYERS] = {
  "xl-FC3",
  "xl-FC2",
  "xl-FC1",
  "xl-Conv6",
  "xl-Conv5",
  "xl-Conv4",
  "xl-Conv3",
  "xl-Conv2",
  "xl-Conv1"
};

// -----------------------------------------------------------------------
// Each layer may need multiple invocations of the accelerator due to
// limited on-chip storage of weights.
//
// This function computes the number of invocations needed and splits
// the weights for each invocation.
//
// We make the following assumptions now:
// 1. Only 1 output image per invocation
// 2. wt_mem is large enough to hold the weights for at least 1 image
// -----------------------------------------------------------------------
void compute_accel_schedule(
    Word* wt,
    Word* kh,
    unsigned n_inputs,
    unsigned n_outputs,
    unsigned width,
    const ap_uint<2> layer_type,  // 0=conv1, 1=conv, 2=dense
    const ap_uint<1> max_pool,
    AccelSchedule &schedule
) {
  assert (wt != NULL);
  assert (kh != NULL);

  ap_uint<3> layer_mode = 0;
  layer_mode(2,1) = layer_type(1,0);

  // for conv layers
  unsigned width_o = (max_pool==0) ? width : width / 2;
  // imgs_per_batch is the number of output images to compute per batch
  unsigned imgs_per_batch = 0;
  if (layer_type == LAYER_CONV1 || layer_type == LAYER_CONV)
    imgs_per_batch = find_conv_batch_size(width, width_o, n_inputs, n_outputs);

  // recalculate some values if dense layer
  if (layer_type == LAYER_DENSE || layer_type == LAYER_LAST) {
    width_o = 1;
    imgs_per_batch = find_dense_batch_size(n_inputs, n_outputs);
  }

  assert (imgs_per_batch != 0);

  unsigned n_batches = n_outputs / imgs_per_batch;
  schedule.resize(n_batches);

  // divide up the weights according to the value of imgs_per_batch
  unsigned idx = 0;
  for (unsigned o = 0; o < n_outputs; o+=imgs_per_batch, idx++) {
    layer_mode[0] = (o==0) ? 1 : 0;

    // add a new invocation to the schedule
    schedule[idx].n_inputs = n_inputs;
    schedule[idx].n_outputs = imgs_per_batch;
    schedule[idx].layer_mode = layer_mode;
    schedule[idx].width_mode = width_mode;
    schedule[idx].norm_mode = max_pool + 1;

    // now we divide up the weights
    Word* wt_i = schedule[idx].wt;
    if (layer_type == LAYER_CONV1)
      load_conv1_weights(wt, wt_i, o, imgs_per_batch);
    else if (layer_type == LAYER_CONV)
      load_conv_weights(wt, wt_i, o, n_inputs, imgs_per_batch);
    else
      load_dense_weights(wt, wt_i, o, n_inputs, imgs_per_batch);
    // divide up the kh params
    Word* kh_i = schedule[idx].kh;
    if (layer_type != LAYER_LAST)
      load_kh (kh, kh_i, o, imgs_per_batch);
    else
      load_kh (kh, kh_i, o, 2*imgs_per_batch);
  }
}

// -----------------------------------------------------------------------
// Invoke accel multiple times based on an AccelSchedule (vec of AccelInfo)
// -----------------------------------------------------------------------
void run_accel_schedule(
    Word* data_i,
    Word* data_o,
    unsigned layer_idx,
    unsigned input_words,
    unsigned output_words,
    ap_uint<1> dmem_mode,
    AccelSchedule& s
) {
  // weight mems
  static Word* wt_i = (Word*) MEM_ALLOC( WT_WORDS*sizeof(Word) );
  static Word* kh_i = (Word*) MEM_ALLOC( KH_WORDS*sizeof(Word) );
  if (!wt_i || !kh_i) {
    fprintf(stderr, "**** ERROR: Alloc wt_i or kh_i failed in %s\n", __FILE__);
    exit(-2);
  }

  const unsigned N = s.size();
  const unsigned LAYERS = 9;

  // Invoke accelerator once for each element in the schedule
  for (unsigned i = 0; i < N; ++i) {
    for (unsigned j = 0; j < WT_WORDS; ++j)
      wt_i[j] = s[i].wt[j];
    for (unsigned j = 0; j < KH_WORDS; ++j)
      kh_i[j] = s[i].kh[j];

    timers[LAYERS-1-layer_idx].start();

    top(
        wt_i, kh_i, data_i, data_o,
        s[i].n_inputs, s[i].n_outputs,
        (i==0)   ? input_words : 0,
        (i==N-1) ? output_words : 0,
        s[i].layer_mode,
        dmem_mode,
        s[i].width_mode,
        s[i].norm_mode
    );

    timers[LAYERS-1-layer_idx].stop();
  }

  //MEM_FREE( wt_i );
  //MEM_FREE( kh_i );
}

// -----------------------------------------------------------------------
// determine the appropriate output batch size which allows the params
// and data to fit within their respective memory sizes
// -----------------------------------------------------------------------
unsigned find_conv_batch_size(unsigned width, unsigned width_o,
                         unsigned n_inputs, unsigned n_outputs) {
  const unsigned input_bsize = DMEM_WORDS*WORD_SIZE / (width*width);
  const unsigned wt_bsize = WT_WORDS*CONV_W_PER_WORD /  n_inputs;
  const unsigned kh_bsize = KH_WORDS*KH_PER_WORD;
  unsigned imgs_per_batch = DMEM_WORDS*WORD_SIZE / (width_o*width_o);

  // adjust output batch size to fit into memories cleanly
  if (imgs_per_batch > n_outputs) imgs_per_batch = n_outputs;
  if (imgs_per_batch > wt_bsize) imgs_per_batch = wt_bsize;
  if (imgs_per_batch > kh_bsize) imgs_per_batch = kh_bsize;
  while (n_outputs % imgs_per_batch != 0) {
    imgs_per_batch--;
  }
  assert(imgs_per_batch != 0);

  DB_PRINT(0, ">> (Wt, KH) batch: (%u %u)\n", wt_bsize, kh_bsize);
  DB_PRINT(0, ">> Final batch: %u\n", imgs_per_batch);

  // We are going to assume the following:
  //  1. We have space for all the input feature maps in dmem
  //  2. We have space for at least n_inputs parameters in wt_i
  assert(n_inputs <= input_bsize);
  assert(wt_bsize != 0);

  return imgs_per_batch;
}

// returns the number of output WORDS per batch
unsigned find_dense_batch_size(unsigned n_inputs, unsigned n_outputs) {
  assert(WT_WORDS*WORD_SIZE >= n_inputs);
  const unsigned wt_bsize = WT_WORDS*WORD_SIZE / n_inputs;
  const unsigned kh_bsize = KH_WORDS*KH_PER_WORD;
  unsigned bits_per_batch = DMEM_WORDS*WORD_SIZE;

  // adjust output batch size to fit into memories cleanly
  if (bits_per_batch > n_outputs) bits_per_batch = n_outputs;
  if (bits_per_batch > wt_bsize) bits_per_batch = wt_bsize;
  if (bits_per_batch > kh_bsize) bits_per_batch = kh_bsize;
  while (n_outputs % bits_per_batch != 0) {
    bits_per_batch--;
  }
  assert(bits_per_batch != 0);

  DB_PRINT(0, ">> (Wt, KH) bits batch: (%u %u)\n", wt_bsize, kh_bsize);
  DB_PRINT(0, ">> Final bits batch: %u\n", bits_per_batch);

  return bits_per_batch;
}

// -----------------------------------------------------------------------
// load weights for 1st conv layer, the weights are arranged linearly
// within the CONVOLVERS banks of the wt_mem such that the first bank
// contains filters 0,1,2,...,C_WT_WORDS-1, etc
// We also pack 3 weights per word
// -----------------------------------------------------------------------
void load_conv1_weights(Word* wt, Word* wt_o, unsigned o, unsigned n_out)
{
  // curr is the index of the starting weight in [wt]
  const unsigned M = 3;
  unsigned curr = o*M;
  unsigned addr_i = curr / CONV_W_PER_WORD;
  unsigned off_i = curr % CONV_W_PER_WORD;

  Word w = wt[addr_i] >> off_i*WT_SIZE;
  Word w_o;
  for (unsigned n = 0; n < n_out; ++n) {
    for (unsigned i = 0; i < M; ++i) {
      if (off_i == 0)
        w = wt[addr_i];

      w_o = w_o >> WT_SIZE;
      w_o(M*WT_SIZE-1, (M-1)*WT_SIZE) =
        w(WT_SIZE-1, 0);
      w = w >> WT_SIZE;

      if (++off_i == CONV_W_PER_WORD) {
        off_i = 0;
        addr_i++;
      }
    }
    wt_o[n] = w_o;
  }
  //printf ("\nLoaded Weights:\n");
  //print_params3d(wt_o, 0, 15);
}

// -----------------------------------------------------------------------
// load weights for the bin conv layers, the weights are arranged within
// the CONVOLVERS banks of the wt_mem such that the first bank contains
// filters 0, CONVOLVERS, 2*CONVOLVERS, ...
// -----------------------------------------------------------------------
void load_conv_weights(Word* wt, Word* wt_o,
                  unsigned o, unsigned n_in, unsigned n_out
) {
  // curr is the index of the starting weight in [wt]
  unsigned curr = o*n_in;
  unsigned addr_i = curr / CONV_W_PER_WORD;
  unsigned off_i = curr % CONV_W_PER_WORD;
  unsigned wt_words = WTS_TO_WORDS(n_in*n_out);
  assert (wt_words <= WT_WORDS);

  Word w = wt[addr_i] >> off_i*WT_SIZE;
  Word w_o[CONVOLVERS];
  for (unsigned i = 0; i < CONVOLVERS; ++i)
    w_o[i] = 0;

  for (unsigned n = 0; n < (wt_words+CONVOLVERS-1)/CONVOLVERS; ++n) {
    for (unsigned i = 0; i < CONV_W_PER_WORD*CONVOLVERS; ++i) {
      if (off_i == 0)
        w = wt[addr_i];

      // for each 3x3 filter, write it to the right partition
      w_o[i % CONVOLVERS] = w_o[i % CONVOLVERS] >> WT_SIZE;
      w_o[i % CONVOLVERS](CONV_W_PER_WORD*WT_SIZE-1, (CONV_W_PER_WORD-1)*WT_SIZE) =
        w(WT_SIZE-1, 0);
      w = w >> WT_SIZE;

      if (++off_i == CONV_W_PER_WORD) {
        //print_wt_word(w);
        off_i = 0;
        addr_i++;
      }
    }

    for (unsigned m = 0; m < CONVOLVERS; ++m)
      wt_o[n*CONVOLVERS+m] = w_o[m];
  }
  //printf ("\nLoaded Weights:\n");
  //print_params3d(wt_o, 0, n_in*n_out);
}

// -----------------------------------------------------------------------
// load n_in*n_out single bit weights into accelerator
// o is which output bit we are starting from
// -----------------------------------------------------------------------
void load_dense_weights(Word* wt, Word* wt_o,
                      unsigned o, unsigned n_in, unsigned n_out
) {
  assert(n_in % WORD_SIZE == 0);
  // load in Word-sized chunks
  for (unsigned i = 0; i < n_in*n_out/WORD_SIZE; ++i) {
    wt_o[i] = wt[o*n_in/WORD_SIZE + i];
  }
}

// -----------------------------------------------------------------------
// load n_out sets of kh params into accelerator
// -----------------------------------------------------------------------
void load_kh(Word* kh, Word kh_i[], unsigned o, unsigned n_out) {
  unsigned kh_addr = o / KH_PER_WORD;
  for (unsigned i = 0; i*KH_PER_WORD < n_out; ++i) {
    kh_i[i] = kh[kh_addr + i];
  }
}

float total_time() {
  float t = 0;
  for (unsigned n = 0; n < N_LAYERS; ++n) {
    t += timers[n].get_time();
  }
  return t;
}

const ap_uint<2> width_mode = width >> 4;


  std::ofstream ofile;
  ofile.open("outputs.txt");
  if (ofile.is_open())
  {
    for (int i = 0; i < cnt; i ++ )
    {
      if (result[i] != expected[i])
        ofile << "Test " << i << ": expected = " << int(expected[i]) << ", result = " << int(result[i]) << std::endl;
      else
        correct_cnt ++;
    }

    ofile << "\n\t " << correct_cnt << " / " << cnt << " correct!" << std::endl;
    ofile.close();
  }
  else
  {
    std::cout << "Failed to create output file!" << std::endl;
  }

}


#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "image.h"

#define MYDEBUG

char* strrev(char* str)
{
  char *p1, *p2;
  if (!str || !*str)
  	return str;
  for (p1 = str, p2 = str + strlen(str) - 1; p2 > p1; ++p1, --p2)
  {
  	*p1 ^= *p2;
  	*p2 ^= *p1;
  	*p1 ^= *p2;
  }
  return str;
}

void itochar(int x, char* szBuffer, int radix)
{
  int i = 0, n, xx;
  n = x;
  while (n > 0)
  {
  	xx = n%radix;
  	n = n/radix;
  	szBuffer[i++] = '0' + xx;
  }
  szBuffer[i] = '\0';
  strrev(szBuffer);
}

/* Writes a Pgm file using the hex image */
int writePgm(const char *fileName, unsigned char Data[IMAGE_HEIGHT][IMAGE_WIDTH] )
{
  char parameters_str[5];
  int i;
  const char *format = "P5";

  if (!fp){
    printf("Unable to open file %s\n", fileName);
    return -1;
  }

  fputs(format, fp);
  fputc('\n', fp);

  itochar(IMAGE_WIDTH, parameters_str, 10);
  fputs(parameters_str, fp);
  parameters_str[0] = 0;
  fputc(' ', fp);

  itochar(IMAGE_HEIGHT, parameters_str, 10);
  fputs(parameters_str, fp);
  parameters_str[0] = 0;
  fputc('\n', fp);

  itochar(IMAGE_MAXGREY, parameters_str, 10);
  fputs(parameters_str, fp);
  fputc('\n', fp);

  for (i = 0; i < IMAGE_HEIGHT; i++)
    for (int j = 0; j < IMAGE_WIDTH ; j++)
       fputc(Data[i][j], fp);

  fclose(fp);
  return 0;
}

/* draw white bounding boxes around detected faces */
void drawRectangle(unsigned char Data[IMAGE_HEIGHT][IMAGE_WIDTH], MyRect r)
{
  int i;
  int col = IMAGE_WIDTH;

  for (i = 0; i < r.width; i++)
    Data[r.y][r.x + i] = 255;
  for (i = 0; i < r.height; i++)
    Data[r.y+i][r.x + r.width] = 255;
  for (i = 0; i < r.width; i++)
    Data[r.y + r.height][r.x + r.width - i] = 255;
  for (i = 0; i < r.height; i++)
    Data[r.y + r.height - i][r.x] = 255;
}


///////////////////////////////////////////////////////////////////////////
//
// NAME
//  ImageIO.cpp -- image file input/output
//
// DESCRIPTION
//  Read/write image files, potentially using an interface to an
//  external package.
//
//  File formats currently supported:
//  - a subset of Targa
//  - PGM (1 band) and PPM (4 band)
//  - PMF (multiband float) - homegrown, non-standard
//        (PFM already taken by postscript font maps)
//  - PNG (requires ImageIOpng.cpp, and pnglib and zlib packages)
//
// SEE ALSO
//  ImageIO.h            longer description
//  ImageIOpng.cpp       png reader/writer
//
// Copyright  Richard Szeliski and Daniel Scharstein, 2001.
// See Copyright.h for more details
//
///////////////////////////////////////////////////////////////////////////

#include "Image.h"
#include "Error.h"
#include "ImageIO.h"
#include <vector>

// Comment out next line if you don't have the PNG library
//#define HAVE_PNG_LIB

#ifdef HAVE_PNG_LIB
// implemented in ImageIOpng.cpp
void ReadFilePNG(CByteImage& img, const char* filename);
void WriteFilePNG(CByteImage img, const char* filename);
#endif

// Comment out next line if not using jpeg library
//#define HAVE_JPEG_READER

#ifdef HAVE_JPEG_READER
// implemented in ReadJpg.cpp
void ReadFileJPG(CByteImage& img, const char* filename);
#endif

//
//  Truevision Targa (TGA):  support 24 bit RGB and 32-bit RGBA files
//

typedef unsigned char uchar;

struct CTargaHead
{
    uchar idLength;     // number of chars in identification field
    uchar colorMapType;	// color map type
    uchar imageType;	// image type code
    uchar cMapOrigin[2];// color map origin
    uchar cMapLength[2];// color map length
    uchar cMapBits;     // color map entry size
    short x0;			// x-origin of image
    short y0;			// y-origin of image
    short width;		// width of image
    short height;		// height of image
    uchar pixelSize;    // image pixel size
    uchar descriptor;   // image descriptor byte
};

// Image data type codes
const int TargaRawColormap	= 1;
const int TargaRawRGB		= 2;
const int TargaRawBW		= 3;
const int TargaRunColormap	= 9;
const int TargaRunRGB		= 10;
const int TargaRunBW		= 11;

// Descriptor fields
const int TargaAttrBits     = 15;
const int TargaScreenOrigin = (1<<5);
const int TargaCMapSize		= 256;
const int TargaCMapBands    = 3;
/*
const int TargaInterleaveShift = 6;
const int TargaNON_INTERLEAVE	0
const int TargaTWO_INTERLEAVE	1
const int TargaFOUR_INTERLEAVE	2
const int PERMUTE_BANDS		1
*/

class CTargaRLC
{
    // Helper class to decode run-length-coded image data
public:
    CTargaRLC(bool RLC) : m_count(0), m_RLC(RLC) {}
    uchar* getBytes(int nBytes, FILE *stream);
private:
    int m_count;        // remaining count in current run
    bool m_RLC;         // is stream run-length coded?
    bool m_isRun;       // is current stream of pixels a run?
    uchar m_buffer[4];  // internal buffer
};

inline uchar* CTargaRLC::getBytes(int nBytes, FILE *stream)
{
    // Get one pixel, which consists of nBytes
    if (nBytes > 4)
        throw CError("ReadFileTGA: only support pixels up to 4 bytes long");

    if (! m_RLC)
    {
    	if ((int)fread(m_buffer, sizeof(uchar), nBytes, stream) != nBytes)
    	    throw CError("ReadFileTGA: file is too short");
    }
    else
    {
        if (m_count == 0)
        {
            // Read in the next run count
            m_count = fgetc(stream);
            m_isRun = (m_count & 0x80) != 0;
            m_count = (m_count & 0x7f)  + 1;
            if (m_isRun)  // read the pixels for this run
    	        if ((int)fread(m_buffer, sizeof(uchar), nBytes, stream) != nBytes)
    	            throw CError("ReadFileTGA: file is too short");
        }
        if (! m_isRun)
        {
    	    if ((int)fread(m_buffer, sizeof(uchar), nBytes, stream) != nBytes)
    	        throw CError("ReadFileTGA: file is too short");
        }
        m_count -= 1;
    }
    return m_buffer;
}

void ReadFileTGA(CByteImage& img, const char* filename)
{
    // Open the file and read the header
    FILE *stream = fopen(filename, "rb");
    if (stream == 0)
        throw CError("ReadFileTGA: could not open %s", filename);
    CTargaHead h;
    if ((int)fread(&h, sizeof(CTargaHead), 1, stream) != 1)
	    throw CError("ReadFileTGA(%s): file is too short", filename);

    // Throw away the image descriptor
    if (h.idLength > 0)
    {
        char* tmp = new char[h.idLength];
        int nread = (int)fread(tmp, sizeof(uchar), h.idLength, stream);
        delete tmp;   // throw away this data
        if (nread != h.idLength)
	        throw CError("ReadFileTGA(%s): file is too short", filename);
    }
    //bool isRun = (h.imageType & 8) != 0;
    bool reverseRows = (h.descriptor & TargaScreenOrigin) != 0;
    int fileBytes = (h.pixelSize + 7) / 8;

    // Read the colormap
    uchar colormap[TargaCMapSize][TargaCMapBands];
    int cMapSize = 0;
    bool grayRamp = false;
    if (h.colorMapType == 1)
    {
        cMapSize = (h.cMapLength[1] << 8) + h.cMapLength[0];
        if (h.cMapBits != 24) {
            throw CError("ReadFileTGA(%s): only 24-bit colormap currently supported", filename);
        }
	    int l = fileBytes * cMapSize;
        if (l > TargaCMapSize * TargaCMapBands)
	        throw CError("ReadFileTGA(%s): colormap is too large", filename);
	    if ((int)fread(colormap, sizeof(uchar), l, stream) != l)
	        throw CError("ReadFileTGA(%s): could not read the colormap", filename);

        // Check if it's just a standard gray ramp
	int i;
        for (i = 0; i < cMapSize; i++) {
            for (int j = 0; j < TargaCMapBands; j++)
                if (colormap[i][j] != i)
                    break;
        }
        grayRamp = (i == cMapSize);    // didn't break out too soon
    }
    bool isGray =
        h.imageType == TargaRawBW || h.imageType == TargaRunBW ||
        grayRamp &&
        (h.imageType == TargaRawColormap || h.imageType == TargaRunColormap);
    bool isRaw = h.imageType == TargaRawBW || h.imageType == TargaRawRGB ||
        h.imageType == TargaRawRGB && isGray;

    // Determine the image shape
    CShape sh(h.width, h.height, (isGray) ? 1 : 4);

    // Allocate the image if necessary
    img.ReAllocate(sh, false);

    // Construct a run-length code reader
    CTargaRLC rlc(! isRaw);

    // Read in the rows
    for (int y = 0; y < sh.height; y++)
    {
        int yr = reverseRows ? sh.height-1-y : y;
        uchar* ptr = (uchar *) img.PixelAddress(0, yr, 0);
        if (fileBytes == sh.nBands && isRaw)
        {
            // Special case for raw image, same as destination
            int n = sh.width*sh.nBands;
    	    if ((int)fread(ptr, sizeof(uchar), n, stream) != n)
    	        throw CError("ReadFileTGA(%s): file is too short", filename);
        }
        else
        {
            // Read one pixel at a time
            for (int x = 0; x < sh.width; x++, ptr += sh.nBands)
            {
                uchar* buf = rlc.getBytes(fileBytes, stream);
                if (fileBytes == 1 && sh.nBands == 1)
                {
                    ptr[0] = buf[0];
                }
                else if (fileBytes == 1 && sh.nBands == 4)
                {
                    for (int i = 0; i < 3; i++)
                        ptr[i] = (isGray) ? buf[0] : colormap[buf[0]][i];
                    ptr[3] = 255;   // full alpha;
                }
                else if ((fileBytes == 3 || fileBytes == 4) && sh.nBands == 4)
                {
                    int i;
                    for (i = 0; i < fileBytes; i++)
                        ptr[i] = buf[i];
                    if (i == 3) // missing alpha channel

            	    throw CError("ReadFileTGA(%s): unhandled pixel depth or # of bands", filename);
            }
        }
    }

    if (fclose(stream))
        throw CError("ReadFileTGA(%s): error closing file", filename);
}

void WriteFileTGA(CImage img, const char* filename)
{
    // Only 1, 3, or 4 bands supported
    CShape sh = img.Shape();
    int nBands = sh.nBands;
    if (nBands != 1 && nBands != 3 && nBands != 4)
        throw CError("WriteFileTGA(%s): can only write 1, 3, or 4 bands", filename);

    // Only unsigned_8 supported directly
#if 0   // broken for now
    if (img.PixType() != unsigned_8)
    {
        CImage u8img(sh, unsigned_8);
        TypeConvert(img, u8img);
        img = u8img;
    }
#endif

    // Fill in the header structure
    CTargaHead h;
    memset(&h, 0, sizeof(h));
    h.imageType = (nBands == 1) ? TargaRawBW : TargaRawRGB;
        // TODO:  is TargaRawBW the right thing, or only binary?
    h.width     = sh.width;
    h.height    = sh.height;
    h.pixelSize = 8 * nBands;
    bool reverseRows = false;   // TODO: when is this true?

    // Open the file and write the header
    FILE *stream = fopen(filename, "wb");
    if (stream == 0)
        throw CError("WriteFileTGA: could not open %s", filename);
    if (fwrite(&h, sizeof(CTargaHead), 1, stream) != 1)
	    throw CError("WriteFileTGA(%s): file is too short", filename);

    // Write out the rows
    for (int y = 0; y < sh.height; y++)
    {
        int yr = reverseRows ? sh.height-1-y : y;
        char* ptr = (char *) img.PixelAddress(0, yr, 0);
        int n = sh.width*sh.nBands;
    	if ((int)fwrite(ptr, sizeof(uchar), n, stream) != n)
    	    throw CError("WriteFileTGA(%s): file is too short", filename);
    }

    if (fclose(stream))
        throw CError("WriteFileTGA(%s): error closing file", filename);
}

//
// Portable Graymaps: support PGM, PPM, and PMF images
//

void skip_comment(FILE *fp)
{
    // skip comment lines in the headers of pnm files

    char c;
    while ((c=getc(fp)) == '#')
        while (getc(fp) != '\n');
    ungetc(c, fp);
}

void skip_space(FILE *fp)
{
    // skip white space in the headers or pnm files

    char c;
    do {
        c = getc(fp);
    } while (c == '\n' || c == ' ' || c == '\t' || c == '\r');
    ungetc(c, fp);
}

void read_header(FILE *fp, const char *imtype, char c1, char c2,
                 int *width, int *height, int *nbands, int thirdArg)
{
    // read the header of a pnmfile and initialize width and height

    char c;

	if (getc(fp) != c1 || getc(fp) != c2)
		throw CError("ReadFilePGM: wrong magic code for %s file", imtype);
	skip_space(fp);
	skip_comment(fp);
	skip_space(fp);
	fscanf(fp, "%d", width);
	skip_space(fp);
	fscanf(fp, "%d", height);
	if (thirdArg) {
		skip_space(fp);
		fscanf(fp, "%d", nbands);
	}
    // skip SINGLE newline character after reading image height (or third arg)
	c = getc(fp);
    if (c == '\r')      // <cr> in some files before newline
        c = getc(fp);
    if (c != '\n') {
        if (c == ' ' || c == '\t' || c == '\r')
            throw CError("newline expected in file after image height");
        else
            throw CError("whitespace expected in file after image height");
  }
}

void ReadFilePGM(CByteImage& img, const char* filename)
{
    // Open the file and read the header
    FILE *stream = fopen(filename, "rb");
    if (stream == 0) {
        throw CError("ReadFilePGM: could not open %s", filename);
    }

	int width, height, nBands;
	const char *dot = strrchr(filename, '.');
	int isGray = 0, isFloat = 0;

    if (strcmp(dot, ".pgm") == 0) {
		read_header(stream, "PGM", 'P', '5', &width, &height, &nBands, 1);
		isGray = 1;
	}
    else if (strcmp(dot, ".ppm") == 0) {
		read_header(stream, "PGM", 'P', '6', &width, &height, &nBands, 1);
		isGray = 0;
	}
    else if (strcmp(dot, ".pmf") == 0) {
		read_header(stream, "PMF", 'P', '9', &width, &height, &nBands, 1);
		isGray = 0;
        isFloat = 1;
	}

    // Determine the image shape
    CShape sh(width, height, (isGray) ? 1 : (isFloat) ? nBands : 4);

    // Allocate the image if necessary
    if (isFloat)
        ((CFloatImage *) &img)->ReAllocate(sh);
    else
        img.ReAllocate(sh);

    if (isGray || isFloat) { // read PGM or PMF

		// read the rows
        int n = isFloat ? width * nBands * sizeof(float) : sh.width;
		for (int y = 0; y<sh.height; y++) {
			uchar* ptr = (uchar *) img.PixelAddress(0, y, 0);
    	    if ((int)fread(ptr, sizeof(uchar), n, stream) != n)
    	        throw CError("ReadFilePGM(%s): file is too short", filename);
		}
	}
    else { // read PPM

		// read the rows
        int n = sh.width*3;
		std::vector<uchar> rowBuf;
		rowBuf.resize(n);
		for (int y = 0; y<sh.height; y++) {
	   	    if ((int)fread(&rowBuf[0], sizeof(uchar), n, stream) != n)
    	        throw CError("ReadFilePGM(%s): file is too short", filename);

			uchar* ptr = (uchar *) img.PixelAddress(0, y, 0);
			int x = 0;
			while (x < n) {
				ptr[2] = rowBuf[x++];
				ptr[1] = rowBuf[x++];
				ptr[0] = rowBuf[x++];
				ptr[3] = 255; // full alpha
				ptr += 4;
			}
		}
	}

    if (fclose(stream))
        throw CError("ReadFilePGM(%s): error closing file", filename);
}

void WriteFilePGM(CByteImage img, const char* filename)
{
    // Write a PGM, PPM, or PMF file
    CShape sh = img.Shape();
    int nBands = sh.nBands;
    int isFloat = img.PixType() == typeid(float);

	// Determine the file extension
    const char *dot = strrchr(filename, '.');
	if (strcmp(dot, ".pgm") == 0 && nBands != 1)
		throw CError("WriteFilePGM(%s): can only write 1-band image as pgm", filename);

	if (strcmp(dot, ".ppm") == 0 && nBands != 3 && nBands != 4)
		throw CError("WriteFilePGM(%s): can only write 3 or 4-band image as ppm", filename);

	if (strcmp(dot, ".pmf") == 0 && ! isFloat)
		throw CError("WriteFilePMF(%s): can only write floating point image as pmf", filename);
    // Open the file
    FILE *stream = fopen(filename, "wb");
    if (stream == 0)
        throw CError("WriteFilePGM: could not open %s", filename);

    if (nBands == 1 || isFloat) { // write PGM or PMF

		// write the header
        fprintf(stream, "P%d\n%d %d\n%d\n", isFloat ? 9 : 5, sh.width, sh.height,
                isFloat ? sh.nBands : 255);

		// write the rows
        int n = isFloat ? sh.width * sh.nBands * sizeof(float) : sh.width;
		for (int y = 0; y<sh.height; y++) {
			char* ptr = (char *) img.PixelAddress(0, y, 0);
    		if ((int)fwrite(ptr, sizeof(uchar), n, stream) != n)
    			throw CError("WriteFilePGM(%s): file is too short", filename);
		}
	}
    /*
    else if (nBands == 3) { // write PPM

		// write the header
		fprintf(stream, "P6\n%d %d\n%d\n", sh.width, sh.height, 255);

		// write the rows
        int n = sh.width*3;
		for (int y = 0; y<sh.height; y++) {
			char* ptr = (char *) img.PixelAddress(0, y, 0);
    		if ((int)fwrite(ptr, sizeof(uchar), n, stream) != n)  // ??? TODO: test this
                // ??? not sure this will work - RGB may be in wrong order
    			throw CError("WriteFilePGM(%s): file is too short", filename);
		}
	}
    */
    else if (nBands == 3 || nBands == 4) { // write PPM, ignoring alpha

		// write the header
		fprintf(stream, "P6\n%d %d\n%d\n", sh.width, sh.height, 255);

		// write the rows
        int n = sh.width*3;
		std::vector<uchar> rowBuf;
		rowBuf.resize(n);
		for (int y = 0; y<sh.height; y++) {
			uchar* ptr = (uchar *) img.PixelAddress(0, y, 0);
			int x = 0;
			while (x < n) {
				rowBuf[x++] = ptr[2];
				rowBuf[x++] = ptr[1];
				rowBuf[x++] = ptr[0];
				ptr += nBands;
			}

    		if ((int)fwrite(&rowBuf[0], sizeof(uchar), n, stream) != n)
    			throw CError("WriteFilePGM(%s): file is too short", filename);
		}
	}
	else
        throw CError("WriteFilePGM(%s): unhandled # of bands %d", filename, nBands);

	// close file

    if (fclose(stream))
        throw CError("WriteFilePGM(%s): error closing file", filename);
}

//
// main dispatch functions
//

void ReadImage (CImage& img, const char* filename)
{
	if (filename == NULL)
		throw CError("ReadImage: empty filename");

    // Determine the file extension
    const char *dot = strrchr(filename, '.');
	if (dot == NULL)
		throw CError("ReadImage: extension required in filename '%s'", filename);

    if (strcmp(dot, ".TGA") == 0 || strcmp(dot, ".tga") == 0)
    {
        if ((&img.PixType()) == 0)
            img.ReAllocate(CShape(), typeid(uchar), sizeof(uchar), true);
        if (img.PixType() == typeid(uchar))
            ReadFileTGA(*(CByteImage *) &img, filename);
        else
           throw CError("ReadImage(%s): can only read CByteImage in TGA format", filename);
    }
    else if (strcmp(dot, ".pgm") == 0 || strcmp(dot, ".ppm") == 0 ||
             strcmp(dot, ".pmf") == 0)
    {
        if ((&img.PixType()) == 0)
        {
            if (strcmp(dot, ".pmf") == 0)
                img.ReAllocate(CShape(), typeid(float), sizeof(float), true);
            else
                img.ReAllocate(CShape(), typeid(uchar), sizeof(uchar), true);
        }
        if (img.PixType() == typeid(uchar) ||
            img.PixType() == typeid(float))
            ReadFilePGM(*(CByteImage *) &img, filename);
        else
           throw CError("ReadImage(%s): wrong image type for PGM/PPM/PMF", filename);
    }
#ifdef HAVE_PNG_LIB
    else if (strcmp(dot, ".PNG") == 0 || strcmp(dot, ".png") == 0)
    {
        if ((&img.PixType()) == 0)
            img.ReAllocate(CShape(), typeid(uchar), sizeof(uchar), true);
        if (img.PixType() == typeid(uchar))
            ReadFilePNG(*(CByteImage *) &img, filename);
        else
           throw CError("ReadImage(%s): can only read CByteImage in PNG format", filename);
    }
#endif
#ifdef HAVE_JPEG_READER
    else if (strcmp(dot, ".JPG") == 0 || strcmp(dot, ".jpg") == 0)
    {
        if ((&img.PixType()) == 0)
            img.ReAllocate(CShape(), typeid(uchar), sizeof(uchar), true);
        if (img.PixType() == typeid(uchar))
            ReadFileJPG(*(CByteImage *) &img, filename);
        else
           throw CError("ReadImage(%s): can only read CByteImage in JPG format", filename);
    }
#endif
    else
        throw CError("ReadImage(%s): file type not supported", filename);
}

void WriteImage(CImage& img, const char* filename)
{
	if (filename == NULL)
		throw CError("WriteImage: empty filename");

    // Determine the file extension
    const char *dot = strrchr(filename, '.');
	if (dot == NULL)
		throw CError("WriteImage: extension required in filename '%s'", filename);

    if (strcmp(dot, ".TGA") == 0 || strcmp(dot, ".tga") == 0)
    {
        if (img.PixType() == typeid(uchar))
            WriteFileTGA(*(CByteImage *) &img, filename);
        else
           throw CError("WriteImage(%s): can only write CByteImage in TGA format", filename);
    }
    else if (strcmp(dot, ".pgm") == 0 || strcmp(dot, ".ppm") == 0 ||
             strcmp(dot, ".pmf") == 0)
    {
        if (img.PixType() == typeid(uchar) ||
            img.PixType() == typeid(float))
            WriteFilePGM(*(CByteImage *) &img, filename);
        else
           throw CError("WriteImage(%s): wrong image type for PGM/PPM/PMF", filename);
    }
#ifdef HAVE_PNG_LIB
    else if (strcmp(dot, ".PNG") == 0 || strcmp(dot, ".png") == 0)
    {
        if (img.PixType() == typeid(uchar))
            WriteFilePNG(*(CByteImage *) &img, filename);
        else
           throw CError("WriteImage(%s): can only write CByteImage in PNG format", filename);
    }
#endif
    else
        throw CError("WriteImage(%s): file type not supported", filename);
}

// read an image and perhaps tell the user you're doing so
void ReadImageVerb(CImage& img, const char* filename, int verbose) {
	if (verbose)
		fprintf(stderr, "Reading image %s\n", filename);
	ReadImage(img, filename);
}

// write out an image and perhaps tell the user you're doing so
void WriteImageVerb(CImage& img, const char* filename, int verbose) {
	if (verbose)
		fprintf(stderr, "Writing image %s\n", filename);
	WriteImage(img, filename);
}

                        ptr[3] = 255;   // full alpha;
                }


///////////////////////////////////////////////////////////////////////////
//
// NAME
//  RefCntMem.cpp -- reference-counted heap memory
//
// SEE ALSO
//  RefCntMem.h         definition and explanation of this class
//
// Copyright  Richard Szeliski, 2001.
// See Copyright.h for more details
//
///////////////////////////////////////////////////////////////////////////

#include "RefCntMem.h"

CRefCntMem::CRefCntMem()
{
    // Default constructor
    m_ptr = 0;
}

CRefCntMem::~CRefCntMem()
{
    // Destructor
    DecrementCount();
    m_ptr = 0;      // not necessary, just for debugging
}

void CRefCntMem::DecrementCount()
{
    // Decrement the reference count and delete if done
    if (m_ptr)
    {
        m_ptr->m_refCnt -= 1;
        if (m_ptr->m_refCnt == 0)
        {
            if (m_ptr->m_deleteWhenDone)
            {
                if (m_ptr->m_delFn)
                    m_ptr->m_delFn(m_ptr->m_memory);

    }
}

void CRefCntMem::IncrementCount()
{
    // Increment the reference count
    if (m_ptr)
    {
        m_ptr->m_refCnt += 1;
    }
}

CRefCntMem::CRefCntMem(const CRefCntMem& ref)
{
    // Copy constructor
    m_ptr = 0;
    (*this) = ref;      // use assignment operator
}

CRefCntMem& CRefCntMem::operator=(const CRefCntMem& ref)
{
    // Assignment
    DecrementCount();   // if m_ptr exists, no longer pointing to it
    m_ptr = ref.m_ptr;
    IncrementCount();
    return *this;
}

void CRefCntMem::ReAllocate(int nBytes, void *memory, bool deleteWhenDone,
                            void (*deleteFunction)(void *ptr))
{
    // Allocate/deallocate memory
    DecrementCount();
    if (memory)
    {
        m_ptr = new CRefCntMemPtr;
        m_ptr->m_nBytes = nBytes;
        m_ptr->m_memory = memory;
        m_ptr->m_deleteWhenDone = deleteWhenDone;
        m_ptr->m_refCnt = 1;
        m_ptr->m_delFn = deleteFunction;
    }
    else
        m_ptr = 0;  // don't bother storing pointer to null memory
}

int CRefCntMem::NBytes()
{
    // Number of stored bytes
    return (m_ptr) ? m_ptr->m_nBytes : 0;
}

bool CRefCntMem::InBounds(int index)
{
    // Check if index is in bounds
    return (m_ptr && 0 <= index && index < m_ptr->m_nBytes);
}

void* CRefCntMem::Memory()
{
    // Pointer to allocated memory
    return (m_ptr) ? m_ptr->m_memory : 0;
}

                else
                    delete (double *) m_ptr->m_memory;
            }


// -*- C++ -*-
#ifndef _annotation_checker_h_
#define _annotation_checker_h_

#include "compiler/common.h"

namespace compiler {
class AnnotationChecker {
 public:
  static void Check(vm::Insn *insn);

 private:
  static void CheckMethodDecl(vm::Insn *insn);

  static void CheckChannelDecl(vm::Insn *insn);
  static void CheckMailboxDecl(vm::Insn *insn);
  static void CheckVarDecl(vm::Insn *insn);
};


// -*- C++ -*-
#ifndef _synth_common_h_
#define _synth_common_h_

#include "karuta/karuta.h"

class Annotation;

namespace fe {
class VarDecl;
class VarDeclSet;
}  // namespace fe

namespace iroha {
class IDesign;
class IInsn;
class IModule;
class IRegister;
class IResource;
class IState;
class ITable;
class IValueType;
class ResourceParams;
}  // namespace iroha

using namespace iroha;

namespace vm {

class Method;
class Object;
class Register;
class VM;
class Value;
}  // namespace vm

namespace synth {
class DesignSynth;
class InsnWalker;
class MethodContext;
class MethodSynth;
class ObjectSynth;
class ObjectTree;
class ThreadSynth;
class ResourceSet;
class ResourceSynth;
class StateWrapper;
class SharedResource;
class SharedResourceSet;

// Sub obj call, data flow call or ext stub call.
// MethodExpander populates this.
struct TableCall {
  TableCall()
      : is_sub_obj_call(false),
        is_data_flow_call(false),
        is_ext_stub_call(false),
        is_ext_flow_stub_call(false) {}
  IInsn *call_insn;
  IState *call_state;
  ThreadSynth *caller_thread;
  vm::Object *callee_obj;
  string callee_func;
  vm::Method *callee_method;
  // type
  bool is_sub_obj_call;
  bool is_data_flow_call;
  bool is_ext_stub_call;
  bool is_ext_flow_stub_call;
};

}  // namespace synth

#endif  // _synth_common_h_


// -*- C++ -*-
#ifndef _synth_method_expander_h_
#define _synth_method_expander_h_

#include <map>

#include "synth/common.h"

using std::map;

namespace synth {

class CalleeInfo {
 public:
  IState *initial;
  IState *final;
  vector<IRegister *> args;
  vector<IRegister *> rets;
};

class MethodExpander {
 public:
  MethodExpander(MethodContext *root, ThreadSynth *thr_synth,
                 vector<TableCall> *table_calls);

  bool Expand();

 private:
  CalleeInfo ExpandMethod(MethodContext *method);
  void CopyState(IState *ost, map<IState *, IState *> &st_map,
                 map<IRegister *, IRegister *> &reg_map, IState *nst,
                 map<IInsn *, IInsn *> &insn_map);
  IInsn *CopyInsn(IInsn *oinsn, map<IState *, IState *> &st_map,
                  map<IRegister *, IRegister *> &reg_map);

  void BuildRegCopy(IRegister *reg, map<IRegister *, IRegister *> &reg_map);
  void ExpandCalleeStates(MethodContext *method,
                          map<IState *, IState *> &st_map,
                          map<IRegister *, IRegister *> &reg_map);
  // Fills sub_obj_calls_ or data_flow_calls_
  void CollectTableCalls(MethodContext *method,
                         map<IState *, IState *> &st_map);

  MethodContext *root_method_;
  ThreadSynth *thr_synth_;
  ITable *tab_;
  vector<TableCall> *table_calls_;
};

}  // namespace synth

#endif  // _synth_method_expander_h_

  void CopyDependingInsns(MethodContext *method,
                          map<IInsn *, IInsn *> &insn_map);


// -*- C++ -*-
#ifndef _vm_register_h_
#define _vm_register_h_

#include "iroha/numeric.h"
#include "vm/common.h"
#include "vm/value.h"

namespace vm {

class RegisterType {
 public:
  RegisterType(Value::ValueType value_type, const Object *enum_type,
               const iroha::NumericWidth &width, sym_t object_name,
               bool is_const);
  void Dump();
  void Dump(DumpStream &ds);

  // VALUE_*
  enum Value::ValueType value_type_;
  // when value_type_ == ENUM_ITEM
  const Object *enum_type_;
  iroha::NumericWidth num_width_;
  sym_t object_name_;
  bool is_const_;

 private:
  RegisterType();
};

// Register definition. Actual value will be stored in
// MethodFrame.reg_values_.
class Register {
 public:
  Register();

  void Dump();
  void Dump(DumpStream &ds);

  void SetIsDeclaredType(bool is_declared_type);
  bool GetIsDeclaredType() const;

  void SetAnnotation(Annotation *an);
  Annotation *GetAnnotation();

  int id_;

  iroha::Numeric initial_num_;
  sym_t orig_name_;
  Object *type_object_;

 private:
  bool is_declared_type_;
  Annotation *annotation_;
};

}  // namespace vm

#endif  // _vm_register_h_


// -*- C++ -*-
//
// Thread Local Storage.
//
#ifndef _vm_tls_wrapper_h_
#define _vm_tls_wrapper_h_

#include "vm/common.h"

namespace vm {

};

}  // namespace vm

#endif  // _vm_tls_wrapper_h_

class TlsWrapper {
 public:
  static bool IsTls(Object *obj);
  static bool IsTlsValue(Value *value);
  static void InjectTlsWrapper(VM *vm, Value *value);


#include "base/dump_stream.h"

DumpStream::DumpStream(std::ostream &_os) : os(_os) { indent_ = 0; }

void DumpStream::pop_indent() { indent_--; }

void DumpStream::indent() {
  int i;
  for (i = 0; i < indent_; i++) {
    os << "  ";
  }
}

void DumpStream::push_indent() { indent_++; }


#include "base/logging.h"

#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

#include <iomanip>

using std::ostream;
using std::set;
using std::string;

std::stringstream Logger::os_;
bool Logger::is_enabled_;
set<std::string> Logger::modules_;

static string TimeStamp() {
  std::stringstream ss;
  struct timeval tv;
  gettimeofday(&tv, NULL);
  double us = (tv.tv_sec % 1000) + tv.tv_usec / (double)1000000;
  ss << std::setprecision(2) << std::fixed << us;
  return ss.str();
}

void Logger::Init(bool on, const set<string> &modules) {
  is_enabled_ = on;
  modules_ = modules;
}

ostream &Logger::GetStream(LogSeverity sev) { return os_; }

void Logger::Finalize(LogSeverity sev, const char *fn, int line) {
  if (sev == LOG_NONE) {
    os_.str("");
    return;
  }
  if (sev == PHASE) {

    const char *e = strrchr(fn, '.');
    if (e) {
      module = string(fn, e - fn);
    } else {
      module = string(fn);
    }
    if (modules_.find(module) == modules_.end()) {
      os_.str("");
      return;
    }
  }
  if (is_enabled_ || sev == FATAL || sev == DEBUG) {
    std::cout << TimeStamp() << " " << fn << ":" << line << ":" << os_.str()
              << "\n";
  }
  os_.str("");
  if (sev == FATAL) {
    std::cout << std::flush;
    abort();
  }
}

LogFinalizer::LogFinalizer(LogSeverity sev, const char *fn, int line)
    : sev_(sev), fn_(fn), line_(line) {}

LogFinalizer::~LogFinalizer() { Logger::Finalize(sev_, fn_, line_); }


#include "synth/design_synth.h"

#include <list>

#include "base/status.h"
#include "base/stl_util.h"
#include "base/util.h"
#include "iroha/i_design.h"
#include "iroha/iroha.h"
#include "karuta/annotation.h"
#include "synth/dot_output.h"
#include "synth/object_attr_names.h"
#include "synth/object_synth.h"
#include "synth/object_tree.h"
#include "synth/shared_resource_set.h"
#include "vm/object.h"

namespace synth {

DesignSynth::DesignSynth(vm::VM *vm, vm::Object *obj)
    : vm_(vm), root_obj_(obj) {
  i_design_.reset(new IDesign);
  shared_resources_.reset(new SharedResourceSet);
  obj_tree_.reset(new ObjectTree(vm, obj));
}

DesignSynth::~DesignSynth() { STLDeleteSecondElements(&obj_synth_map_); }

bool DesignSynth::Synth() {
  SetSynthParams();

  const string &prefix = Env::GetModulePrefix();
  if (!prefix.empty()) {
    iroha::ResourceParams *params = i_design_->GetParams();
    params->SetModuleNamePrefix(prefix + "_");
  }

  if (!SynthObjects()) {
    return false;
  }
  if (Status::CheckAllErrors(false)) {
    return false;
  }
  if (Env::DotOutput()) {
    DotOutput writer(this, obj_tree_.get());
    string fn = ::Util::BaseNameWithoutSuffix(Env::GetCurrentFile()) + ".0.dot";
    string ofn;
    if (Env::GetOutputPath(fn, &ofn)) {
      writer.Write(ofn);
      const string &marker = Env::GetOutputMarker();
      if (!marker.empty()) {
        cout << marker << fn << "\n";
      }
    }
  }

  DesignTool::Validate(i_design_.get());

  iroha::OptAPI *optimizer = iroha::Iroha::CreateOptimizer(i_design_.get());
  optimizer->ApplyPass("clean_pseudo_resource");
  return true;
}

bool DesignSynth::SynthObjects() {
  obj_tree_->Build();
  // Pass 1: Scan.
  ObjectSynth *root_synth = GetObjectSynth(root_obj_, true);
  CollectScanRootObjRec(root_obj_);
  if (!ScanObjs()) {
    return false;
  }
  DeterminePrimaryThread();
  shared_resources_->DetermineOwnerThreadAll();
  // Pass 2: Synth.
  if (!SynthObjectsAll(root_synth)) {
    return false;
  }
  shared_resources_->ResolveResourceAccessors();
  shared_resources_->ResolveAccessorDistanceAll(this);
  for (auto it : obj_synth_map_) {
    ObjectSynth *obj_synth = it.second;
    obj_synth->ResolveTableCallsAll();
  }
  return true;
}

bool DesignSynth::SynthObjectsAll(ObjectSynth *root_synth) {
  vector<ObjectSynth *> synthes;
  CollectObjSynthRec(root_synth, &synthes);
  for (ObjectSynth *osynth : synthes) {
    if (!osynth->Synth()) {
      return false;
    }
  }
  return true;
}

vm::VM *DesignSynth::GetVM() { return vm_; }

IDesign *DesignSynth::GetIDesign() { return i_design_.get(); }

ObjectSynth *DesignSynth::GetObjectSynth(vm::Object *obj, bool cr) {
  auto it = obj_synth_map_.find(obj);
  if (it != obj_synth_map_.end()) {
    return it->second;
  }
  if (!cr) {
    return nullptr;
  }
  bool is_root = (obj == root_obj_);
  string name = obj_tree_->GetObjectName(obj);
  CHECK(!name.empty());

  obj_synth_map_[obj] = osynth;
  return osynth;
}

SharedResourceSet *DesignSynth::GetSharedResourceSet() {
  return shared_resources_.get();
}

string DesignSynth::GetObjectName(vm::Object *obj) {
  return obj_tree_->GetObjectName(obj);
}

int DesignSynth::GetObjectDistance(vm::Object *src, vm::Object *dst) {
  return obj_tree_->GetDistance(src, dst);
}

bool DesignSynth::ScanObjs() {
  int num_scan;
  // Loop until every objects stops to request rescan.
  do {
    num_scan = 0;
    map<vm::Object *, ObjectSynth *> m = obj_synth_map_;
    for (auto it : m) {
      bool ok = true;
      ObjectSynth *osynth = it.second;
      if (osynth->Scan(&ok)) {
        ++num_scan;
      }
      if (!ok) {
        return false;
      }
    }
  } while (num_scan > 0);
  return true;
}

void DesignSynth::CollectScanRootObjRec(vm::Object *obj) {
  if (obj_synth_map_.find(obj) == obj_synth_map_.end()) {
    if (ObjectSynth::HasSynthesizable(obj)) {
      (void)GetObjectSynth(obj, true);
    }
  }
  auto m = obj_tree_->GetChildObjects(obj);
  for (auto it : m) {
    CollectScanRootObjRec(it.first);
  }
}

void DesignSynth::DeterminePrimaryThread() {
  for (auto it : obj_synth_map_) {
    it.second->DeterminePrimaryThread();
  }
}

bool DesignSynth::GetResetPolarity(Annotation *an) {
  // Use positive reset as the default, since libraries of Karuta
  // assumes positive reset.
  if (an == nullptr) {
    return true;
  }
  return an->ResetPolarity();
}

void DesignSynth::SetSynthParams() {
  iroha::ResourceParams *params = i_design_->GetParams();

  sym_t synth_params = sym_lookup(kSynthParams);
  Annotation *an = nullptr;
  vm::Value *value = root_obj_->LookupValue(synth_params, false);
  if (value != nullptr && value->type_ == vm::Value::ANNOTATION) {
    an = value->annotation_;
  }
  params->SetResetPolarity(GetResetPolarity(an));
  if (an != nullptr) {
    int d = an->MaxDelayPs();
    if (d >= 0) {
      params->SetMaxDelayPs(d);
    }
    string f = an->GetPlatformFamily();
    if (!f.empty()) {
      params->SetPlatformFamily(f);
    }
    string n = an->GetPlatformName();
    if (!n.empty()) {
      params->SetPlatformName(n);
    }
  }
}

void DesignSynth::CollectObjSynthRec(ObjectSynth *osynth,
                                     vector<ObjectSynth *> *synthes) {
  synthes->push_back(osynth);
  auto m = obj_tree_->GetChildObjects(osynth->GetObject());
  for (auto it : m) {
    vm::Object *cobj = it.first;
    auto cit = obj_synth_map_.find(cobj);
    if (cit == obj_synth_map_.end()) {
      // Non synthesizable object member.
      continue;
    }
    ObjectSynth *csynth = cit->second;
    CollectObjSynthRec(csynth, synthes);
    csynth->GetIModule()->SetParentModule(osynth->GetIModule());
  }
}

}  // namespace synth

ObjectSynth *osynth = new ObjectSynth(obj, this, is_root, name);


#include "synth/method_synth.h"

#include "base/status.h"
#include "fe/expr.h"
#include "fe/method.h"
#include "fe/var_decl.h"
#include "iroha/iroha.h"
#include "karuta/annotation.h"
#include "synth/design_synth.h"
#include "synth/method_context.h"
#include "synth/object_method.h"
#include "synth/object_method_names.h"
#include "synth/object_synth.h"
#include "synth/resource_set.h"
#include "synth/resource_synth.h"
#include "synth/shared_resource_set.h"
#include "synth/thread_synth.h"
#include "synth/tool.h"
#include "vm/array_wrapper.h"
#include "vm/channel_wrapper.h"
#include "vm/insn.h"
#include "vm/int_array.h"
#include "vm/vm_method.h"
#include "vm/object.h"
#include "vm/profile.h"
#include "vm/register.h"
#include "vm/tls_wrapper.h"
#include "vm/value.h"
#include "vm/vm.h"

namespace synth {

MethodSynth::MethodSynth(ThreadSynth *thr_synth, vm::Object *obj,
                         const string &method_name, ITable *tab,
                         ResourceSynth *rsynth, ResourceSet *res)
    : InsnWalker(thr_synth, obj),
      thr_synth_(thr_synth),
      method_name_(method_name),
      tab_(tab),
      rsynth_(rsynth),
      res_set_(res),
      method_(nullptr),
      is_task_entry_(false),
      is_root_(false),
      thread_index_(0) {
  context_.reset(new MethodContext(this));
  vm::Value *value = obj_->LookupValue(sym_lookup(method_name_.c_str()), false);
  method_ = value->method_;
}

MethodSynth::~MethodSynth() {}

ResourceSet *MethodSynth::GetResourceSet() { return res_set_; }

bool MethodSynth::Synth() {
  if (method_ == nullptr) {
    return false;
  }
  if (method_->GetParseTree() == nullptr && method_->GetSynthName() != kMain) {
    return SynthAlternativeImplMethod(method_);
  }
  if (method_->GetAnnotation()->IsImportedModule()) {
    SynthEmbeddedMethod(method_);
    return true;
  }
  if (method_->GetAnnotation()->IsExtIO()) {
    SynthExtIOMethod();
    return true;
  }

  return SynthFromInsns();
}

bool MethodSynth::SynthFromInsns() {
  EmitSignatureInsn(method_);
  // Initial insn. (ext) task entry may be allocated to here.
  StateWrapper *prev_last = AllocState();
  // mapping from vm::insn index to state index.
  map<int, int> state_index;
  state_index[0] = context_->states_.size();
  for (size_t i = 0; i < method_->insns_.size(); ++i) {
    vm::Insn *insn = method_->insns_[i];
    SynthInsn(insn);
    if (Status::CheckAllErrors(false)) {
      return false;
    }
    MayAnnotateProfile(i, prev_last);
    StateWrapper *lw = context_->LastState();
    if (prev_last == lw) {
      // Dummy state, since this insn didn't emit a state.
      AllocState();
    }
    state_index[i + 1] = context_->states_.size();
    prev_last = context_->LastState();
  }
  for (size_t i = 0; i < method_->insns_.size(); ++i) {
    vm_insn_state_map_[i] = context_->states_[state_index[i]];
  }
  AllocState();

  ResolveJumps();
  LinkStates();
  if (is_task_entry_) {
    EmitTaskEntry(context_->states_[0]->state_);
    EmitTaskReturn(context_->states_[context_->states_.size() - 1]->state_);
  }
  if (is_root_ && IsExtEntry()) {
    EmitExtTaskEntry(context_->states_[0]->state_);
    EmitExtTaskDone(context_->states_[context_->states_.size() - 1]->state_);
  }
  if (is_root_ && IsDataFlowEntry()) {
    EmitDataFlowEntry(context_->states_[0]->state_);
  }
  if (IsThreadEntry()) {
    MayEmitThreadIndex(context_->states_[0]->state_);
  }
  return true;
}

void MethodSynth::SetTaskEntry() { is_task_entry_ = true; }

void MethodSynth::SetRoot(int thread_index) {
  is_root_ = true;
  thread_index_ = thread_index;
}

MethodContext *MethodSynth::GetContext() { return context_.get(); }

void MethodSynth::EmitDataFlowEntry(IState *st) {
  vm::Object *obj = thr_synth_->GetThreadObject();
  CHECK(obj);
  int width = 0;
  for (IRegister *arg : context_->method_signature_insn_->inputs_) {
    width += arg->value_type_.GetWidth();
  }
  // 1 for dummy argument.
  if (width == 0) {
    width = 1;
  }
  int depth = method_->GetAnnotation()->GetDepth();
  IResource *mb = res_set_->GetChannelResource(obj, true, false, width, depth);
  IResource *df = res_set_->GetDataFlowInResource();
  df->SetParentResource(mb);
  IInsn *df_insn = new IInsn(df);
  st->insns_.push_back(df_insn);
  for (IRegister *arg : context_->method_signature_insn_->inputs_) {
    df_insn->outputs_.push_back(arg);
  }
}

void MethodSynth::EmitTaskEntry(IState *st) {
  IResource *res = res_set_->GetSubModuleTaskResource();
  IInsn *iinsn = new IInsn(res);
  // Args
  for (IRegister *reg : context_->method_signature_insn_->inputs_) {
    iinsn->outputs_.push_back(reg);
    res->output_types_.push_back(reg->value_type_);
  }
  st->insns_.push_back(iinsn);
}

void MethodSynth::EmitTaskReturn(IState *last) {
  int num_rets = method_->GetNumReturnRegisters();
  if (num_rets == 0) {
    return;
  }
  int num_args = method_->GetNumArgRegisters();
  int width = 0;
  for (int i = 0; i < num_rets; ++i) {
    vm::Register *ret = method_->method_regs_[i + num_args];
    if (ret->type_.value_type_ == vm::Value::ENUM_ITEM) {
      // Assumes bool.
      width += 1;
    } else {
      width += ret->type_.num_width_.GetWidth();
    }
  }
  IResource *res = res_set_->GetTaskReturnRegWriter(width);
  IInsn *insn = new IInsn(res);
  for (int i = 0; i < num_rets; ++i) {
    vm::Register *ret = method_->method_regs_[i + num_args];
    IRegister *reg = local_reg_map_[ret];
    insn->inputs_.push_back(reg);
  }
  insn->SetOperand(iroha::operand::kPutMailbox);
  IInsn *entry_insn = nullptr;
  for (IInsn *insn : context_->states_[0]->state_->insns_) {
    if (resource::IsTask(*(insn->GetResource()->GetClass()))) {
      entry_insn = insn;
    }
  }
  CHECK(entry_insn);
  insn->depending_insns_.push_back(entry_insn);
  last->insns_.push_back(insn);
}

void MethodSynth::EmitExtTaskEntry(IState *st) {
  IResource *ext_task = res_set_->GetExtTaskResource();
  string name = method_->GetAnnotation()->GetName();
  if (name.empty()) {
    name = method_name_;
  }
  ext_task->GetParams()->SetExtTaskName(name);
  IInsn *task_insn = new IInsn(ext_task);
  // Args
  for (IRegister *arg : context_->method_signature_insn_->inputs_) {
    task_insn->outputs_.push_back(arg);
    ext_task->output_types_.push_back(arg->value_type_);
  }
  st->insns_.push_back(task_insn);
}

void MethodSynth::EmitExtTaskDone(IState *st) {
  IResource *ext_task_done = res_set_->GetExtTaskDoneResource();
  IResource *ext_task = res_set_->GetExtTaskResource();
  ext_task_done->SetParentResource(ext_task);
  IInsn *task_done_insn = new IInsn(ext_task_done);
  st->insns_.push_back(task_done_insn);
  int num_rets = method_->GetNumReturnRegisters();
  if (num_rets == 0) {
    return;
  }
  int num_args = method_->GetNumArgRegisters();
  for (int i = 0; i < num_rets; ++i) {
    vm::Register *ret = method_->method_regs_[i + num_args];
    IRegister *reg = local_reg_map_[ret];
    task_done_insn->inputs_.push_back(reg);
    ext_task_done->input_types_.push_back(reg->value_type_);
  }
}

void MethodSynth::MayEmitThreadIndex(IState *st) {
  CHECK(method_->GetNumArgRegisters() <= 1)
      << "Too many arguments for a thread entry method";
  if (method_->GetNumArgRegisters() != 1) {
    return;
  }
  vm::Register *arg = method_->method_regs_[0];
  IRegister *arg_reg = local_reg_map_[arg];
  IResource *assign = res_set_->AssignResource();
  IInsn *iinsn = new IInsn(assign);
  IRegister *ireg = DesignTool::AllocConstNum(
      tab_, arg_reg->value_type_.GetWidth(), thread_index_);
  iinsn->inputs_.push_back(ireg);
  iinsn->outputs_.push_back(arg_reg);
  st->insns_.push_back(iinsn);
}

bool MethodSynth::IsDataFlowEntry() const {
  return method_->GetAnnotation()->IsDataFlowEntry();
}

bool MethodSynth::IsExtEntry() const {
  return method_->GetAnnotation()->IsExtEntry();
}

bool MethodSynth::IsThreadEntry() const {
  return is_root_ && !is_task_entry_ && !IsExtEntry() && !IsDataFlowEntry();
}

bool MethodSynth::SynthAlternativeImplMethod(vm::Method *method) {
  sym_t name = sym_lookup(method->AlternativeImplementation());
  if (name == sym_null) {
    Status::os(Status::USER_ERROR)
        << "No native implementation for method: " << method_name_;
    return false;
  }
  vm::Value *value = obj_->LookupValue(name, false);
  CHECK(value && value->type_ == vm::Value::METHOD) << sym_cstr(name);
  vm::Method *alt_method = value->method_;
  CHECK(alt_method->GetAnnotation()->IsImportedModule()) << sym_cstr(name);

  SynthEmbeddedMethod(alt_method);
  return true;
}

void MethodSynth::SynthEmbeddedMethod(vm::Method *method) {
  EmitSignatureInsn(method);
  StateWrapper *sw = AllocState();
  IResource *res = res_set_->GetImportedResource(method);
  IInsn *iinsn = new IInsn(res);
  iinsn->inputs_ = context_->method_signature_insn_->inputs_;
  iinsn->outputs_ = context_->method_signature_insn_->outputs_;
  sw->state_->insns_.push_back(iinsn);
}

void MethodSynth::SynthExtIOMethod() {
  EmitSignatureInsn(method_);
  Annotation *an = method_->GetAnnotation();
  if (an->IsExtOutput()) {
    DoSynthExtIO(true);
  }
  if (an->IsExtInput()) {
    DoSynthExtIO(false);
  }
}

void MethodSynth::DoSynthExtIO(bool is_output) {
  if (!shared_resource_set_->AddExtIOMethodAccessor(thr_synth_, method_)) {
    Status::os(Status::USER_ERROR)
        << "ExtIO can be accessed from only 1 thread.";
    return;
  }
  IResource *res = rsynth_->MayAddExtIO(method_, is_output);
  if (res == nullptr) {
    // Error.
    return;
  }
  IInsn *iinsn = new IInsn(res);
  if (is_output) {
    CHECK(context_->method_signature_insn_->inputs_.size() == 1);
    iinsn->inputs_.push_back(context_->method_signature_insn_->inputs_[0]);
  } else {
    CHECK(context_->method_signature_insn_->outputs_.size() == 1);
    iinsn->outputs_.push_back(context_->method_signature_insn_->outputs_[0]);
  }
  StateWrapper *sw = AllocState();
  sw->state_->insns_.push_back(iinsn);
}

void MethodSynth::LinkStates() {
  for (size_t i = 0; i < context_->states_.size() - 1; ++i) {
    StateWrapper *s0 = context_->states_[i];
    IInsn *tr_insn = DesignUtil::FindTransitionInsn(s0->state_);
    if (tr_insn != nullptr && tr_insn->target_states_.size() > 0) {
      continue;
    }
    StateWrapper *s1 = context_->states_[i + 1];
    DesignTool::AddNextState(s0->state_, s1->state_);
  }
}

void MethodSynth::SynthInsn(vm::Insn *insn) {
  switch (insn->op_) {
    case vm::OP_NOP:
    case vm::OP_YIELD:
      SynthNop(insn);
      break;
    case vm::OP_NUM:
      SynthNum(insn);
      break;
    case vm::OP_ASSIGN:
      SynthAssign(insn);
      break;
    case vm::OP_FUNCALL:
      SynthFuncall(insn);
      break;
    case vm::OP_FUNCALL_DONE:
      SynthFuncallDone(insn);
      break;
    case vm::OP_LOAD_OBJ:
      SynthLoadObj(insn);
      break;
    case vm::OP_GOTO:
      SynthGoto(insn);
      break;
    case vm::OP_IF:
      SynthIf(insn);
      break;
    case vm::OP_ADD:
    case vm::OP_SUB:
    case vm::OP_MUL:
    case vm::OP_DIV:
    case vm::OP_EQ:
    case vm::OP_NE:
    case vm::OP_GT:
    case vm::OP_LT:
    case vm::OP_GTE:
    case vm::OP_LTE:
    case vm::OP_AND:
    case vm::OP_OR:
    case vm::OP_XOR:
    case vm::OP_LAND:
    case vm::OP_LOR:
      SynthBinCalcExpr(insn);
      break;
    case vm::OP_LSHIFT:
    case vm::OP_RSHIFT:
      SynthShiftExpr(insn);
      break;
    case vm::OP_CONCAT:
      SynthConcat(insn);
      break;
    case vm::OP_PRE_DEC:
    case vm::OP_PRE_INC:
      SynthPreIncDec(insn);
      break;
    case vm::OP_MEMBER_READ:
      SynthMemberAccess(insn, false);
      break;
    case vm::OP_MEMBER_WRITE:
      SynthMemberAccess(insn, true);
      break;
    case vm::OP_ARRAY_READ:
      InsnWalker::MaybeLoadObjectArrayElement(insn);
      SynthArrayAccess(insn, false);
      break;
    case vm::OP_ARRAY_WRITE:
      SynthArrayAccess(insn, true);
      break;
    case vm::OP_BIT_RANGE:
      SynthBitRange(insn);
      break;
    case vm::OP_BIT_INV:
    case vm::OP_LOGIC_INV:
      SynthBitInv(insn);
      break;
    default:
      CHECK(false) << "unknown insn:" << vm::OpCodeName(insn->op_);
  }
}

void MethodSynth::SynthNop(vm::Insn *insn) {}

void MethodSynth::SynthNum(vm::Insn *insn) {
  vm::Register *src_reg = insn->src_regs_[0];
  CHECK(src_reg->type_.value_type_ == vm::Value::NUM);
  vm::Register *dst_reg = insn->dst_regs_[0];
  IRegister *ireg =
      DesignTool::AllocConstNum(tab_, src_reg->type_.num_width_.GetWidth(),
                                src_reg->initial_num_.GetValue0());
  local_reg_map_[dst_reg] = ireg;
}

void MethodSynth::SynthAssign(vm::Insn *insn) {
  IRegister *lhs_reg = FindLocalVarRegister(insn->src_regs_[0]);
  IRegister *rhs_reg = FindLocalVarRegister(insn->src_regs_[1]);
  IResource *assign = res_set_->AssignResource();
  IInsn *iinsn = new IInsn(assign);
  iinsn->inputs_.push_back(rhs_reg);
  iinsn->outputs_.push_back(lhs_reg);
  StateWrapper *sw = AllocState();
  sw->state_->insns_.push_back(iinsn);
}

void MethodSynth::SynthLoadObj(vm::Insn *insn) { InsnWalker::LoadObj(insn); }

void MethodSynth::SynthPreIncDec(vm::Insn *insn) {
  IValueType vt;
  InsnToCalcValueType(insn, &vt);
  IResource *res = res_set_->GetOpResource(
      insn->op_ == vm::OP_PRE_INC ? vm::OP_ADD : vm::OP_SUB, vt);
  IInsn *iinsn = new IInsn(res);
  IRegister *reg = FindLocalVarRegister(insn->src_regs_[0]);
  iinsn->inputs_.push_back(reg);
  int w = reg->value_type_.GetWidth();
  iinsn->inputs_.push_back(DesignTool::AllocConstNum(tab_, w, 1));
  iinsn->outputs_.push_back(reg);
  StateWrapper *sw = AllocState();
  sw->state_->insns_.push_back(iinsn);
}

void MethodSynth::SynthNative(vm::Insn *insn) {
  ObjectMethod m(this, this, rsynth_, insn);
  m.Synth();
}

void MethodSynth::SynthFuncall(vm::Insn *insn) {
  if (IsNativeFuncall(insn)) {
    SynthNative(insn);
    return;
  }

  // Setup arguments.
  vector<IRegister *> iargs;
  for (vm::Register *arg : insn->src_regs_) {
    IRegister *iarg = FindLocalVarRegister(arg);
    iargs.push_back(iarg);
  }
  if (IsDataFlowCall(insn) || IsExtStubCall(insn)) {
    AdjustArgWidth(insn, &iargs);
  }
  IInsn *iinsn = new IInsn(res_set_->PseudoCallResource());
  StateWrapper *sw = AllocState();
  sw->state_->insns_.push_back(iinsn);
  for (IRegister *iarg : iargs) {
    iinsn->inputs_.push_back(iarg);
  }

  vm::Object *callee_obj = GetCalleeObject(insn);
  sym_t func_name = insn->label_;
  sw->callee_func_name_ = sym_str(func_name);
  sw->callee_vm_obj_ = callee_obj;
  if (IsExtStubCall(insn)) {
    sw->is_ext_stub_call_ = true;
    sw->is_ext_flow_stub_call_ = IsExtFlowStubCall(insn);
  }
  if (IsDataFlowCall(insn)) {
    sw->is_data_flow_call_ = true;
  }
  if (IsSubObjCall(insn)) {
    CHECK(callee_obj);
    sw->is_sub_obj_call_ = true;
    DesignSynth *ds = thr_synth_->GetObjectSynth()->GetDesignSynth();
    sw->obj_name_ = ds->GetObjectName(callee_obj);
  }
}

void MethodSynth::AdjustArgWidth(vm::Insn *insn, vector<IRegister *> *args) {
  vm::Object *callee_obj = GetCalleeObject(insn);
  vm::Value *value = callee_obj->LookupValue(insn->label_, false);
  CHECK(value && value->type_ == vm::Value::METHOD) << sym_cstr(insn->label_);
  vm::Method *method = value->method_;
  int num_args = method->GetNumArgRegisters();
  CHECK(num_args == args->size());
  for (int i = 0; i < args->size(); ++i) {
    IRegister *arg = args->at(i);
    const iroha::NumericWidth &actual_width = method->GetNthArgWidth(i);
    if (arg->value_type_.GetWidth() != actual_width.GetWidth()) {
      IResource *assign = res_set_->AssignResource();
      IRegister *lhs = thr_synth_->AllocRegister("df_arg");
      lhs->value_type_.SetWidth(actual_width.GetWidth());
      IInsn *a = new IInsn(assign);
      a->inputs_.push_back(arg);
      a->outputs_.push_back(lhs);
      StateWrapper *sw = AllocState();

void MethodSynth::SynthFuncallDone(vm::Insn *insn) {
  // Adds return values to the insn generated for funcall.
  StateWrapper *sw = context_->LastState();
  CHECK(sw->state_->insns_.size() == 1);
  IInsn *iinsn = sw->state_->insns_[0];
  for (vm::Register *ret : insn->dst_regs_) {
    IRegister *iret = FindLocalVarRegister(ret);
    iinsn->outputs_.push_back(iret);
  }

  if (IsSubObjCall(insn) || IsExtStubCall(insn)) {
    // state for capturing return value.
    AllocState();
  }
}

void MethodSynth::SynthIf(vm::Insn *insn) {
  StateWrapper *sw = AllocState();
  sw->vm_insn_ = insn;
  IInsn *iinsn = DesignUtil::GetTransitionInsn(sw->state_);
  IRegister *cond_reg = FindLocalVarRegister(insn->src_regs_[0]);
  iinsn->inputs_.push_back(cond_reg);
}

void MethodSynth::SynthConcat(vm::Insn *insn) {
  IValueType vt;
  IResource *concat = res_set_->GetOpResource(vm::OP_CONCAT, vt);
  IInsn *iinsn = new IInsn(concat);
  IRegister *lhs = FindLocalVarRegister(insn->src_regs_[0]);
  IRegister *rhs = FindLocalVarRegister(insn->src_regs_[1]);
  iinsn->inputs_.push_back(lhs);
  iinsn->inputs_.push_back(rhs);
  IRegister *res = FindLocalVarRegister(insn->dst_regs_[0]);
  iinsn->outputs_.push_back(res);
  StateWrapper *sw = AllocState();
  sw->state_->insns_.push_back(iinsn);
}

void MethodSynth::SynthBitRange(vm::Insn *insn) {
  IRegister *src = FindLocalVarRegister(insn->src_regs_[0]);
  IRegister *res = FindLocalVarRegister(insn->dst_regs_[0]);
  int msb = insn->src_regs_[1]->initial_num_.GetValue0();
  int lsb = insn->src_regs_[2]->initial_num_.GetValue0();

  StateWrapper *sw = AllocState();
  IValueType vt_none;
  IResource *bit_sel = res_set_->GetOpResource(vm::OP_BIT_RANGE, vt_none);
  IInsn *iinsn = new IInsn(bit_sel);

  iinsn->inputs_.push_back(src);
  iinsn->inputs_.push_back(DesignTool::AllocConstNum(tab_, 32, msb));
  iinsn->inputs_.push_back(DesignTool::AllocConstNum(tab_, 32, lsb));
  iinsn->outputs_.push_back(res);
  sw->state_->insns_.push_back(iinsn);
}

void MethodSynth::SynthBitInv(vm::Insn *insn) {
  IRegister *src = FindLocalVarRegister(insn->src_regs_[0]);
  IRegister *res = FindLocalVarRegister(insn->dst_regs_[0]);
  GenNeg(src, res);
}

IRegister *MethodSynth::FindLocalVarRegister(vm::Register *vreg) {
  auto it = local_reg_map_.find(vreg);
  if (it != local_reg_map_.end()) {
    return it->second;
  }
  string name = method_name_;
  if (vreg->orig_name_ != sym_null) {
    name = name + "_" + sym_str(vreg->orig_name_);
  }
  IRegister *ireg = thr_synth_->AllocRegister(name);
  int w = vreg->type_.num_width_.GetWidth();
  if (vreg->type_.value_type_ == vm::Value::ENUM_ITEM) {
    w = 0;
  }
  ireg->value_type_.SetWidth(w);
  local_reg_map_[vreg] = ireg;
  Annotation *an = vreg->GetAnnotation();
  if (an != nullptr) {
    auto *params = ireg->GetMutableParams(true);
    params->SetLoopUnroll(an->GetNum());
    if (an->IsPipeline()) {
      params->SetIsPipeline(true);
      if (an->IsPipelineX()) {
        params->SetExperimental(1);
      }
    }
  }
  return ireg;
}

ITable *MethodSynth::GetITable() { return tab_; }

ThreadSynth *MethodSynth::GetThreadSynth() { return thr_synth_; }

IRegister *MethodSynth::FindArgRegister(vm::Method *method, int nth,
                                        fe::VarDecl *arg_decl) {
  int w = 0;
  if (arg_decl->GetType() == sym_bool) {
    w = 0;
  } else if (arg_decl->GetType() == sym_uint ||
             arg_decl->GetType() == sym_null) {
    w = arg_decl->GetWidth().GetWidth();
  } else {
    CHECK(false);
  }
  string reg_name = sym_str(arg_decl->GetNameExpr()->GetSym());
  IRegister *reg = thr_synth_->AllocRegister(reg_name);
  reg->value_type_.SetWidth(w);
  // Add as a local variable if this isn't an imported method.
  if (!(method->GetAnnotation()->IsImportedModule())) {
    local_reg_map_[method->method_regs_[nth]] = reg;
  }
  return reg;
}

void MethodSynth::ResolveJumps() {
  int sw_idx = 0;
  for (auto &sw : context_->states_) {
    if (sw->vm_insn_) {
      if (sw->vm_insn_->op_ == vm::OP_GOTO) {
        IState *st = vm_insn_state_map_[sw->vm_insn_->jump_target_]->state_;
        CHECK(st) << "couldn't resolve goto" << sw->vm_insn_->jump_target_;
        DesignTool::AddNextState(sw->state_, st);
      }
      if (sw->vm_insn_->op_ == vm::OP_IF) {
        // next, jump_target
        IState *next_st = context_->states_[sw_idx + 1]->state_;
        IState *target_st =
            vm_insn_state_map_[sw->vm_insn_->jump_target_]->state_;
        IInsn *tr_insn = DesignUtil::FindTransitionInsn(sw->state_);
        tr_insn->target_states_.push_back(target_st);
        tr_insn->target_states_.push_back(next_st);
      }
    }
    ++sw_idx;
  }
}

StateWrapper *MethodSynth::AllocState() {
  StateWrapper *sw = new StateWrapper();
  IState *st = new IState(tab_);
  sw->state_ = st;
  sw->index_ = context_->states_.size();
  context_->states_.push_back(sw);
  return sw;
}

void MethodSynth::SynthDivExpr(vm::Insn *insn) {
  // Allows const / const only for now.
  CHECK(insn->src_regs_[0]->type_.is_const_ &&
        insn->src_regs_[1]->type_.is_const_);
  int v = insn->src_regs_[0]->initial_num_.GetValue0() /
          insn->src_regs_[1]->initial_num_.GetValue0();
  vm::Register *dst_reg = insn->dst_regs_[0];
  IRegister *ireg =
      DesignTool::AllocConstNum(tab_, dst_reg->type_.num_width_.GetWidth(), v);
  local_reg_map_[dst_reg] = ireg;
}

void MethodSynth::SynthBinCalcExpr(vm::Insn *insn) {
  if (insn->op_ == vm::OP_DIV) {
    SynthDivExpr(insn);
    return;
  }
  IValueType vt;
  InsnToCalcValueType(insn, &vt);
  IResource *res = res_set_->GetOpResource(insn->op_, vt);
  IInsn *iinsn = new IInsn(res);
  for (vm::Register *vreg : insn->src_regs_) {
    IRegister *reg = FindLocalVarRegister(vreg);
    iinsn->inputs_.push_back(reg);
  }

  // GT: x > y : x > y
  // LT: x < y : y > x
  // LTE: x <= y : !(x > y)
  // GTE: x >= y : !(y > x)
  if (insn->op_ == vm::OP_LT || insn->op_ == vm::OP_GTE) {
    // swap.
    IRegister *tmp_reg = iinsn->inputs_[0];
    iinsn->inputs_[0] = iinsn->inputs_[1];
    iinsn->inputs_[1] = tmp_reg;
  }
  StateWrapper *w = AllocState();
  IRegister *res_reg = FindLocalVarRegister(insn->dst_regs_[0]);
  if (insn->op_ == vm::OP_LTE || insn->op_ == vm::OP_GTE ||
      insn->op_ == vm::OP_NE) {
    IRegister *neg_reg = thr_synth_->AllocRegister("t");
    neg_reg->value_type_.SetWidth(0);
    iinsn->outputs_.push_back(neg_reg);
    GenNeg(neg_reg, res_reg);
    w->state_->insns_.push_back(iinsn);
  } else {
    iinsn->outputs_.push_back(res_reg);
    w->state_->insns_.push_back(iinsn);
  }
}

void MethodSynth::SynthShiftExpr(vm::Insn *insn) {
  CHECK(insn->src_regs_[1]->type_.is_const_);
  int shift_count = insn->src_regs_[1]->initial_num_.GetValue0();
  IValueType vt;
  IResource *shifter = res_set_->GetOpResource(insn->op_, vt);
  IInsn *iinsn = new IInsn(shifter);
  iinsn->inputs_.push_back(FindLocalVarRegister(insn->src_regs_[0]));
  iinsn->inputs_.push_back(DesignTool::AllocConstNum(tab_, 32, shift_count));
  iinsn->outputs_.push_back(FindLocalVarRegister(insn->dst_regs_[0]));
  if (insn->op_ == vm::OP_LSHIFT) {
    iinsn->SetOperand(iroha::operand::kLeft);
  } else {
    iinsn->SetOperand(iroha::operand::kRight);
  }
  StateWrapper *w = AllocState();
  w->state_->insns_.push_back(iinsn);
}

void MethodSynth::SynthGoto(vm::Insn *insn) {
  StateWrapper *w = AllocState();
  w->vm_insn_ = insn;
}

void MethodSynth::SynthMemberAccess(vm::Insn *insn, bool is_store) {
  InsnWalker::MaybeLoadMemberObject(insn);
  vm::Object *obj;
  if (is_store) {
    obj = member_reg_to_obj_map_[insn->obj_reg_];
  } else {
    obj = member_reg_to_obj_map_[insn->obj_reg_];
  }
  CHECK(obj);
  vm::Value *value = obj->LookupValue(insn->label_, false);
  CHECK(value) << "member not found";
  auto vt = value->type_;
  if (vm::TlsWrapper::IsTlsValue(value)) {
    vm::Value *baseValue = vm::TlsWrapper::GetValue(value->object_, nullptr);
    vt = baseValue->type_;
  }
  if (value->is_const_) {
    // assuming only bool type for now.
    CHECK(!is_store);
    CHECK(value->type_ == vm::Value::ENUM_ITEM);
    IRegister *reg = DesignTool::AllocConstNum(tab_, 0, value->enum_val_.val);
    local_reg_map_[insn->dst_regs_[0]] = reg;
  } else if (vt == vm::Value::OBJECT || vt == vm::Value::INT_ARRAY ||
             vt == vm::Value::OBJECT_ARRAY) {
    // processed in MaybeLoadMemberObject()
    CHECK(!is_store);
  } else {
    ThreadSynth *local_accessor = nullptr;
    if (vm::TlsWrapper::IsTlsValue(value)) {
      value = vm::TlsWrapper::GetValue(value->object_, nullptr);
      local_accessor = thr_synth_;
    }
    CHECK(!vm::TlsWrapper::IsTlsValue(value));
    CHECK(value->type_ == vm::Value::NUM);
    SharedResource *sres =
        shared_resource_set_->GetBySlotName(obj, local_accessor, insn->label_);
    if (sres->accessors_.size() > 1) {
      SynthMemberSharedRegAccess(insn, obj, value, is_store);
    } else {
      SynthMemberRegAccess(insn, obj, value, is_store);
    }
  }
}

void MethodSynth::SynthMemberRegAccess(vm::Insn *insn, vm::Object *owner_obj,
                                       vm::Value *value, bool is_store) {
  IRegister *reg =
      member_name_reg_map_[std::make_tuple(owner_obj, sym_cstr(insn->label_))];
  if (reg == nullptr) {
    string name = sym_cstr(insn->label_);
    name = "m_" + name;
    reg = thr_synth_->AllocRegister(name);
    iroha::Numeric n(value->num_value_, value->num_width_);
    reg->SetInitialValue(n);
    int w = 0;
    if (value->type_ == vm::Value::NUM) {
      w = value->num_width_.GetWidth();
    }
    reg->value_type_.SetWidth(w);
    member_name_reg_map_[std::make_tuple(owner_obj, sym_cstr(insn->label_))] =
        reg;
  }
  IResource *assign = res_set_->AssignResource();
  IInsn *iinsn = new IInsn(assign);
  if (is_store) {
    iinsn->inputs_.push_back(FindLocalVarRegister(insn->src_regs_[0]));
    iinsn->outputs_.push_back(reg);
  } else {
    iinsn->inputs_.push_back(reg);
    IRegister *oreg = FindLocalVarRegister(insn->dst_regs_[0]);
    oreg->value_type_.SetWidth(reg->value_type_.GetWidth());
    iinsn->outputs_.push_back(oreg);
  }
  StateWrapper *sw = AllocState();
  sw->state_->insns_.push_back(iinsn);
}

void MethodSynth::SynthMemberSharedRegAccess(vm::Insn *insn,
                                             vm::Object *owner_obj,
                                             vm::Value *value, bool is_store) {
  // This can't be a tls (because shared != tls), so use the default thread.
  SharedResource *sres =
      shared_resource_set_->GetBySlotName(owner_obj, nullptr, insn->label_);
  IResource *res;
  if (sres->GetOwnerThread() == thr_synth_) {
    res = res_set_->GetMemberSharedReg(insn->label_, true, is_store);
    sres->SetOwnerIResource(res);
    int w = value->num_width_.GetWidth();
    auto *params = res->GetParams();
    params->SetWidth(w);
    // TODO: Fix this for a value wider than 32bits.
    params->SetInitialValue(value->num_value_.GetValue0());
  } else {
    res = res_set_->GetMemberSharedReg(insn->label_, false, is_store);
    sres->AddAccessorResource(res, thr_synth_);
  }
  IInsn *iinsn = new IInsn(res);
  vm::Register *vm_reg;
  if (is_store) {
    vm_reg = insn->src_regs_[0];
  } else {
    vm_reg = insn->dst_regs_[0];
  }
  IRegister *ireg = FindLocalVarRegister(vm_reg);
  if (is_store) {
    iinsn->inputs_.push_back(ireg);
  } else {
    ireg->value_type_.SetWidth(value->num_width_.GetWidth());
    iinsn->outputs_.push_back(ireg);
  }
  StateWrapper *sw = AllocState();
  sw->state_->insns_.push_back(iinsn);
}

void MethodSynth::SynthArrayAccess(vm::Insn *insn, bool is_write) {
  vm::Object *array_obj = member_reg_to_obj_map_[insn->obj_reg_];
  CHECK(array_obj);
  if (vm::ArrayWrapper::IsObjectArray(array_obj)) {
    return;
  }
  int s = 0;
  if (is_write) {
    s = 1;
  }
  IRegister *index = GetArrayIndex(array_obj, insn, s);
  if (UseSharedArray(array_obj)) {
    SynthSharedArrayAccess(insn, is_write, index);
  } else {
    SynthLocalArrayAccess(insn, is_write, index);
  }
}

bool MethodSynth::UseSharedArray(vm::Object *array_obj) {
  Annotation *a = vm::ArrayWrapper::GetAnnotation(array_obj);
  if (a != nullptr &&
      (a->IsAxiMaster() || a->IsAxiSlave() || a->IsExportSramIf())) {
    return true;
  }
  if (a != nullptr && a->GetNum() > 1) {
    return true;
  }
  ThreadSynth *tls_thr = nullptr;
  if (thread_local_objs_.find(array_obj) != thread_local_objs_.end()) {
    tls_thr = thr_synth_;
  }
  SharedResource *sres = shared_resource_set_->GetByObj(array_obj, tls_thr);
  if (sres->accessors_.size() >= 2 || sres->axi_master_ctrl_thrs_.size() > 0) {
    return true;
  }
  return false;
}

void MethodSynth::SynthSharedArrayAccess(vm::Insn *insn, bool is_write,
                                         IRegister *index) {
  vm::Object *array_obj = GetObjByReg(insn->obj_reg_);
  SharedResource *array_sres =
      shared_resource_set_->GetByObj(array_obj, nullptr);
  IResource *res = nullptr;
  bool use_replica = false;
  Annotation *a = vm::ArrayWrapper::GetAnnotation(array_obj);
  bool forrceAccessor = false;
  if (a != nullptr && (a->IsAxiMasterAndExport() || a->IsAxiSlaveAndExport())) {
    // This array should be accessed with shared-memory-{reader, writer},
    // because there is no available port for shared-memory.
    forrceAccessor = true;
  }
  if (array_sres->GetOwnerThread() == thr_synth_ && !forrceAccessor) {
    int ridx = -1;
    if (!is_write) {
      ridx = GetArrayReplicaIndex(array_obj, insn);
    }
    if (ridx > -1) {
      use_replica = true;
      res = res_set_->GetSharedArrayReplica(array_obj, ridx);
    } else {
      res = res_set_->GetSharedArray(array_obj, true /* is_owner */,
                                     false /* !is_write */);
    }
    array_sres->SetOwnerIResource(res);
    rsynth_->MayAddAxiMasterPort(obj_, array_obj);
    rsynth_->MayAddAxiSlavePort(obj_, array_obj);
    rsynth_->MayAddSramIfPort(obj_, array_obj);
    rsynth_->MayConfigureExternalSram(array_obj, res);
  } else {
    res = res_set_->GetSharedArray(array_obj, false /* is_owner */, is_write);
    array_sres->AddAccessorResource(res, thr_synth_);
  }
  IInsn *iinsn = new IInsn(res);
  if (!is_write && use_replica) {
    SynthSramRead(insn, iinsn, index);
    return;
  }
  if (is_write) {
    // index, value
    iinsn->inputs_.push_back(index);
    iinsn->inputs_.push_back(FindLocalVarRegister(insn->src_regs_[0]));
  } else {
    iinsn->inputs_.push_back(index);
    iinsn->outputs_.push_back(FindLocalVarRegister(insn->dst_regs_[0]));
  }
  StateWrapper *sw = AllocState();
  sw->state_->insns_.push_back(iinsn);
}

void MethodSynth::SynthLocalArrayAccess(vm::Insn *insn, bool is_write,
                                        IRegister *index) {
  vm::Object *array_obj = GetObjByReg(insn->obj_reg_);
  IResource *res = res_set_->GetInternalArrayResource(array_obj);
  rsynth_->MayConfigureExternalSram(array_obj, res);
  IInsn *iinsn = new IInsn(res);
  if (!is_write) {
    SynthSramRead(insn, iinsn, index);
    return;
  }
  // index, value
  iinsn->SetOperand(iroha::operand::kSramWrite);
  iinsn->inputs_.push_back(index);
  iinsn->inputs_.push_back(FindLocalVarRegister(insn->src_regs_[0]));
  StateWrapper *w = AllocState();
  w->state_->insns_.push_back(iinsn);
}

void MethodSynth::SynthSramRead(vm::Insn *insn, IInsn *iinsn,
                                IRegister *index) {
  // Output address.
  iinsn->SetOperand(iroha::operand::kSramReadAddress);
  iinsn->inputs_.push_back(index);
  StateWrapper *w = AllocState();
  w->state_->insns_.push_back(iinsn);
  // Read data
  iinsn = new IInsn(iinsn->GetResource());
  iinsn->SetOperand(iroha::operand::kSramReadData);
  iinsn->outputs_.push_back(FindLocalVarRegister(insn->dst_regs_[0]));
  w = AllocState();
  w->state_->insns_.push_back(iinsn);
}

IRegister *MethodSynth::GetArrayIndex(vm::Object *array_obj, vm::Insn *insn,
                                      int start) {
  vector<IRegister *> indexes;
  vm::IntArray *array = vm::ArrayWrapper::GetIntArray(array_obj);
  const vector<uint64_t> &shape = array->GetShape();
  for (int i = start;
       i < insn->src_regs_.size() && indexes.size() < shape.size(); i++) {
    indexes.push_back(FindLocalVarRegister(insn->src_regs_[i]));
  }
  if (indexes.size() == 1 || shape.size() == 1) {
    return indexes[0];
  }
  IRegister *reg = thr_synth_->AllocRegister("t");
  IValueType vt;
  IResource *concat = res_set_->GetOpResource(vm::OP_CONCAT, vt);
  IInsn *iinsn = new IInsn(concat);
  iinsn->outputs_.push_back(reg);
  reg->value_type_.SetWidth(array->GetAddressWidth());
  // Shape and indexes are low to high, on the other hand
  // concat insn takes input from high to low.
  for (int i = indexes.size() - 1; i >= 0; --i) {
    int w = Util::Log2(shape[i]);
    IRegister *idx_reg = indexes[i];
    if (idx_reg->value_type_.GetWidth() != w) {
      IRegister *tmp_reg = thr_synth_->AllocRegister("t");
      tmp_reg->value_type_.SetWidth(w);
      IResource *assign = res_set_->AssignResource();
      IInsn *assign_insn = new IInsn(assign);
      assign_insn->inputs_.push_back(idx_reg);
      assign_insn->outputs_.push_back(tmp_reg);
      idx_reg = tmp_reg;
      StateWrapper *sw = AllocState();
      sw->state_->insns_.push_back(assign_insn);
    }
    iinsn->inputs_.push_back(idx_reg);
  }
  StateWrapper *sw = AllocState();
  sw->state_->insns_.push_back(iinsn);
  return reg;
}

int MethodSynth::GetArrayReplicaIndex(vm::Object *array_obj, vm::Insn *insn) {
  vm::IntArray *array = vm::ArrayWrapper::GetIntArray(array_obj);
  const vector<uint64_t> &shape = array->GetShape();
  if (insn->src_regs_.size() != shape.size() + 1) {
    return -1;
  }
  vm::Register *rep = insn->src_regs_[shape.size()];
  CHECK(rep->type_.value_type_ == vm::Value::NUM);
  return rep->initial_num_.GetValue0();
}

void MethodSynth::GenNeg(IRegister *src, IRegister *dst) {
  StateWrapper *w = AllocState();
  IValueType vt;
  vt.SetWidth(0);
  IResource *bit_inv = res_set_->GetOpResource(vm::OP_BIT_INV, vt);
  IInsn *insn = new IInsn(bit_inv);
  insn->inputs_.push_back(src);
  insn->outputs_.push_back(dst);
  w->state_->insns_.push_back(insn);
}

void MethodSynth::EmitSignatureInsn(vm::Method *method) {
  // This insn doesn't belong to a state.
  IResource *pseudo = res_set_->PseudoCallResource();
  context_->method_signature_insn_ = new IInsn(pseudo);
  context_->method_signature_insn_->SetOperand("method_entry");

  auto *parse_tree = method->GetParseTree();
  fe::VarDeclSet *args = nullptr;
  if (parse_tree != nullptr) {
    args = parse_tree->GetArgs();
  }
  int num_args = 0;
  if (args != nullptr) {
    num_args = args->decls.size();
    for (size_t i = 0; i < args->decls.size(); ++i) {
      IRegister *ireg = FindArgRegister(method, i, args->decls[i]);
      context_->method_signature_insn_->inputs_.push_back(ireg);
    }
  }
  fe::VarDeclSet *rets = nullptr;
  if (parse_tree != nullptr) {
    rets = parse_tree->GetReturns();
  }
  if (rets != nullptr) {
    for (size_t i = 0; i < rets->decls.size(); ++i) {
      vm::Register *vreg = method->method_regs_[i + num_args];
      IRegister *ireg = FindLocalVarRegister(vreg);
      context_->method_signature_insn_->outputs_.push_back(ireg);
    }
    // Adds a dummy return value.
    if (rets->decls.size() == 0) {
      IRegister *ireg = thr_synth_->AllocRegister("r");
      context_->method_signature_insn_->outputs_.push_back(ireg);
    }
  }
}

void MethodSynth::InsnToCalcValueType(vm::Insn *insn, IValueType *vt) {
  vm::Register *reg;
  if (vm::InsnType::IsComparison(insn->op_)) {
    reg = insn->src_regs_[0];
  } else {
    reg = insn->dst_regs_[0];
  }
  vt->SetIsSigned(false);
  if (reg->type_.value_type_ == vm::Value::NUM) {
    vt->SetWidth(reg->type_.num_width_.GetWidth());
  } else if (reg->type_.value_type_ == vm::Value::ENUM_ITEM) {
    vt->SetWidth(reg->type_.num_width_.GetWidth());
  }
}

void MethodSynth::MayAnnotateProfile(int pc, StateWrapper *prev_last) {
  vm::VM *vm = thr_synth_->GetObjectSynth()->GetVM();
  vm::Profile *profile = vm->GetProfile();
  if (!profile->HasInfo()) {
    return;
  }
  int c = profile->GetCount(method_, pc);
  if (c == 0) {
    return;
  }
  for (int i = prev_last->index_ + 1; i < context_->states_.size(); ++i) {
    IState *st = context_->states_[i]->state_;
    IProfile *profile = st->GetMutableProfile();
    profile->valid_ = true;
    profile->raw_count_ = c;
  }
}

}  // namespace synth

      sw->state_->insns_.push_back(a);
      (*args)[i] = lhs;
    }


#include "vm/executor/executor.h"

#include "vm/thread.h"

namespace vm {
namespace executor {

bool Executor::ExecInsn(Insn *insn) {
  insn_ = insn;
  bool need_suspend = false;
  switch (op()) {
    case OP_NUM:
      ExecNum();
      break;
    case OP_STR:
      ExecStr();
      break;
    case OP_TL_ADD_MAY_WITH_TYPE:
    case OP_TL_SUB_MAY_WITH_TYPE:
    case OP_TL_MUL_MAY_WITH_TYPE:
    case OP_TL_DIV_MAY_WITH_TYPE:
      if (MayExecuteCustomOp()) {
        need_suspend = true;
        if (!thr_->IsRunnable()) {
          return true;
        }
        break;
      }
      // fall through.
    case OP_ADD:
    case OP_SUB:
    case OP_MUL:
    case OP_DIV:
    case OP_ASSIGN:
    case OP_GT:
    case OP_LT:
    case OP_GTE:
    case OP_LTE:
    case OP_EQ:
    case OP_NE:
    case OP_LAND:
    case OP_LOR:
    case OP_AND:
    case OP_OR:
    case OP_XOR:
    case OP_CONCAT:
    case OP_LSHIFT:
    case OP_RSHIFT:
      ExecBinop();
      break;
    case OP_FUNCALL:
      need_suspend = ExecFuncall();
      if (!thr_->IsRunnable()) {
        return true;
      }
      break;
    case OP_FUNCALL_DONE:
      ExecFuncallDone();
      break;
    case OP_LOAD_OBJ:
      ExecLoadObj();
      break;

    }
    case OP_NOP:
      break;
    case OP_YIELD:
      need_suspend = ExecYield();
      break;
    case OP_PRE_INC:
    case OP_PRE_DEC:
      ExecIncDec();
      break;
    case OP_ARRAY_READ:
      ExecArrayRead();
      break;
    case OP_ARRAY_WRITE:
      ExecArrayWrite();
      break;
    case OP_LOGIC_INV:
      ExecLogicInv();
      break;
    case OP_BIT_INV:
    case OP_PLUS:
    case OP_MINUS:
      ExecNumUniop();
      break;
    case OP_MEMBER_READ:
    case OP_MEMBER_WRITE:
      ExecMemberAccess();
      break;
    case OP_BIT_RANGE:
      ExecBitRange();
      break;
    case OP_TL_FUNCDECL:
      ExecFuncdecl();
      break;
    case OP_TL_VARDECL:
      ExecVardecl();
      break;
    case OP_TL_CHANNEL_DECL:
      ExecChannelDecl();
      break;
    case OP_TL_MAILBOX_DECL:
      ExecMailboxDecl();
      break;
    case OP_TL_IMPORT:
      ExecImport();
      need_suspend = true;
      break;
    case OP_TL_MEMBER_READ_WITH_CHECK:
      ExecMemberReadWithCheck();
      break;
    case OP_TL_ARRAY_WRITE_WITH_CHECK:
      ExecArrayWriteWithCheck();
      break;
    case OP_TL_FUNCALL_WITH_CHECK:
      need_suspend = ExecFuncallWithCheck();
      if (!thr_->IsRunnable()) {
        return true;
      }
      break;
    case OP_TL_FUNCALL_DONE_WITH_CHECK:
      ExecFuncallDoneWithCheck();
      break;
    case OP_TL_SET_TYPE_OBJECT:
      ExecSetTypeObject();
      break;
    case OP_TL_PUSH_CURRENT_OBJECT:
      PushCurrentObject();
      break;
    case OP_TL_POP_CURRENT_OBJECT:
      PopCurrentObject();
      break;
    case OP_TL_MAY_WITH_TYPE_DONE:
      ExecMayWithTypeDone();
      break;
    default:
      CHECK(false) << "unknown insn:" << vm::OpCodeName(op());
  }
  ++frame_->pc_;
  return need_suspend;
}

}  // namespace executor
}  // namespace vm

    case OP_IF: {
      bool v = ExecIf();
      // do not increment pc.
      return v;
    }


#include "vm/profile.h"

#include <map>
#include <tuple>

using std::map;
using std::tuple;

namespace vm {

class ProfileData {
 public:
  ~ProfileData() {}

  map<tuple<Method *, int>, int> count_;
};

Profile::Profile() : enabled_(false), has_info_(false) {
  data_.reset(new ProfileData);
}

Profile::~Profile() {}

bool Profile::IsEnabled() const { return enabled_; }

void Profile::Clear() {
  data_->count_.clear();
  has_info_ = false;
}

void Profile::Mark(Method *method, int pc) {
  // For now this doesn't consider multiple inlined instances of a
  // same method. Probably I might change to store current trace or
  // whole the stack.
  auto k = std::make_tuple(method, pc);
  ++(data_->count_[k]);
  has_info_ = true;
}

int Profile::GetCount(Method *method, int pc) {
  auto k = std::make_tuple(method, pc);
  return data_->count_[k];
}

bool Profile::HasInfo() { return has_info_; }

}  // namespace vm

void Profile::SetEnable(bool enable) { enabled_ = enable; }


// Reference model of the behavior of FP16.
//

#include <iostream>
#include <math.h>

using namespace std;

int f2i(float f) {
  int *p = (int *)&f;
  return *p;
}

class FP16 {
public:
  FP16() {
    im_ = 0;
  }

  FP16(float f) {
    im_ = 0;
    int fi = f2i(f);
    if (f < 0) {
      SetSign(true);
    }
    int e = (fi >> 23) & 255;
    // 127 - 15
    SetExponent(e - 112);
    // 23bits -> 10
    int fr = fi & 0x7fffff;
    SetFraction(fr >> 13);
  }

  FP16(int s, int e, int f) {
    im_ = 0;
    SetSign(s);
    SetExponent(e);
    SetFraction(f);
  }

  int GetSign() {
    if (im_ & 0x8000) {
      return 1;
    } else {
      return 0;
    }
  }
  void SetSign(int s) {
    if (s) {
      im_ |= 0x8000;
    } else {
      im_ &= (~((int)0x8000));
    }
  }
  int GetExponent() {
    return ((im_ >> 10) & 0x1f);
  }
  void SetExponent(int e) {
    im_ &= (~((int)0x7c00));
    im_ |= (e << 10);
  }
  int GetFraction() {
    return (im_ & 0x3ff);
  }
  void SetFraction(int f) {
    im_ &= (~((int)0x3ff));
    im_ |= (f & 0x3ff);
  }
  void Dump() {
    // exp=0: 0, -0, -- subnormal
    // exp=1: 2^-14
    // exp=15: 2^0
    // exp=30: 2^15
    // exp=31: +-inf -- NaN
    cout << "s=" << GetSign() << ", "
	 << "e=" << GetExponent() << ", "
	 << "f=" << GetFraction() << " ";
    int e = GetExponent();
    double v = 0;
    cout << "value=";
    if (e == 0) {
      double f = GetFraction();
      if (f == 0) {
	if (GetSign()) {
	  cout << "-";
	}
	cout << "ZERO";
      } else {
	if (GetSign()) {
	  cout << "-";
	}
	f /= 1024.0f;
	f /= 16384.0f;
	cout << f;
      }
    } else if (e == 31) {
      int f = GetFraction();
      if (f == 0) {
	if (GetSign()) {
	  cout << "-";
	}
	cout << "inf";
      } else {
	cout << "NaN";
      }
    } else {
      cout << GetValue();
    }
    cout << "\n";
  }
  double GetValue() {
    double f = GetFraction();
    double v = 1 + (f / 1024.0f);
    int e = GetExponent();
    v = v * pow(2, e - 15);
    if (GetSign()) {
      v = -v;
    }
    return v;
  }
private:
  int im_;
};

FP16 MulP1(bool sign, int xe, int ye, int m) {
  FP16 z;
  int c = 0;
  if (m & 0x800) {
    c = 1;
  }
  int e = xe + ye - 15 + c;
  int f;
  if (c) {
    f = m >> 1;
  } else if (e <= 0 || e >= 31 || xe == 31 || ye == 31) {
    f = 0;
  } else {
    f = m;
  }
  if (e >= 31 || xe == 31 || ye == 31) {
    e = 31;
  }
  z.SetSign(sign);
  z.SetExponent(e);
  z.SetFraction(f);
  return z;
}

FP16 MulP0(FP16 x, FP16 y) {
  bool s = x.GetSign() ^ y.GetSign();
  int xe = x.GetExponent();
  int ye = y.GetExponent();
  int xf = x.GetFraction();
  int yf = y.GetFraction();
  if (xe > 0) {
    xf |= 0x400;
  }
  if (ye > 0) {
    yf |= 0x400;
  }
  int m = (xf * yf) >> 10;
  return MulP1(s, xe, ye, m);
}

FP16 Mul(FP16 x, FP16 y) {
  return MulP0(x, y);
}

FP16 AddSubP4(int r, bool xs, bool ys, int e, bool neg, bool xn, bool yn) {
  if (xn || yn) {
    if ((r & 0x1fe000) == 0) {
      e -= 8;
      r <<= 8;
    }
    if ((r & 0x1e0000) == 0) {
      e -= 4;
      r <<= 4;
    }
    if ((r & 0x180000) == 0) {
      e -= 2;
      r <<= 2;
    }
    if ((r & 0x100000) == 0) {
      e -= 1;
      r <<= 1;
    }
  }
  r >>= 10;
  r &= 0x3ff;
  bool s = false;
  if (xn != yn) {
    if (yn) {
      s = neg ^ xs;
    } else {
      s = neg ^ ys;
    }
  } else {
    s = xs;
  }
  if (e < 0) {
    e = 0;
    r = 0;
  }

  z.SetSign(s);
  z.SetExponent(e);
  z.SetFraction(r);
  return z;
}

FP16 AddSubP3(int r, bool xs, bool ys, int e, bool xn, bool yn) {
  bool neg = false;
  if (xn == yn) {
    if (r & 0x200000) {
      // Carry.
      e++;
      if (e > 31) {
	e = 31;
      }
      r >>= 1;
    }
  } else {
    if (r & 0x200000) {
      // positive.
      r &= 0x1fffff;
    } else {
      // negative.
      r = ~r;
      r = r + 1;
      r &= 0x1fffff;
      neg = true;
    }
  }
  return AddSubP4(r, xs, ys, e, neg, xn, yn);
}

FP16 AddSubP2(bool xs, bool ys, int xr, int yr, int xe, bool xn, bool yn) {
  int r = xr + yr;
  if (xn || yn) {
    r += 1;
  }
  return AddSubP3(r, xs, ys, xe, xn, yn);
}

FP16 AddSubP1(FP16 x, FP16 y, bool xn, bool yn) {
  // x.Exponent() >= y.Exponent()
  int d = x.GetExponent() - y.GetExponent();
  int xr = x.GetFraction();
    if (x.GetExponent() > 0) {
    xr |= 0x400;
  }
  xr <<= 10;
  if (xn) {
    xr = ~xr;
    xr &= 0x1fffff;
  }
  int yr = y.GetFraction();
  if (y.GetExponent() > 0) {
    yr |= 0x400;
  }
  if (d <= 10) {
    yr <<= (10 - d);
  } else {
    yr = 0;
  }
  if (yn) {
    yr = ~yr;
    yr &= 0x1fffff;
  }
  return AddSubP2(x.GetSign(), y.GetSign(), xr, yr, x.GetExponent(), xn, yn);
}

FP16 AddSubP0(FP16 x, FP16 y, bool is_sub) {
  bool neg_lhs = false;
  bool neg_rhs = false;
  if (is_sub) {
    y.SetSign(!y.GetSign());
  }
  FP16 lhs, rhs;
  if (x.GetExponent() < y.GetExponent()) {
    lhs = y;
    rhs = x;
  } else {
    lhs = x;
    rhs = y;
  }
  bool diff_sign = x.GetSign() ^ y.GetSign();
  if (diff_sign) {
    neg_lhs = lhs.GetSign();
    neg_rhs = rhs.GetSign();
  }
  return AddSubP1(lhs, rhs, neg_lhs, neg_rhs);
}

FP16 AddSub(FP16 x, FP16 y, bool is_sub) {
  return AddSubP0(x, y, is_sub);
}

bool Check(FP16 &n, bool s, int e, int f) {
  if (n.GetSign() == s && n.GetExponent() == e && n.GetFraction() == f) {
    return true;
  }
  FP16 en;
  en.SetSign(s);
  en.SetExponent(e);
  en.SetFraction(f);
  cout << "Failure! Expecting:";
  en.Dump();
  cout << "Got:";
  n.Dump();
  return false;
}

bool CheckFP(FP16 &n, float f) {
  FP16 m(f);
  return Check(n, m.GetSign(), m.GetExponent(), m.GetFraction());
}

void TestMul() {
  cout << "-- Testing multiplier --\n";
  FP16 z = Mul(FP16(1.5), FP16(1.5));
  if (!CheckFP(z, 2.25)) {
  }
  // 2^-14 * 0.5
  z = Mul(FP16(0, 1, 0), FP16(0.5));
  if (!CheckFP(z, FP16(0, 0, 0).GetValue())) {
  }
  // 2^9 * 2^9
  z = Mul(FP16(0, 24, 0), FP16(0, 24, 0));
  if (!CheckFP(z, FP16(0, 31, 0).GetValue())) {
  }
}

void TestAdd() {
  cout << "-- Testing adder --\n";
  FP16 fp;
  fp.SetExponent(15);
  fp.SetFraction(256);

  // 1.25 + 1.25
  FP16 z = AddSub(fp, fp, false);
  if (!Check(z, false, 16, 256)) {
  }
  z = AddSub(fp, z, false);
  // 2.5 + 1.25
  if (!Check(z, false, 16, 896)) {
  }

  // 1.25 + 0.03125.
  FP16 x;
  x.SetExponent(10);
  x.SetFraction(0);
  z = AddSub(fp, x, false);
  if (!Check(z, false, 15, 288)) {
  }

  // 1.25 + - 1.5
  z = AddSub(FP16(1.25), FP16(-1.5), false);
  if (!CheckFP(z, -0.25)) {
  }
  // 1.5 +  -1.25
  z = AddSub(FP16(1.5), FP16(-1.25), false);
  if (!CheckFP(z, 0.25)) {
  }

  // -1.5 + 1.25
  z = AddSub(FP16(-1.5), FP16(1.25), false);
  if (!CheckFP(z, -0.25)) {
  }
  // -1.25 + 1.5
  z = AddSub(FP16(-1.25), FP16(1.5), false);
  if (!CheckFP(z, 0.25)) {
  }
  // -1.25 + - 1.5
  z = AddSub(FP16(-1.25), FP16(-1.5), false);
  if (!CheckFP(z, -2.75)) {
  }
}

void TestSub() {
  cout << "-- Testing subtractor --\n";
  FP16 z = AddSub(FP16(1.25), FP16(0.0625), true);
  if (!CheckFP(z, 1.1875)) {
  }

  z = AddSub(FP16(1.25), FP16(2.0), true);
  if (!CheckFP(z, -0.75)) {
  }

  z = AddSub(FP16(-1.25), FP16(-2.0), true);
  if (!CheckFP(z, 0.75)) {
  }

  z = AddSub(FP16(-1.25), FP16(-0.5), true);
  if (!CheckFP(z, -0.75)) {
  }
}

void TestOne() {
  cout << "-- Testing specific issue --\n";
}

int main(int argc, char **argv) {
  TestMul();
  TestAdd();
  TestSub();

  TestOne();
  return 0;
}