/* This file is a part of MIR project.
   Copyright (C) 2018-2020 Vladimir Makarov <vmakarov.gcc@gmail.com>.
*/

#include "mir.h"

DEF_VARR (MIR_insn_t);
DEF_VARR (MIR_reg_t);
DEF_VARR (MIR_op_t);
DEF_VARR (MIR_type_t);
DEF_HTAB (MIR_item_t);
DEF_VARR (MIR_module_t);
DEF_VARR (size_t);
DEF_VARR (char);
DEF_VARR (uint8_t);
DEF_VARR (MIR_proto_t);

struct gen_ctx;
struct c2mir_ctx;
struct string_ctx;
struct reg_ctx;
struct simplify_ctx;
struct machine_code_ctx;
struct io_ctx;
struct scan_ctx;
struct interp_ctx;

struct MIR_context {
  struct gen_ctx *gen_ctx;     /* should be the 1st member */
  struct c2mir_ctx *c2mir_ctx; /* should be the 2nd member */
#if MIR_PARALLEL_GEN
  mir_mutex_t ctx_mutex;
#endif
  MIR_error_func_t error_func;
  VARR (size_t) * insn_nops;          /* constant after initialization */
  VARR (MIR_proto_t) * unspec_protos; /* protos of unspec insns (set only during initialization) */
  VARR (char) * temp_string;
  VARR (uint8_t) * temp_data;
  HTAB (MIR_item_t) * module_item_tab;
  /* Module to keep items potentially used by all modules:  */
  struct MIR_module environment_module;
  MIR_module_t curr_module;
  MIR_func_t curr_func;
  int curr_label_num;
  DLIST (MIR_module_t) all_modules;
  VARR (MIR_module_t) * modules_to_link;
  struct string_ctx *string_ctx;
  struct reg_ctx *reg_ctx;
  struct simplify_ctx *simplify_ctx;
  struct machine_code_ctx *machine_code_ctx;
  struct io_ctx *io_ctx;
  struct scan_ctx *scan_ctx;
  struct interp_ctx *interp_ctx;
};

#define ctx_mutex ctx->ctx_mutex
#define error_func ctx->error_func
#define unspec_protos ctx->unspec_protos
#define insn_nops ctx->insn_nops
#define temp_string ctx->temp_string
#define temp_data ctx->temp_data
#define module_item_tab ctx->module_item_tab
#define environment_module ctx->environment_module
#define curr_module ctx->curr_module
#define curr_func ctx->curr_func
#define curr_label_num ctx->curr_label_num
#define all_modules ctx->all_modules
#define modules_to_link ctx->modules_to_link

static void util_error (MIR_context_t ctx, const char *message);
#define MIR_VARR_ERROR util_error
#define MIR_HTAB_ERROR MIR_VARR_ERROR

#include "mir-hash.h"
#include "mir-htab.h"
#include "mir-reduce.h"
#include <string.h>
#include <stdarg.h>
#include <inttypes.h>
#include <float.h>
#include <ctype.h>
#include <limits.h>

static void interp_init (MIR_context_t ctx);
static void finish_func_interpretation (MIR_item_t func_item);
static void interp_finish (MIR_context_t ctx);

static void MIR_NO_RETURN default_error (enum MIR_error_type error_type, const char *format, ...) {
  va_list ap;

  va_start (ap, format);
  vfprintf (stderr, format, ap);
  fprintf (stderr, "\n");
  va_end (ap);
  exit (1);
}

static void MIR_NO_RETURN MIR_UNUSED util_error (MIR_context_t ctx, const char *message) {
  MIR_get_error_func (ctx) (MIR_alloc_error, message);
}

#define HARD_REG_NAME_PREFIX "hr"
#define TEMP_REG_NAME_PREFIX "t"
#define TEMP_ITEM_NAME_PREFIX ".lc"

int _MIR_reserved_ref_name_p (MIR_context_t ctx, const char *name) {
  return strncmp (name, TEMP_ITEM_NAME_PREFIX, strlen (TEMP_ITEM_NAME_PREFIX)) == 0;
}

/* Reserved names:
   fp - frame pointer
   hr<number> - a hardware reg
   lc<number> - a temp item */
int _MIR_reserved_name_p (MIR_context_t ctx, const char *name) {
  size_t i, start;

  if (_MIR_reserved_ref_name_p (ctx, name))
    return TRUE;
  else if (strncmp (name, HARD_REG_NAME_PREFIX, strlen (HARD_REG_NAME_PREFIX)) == 0)
    start = strlen (HARD_REG_NAME_PREFIX);
  else
    return FALSE;
  for (i = start; name[i] != '\0'; i++)
    if (name[i] < '0' || name[i] > '9') return FALSE;
  return TRUE;
}

struct insn_desc {
  MIR_insn_code_t code;
  const char *name;
  unsigned char op_modes[5];
};

#define OUTPUT_FLAG (1 << 7)

static const struct insn_desc insn_descs[] = {
  {MIR_MOV, "mov", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FMOV, "fmov", {MIR_OP_FLOAT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DMOV, "dmov", {MIR_OP_DOUBLE | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDMOV, "ldmov", {MIR_OP_LDOUBLE | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_EXT8, "ext8", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_EXT16, "ext16", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_EXT32, "ext32", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UEXT8, "uext8", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UEXT16, "uext16", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UEXT32, "uext32", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_I2F, "i2f", {MIR_OP_FLOAT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_I2D, "i2d", {MIR_OP_DOUBLE | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_I2LD, "i2ld", {MIR_OP_LDOUBLE | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UI2F, "ui2f", {MIR_OP_FLOAT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UI2D, "ui2d", {MIR_OP_DOUBLE | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UI2LD, "ui2ld", {MIR_OP_LDOUBLE | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_F2I, "f2i", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_D2I, "d2i", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LD2I, "ld2i", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_F2D, "f2d", {MIR_OP_DOUBLE | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_F2LD, "f2ld", {MIR_OP_LDOUBLE | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_D2F, "d2f", {MIR_OP_FLOAT | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_D2LD, "d2ld", {MIR_OP_LDOUBLE | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LD2F, "ld2f", {MIR_OP_FLOAT | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_LD2D, "ld2d", {MIR_OP_DOUBLE | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_NEG, "neg", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_NEGS, "negs", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FNEG, "fneg", {MIR_OP_FLOAT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DNEG, "dneg", {MIR_OP_DOUBLE | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDNEG, "ldneg", {MIR_OP_LDOUBLE | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_ADD, "add", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_ADDS, "adds", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FADD, "fadd", {MIR_OP_FLOAT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DADD, "dadd", {MIR_OP_DOUBLE | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDADD,
   "ldadd",
   {MIR_OP_LDOUBLE | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_SUB, "sub", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_SUBS, "subs", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FSUB, "fsub", {MIR_OP_FLOAT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DSUB, "dsub", {MIR_OP_DOUBLE | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDSUB,
   "ldsub",
   {MIR_OP_LDOUBLE | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_MUL, "mul", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_MULS, "muls", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FMUL, "fmul", {MIR_OP_FLOAT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DMUL, "dmul", {MIR_OP_DOUBLE | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDMUL,
   "ldmul",
   {MIR_OP_LDOUBLE | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_DIV, "div", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_DIVS, "divs", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UDIV, "udiv", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UDIVS, "udivs", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FDIV, "fdiv", {MIR_OP_FLOAT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DDIV, "ddiv", {MIR_OP_DOUBLE | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDDIV,
   "lddiv",
   {MIR_OP_LDOUBLE | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_MOD, "mod", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_MODS, "mods", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UMOD, "umod", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UMODS, "umods", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_AND, "and", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_ANDS, "ands", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_OR, "or", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_ORS, "ors", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_XOR, "xor", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_XORS, "xors", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_LSH, "lsh", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_LSHS, "lshs", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_RSH, "rsh", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_RSHS, "rshs", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_URSH, "ursh", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_URSHS, "urshs", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_EQ, "eq", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_EQS, "eqs", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FEQ, "feq", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DEQ, "deq", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDEQ, "ldeq", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_NE, "ne", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_NES, "nes", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FNE, "fne", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DNE, "dne", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDNE, "ldne", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_LT, "lt", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_LTS, "lts", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_ULT, "ult", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_ULTS, "ults", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FLT, "flt", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DLT, "dlt", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDLT, "ldlt", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_LE, "le", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_LES, "les", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_ULE, "ule", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_ULES, "ules", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FLE, "fle", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DLE, "dle", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDLE, "ldle", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_GT, "gt", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_GTS, "gts", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UGT, "ugt", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UGTS, "ugts", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FGT, "fgt", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DGT, "dgt", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDGT, "ldgt", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_GE, "ge", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_GES, "ges", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UGE, "uge", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UGES, "uges", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FGE, "fge", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DGE, "dge", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDGE, "ldge", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_JMP, "jmp", {MIR_OP_LABEL, MIR_OP_BOUND}},
  {MIR_BT, "bt", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_BTS, "bts", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_BF, "bf", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_BFS, "bfs", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_BEQ, "beq", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_BEQS, "beqs", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FBEQ, "fbeq", {MIR_OP_LABEL, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DBEQ, "dbeq", {MIR_OP_LABEL, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDBEQ, "ldbeq", {MIR_OP_LABEL, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_BNE, "bne", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_BNES, "bnes", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FBNE, "fbne", {MIR_OP_LABEL, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DBNE, "dbne", {MIR_OP_LABEL, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDBNE, "ldbne", {MIR_OP_LABEL, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_BLT, "blt", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_BLTS, "blts", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UBLT, "ublt", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UBLTS, "ublts", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FBLT, "fblt", {MIR_OP_LABEL, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DBLT, "dblt", {MIR_OP_LABEL, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDBLT, "ldblt", {MIR_OP_LABEL, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_BLE, "ble", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_BLES, "bles", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UBLE, "uble", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UBLES, "ubles", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FBLE, "fble", {MIR_OP_LABEL, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DBLE, "dble", {MIR_OP_LABEL, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDBLE, "ldble", {MIR_OP_LABEL, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_BGT, "bgt", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_BGTS, "bgts", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UBGT, "ubgt", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UBGTS, "ubgts", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FBGT, "fbgt", {MIR_OP_LABEL, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DBGT, "dbgt", {MIR_OP_LABEL, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDBGT, "ldbgt", {MIR_OP_LABEL, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_BGE, "bge", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_BGES, "bges", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UBGE, "ubge", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_UBGES, "ubges", {MIR_OP_LABEL, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_FBGE, "fbge", {MIR_OP_LABEL, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
  {MIR_DBGE, "dbge", {MIR_OP_LABEL, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
  {MIR_LDBGE, "ldbge", {MIR_OP_LABEL, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
  {MIR_CALL, "call", {MIR_OP_BOUND}},
  {MIR_INLINE, "inline", {MIR_OP_BOUND}},
  {MIR_SWITCH, "switch", {MIR_OP_BOUND}},
  {MIR_RET, "ret", {MIR_OP_BOUND}},
  {MIR_ALLOCA, "alloca", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_BSTART, "bstart", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_BOUND}},
  {MIR_BEND, "bend", {MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_VA_ARG, "va_arg", {MIR_OP_INT | OUTPUT_FLAG, MIR_OP_INT, MIR_OP_UNDEF, MIR_OP_BOUND}},
  {MIR_VA_BLOCK_ARG,
   "va_block_arg",
   {MIR_OP_INT, MIR_OP_INT, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_VA_START, "va_start", {MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_VA_END, "va_end", {MIR_OP_INT, MIR_OP_BOUND}},
  {MIR_LABEL, "label", {MIR_OP_BOUND}},
  {MIR_UNSPEC, "unspec", {MIR_OP_BOUND}},
  {MIR_PHI, "phi", {MIR_OP_BOUND}},
  {MIR_INVALID_INSN, "invalid-insn", {MIR_OP_BOUND}},
};

static void check_and_prepare_insn_descs (MIR_context_t ctx) {
  size_t i, j;

  VARR_CREATE (size_t, insn_nops, 0);
  for (i = 0; i < MIR_INSN_BOUND; i++) {
    mir_assert (insn_descs[i].code == i);
    for (j = 0; insn_descs[i].op_modes[j] != MIR_OP_BOUND; j++)
      ;
    VARR_PUSH (size_t, insn_nops, j);
  }
}

static MIR_op_mode_t type2mode (MIR_type_t type) {
  return (type == MIR_T_UNDEF
            ? MIR_OP_UNDEF
            : type == MIR_T_F
                ? MIR_OP_FLOAT
                : type == MIR_T_D ? MIR_OP_DOUBLE : type == MIR_T_LD ? MIR_OP_LDOUBLE : MIR_OP_INT);
}

/* New Page */

typedef struct string {
  size_t num; /* string number starting with 1 */
  MIR_str_t str;
} string_t;

DEF_VARR (string_t);
DEF_HTAB (string_t);

struct string_ctx {
  VARR (string_t) * strings;
  HTAB (string_t) * string_tab;
};

#define strings ctx->string_ctx->strings
#define string_tab ctx->string_ctx->string_tab

static htab_hash_t str_hash (string_t str, void *arg) {
  return mir_hash (str.str.s, str.str.len, 0);
}
static int str_eq (string_t str1, string_t str2, void *arg) {
  return str1.str.len == str2.str.len && memcmp (str1.str.s, str2.str.s, str1.str.len) == 0;
}

static void string_init (VARR (string_t) * *strs, HTAB (string_t) * *str_tab) {
  string_t string = {0, {0, NULL}};

  VARR_CREATE (string_t, *strs, 0);
  VARR_PUSH (string_t, *strs, string); /* don't use 0th string */
  HTAB_CREATE (string_t, *str_tab, 1000, str_hash, str_eq, NULL);
}

static int string_find (VARR (string_t) * *strs, HTAB (string_t) * *str_tab, MIR_str_t str,
                        string_t *s) {
  string_t string;

  string.str = str;
  return HTAB_DO (string_t, *str_tab, string, HTAB_FIND, *s);
}

static string_t string_store (MIR_context_t ctx, VARR (string_t) * *strs,
                              HTAB (string_t) * *str_tab, MIR_str_t str) {
  char *heap_str;
  string_t el, string;

  if (string_find (strs, str_tab, str, &el)) return el;
  if ((heap_str = malloc (str.len)) == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for strings");
  memcpy (heap_str, str.s, str.len);
  string.str.s = heap_str;
  string.str.len = str.len;
  string.num = VARR_LENGTH (string_t, *strs);
  VARR_PUSH (string_t, *strs, string);
  HTAB_DO (string_t, *str_tab, string, HTAB_INSERT, el);
  return string;
}

static struct string get_ctx_string (MIR_context_t ctx, MIR_str_t str) {
  return string_store (ctx, &strings, &string_tab, str);
}

static const char *get_ctx_str (MIR_context_t ctx, const char *string) {
  return get_ctx_string (ctx, (MIR_str_t){strlen (string) + 1, string}).str.s;
}

static void string_finish (VARR (string_t) * *strs, HTAB (string_t) * *str_tab) {
  size_t i;

  for (i = 1; i < VARR_LENGTH (string_t, *strs); i++)
    free ((char *) VARR_ADDR (string_t, *strs)[i].str.s);
  VARR_DESTROY (string_t, *strs);
  HTAB_DESTROY (string_t, *str_tab);
}

/* New Page */

typedef struct reg_desc {
  char *name; /* 1st key for the name2rdn hash tab */
  MIR_type_t type;
  MIR_reg_t reg; /* 1st key reg2rdn hash tab */
} reg_desc_t;

DEF_VARR (reg_desc_t);

DEF_HTAB (size_t);

typedef struct func_regs {
  VARR (reg_desc_t) * reg_descs;
  HTAB (size_t) * name2rdn_tab;
  HTAB (size_t) * reg2rdn_tab;
} * func_regs_t;

static int name2rdn_eq (size_t rdn1, size_t rdn2, void *arg) {
  func_regs_t func_regs = arg;
  reg_desc_t *addr = VARR_ADDR (reg_desc_t, func_regs->reg_descs);

  return strcmp (addr[rdn1].name, addr[rdn2].name) == 0;
}

static htab_hash_t name2rdn_hash (size_t rdn, void *arg) {
  func_regs_t func_regs = arg;
  reg_desc_t *addr = VARR_ADDR (reg_desc_t, func_regs->reg_descs);

  return mir_hash (addr[rdn].name, strlen (addr[rdn].name), 0);
}

static int reg2rdn_eq (size_t rdn1, size_t rdn2, void *arg) {
  func_regs_t func_regs = arg;
  reg_desc_t *addr = VARR_ADDR (reg_desc_t, func_regs->reg_descs);

  return addr[rdn1].reg == addr[rdn2].reg;
}

static htab_hash_t reg2rdn_hash (size_t rdn, void *arg) {
  func_regs_t func_regs = arg;
  reg_desc_t *addr = VARR_ADDR (reg_desc_t, func_regs->reg_descs);

  return mir_hash_finish (mir_hash_step (mir_hash_init (0), addr[rdn].reg));
}

static void func_regs_init (MIR_context_t ctx, MIR_func_t func) {
  func_regs_t func_regs;
  reg_desc_t rd = {NULL, MIR_T_I64, 0};

  if ((func_regs = func->internal = malloc (sizeof (struct func_regs))) == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for func regs info");
  VARR_CREATE (reg_desc_t, func_regs->reg_descs, 50);
  VARR_PUSH (reg_desc_t, func_regs->reg_descs, rd); /* for 0 reg */
  HTAB_CREATE (size_t, func_regs->name2rdn_tab, 100, name2rdn_hash, name2rdn_eq, func_regs);
  HTAB_CREATE (size_t, func_regs->reg2rdn_tab, 100, reg2rdn_hash, reg2rdn_eq, func_regs);
}

static MIR_reg_t create_func_reg (MIR_context_t ctx, MIR_func_t func, const char *name,
                                  MIR_reg_t reg, MIR_type_t type, int any_p, char **name_ptr) {
  func_regs_t func_regs = func->internal;
  reg_desc_t rd;
  size_t rdn, tab_rdn;
  int htab_res;

  if (!any_p && _MIR_reserved_name_p (ctx, name))
    MIR_get_error_func (ctx) (MIR_reserved_name_error, "redefining a reserved name %s", name);
  rd.name = (char *) name;
  rd.type = type;
  rd.reg = reg; /* 0 is reserved */
  rdn = VARR_LENGTH (reg_desc_t, func_regs->reg_descs);
  VARR_PUSH (reg_desc_t, func_regs->reg_descs, rd);
  if (HTAB_DO (size_t, func_regs->name2rdn_tab, rdn, HTAB_FIND, tab_rdn)) {
    VARR_POP (reg_desc_t, func_regs->reg_descs);
    MIR_get_error_func (ctx) (MIR_repeated_decl_error, "Repeated reg declaration %s", name);
  }
  if ((rd.name = malloc (strlen (name) + 1)) == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for reg names");
  VARR_ADDR (reg_desc_t, func_regs->reg_descs)[rdn].name = *name_ptr = rd.name;
  strcpy (*name_ptr, name);
  htab_res = HTAB_DO (size_t, func_regs->name2rdn_tab, rdn, HTAB_INSERT, tab_rdn);
  mir_assert (!htab_res);
  htab_res = HTAB_DO (size_t, func_regs->reg2rdn_tab, rdn, HTAB_INSERT, tab_rdn);
  mir_assert (!htab_res);
  return reg;
}

static void func_regs_finish (MIR_context_t ctx, MIR_func_t func) {
  func_regs_t func_regs = func->internal;
  char *name;

  for (size_t i = 0; i < VARR_LENGTH (reg_desc_t, func_regs->reg_descs); i++)
    if ((name = VARR_GET (reg_desc_t, func_regs->reg_descs, i).name) != NULL) free (name);
  VARR_DESTROY (reg_desc_t, func_regs->reg_descs);
  HTAB_DESTROY (size_t, func_regs->name2rdn_tab);
  HTAB_DESTROY (size_t, func_regs->reg2rdn_tab);
  free (func->internal);
  func->internal = NULL;
}

/* New Page */

static void push_data (MIR_context_t ctx, uint8_t *els, size_t size) {
  for (size_t i = 0; i < size; i++) VARR_PUSH (uint8_t, temp_data, els[i]);
}

const char *MIR_item_name (MIR_context_t ctx, MIR_item_t item) {
  mir_assert (item != NULL);
  switch (item->item_type) {
  case MIR_func_item: return item->u.func->name;
  case MIR_proto_item: return item->u.proto->name;
  case MIR_import_item: return item->u.import_id;
  case MIR_export_item: return item->u.export_id;
  case MIR_forward_item: return item->u.forward_id;
  case MIR_bss_item: return item->u.bss->name;
  case MIR_data_item: return item->u.data->name;
  case MIR_ref_data_item: return item->u.ref_data->name;
  case MIR_expr_data_item: return item->u.expr_data->name;
  default: mir_assert (FALSE); return NULL;
  }
}

#if !MIR_NO_IO
static void io_init (MIR_context_t ctx);
static void io_finish (MIR_context_t ctx);
#endif

#if !MIR_NO_SCAN
static void scan_init (MIR_context_t ctx);
static void scan_finish (MIR_context_t ctx);
#endif

static void simplify_init (MIR_context_t ctx);
static void simplify_finish (MIR_context_t ctx);

MIR_error_func_t MIR_get_error_func (MIR_context_t ctx) { return error_func; }  // ??? atomic

void MIR_set_error_func (MIR_context_t ctx, MIR_error_func_t func) {  // ?? atomic access
  error_func = func;
}

static htab_hash_t item_hash (MIR_item_t it, void *arg) {
  return mir_hash_finish (
    mir_hash_step (mir_hash_step (mir_hash_init (28), (uint64_t) MIR_item_name (NULL, it)),
                   (uint64_t) it->module));
}
static int item_eq (MIR_item_t it1, MIR_item_t it2, void *arg) {
  return it1->module == it2->module && MIR_item_name (NULL, it1) == MIR_item_name (NULL, it2);
}

static MIR_item_t item_tab_find (MIR_context_t ctx, const char *name, MIR_module_t module) {
  MIR_item_t tab_item;
  struct MIR_item item_s;
  struct MIR_func func_s;

  item_s.item_type = MIR_func_item;
  func_s.name = name;
  item_s.module = module;
  item_s.u.func = &func_s;
  if (HTAB_DO (MIR_item_t, module_item_tab, &item_s, HTAB_FIND, tab_item)) return tab_item;
  return NULL;
}

static MIR_item_t item_tab_insert (MIR_context_t ctx, MIR_item_t item) {
  MIR_item_t tab_item;

  HTAB_DO (MIR_item_t, module_item_tab, item, HTAB_INSERT, tab_item);
  return tab_item;
}

static void item_tab_remove (MIR_context_t ctx, MIR_item_t item) {
  HTAB_DO (MIR_item_t, module_item_tab, item, HTAB_DELETE, item);
}

static void init_module (MIR_context_t ctx, MIR_module_t m, const char *name) {
  m->data = NULL;
  m->last_temp_item_num = 0;
  m->name = get_ctx_str (ctx, name);
  DLIST_INIT (MIR_item_t, m->items);
}

static void code_init (MIR_context_t ctx);
static void code_finish (MIR_context_t ctx);

static void parallel_error (MIR_context_t ctx, const char *err_message) {
  MIR_get_error_func (ctx) (MIR_parallel_error, err_message);
}

MIR_context_t MIR_init (void) {
  MIR_context_t ctx;

  mir_assert (MIR_OP_BOUND < OUTPUT_FLAG);
  if ((ctx = malloc (sizeof (struct MIR_context))) == NULL)
    default_error (MIR_alloc_error, "Not enough memory for ctx");
  ctx->string_ctx = NULL;
  ctx->reg_ctx = NULL;
  ctx->simplify_ctx = NULL;
  ctx->machine_code_ctx = NULL;
  ctx->io_ctx = NULL;
  ctx->scan_ctx = NULL;
  ctx->interp_ctx = NULL;
  if (mir_mutex_init (&ctx_mutex, NULL)) parallel_error (ctx, "error in mutex init");
#ifndef NDEBUG
  for (MIR_insn_code_t c = 0; c < MIR_INVALID_INSN; c++) mir_assert (c == insn_descs[c].code);
#endif
  error_func = default_error;
  curr_module = NULL;
  curr_func = NULL;
  curr_label_num = 0;
  if ((ctx->string_ctx = malloc (sizeof (struct string_ctx))) == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for ctx");
  string_init (&strings, &string_tab);
  VARR_CREATE (MIR_proto_t, unspec_protos, 0);
  check_and_prepare_insn_descs (ctx);
  DLIST_INIT (MIR_module_t, all_modules);
  simplify_init (ctx);
  VARR_CREATE (char, temp_string, 64);
  VARR_CREATE (uint8_t, temp_data, 512);
#if !MIR_NO_IO
  io_init (ctx);
#endif
#if !MIR_NO_SCAN
  scan_init (ctx);
#endif
  VARR_CREATE (MIR_module_t, modules_to_link, 0);
  init_module (ctx, &environment_module, ".environment");
  HTAB_CREATE (MIR_item_t, module_item_tab, 512, item_hash, item_eq, NULL);
  code_init (ctx);
  interp_init (ctx);
  return ctx;
}

void MIR_remove_insn (MIR_context_t ctx, MIR_item_t func_item, MIR_insn_t insn) {
  mir_assert (func_item != NULL);
  if (func_item->item_type != MIR_func_item)
    MIR_get_error_func (ctx) (MIR_wrong_param_value_error, "MIR_remove_insn: wrong func item");
  DLIST_REMOVE (MIR_insn_t, func_item->u.func->insns, insn);
  free (insn);
}

static void remove_func_insns (MIR_context_t ctx, MIR_item_t func_item,
                               DLIST (MIR_insn_t) * insns) {
  MIR_insn_t insn;

  mir_assert (func_item->item_type == MIR_func_item);
  while ((insn = DLIST_HEAD (MIR_insn_t, *insns)) != NULL) {
    MIR_remove_insn (ctx, func_item, insn);
  }
}

static void remove_item (MIR_context_t ctx, MIR_item_t item) {
  switch (item->item_type) {
  case MIR_func_item:
    remove_func_insns (ctx, item, &item->u.func->insns);
    remove_func_insns (ctx, item, &item->u.func->original_insns);
    VARR_DESTROY (MIR_var_t, item->u.func->vars);
    func_regs_finish (ctx, item->u.func);
    free (item->u.func);
    break;
  case MIR_proto_item:
    VARR_DESTROY (MIR_var_t, item->u.proto->args);
    free (item->u.proto);
    break;
  case MIR_import_item:
  case MIR_export_item:
  case MIR_forward_item: break;
  case MIR_data_item:
    if (item->addr != NULL && item->section_head_p) free (item->addr);
    free (item->u.data);
    break;
  case MIR_ref_data_item:
    if (item->addr != NULL && item->section_head_p) free (item->addr);
    free (item->u.ref_data);
    break;
  case MIR_expr_data_item:
    if (item->addr != NULL && item->section_head_p) free (item->addr);
    free (item->u.expr_data);
    break;
  case MIR_bss_item:
    if (item->addr != NULL && item->section_head_p) free (item->addr);
    free (item->u.bss);
    break;
  default: mir_assert (FALSE);
  }
  if (item->data != NULL) free (item->data);
  free (item);
}

static void remove_module (MIR_context_t ctx, MIR_module_t module, int free_module_p) {
  MIR_item_t item;

  while ((item = DLIST_HEAD (MIR_item_t, module->items)) != NULL) {
    DLIST_REMOVE (MIR_item_t, module->items, item);
    remove_item (ctx, item);
  }
  if (module->data != NULL) free (module->data);
  if (free_module_p) free (module);
}

static void remove_all_modules (MIR_context_t ctx) {
  MIR_module_t module;

  while ((module = DLIST_HEAD (MIR_module_t, all_modules)) != NULL) {
    DLIST_REMOVE (MIR_module_t, all_modules, module);
    remove_module (ctx, module, TRUE);
  }
  remove_module (ctx, &environment_module, FALSE);
}

void MIR_finish (MIR_context_t ctx) {
  interp_finish (ctx);
  remove_all_modules (ctx);
  HTAB_DESTROY (MIR_item_t, module_item_tab);
  VARR_DESTROY (MIR_module_t, modules_to_link);
#if !MIR_NO_SCAN
  scan_finish (ctx);
#endif
#if !MIR_NO_IO
  io_finish (ctx);
#endif
  VARR_DESTROY (uint8_t, temp_data);
  VARR_DESTROY (char, temp_string);
  while (VARR_LENGTH (MIR_proto_t, unspec_protos) != 0) {
    MIR_proto_t proto = VARR_POP (MIR_proto_t, unspec_protos);
    VARR_DESTROY (MIR_var_t, proto->args);
    free (proto);
  }
  VARR_DESTROY (MIR_proto_t, unspec_protos);
  string_finish (&strings, &string_tab);
  simplify_finish (ctx);
  VARR_DESTROY (size_t, insn_nops);
  code_finish (ctx);
  if (curr_func != NULL)
    MIR_get_error_func (ctx) (MIR_finish_error, "finish when function %s is not finished",
                              curr_func->name);
  if (curr_module != NULL)
    MIR_get_error_func (ctx) (MIR_finish_error, "finish when module %s is not finished",
                              curr_module->name);
  free (ctx->string_ctx);
  if (mir_mutex_destroy (&ctx_mutex)) parallel_error (ctx, "error in mutex destroy");
  free (ctx);
  ctx = NULL;
}

MIR_module_t MIR_new_module (MIR_context_t ctx, const char *name) {
  if (curr_module != NULL)
    MIR_get_error_func (ctx) (MIR_nested_module_error,
                              "Creating module when previous module %s is not finished",
                              curr_module->name);
  if ((curr_module = malloc (sizeof (struct MIR_module))) == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for module %s creation", name);
  init_module (ctx, curr_module, name);
  DLIST_APPEND (MIR_module_t, all_modules, curr_module);
  return curr_module;
}

DLIST (MIR_module_t) * MIR_get_module_list (MIR_context_t ctx) { return &all_modules; }

static const char *type_str (MIR_type_t tp) {
  switch (tp) {
  case MIR_T_I8: return "i8";
  case MIR_T_U8: return "u8";
  case MIR_T_I16: return "i16";
  case MIR_T_U16: return "u16";
  case MIR_T_I32: return "i32";
  case MIR_T_U32: return "u32";
  case MIR_T_I64: return "i64";
  case MIR_T_U64: return "u64";
  case MIR_T_F: return "f";
  case MIR_T_D: return "d";
  case MIR_T_LD: return "ld";
  case MIR_T_P: return "p";
  case MIR_T_BLK: return "blk";
  case MIR_T_BLK2: return "blk2";
  case MIR_T_BLK3: return "blk3";
  case MIR_T_BLK4: return "blk4";
  case MIR_T_BLK5: return "blk5";
  case MIR_T_RBLK: return "rblk";
  case MIR_T_UNDEF: return "undef";
  default: return "";
  }
}

const char *MIR_type_str (MIR_context_t ctx, MIR_type_t tp) {
  const char *str = type_str (tp);

  if (strcmp (str, "") == 0)
    MIR_get_error_func (ctx) (MIR_wrong_param_value_error, "MIR_type_str: wrong type");
  return str;
}

static const char *mode_str (MIR_op_mode_t mode) {
  switch (mode) {
  case MIR_OP_REG: return "reg";
  case MIR_OP_HARD_REG: return "hard_reg";
  case MIR_OP_INT: return "int";
  case MIR_OP_UINT: return "uint";
  case MIR_OP_FLOAT: return "float";
  case MIR_OP_DOUBLE: return "double";
  case MIR_OP_LDOUBLE: return "ldouble";
  case MIR_OP_REF: return "ref";
  case MIR_OP_STR: return "str";
  case MIR_OP_MEM: return "mem";
  case MIR_OP_HARD_REG_MEM: return "hard_reg_mem";
  case MIR_OP_LABEL: return "label";
  case MIR_OP_BOUND: return "bound";
  case MIR_OP_UNDEF: return "undef";
  default: return "";
  }
}

static MIR_item_t add_item (MIR_context_t ctx, MIR_item_t item) {
  int replace_p;
  MIR_item_t tab_item;

  if ((tab_item = item_tab_find (ctx, MIR_item_name (ctx, item), item->module)) == NULL) {
    DLIST_APPEND (MIR_item_t, curr_module->items, item);
    HTAB_DO (MIR_item_t, module_item_tab, item, HTAB_INSERT, item);
    return item;
  }
  switch (tab_item->item_type) {
  case MIR_import_item:
    if (item->item_type != MIR_import_item)
      MIR_get_error_func (ctx) (MIR_import_export_error,
                                "existing module definition %s already defined as import",
                                tab_item->u.import_id);
    item = tab_item;
    break;
  case MIR_export_item:
  case MIR_forward_item:
    replace_p = FALSE;
    if (item->item_type == MIR_import_item) {
      MIR_get_error_func (ctx) (MIR_import_export_error, "export/forward of import %s",
                                item->u.import_id);
    } else if (item->item_type != MIR_export_item && item->item_type != MIR_forward_item) {
      replace_p = TRUE;
      DLIST_APPEND (MIR_item_t, curr_module->items, item);
    } else {
      if (tab_item->item_type == item->item_type)
        item = tab_item;
      else
        DLIST_APPEND (MIR_item_t, curr_module->items, item);
      if (item->item_type == MIR_export_item && tab_item->item_type == MIR_forward_item)
        replace_p = TRUE;
    }
    if (replace_p) { /* replace forward by export or export/forward by its definition: */
      tab_item->ref_def = item;
      if (tab_item->item_type == MIR_export_item) item->export_p = TRUE;
      item_tab_remove (ctx, tab_item);
      tab_item = item_tab_insert (ctx, item);
      mir_assert (item == tab_item);
    }
    break;
  case MIR_proto_item:
    MIR_get_error_func (ctx) (MIR_repeated_decl_error, "item %s was already defined as proto",
                              tab_item->u.proto->name);
    break;
  case MIR_bss_item:
  case MIR_data_item:
  case MIR_ref_data_item:
  case MIR_expr_data_item:
  case MIR_func_item:
    if (item->item_type == MIR_export_item) {
      if (tab_item->export_p) {
        item = tab_item;
      } else { /* just keep one export: */
        tab_item->export_p = TRUE;
        DLIST_APPEND (MIR_item_t, curr_module->items, item);
        item->ref_def = tab_item;
      }
    } else if (item->item_type == MIR_forward_item) {
      DLIST_APPEND (MIR_item_t, curr_module->items, item);
      item->ref_def = tab_item;
    } else if (item->item_type == MIR_import_item) {
      MIR_get_error_func (ctx) (MIR_import_export_error, "import of local definition %s",
                                item->u.import_id);
    } else {
      MIR_get_error_func (ctx) (MIR_repeated_decl_error, "Repeated item declaration %s",
                                MIR_item_name (ctx, item));
    }
    break;
  default: mir_assert (FALSE);
  }
  return item;
}

static MIR_item_t create_item (MIR_context_t ctx, MIR_item_type_t item_type,
                               const char *item_name) {
  MIR_item_t item;

  if (curr_module == NULL)
    MIR_get_error_func (ctx) (MIR_no_module_error, "%s outside module", item_name);
  if ((item = malloc (sizeof (struct MIR_item))) == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for creation of item %s",
                              item_name);
  item->data = NULL;
  item->module = curr_module;
  item->item_type = item_type;
  item->ref_def = NULL;
  item->export_p = FALSE;
  item->section_head_p = FALSE;
  item->addr = NULL;
  return item;
}

static MIR_item_t new_export_import_forward (MIR_context_t ctx, const char *name,
                                             MIR_item_type_t item_type, const char *item_name,
                                             int create_only_p) {
  MIR_item_t item, tab_item;
  const char *uniq_name;

  item = create_item (ctx, item_type, item_name);
  uniq_name = get_ctx_str (ctx, name);
  if (item_type == MIR_export_item)
    item->u.export_id = uniq_name;
  else if (item_type == MIR_import_item)
    item->u.import_id = uniq_name;
  else
    item->u.forward_id = uniq_name;
  if (create_only_p) return item;
  if ((tab_item = add_item (ctx, item)) != item) {
    free (item);
    item = tab_item;
  }
  return item;
}

MIR_item_t MIR_new_export (MIR_context_t ctx, const char *name) {
  return new_export_import_forward (ctx, name, MIR_export_item, "export", FALSE);
}

MIR_item_t MIR_new_import (MIR_context_t ctx, const char *name) {
  return new_export_import_forward (ctx, name, MIR_import_item, "import", FALSE);
}

MIR_item_t MIR_new_forward (MIR_context_t ctx, const char *name) {
  return new_export_import_forward (ctx, name, MIR_forward_item, "forward", FALSE);
}

MIR_item_t MIR_new_bss (MIR_context_t ctx, const char *name, size_t len) {
  MIR_item_t tab_item, item = create_item (ctx, MIR_bss_item, "bss");

  item->u.bss = malloc (sizeof (struct MIR_bss));
  if (item->u.bss == NULL) {
    free (item);
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for creation of bss %s", name);
  }
  if (name != NULL) name = get_ctx_str (ctx, name);
  item->u.bss->name = name;
  item->u.bss->len = len;
  if (name == NULL) {
    DLIST_APPEND (MIR_item_t, curr_module->items, item);
  } else if ((tab_item = add_item (ctx, item)) != item) {
    free (item);
    item = tab_item;
  }
  return item;
}

static MIR_type_t canon_type (MIR_type_t type) {
#if defined(_WIN32) || __SIZEOF_LONG_DOUBLE__ == 8
  if (type == MIR_T_LD) type = MIR_T_D;
#endif
  return type;
}

size_t _MIR_type_size (MIR_context_t ctx, MIR_type_t type) {
  switch (type) {
  case MIR_T_I8: return sizeof (int8_t);
  case MIR_T_U8: return sizeof (uint8_t);
  case MIR_T_I16: return sizeof (int16_t);
  case MIR_T_U16: return sizeof (uint16_t);
  case MIR_T_I32: return sizeof (int32_t);
  case MIR_T_U32: return sizeof (uint32_t);
  case MIR_T_I64: return sizeof (int64_t);
  case MIR_T_U64: return sizeof (uint64_t);
  case MIR_T_F: return sizeof (float);
  case MIR_T_D: return sizeof (double);
  case MIR_T_LD: return sizeof (long double);
  case MIR_T_P: return sizeof (void *);
  default: mir_assert (FALSE); return 1;
  }
}

static int wrong_type_p (MIR_type_t type) { return type < MIR_T_I8 || type >= MIR_T_BLK; }

MIR_item_t MIR_new_data (MIR_context_t ctx, const char *name, MIR_type_t el_type, size_t nel,
                         const void *els) {
  MIR_item_t tab_item, item = create_item (ctx, MIR_data_item, "data");
  MIR_data_t data;
  size_t el_len;

  if (wrong_type_p (el_type)) {
    free (item);
    MIR_get_error_func (ctx) (MIR_wrong_type_error, "wrong type in data %s", name);
  }
  el_len = _MIR_type_size (ctx, el_type);
  item->u.data = data = malloc (sizeof (struct MIR_data) + el_len * nel);
  if (data == NULL) {
    free (item);
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for creation of data %s",
                              name == NULL ? "" : name);
  }
  if (name != NULL) name = get_ctx_str (ctx, name);
  data->name = name;
  if (name == NULL) {
    DLIST_APPEND (MIR_item_t, curr_module->items, item);
  } else if ((tab_item = add_item (ctx, item)) != item) {
    free (item);
    item = tab_item;
  }
  data->el_type = canon_type (el_type);
  data->nel = nel;
  memcpy (data->u.els, els, el_len * nel);
  return item;
}

MIR_item_t MIR_new_string_data (MIR_context_t ctx, const char *name, MIR_str_t str) {
  return MIR_new_data (ctx, name, MIR_T_U8, str.len, str.s);
}

MIR_item_t MIR_new_ref_data (MIR_context_t ctx, const char *name, MIR_item_t ref_item,
                             int64_t disp) {
  MIR_item_t tab_item, item = create_item (ctx, MIR_ref_data_item, "ref data");
  MIR_ref_data_t ref_data;

  item->u.ref_data = ref_data = malloc (sizeof (struct MIR_ref_data));
  if (ref_data == NULL) {
    free (item);
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for creation of ref data %s",
                              name == NULL ? "" : name);
  }
  if (name != NULL) name = get_ctx_str (ctx, name);
  ref_data->name = name;
  ref_data->ref_item = ref_item;
  ref_data->disp = disp;
  if (name == NULL) {
    DLIST_APPEND (MIR_item_t, curr_module->items, item);
  } else if ((tab_item = add_item (ctx, item)) != item) {
    free (item);
    item = tab_item;
  }
  return item;
}

MIR_item_t MIR_new_expr_data (MIR_context_t ctx, const char *name, MIR_item_t expr_item) {
  MIR_item_t tab_item, item = create_item (ctx, MIR_expr_data_item, "expr data");
  MIR_expr_data_t expr_data;

  item->u.expr_data = expr_data = malloc (sizeof (struct MIR_expr_data));
  if (expr_data == NULL) {
    free (item);
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for creation of expr data %s",
                              name == NULL ? "" : name);
  }
  mir_assert (expr_item != NULL);
  if (expr_item->item_type != MIR_func_item || expr_item->u.func->vararg_p
      || expr_item->u.func->nargs != 0 || expr_item->u.func->nres != 1)
    MIR_get_error_func (
      ctx) (MIR_binary_io_error,
            "%s can not be an expr which should be non-argument, one result function",
            MIR_item_name (ctx, expr_item));
  if (name != NULL) name = get_ctx_str (ctx, name);
  expr_data->name = name;
  expr_data->expr_item = expr_item;
  if (name == NULL) {
    DLIST_APPEND (MIR_item_t, curr_module->items, item);
  } else if ((tab_item = add_item (ctx, item)) != item) {
    free (item);
    item = tab_item;
  }
  return item;
}

static MIR_proto_t create_proto (MIR_context_t ctx, const char *name, size_t nres,
                                 MIR_type_t *res_types, size_t nargs, int vararg_p,
                                 MIR_var_t *args) {
  MIR_proto_t proto = malloc (sizeof (struct MIR_proto) + nres * sizeof (MIR_type_t));

  if (proto == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for creation of proto %s", name);
  proto->name = get_ctx_str (ctx, name);
  proto->res_types = (MIR_type_t *) ((char *) proto + sizeof (struct MIR_proto));
  if (nres != 0) memcpy (proto->res_types, res_types, nres * sizeof (MIR_type_t));
  proto->nres = nres;
  proto->vararg_p = vararg_p != 0;
  VARR_CREATE (MIR_var_t, proto->args, nargs);
  for (size_t i = 0; i < nargs; i++) VARR_PUSH (MIR_var_t, proto->args, args[i]);
  return proto;
}

static MIR_item_t new_proto_arr (MIR_context_t ctx, const char *name, size_t nres,
                                 MIR_type_t *res_types, size_t nargs, int vararg_p,
                                 MIR_var_t *args) {
  MIR_item_t proto_item, tab_item;

  if (curr_module == NULL)
    MIR_get_error_func (ctx) (MIR_no_module_error, "Creating proto %s outside module", name);
  for (size_t i = 0; i < nres; i++)
    if (wrong_type_p (res_types[i]))
      MIR_get_error_func (ctx) (MIR_wrong_type_error, "wrong result type in proto %s", name);
  proto_item = create_item (ctx, MIR_proto_item, "proto");
  proto_item->u.proto = create_proto (ctx, name, nres, res_types, nargs, vararg_p, args);
  tab_item = add_item (ctx, proto_item);
  mir_assert (tab_item == proto_item);
  return proto_item;
}

MIR_item_t MIR_new_proto_arr (MIR_context_t ctx, const char *name, size_t nres,
                              MIR_type_t *res_types, size_t nargs, MIR_var_t *args) {
  return new_proto_arr (ctx, name, nres, res_types, nargs, FALSE, args);
}

MIR_item_t MIR_new_vararg_proto_arr (MIR_context_t ctx, const char *name, size_t nres,
                                     MIR_type_t *res_types, size_t nargs, MIR_var_t *args) {
  return new_proto_arr (ctx, name, nres, res_types, nargs, TRUE, args);
}

#if defined(_MSC_VER)
#define alloca _alloca
#endif

static MIR_item_t new_proto (MIR_context_t ctx, const char *name, size_t nres,
                             MIR_type_t *res_types, size_t nargs, int vararg_p, va_list argp) {
  MIR_var_t *args = alloca (nargs * sizeof (MIR_var_t));
  size_t i;

  for (i = 0; i < nargs; i++) {
    args[i].type = va_arg (argp, MIR_type_t);
    args[i].name = va_arg (argp, const char *);
  }
  return new_proto_arr (ctx, name, nres, res_types, nargs, vararg_p, args);
}

MIR_item_t MIR_new_proto (MIR_context_t ctx, const char *name, size_t nres, MIR_type_t *res_types,
                          size_t nargs, ...) {
  va_list argp;
  MIR_item_t proto_item;

  va_start (argp, nargs);
  proto_item = new_proto (ctx, name, nres, res_types, nargs, FALSE, argp);
  va_end (argp);
  return proto_item;
}

MIR_item_t MIR_new_vararg_proto (MIR_context_t ctx, const char *name, size_t nres,
                                 MIR_type_t *res_types, size_t nargs, ...) {
  va_list argp;
  MIR_item_t proto_item;

  va_start (argp, nargs);
  proto_item = new_proto (ctx, name, nres, res_types, nargs, TRUE, argp);
  va_end (argp);
  return proto_item;
}

static MIR_item_t new_func_arr (MIR_context_t ctx, const char *name, size_t nres,
                                MIR_type_t *res_types, size_t nargs, int vararg_p,
                                MIR_var_t *vars) {
  MIR_item_t func_item, tab_item;
  MIR_func_t func;

  if (curr_func != NULL)
    MIR_get_error_func (ctx) (MIR_nested_func_error,
                              "Creating function when previous function %s is not finished",
                              curr_func->name);
  if (nargs == 0 && vararg_p)
    MIR_get_error_func (ctx) (MIR_vararg_func_error,
                              "Variable arg function %s w/o any mandatory argument", name);
  for (size_t i = 0; i < nres; i++)
    if (wrong_type_p (res_types[i]))
      MIR_get_error_func (ctx) (MIR_wrong_type_error, "wrong result type in func %s", name);
  func_item = create_item (ctx, MIR_func_item, "function");
  curr_func = func_item->u.func = func
    = malloc (sizeof (struct MIR_func) + nres * sizeof (MIR_type_t));
  if (func == NULL) {
    free (func_item);
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for creation of func %s", name);
  }
  func->name = get_ctx_str (ctx, name);
  func->nres = nres;
  func->res_types = (MIR_type_t *) ((char *) func + sizeof (struct MIR_func));
  for (size_t i = 0; i < nres; i++) func->res_types[i] = canon_type (res_types[i]);
  tab_item = add_item (ctx, func_item);
  mir_assert (tab_item == func_item);
  DLIST_INIT (MIR_insn_t, func->insns);
  DLIST_INIT (MIR_insn_t, func->original_insns);
  VARR_CREATE (MIR_var_t, func->vars, nargs + 8);
  func->nargs = nargs;
  func->last_temp_num = 0;
  func->vararg_p = vararg_p != 0;
  func->expr_p = FALSE;
  func->n_inlines = 0;
  func->machine_code = func->call_addr = NULL;
  func_regs_init (ctx, func);
  for (size_t i = 0; i < nargs; i++) {
    char *stored_name;
    MIR_type_t type = canon_type (vars[i].type);

    create_func_reg (ctx, func, vars[i].name, i + 1,
                     type == MIR_T_F || type == MIR_T_D || type == MIR_T_LD ? type : MIR_T_I64,
                     FALSE, &stored_name);
    vars[i].name = stored_name;
    VARR_PUSH (MIR_var_t, func->vars, vars[i]);
  }
  return func_item;
}

MIR_item_t MIR_new_func_arr (MIR_context_t ctx, const char *name, size_t nres,
                             MIR_type_t *res_types, size_t nargs, MIR_var_t *vars) {
  return new_func_arr (ctx, name, nres, res_types, nargs, FALSE, vars);
}

MIR_item_t MIR_new_vararg_func_arr (MIR_context_t ctx, const char *name, size_t nres,
                                    MIR_type_t *res_types, size_t nargs, MIR_var_t *vars) {
  return new_func_arr (ctx, name, nres, res_types, nargs, TRUE, vars);
}

static MIR_item_t new_func (MIR_context_t ctx, const char *name, size_t nres, MIR_type_t *res_types,
                            size_t nargs, int vararg_p, va_list argp) {
  size_t i;
  MIR_var_t *vars = alloca (sizeof (MIR_var_t) * nargs);

  for (i = 0; i < nargs; i++) {
    vars[i].type = va_arg (argp, MIR_type_t);
    vars[i].name = va_arg (argp, const char *);
  }
  return new_func_arr (ctx, name, nres, res_types, nargs, vararg_p, vars);
}

MIR_item_t MIR_new_func (MIR_context_t ctx, const char *name, size_t nres, MIR_type_t *res_types,
                         size_t nargs, ...) {
  va_list argp;
  MIR_item_t func_item;

  va_start (argp, nargs);
  func_item = new_func (ctx, name, nres, res_types, nargs, FALSE, argp);
  va_end (argp);
  return func_item;
}

MIR_item_t MIR_new_vararg_func (MIR_context_t ctx, const char *name, size_t nres,
                                MIR_type_t *res_types, size_t nargs, ...) {
  va_list argp;
  MIR_item_t func_item;

  va_start (argp, nargs);
  func_item = new_func (ctx, name, nres, res_types, nargs, TRUE, argp);
  va_end (argp);
  return func_item;
}

MIR_reg_t MIR_new_func_reg (MIR_context_t ctx, MIR_func_t func, MIR_type_t type, const char *name) {
  MIR_var_t var;
  MIR_reg_t res;
  char *stored_name;

  if (type != MIR_T_I64 && type != MIR_T_F && type != MIR_T_D && type != MIR_T_LD)
    MIR_get_error_func (ctx) (MIR_reg_type_error, "wrong type for register %s: got '%s'", name,
                              type_str (type));
  mir_assert (func != NULL);
  res = create_func_reg (ctx, func, name, VARR_LENGTH (MIR_var_t, func->vars) + 1, type, FALSE,
                         &stored_name);
  var.type = type;
  var.name = stored_name;
  VARR_PUSH (MIR_var_t, func->vars, var);
  return res;
}

static reg_desc_t *find_rd_by_name (MIR_context_t ctx, const char *name, MIR_func_t func) {
  func_regs_t func_regs = func->internal;
  size_t rdn, temp_rdn;
  reg_desc_t rd;

  rd.name = (char *) name;
  rd.type = MIR_T_I64;
  rd.reg = 0; /* to eliminate warnings */
  temp_rdn = VARR_LENGTH (reg_desc_t, func_regs->reg_descs);
  VARR_PUSH (reg_desc_t, func_regs->reg_descs, rd);
  if (!HTAB_DO (size_t, func_regs->name2rdn_tab, temp_rdn, HTAB_FIND, rdn)) {
    VARR_POP (reg_desc_t, func_regs->reg_descs);
    return NULL; /* undeclared */
  }
  VARR_POP (reg_desc_t, func_regs->reg_descs);
  return &VARR_ADDR (reg_desc_t, func_regs->reg_descs)[rdn];
}

static reg_desc_t *find_rd_by_reg (MIR_context_t ctx, MIR_reg_t reg, MIR_func_t func) {
  func_regs_t func_regs = func->internal;
  size_t rdn, temp_rdn;
  reg_desc_t rd;

  rd.reg = reg;
  rd.name = NULL;
  rd.type = MIR_T_I64; /* to eliminate warnings */
  temp_rdn = VARR_LENGTH (reg_desc_t, func_regs->reg_descs);
  VARR_PUSH (reg_desc_t, func_regs->reg_descs, rd);
  if (!HTAB_DO (size_t, func_regs->reg2rdn_tab, temp_rdn, HTAB_FIND, rdn)) {
    VARR_POP (reg_desc_t, func_regs->reg_descs);
    MIR_get_error_func (ctx) (MIR_undeclared_func_reg_error, "undeclared reg %u of func %s", reg,
                              func->name);
  }
  VARR_POP (reg_desc_t, func_regs->reg_descs);
  return &VARR_ADDR (reg_desc_t, func_regs->reg_descs)[rdn];
}

void MIR_finish_func (MIR_context_t ctx) {
  int expr_p = TRUE;
  MIR_insn_t insn;
  MIR_insn_code_t code;
  const char *func_name;

  if (curr_func == NULL)
    MIR_get_error_func (ctx) (MIR_no_func_error, "finish of non-existing function");
  func_name = curr_func->name;
  if (curr_func->vararg_p || curr_func->nargs != 0 || curr_func->nres != 1) expr_p = FALSE;
  for (insn = DLIST_HEAD (MIR_insn_t, curr_func->insns); insn != NULL;
       insn = DLIST_NEXT (MIR_insn_t, insn)) {
    size_t i, actual_nops = MIR_insn_nops (ctx, insn);
    MIR_op_mode_t mode, expected_mode;
    reg_desc_t *rd;
    int out_p, can_be_out_p;

    code = insn->code;
    if (code == MIR_PHI) {
      curr_func = NULL;
      MIR_get_error_func (ctx) (MIR_vararg_func_error, "phi can be used only internally");
    } else if (!curr_func->vararg_p && code == MIR_VA_START) {
      curr_func = NULL;
      MIR_get_error_func (ctx) (MIR_vararg_func_error, "va_start is not in vararg function");
    } else if (code == MIR_RET && actual_nops != curr_func->nres) {
      int nres = curr_func->nres;

      curr_func = NULL;
      MIR_get_error_func (
        ctx) (MIR_vararg_func_error,
              "func %s: in instruction '%s': number of operands in return does not "
              "correspond number of function returns. Expected %d, got %d",
              func_name, insn_descs[code].name, nres, actual_nops);
    } else if (MIR_call_code_p (code))
      expr_p = FALSE;
    for (i = 0; i < actual_nops; i++) {
      if (code == MIR_UNSPEC && i == 0) {
        mir_assert (insn->ops[i].mode == MIR_OP_INT);
        continue;
      } else if (MIR_call_code_p (code)) {
        if (i == 0) {
          mir_assert (insn->ops[i].mode == MIR_OP_REF
                      && insn->ops[i].u.ref->item_type == MIR_proto_item);
          continue; /* We checked the operand during insn creation -- skip the prototype */
        } else if (i == 1 && insn->ops[i].mode == MIR_OP_REF) {
          mir_assert (insn->ops[i].u.ref->item_type == MIR_import_item
                      || insn->ops[i].u.ref->item_type == MIR_export_item
                      || insn->ops[i].u.ref->item_type == MIR_forward_item
                      || insn->ops[i].u.ref->item_type == MIR_func_item);
          continue; /* We checked the operand during insn creation -- skip the func */
        }
      }
      if (code == MIR_VA_ARG && i == 2) {
        mir_assert (insn->ops[i].mode == MIR_OP_MEM);
        continue; /* We checked the operand during insn creation -- skip va_arg type  */
      }
      if (code == MIR_SWITCH) {
        out_p = FALSE;
        expected_mode = i == 0 ? MIR_OP_INT : MIR_OP_LABEL;
      } else if (code != MIR_RET) {
        expected_mode = MIR_insn_op_mode (ctx, insn, i, &out_p);
      } else {
        out_p = FALSE;
        expected_mode = type2mode (curr_func->res_types[i]);
      }
      can_be_out_p = TRUE;
      switch (insn->ops[i].mode) {
      case MIR_OP_REG:
        rd = find_rd_by_reg (ctx, insn->ops[i].u.reg, curr_func);
        mir_assert (rd != NULL && insn->ops[i].u.reg == rd->reg);
        mode = type2mode (rd->type);
        break;
      case MIR_OP_MEM:
        expr_p = FALSE;
        if (wrong_type_p (insn->ops[i].u.mem.type)
            && (!MIR_all_blk_type_p (insn->ops[i].u.mem.type) || !MIR_call_code_p (code))) {
          curr_func = NULL;
          MIR_get_error_func (ctx) (MIR_wrong_type_error,
                                    "func %s: in instruction '%s': wrong type memory", func_name,
                                    insn_descs[code].name);
        }
        if (MIR_all_blk_type_p (insn->ops[i].u.mem.type) && insn->ops[i].u.mem.disp < 0) {
          curr_func = NULL;
          MIR_get_error_func (ctx) (MIR_wrong_type_error,
                                    "func %s: in instruction '%s': block type memory with disp < 0",
                                    func_name, insn_descs[code].name);
        }
        if (insn->ops[i].u.mem.base != 0) {
          rd = find_rd_by_reg (ctx, insn->ops[i].u.mem.base, curr_func);
          mir_assert (rd != NULL && insn->ops[i].u.mem.base == rd->reg);
          if (type2mode (rd->type) != MIR_OP_INT) {
            curr_func = NULL;
            MIR_get_error_func (
              ctx) (MIR_reg_type_error,
                    "func %s: in instruction '%s': base reg of non-integer type for operand "
                    "#%d",
                    func_name, insn_descs[code].name, i + 1);
          }
        }
        if (insn->ops[i].u.mem.index != 0) {
          rd = find_rd_by_reg (ctx, insn->ops[i].u.mem.index, curr_func);
          mir_assert (rd != NULL && insn->ops[i].u.mem.index == rd->reg);
          if (type2mode (rd->type) != MIR_OP_INT) {
            curr_func = NULL;
            MIR_get_error_func (
              ctx) (MIR_reg_type_error,
                    "func %s: in instruction '%s': index reg of non-integer type for "
                    "operand #%d",
                    func_name, insn_descs[code].name, i + 1);
          }
        }
        mode = type2mode (insn->ops[i].u.mem.type);
        break;
      case MIR_OP_HARD_REG:
      case MIR_OP_HARD_REG_MEM:
        expr_p = FALSE;
        mode = expected_mode;
        mir_assert (FALSE); /* Should not be here */
        break;
      default:
        can_be_out_p = FALSE;
        mode = insn->ops[i].mode;
        if (mode == MIR_OP_REF || mode == MIR_OP_STR) mode = MIR_OP_INT; /* just an address */
        break;
      }
      insn->ops[i].value_mode = mode;
      if (mode == MIR_OP_UNDEF && insn->ops[i].mode == MIR_OP_MEM
          && ((code == MIR_VA_START && i == 0)
              || ((code == MIR_VA_ARG || code == MIR_VA_BLOCK_ARG) && i == 1)
              || (code == MIR_VA_END && i == 1))) { /* a special case: va_list as undef type mem */
        insn->ops[i].value_mode = expected_mode;
      } else if (expected_mode != MIR_OP_UNDEF
                 && (mode == MIR_OP_UINT ? MIR_OP_INT : mode) != expected_mode) {
        curr_func = NULL;
        MIR_get_error_func (
          ctx) (MIR_op_mode_error,
                "func %s: in instruction '%s': unexpected operand mode for operand #%d. Got "
                "'%s', expected '%s'",
                func_name, insn_descs[code].name, i + 1, mode_str (mode), mode_str (expected_mode));
      }
      if (out_p && !can_be_out_p) {
        curr_func = NULL;
        MIR_get_error_func (ctx) (MIR_out_op_error,
                                  "func %s; in instruction '%s': wrong operand #%d for insn output",
                                  func_name, insn_descs[code].name, i + 1);
      }
    }
  }
  curr_func->expr_p = expr_p;
  curr_func = NULL;
}

void MIR_finish_module (MIR_context_t ctx) {
  if (curr_module == NULL)
    MIR_get_error_func (ctx) (MIR_no_module_error, "finish of non-existing module");
  curr_module = NULL;
}

static void setup_global (MIR_context_t ctx, const char *name, void *addr, MIR_item_t def) {
  MIR_item_t item, tab_item;
  MIR_module_t saved = curr_module;

  curr_module = &environment_module;
  /* Use import for proto representation: */
  item = new_export_import_forward (ctx, name, MIR_import_item, "import", TRUE);
  if ((tab_item = item_tab_find (ctx, MIR_item_name (ctx, item), &environment_module)) != item
      && tab_item != NULL) {
    free (item);
  } else {
    HTAB_DO (MIR_item_t, module_item_tab, item, HTAB_INSERT, tab_item);
    DLIST_APPEND (MIR_item_t, environment_module.items, item);
    tab_item = item;
  }
  tab_item->addr = addr;
  tab_item->ref_def = def;
  curr_module = saved;
}

static void undefined_interface (MIR_context_t ctx) {
  MIR_get_error_func (ctx) (MIR_call_op_error, "undefined call interface");
}

static MIR_item_t load_bss_data_section (MIR_context_t ctx, MIR_item_t item, int first_only_p) {
  const char *name;
  MIR_item_t curr_item, last_item, expr_item;
  size_t len, section_size = 0;
  uint8_t *addr;

  if (item->addr == NULL) {
    /* Calculate section size: */
    for (curr_item = item; curr_item != NULL && curr_item->addr == NULL;
         curr_item = first_only_p ? NULL : DLIST_NEXT (MIR_item_t, curr_item))
      if (curr_item->item_type == MIR_bss_item
          && (curr_item == item || curr_item->u.bss->name == NULL))
        section_size += curr_item->u.bss->len;
      else if (curr_item->item_type == MIR_data_item
               && (curr_item == item || curr_item->u.data->name == NULL))
        section_size += (curr_item->u.data->nel * _MIR_type_size (ctx, curr_item->u.data->el_type));
      else if (curr_item->item_type == MIR_ref_data_item
               && (curr_item == item || curr_item->u.ref_data->name == NULL))
        section_size += _MIR_type_size (ctx, MIR_T_P);
      else if (curr_item->item_type == MIR_expr_data_item
               && (curr_item == item || curr_item->u.expr_data->name == NULL)) {
        expr_item = curr_item->u.expr_data->expr_item;
        if (expr_item->item_type != MIR_func_item || !expr_item->u.func->expr_p
            || expr_item->u.func->nres != 1)
          MIR_get_error_func (
            ctx) (MIR_binary_io_error,
                  "%s can not be an expr which should be a func w/o calls and memory ops",
                  MIR_item_name (ctx, expr_item));
        section_size += _MIR_type_size (ctx, expr_item->u.func->res_types[0]);
      } else
        break;
    if ((item->addr = malloc (section_size)) == NULL) {
      name = MIR_item_name (ctx, item);
      MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory to allocate data/bss %s",
                                name == NULL ? "" : name);
    }
    item->section_head_p = TRUE;
  }
  /* Set up section memory: */
  for (last_item = item, curr_item = item, addr = item->addr;
       curr_item != NULL && (curr_item == item || curr_item->addr == NULL);
       last_item = curr_item, curr_item = first_only_p ? NULL : DLIST_NEXT (MIR_item_t, curr_item))
    if (curr_item->item_type == MIR_bss_item
        && (curr_item == item || curr_item->u.bss->name == NULL)) {
      memset (addr, 0, curr_item->u.bss->len);
      curr_item->addr = addr;
      addr += curr_item->u.bss->len;
    } else if (curr_item->item_type == MIR_data_item
               && (curr_item == item || curr_item->u.data->name == NULL)) {
      len = curr_item->u.data->nel * _MIR_type_size (ctx, curr_item->u.data->el_type);
      memmove (addr, curr_item->u.data->u.els, len);
      curr_item->addr = addr;
      addr += len;
    } else if (curr_item->item_type == MIR_ref_data_item
               && (curr_item == item || curr_item->u.ref_data->name == NULL)) {
      curr_item->u.ref_data->load_addr = addr;
      curr_item->addr = addr;
      addr += _MIR_type_size (ctx, MIR_T_P);
    } else if (curr_item->item_type == MIR_expr_data_item
               && (curr_item == item || curr_item->u.expr_data->name == NULL)) {
      expr_item = curr_item->u.expr_data->expr_item;
      len = _MIR_type_size (ctx, expr_item->u.func->res_types[0]);
      curr_item->u.expr_data->load_addr = addr;
      curr_item->addr = addr;
      addr += len;
    } else {
      break;
    }
  return last_item;
}

void MIR_load_module (MIR_context_t ctx, MIR_module_t m) {
  mir_assert (m != NULL);
  for (MIR_item_t item = DLIST_HEAD (MIR_item_t, m->items); item != NULL;
       item = DLIST_NEXT (MIR_item_t, item)) {
    MIR_item_t first_item = item;

    if (item->item_type == MIR_bss_item || item->item_type == MIR_data_item
        || item->item_type == MIR_ref_data_item || item->item_type == MIR_expr_data_item) {
      item = load_bss_data_section (ctx, item, FALSE);
    } else if (item->item_type == MIR_func_item) {
      if (item->addr == NULL) {
        item->addr = _MIR_get_thunk (ctx);
#ifdef MIR_DEBUG
        fprintf (stderr, "%016lx: %s\n", (size_t) item->addr, item->u.func->name);
#endif
      }
      _MIR_redirect_thunk (ctx, item->addr, undefined_interface);
    }
    if (first_item->export_p) { /* update global item table */
      mir_assert (first_item->item_type != MIR_export_item
                  && first_item->item_type != MIR_import_item
                  && first_item->item_type != MIR_forward_item);
      setup_global (ctx, MIR_item_name (ctx, first_item), first_item->addr, first_item);
    }
  }
  VARR_PUSH (MIR_module_t, modules_to_link, m);
}

void MIR_load_external (MIR_context_t ctx, const char *name, void *addr) {
  setup_global (ctx, name, addr, NULL);
}

static int simplify_func (MIR_context_t ctx, MIR_item_t func_item, int mem_float_p);
static void process_inlines (MIR_context_t ctx, MIR_item_t func_item);

void MIR_link (MIR_context_t ctx, void (*set_interface) (MIR_context_t ctx, MIR_item_t item),
               void *import_resolver (const char *)) {
  MIR_item_t item, tab_item, expr_item;
  MIR_type_t type;
  MIR_val_t res;
  MIR_module_t m;
  void *addr;
  union {
    int8_t i8;
    int16_t i16;
    int32_t i32;
    int64_t i64;
    float f;
    double d;
    long double ld;
    void *a;
  } v;

  for (size_t i = 0; i < VARR_LENGTH (MIR_module_t, modules_to_link); i++) {
    m = VARR_GET (MIR_module_t, modules_to_link, i);
    for (item = DLIST_HEAD (MIR_item_t, m->items); item != NULL;
         item = DLIST_NEXT (MIR_item_t, item))
      if (item->item_type == MIR_func_item) {
        assert (item->data == NULL);
        if (simplify_func (ctx, item, TRUE)) item->data = (void *) 1; /* flag inlining */
      } else if (item->item_type == MIR_import_item) {
        if ((tab_item = item_tab_find (ctx, item->u.import_id, &environment_module)) == NULL) {
          if (import_resolver == NULL || (addr = import_resolver (item->u.import_id)) == NULL)
            MIR_get_error_func (ctx) (MIR_undeclared_op_ref_error, "import of undefined item %s",
                                      item->u.import_id);
          MIR_load_external (ctx, item->u.import_id, addr);
          tab_item = item_tab_find (ctx, item->u.import_id, &environment_module);
          mir_assert (tab_item != NULL);
        }
        item->addr = tab_item->addr;
        item->ref_def = tab_item;
      } else if (item->item_type == MIR_export_item) {
        if ((tab_item = item_tab_find (ctx, item->u.export_id, m)) == NULL)
          MIR_get_error_func (ctx) (MIR_undeclared_op_ref_error, "export of undefined item %s",
                                    item->u.export_id);
        item->addr = tab_item->addr;
        item->ref_def = tab_item;
      } else if (item->item_type == MIR_forward_item) {
        if ((tab_item = item_tab_find (ctx, item->u.forward_id, m)) == NULL)
          MIR_get_error_func (ctx) (MIR_undeclared_op_ref_error, "forward of undefined item %s",
                                    item->u.forward_id);
        item->addr = tab_item->addr;
        item->ref_def = tab_item;
      }
  }
  for (size_t i = 0; i < VARR_LENGTH (MIR_module_t, modules_to_link); i++) {
    m = VARR_GET (MIR_module_t, modules_to_link, i);
    for (item = DLIST_HEAD (MIR_item_t, m->items); item != NULL;
         item = DLIST_NEXT (MIR_item_t, item)) {
      if (item->item_type == MIR_func_item && item->data != NULL) {
        process_inlines (ctx, item);
        item->data = NULL;
#if 0
        fprintf (stderr, "+++++ Function after inlining:\n");
        MIR_output_item (ctx, stderr, item);
#endif
      } else if (item->item_type == MIR_ref_data_item) {
        assert (item->u.ref_data->ref_item->addr != NULL);
        addr = (char *) item->u.ref_data->ref_item->addr + item->u.ref_data->disp;
        memcpy (item->u.ref_data->load_addr, &addr, _MIR_type_size (ctx, MIR_T_P));
        continue;
      }
      if (item->item_type != MIR_expr_data_item) continue;
      expr_item = item->u.expr_data->expr_item;
      MIR_interp (ctx, expr_item, &res, 0);
      type = expr_item->u.func->res_types[0];
      switch (type) {
      case MIR_T_I8:
      case MIR_T_U8: v.i8 = (int8_t) res.i; break;
      case MIR_T_I16:
      case MIR_T_U16: v.i16 = (int16_t) res.i; break;
      case MIR_T_I32:
      case MIR_T_U32: v.i32 = (int32_t) res.i; break;
      case MIR_T_I64:
      case MIR_T_U64: v.i64 = (int64_t) res.i; break;
      case MIR_T_F: v.f = res.f; break;
      case MIR_T_D: v.d = res.d; break;
      case MIR_T_LD: v.ld = res.ld; break;
      case MIR_T_P: v.a = res.a; break;
      default: assert (FALSE); break;
      }
      memcpy (item->u.expr_data->load_addr, &v,
              _MIR_type_size (ctx, expr_item->u.func->res_types[0]));
    }
  }
  if (set_interface != NULL) {
    while (VARR_LENGTH (MIR_module_t, modules_to_link) != 0) {
      m = VARR_POP (MIR_module_t, modules_to_link);
      for (item = DLIST_HEAD (MIR_item_t, m->items); item != NULL;
           item = DLIST_NEXT (MIR_item_t, item))
        if (item->item_type == MIR_func_item) {
          finish_func_interpretation (item); /* in case if it was used for expr data */
          set_interface (ctx, item);
        }
    }
    set_interface (ctx, NULL); /* finish interface setting */
  }
}

static const char *insn_name (MIR_insn_code_t code) {
  return (unsigned) code >= MIR_INSN_BOUND ? "" : insn_descs[code].name;
}

const char *MIR_insn_name (MIR_context_t ctx, MIR_insn_code_t code) {
  if ((unsigned) code >= MIR_INSN_BOUND)
    MIR_get_error_func (ctx) (MIR_wrong_param_value_error, "MIR_insn_name: wrong insn code %d",
                              (int) code);
  return insn_descs[code].name;
}

static size_t insn_code_nops (MIR_context_t ctx, MIR_insn_code_t code) { /* 0 for calls */
  if ((unsigned) code >= MIR_INSN_BOUND)
    MIR_get_error_func (ctx) (MIR_wrong_param_value_error, "insn_code_nops: wrong insn code %d",
                              (int) code);
  return VARR_GET (size_t, insn_nops, code);
}

size_t MIR_insn_nops (MIR_context_t ctx, MIR_insn_t insn) {
  mir_assert (insn != NULL);
  return insn->nops;
}

MIR_op_mode_t _MIR_insn_code_op_mode (MIR_context_t ctx, MIR_insn_code_t code, size_t nop,
                                      int *out_p) {
  unsigned mode;
  mir_assert (out_p != NULL);

  if (nop >= insn_code_nops (ctx, code)) return MIR_OP_BOUND;
  mode = insn_descs[code].op_modes[nop];
  mir_assert (out_p != NULL);
  *out_p = (mode & OUTPUT_FLAG) != 0;
  return *out_p ? mode ^ OUTPUT_FLAG : mode;
}

MIR_op_mode_t MIR_insn_op_mode (MIR_context_t ctx, MIR_insn_t insn, size_t nop, int *out_p) {
  MIR_insn_code_t code = insn->code;
  size_t nargs, nops = MIR_insn_nops (ctx, insn);
  unsigned mode;

  *out_p = FALSE; /* to remove unitialized warning */
  if (nop >= nops) return MIR_OP_BOUND;
  mir_assert (out_p != NULL);
  if (code == MIR_RET || code == MIR_SWITCH) {
    *out_p = FALSE;
    /* should be already checked in MIR_finish_func */
    return nop == 0 && code == MIR_SWITCH ? MIR_OP_INT : insn->ops[nop].mode;
  } else if (code == MIR_PHI) {
    *out_p = nop == 0;
    return insn->ops[nop].mode;
  } else if (MIR_call_code_p (code) || code == MIR_UNSPEC) {
    MIR_op_t proto_op;
    MIR_proto_t proto;
    size_t args_start;

    if (code == MIR_UNSPEC) {
      args_start = 1;
      mir_assert (insn->ops[0].mode == MIR_OP_INT);
      mir_assert (insn->ops[0].u.u < VARR_LENGTH (MIR_proto_t, unspec_protos));
      proto = VARR_GET (MIR_proto_t, unspec_protos, insn->ops[0].u.u);
    } else {
      args_start = 2;
      proto_op = insn->ops[0];
      mir_assert (proto_op.mode == MIR_OP_REF && proto_op.u.ref->item_type == MIR_proto_item);
      proto = proto_op.u.ref->u.proto;
    }
    *out_p = args_start <= nop && nop < proto->nres + args_start;
    nargs
      = proto->nres + args_start + (proto->args == NULL ? 0 : VARR_LENGTH (MIR_var_t, proto->args));
    if (proto->vararg_p && nop >= nargs) return MIR_OP_UNDEF; /* unknown */
    mir_assert (nops >= nargs && (proto->vararg_p || nops == nargs));
    if (nop == 0) return insn->ops[nop].mode;
    if (nop == 1 && code != MIR_UNSPEC) return MIR_OP_INT; /* call func addr */
    if (args_start <= nop && nop < proto->nres + args_start)
      return type2mode (proto->res_types[nop - args_start]);
    return type2mode (VARR_GET (MIR_var_t, proto->args, nop - args_start - proto->nres).type);
  }
  mode = insn_descs[code].op_modes[nop];
  *out_p = (mode & OUTPUT_FLAG) != 0;
  return *out_p ? mode ^ OUTPUT_FLAG : mode;
}

static MIR_insn_t create_insn (MIR_context_t ctx, size_t nops, MIR_insn_code_t code) {
  MIR_insn_t insn;

  if (nops == 0) nops = 1;
  insn = malloc (sizeof (struct MIR_insn) + sizeof (MIR_op_t) * (nops - 1));
  if (insn == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for insn creation");
#if defined(_WIN32) || __SIZEOF_LONG_DOUBLE__ == 8
  switch (code) {
  case MIR_LDMOV: code = MIR_DMOV; break;
  case MIR_I2LD: code = MIR_I2D; break;
  case MIR_UI2LD: code = MIR_UI2D; break;
  case MIR_LD2I: code = MIR_D2I; break;
  case MIR_F2LD: code = MIR_F2D; break;
  case MIR_D2LD: code = MIR_DMOV; break;
  case MIR_LD2F: code = MIR_D2F; break;
  case MIR_LD2D: code = MIR_DMOV; break;
  case MIR_LDNEG: code = MIR_DNEG; break;
  case MIR_LDADD: code = MIR_DADD; break;
  case MIR_LDSUB: code = MIR_DSUB; break;
  case MIR_LDMUL: code = MIR_DMUL; break;
  case MIR_LDDIV: code = MIR_DDIV; break;
  case MIR_LDEQ: code = MIR_DEQ; break;
  case MIR_LDNE: code = MIR_DNE; break;
  case MIR_LDLT: code = MIR_DLT; break;
  case MIR_LDLE: code = MIR_DLE; break;
  case MIR_LDGT: code = MIR_DGT; break;
  case MIR_LDGE: code = MIR_DGE; break;
  case MIR_LDBEQ: code = MIR_DBEQ; break;
  case MIR_LDBNE: code = MIR_DBNE; break;
  case MIR_LDBLT: code = MIR_DBLT; break;
  case MIR_LDBLE: code = MIR_DBLE; break;
  case MIR_LDBGT: code = MIR_DBGT; break;
  case MIR_LDBGE: code = MIR_DBGE; break;
  }
#endif
  insn->code = code;
  insn->data = NULL;
  return insn;
}

static MIR_insn_t new_insn1 (MIR_context_t ctx, MIR_insn_code_t code) {
  return create_insn (ctx, 1, code);
}

MIR_insn_t MIR_new_insn_arr (MIR_context_t ctx, MIR_insn_code_t code, size_t nops, MIR_op_t *ops) {
  MIR_insn_t insn;
  MIR_proto_t proto;
  size_t args_start, narg, i = 0, expected_nops = insn_code_nops (ctx, code);
  mir_assert (ops != NULL);

  if (!MIR_call_code_p (code) && code != MIR_UNSPEC && code != MIR_PHI && code != MIR_RET
      && code != MIR_SWITCH && nops != expected_nops) {
    MIR_get_error_func (ctx) (MIR_ops_num_error, "wrong number of operands for insn %s",
                              insn_descs[code].name);
  } else if (code == MIR_SWITCH) {
    if (nops < 2)
      MIR_get_error_func (ctx) (MIR_ops_num_error, "number of MIR_SWITCH operands is less 2");
  } else if (code == MIR_PHI) {
    if (nops < 3)
      MIR_get_error_func (ctx) (MIR_ops_num_error, "number of MIR_PHI operands is less 3");
  } else if (MIR_call_code_p (code) || code == MIR_UNSPEC) {
    args_start = code == MIR_UNSPEC ? 1 : 2;
    if (nops < args_start)
      MIR_get_error_func (ctx) (MIR_ops_num_error, "wrong number of call/unspec operands");
    if (code == MIR_UNSPEC) {
      if (ops[0].mode != MIR_OP_INT || ops[0].u.u >= VARR_LENGTH (MIR_proto_t, unspec_protos))
        MIR_get_error_func (ctx) (MIR_unspec_op_error,
                                  "the 1st unspec operand should be valid unspec code");
      proto = VARR_GET (MIR_proto_t, unspec_protos, ops[0].u.u);
    } else {
      if (ops[0].mode != MIR_OP_REF || ops[0].u.ref->item_type != MIR_proto_item)
        MIR_get_error_func (ctx) (MIR_call_op_error, "the 1st call operand should be a prototype");
      proto = ops[0].u.ref->u.proto;
    }
    i = proto->nres;
    if (proto->args != NULL) i += VARR_LENGTH (MIR_var_t, proto->args);
    if (nops < i + args_start || (nops != i + args_start && !proto->vararg_p))
      MIR_get_error_func (
        ctx) (code == MIR_UNSPEC ? MIR_unspec_op_error : MIR_call_op_error,
              "number of %s operands or results does not correspond to prototype %s",
              code == MIR_UNSPEC ? "unspec" : "call", proto->name);
    for (i = args_start; i < nops; i++) {
      if (ops[i].mode == MIR_OP_MEM && MIR_all_blk_type_p (ops[i].u.mem.type)) {
        if (i - args_start < proto->nres)
          MIR_get_error_func (ctx) (MIR_wrong_type_error, "result of %s is block type memory",
                                    code == MIR_UNSPEC ? "unspec" : "call");
        else if ((narg = i - args_start - proto->nres) < VARR_LENGTH (MIR_var_t, proto->args)) {
          if (VARR_GET (MIR_var_t, proto->args, narg).type != ops[i].u.mem.type) {
            MIR_get_error_func (
              ctx) (MIR_wrong_type_error,
                    "arg of %s is block type memory but param is not of block type",
                    code == MIR_UNSPEC ? "unspec" : "call");
          } else if (VARR_GET (MIR_var_t, proto->args, narg).size != ops[i].u.mem.disp) {
            MIR_get_error_func (
              ctx) (MIR_wrong_type_error,
                    "different sizes (%lu vs %lu) of arg and param block memory in %s insn",
                    (unsigned long) VARR_GET (MIR_var_t, proto->args, narg).size,
                    (unsigned long) ops[i].u.mem.disp, code == MIR_UNSPEC ? "unspec" : "call");
          }
        } else if (ops[i].u.mem.type == MIR_T_RBLK) {
          MIR_get_error_func (ctx) (MIR_wrong_type_error,
                                    "RBLK memory can not correspond to unnamed param in %s insn",
                                    code == MIR_UNSPEC ? "unspec" : "call");
        }
      } else if (i - args_start >= proto->nres
                 && (narg = i - args_start - proto->nres) < VARR_LENGTH (MIR_var_t, proto->args)
                 && MIR_all_blk_type_p (VARR_GET (MIR_var_t, proto->args, narg).type)) {
        MIR_get_error_func (
          ctx) (MIR_wrong_type_error,
                "param of %s is of block type but arg is not of block type memory",
                code == MIR_UNSPEC ? "unspec" : "call");
      }
    }
  } else if (code == MIR_VA_ARG) {  // undef mem ???
    if (ops[2].mode != MIR_OP_MEM)
      MIR_get_error_func (ctx) (MIR_op_mode_error,
                                "3rd operand of va_arg should be any memory with given type");
  }
  insn = create_insn (ctx, nops, code);
  insn->nops = nops;
  for (i = 0; i < nops; i++) insn->ops[i] = ops[i];
  return insn;
}

static MIR_insn_t new_insn (MIR_context_t ctx, MIR_insn_code_t code, size_t nops, va_list argp) {
  MIR_op_t *insn_ops = alloca (sizeof (MIR_op_t) * nops);

  for (size_t i = 0; i < nops; i++) insn_ops[i] = va_arg (argp, MIR_op_t);
  va_end (argp);
  return MIR_new_insn_arr (ctx, code, nops, insn_ops);
}

MIR_insn_t MIR_new_insn (MIR_context_t ctx, MIR_insn_code_t code, ...) {
  va_list argp;
  size_t nops = insn_code_nops (ctx, code);

  if (code == MIR_PHI)
    MIR_get_error_func (ctx) (MIR_call_op_error,
                              "Use only MIR_new_insn_arr for creating a phi insn");
  else if (MIR_call_code_p (code) || code == MIR_UNSPEC || code == MIR_RET || code == MIR_SWITCH)
    MIR_get_error_func (
      ctx) (MIR_call_op_error,
            "Use only MIR_new_insn_arr or MIR_new_{call,unspec,ret}_insn for creating a "
            "call/unspec/ret/switch insn");
  va_start (argp, code);
  return new_insn (ctx, code, nops, argp);
}

MIR_insn_t MIR_new_call_insn (MIR_context_t ctx, size_t nops, ...) {
  va_list argp;

  va_start (argp, nops);
  return new_insn (ctx, MIR_CALL, nops, argp);
}

MIR_insn_t MIR_new_ret_insn (MIR_context_t ctx, size_t nops, ...) {
  va_list argp;

  va_start (argp, nops);
  return new_insn (ctx, MIR_RET, nops, argp);
}

MIR_insn_t _MIR_new_unspec_insn (MIR_context_t ctx, size_t nops, ...) {
  va_list argp;

  va_start (argp, nops);
  return new_insn (ctx, MIR_UNSPEC, nops, argp);
}

void _MIR_register_unspec_insn (MIR_context_t ctx, uint64_t code, const char *name, size_t nres,
                                MIR_type_t *res_types, size_t nargs, int vararg_p,
                                MIR_var_t *args) {
  MIR_proto_t proto;

  while (VARR_LENGTH (MIR_proto_t, unspec_protos) <= code)
    VARR_PUSH (MIR_proto_t, unspec_protos, NULL);
  if ((proto = VARR_GET (MIR_proto_t, unspec_protos, code)) == NULL) {
    VARR_SET (MIR_proto_t, unspec_protos, code,
              create_proto (ctx, name, nres, res_types, nargs, vararg_p, args));
  } else {
    assert (strcmp (proto->name, name) == 0);
  }
}

MIR_insn_t MIR_copy_insn (MIR_context_t ctx, MIR_insn_t insn) {
  size_t size;
  mir_assert (insn != NULL);
  size = sizeof (struct MIR_insn) + sizeof (MIR_op_t) * (insn->nops == 0 ? 0 : insn->nops - 1);
  MIR_insn_t new_insn = malloc (size);

  if (new_insn == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory to copy insn %s",
                              insn_name (insn->code));
  memcpy (new_insn, insn, size);
  return new_insn;
}

static MIR_insn_t create_label (MIR_context_t ctx, int64_t label_num) {
  MIR_insn_t insn = new_insn1 (ctx, MIR_LABEL);

  insn->ops[0] = MIR_new_int_op (ctx, label_num);
  insn->nops = 0;
  return insn;
}

MIR_insn_t MIR_new_label (MIR_context_t ctx) { return create_label (ctx, ++curr_label_num); }

MIR_reg_t _MIR_new_temp_reg (MIR_context_t ctx, MIR_type_t type, MIR_func_t func) {
  char reg_name[30];

  if (type != MIR_T_I64 && type != MIR_T_F && type != MIR_T_D && type != MIR_T_LD)
    MIR_get_error_func (ctx) (MIR_reg_type_error, "wrong type %s for temporary register",
                              type_str (type));
  mir_assert (func != NULL);
  for (;;) {
    func->last_temp_num++;
    if (func->last_temp_num == 0)
      MIR_get_error_func (ctx) (MIR_unique_reg_error, "out of unique regs");
    sprintf (reg_name, "%s%d", TEMP_REG_NAME_PREFIX, func->last_temp_num);
    if (find_rd_by_name (ctx, reg_name, func) == NULL)
      return MIR_new_func_reg (ctx, func, type, reg_name);
  }
}

static reg_desc_t *get_func_rd_by_name (MIR_context_t ctx, const char *reg_name, MIR_func_t func) {
  reg_desc_t *rd;

  rd = find_rd_by_name (ctx, reg_name, func);
  if (rd == NULL)
    MIR_get_error_func (ctx) (MIR_undeclared_func_reg_error, "undeclared func reg %s", reg_name);
  return rd;
}

static reg_desc_t *get_func_rd_by_reg (MIR_context_t ctx, MIR_reg_t reg, MIR_func_t func) {
  reg_desc_t *rd;

  rd = find_rd_by_reg (ctx, reg, func);
  return rd;
}

MIR_reg_t MIR_reg (MIR_context_t ctx, const char *reg_name, MIR_func_t func) {
  return get_func_rd_by_name (ctx, reg_name, func)->reg;
}

MIR_type_t MIR_reg_type (MIR_context_t ctx, MIR_reg_t reg, MIR_func_t func) {
  return get_func_rd_by_reg (ctx, reg, func)->type;
}

const char *MIR_reg_name (MIR_context_t ctx, MIR_reg_t reg, MIR_func_t func) {
  return get_func_rd_by_reg (ctx, reg, func)->name;
}

/* Functions to create operands.  */

static void init_op (MIR_op_t *op, MIR_op_mode_t mode) {
  op->mode = mode;
  op->data = NULL;
}

MIR_op_t MIR_new_reg_op (MIR_context_t ctx, MIR_reg_t reg) {
  MIR_op_t op;

  init_op (&op, MIR_OP_REG);
  op.u.reg = reg;
  return op;
}

MIR_op_t _MIR_new_hard_reg_op (MIR_context_t ctx, MIR_reg_t hard_reg) { /* used only internally */
  MIR_op_t op;

  init_op (&op, MIR_OP_HARD_REG);
  op.u.hard_reg = hard_reg;
  return op;
}

MIR_op_t MIR_new_int_op (MIR_context_t ctx, int64_t i) {
  MIR_op_t op;

  init_op (&op, MIR_OP_INT);
  op.u.i = i;
  return op;
}

MIR_op_t MIR_new_uint_op (MIR_context_t ctx, uint64_t u) {
  MIR_op_t op;

  init_op (&op, MIR_OP_UINT);
  op.u.u = u;
  return op;
}

MIR_op_t MIR_new_float_op (MIR_context_t ctx, float f) {
  MIR_op_t op;

  mir_assert (sizeof (float) == 4); /* IEEE single */
  init_op (&op, MIR_OP_FLOAT);
  op.u.f = f;
  return op;
}

MIR_op_t MIR_new_double_op (MIR_context_t ctx, double d) {
  MIR_op_t op;

  mir_assert (sizeof (double) == 8); /* IEEE double */
  init_op (&op, MIR_OP_DOUBLE);
  op.u.d = d;
  return op;
}

MIR_op_t MIR_new_ldouble_op (MIR_context_t ctx, long double ld) {
  MIR_op_t op;

#if defined(_WIN32) || __SIZEOF_LONG_DOUBLE__ == 8
  return MIR_new_double_op (ctx, ld);
#endif
  mir_assert (sizeof (long double) == 16); /* machine-defined 80- or 128-bit FP  */
  init_op (&op, MIR_OP_LDOUBLE);
  op.u.ld = ld;
  return op;
}

MIR_op_t MIR_new_ref_op (MIR_context_t ctx, MIR_item_t item) {
  MIR_op_t op;

  init_op (&op, MIR_OP_REF);
  op.u.ref = item;
  return op;
}

MIR_op_t MIR_new_str_op (MIR_context_t ctx, MIR_str_t str) {
  MIR_op_t op;

  init_op (&op, MIR_OP_STR);
  op.u.str = get_ctx_string (ctx, str).str;
  return op;
}

MIR_op_t MIR_new_mem_op (MIR_context_t ctx, MIR_type_t type, MIR_disp_t disp, MIR_reg_t base,
                         MIR_reg_t index, MIR_scale_t scale) {
  MIR_op_t op;

  init_op (&op, MIR_OP_MEM);
  op.u.mem.type = canon_type (type);
  op.u.mem.disp = disp;
  op.u.mem.base = base;
  op.u.mem.index = index;
  op.u.mem.scale = scale;
  return op;
}

MIR_op_t _MIR_new_hard_reg_mem_op (MIR_context_t ctx, MIR_type_t type, MIR_disp_t disp,
                                   MIR_reg_t base, MIR_reg_t index, MIR_scale_t scale) {
  MIR_op_t op;

  init_op (&op, MIR_OP_HARD_REG_MEM);
  op.u.hard_reg_mem.type = type;
  op.u.hard_reg_mem.disp = disp;
  op.u.hard_reg_mem.base = base;
  op.u.hard_reg_mem.index = index;
  op.u.hard_reg_mem.scale = scale;
  return op;
}

MIR_op_t MIR_new_label_op (MIR_context_t ctx, MIR_label_t label) {
  MIR_op_t op;

  init_op (&op, MIR_OP_LABEL);
  op.u.label = label;
  return op;
}

int MIR_op_eq_p (MIR_context_t ctx, MIR_op_t op1, MIR_op_t op2) {
  if (op1.mode != op2.mode) return FALSE;
  switch (op1.mode) {
  case MIR_OP_REG: return op1.u.reg == op2.u.reg;
  case MIR_OP_HARD_REG: return op1.u.hard_reg == op2.u.hard_reg;
  case MIR_OP_INT: return op1.u.i == op2.u.i;
  case MIR_OP_UINT: return op1.u.u == op2.u.u;
  case MIR_OP_FLOAT: return op1.u.f == op2.u.f;
  case MIR_OP_DOUBLE: return op1.u.d == op2.u.d;
  case MIR_OP_LDOUBLE: return op1.u.ld == op2.u.ld;
  case MIR_OP_REF:
    return strcmp (MIR_item_name (ctx, op1.u.ref), MIR_item_name (ctx, op2.u.ref)) == 0;
  case MIR_OP_STR:
    return op1.u.str.len == op2.u.str.len && memcmp (op1.u.str.s, op2.u.str.s, op1.u.str.len) == 0;
  case MIR_OP_MEM:
    return (op1.u.mem.type == op2.u.mem.type && op1.u.mem.disp == op2.u.mem.disp
            && op1.u.mem.base == op2.u.mem.base && op1.u.mem.index == op2.u.mem.index
            && (op1.u.mem.index == 0 || op1.u.mem.scale == op2.u.mem.scale));
  case MIR_OP_HARD_REG_MEM:
    return (op1.u.hard_reg_mem.type == op2.u.hard_reg_mem.type
            && op1.u.hard_reg_mem.disp == op2.u.hard_reg_mem.disp
            && op1.u.hard_reg_mem.base == op2.u.hard_reg_mem.base
            && op1.u.hard_reg_mem.index == op2.u.hard_reg_mem.index
            && (op1.u.hard_reg_mem.index == MIR_NON_HARD_REG
                || op1.u.hard_reg_mem.scale == op2.u.hard_reg_mem.scale));
  case MIR_OP_LABEL: return op1.u.label == op2.u.label;
  default: mir_assert (FALSE); /* we should not have other operands here */
  }
  return FALSE;
}

htab_hash_t MIR_op_hash_step (MIR_context_t ctx, htab_hash_t h, MIR_op_t op) {
  h = mir_hash_step (h, (uint64_t) op.mode);
  switch (op.mode) {
  case MIR_OP_REG: return mir_hash_step (h, (uint64_t) op.u.reg);
  case MIR_OP_HARD_REG: return mir_hash_step (h, (uint64_t) op.u.hard_reg);
  case MIR_OP_INT: return mir_hash_step (h, (uint64_t) op.u.i);
  case MIR_OP_UINT: return mir_hash_step (h, (uint64_t) op.u.u);
  case MIR_OP_FLOAT: {
    union {
      double d;
      uint64_t u;
    } u;

    u.d = op.u.f;
    return mir_hash_step (h, u.u);
  }
  case MIR_OP_DOUBLE: return mir_hash_step (h, op.u.u);
  case MIR_OP_LDOUBLE: {
    union {
      long double ld;
      uint64_t u[2];
    } u;

    u.ld = op.u.ld;
    return mir_hash_step (mir_hash_step (h, u.u[0]), u.u[1]);
  }
  case MIR_OP_REF: return mir_hash_step (h, (uint64_t) MIR_item_name (ctx, op.u.ref));
  case MIR_OP_STR: return mir_hash_step (h, (uint64_t) op.u.str.s);
  case MIR_OP_MEM:
    h = mir_hash_step (h, (uint64_t) op.u.mem.type);
    h = mir_hash_step (h, (uint64_t) op.u.mem.disp);
    h = mir_hash_step (h, (uint64_t) op.u.mem.base);
    h = mir_hash_step (h, (uint64_t) op.u.mem.index);
    if (op.u.mem.index != 0) h = mir_hash_step (h, (uint64_t) op.u.mem.scale);
    break;
  case MIR_OP_HARD_REG_MEM:
    h = mir_hash_step (h, (uint64_t) op.u.hard_reg_mem.type);
    h = mir_hash_step (h, (uint64_t) op.u.hard_reg_mem.disp);
    h = mir_hash_step (h, (uint64_t) op.u.hard_reg_mem.base);
    h = mir_hash_step (h, (uint64_t) op.u.hard_reg_mem.index);
    if (op.u.hard_reg_mem.index != MIR_NON_HARD_REG)
      h = mir_hash_step (h, (uint64_t) op.u.hard_reg_mem.scale);
    break;
  case MIR_OP_LABEL: return mir_hash_step (h, (uint64_t) op.u.label);
  default: mir_assert (FALSE); /* we should not have other operands here */
  }
  return h;
}

void MIR_append_insn (MIR_context_t ctx, MIR_item_t func_item, MIR_insn_t insn) {
  mir_assert (func_item != NULL);
  if (func_item->item_type != MIR_func_item)
    MIR_get_error_func (ctx) (MIR_wrong_param_value_error, "MIR_append_insn: wrong func item");
  DLIST_APPEND (MIR_insn_t, func_item->u.func->insns, insn);
}

void MIR_prepend_insn (MIR_context_t ctx, MIR_item_t func_item, MIR_insn_t insn) {
  mir_assert (func_item != NULL);
  if (func_item->item_type != MIR_func_item)
    MIR_get_error_func (ctx) (MIR_wrong_param_value_error, "MIR_prepend_insn: wrong func item");
  DLIST_PREPEND (MIR_insn_t, func_item->u.func->insns, insn);
}

void MIR_insert_insn_after (MIR_context_t ctx, MIR_item_t func_item, MIR_insn_t after,
                            MIR_insn_t insn) {
  mir_assert (func_item != NULL);
  if (func_item->item_type != MIR_func_item)
    MIR_get_error_func (ctx) (MIR_wrong_param_value_error,
                              "MIR_insert_insn_after: wrong func item");
  DLIST_INSERT_AFTER (MIR_insn_t, func_item->u.func->insns, after, insn);
}

void MIR_insert_insn_before (MIR_context_t ctx, MIR_item_t func_item, MIR_insn_t before,
                             MIR_insn_t insn) {
  mir_assert (func_item != NULL);
  if (func_item->item_type != MIR_func_item)
    MIR_get_error_func (ctx) (MIR_wrong_param_value_error,
                              "MIR_insert_insn_before: wrong func item");
  DLIST_INSERT_BEFORE (MIR_insn_t, func_item->u.func->insns, before, insn);
}

static void store_labels_for_duplication (MIR_context_t ctx, VARR (MIR_insn_t) * labels,
                                          VARR (MIR_insn_t) * branch_insns, MIR_insn_t insn,
                                          MIR_insn_t new_insn) {
  if (MIR_branch_code_p (insn->code) || insn->code == MIR_SWITCH) {
    VARR_PUSH (MIR_insn_t, branch_insns, new_insn);
  } else if (insn->code == MIR_LABEL) {
    mir_assert (insn->data == NULL);
    insn->data = new_insn;
    VARR_PUSH (MIR_insn_t, labels, insn);
  }
}

static void redirect_duplicated_labels (MIR_context_t ctx, VARR (MIR_insn_t) * labels,
                                        VARR (MIR_insn_t) * branch_insns) {
  MIR_insn_t insn;

  while (VARR_LENGTH (MIR_insn_t, branch_insns) != 0) { /* redirect new label operands */
    size_t start_label_nop = 0, bound_label_nop = 1, n;

    insn = VARR_POP (MIR_insn_t, branch_insns);
    if (insn->code == MIR_SWITCH) {
      start_label_nop = 1;
      bound_label_nop = start_label_nop + insn->nops - 1;
    }
    for (n = start_label_nop; n < bound_label_nop; n++)
      insn->ops[n].u.label = insn->ops[n].u.label->data;
  }
  while (VARR_LENGTH (MIR_insn_t, labels) != 0) { /* reset data */
    insn = VARR_POP (MIR_insn_t, labels);
    insn->data = NULL;
  }
}

void _MIR_duplicate_func_insns (MIR_context_t ctx, MIR_item_t func_item) {
  MIR_func_t func;
  MIR_insn_t insn, new_insn;
  VARR (MIR_insn_t) * labels, *branch_insns;

  mir_assert (func_item != NULL && func_item->item_type == MIR_func_item);
  func = func_item->u.func;
  mir_assert (DLIST_HEAD (MIR_insn_t, func->original_insns) == NULL);
  func->original_insns = func->insns;
  DLIST_INIT (MIR_insn_t, func->insns);
  VARR_CREATE (MIR_insn_t, labels, 0);
  VARR_CREATE (MIR_insn_t, branch_insns, 0);
  for (insn = DLIST_HEAD (MIR_insn_t, func->original_insns); insn != NULL;
       insn = DLIST_NEXT (MIR_insn_t, insn)) { /* copy insns and collect label info */
    new_insn = MIR_copy_insn (ctx, insn);
    DLIST_APPEND (MIR_insn_t, func->insns, new_insn);
    store_labels_for_duplication (ctx, labels, branch_insns, insn, new_insn);
  }
  redirect_duplicated_labels (ctx, labels, branch_insns);
  VARR_DESTROY (MIR_insn_t, labels);
  VARR_DESTROY (MIR_insn_t, branch_insns);
}

void _MIR_restore_func_insns (MIR_context_t ctx, MIR_item_t func_item) {
  MIR_func_t func;
  MIR_insn_t insn;

  mir_assert (func_item != NULL && func_item->item_type == MIR_func_item);
  func = func_item->u.func;
  while ((insn = DLIST_HEAD (MIR_insn_t, func->insns)) != NULL)
    MIR_remove_insn (ctx, func_item, insn);
  func->insns = func->original_insns;
  DLIST_INIT (MIR_insn_t, func->original_insns);
}

static void set_item_name (MIR_item_t item, const char *name) {
  mir_assert (item != NULL);
  switch (item->item_type) {
  case MIR_func_item: item->u.func->name = name; break;
  case MIR_proto_item: item->u.proto->name = name; break;
  case MIR_import_item: item->u.import_id = name; break;
  case MIR_export_item: item->u.export_id = name; break;
  case MIR_forward_item: item->u.forward_id = name; break;
  case MIR_bss_item: item->u.bss->name = name; break;
  case MIR_data_item: item->u.data->name = name; break;
  case MIR_ref_data_item: item->u.ref_data->name = name; break;
  case MIR_expr_data_item: item->u.expr_data->name = name; break;
  default: mir_assert (FALSE);
  }
}

static void change_var_names (MIR_context_t new_ctx, VARR (MIR_var_t) * vars) {
  for (size_t i = 0; i < VARR_LENGTH (MIR_var_t, vars); i++) {
    MIR_var_t *var_ptr = &VARR_ADDR (MIR_var_t, vars)[i];
    var_ptr->name = get_ctx_str (new_ctx, var_ptr->name);
  }
}

/* It is not thread-safe */
void MIR_change_module_ctx (MIR_context_t old_ctx, MIR_module_t m, MIR_context_t new_ctx) {
  MIR_item_t item, tab_item;
  MIR_op_mode_t mode;
  const char *name, *new_name;

  DLIST_REMOVE (MIR_module_t, *MIR_get_module_list (old_ctx), m);
  DLIST_APPEND (MIR_module_t, *MIR_get_module_list (new_ctx), m);
  m->name = get_ctx_str (new_ctx, m->name);
  for (item = DLIST_HEAD (MIR_item_t, m->items); item != NULL;
       item = DLIST_NEXT (MIR_item_t, item)) {
    if (item->addr != NULL)
      MIR_get_error_func (old_ctx) (MIR_ctx_change_error, "Change context of a loaded module");
    if ((name = MIR_item_name (old_ctx, item)) != NULL) {
      new_name = get_ctx_str (new_ctx, name);
      if (item_tab_find (old_ctx, name, m) != item) {
        set_item_name (item, new_name);
      } else {
        item_tab_remove (old_ctx, item);
        set_item_name (item, new_name);
        tab_item = item_tab_insert (new_ctx, item);
        mir_assert (item == tab_item);
      }
    }
    if (item->item_type == MIR_proto_item) {
      change_var_names (new_ctx, item->u.proto->args);
    } else if (item->item_type == MIR_func_item) {
      change_var_names (new_ctx, item->u.func->vars);
      for (MIR_insn_t insn = DLIST_HEAD (MIR_insn_t, item->u.func->insns); insn != NULL;
           insn = DLIST_NEXT (MIR_insn_t, insn))
        for (size_t i = 0; i < insn->nops; i++) {
          if ((mode = insn->ops[i].mode) == MIR_OP_STR)
            insn->ops[i].u.str = get_ctx_string (new_ctx, insn->ops[i].u.str).str;
        }
    }
  }
}

static void output_type (FILE *f, MIR_type_t tp) { fprintf (f, "%s", MIR_type_str (NULL, tp)); }

static void output_disp (FILE *f, MIR_disp_t disp) { fprintf (f, "%" PRId64, (int64_t) disp); }

static void output_scale (FILE *f, unsigned scale) { fprintf (f, "%u", scale); }

static void output_reg (MIR_context_t ctx, FILE *f, MIR_func_t func, MIR_reg_t reg) {
  fprintf (f, "%s", MIR_reg_name (ctx, reg, func));
}

static void output_hard_reg (FILE *f, MIR_reg_t reg) { fprintf (f, "hr%u", reg); }

static void output_label (MIR_context_t ctx, FILE *f, MIR_func_t func, MIR_label_t label);

static void out_str (FILE *f, MIR_str_t str) {
  fprintf (f, "\"");
  for (size_t i = 0; i < str.len; i++)
    if (str.s[i] == '\\')
      fprintf (f, "\\\\");
    else if (str.s[i] == '"')
      fprintf (f, "\\\"");
    else if (isprint (str.s[i]))
      fprintf (f, "%c", str.s[i]);
    else if (str.s[i] == '\n')
      fprintf (f, "\\n");
    else if (str.s[i] == '\t')
      fprintf (f, "\\t");
    else if (str.s[i] == '\v')
      fprintf (f, "\\v");
    else if (str.s[i] == '\a')
      fprintf (f, "\\a");
    else if (str.s[i] == '\b')
      fprintf (f, "\\b");
    else if (str.s[i] == '\f')
      fprintf (f, "\\f");
    else
      fprintf (f, "\\%03o", str.s[i]);
  fprintf (f, "\"");
}

void MIR_output_op (MIR_context_t ctx, FILE *f, MIR_op_t op, MIR_func_t func) {
  switch (op.mode) {
  case MIR_OP_REG: output_reg (ctx, f, func, op.u.reg); break;
  case MIR_OP_HARD_REG: output_hard_reg (f, op.u.hard_reg); break;
  case MIR_OP_INT: fprintf (f, "%" PRId64, op.u.i); break;
  case MIR_OP_UINT: fprintf (f, "%" PRIu64, op.u.u); break;
  case MIR_OP_FLOAT: fprintf (f, "%.*ef", FLT_MANT_DIG, op.u.f); break;
  case MIR_OP_DOUBLE: fprintf (f, "%.*e", DBL_MANT_DIG, op.u.d); break;
  case MIR_OP_LDOUBLE: fprintf (f, "%.*LeL", LDBL_MANT_DIG, op.u.ld); break;
  case MIR_OP_MEM:
  case MIR_OP_HARD_REG_MEM: {
    MIR_reg_t no_reg = op.mode == MIR_OP_MEM ? 0 : MIR_NON_HARD_REG;

    output_type (f, op.u.mem.type);
    fprintf (f, ":");
    if (op.u.mem.disp != 0 || (op.u.mem.base == no_reg && op.u.mem.index == no_reg))
      output_disp (f, op.u.mem.disp);
    if (op.u.mem.base != no_reg || op.u.mem.index != no_reg) {
      fprintf (f, "(");
      if (op.u.mem.base != no_reg) {
        if (op.mode == MIR_OP_MEM)
          output_reg (ctx, f, func, op.u.mem.base);
        else
          output_hard_reg (f, op.u.hard_reg_mem.base);
      }
      if (op.u.mem.index != no_reg) {
        fprintf (f, ", ");
        if (op.mode == MIR_OP_MEM)
          output_reg (ctx, f, func, op.u.mem.index);
        else
          output_hard_reg (f, op.u.hard_reg_mem.index);
        if (op.u.mem.scale != 1) {
          fprintf (f, ", ");
          output_scale (f, op.u.mem.scale);
        }
      }
      fprintf (f, ")");
    }
    break;
  }
  case MIR_OP_REF: fprintf (f, "%s", MIR_item_name (ctx, op.u.ref)); break;
  case MIR_OP_STR: out_str (f, op.u.str); break;
  case MIR_OP_LABEL: output_label (ctx, f, func, op.u.label); break;
  default: mir_assert (FALSE);
  }
}

static void output_label (MIR_context_t ctx, FILE *f, MIR_func_t func, MIR_label_t label) {
  fprintf (f, "L");
  MIR_output_op (ctx, f, label->ops[0], func);
}

void MIR_output_insn (MIR_context_t ctx, FILE *f, MIR_insn_t insn, MIR_func_t func, int newline_p) {
  size_t i, nops;

  mir_assert (insn != NULL);
  if (insn->code == MIR_LABEL) {
    output_label (ctx, f, func, insn);
    if (newline_p) fprintf (f, ":\n");
    return;
  }
  fprintf (f, "\t%s", MIR_insn_name (ctx, insn->code));
  nops = MIR_insn_nops (ctx, insn);
  for (i = 0; i < nops; i++) {
    fprintf (f, i == 0 ? "\t" : ", ");
    MIR_output_op (ctx, f, insn->ops[i], func);
  }
  if (insn->code == MIR_UNSPEC)
    fprintf (f, " # %s", VARR_GET (MIR_proto_t, unspec_protos, insn->ops[0].u.u)->name);
  if (newline_p) fprintf (f, "\n");
}

static void output_func_proto (FILE *f, size_t nres, MIR_type_t *types, size_t nargs,
                               VARR (MIR_var_t) * args, int vararg_p) {
  size_t i;
  MIR_var_t var;

  for (i = 0; i < nres; i++) {
    if (i != 0) fprintf (f, ", ");
    fprintf (f, "%s", MIR_type_str (NULL, types[i]));
  }
  for (i = 0; i < nargs; i++) {
    var = VARR_GET (MIR_var_t, args, i);
    if (i != 0 || nres != 0) fprintf (f, ", ");
    mir_assert (var.name != NULL);
    if (!MIR_all_blk_type_p (var.type))
      fprintf (f, "%s:%s", MIR_type_str (NULL, var.type), var.name);
    else
      fprintf (f, "%s:%lu(%s)", MIR_type_str (NULL, var.type), (unsigned long) var.size, var.name);
  }
  if (vararg_p) fprintf (f, nargs == 0 && nres == 0 ? "..." : ", ...");
  fprintf (f, "\n");
}

void MIR_output_item (MIR_context_t ctx, FILE *f, MIR_item_t item) {
  MIR_insn_t insn;
  MIR_func_t func;
  MIR_proto_t proto;
  MIR_var_t var;
  MIR_data_t data;
  MIR_ref_data_t ref_data;
  MIR_expr_data_t expr_data;
  size_t i, nlocals;

  mir_assert (f != NULL && item != NULL);
  if (item->item_type == MIR_export_item) {
    fprintf (f, "\texport\t%s\n", item->u.export_id);
    return;
  }
  if (item->item_type == MIR_import_item) {
    fprintf (f, "\timport\t%s\n", item->u.import_id);
    return;
  }
  if (item->item_type == MIR_forward_item) {
    fprintf (f, "\tforward\t%s\n", item->u.forward_id);
    return;
  }
  if (item->item_type == MIR_bss_item) {
    if (item->u.bss->name != NULL) fprintf (f, "%s:", item->u.bss->name);
    fprintf (f, "\tbss\t%" PRIu64 "\n", item->u.bss->len);
    return;
  }
  if (item->item_type == MIR_ref_data_item) {
    ref_data = item->u.ref_data;
    if (ref_data->name != NULL) fprintf (f, "%s:", ref_data->name);
    fprintf (f, "\tref\t%s, %" PRId64 "\n", MIR_item_name (ctx, ref_data->ref_item),
             (int64_t) ref_data->disp);
    return;
  }
  if (item->item_type == MIR_expr_data_item) {
    expr_data = item->u.expr_data;
    if (expr_data->name != NULL) fprintf (f, "%s:", expr_data->name);
    fprintf (f, "\texpr\t%s", MIR_item_name (ctx, expr_data->expr_item));
  }
  if (item->item_type == MIR_data_item) {
    data = item->u.data;
    if (data->name != NULL) fprintf (f, "%s:", data->name);
    fprintf (f, "\t%s\t", MIR_type_str (NULL, data->el_type));
    for (size_t i = 0; i < data->nel; i++) {
      switch (data->el_type) {
      case MIR_T_I8: fprintf (f, "%" PRId8, ((int8_t *) data->u.els)[i]); break;
      case MIR_T_U8: fprintf (f, "%" PRIu8, ((uint8_t *) data->u.els)[i]); break;
      case MIR_T_I16: fprintf (f, "%" PRId16, ((int16_t *) data->u.els)[i]); break;
      case MIR_T_U16: fprintf (f, "%" PRIu16, ((uint16_t *) data->u.els)[i]); break;
      case MIR_T_I32: fprintf (f, "%" PRId32, ((int32_t *) data->u.els)[i]); break;
      case MIR_T_U32: fprintf (f, "%" PRIu32, ((uint32_t *) data->u.els)[i]); break;
      case MIR_T_I64: fprintf (f, "%" PRId64, ((int64_t *) data->u.els)[i]); break;
      case MIR_T_U64: fprintf (f, "%" PRIu64, ((uint64_t *) data->u.els)[i]); break;
      case MIR_T_F: fprintf (f, "%.*ef", FLT_MANT_DIG, ((float *) data->u.els)[i]); break;
      case MIR_T_D: fprintf (f, "%.*e", DBL_MANT_DIG, ((double *) data->u.els)[i]); break;
      case MIR_T_LD:
        fprintf (f, "%.*LeL", LDBL_MANT_DIG, ((long double *) data->u.els)[i]);
        break;
        /* only ptr as ref ??? */
      case MIR_T_P: fprintf (f, "0x%" PRIxPTR, ((uintptr_t *) data->u.els)[i]); break;
      default: mir_assert (FALSE);
      }
      if (i + 1 < data->nel) fprintf (f, ", ");
    }
    if (data->el_type == MIR_T_U8 && data->nel != 0 && data->u.els[data->nel - 1] == '\0') {
      fprintf (f, " # "); /* print possible string as a comment */
      out_str (f, (MIR_str_t){data->nel, (char *) data->u.els});
    }
    fprintf (f, "\n");
    return;
  }
  if (item->item_type == MIR_proto_item) {
    proto = item->u.proto;
    fprintf (f, "%s:\tproto\t", proto->name);
    output_func_proto (f, proto->nres, proto->res_types, VARR_LENGTH (MIR_var_t, proto->args),
                       proto->args, proto->vararg_p);
    return;
  }
  func = item->u.func;
  fprintf (f, "%s:\tfunc\t", func->name);
  output_func_proto (f, func->nres, func->res_types, func->nargs, func->vars, func->vararg_p);
  nlocals = VARR_LENGTH (MIR_var_t, func->vars) - func->nargs;
  for (i = 0; i < nlocals; i++) {
    var = VARR_GET (MIR_var_t, func->vars, i + func->nargs);
    if (i % 8 == 0) {
      if (i != 0) fprintf (f, "\n");
      fprintf (f, "\tlocal\t");
    }
    fprintf (f, i % 8 == 0 ? "%s:%s" : ", %s:%s", MIR_type_str (NULL, var.type), var.name);
  }
  fprintf (f, "\n# %u arg%s, %u local%s\n", func->nargs, func->nargs == 1 ? "" : "s",
           (unsigned) nlocals, nlocals == 1 ? "" : "s");
  for (insn = DLIST_HEAD (MIR_insn_t, func->insns); insn != NULL;
       insn = DLIST_NEXT (MIR_insn_t, insn))
    MIR_output_insn (ctx, f, insn, func, TRUE);
  fprintf (f, "\tendfunc\n");
}

void MIR_output_module (MIR_context_t ctx, FILE *f, MIR_module_t module) {
  mir_assert (f != NULL && module != NULL);
  fprintf (f, "%s:\tmodule\n", module->name);
  for (MIR_item_t item = DLIST_HEAD (MIR_item_t, module->items); item != NULL;
       item = DLIST_NEXT (MIR_item_t, item))
    MIR_output_item (ctx, f, item);
  fprintf (f, "\tendmodule\n");
}

void MIR_output (MIR_context_t ctx, FILE *f) {
  mir_assert (f != NULL);
  for (MIR_module_t module = DLIST_HEAD (MIR_module_t, all_modules); module != NULL;
       module = DLIST_NEXT (MIR_module_t, module))
    MIR_output_module (ctx, f, module);
}

/* New Page */
/* This page contains code for simplification and inlining */

static MIR_insn_t insert_op_insn (MIR_context_t ctx, int out_p, MIR_item_t func_item,
                                  MIR_insn_t anchor, MIR_insn_t insn) {
  if (!out_p) {
    MIR_insert_insn_before (ctx, func_item, anchor, insn);
    return anchor;
  }
  MIR_insert_insn_after (ctx, func_item, anchor, insn);
  return insn;
}

typedef struct {
  MIR_insn_code_t code;
  MIR_type_t type;
  MIR_op_t op1, op2;
  MIR_reg_t reg;
} val_t;

DEF_HTAB (val_t);

struct simplify_ctx {
  HTAB (val_t) * val_tab;
  VARR (MIR_insn_t) * temp_insns, *labels; /* temp_insns is for branch or ret insns */
  VARR (MIR_reg_t) * inline_reg_map;
  size_t inlined_calls, inline_insns_before, inline_insns_after;
};

#define val_tab ctx->simplify_ctx->val_tab
#define temp_insns ctx->simplify_ctx->temp_insns
#define labels ctx->simplify_ctx->labels
#define inline_reg_map ctx->simplify_ctx->inline_reg_map
#define inlined_calls ctx->simplify_ctx->inlined_calls
#define inline_insns_before ctx->simplify_ctx->inline_insns_before
#define inline_insns_after ctx->simplify_ctx->inline_insns_after

static htab_hash_t val_hash (val_t v, void *arg) {
  MIR_context_t ctx = arg;
  htab_hash_t h;

  h = mir_hash_step (mir_hash_init (0), (uint64_t) v.code);
  h = mir_hash_step (h, (uint64_t) v.type);
  h = MIR_op_hash_step (ctx, h, v.op1);
  if (v.code != MIR_INSN_BOUND) h = MIR_op_hash_step (ctx, h, v.op2);
  return mir_hash_finish (h);
}

static int val_eq (val_t v1, val_t v2, void *arg) {
  MIR_context_t ctx = arg;

  if (v1.code != v2.code || v1.type != v2.type || !MIR_op_eq_p (ctx, v1.op1, v2.op1)) return FALSE;
  return v1.code == MIR_INSN_BOUND || MIR_op_eq_p (ctx, v1.op2, v2.op2);
}

static void simplify_init (MIR_context_t ctx) {
  if ((ctx->simplify_ctx = malloc (sizeof (struct simplify_ctx))) == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for ctx");
  HTAB_CREATE (val_t, val_tab, 512, val_hash, val_eq, ctx);
  VARR_CREATE (MIR_insn_t, temp_insns, 0);
  VARR_CREATE (MIR_insn_t, labels, 0);
  VARR_CREATE (MIR_reg_t, inline_reg_map, 256);
  inlined_calls = inline_insns_before = inline_insns_after = 0;
}

static void simplify_finish (MIR_context_t ctx) {
  VARR_DESTROY (MIR_reg_t, inline_reg_map);
#if 0
  if (inlined_calls != 0)
    fprintf (stderr, "inlined calls = %lu, insns before = %lu, insns_after = %lu, ratio = %.2f\n",
             inlined_calls, inline_insns_before, inline_insns_after,
             (double) inline_insns_after / inline_insns_before);
#endif
  VARR_DESTROY (MIR_insn_t, labels);
  VARR_DESTROY (MIR_insn_t, temp_insns);
  HTAB_DESTROY (val_t, val_tab);
  free (ctx->simplify_ctx);
  ctx->simplify_ctx = NULL;
}

static void vn_empty (MIR_context_t ctx) { HTAB_CLEAR (val_t, val_tab); }

static MIR_reg_t vn_add_val (MIR_context_t ctx, MIR_func_t func, MIR_type_t type,
                             MIR_insn_code_t code, MIR_op_t op1, MIR_op_t op2) {
  val_t val, tab_val;

  val.type = type;
  val.code = code;
  val.op1 = op1;
  val.op2 = op2;
  if (HTAB_DO (val_t, val_tab, val, HTAB_FIND, tab_val)) return tab_val.reg;
  val.reg = _MIR_new_temp_reg (ctx, type, func);
  HTAB_DO (val_t, val_tab, val, HTAB_INSERT, tab_val);
  return val.reg;
}

void _MIR_get_temp_item_name (MIR_context_t ctx, MIR_module_t module, char *buff, size_t buff_len) {
  mir_assert (module != NULL);
  module->last_temp_item_num++;
  snprintf (buff, buff_len, "%s%u", TEMP_ITEM_NAME_PREFIX, (unsigned) module->last_temp_item_num);
}

void MIR_simplify_op (MIR_context_t ctx, MIR_item_t func_item, MIR_insn_t insn, int nop, int out_p,
                      MIR_insn_code_t code, int keep_ref_p, int mem_float_p) {
  mir_assert (insn != NULL && func_item != NULL);
  MIR_op_t new_op, mem_op, *op = &insn->ops[nop];
  MIR_insn_t new_insn;
  MIR_func_t func = func_item->u.func;
  MIR_type_t type;
  MIR_op_mode_t value_mode = op->value_mode;
  int move_p = code == MIR_MOV || code == MIR_FMOV || code == MIR_DMOV || code == MIR_LDMOV;

  if (code == MIR_PHI) return;                /* do nothing: it is a phi insn */
  if (code == MIR_UNSPEC && nop == 0) return; /* do nothing: it is an unspec code */
  if (MIR_call_code_p (code)) {
    if (nop == 0) return; /* do nothing: it is a prototype */
    if (nop == 1 && op->mode == MIR_OP_REF
        && (op->u.ref->item_type == MIR_import_item || op->u.ref->item_type == MIR_func_item))
      return; /* do nothing: it is an immediate oeprand */
  }
  if (code == MIR_VA_ARG && nop == 2) return; /* do nothing: this operand is used as a type */
  switch (op->mode) {
  case MIR_OP_REF:
    if (keep_ref_p) break;
  case MIR_OP_INT:
  case MIR_OP_UINT:
  case MIR_OP_FLOAT:
  case MIR_OP_DOUBLE:
  case MIR_OP_LDOUBLE:
  case MIR_OP_STR:
    mir_assert (!out_p);
    if (op->mode == MIR_OP_REF) {
      for (MIR_item_t item = op->u.ref; item != NULL; item = item->ref_def)
        if (item->item_type != MIR_export_item && item->item_type != MIR_forward_item) {
          op->u.ref = item;
          break;
        }
    } else if (op->mode == MIR_OP_STR
               || (mem_float_p
                   && (op->mode == MIR_OP_FLOAT || op->mode == MIR_OP_DOUBLE
                       || op->mode == MIR_OP_LDOUBLE))) {
      const char *name;
      char buff[50];
      MIR_item_t item;
      MIR_module_t m = curr_module;

      curr_module = func_item->module;
      _MIR_get_temp_item_name (ctx, curr_module, buff, sizeof (buff));
      name = buff;
      if (op->mode == MIR_OP_STR) {
        item = MIR_new_string_data (ctx, name, op->u.str);
        *op = MIR_new_ref_op (ctx, item);
      } else {
        if (op->mode == MIR_OP_FLOAT)
          item = MIR_new_data (ctx, name, MIR_T_F, 1, (uint8_t *) &op->u.f);
        else if (op->mode == MIR_OP_DOUBLE)
          item = MIR_new_data (ctx, name, MIR_T_D, 1, (uint8_t *) &op->u.d);
        else
          item = MIR_new_data (ctx, name, MIR_T_LD, 1, (uint8_t *) &op->u.ld);
        type = op->mode == MIR_OP_FLOAT ? MIR_T_F : op->mode == MIR_OP_DOUBLE ? MIR_T_D : MIR_T_LD;
        *op = MIR_new_ref_op (ctx, item);
        new_op = MIR_new_reg_op (ctx, vn_add_val (ctx, func, MIR_T_I64, MIR_INSN_BOUND, *op, *op));
        MIR_insert_insn_before (ctx, func_item, insn, MIR_new_insn (ctx, MIR_MOV, new_op, *op));
        *op = MIR_new_mem_op (ctx, type, 0, new_op.u.reg, 0, 1);
      }
      if (func_item->addr != NULL) /* The function was already loaded: we should load new data */
        load_bss_data_section (ctx, item, TRUE);
      curr_module = m;
    }
    if (move_p) return;
    type = (op->mode == MIR_OP_FLOAT ? MIR_T_F
                                     : op->mode == MIR_OP_DOUBLE
                                         ? MIR_T_D
                                         : op->mode == MIR_OP_LDOUBLE
                                             ? MIR_T_LD
                                             : op->mode == MIR_OP_MEM ? op->u.mem.type : MIR_T_I64);
    new_op = MIR_new_reg_op (ctx, vn_add_val (ctx, func, type, MIR_INSN_BOUND, *op, *op));
    MIR_insert_insn_before (ctx, func_item, insn,
                            MIR_new_insn (ctx,
                                          type == MIR_T_F
                                            ? MIR_FMOV
                                            : type == MIR_T_D
                                                ? MIR_DMOV
                                                : type == MIR_T_LD ? MIR_LDMOV : MIR_MOV,
                                          new_op, *op));
    *op = new_op;
    break;
  case MIR_OP_REG:
  case MIR_OP_HARD_REG:
  case MIR_OP_LABEL: break; /* Do nothing */
  case MIR_OP_MEM: {
    MIR_reg_t addr_reg = 0;

    mem_op = *op;
    type = mem_op.u.mem.type;
    if (op->u.mem.base != 0 && op->u.mem.disp == 0
        && (op->u.mem.index == 0 || op->u.mem.scale == 0)) {
      addr_reg = op->u.mem.base;
    } else if (op->u.mem.base == 0 && op->u.mem.index != 0 && op->u.mem.scale == 1
               && op->u.mem.disp == 0) {
      addr_reg = op->u.mem.index;
    } else {
      int after_p = !move_p && out_p;
      MIR_reg_t disp_reg = 0, scale_ind_reg = op->u.mem.index;
      MIR_reg_t base_reg = op->u.mem.base, base_ind_reg = 0;

      if (op->u.mem.disp != 0) {
        MIR_op_t disp_op = MIR_new_int_op (ctx, op->u.mem.disp);

        disp_reg = vn_add_val (ctx, func, MIR_T_I64, MIR_INSN_BOUND, disp_op, disp_op);
        insn
          = insert_op_insn (ctx, after_p, func_item, insn,
                            MIR_new_insn (ctx, MIR_MOV, MIR_new_reg_op (ctx, disp_reg), disp_op));
      }
      if (scale_ind_reg != 0 && op->u.mem.scale > 1) {
        MIR_op_t ind_op = MIR_new_reg_op (ctx, op->u.mem.index);
        MIR_op_t scale_reg_op, scale_int_op = MIR_new_int_op (ctx, op->u.mem.scale);

        scale_reg_op = MIR_new_reg_op (ctx, vn_add_val (ctx, func, MIR_T_I64, MIR_INSN_BOUND,
                                                        scale_int_op, scale_int_op));
        insn = insert_op_insn (ctx, after_p, func_item, insn,
                               MIR_new_insn (ctx, MIR_MOV, scale_reg_op, scale_int_op));
        scale_ind_reg = vn_add_val (ctx, func, MIR_T_I64, MIR_MUL, ind_op, scale_reg_op);
        insn = insert_op_insn (ctx, after_p, func_item, insn,
                               MIR_new_insn (ctx, MIR_MUL, MIR_new_reg_op (ctx, scale_ind_reg),
                                             ind_op, scale_reg_op));
      }
      if (base_reg != 0 && scale_ind_reg != 0) {
        MIR_op_t base_op = MIR_new_reg_op (ctx, base_reg),
                 ind_op = MIR_new_reg_op (ctx, scale_ind_reg);

        base_ind_reg = vn_add_val (ctx, func, MIR_T_I64, MIR_ADD, base_op, ind_op);
        insn = insert_op_insn (ctx, after_p, func_item, insn,
                               MIR_new_insn (ctx, MIR_ADD, MIR_new_reg_op (ctx, base_ind_reg),
                                             base_op, ind_op));
      } else {
        base_ind_reg = base_reg != 0 ? base_reg : scale_ind_reg;
      }
      if (base_ind_reg == 0) {
        mir_assert (disp_reg != 0);
        addr_reg = disp_reg;
      } else if (disp_reg == 0) {
        mir_assert (base_ind_reg != 0);
        addr_reg = base_ind_reg;
      } else {
        MIR_op_t base_ind_op = MIR_new_reg_op (ctx, base_ind_reg);
        MIR_op_t disp_op = MIR_new_reg_op (ctx, disp_reg);

        addr_reg = vn_add_val (ctx, func, MIR_T_I64, MIR_ADD, base_ind_op, disp_op);
        insn = insert_op_insn (ctx, after_p, func_item, insn,
                               MIR_new_insn (ctx, MIR_ADD, MIR_new_reg_op (ctx, addr_reg),
                                             base_ind_op, disp_op));
      }
    }
    mem_op.u.mem.base = addr_reg;
    mem_op.u.mem.disp = 0;
    mem_op.u.mem.index = 0;
    mem_op.u.mem.scale = 0;
    if (move_p && (nop == 1 || insn->ops[1].mode == MIR_OP_REG)) {
      *op = mem_op;
    } else if (((code == MIR_VA_START && nop == 0)
                || ((code == MIR_VA_ARG || code == MIR_VA_BLOCK_ARG) && nop == 1)
                || (code == MIR_VA_END && nop == 0))
               && mem_op.u.mem.type == MIR_T_UNDEF) {
      *op = MIR_new_reg_op (ctx, addr_reg);
    } else if (!MIR_all_blk_type_p (mem_op.u.mem.type) || !MIR_call_code_p (code)) {
      type = (mem_op.u.mem.type == MIR_T_F || mem_op.u.mem.type == MIR_T_D
                  || mem_op.u.mem.type == MIR_T_LD
                ? mem_op.u.mem.type
                : MIR_T_I64);
      code
        = (type == MIR_T_F ? MIR_FMOV
                           : type == MIR_T_D ? MIR_DMOV : type == MIR_T_LD ? MIR_LDMOV : MIR_MOV);
      new_op = MIR_new_reg_op (ctx, vn_add_val (ctx, func, type, MIR_INSN_BOUND, mem_op, mem_op));
      if (out_p)
        new_insn = MIR_new_insn (ctx, code, mem_op, new_op);
      else
        new_insn = MIR_new_insn (ctx, code, new_op, mem_op);
      insn = insert_op_insn (ctx, out_p, func_item, insn, new_insn);
      *op = new_op;
    }
    break;
  }
  default:
    /* We don't simplify code with hard regs.  */
    mir_assert (FALSE);
  }
  op->value_mode = value_mode;
}

void _MIR_simplify_insn (MIR_context_t ctx, MIR_item_t func_item, MIR_insn_t insn, int keep_ref_p,
                         int mem_float_p) {
  int out_p;
  mir_assert (insn != NULL);
  MIR_insn_code_t code = insn->code;
  size_t i, nops = MIR_insn_nops (ctx, insn);

  for (i = 0; i < nops; i++) {
    MIR_insn_op_mode (ctx, insn, i, &out_p);
    MIR_simplify_op (ctx, func_item, insn, i, out_p, code,
                     (insn->code == MIR_INLINE || insn->code == MIR_CALL) && i == 1 && keep_ref_p,
                     mem_float_p);
  }
}

static void make_one_ret (MIR_context_t ctx, MIR_item_t func_item) {
  size_t i, j;
  MIR_insn_code_t mov_code, ext_code;
  MIR_reg_t ret_reg;
  MIR_op_t reg_op, ret_reg_op;
  MIR_func_t func = func_item->u.func;
  MIR_type_t *res_types = func->res_types;
  MIR_insn_t ret_label, insn, first_ret_insn = NULL;
  VARR (MIR_op_t) * ret_ops;
  int one_last_ret_p;

  one_last_ret_p
    = (VARR_LENGTH (MIR_insn_t, temp_insns) == 1
       && VARR_GET (MIR_insn_t, temp_insns, 0) == DLIST_TAIL (MIR_insn_t, func->insns));
  ret_label = NULL;
  if (one_last_ret_p) {
    first_ret_insn = VARR_GET (MIR_insn_t, temp_insns, 0);
  } else {
    ret_label = MIR_new_label (ctx);
    MIR_append_insn (ctx, func_item, ret_label);
  }
  VARR_CREATE (MIR_op_t, ret_ops, 16);
  for (i = 0; i < func->nres; i++) {
    if (one_last_ret_p) {
      ret_reg_op = first_ret_insn->ops[i];
    } else {
      mov_code
        = (res_types[i] == MIR_T_F
             ? MIR_FMOV
             : res_types[i] == MIR_T_D ? MIR_DMOV : res_types[i] == MIR_T_LD ? MIR_LDMOV : MIR_MOV);
      ret_reg = _MIR_new_temp_reg (ctx, mov_code == MIR_MOV ? MIR_T_I64 : res_types[i], func);
      ret_reg_op = MIR_new_reg_op (ctx, ret_reg);
      VARR_PUSH (MIR_op_t, ret_ops, ret_reg_op);
    }
    switch (res_types[i]) {
    case MIR_T_I8: ext_code = MIR_EXT8; break;
    case MIR_T_U8: ext_code = MIR_UEXT8; break;
    case MIR_T_I16: ext_code = MIR_EXT16; break;
    case MIR_T_U16: ext_code = MIR_UEXT16; break;
    case MIR_T_I32: ext_code = MIR_EXT32; break;
    case MIR_T_U32: ext_code = MIR_UEXT32; break;
    default: ext_code = MIR_INVALID_INSN; break;
    }
    if (ext_code == MIR_INVALID_INSN) continue;
    if (one_last_ret_p)
      MIR_insert_insn_before (ctx, func_item, first_ret_insn,
                              MIR_new_insn (ctx, ext_code, ret_reg_op, ret_reg_op));
    else
      MIR_append_insn (ctx, func_item, MIR_new_insn (ctx, ext_code, ret_reg_op, ret_reg_op));
  }
  if (!one_last_ret_p) {
    MIR_append_insn (ctx, func_item,
                     MIR_new_insn_arr (ctx, MIR_RET, func->nres, VARR_ADDR (MIR_op_t, ret_ops)));
    for (i = 0; i < VARR_LENGTH (MIR_insn_t, temp_insns); i++) {
      insn = VARR_GET (MIR_insn_t, temp_insns, i);
      mir_assert (func->nres == MIR_insn_nops (ctx, insn));
      for (j = 0; j < func->nres; j++) {
        mov_code = (res_types[j] == MIR_T_F
                      ? MIR_FMOV
                      : res_types[j] == MIR_T_D ? MIR_DMOV
                                                : res_types[j] == MIR_T_LD ? MIR_LDMOV : MIR_MOV);
        reg_op = insn->ops[j];
        mir_assert (reg_op.mode == MIR_OP_REG);
        ret_reg_op = VARR_GET (MIR_op_t, ret_ops, j);
        MIR_insert_insn_before (ctx, func_item, insn,
                                MIR_new_insn (ctx, mov_code, ret_reg_op, reg_op));
      }
      MIR_insert_insn_before (ctx, func_item, insn,
                              MIR_new_insn (ctx, MIR_JMP, MIR_new_label_op (ctx, ret_label)));
      MIR_remove_insn (ctx, func_item, insn);
    }
  }
  VARR_DESTROY (MIR_op_t, ret_ops);
}

static void remove_unused_labels (MIR_context_t ctx, MIR_item_t func_item) {
  while (VARR_LENGTH (MIR_insn_t, labels) != 0) {
    MIR_insn_t label = VARR_POP (MIR_insn_t, labels);
    int64_t label_num = label->ops[0].u.i;

    if (label_num < VARR_LENGTH (uint8_t, temp_data) && VARR_GET (uint8_t, temp_data, label_num))
      continue;
    MIR_remove_insn (ctx, func_item, label);
  }
}

MIR_insn_code_t MIR_reverse_branch_code (MIR_insn_code_t code) {
  switch (code) {
  case MIR_BT: return MIR_BF;
  case MIR_BTS: return MIR_BFS;
  case MIR_BF: return MIR_BT;
  case MIR_BFS: return MIR_BTS;
  case MIR_BEQ: return MIR_BNE;
  case MIR_BEQS: return MIR_BNES;
  case MIR_BNE: return MIR_BEQ;
  case MIR_BNES: return MIR_BEQS;
  case MIR_BLT: return MIR_BGE;
  case MIR_BLTS: return MIR_BGES;
  case MIR_UBLT: return MIR_UBGE;
  case MIR_UBLTS: return MIR_UBGES;
  case MIR_BLE: return MIR_BGT;
  case MIR_BLES: return MIR_BGTS;
  case MIR_UBLE: return MIR_UBGT;
  case MIR_UBLES: return MIR_UBGTS;
  case MIR_BGT: return MIR_BLE;
  case MIR_BGTS: return MIR_BLES;
  case MIR_UBGT: return MIR_UBLE;
  case MIR_UBGTS: return MIR_UBLES;
  case MIR_BGE: return MIR_BLT;
  case MIR_BGES: return MIR_BLTS;
  case MIR_UBGE: return MIR_UBLT;
  case MIR_UBGES: return MIR_UBLTS;
  default: return MIR_INSN_BOUND;
  }
}

static MIR_insn_t skip_labels (MIR_label_t label, MIR_label_t stop) {
  for (MIR_insn_t insn = label;; insn = DLIST_NEXT (MIR_insn_t, insn))
    if (insn == NULL || insn->code != MIR_LABEL || insn == stop) return insn;
}

static int64_t natural_alignment (int64_t s) { return s <= 2 ? s : s <= 4 ? 4 : s <= 8 ? 8 : 16; }

static const int MAX_JUMP_CHAIN_LEN = 32;

static int simplify_func (MIR_context_t ctx, MIR_item_t func_item, int mem_float_p) {
  MIR_func_t func = func_item->u.func;
  MIR_insn_t insn, next_insn, next_next_insn, jmp_insn, new_insn;
  MIR_insn_code_t ext_code, rev_code;
  int jmps_num = 0, inline_p = FALSE;

  if (func_item->item_type != MIR_func_item)
    MIR_get_error_func (ctx) (MIR_wrong_param_value_error, "MIR_remove_simplify: wrong func item");
  vn_empty (ctx);
  func = func_item->u.func;
  for (size_t i = 0; i < func->nargs; i++) {
    MIR_var_t var = VARR_GET (MIR_var_t, func->vars, i);

    if (var.type == MIR_T_I64 || var.type == MIR_T_U64 || var.type == MIR_T_F || var.type == MIR_T_D
        || var.type == MIR_T_LD)
      continue;
    switch (var.type) {
    case MIR_T_I8: ext_code = MIR_EXT8; break;
    case MIR_T_U8: ext_code = MIR_UEXT8; break;
    case MIR_T_I16: ext_code = MIR_EXT16; break;
    case MIR_T_U16: ext_code = MIR_UEXT16; break;
    case MIR_T_I32: ext_code = MIR_EXT32; break;
    case MIR_T_U32: ext_code = MIR_UEXT32; break;
    default: ext_code = MIR_INVALID_INSN; break;
    }
    if (ext_code != MIR_INVALID_INSN) {
      MIR_reg_t reg = MIR_reg (ctx, var.name, func);
      MIR_insn_t new_insn
        = MIR_new_insn (ctx, ext_code, MIR_new_reg_op (ctx, reg), MIR_new_reg_op (ctx, reg));

      MIR_prepend_insn (ctx, func_item, new_insn);
    }
  }
  VARR_TRUNC (MIR_insn_t, temp_insns, 0);
  VARR_TRUNC (MIR_insn_t, labels, 0);
  VARR_TRUNC (uint8_t, temp_data, 0);
  for (insn = DLIST_HEAD (MIR_insn_t, func->insns); insn != NULL; insn = next_insn) {
    MIR_insn_code_t code = insn->code;
    MIR_op_t temp_op;

    if ((code == MIR_MOV || code == MIR_FMOV || code == MIR_DMOV || code == MIR_LDMOV)
        && insn->ops[0].mode == MIR_OP_MEM && insn->ops[1].mode == MIR_OP_MEM) {
      temp_op
        = MIR_new_reg_op (ctx, _MIR_new_temp_reg (ctx,
                                                  code == MIR_MOV
                                                    ? MIR_T_I64
                                                    : code == MIR_FMOV
                                                        ? MIR_T_F
                                                        : code == MIR_DMOV ? MIR_T_D : MIR_T_LD,
                                                  func));
      MIR_insert_insn_after (ctx, func_item, insn, MIR_new_insn (ctx, code, insn->ops[0], temp_op));
      insn->ops[0] = temp_op;
    }
    if (code == MIR_RET) VARR_PUSH (MIR_insn_t, temp_insns, insn);
    if (code == MIR_LABEL) VARR_PUSH (MIR_insn_t, labels, insn);
    next_insn = DLIST_NEXT (MIR_insn_t, insn);
    if (code == MIR_ALLOCA
        && (insn->ops[1].mode == MIR_OP_INT || insn->ops[1].mode == MIR_OP_UINT)) {
      /* Consolidate allocas */
      int64_t size, overall_size, align, max_align;

      size = insn->ops[1].u.i;
      overall_size = size <= 0 ? 1 : size;
      max_align = align = natural_alignment (overall_size);
      overall_size = (overall_size + align - 1) / align * align;
      while (next_insn != NULL && next_insn->code == MIR_ALLOCA
             && (next_insn->ops[1].mode == MIR_OP_INT || next_insn->ops[1].mode == MIR_OP_UINT)
             && !MIR_op_eq_p (ctx, insn->ops[0], next_insn->ops[0])) {
        size = next_insn->ops[1].u.i;
        size = size <= 0 ? 1 : size;
        align = natural_alignment (size);
        size = (size + align - 1) / align * align;
        if (max_align < align) {
          max_align = align;
          overall_size = (overall_size + align - 1) / align * align;
        }
        new_insn = MIR_new_insn (ctx, MIR_PTR32 ? MIR_ADDS : MIR_ADD, next_insn->ops[0],
                                 insn->ops[0], MIR_new_int_op (ctx, overall_size));
        overall_size += size;
        MIR_insert_insn_before (ctx, func_item, next_insn, new_insn);
        MIR_remove_insn (ctx, func_item, next_insn);
        next_insn = DLIST_NEXT (MIR_insn_t, new_insn);
      }
      insn->ops[1].u.i = overall_size;
      next_insn = DLIST_NEXT (MIR_insn_t, insn); /* to process the current and new insns */
    }
    if (code == MIR_INLINE || code == MIR_CALL) inline_p = TRUE;
    if ((MIR_int_branch_code_p (code) || code == MIR_JMP) && insn->ops[0].mode == MIR_OP_LABEL
        && skip_labels (next_insn, insn->ops[0].u.label) == insn->ops[0].u.label) {
      /* BR L|JMP L; <labels>L: => <labels>L: Also Remember signaling NAN*/
      MIR_remove_insn (ctx, func_item, insn);
    } else if (((code == MIR_MUL || code == MIR_MULS || code == MIR_DIV || code == MIR_DIVS)
                && insn->ops[2].mode == MIR_OP_INT && insn->ops[2].u.i == 1)
               || ((code == MIR_ADD || code == MIR_ADDS || code == MIR_SUB || code == MIR_SUBS
                    || code == MIR_OR || code == MIR_ORS || code == MIR_XOR || code == MIR_XORS
                    || code == MIR_LSH || code == MIR_LSHS || code == MIR_RSH || code == MIR_RSHS
                    || code == MIR_URSH || code == MIR_URSHS)
                   && insn->ops[2].mode == MIR_OP_INT && insn->ops[2].u.i == 0)) {
      if (!MIR_op_eq_p (ctx, insn->ops[0], insn->ops[1])) {
        next_insn = MIR_new_insn (ctx, MIR_MOV, insn->ops[0], insn->ops[1]);
        MIR_insert_insn_before (ctx, func_item, insn, next_insn);
      }
      MIR_remove_insn (ctx, func_item, insn);
    } else if (MIR_int_branch_code_p (code) && next_insn != NULL && next_insn->code == MIR_JMP
               && insn->ops[0].mode == MIR_OP_LABEL && next_insn->ops[0].mode == MIR_OP_LABEL
               && (skip_labels (next_insn->ops[0].u.label, insn->ops[0].u.label)
                     == insn->ops[0].u.label
                   || skip_labels (insn->ops[0].u.label, next_insn->ops[0].u.label)
                        == next_insn->ops[0].u.label)) {
      /* BR L1;JMP L2; L2:<labels>L1: or L1:<labels>L2: =>  JMP L2*/
      MIR_remove_insn (ctx, func_item, insn);
    } else if ((code == MIR_BT || code == MIR_BTS || code == MIR_BF || code == MIR_BFS)
               && insn->ops[1].mode == MIR_OP_INT
               && (insn->ops[1].u.i == 0 || insn->ops[1].u.i == 1)) {
      if ((code == MIR_BT || code == MIR_BTS) == (insn->ops[1].u.i == 1)) {
        new_insn = MIR_new_insn (ctx, MIR_JMP, insn->ops[0]);
        MIR_insert_insn_before (ctx, func_item, insn, new_insn);
        next_insn = new_insn;
      }
      MIR_remove_insn (ctx, func_item, insn);
      // ??? make imm always second,  what is about mem?
    } else if ((rev_code = MIR_reverse_branch_code (insn->code)) != MIR_INSN_BOUND
               && next_insn != NULL && next_insn->code == MIR_JMP
               && (next_next_insn = DLIST_NEXT (MIR_insn_t, next_insn)) != NULL
               && next_next_insn->code == MIR_LABEL && insn->ops[0].mode == MIR_OP_LABEL
               && skip_labels (next_next_insn, insn->ops[0].u.label) == insn->ops[0].u.label) {
      /* BCond L;JMP L2;<lables>L: => BNCond L2;<labels>L: */
      insn->ops[0] = next_insn->ops[0];
      insn->code = rev_code;
      MIR_remove_insn (ctx, func_item, next_insn);
      next_insn = insn;
    } else if (MIR_branch_code_p (code) && insn->ops[0].mode == MIR_OP_LABEL
               && (jmp_insn = skip_labels (insn->ops[0].u.label, NULL)) != NULL
               && jmp_insn->code == MIR_JMP && ++jmps_num < MAX_JUMP_CHAIN_LEN) {
      /* B L;...;L<labels>:JMP L2 => B L2; ... Also constrain processing to avoid infinite loops
       */
      insn->ops[0] = jmp_insn->ops[0];
      next_insn = insn;
      continue;
    } else {
      if (MIR_branch_code_p (code) || code == MIR_SWITCH) {
        int64_t label_num;
        size_t start_label_nop = 0, bound_label_nop = 1, n;

        if (code == MIR_SWITCH) {
          start_label_nop = 1;
          bound_label_nop = start_label_nop + insn->nops - 1;
        }
        for (n = start_label_nop; n < bound_label_nop; n++) {
          label_num = insn->ops[n].u.label->ops[0].u.i;
          while (label_num >= VARR_LENGTH (uint8_t, temp_data))
            VARR_PUSH (uint8_t, temp_data, FALSE);
          VARR_SET (uint8_t, temp_data, label_num, TRUE);
        }
      }
      _MIR_simplify_insn (ctx, func_item, insn, TRUE, mem_float_p);
    }
    jmps_num = 0;
  }
  make_one_ret (ctx, func_item);
  remove_unused_labels (ctx, func_item);
#if 0
  fprintf (stderr, "+++++ Function after simplification:\n");
  MIR_output_item (ctx, stderr, func_item);
#endif
  return inline_p;
}

static void set_inline_reg_map (MIR_context_t ctx, MIR_reg_t old_reg, MIR_reg_t new_reg) {
  while (VARR_LENGTH (MIR_reg_t, inline_reg_map) <= old_reg)
    VARR_PUSH (MIR_reg_t, inline_reg_map, 0);
  VARR_SET (MIR_reg_t, inline_reg_map, old_reg, new_reg);
}

#ifndef MIR_MAX_INSNS_FOR_INLINE
#define MIR_MAX_INSNS_FOR_INLINE 200
#endif

#ifndef MIR_MAX_INSNS_FOR_CALL_INLINE
#define MIR_MAX_INSNS_FOR_CALL_INLINE 50
#endif

#ifndef MIR_MAX_FUNC_INLINE_GROWTH
#define MIR_MAX_FUNC_INLINE_GROWTH 50
#endif

#ifndef MIR_MAX_CALLER_SIZE_FOR_ANY_GROWTH_INLINE
#define MIR_MAX_CALLER_SIZE_FOR_ANY_GROWTH_INLINE MIR_MAX_INSNS_FOR_INLINE
#endif

static void add_blk_move (MIR_context_t ctx, MIR_item_t func_item, MIR_insn_t before, MIR_op_t dest,
                          MIR_op_t src, size_t src_size) { /* simplified MIR only */
  MIR_func_t func = func_item->u.func;
  size_t blk_size = (src_size + 7) / 8 * 8;
  MIR_insn_t insn;
  MIR_reg_t addr_reg = _MIR_new_temp_reg (ctx, MIR_T_I64, func);
  MIR_op_t addr = MIR_new_reg_op (ctx, addr_reg);
  MIR_op_t disp = MIR_new_reg_op (ctx, _MIR_new_temp_reg (ctx, MIR_T_I64, func));
  MIR_op_t size = MIR_new_reg_op (ctx, _MIR_new_temp_reg (ctx, MIR_T_I64, func));
  MIR_op_t step = MIR_new_reg_op (ctx, _MIR_new_temp_reg (ctx, MIR_T_I64, func));
  MIR_op_t temp = MIR_new_reg_op (ctx, _MIR_new_temp_reg (ctx, MIR_T_I64, func));
  MIR_label_t loop = MIR_new_label (ctx), skip = MIR_new_label (ctx);

  insn = MIR_new_insn (ctx, MIR_MOV, size, MIR_new_int_op (ctx, blk_size));
  MIR_insert_insn_before (ctx, func_item, before, insn);
  insn = MIR_new_insn (ctx, MIR_ALLOCA, dest, size);
  MIR_insert_insn_before (ctx, func_item, before, insn);
  insn = MIR_new_insn (ctx, MIR_MOV, disp, MIR_new_int_op (ctx, 0));
  MIR_insert_insn_before (ctx, func_item, before, insn);
  insn = MIR_new_insn (ctx, MIR_BLT, MIR_new_label_op (ctx, skip), size, disp);
  MIR_insert_insn_before (ctx, func_item, before, insn);
  MIR_insert_insn_before (ctx, func_item, before, loop);
  insn = MIR_new_insn (ctx, MIR_ADD, addr, src, disp);
  MIR_insert_insn_before (ctx, func_item, before, insn);
  insn = MIR_new_insn (ctx, MIR_MOV, temp, MIR_new_mem_op (ctx, MIR_T_I64, 0, addr_reg, 0, 1));
  MIR_insert_insn_before (ctx, func_item, before, insn);
  insn = MIR_new_insn (ctx, MIR_ADD, addr, dest, disp);
  MIR_insert_insn_before (ctx, func_item, before, insn);
  insn = MIR_new_insn (ctx, MIR_MOV, MIR_new_mem_op (ctx, MIR_T_I64, 0, addr_reg, 0, 1), temp);
  MIR_insert_insn_before (ctx, func_item, before, insn);
  insn = MIR_new_insn (ctx, MIR_MOV, step, MIR_new_int_op (ctx, 8));
  MIR_insert_insn_before (ctx, func_item, before, insn);
  insn = MIR_new_insn (ctx, MIR_ADD, disp, disp, step);
  MIR_insert_insn_before (ctx, func_item, before, insn);
  insn = MIR_new_insn (ctx, MIR_BLT, MIR_new_label_op (ctx, loop), disp, size);
  MIR_insert_insn_before (ctx, func_item, before, insn);
  MIR_insert_insn_before (ctx, func_item, before, skip);
}

/* Only simplified code should be inlined because we need already
   extensions and one return.  */
static void process_inlines (MIR_context_t ctx, MIR_item_t func_item) {
  int alloca_p;
  size_t i, actual_nops, nargs, nvars;
  MIR_type_t type, *res_types;
  MIR_var_t var;
  MIR_reg_t ret_reg, old_reg, new_reg, temp_reg;
  MIR_insn_t func_insn, next_func_insn, call, insn, new_insn, ret_insn, ret_label;
  MIR_item_t called_func_item;
  MIR_func_t func, called_func;
  size_t func_insns_num, called_func_insns_num;
  char buff[50];

  mir_assert (func_item->item_type == MIR_func_item);
  vn_empty (ctx);
  func = func_item->u.func;
  func_insns_num = DLIST_LENGTH (MIR_insn_t, func->insns);
  for (func_insn = DLIST_HEAD (MIR_insn_t, func->insns); func_insn != NULL;
       func_insn = next_func_insn) {
    inline_insns_before++;
    inline_insns_after++;
    next_func_insn = DLIST_NEXT (MIR_insn_t, func_insn);
    if (func_insn->code != MIR_INLINE && func_insn->code != MIR_CALL) continue;
    call = func_insn;
    if (call->ops[1].mode != MIR_OP_REF) {
      MIR_simplify_op (ctx, func_item, func_insn, 1, FALSE, func_insn->code, FALSE, TRUE);
      continue;
    }
    called_func_item = call->ops[1].u.ref;
    while (called_func_item != NULL
           && (called_func_item->item_type == MIR_import_item
               || called_func_item->item_type == MIR_export_item
               || called_func_item->item_type == MIR_forward_item))
      called_func_item = called_func_item->ref_def;
    if (called_func_item == NULL || called_func_item->item_type != MIR_func_item
        || func_item == called_func_item) { /* Simplify function operand in the inline insn */
      MIR_simplify_op (ctx, func_item, func_insn, 1, FALSE, func_insn->code, FALSE, TRUE);
      continue;
    }
    called_func = called_func_item->u.func;
    called_func_insns_num = DLIST_LENGTH (MIR_insn_t, called_func->insns);
    if (called_func->vararg_p
        || called_func_insns_num > (func_insn->code == MIR_CALL ? MIR_MAX_INSNS_FOR_CALL_INLINE
                                                                : MIR_MAX_INSNS_FOR_INLINE)
        || (called_func_insns_num > MIR_MAX_FUNC_INLINE_GROWTH * func_insns_num / 100
            && func_insns_num > MIR_MAX_CALLER_SIZE_FOR_ANY_GROWTH_INLINE)) {
      MIR_simplify_op (ctx, func_item, func_insn, 1, FALSE, func_insn->code, FALSE, TRUE);
      continue;
    }
    func_insns_num += called_func_insns_num;
    inlined_calls++;
    res_types = call->ops[0].u.ref->u.proto->res_types;
    ret_label = MIR_new_label (ctx);
    MIR_insert_insn_after (ctx, func_item, call, ret_label);
    func->n_inlines++;
    nargs = called_func->nargs;
    nvars = VARR_LENGTH (MIR_var_t, called_func->vars);
    for (i = 0; i < nvars; i++) {
      VARR_TRUNC (char, temp_string, 0);
      sprintf (buff, ".c%d_", func->n_inlines);
      VARR_PUSH_ARR (char, temp_string, buff, strlen (buff));
      var = VARR_GET (MIR_var_t, called_func->vars, i);
      type = (var.type == MIR_T_F || var.type == MIR_T_D || var.type == MIR_T_LD ? var.type
                                                                                 : MIR_T_I64);
      old_reg = MIR_reg (ctx, var.name, called_func);
      VARR_PUSH_ARR (char, temp_string, var.name, strlen (var.name) + 1);
      new_reg = MIR_new_func_reg (ctx, func, type, VARR_ADDR (char, temp_string));
      set_inline_reg_map (ctx, old_reg, new_reg);
      if (i < nargs && call->nops > i + 2 + called_func->nres) { /* Parameter passing */
        MIR_op_t op = call->ops[i + 2 + called_func->nres];

        mir_assert (!MIR_all_blk_type_p (type) || (op.mode == MIR_OP_MEM && type == MIR_T_I64));
        if (MIR_blk_type_p (var.type)) { /* alloca and block move: */
          add_blk_move (ctx, func_item, ret_label, MIR_new_reg_op (ctx, new_reg),
                        MIR_new_reg_op (ctx, op.u.mem.base), var.size);
        } else {
          if (var.type == MIR_T_RBLK) op = MIR_new_reg_op (ctx, op.u.mem.base);
          new_insn
            = MIR_new_insn (ctx,
                            type == MIR_T_F
                              ? MIR_FMOV
                              : type == MIR_T_D ? MIR_DMOV : type == MIR_T_LD ? MIR_LDMOV : MIR_MOV,
                            MIR_new_reg_op (ctx, new_reg), op);
          MIR_insert_insn_before (ctx, func_item, ret_label, new_insn);
        }
      }
    }
    /* ??? No frame only alloca */
    /* Add new insns: */
    ret_reg = 0;
    alloca_p = FALSE;
    VARR_TRUNC (MIR_insn_t, temp_insns, 0);
    VARR_TRUNC (MIR_insn_t, labels, 0);
    VARR_TRUNC (uint8_t, temp_data, 0);
    for (insn = DLIST_HEAD (MIR_insn_t, called_func->insns); insn != NULL;
         insn = DLIST_NEXT (MIR_insn_t, insn)) {
      inline_insns_after++;
      actual_nops = MIR_insn_nops (ctx, insn);
      new_insn = MIR_copy_insn (ctx, insn);
      /* va insns are possible here as va_list can be passed as arg */
      if (insn->code == MIR_ALLOCA) alloca_p = TRUE;
      for (i = 0; i < actual_nops; i++) switch (new_insn->ops[i].mode) {
        case MIR_OP_REG:
          new_insn->ops[i].u.reg = VARR_GET (MIR_reg_t, inline_reg_map, new_insn->ops[i].u.reg);
          break;
        case MIR_OP_MEM:
          if (insn->ops[i].u.mem.base != 0)
            new_insn->ops[i].u.mem.base
              = VARR_GET (MIR_reg_t, inline_reg_map, new_insn->ops[i].u.mem.base);
          if (insn->ops[i].u.mem.index != 0)
            new_insn->ops[i].u.mem.index
              = VARR_GET (MIR_reg_t, inline_reg_map, new_insn->ops[i].u.mem.index);
          break;
        default: /* do nothing */ break;
        }
      if (new_insn->code != MIR_RET) {
        MIR_insert_insn_before (ctx, func_item, ret_label, new_insn);
        store_labels_for_duplication (ctx, labels, temp_insns, insn, new_insn);
      } else {
        /* should be the last insn after simplification */
        mir_assert (DLIST_NEXT (MIR_insn_t, insn) == NULL && call->ops[0].mode == MIR_OP_REF
                    && call->ops[0].u.ref->item_type == MIR_proto_item);
        mir_assert (called_func->nres == actual_nops);
        ret_insn = new_insn;
        for (i = 0; i < actual_nops; i++) {
          mir_assert (ret_insn->ops[i].mode == MIR_OP_REG);
          ret_reg = ret_insn->ops[i].u.reg;
          new_insn = MIR_new_insn (ctx,
                                   res_types[i] == MIR_T_F
                                     ? MIR_FMOV
                                     : res_types[i] == MIR_T_D
                                         ? MIR_DMOV
                                         : res_types[i] == MIR_T_LD ? MIR_LDMOV : MIR_MOV,
                                   call->ops[i + 2], MIR_new_reg_op (ctx, ret_reg));
          MIR_insert_insn_before (ctx, func_item, ret_label, new_insn);
        }
        free (ret_insn);
      }
    }
    redirect_duplicated_labels (ctx, labels, temp_insns);
    if (alloca_p) {
      temp_reg = _MIR_new_temp_reg (ctx, MIR_T_I64, func);
      new_insn = MIR_new_insn (ctx, MIR_BSTART, MIR_new_reg_op (ctx, temp_reg));
      MIR_insert_insn_after (ctx, func_item, call, new_insn);
      new_insn = MIR_new_insn (ctx, MIR_BEND, MIR_new_reg_op (ctx, temp_reg));
      MIR_insert_insn_before (ctx, func_item, ret_label, new_insn);
    }
    MIR_remove_insn (ctx, func_item, call);
  }
}

/* New Page */

const char *_MIR_uniq_string (MIR_context_t ctx, const char *str) { return get_ctx_str (ctx, str); }

/* The next two function can be called any time relative to
   load/linkage.  You can also call them many times for the same name
   but you should always use the same prototype or/and addr for the
   same proto/func name.  */
MIR_item_t _MIR_builtin_proto (MIR_context_t ctx, MIR_module_t module, const char *name,
                               size_t nres, MIR_type_t *res_types, size_t nargs, ...) {
  size_t i;
  va_list argp;
  MIR_var_t *args = alloca (nargs * sizeof (MIR_var_t));
  MIR_item_t proto_item;
  MIR_module_t saved_module = curr_module;

  va_start (argp, nargs);
  if (mir_mutex_lock (&ctx_mutex)) parallel_error (ctx, "error in mutex lock");
  for (i = 0; i < nargs; i++) {
    args[i].type = va_arg (argp, MIR_type_t);
    args[i].name = va_arg (argp, const char *);
  }
  va_end (argp);
  name = _MIR_uniq_string (ctx, name);
  proto_item = item_tab_find (ctx, name, module);
  if (proto_item != NULL) {
    if (proto_item->item_type == MIR_proto_item && proto_item->u.proto->nres == nres
        && VARR_LENGTH (MIR_var_t, proto_item->u.proto->args) == nargs) {
      for (i = 0; i < nres; i++)
        if (res_types[i] != proto_item->u.proto->res_types[i]) break;
      if (i >= nres) {
        for (i = 0; i < nargs; i++)
          if (args[i].type != VARR_GET (MIR_var_t, proto_item->u.proto->args, i).type) break;
        if (i >= nargs) {
          if (mir_mutex_unlock (&ctx_mutex)) parallel_error (ctx, "error in mutex unlock");
          return proto_item;
        }
      }
    }
    MIR_get_error_func (ctx) (MIR_repeated_decl_error,
                              "_MIR_builtin_proto: proto item %s was already defined differently",
                              name);
  }
  saved_module = curr_module;
  curr_module = module;
  proto_item = MIR_new_proto_arr (ctx, name, nres, res_types, nargs, args);
  DLIST_REMOVE (MIR_item_t, curr_module->items, proto_item);
  DLIST_PREPEND (MIR_item_t, curr_module->items, proto_item); /* make it first in the list */
  curr_module = saved_module;
  if (mir_mutex_unlock (&ctx_mutex)) parallel_error (ctx, "error in mutex unlock");
  return proto_item;
}

MIR_item_t _MIR_builtin_func (MIR_context_t ctx, MIR_module_t module, const char *name,
                              void *addr) {
  MIR_item_t item, ref_item;
  MIR_module_t saved_module = curr_module;

  if (mir_mutex_lock (&ctx_mutex)) parallel_error (ctx, "error in mutex lock");
  name = _MIR_uniq_string (ctx, name);
  if ((ref_item = item_tab_find (ctx, name, &environment_module)) != NULL) {
    if (ref_item->item_type != MIR_import_item || ref_item->addr != addr)
      MIR_get_error_func (ctx) (MIR_repeated_decl_error,
                                "_MIR_builtin_func: func %s has already another address", name);
  } else {
    curr_module = &environment_module;
    /* Use import for builtin func: */
    item = new_export_import_forward (ctx, name, MIR_import_item, "import", TRUE);
    HTAB_DO (MIR_item_t, module_item_tab, item, HTAB_INSERT, ref_item);
    mir_assert (item == ref_item);
    DLIST_APPEND (MIR_item_t, environment_module.items, item);
    ref_item->addr = addr;
    curr_module = saved_module;
  }
  if ((item = item_tab_find (ctx, name, module)) != NULL) {
    if (item->item_type != MIR_import_item || item->addr != addr || item->ref_def != ref_item)
      MIR_get_error_func (
        ctx) (MIR_repeated_decl_error,
              "_MIR_builtin_func: func name %s was already defined differently in the "
              "module",
              name);
  } else {
    curr_module = module;
    item = new_export_import_forward (ctx, name, MIR_import_item, "import", FALSE);
    DLIST_REMOVE (MIR_item_t, curr_module->items, item);
    DLIST_PREPEND (MIR_item_t, curr_module->items, item); /* make it first in the list */
    item->addr = ref_item->addr;
    item->ref_def = ref_item;
    curr_module = saved_module;
  }
  if (mir_mutex_unlock (&ctx_mutex)) parallel_error (ctx, "error in mutex unlock");
  return item;
}

/* New Page */
/* This page is for dealing with generated machine code */

#ifndef _WIN32
#include <sys/mman.h>
#include <unistd.h>

#define PROT_WRITE_EXEC (PROT_WRITE | PROT_EXEC)
#define PROT_READ_EXEC (PROT_READ | PROT_EXEC)
#define mem_protect mprotect
#define mem_unmap munmap

static void *mem_map (size_t len) {
  return mmap (NULL, len, PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}

static size_t mem_page_size () { return sysconf (_SC_PAGE_SIZE); }
#else
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#define PROT_WRITE_EXEC PAGE_EXECUTE_READWRITE
#define PROT_READ_EXEC PAGE_EXECUTE_READ
#define MAP_FAILED NULL

static int mem_protect (void *addr, size_t len, int prot) {
  DWORD old_prot = 0;
  return VirtualProtect (addr, len, prot, &old_prot) ? 0 : -1;
}

static int mem_unmap (void *addr, size_t len) {
  return VirtualFree (addr, len, MEM_RELEASE) ? 0 : -1;
}

static void *mem_map (size_t len) { return VirtualAlloc (NULL, len, MEM_COMMIT, PAGE_EXECUTE); }

static size_t mem_page_size () {
  SYSTEM_INFO sysInfo;
  GetSystemInfo (&sysInfo);
  return sysInfo.dwPageSize;
}
#endif

struct code_holder {
  uint8_t *start, *free, *bound;
};

typedef struct code_holder code_holder_t;

DEF_VARR (code_holder_t);

struct machine_code_ctx {
#if MIR_PARALLEL_GEN
  mir_mutex_t code_mutex;
#endif
  VARR (code_holder_t) * code_holders;
  size_t page_size;
};

#define code_mutex ctx->machine_code_ctx->code_mutex
#define code_holders ctx->machine_code_ctx->code_holders
#define page_size ctx->machine_code_ctx->page_size

static code_holder_t *get_last_code_holder (MIR_context_t ctx, size_t size) {
  uint8_t *mem;
  size_t len, npages;
  code_holder_t ch, *ch_ptr;

  if ((len = VARR_LENGTH (code_holder_t, code_holders)) > 0) {
    ch_ptr = VARR_ADDR (code_holder_t, code_holders) + len - 1;
    ch_ptr->free = (uint8_t *) ((uint64_t) (ch_ptr->free + 15) / 16 * 16); /* align */
    if (ch_ptr->free + size <= ch_ptr->bound) return ch_ptr;
  }
  npages = (size + page_size) / page_size;
  len = page_size * npages;
  mem = (uint8_t *) mem_map (len);
  if (mem == MAP_FAILED) return NULL;
  ch.start = mem;
  ch.free = mem;
  ch.bound = mem + len;
  VARR_PUSH (code_holder_t, code_holders, ch);
  len = VARR_LENGTH (code_holder_t, code_holders);
  return VARR_ADDR (code_holder_t, code_holders) + len - 1;
}

#ifndef __MIRC__
void _MIR_flush_code_cache (void *start, void *bound) {
#ifdef __GNUC__
  __builtin___clear_cache (start, bound);
#endif
}
#endif

static uint8_t *add_code (MIR_context_t ctx, code_holder_t *ch_ptr, const uint8_t *code,
                          size_t code_len) {
  uint8_t *mem = ch_ptr->free;

  ch_ptr->free += code_len;
  mir_assert (ch_ptr->free <= ch_ptr->bound);
  mem_protect (ch_ptr->start, ch_ptr->bound - ch_ptr->start, PROT_WRITE_EXEC);
  memcpy (mem, code, code_len);
  mem_protect (ch_ptr->start, ch_ptr->bound - ch_ptr->start, PROT_READ_EXEC);
  _MIR_flush_code_cache (mem, ch_ptr->free);
  return mem;
}

uint8_t *_MIR_publish_code (MIR_context_t ctx, const uint8_t *code,
                            size_t code_len) { /* thread safe */
  code_holder_t *ch_ptr;
  uint8_t *res = NULL;

  if (mir_mutex_lock (&code_mutex)) parallel_error (ctx, "error in mutex lock");
  if ((ch_ptr = get_last_code_holder (ctx, code_len)) != NULL)
    res = add_code (ctx, ch_ptr, code, code_len);
  if (mir_mutex_unlock (&code_mutex)) parallel_error (ctx, "error in mutex unlock");
  return res;
}

uint8_t *_MIR_get_new_code_addr (MIR_context_t ctx, size_t size) {
  code_holder_t *ch_ptr = get_last_code_holder (ctx, size);

  return ch_ptr == NULL ? NULL : ch_ptr->free;
}

uint8_t *_MIR_publish_code_by_addr (MIR_context_t ctx, void *addr, const uint8_t *code,
                                    size_t code_len) {
  code_holder_t *ch_ptr = get_last_code_holder (ctx, 0);
  uint8_t *res = NULL;

  if (ch_ptr != NULL && ch_ptr->free == addr && ch_ptr->free + code_len < ch_ptr->bound)
    res = add_code (ctx, ch_ptr, code, code_len);
  return res;
}

void _MIR_change_code (MIR_context_t ctx, uint8_t *addr, const uint8_t *code,
                       size_t code_len) { /* thread safe */
  size_t len, start;

  start = (size_t) addr / page_size * page_size;
  len = (size_t) addr + code_len - start;
  if (mir_mutex_lock (&code_mutex)) parallel_error (ctx, "error in mutex lock");
  mem_protect ((uint8_t *) start, len, PROT_WRITE_EXEC);
  memcpy (addr, code, code_len);
  mem_protect ((uint8_t *) start, len, PROT_READ_EXEC);
  _MIR_flush_code_cache (addr, addr + code_len);
  if (mir_mutex_unlock (&code_mutex)) parallel_error (ctx, "error in mutex unlock");
}

void _MIR_update_code_arr (MIR_context_t ctx, uint8_t *base, size_t nloc,
                           const MIR_code_reloc_t *relocs) { /* thread safe */
  size_t i, len, start, max_offset = 0;

  mir_assert (relocs != NULL);
  for (i = 0; i < nloc; i++)
    if (max_offset < relocs[i].offset) max_offset = relocs[i].offset;
  start = (size_t) base / page_size * page_size;
  len = (size_t) base + max_offset + sizeof (void *) - start;
  if (mir_mutex_lock (&code_mutex)) parallel_error (ctx, "error in mutex lock");
  mem_protect ((uint8_t *) start, len, PROT_WRITE_EXEC);
  for (i = 0; i < nloc; i++) memcpy (base + relocs[i].offset, &relocs[i].value, sizeof (void *));
  mem_protect ((uint8_t *) start, len, PROT_READ_EXEC);
  _MIR_flush_code_cache (base, base + max_offset + sizeof (void *));
  if (mir_mutex_unlock (&code_mutex)) parallel_error (ctx, "error in mutex unlock");
}

void _MIR_update_code (MIR_context_t ctx, uint8_t *base, size_t nloc, ...) { /* thread safe */
  size_t start, len, offset, max_offset = 0;
  void *value;
  va_list args;

  va_start (args, nloc);
  for (size_t i = 0; i < nloc; i++) {
    offset = va_arg (args, size_t);
    value = va_arg (args, void *);
    if (max_offset < offset) max_offset = offset;
  }
  va_end (args);
  start = (size_t) base / page_size * page_size;
  len = (size_t) base + max_offset + sizeof (void *) - start;
  va_start (args, nloc);
  if (mir_mutex_lock (&code_mutex)) parallel_error (ctx, "error in mutex lock");
  mem_protect ((uint8_t *) start, len, PROT_WRITE_EXEC);
  for (size_t i = 0; i < nloc; i++) {
    offset = va_arg (args, size_t);
    value = va_arg (args, void *);
    memcpy (base + offset, &value, sizeof (void *));
  }
  mem_protect ((uint8_t *) start, len, PROT_READ_EXEC);
  _MIR_flush_code_cache (base, base + max_offset + sizeof (void *));
  if (mir_mutex_unlock (&code_mutex)) parallel_error (ctx, "error in mutex unlock");
  va_end (args);
}

static void code_init (MIR_context_t ctx) {
  if ((ctx->machine_code_ctx = malloc (sizeof (struct machine_code_ctx))) == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for ctx");
  page_size = mem_page_size ();
  VARR_CREATE (code_holder_t, code_holders, 128);
  if (mir_mutex_init (&code_mutex, NULL)) parallel_error (ctx, "error in mutex init");
}

static void code_finish (MIR_context_t ctx) {
  if (mir_mutex_destroy (&code_mutex)) parallel_error (ctx, "error in mutex destroy");
  while (VARR_LENGTH (code_holder_t, code_holders) != 0) {
    code_holder_t ch = VARR_POP (code_holder_t, code_holders);
    mem_unmap (ch.start, ch.bound - ch.start);
  }
  VARR_DESTROY (code_holder_t, code_holders);
  free (ctx->machine_code_ctx);
  ctx->machine_code_ctx = NULL;
}

/* New Page */

#if !MIR_NO_IO

/* Input/output of binary MIR.  Major goal of binary MIR is fast
   reading, not compression ratio.  Text MIR major CPU time consumer
   is a scanner.  Mostly in reading binary MIR we skip the scanner
   part by using tokens.  Each token starts with a tag which describes
   subsequent optional bytes.  */

#define TAG_EL(t) TAG_##t
#define REP_SEP ,
typedef enum {
  TAG_EL (U0),
  REP8 (TAG_EL, U1, U2, U3, U4, U5, U6, U7, U8),
  REP8 (TAG_EL, I1, I2, I3, I4, I5, I6, I7, I8),
  REP3 (TAG_EL, F, D, LD),                   /* 4, 8, 16 bytes for floating point numbers */
  REP4 (TAG_EL, REG1, REG2, REG3, REG4),     /* Reg string number in 1, 2, 3, 4 bytes */
  REP4 (TAG_EL, NAME1, NAME2, NAME3, NAME4), /* Name string number in 1, 2, 3, 4 bytes */
  REP4 (TAG_EL, STR1, STR2, STR3, STR4),     /* String number in 1, 2, 3, 4 bytes */
  REP4 (TAG_EL, LAB1, LAB2, LAB3, LAB4),     /* Label number in 1, 2, 3, 4 bytes */
  /* Tags for memory operands.  The memory address parts are the subsequent tokens */
  REP4 (TAG_EL, MEM_DISP, MEM_BASE, MEM_INDEX, MEM_DISP_BASE),
  REP3 (TAG_EL, MEM_DISP_INDEX, MEM_BASE_INDEX, MEM_DISP_BASE_INDEX),
  /* MIR types. The same order as MIR types: */
  REP8 (TAG_EL, TI8, TU8, TI16, TU16, TI32, TU32, TI64, TU64),
  REP8 (TAG_EL, TF, TD, TP, TV, TBLOCK, TBLOCK2, TBLOCK3, TBLOCK4),
  REP3 (TAG_EL, TBLOCK5, TRBLOCK, EOI),
  TAG_EL (EOFILE), /* end of insn with variable number operands (e.g. a call) or end of file */
  /* unsigned integer 0..127 is kept in one byte.  The most significant bit of the byte is 1: */
  U0_MASK = 0x7f,
  U0_FLAG = 0x80,
} bin_tag_t;
#undef REP_SEP

/* MIR binary format:

   VERSION
   NSTR
   (string)*
   ( ((label)* (insn code) (operand)* | STRN=(func|local|import|export|forward|<data>) ...) EOI?
   )* EOF

   where
   o VERSION and NSTR are unsigned tokens
   o insn code is unsigned token
   o string is string number tokens
   o operand is unsigned, signed, float, double, string, label, memory tokens
   o EOI, EOF - tokens for end of insn (optional for most insns) and end of file
*/

static const int CURR_BIN_VERSION = 1;

DEF_VARR (MIR_str_t);
DEF_VARR (uint64_t);
DEF_VARR (MIR_label_t);

struct io_ctx {
  FILE *io_file;
  int (*io_writer) (MIR_context_t, uint8_t);
  int (*io_reader) (MIR_context_t);
  struct reduce_data *io_reduce_data;
  VARR (MIR_var_t) * proto_vars;
  VARR (MIR_type_t) * proto_types;
  VARR (MIR_op_t) * read_insn_ops;
  VARR (string_t) * output_strings;
  HTAB (string_t) * output_string_tab;
  VARR (MIR_str_t) * bin_strings;
  VARR (uint64_t) * insn_label_string_nums;
  VARR (MIR_label_t) * func_labels;
  size_t output_insns_len, output_labs_len;
  size_t output_regs_len, output_mem_len, output_int_len, output_float_len;
};

#define io_file ctx->io_ctx->io_file
#define io_writer ctx->io_ctx->io_writer
#define io_reader ctx->io_ctx->io_reader
#define io_reduce_data ctx->io_ctx->io_reduce_data
#define proto_vars ctx->io_ctx->proto_vars
#define proto_types ctx->io_ctx->proto_types
#define read_insn_ops ctx->io_ctx->read_insn_ops
#define output_strings ctx->io_ctx->output_strings
#define output_string_tab ctx->io_ctx->output_string_tab
#define bin_strings ctx->io_ctx->bin_strings
#define insn_label_string_nums ctx->io_ctx->insn_label_string_nums
#define func_labels ctx->io_ctx->func_labels
#define output_insns_len ctx->io_ctx->output_insns_len
#define output_labs_len ctx->io_ctx->output_labs_len
#define output_regs_len ctx->io_ctx->output_regs_len
#define output_mem_len ctx->io_ctx->output_mem_len
#define output_int_len ctx->io_ctx->output_int_len
#define output_float_len ctx->io_ctx->output_float_len

typedef reduce_writer_t writer_func_t;

static size_t put_byte (MIR_context_t ctx, writer_func_t writer, int ch) {
  if (writer == NULL) return 0;
#ifdef MIR_NO_BIN_COMPRESSION
  io_writer (ctx, ch);
#else
  reduce_encode_put (io_reduce_data, ch);
#endif
  return 1;
}

static size_t uint_length (uint64_t u) {
  size_t n;

  if (u <= 127) return 0;
  for (n = 0; u != 0; n++) u >>= CHAR_BIT;
  return n;
}

static size_t put_uint (MIR_context_t ctx, writer_func_t writer, uint64_t u, int nb) {
  if (writer == NULL) return 0;
  for (int n = 0; n < nb; n++) {
    put_byte (ctx, writer, u & 0xff);
    u >>= CHAR_BIT;
  }
  return nb;
}

static size_t int_length (int64_t i) {
  uint64_t u = i;
  size_t n = 0;

  for (n = 0; u != 0; n++) u >>= CHAR_BIT;
  return n == 0 ? 1 : n;
}

static size_t put_int (MIR_context_t ctx, writer_func_t writer, int64_t i, int nb) {
  return put_uint (ctx, writer, (uint64_t) i, nb);
}

static size_t put_float (MIR_context_t ctx, writer_func_t writer, float fl) {
  union {
    uint32_t u;
    float f;
  } u;

  if (writer == NULL) return 0;
  u.f = fl;
  return put_uint (ctx, writer, u.u, sizeof (uint32_t));
}

static size_t put_double (MIR_context_t ctx, writer_func_t writer, double d) {
  union {
    uint64_t u;
    double d;
  } u;

  if (writer == NULL) return 0;
  u.d = d;
  return put_uint (ctx, writer, u.u, sizeof (uint64_t));
}

static size_t put_ldouble (MIR_context_t ctx, writer_func_t writer, long double ld) {
  union {
    uint64_t u[2];
    long double ld;
  } u;
  size_t len;

  if (writer == NULL) return 0;
  u.ld = ld;
  len = put_uint (ctx, writer, u.u[0], sizeof (uint64_t));
  return put_uint (ctx, writer, u.u[1], sizeof (uint64_t)) + len;
}

/* Write binary MIR */

static size_t write_int (MIR_context_t ctx, writer_func_t writer, int64_t i) {
  size_t nb, len;

  if (writer == NULL) return 0;
  nb = int_length (i);
  assert (nb > 0);
  put_byte (ctx, writer, TAG_I1 + nb - 1);
  len = put_int (ctx, writer, i, nb) + 1;
  output_int_len += len;
  return len;
}

static size_t write_uint (MIR_context_t ctx, writer_func_t writer, uint64_t u) {
  size_t nb, len;

  if (writer == NULL) return 0;
  if ((nb = uint_length (u)) == 0) {
    put_byte (ctx, writer, 0x80 | u);
    return 1;
  }
  put_byte (ctx, writer, TAG_U1 + nb - 1);
  len = put_uint (ctx, writer, u, nb) + 1;
  output_int_len += len;
  return len;
}

static size_t write_float (MIR_context_t ctx, writer_func_t writer, float fl) {
  size_t len;

  if (writer == NULL) return 0;
  put_byte (ctx, writer, TAG_F);
  len = put_float (ctx, writer, fl) + 1;
  output_float_len += len;
  return len;
}

static size_t write_double (MIR_context_t ctx, writer_func_t writer, double d) {
  size_t len;

  if (writer == NULL) return 0;
  put_byte (ctx, writer, TAG_D);
  len = put_double (ctx, writer, d) + 1;
  output_float_len += len;
  return len;
}

static size_t write_ldouble (MIR_context_t ctx, writer_func_t writer, long double ld) {
  size_t len;

  if (writer == NULL) return 0;
  put_byte (ctx, writer, TAG_LD);
  len = put_ldouble (ctx, writer, ld) + 1;
  output_int_len += len;
  return len;
}

static size_t write_str_tag (MIR_context_t ctx, writer_func_t writer, MIR_str_t str,
                             bin_tag_t start_tag) {
  size_t nb;
  int ok_p;
  string_t string;

  if (writer == NULL) {
    string_store (ctx, &output_strings, &output_string_tab, str);
    return 0;
  }
  ok_p = string_find (&output_strings, &output_string_tab, str, &string);
  mir_assert (ok_p && string.num >= 1);
  nb = uint_length (string.num - 1);
  mir_assert (nb <= 4);
  if (nb == 0) nb = 1;
  put_byte (ctx, writer, start_tag + nb - 1);
  return put_uint (ctx, writer, string.num - 1, nb) + 1;
}

static size_t write_str (MIR_context_t ctx, writer_func_t writer, MIR_str_t str) {
  return write_str_tag (ctx, writer, str, TAG_STR1);
}
static size_t write_name (MIR_context_t ctx, writer_func_t writer, const char *name) {
  return write_str_tag (ctx, writer, (MIR_str_t){strlen (name) + 1, name}, TAG_NAME1);
}

static size_t write_reg (MIR_context_t ctx, writer_func_t writer, const char *reg_name) {
  size_t len = write_str_tag (ctx, writer, (MIR_str_t){strlen (reg_name) + 1, reg_name}, TAG_REG1);

  output_regs_len += len;
  return len;
}

static size_t write_type (MIR_context_t ctx, writer_func_t writer, MIR_type_t t) {
  return put_byte (ctx, writer, TAG_TI8 + (t - MIR_T_I8));
}

static size_t write_lab (MIR_context_t ctx, writer_func_t writer, MIR_label_t lab) {
  size_t nb, len;
  uint64_t lab_num;

  if (writer == NULL) return 0;
  lab_num = lab->ops[0].u.u;
  nb = uint_length (lab_num);
  mir_assert (nb <= 4);
  if (nb == 0) nb = 1;
  put_byte (ctx, writer, TAG_LAB1 + nb - 1);
  len = put_uint (ctx, writer, lab_num, nb) + 1;
  output_labs_len += len;
  return len;
}

static size_t write_op (MIR_context_t ctx, writer_func_t writer, MIR_func_t func, MIR_op_t op) {
  switch (op.mode) {
  case MIR_OP_REG: return write_reg (ctx, writer, MIR_reg_name (ctx, op.u.reg, func));
  case MIR_OP_INT: return write_int (ctx, writer, op.u.i);
  case MIR_OP_UINT: return write_uint (ctx, writer, op.u.u);
  case MIR_OP_FLOAT: return write_float (ctx, writer, op.u.f);
  case MIR_OP_DOUBLE: return write_double (ctx, writer, op.u.d);
  case MIR_OP_LDOUBLE: return write_ldouble (ctx, writer, op.u.ld);
  case MIR_OP_MEM: {
    bin_tag_t tag;
    size_t len;

    if (op.u.mem.disp != 0) {
      if (op.u.mem.base != 0)
        tag = op.u.mem.index != 0 ? TAG_MEM_DISP_BASE_INDEX : TAG_MEM_DISP_BASE;
      else
        tag = op.u.mem.index != 0 ? TAG_MEM_DISP_INDEX : TAG_MEM_DISP;
    } else if (op.u.mem.base != 0) {
      tag = op.u.mem.index != 0 ? TAG_MEM_BASE_INDEX : TAG_MEM_BASE;
    } else if (op.u.mem.index != 0) {
      tag = TAG_MEM_INDEX;
    } else {
      tag = TAG_MEM_DISP;
    }
    put_byte (ctx, writer, tag);
    len = write_type (ctx, writer, op.u.mem.type) + 1;
    if (op.u.mem.disp != 0 || (op.u.mem.base == 0 && op.u.mem.index == 0))
      write_int (ctx, writer, op.u.mem.disp);
    if (op.u.mem.base != 0) write_reg (ctx, writer, MIR_reg_name (ctx, op.u.mem.base, func));
    if (op.u.mem.index != 0) {
      len += write_reg (ctx, writer, MIR_reg_name (ctx, op.u.mem.index, func));
      len += write_uint (ctx, writer, op.u.mem.scale);
    }
    output_mem_len += len;
    return len;
  }
  case MIR_OP_REF: return write_name (ctx, writer, MIR_item_name (ctx, op.u.ref));
  case MIR_OP_STR: return write_str (ctx, writer, op.u.str);
  case MIR_OP_LABEL: return write_lab (ctx, writer, op.u.label);
  default: mir_assert (FALSE); return 0;
  }
}

static size_t write_insn (MIR_context_t ctx, writer_func_t writer, MIR_func_t func,
                          MIR_insn_t insn) {
  size_t i, nops;
  MIR_insn_code_t code = insn->code;
  size_t len;

  if (code == MIR_UNSPEC || code == MIR_PHI)
    MIR_get_error_func (ctx) (MIR_binary_io_error,
                              "UNSPEC or PHI is not portable and can not be output");
  if (code == MIR_LABEL) return write_lab (ctx, writer, insn);
  nops = MIR_insn_nops (ctx, insn);
  len = write_uint (ctx, writer, code);
  for (i = 0; i < nops; i++) len += write_op (ctx, writer, func, insn->ops[i]);
  if (insn_descs[code].op_modes[0] == MIR_OP_BOUND) {
    /* first operand mode is undefined if it is a variable operand insn */
    mir_assert (MIR_call_code_p (code) || code == MIR_RET || code == MIR_SWITCH);
    put_byte (ctx, writer, TAG_EOI);
    len++;
  }
  output_insns_len += len;
  return len;
}

static size_t write_item (MIR_context_t ctx, writer_func_t writer, MIR_item_t item) {
  MIR_insn_t insn;
  MIR_func_t func;
  MIR_proto_t proto;
  MIR_var_t var;
  size_t i, nlocals, len = 0;

  if (item->item_type == MIR_import_item) {
    len += write_name (ctx, writer, "import");
    len += write_name (ctx, writer, item->u.import_id);
    return len;
  }
  if (item->item_type == MIR_export_item) {
    len += write_name (ctx, writer, "export");
    len += write_name (ctx, writer, item->u.export_id);
    return len;
  }
  if (item->item_type == MIR_forward_item) {
    len += write_name (ctx, writer, "forward");
    len += write_name (ctx, writer, item->u.forward_id);
    return len;
  }
  if (item->item_type == MIR_bss_item) {
    if (item->u.bss->name == NULL) {
      len += write_name (ctx, writer, "bss");
    } else {
      len += write_name (ctx, writer, "nbss");
      len += write_name (ctx, writer, item->u.bss->name);
    }
    len += write_uint (ctx, writer, item->u.bss->len);
    return len;
  }
  if (item->item_type == MIR_ref_data_item) {
    if (item->u.ref_data->name == NULL) {
      len += write_name (ctx, writer, "ref");
    } else {
      len += write_name (ctx, writer, "nref");
      len += write_name (ctx, writer, item->u.ref_data->name);
    }
    len += write_name (ctx, writer, MIR_item_name (ctx, item->u.ref_data->ref_item));
    len += write_int (ctx, writer, item->u.ref_data->disp);
    return len;
  }
  if (item->item_type == MIR_expr_data_item) {
    if (item->u.expr_data->name == NULL) {
      len += write_name (ctx, writer, "expr");
    } else {
      len += write_name (ctx, writer, "nexpr");
      len += write_name (ctx, writer, item->u.expr_data->name);
    }
    len += write_name (ctx, writer, MIR_item_name (ctx, item->u.expr_data->expr_item));
    return len;
  }
  if (item->item_type == MIR_data_item) {
    MIR_data_t data = item->u.data;

    if (data->name == NULL) {
      len += write_name (ctx, writer, "data");
    } else {
      len += write_name (ctx, writer, "ndata");
      len += write_name (ctx, writer, data->name);
    }
    write_type (ctx, writer, data->el_type);
    for (i = 0; i < data->nel; i++) switch (data->el_type) {
      case MIR_T_I8: len += write_int (ctx, writer, ((int8_t *) data->u.els)[i]); break;
      case MIR_T_U8: len += write_uint (ctx, writer, ((uint8_t *) data->u.els)[i]); break;
      case MIR_T_I16: len += write_int (ctx, writer, ((int16_t *) data->u.els)[i]); break;
      case MIR_T_U16: len += write_uint (ctx, writer, ((uint16_t *) data->u.els)[i]); break;
      case MIR_T_I32: len += write_int (ctx, writer, ((int32_t *) data->u.els)[i]); break;
      case MIR_T_U32: len += write_uint (ctx, writer, ((uint32_t *) data->u.els)[i]); break;
      case MIR_T_I64: len += write_int (ctx, writer, ((int64_t *) data->u.els)[i]); break;
      case MIR_T_U64: len += write_uint (ctx, writer, ((uint64_t *) data->u.els)[i]); break;
      case MIR_T_F: len += write_float (ctx, writer, ((float *) data->u.els)[i]); break;
      case MIR_T_D: len += write_double (ctx, writer, ((double *) data->u.els)[i]); break;
      case MIR_T_LD:
        len += write_ldouble (ctx, writer, ((long double *) data->u.els)[i]);
        break;
        /* only ptr as ref ??? */
      case MIR_T_P: len += write_uint (ctx, writer, ((uintptr_t *) data->u.els)[i]); break;
      default: mir_assert (FALSE);
      }
    len += put_byte (ctx, writer, TAG_EOI);
    return len;
  }
  if (item->item_type == MIR_proto_item) {
    proto = item->u.proto;
    len += write_name (ctx, writer, "proto");
    len += write_name (ctx, writer, proto->name);
    len += write_uint (ctx, writer, proto->vararg_p != 0);
    len += write_uint (ctx, writer, proto->nres);
    for (i = 0; i < proto->nres; i++) write_type (ctx, writer, proto->res_types[i]);
    for (i = 0; i < VARR_LENGTH (MIR_var_t, proto->args); i++) {
      var = VARR_GET (MIR_var_t, proto->args, i);
      len += write_type (ctx, writer, var.type);
      len += write_name (ctx, writer, var.name);
      if (MIR_all_blk_type_p (var.type)) len += write_uint (ctx, writer, var.size);
    }
    len += put_byte (ctx, writer, TAG_EOI);
    return len;
  }
  func = item->u.func;
  len += write_name (ctx, writer, "func");
  len += write_name (ctx, writer, func->name);
  len += write_uint (ctx, writer, func->vararg_p != 0);
  len += write_uint (ctx, writer, func->nres);
  for (i = 0; i < func->nres; i++) len += write_type (ctx, writer, func->res_types[i]);
  for (i = 0; i < func->nargs; i++) {
    var = VARR_GET (MIR_var_t, func->vars, i);
    len += write_type (ctx, writer, var.type);
    len += write_name (ctx, writer, var.name);
    if (MIR_all_blk_type_p (var.type)) len += write_uint (ctx, writer, var.size);
  }
  len += put_byte (ctx, writer, TAG_EOI);
  nlocals = VARR_LENGTH (MIR_var_t, func->vars) - func->nargs;
  if (nlocals != 0) {
    len += write_name (ctx, writer, "local");
    for (i = 0; i < nlocals; i++) {
      var = VARR_GET (MIR_var_t, func->vars, i + func->nargs);
      len += write_type (ctx, writer, var.type);
      len += write_name (ctx, writer, var.name);
    }
    len += put_byte (ctx, writer, TAG_EOI);
  }
  for (insn = DLIST_HEAD (MIR_insn_t, func->insns); insn != NULL;
       insn = DLIST_NEXT (MIR_insn_t, insn))
    len += write_insn (ctx, writer, func, insn);
  len += write_name (ctx, writer, "endfunc");
  return len;
}

static size_t write_module (MIR_context_t ctx, writer_func_t writer, MIR_module_t module) {
  size_t len = write_name (ctx, writer, "module");

  len += write_name (ctx, writer, module->name);
  for (MIR_item_t item = DLIST_HEAD (MIR_item_t, module->items); item != NULL;
       item = DLIST_NEXT (MIR_item_t, item))
    len += write_item (ctx, writer, item);
  len += write_name (ctx, writer, "endmodule");
  return len;
}

static size_t write_modules (MIR_context_t ctx, writer_func_t writer, MIR_module_t module) {
  size_t len = 0;

  for (MIR_module_t m = DLIST_HEAD (MIR_module_t, all_modules); m != NULL;
       m = DLIST_NEXT (MIR_module_t, m))
    if (module == NULL || m == module) len += write_module (ctx, writer, m);
  return len;
}

static size_t reduce_writer (const void *start, size_t len, void *aux_data) {
  MIR_context_t ctx = aux_data;
  size_t i, n = 0;

  for (i = n = 0; i < len; i++, n++)
    if (io_writer (ctx, ((uint8_t *) start)[i]) == EOF) break;
  return n;
}

void MIR_write_module_with_func (MIR_context_t ctx, int (*const writer) (MIR_context_t, uint8_t),
                                 MIR_module_t module) {
  size_t len, str_len;

  io_writer = writer;
#ifndef MIR_NO_BIN_COMPRESSION
  if ((io_reduce_data = reduce_encode_start (reduce_writer, ctx)) == NULL)
    MIR_get_error_func (ctx) (MIR_binary_io_error, "can not alloc data for MIR binary compression");
#endif
  output_insns_len = output_labs_len = 0;
  output_regs_len = output_mem_len = output_int_len = output_float_len = 0;
  string_init (&output_strings, &output_string_tab);
  write_modules (ctx, NULL, module); /* store strings */
  len = write_uint (ctx, reduce_writer, CURR_BIN_VERSION);
  str_len = write_uint (ctx, reduce_writer, VARR_LENGTH (string_t, output_strings) - 1);
  for (size_t i = 1; i < VARR_LENGTH (string_t, output_strings); i++) { /* output strings */
    MIR_str_t str = VARR_GET (string_t, output_strings, i).str;

    str_len += write_uint (ctx, reduce_writer, str.len);
    for (size_t j = 0; j < str.len; j++) {
      put_byte (ctx, reduce_writer, str.s[j]);
      str_len++;
    }
  }
  len += write_modules (ctx, reduce_writer, module) + str_len;
#if 0
  fprintf (stderr,
           "Overall output length = %lu.  Number of strings = %lu.\n"
           "Lengths of: strings = %lu, insns = %lu, labs = %lu,\n"
           "   reg ops = %lu, mem ops = %lu, int ops = %lu, float ops = %lu\n",
           len, VARR_LENGTH (string_t, output_strings), str_len, output_insns_len, output_labs_len,
           output_regs_len, output_mem_len, output_int_len, output_float_len);
#endif
  put_byte (ctx, reduce_writer, TAG_EOFILE);
  string_finish (&output_strings, &output_string_tab);
#ifndef MIR_NO_BIN_COMPRESSION
  if (!reduce_encode_finish (io_reduce_data))
    MIR_get_error_func (ctx) (MIR_binary_io_error, "error in writing MIR binary");
#endif
}

void MIR_write_with_func (MIR_context_t ctx, int (*const writer) (MIR_context_t, uint8_t)) {
  MIR_write_module_with_func (ctx, writer, NULL);
}

static int file_writer (MIR_context_t ctx, uint8_t byte) { return fputc (byte, io_file); }

void MIR_write_module (MIR_context_t ctx, FILE *f, MIR_module_t module) {
  io_file = f;
  MIR_write_module_with_func (ctx, file_writer, module);
}

void MIR_write (MIR_context_t ctx, FILE *f) { MIR_write_module (ctx, f, NULL); }

/* New Page */

static int get_byte (MIR_context_t ctx) {
#ifdef MIR_NO_BIN_COMPRESSION
  int c = io_reader (ctx);
#else
  int c = reduce_decode_get (io_reduce_data);
#endif

  if (c == EOF) MIR_get_error_func (ctx) (MIR_binary_io_error, "unfinished binary MIR");
  return c;
}

typedef union {
  uint64_t u;
  int64_t i;
  float f;
  double d;
  long double ld;
  MIR_type_t t;
  MIR_reg_t reg;
} token_attr_t;

static uint64_t get_uint (MIR_context_t ctx, int nb) {
  uint64_t res = 0;

  for (int i = 0; i < nb; i++) res |= (uint64_t) get_byte (ctx) << (i * CHAR_BIT);
  return res;
}

static int64_t get_int (MIR_context_t ctx, int nb) { return (int64_t) get_uint (ctx, nb); }

static float get_float (MIR_context_t ctx) {
  union {
    uint32_t u;
    float f;
  } u;

  u.u = get_uint (ctx, sizeof (uint32_t));
  return u.f;
}

static double get_double (MIR_context_t ctx) {
  union {
    uint64_t u;
    double d;
  } u;

  u.u = get_uint (ctx, sizeof (uint64_t));
  return u.d;
}

static long double get_ldouble (MIR_context_t ctx) {
  union {
    uint64_t u[2];
    long double ld;
  } u;

  u.u[0] = get_uint (ctx, sizeof (uint64_t));
  u.u[1] = get_uint (ctx, sizeof (uint64_t));
  return u.ld;
}

static MIR_str_t to_str (MIR_context_t ctx, uint64_t str_num) {
  if (str_num >= VARR_LENGTH (MIR_str_t, bin_strings))
    MIR_get_error_func (ctx) (MIR_binary_io_error, "wrong string num %lu", str_num);
  return VARR_GET (MIR_str_t, bin_strings, str_num);
}

static void process_reserved_name (const char *s, const char *prefix, uint32_t *max_num) {
  char *end;
  uint32_t num;
  size_t len = strlen (prefix);

  if (strncmp (s, prefix, len) != 0) return;
  num = strtoul (s + len, &end, 10);
  if (*end != '\0') return;
  if (*max_num < num) *max_num = num;
}

static MIR_reg_t to_reg (MIR_context_t ctx, uint64_t reg_str_num, MIR_item_t func) {
  const char *s = to_str (ctx, reg_str_num).s;

  process_reserved_name (s, TEMP_REG_NAME_PREFIX, &func->u.func->last_temp_num);
  return MIR_reg (ctx, s, func->u.func);
}

static MIR_label_t to_lab (MIR_context_t ctx, uint64_t lab_num) {
  MIR_label_t lab;

  while (lab_num >= VARR_LENGTH (MIR_label_t, func_labels))
    VARR_PUSH (MIR_label_t, func_labels, NULL);
  if ((lab = VARR_GET (MIR_label_t, func_labels, lab_num)) != NULL) return lab;
  lab = create_label (ctx, lab_num);
  VARR_SET (MIR_label_t, func_labels, lab_num, lab);
  return lab;
}

static int64_t read_int (MIR_context_t ctx, const char *err_msg) {
  int c = get_byte (ctx);

  if (TAG_I1 > c || c > TAG_I8) MIR_get_error_func (ctx) (MIR_binary_io_error, err_msg);
  return get_int (ctx, c - TAG_I1 + 1);
}

static uint64_t read_uint (MIR_context_t ctx, const char *err_msg) {
  int c = get_byte (ctx);

  if (c & U0_FLAG) return c & U0_MASK;
  if (TAG_U1 > c || c > TAG_U8) MIR_get_error_func (ctx) (MIR_binary_io_error, err_msg);
  return get_uint (ctx, c - TAG_U1 + 1);
}

static void read_all_strings (MIR_context_t ctx, uint64_t nstr) {
  int c;
  MIR_str_t str;
  uint64_t len, l;

  VARR_TRUNC (MIR_str_t, bin_strings, 0);
  for (uint64_t i = 0; i < nstr; i++) {
    VARR_TRUNC (char, temp_string, 0);
    len = read_uint (ctx, "wrong string length");
    for (l = 0; l < len; l++) {
      c = get_byte (ctx);
      VARR_PUSH (char, temp_string, c);
    }
    str.s = VARR_ADDR (char, temp_string);
    str.len = len;
    str = get_ctx_string (ctx, str).str;
    VARR_PUSH (MIR_str_t, bin_strings, str);
  }
}

static MIR_type_t tag_type (bin_tag_t tag) { return (MIR_type_t) (tag - TAG_TI8) + MIR_T_I8; }

static MIR_type_t read_type (MIR_context_t ctx, const char *err_msg) {
  int c = get_byte (ctx);

  if (TAG_TI8 > c || c > TAG_TRBLOCK) MIR_get_error_func (ctx) (MIR_binary_io_error, err_msg);
  return tag_type (c);
}

static const char *read_name (MIR_context_t ctx, MIR_module_t module, const char *err_msg) {
  int c = get_byte (ctx);
  const char *s;

  if (TAG_NAME1 > c || c > TAG_NAME4) MIR_get_error_func (ctx) (MIR_binary_io_error, err_msg);
  s = to_str (ctx, get_uint (ctx, c - TAG_NAME1 + 1)).s;
  if (module != NULL) process_reserved_name (s, TEMP_ITEM_NAME_PREFIX, &module->last_temp_item_num);
  return s;
}

#define TAG_CASE(t) case TAG_##t:
#define REP_SEP
static bin_tag_t read_token (MIR_context_t ctx, token_attr_t *attr) {
  int c = get_byte (ctx);

  if (c & U0_FLAG) {
    attr->u = c & U0_MASK;
    return TAG_U0;
  }
  switch (c) {
    REP8 (TAG_CASE, U1, U2, U3, U4, U5, U6, U7, U8)
    attr->u = get_uint (ctx, c - TAG_U1 + 1);
    break;
    REP8 (TAG_CASE, I1, I2, I3, I4, I5, I6, I7, I8)
    attr->i = get_int (ctx, c - TAG_I1 + 1);
    break;
    TAG_CASE (F)
    attr->f = get_float (ctx);
    break;
    TAG_CASE (D)
    attr->d = get_double (ctx);
    break;
    TAG_CASE (LD)
    attr->ld = get_ldouble (ctx);
    break;
    REP4 (TAG_CASE, REG1, REG2, REG3, REG4)
    attr->u = get_uint (ctx, c - TAG_REG1 + 1);
    break;
    REP4 (TAG_CASE, NAME1, NAME2, NAME3, NAME4)
    attr->u = get_uint (ctx, c - TAG_NAME1 + 1);
    break;
    REP4 (TAG_CASE, STR1, STR2, STR3, STR4)
    attr->u = get_uint (ctx, c - TAG_STR1 + 1);
    break;
    REP4 (TAG_CASE, LAB1, LAB2, LAB3, LAB4)
    attr->u = get_uint (ctx, c - TAG_LAB1 + 1);
    break;
    REP6 (TAG_CASE, MEM_DISP, MEM_BASE, MEM_INDEX, MEM_DISP_BASE, MEM_DISP_INDEX, MEM_BASE_INDEX)
    REP3 (TAG_CASE, MEM_DISP_BASE_INDEX, EOI, EOFILE)
    break;
    REP8 (TAG_CASE, TI8, TU8, TI16, TU16, TI32, TU32, TI64, TU64)
    REP8 (TAG_CASE, TF, TD, TP, TV, TBLOCK, TBLOCK2, TBLOCK3, TBLOCK4)
    REP2 (TAG_CASE, TBLOCK5, TRBLOCK)
    attr->t = (MIR_type_t) (c - TAG_TI8) + MIR_T_I8;
    break;
  default: MIR_get_error_func (ctx) (MIR_binary_io_error, "wrong tag %d", c);
  }
  return c;
}

static MIR_disp_t read_disp (MIR_context_t ctx) {
  bin_tag_t tag;
  token_attr_t attr;

  tag = read_token (ctx, &attr);
  if (TAG_I1 > tag || tag > TAG_I8)
    MIR_get_error_func (ctx) (MIR_binary_io_error, "memory disp has wrong tag %d", tag);
  return attr.i;
}

static MIR_reg_t read_reg (MIR_context_t ctx, MIR_item_t func) {
  bin_tag_t tag;
  token_attr_t attr;

  tag = read_token (ctx, &attr);
  if (TAG_REG1 > tag || tag > TAG_REG4)
    MIR_get_error_func (ctx) (MIR_binary_io_error, "register has wrong tag %d", tag);
  return to_reg (ctx, attr.u, func);
}

static int read_operand (MIR_context_t ctx, MIR_op_t *op, MIR_item_t func) {
  bin_tag_t tag;
  token_attr_t attr;
  MIR_type_t t;
  MIR_disp_t disp;
  MIR_reg_t base, index;
  MIR_scale_t scale;

  tag = read_token (ctx, &attr);
  switch (tag) {
    TAG_CASE (U0)
    REP8 (TAG_CASE, U1, U2, U3, U4, U5, U6, U7, U8) *op = MIR_new_uint_op (ctx, attr.u);
    break;
    REP8 (TAG_CASE, I1, I2, I3, I4, I5, I6, I7, I8)
    *op = MIR_new_int_op (ctx, attr.i);
    break;
    TAG_CASE (F)
    *op = MIR_new_float_op (ctx, attr.f);
    break;
    TAG_CASE (D)
    *op = MIR_new_double_op (ctx, attr.d);
    break;
    TAG_CASE (LD)
    *op = MIR_new_ldouble_op (ctx, attr.ld);
    break;
    REP4 (TAG_CASE, REG1, REG2, REG3, REG4)
    *op = MIR_new_reg_op (ctx, to_reg (ctx, attr.u, func));
    break;
    REP4 (TAG_CASE, NAME1, NAME2, NAME3, NAME4) {
      const char *name = to_str (ctx, attr.u).s;
      MIR_item_t item = item_tab_find (ctx, name, func->module);

      if (item == NULL) MIR_get_error_func (ctx) (MIR_binary_io_error, "not found item %s", name);
      *op = MIR_new_ref_op (ctx, item);
      break;
    }
    REP4 (TAG_CASE, STR1, STR2, STR3, STR4)
    *op = MIR_new_str_op (ctx, to_str (ctx, attr.u));
    break;
    REP4 (TAG_CASE, LAB1, LAB2, LAB3, LAB4)
    *op = MIR_new_label_op (ctx, to_lab (ctx, attr.u));
    break;
    REP7 (TAG_CASE, MEM_DISP, MEM_BASE, MEM_INDEX, MEM_DISP_BASE, MEM_DISP_INDEX, MEM_BASE_INDEX,
          MEM_DISP_BASE_INDEX)
    t = read_type (ctx, "wrong memory type");
    disp = (tag == TAG_MEM_DISP || tag == TAG_MEM_DISP_BASE || tag == TAG_MEM_DISP_INDEX
                || tag == TAG_MEM_DISP_BASE_INDEX
              ? read_disp (ctx)
              : 0);
    base = (tag == TAG_MEM_BASE || tag == TAG_MEM_DISP_BASE || tag == TAG_MEM_BASE_INDEX
                || tag == TAG_MEM_DISP_BASE_INDEX
              ? read_reg (ctx, func)
              : 0);
    index = 0;
    scale = 0;
    if (tag == TAG_MEM_INDEX || tag == TAG_MEM_DISP_INDEX || tag == TAG_MEM_BASE_INDEX
        || tag == TAG_MEM_DISP_BASE_INDEX) {
      index = read_reg (ctx, func);
      scale = read_uint (ctx, "wrong memory index scale");
    }
    *op = MIR_new_mem_op (ctx, t, disp, base, index, scale);
    break;
  case TAG_EOI: return FALSE;
  default: mir_assert (FALSE);
  }
  return TRUE;
}
#undef REP_SEP

static int func_proto_read (MIR_context_t ctx, MIR_module_t module, uint64_t *nres_ptr) {
  bin_tag_t tag;
  token_attr_t attr;
  MIR_var_t var;
  int vararg_p = read_uint (ctx, "wrong vararg flag") != 0;
  uint64_t i, nres = read_uint (ctx, "wrong func nres");

  VARR_TRUNC (MIR_type_t, proto_types, 0);
  for (i = 0; i < nres; i++) {
    tag = read_token (ctx, &attr);
    if (TAG_TI8 > tag || tag > TAG_TRBLOCK)
      MIR_get_error_func (ctx) (MIR_binary_io_error, "wrong prototype result type tag %d", tag);
    VARR_PUSH (MIR_type_t, proto_types, tag_type (tag));
  }
  VARR_TRUNC (MIR_var_t, proto_vars, 0);
  for (;;) {
    tag = read_token (ctx, &attr);
    if (tag == TAG_EOI) break;
    if (TAG_TI8 > tag || tag > TAG_TRBLOCK)
      MIR_get_error_func (ctx) (MIR_binary_io_error, "wrong prototype arg type tag %d", tag);
    var.type = tag_type (tag);
    var.name = read_name (ctx, module, "wrong arg name");
    if (MIR_all_blk_type_p (var.type)) var.size = read_uint (ctx, "wrong block arg size");
    VARR_PUSH (MIR_var_t, proto_vars, var);
  }
  *nres_ptr = nres;
  return vararg_p;
}

#ifndef MIR_NO_BIN_COMPRESSION
static size_t reduce_reader (void *start, size_t len, void *data) {
  MIR_context_t ctx = data;
  size_t i;
  int c;

  for (i = 0; i < len && (c = io_reader (ctx)) != EOF; i++) ((char *) start)[i] = c;
  return i;
}
#endif

void MIR_read_with_func (MIR_context_t ctx, int (*const reader) (MIR_context_t)) {
  int version;
  bin_tag_t tag;
  token_attr_t attr;
  MIR_label_t lab;
  uint64_t nstr, nres, u;
  int64_t i;
  MIR_op_t op;
  size_t n, nop;
  const char *name, *item_name;
  MIR_module_t module;
  MIR_item_t func, item;

  io_reader = reader;
#ifndef MIR_NO_BIN_COMPRESSION
  if ((io_reduce_data = reduce_decode_start (reduce_reader, ctx)) == NULL)
    MIR_get_error_func (ctx) (MIR_binary_io_error,
                              "can not alloc data for MIR binary decompression");
#endif
  version = read_uint (ctx, "wrong header");
  if (version > CURR_BIN_VERSION)
    MIR_get_error_func (ctx) (MIR_binary_io_error,
                              "can not read version %d MIR binary: expected %d or less", version,
                              CURR_BIN_VERSION);
  nstr = read_uint (ctx, "wrong header");
  read_all_strings (ctx, nstr);
  module = NULL;
  func = NULL;
  for (;;) {
    VARR_TRUNC (uint64_t, insn_label_string_nums, 0);
    tag = read_token (ctx, &attr);
    while (TAG_LAB1 <= tag && tag <= TAG_LAB4) {
      VARR_PUSH (uint64_t, insn_label_string_nums, attr.u);
      tag = read_token (ctx, &attr);
    }
    VARR_TRUNC (MIR_op_t, read_insn_ops, 0);
    if (TAG_NAME1 <= tag && tag <= TAG_NAME4) {
      name = to_str (ctx, attr.u).s;
      if (strcmp (name, "module") == 0) {
        name = read_name (ctx, module, "wrong module name");
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "insn label before module %s", name);
        if (module != NULL)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "nested module %s", name);
        module = MIR_new_module (ctx, name);
      } else if (strcmp (name, "endmodule") == 0) {
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "endmodule should have no labels");
        if (module == NULL)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "endmodule without module");
        MIR_finish_module (ctx);
        module = NULL;
      } else if (strcmp (name, "proto") == 0) {
        name = read_name (ctx, module, "wrong prototype name");
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "insn label before proto %s", name);
        if (module == NULL)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "prototype %s outside module", name);
        if (func_proto_read (ctx, module, &nres))
          MIR_new_vararg_proto_arr (ctx, name, nres, VARR_ADDR (MIR_type_t, proto_types),
                                    VARR_LENGTH (MIR_var_t, proto_vars),
                                    VARR_ADDR (MIR_var_t, proto_vars));
        else
          MIR_new_proto_arr (ctx, name, nres, VARR_ADDR (MIR_type_t, proto_types),
                             VARR_LENGTH (MIR_var_t, proto_vars),
                             VARR_ADDR (MIR_var_t, proto_vars));
      } else if (strcmp (name, "func") == 0) {
        name = read_name (ctx, module, "wrong func name");
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "insn label before func %s", name);
        if (func != NULL) MIR_get_error_func (ctx) (MIR_binary_io_error, "nested func %s", name);
        if (module == NULL)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "func %s outside module", name);
        if (func_proto_read (ctx, module, &nres))
          func = MIR_new_vararg_func_arr (ctx, name, nres, VARR_ADDR (MIR_type_t, proto_types),
                                          VARR_LENGTH (MIR_var_t, proto_vars),
                                          VARR_ADDR (MIR_var_t, proto_vars));
        else
          func = MIR_new_func_arr (ctx, name, nres, VARR_ADDR (MIR_type_t, proto_types),
                                   VARR_LENGTH (MIR_var_t, proto_vars),
                                   VARR_ADDR (MIR_var_t, proto_vars));
        VARR_TRUNC (MIR_label_t, func_labels, 0);
      } else if (strcmp (name, "endfunc") == 0) {
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "endfunc should have no labels");
        if (func == NULL) MIR_get_error_func (ctx) (MIR_binary_io_error, "endfunc without func");
        MIR_finish_func (ctx);
        func = NULL;
      } else if (strcmp (name, "export") == 0) {
        name = read_name (ctx, module, "wrong export name");
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "export %s should have no labels", name);
        MIR_new_export (ctx, name);
      } else if (strcmp (name, "import") == 0) {
        name = read_name (ctx, module, "wrong import name");
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "import %s should have no labels", name);
        MIR_new_import (ctx, name);
      } else if (strcmp (name, "forward") == 0) {
        name = read_name (ctx, module, "wrong forward name");
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "forward %s should have no labels", name);
        MIR_new_forward (ctx, name);
      } else if (strcmp (name, "nbss") == 0 || strcmp (name, "bss") == 0) {
        name = strcmp (name, "nbss") == 0 ? read_name (ctx, module, "wrong bss name") : NULL;
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "bss %s should have no labels",
                                    name == NULL ? "" : name);
        u = read_uint (ctx, "wrong bss len");
        MIR_new_bss (ctx, name, u);
      } else if (strcmp (name, "nref") == 0 || strcmp (name, "ref") == 0) {
        name = strcmp (name, "nref") == 0 ? read_name (ctx, module, "wrong ref data name") : NULL;
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "ref data %s should have no labels",
                                    name == NULL ? "" : name);
        item_name = read_name (ctx, module, "wrong ref data item name");
        if ((item = item_tab_find (ctx, item_name, module)) == NULL)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "ref data refers to non-existing item %s",
                                    item_name);
        i = read_int (ctx, "wrong ref disp");
        MIR_new_ref_data (ctx, name, item, i);
      } else if (strcmp (name, "nexpr") == 0 || strcmp (name, "expr") == 0) {
        name = strcmp (name, "nexpr") == 0 ? read_name (ctx, module, "wrong expr name") : NULL;
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "expr %s should have no labels",
                                    name == NULL ? "" : name);
        item_name = read_name (ctx, module, "wrong expr func name");
        if ((item = item_tab_find (ctx, item_name, module)) == NULL
            || item->item_type != MIR_func_item)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "expr refers to non-function %s",
                                    item_name);
        MIR_new_expr_data (ctx, name, item);
      } else if (strcmp (name, "ndata") == 0 || strcmp (name, "data") == 0) {
        MIR_type_t type;
        size_t nel;
        union {
          uint8_t u8;
          uint16_t u16;
          uint32_t u32;
          uint64_t u64;
          int8_t i8;
          int16_t i16;
          int32_t i32;
          int64_t i64;
        } v;

        name = strcmp (name, "ndata") == 0 ? read_name (ctx, module, "wrong data name") : NULL;
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "data %s should have no labels",
                                    name == NULL ? "" : name);
        tag = read_token (ctx, &attr);
        if (TAG_TI8 > tag || tag > TAG_TRBLOCK)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "wrong data type tag %d", tag);
        type = tag_type (tag);
        VARR_TRUNC (uint8_t, temp_data, 0);
        for (nel = 0;; nel++) {
          tag = read_token (ctx, &attr);
          if (tag == TAG_EOI) break;
          switch (tag) {
          case TAG_U0:
          case TAG_U1:
          case TAG_U2:
          case TAG_U3:
          case TAG_U4:
          case TAG_U5:
          case TAG_U6:
          case TAG_U7:
          case TAG_U8:
            switch (type) {
            case MIR_T_U8:
              v.u8 = attr.u;
              push_data (ctx, &v.u8, sizeof (uint8_t));
              break;
            case MIR_T_U16:
              v.u16 = attr.u;
              push_data (ctx, (uint8_t *) &v.u16, sizeof (uint16_t));
              break;
            case MIR_T_U32:
              v.u32 = attr.u;
              push_data (ctx, (uint8_t *) &v.u32, sizeof (uint32_t));
              break;
            case MIR_T_U64:
              v.u64 = attr.u;
              push_data (ctx, (uint8_t *) &v.i64, sizeof (uint64_t));
              break;
            default:
              MIR_get_error_func (ctx) (MIR_binary_io_error,
                                        "data type %s does not correspond value type",
                                        type_str (type));
            }
            break;
          case TAG_I1:
          case TAG_I2:
          case TAG_I3:
          case TAG_I4:
          case TAG_I5:
          case TAG_I6:
          case TAG_I7:
          case TAG_I8:
            switch (type) {
            case MIR_T_I8:
              v.i8 = attr.i;
              push_data (ctx, (uint8_t *) &v.i8, sizeof (int8_t));
              break;
            case MIR_T_I16:
              v.i16 = attr.i;
              push_data (ctx, (uint8_t *) &v.i16, sizeof (int16_t));
              break;
            case MIR_T_I32:
              v.i32 = attr.i;
              push_data (ctx, (uint8_t *) &v.i32, sizeof (int32_t));
              break;
            case MIR_T_I64:
              v.i64 = attr.i;
              push_data (ctx, (uint8_t *) &v.i64, sizeof (int64_t));
              break;
            default:
              MIR_get_error_func (ctx) (MIR_binary_io_error,
                                        "data type %s does not correspond value type",
                                        type_str (type));
            }
            break;
          case TAG_F:
            if (type != MIR_T_F)
              MIR_get_error_func (ctx) (MIR_binary_io_error,
                                        "data type %s does not correspond value type",
                                        type_str (type));
            push_data (ctx, (uint8_t *) &attr.f, sizeof (float));
            break;
          case TAG_D:
            if (type != MIR_T_D)
              MIR_get_error_func (ctx) (MIR_binary_io_error,
                                        "data type %s does not correspond value type",
                                        type_str (type));
            push_data (ctx, (uint8_t *) &attr.d, sizeof (double));
            break;
          case TAG_LD:
            if (type != MIR_T_LD)
              MIR_get_error_func (ctx) (MIR_binary_io_error,
                                        "data type %s does not correspond value type",
                                        type_str (type));
            push_data (ctx, (uint8_t *) &attr.ld, sizeof (long double));
            break;
            /* ??? ptr */
          default: MIR_get_error_func (ctx) (MIR_binary_io_error, "wrong data value tag %d", tag);
          }
        }
        MIR_new_data (ctx, name, type,
                      VARR_LENGTH (uint8_t, temp_data) / _MIR_type_size (ctx, type),
                      VARR_ADDR (uint8_t, temp_data));
      } else if (strcmp (name, "local") == 0) {
        if (func == NULL) MIR_get_error_func (ctx) (MIR_binary_io_error, "local outside func");
        if (VARR_LENGTH (uint64_t, insn_label_string_nums) != 0)
          MIR_get_error_func (ctx) (MIR_binary_io_error, "local should have no labels");
        for (;;) {
          tag = read_token (ctx, &attr);
          if (tag == TAG_EOI) break;
          if (TAG_TI8 > tag || tag > TAG_TRBLOCK)
            MIR_get_error_func (ctx) (MIR_binary_io_error, "wrong local var type tag %d", tag);
          MIR_new_func_reg (ctx, func->u.func, tag_type (tag),
                            read_name (ctx, module, "wrong local var name"));
        }
      } else {
        MIR_get_error_func (ctx) (MIR_binary_io_error, "unknown insn name %s", name);
      }
    } else if (TAG_U0 <= tag && tag <= TAG_U8) { /* insn code */
      MIR_insn_code_t insn_code = attr.u;

      if (insn_code >= MIR_LABEL)
        MIR_get_error_func (ctx) (MIR_binary_io_error, "wrong insn code %d", insn_code);
      if (insn_code == MIR_UNSPEC || insn_code == MIR_PHI)
        MIR_get_error_func (ctx) (MIR_binary_io_error,
                                  "UNSPEC or PHI is not portable and can not be read");
      for (uint64_t i = 0; i < VARR_LENGTH (uint64_t, insn_label_string_nums); i++) {
        lab = to_lab (ctx, VARR_GET (uint64_t, insn_label_string_nums, i));
        MIR_append_insn (ctx, func, lab);
      }
      nop = insn_code_nops (ctx, insn_code);
      mir_assert (nop != 0 || MIR_call_code_p (insn_code) || insn_code == MIR_RET
                  || insn_code == MIR_SWITCH);
      for (n = 0; (nop == 0 || n < nop) && read_operand (ctx, &op, func); n++)
        VARR_PUSH (MIR_op_t, read_insn_ops, op);
      if (nop != 0 && n < nop)
        MIR_get_error_func (ctx) (MIR_binary_io_error, "wrong number of operands of insn %s",
                                  insn_name (insn_code));
      MIR_append_insn (ctx, func,
                       MIR_new_insn_arr (ctx, insn_code, n, VARR_ADDR (MIR_op_t, read_insn_ops)));
    } else if (tag == TAG_EOFILE) {
      break;
    } else {
      MIR_get_error_func (ctx) (MIR_binary_io_error, "wrong token %d", tag);
    }
  }
  if (func != NULL)
    MIR_get_error_func (ctx) (MIR_binary_io_error, "unfinished func %s", func->u.func->name);
  if (module != NULL)
    MIR_get_error_func (ctx) (MIR_binary_io_error, "unfinished module %s", module->name);
  if (reader (ctx) != EOF)
    MIR_get_error_func (ctx) (MIR_binary_io_error, "garbage at the end of file");
#ifndef MIR_NO_BIN_COMPRESSION
  reduce_decode_finish (io_reduce_data);
#endif
}

static int file_reader (MIR_context_t ctx) { return fgetc (io_file); }

void MIR_read (MIR_context_t ctx, FILE *f) {
  io_file = f;
  MIR_read_with_func (ctx, file_reader);
}

static void io_init (MIR_context_t ctx) {
  if ((ctx->io_ctx = malloc (sizeof (struct io_ctx))) == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for ctx");
  VARR_CREATE (MIR_var_t, proto_vars, 0);
  VARR_CREATE (MIR_type_t, proto_types, 0);
  VARR_CREATE (MIR_op_t, read_insn_ops, 0);
  VARR_CREATE (MIR_str_t, bin_strings, 512);
  VARR_CREATE (uint64_t, insn_label_string_nums, 64);
  VARR_CREATE (MIR_label_t, func_labels, 512);
}

static void io_finish (MIR_context_t ctx) {
  VARR_DESTROY (MIR_label_t, func_labels);
  VARR_DESTROY (uint64_t, insn_label_string_nums);
  VARR_DESTROY (MIR_str_t, bin_strings);
  VARR_DESTROY (MIR_op_t, read_insn_ops);
  VARR_DESTROY (MIR_var_t, proto_vars);
  VARR_DESTROY (MIR_type_t, proto_types);
  free (ctx->io_ctx);
  ctx->io_ctx = NULL;
}

#endif /* if !MIR_NO_IO */

/* New Page */

/* Reading MIR text file */

#if !MIR_NO_SCAN

#include <stddef.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <setjmp.h>

typedef struct insn_name {
  const char *name;
  MIR_insn_code_t code;
} insn_name_t;

static int insn_name_eq (insn_name_t in1, insn_name_t in2, void *arg) {
  return strcmp (in1.name, in2.name) == 0;
}
static htab_hash_t insn_name_hash (insn_name_t in, void *arg) {
  return mir_hash (in.name, strlen (in.name), 0);
}

#define TC_EL(t) TC_##t
#define REP_SEP ,
enum token_code {
  REP8 (TC_EL, INT, FLOAT, DOUBLE, LDOUBLE, NAME, STR, NL, EOFILE),
  REP5 (TC_EL, LEFT_PAR, RIGHT_PAR, COMMA, SEMICOL, COL),
};
#undef REP_SEP

typedef struct token {
  int code; /* enum token_code and EOF */
  union {
    int64_t i;
    float f;
    double d;
    long double ld;
    const char *name;
    MIR_str_t str;
  } u;
} token_t;

DEF_HTAB (insn_name_t);
typedef const char *label_name_t;
DEF_VARR (label_name_t);

typedef struct label_desc {
  const char *name;
  MIR_label_t label;
} label_desc_t;

DEF_HTAB (label_desc_t);

struct scan_ctx {
  jmp_buf error_jmp_buf; /* keep it here to provide malloc alignment */
  VARR (char) * error_msg_buf;
  VARR (MIR_var_t) * scan_vars;
  VARR (MIR_type_t) * scan_types;
  VARR (MIR_op_t) * scan_insn_ops;
  size_t curr_lno;
  HTAB (insn_name_t) * insn_name_tab;
  const char *input_string;
  size_t input_string_char_num;
  VARR (label_name_t) * label_names;
  HTAB (label_desc_t) * label_desc_tab;
};

#define error_jmp_buf ctx->scan_ctx->error_jmp_buf
#define error_msg_buf ctx->scan_ctx->error_msg_buf
#define scan_vars ctx->scan_ctx->scan_vars
#define scan_types ctx->scan_ctx->scan_types
#define scan_insn_ops ctx->scan_ctx->scan_insn_ops
#define curr_lno ctx->scan_ctx->curr_lno
#define insn_name_tab ctx->scan_ctx->insn_name_tab
#define input_string ctx->scan_ctx->input_string
#define input_string_char_num ctx->scan_ctx->input_string_char_num
#define label_names ctx->scan_ctx->label_names
#define label_desc_tab ctx->scan_ctx->label_desc_tab

static void scan_error (MIR_context_t ctx, const char *format, ...) {
  char message[150];
  size_t len;
  va_list va;

  va_start (va, format);
  if (VARR_LENGTH (char, error_msg_buf) != 0) VARR_POP (char, error_msg_buf); /* remove last '\0' */
  sprintf (message, "ln %lu: ", (unsigned long) curr_lno);
  VARR_PUSH_ARR (char, error_msg_buf, message, strlen (message));
  len = vsnprintf (message, sizeof (message), format, va);
  VARR_PUSH_ARR (char, error_msg_buf, message, len);
  VARR_PUSH_ARR (char, error_msg_buf, "\n", 2); /* add '\n' and '\0' */
  va_end (va);
  longjmp (error_jmp_buf, TRUE);
}

/* Read number using GET_CHAR and UNGET_CHAR and already read
   character CH.  It should be guaranted that the input has a righ
   prefix (+|-)?[0-9].  Return base, float and double flag through
   BASE, FLOAT_P, DOUBLE_P.  Put number representation (0x or 0X
   prefix is removed) into TEMP_STRING.  */
static void scan_number (MIR_context_t ctx, int ch, int get_char (MIR_context_t),
                         void unget_char (MIR_context_t, int), int *base, int *float_p,
                         int *double_p, int *ldouble_p) {
  enum scan_number_code { NUMBER_OK, ABSENT_EXPONENT, NON_DECIMAL_FLOAT, WRONG_OCTAL_INT };
  enum scan_number_code err_code = NUMBER_OK;
  int dec_p, hex_p, hex_char_p;

  *base = 10;
  *ldouble_p = *double_p = *float_p = FALSE;
  if (ch == '+' || ch == '-') {
    VARR_PUSH (char, temp_string, ch);
    ch = get_char (ctx);
  }
  mir_assert ('0' <= ch && ch <= '9');
  if (ch == '0') {
    ch = get_char (ctx);
    if (ch != 'x' && ch != 'X') {
      *base = 8;
      unget_char (ctx, ch);
      ch = '0';
    } else {
      ch = get_char (ctx);
      *base = 16;
    }
  }
  dec_p = hex_p = FALSE;
  for (;;) {
    if (ch != '_') VARR_PUSH (char, temp_string, ch);
    ch = get_char (ctx);
    if (ch == '8' || ch == '9') dec_p = TRUE;
    hex_char_p = (('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F'));
    if (ch != '_' && !isdigit (ch) && (*base != 16 || !hex_char_p)) break;
    if (hex_char_p) hex_p = TRUE;
  }
  mir_assert (*base == 16 || !hex_p);
  if (ch == '.') {
    *double_p = TRUE;
    do {
      if (ch != '_') VARR_PUSH (char, temp_string, ch);
      ch = get_char (ctx);
    } while (isdigit (ch) || ch == '_');
  }
  if (ch == 'e' || ch == 'E') {
    *double_p = TRUE;
    ch = get_char (ctx);
    if (ch != '+' && ch != '-' && !isdigit (ch))
      err_code = ABSENT_EXPONENT;
    else {
      VARR_PUSH (char, temp_string, 'e');
      if (ch == '+' || ch == '-') {
        VARR_PUSH (char, temp_string, ch);
        ch = get_char (ctx);
        if (!isdigit (ch)) err_code = ABSENT_EXPONENT;
      }
      if (err_code == NUMBER_OK) do {
          if (ch != '_') VARR_PUSH (char, temp_string, ch);
          ch = get_char (ctx);
        } while (isdigit (ch) || ch == '_');
    }
  }
  if (*double_p) {
    if (*base == 16)
      err_code = NON_DECIMAL_FLOAT;
    else if (ch == 'f' || ch == 'F') {
      *float_p = TRUE;
      *double_p = FALSE;
      ch = get_char (ctx);
    } else if (ch == 'l' || ch == 'L') {
#if !defined(_WIN32) && __SIZEOF_LONG_DOUBLE__ != 8
      *ldouble_p = TRUE;
      *double_p = FALSE;
#endif
      ch = get_char (ctx);
    }
  } else if (*base == 8 && dec_p)
    err_code = WRONG_OCTAL_INT;
  VARR_PUSH (char, temp_string, '\0');
  unget_char (ctx, ch);
}

static void scan_string (MIR_context_t ctx, token_t *t, int c, int get_char (MIR_context_t),
                         void unget_char (MIR_context_t, int)) {
  int ch_code;

  mir_assert (c == '\"');
  VARR_TRUNC (char, temp_string, 0);
  for (;;) {
    if ((c = get_char (ctx)) == EOF || c == '\n') {
      VARR_PUSH (char, temp_string, '\0');
      scan_error (ctx, "unfinished string \"%s", VARR_ADDR (char, temp_string));
    }
    if (c == '"') break;
    if (c == '\\') {
      if ((c = get_char (ctx)) == 'n')
        c = '\n';
      else if (c == 't')
        c = '\t';
      else if (c == 'v')
        c = '\v';
      else if (c == 'a')
        c = '\a';
      else if (c == 'b')
        c = '\b';
      else if (c == 'r')
        c = '\r';
      else if (c == 'f')
        c = '\f';
      else if (c == '\\' || c == '\'' || c == '\"')
        ;
      else if (c == '\n') {
        curr_lno++;
        continue;
      } else if (isdigit (c) && c != '8' && c != '9') {
        ch_code = c - '0';
        c = get_char (ctx);
        if (!isdigit (c) || c == '8' || c == '9')
          unget_char (ctx, c);
        else {
          ch_code = ch_code * 8 + c - '0';
          c = get_char (ctx);
          if (!isdigit (c) || c == '8' || c == '9')
            unget_char (ctx, c);
          else
            ch_code = ch_code * 8 + c - '0';
        }
        c = ch_code;
      } else if (c == 'x') {
        /* Hex escape code.  */
        ch_code = 0;
        for (int i = 2; i > 0; i--) {
          c = get_char (ctx);
          if (!isxdigit (c)) {
            VARR_PUSH (char, temp_string, '\0');
            scan_error (ctx, "wrong hexadecimal escape in %s", VARR_ADDR (char, temp_string));
          }
          c = '0' <= c && c <= '9' ? c - '0' : 'a' <= c && c <= 'f' ? c - 'a' + 10 : c - 'A' + 10;
          ch_code = (ch_code << 4) | c;
        }
        c = ch_code;
      }
    }
    VARR_PUSH (char, temp_string, c);
  }
  if (VARR_LENGTH (char, temp_string) > 0 && VARR_LAST (char, temp_string) != 0)
    VARR_PUSH (char, temp_string, 0);
  t->code = TC_STR;
  t->u.str
    = string_store (ctx, &strings, &string_tab,
                    (MIR_str_t){VARR_LENGTH (char, temp_string), VARR_ADDR (char, temp_string)})
        .str;
}

static int get_string_char (MIR_context_t ctx) {
  int ch = input_string[input_string_char_num];

  if (ch == '\0') return EOF;
  input_string_char_num++;
  if (ch == '\n') curr_lno++;
  return ch;
}

static void unget_string_char (MIR_context_t ctx, int ch) {
  if (input_string_char_num == 0 || ch == EOF) return;
  input_string_char_num--;
  mir_assert (input_string[input_string_char_num] == ch);
  if (ch == '\n') curr_lno--;
}

static void scan_token (MIR_context_t ctx, token_t *token, int (*get_char) (MIR_context_t),
                        void (*unget_char) (MIR_context_t, int)) {
  int ch;

  for (;;) {
    ch = get_char (ctx);
    switch (ch) {
    case EOF: token->code = TC_EOFILE; return;
    case ' ':
    case '\t': break;
    case '#':
      while ((ch = get_char (ctx)) != '\n' && ch != EOF)
        ;
      /* Fall through: */
    case '\n': token->code = TC_NL; return;
    case '(': token->code = TC_LEFT_PAR; return;
    case ')': token->code = TC_RIGHT_PAR; return;
    case ',': token->code = TC_COMMA; return;
    case ';': token->code = TC_SEMICOL; return;
    case ':': token->code = TC_COL; return;
    case '"': scan_string (ctx, token, ch, get_char, unget_char); return;
    default:
      VARR_TRUNC (char, temp_string, 0);
      if (isalpha (ch) || ch == '_' || ch == '$' || ch == '%' || ch == '.') {
        do {
          VARR_PUSH (char, temp_string, ch);
          ch = get_char (ctx);
        } while (isalpha (ch) || isdigit (ch) || ch == '_' || ch == '$' || ch == '%' || ch == '.');
        VARR_PUSH (char, temp_string, '\0');
        unget_char (ctx, ch);
        token->u.name = _MIR_uniq_string (ctx, VARR_ADDR (char, temp_string));
        token->code = TC_NAME;
        return;
      } else if (ch == '+' || ch == '-' || isdigit (ch)) {
        const char *repr;
        char *end;
        int next_ch, base, float_p, double_p, ldouble_p;

        if (ch == '+' || ch == '-') {
          next_ch = get_char (ctx);
          if (!isdigit (next_ch)) scan_error (ctx, "no number after a sign %c", ch);
          unget_char (ctx, next_ch);
        }
        scan_number (ctx, ch, get_char, unget_char, &base, &float_p, &double_p, &ldouble_p);
        repr = VARR_ADDR (char, temp_string);
        errno = 0;
        if (float_p) {
          token->code = TC_FLOAT;
          token->u.f = strtof (repr, &end);
        } else if (double_p) {
          token->code = TC_DOUBLE;
          token->u.d = strtod (repr, &end);
        } else if (ldouble_p) {
          token->code = TC_LDOUBLE;
          token->u.ld = strtold (repr, &end);
        } else {
          token->code = TC_INT;
          token->u.i = (sizeof (long) == sizeof (int64_t) ? strtoul (repr, &end, base)
                                                          : strtoull (repr, &end, base));
        }
        mir_assert (*end == '\0');
        if (errno != 0)
          ;
        return;
      } else {
        VARR_PUSH (char, temp_string, '\0');
        scan_error (ctx, "wrong char after %s", VARR_ADDR (char, temp_string));
      }
    }
  }
}

static int label_eq (label_desc_t l1, label_desc_t l2, void *arg) {
  return strcmp (l1.name, l2.name) == 0;
}
static htab_hash_t label_hash (label_desc_t l, void *arg) {
  return mir_hash (l.name, strlen (l.name), 0);
}

static MIR_label_t create_label_desc (MIR_context_t ctx, const char *name) {
  MIR_label_t label;
  label_desc_t label_desc;

  label_desc.name = name;
  if (HTAB_DO (label_desc_t, label_desc_tab, label_desc, HTAB_FIND, label_desc)) {
    label = label_desc.label;
  } else {
    label_desc.label = label = MIR_new_label (ctx);
    HTAB_DO (label_desc_t, label_desc_tab, label_desc, HTAB_INSERT, label_desc);
  }
  return label;
}

static int func_reg_p (MIR_context_t ctx, MIR_func_t func, const char *name) {
  func_regs_t func_regs = func->internal;
  size_t rdn, tab_rdn;
  reg_desc_t rd;
  int res;

  rd.name = (char *) name;
  rdn = VARR_LENGTH (reg_desc_t, func_regs->reg_descs);
  VARR_PUSH (reg_desc_t, func_regs->reg_descs, rd);
  res = HTAB_DO (size_t, func_regs->name2rdn_tab, rdn, HTAB_FIND, tab_rdn);
  VARR_POP (reg_desc_t, func_regs->reg_descs);
  return res;
}

static void read_func_proto (MIR_context_t ctx, size_t nops, MIR_op_t *ops) {
  MIR_var_t var;
  size_t i;

  VARR_TRUNC (MIR_type_t, scan_types, 0);
  for (i = 0; i < nops; i++) {
    var.name = (const char *) ops[i].u.mem.disp;
    if ((var.name = (const char *) ops[i].u.mem.disp) != NULL) break;
    var.type = ops[i].u.mem.type;
    VARR_PUSH (MIR_type_t, scan_types, var.type);
  }
  VARR_TRUNC (MIR_var_t, scan_vars, 0);
  for (; i < nops; i++) {
    if (ops[i].mode != MIR_OP_MEM) scan_error (ctx, "wrong prototype/func arg");
    var.type = ops[i].u.mem.type;
    var.name = (const char *) ops[i].u.mem.disp;
    if (var.name == NULL)
      scan_error (ctx, "all func/prototype args should have form type:name or (r)blk:size(name)");
    if (MIR_all_blk_type_p (var.type)) var.size = ops[i].u.mem.base;
    VARR_PUSH (MIR_var_t, scan_vars, var);
  }
}

static MIR_type_t str2type (const char *type_name) {
  if (strcmp (type_name, "i64") == 0) return MIR_T_I64;
  if (strcmp (type_name, "u64") == 0) return MIR_T_U64;
  if (strcmp (type_name, "f") == 0) return MIR_T_F;
  if (strcmp (type_name, "d") == 0) return MIR_T_D;
  if (strcmp (type_name, "ld") == 0) return MIR_T_LD;
  if (strcmp (type_name, "p") == 0) return MIR_T_P;
  if (strcmp (type_name, "i32") == 0) return MIR_T_I32;
  if (strcmp (type_name, "u32") == 0) return MIR_T_U32;
  if (strcmp (type_name, "i16") == 0) return MIR_T_I16;
  if (strcmp (type_name, "u16") == 0) return MIR_T_U16;
  if (strcmp (type_name, "i8") == 0) return MIR_T_I8;
  if (strcmp (type_name, "u8") == 0) return MIR_T_U8;
  if (strcmp (type_name, "blk") == 0) return MIR_T_BLK;
  if (strcmp (type_name, "blk2") == 0) return MIR_T_BLK2;
  if (strcmp (type_name, "blk3") == 0) return MIR_T_BLK3;
  if (strcmp (type_name, "blk4") == 0) return MIR_T_BLK4;
  if (strcmp (type_name, "blk5") == 0) return MIR_T_BLK5;
  if (strcmp (type_name, "rblk") == 0) return MIR_T_RBLK;
  return MIR_T_BOUND;
}

/* Syntax:
     program: { insn / sep }
     sep : ';' | NL
     insn : {label ':'}* [ code [ {op / ','} ] ]
     label : name
     code : name
     op : name | int | float | double | long double | mem | str
     mem : type ':' addr
     addr : disp
          | [ disp ] '(' sib ')'
     sib : name | [ name ] ',' name [ ',' scale]
     disp : int | name
     scale : int

*/

void MIR_scan_string (MIR_context_t ctx, const char *str) {
  token_t t;
  const char *name;
  MIR_module_t module = NULL;
  MIR_item_t item, func = NULL;
  MIR_insn_code_t insn_code;
  MIR_insn_t insn;
  MIR_type_t type, data_type = MIR_T_BOUND;
  MIR_op_t op, *op_addr;
  MIR_label_t label;
  size_t n;
  int64_t i;
  int module_p, end_module_p, proto_p, func_p, end_func_p, dots_p, export_p, import_p, forward_p;
  int bss_p, ref_p, expr_p, string_p, local_p, push_op_p, read_p, disp_p;
  insn_name_t in, el;

  VARR_TRUNC (char, error_msg_buf, 0);
  curr_lno = 1;
  input_string = str;
  input_string_char_num = 0;
  t.code = TC_NL;
  for (;;) {
    if (setjmp (error_jmp_buf)) {
      while (t.code != TC_NL && t.code != EOF)
        scan_token (ctx, &t, get_string_char, unget_string_char);
      if (t.code == TC_EOFILE) break;
    }
    VARR_TRUNC (label_name_t, label_names, 0);
    scan_token (ctx, &t, get_string_char, unget_string_char);
    while (t.code == TC_NL) scan_token (ctx, &t, get_string_char, unget_string_char);
    if (t.code == TC_EOFILE) break;
    for (;;) { /* label_names */
      if (t.code != TC_NAME) scan_error (ctx, "insn should start with label or insn name");
      name = t.u.name;
      scan_token (ctx, &t, get_string_char, unget_string_char);
      if (t.code != TC_COL) break;
      VARR_PUSH (label_name_t, label_names, name);
      scan_token (ctx, &t, get_string_char, unget_string_char);
      if (t.code == TC_NL)
        scan_token (ctx, &t, get_string_char, unget_string_char); /* label_names without insn */
    }
    module_p = end_module_p = proto_p = func_p = end_func_p = FALSE;
    export_p = import_p = forward_p = bss_p = ref_p = expr_p = string_p = local_p = FALSE;
    if (strcmp (name, "module") == 0) {
      module_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) != 1)
        scan_error (ctx, "only one label should be used for module");
    } else if (strcmp (name, "endmodule") == 0) {
      end_module_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) != 0)
        scan_error (ctx, "endmodule should have no labels");
    } else if (strcmp (name, "proto") == 0) {
      proto_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) != 1)
        scan_error (ctx, "only one label should be used for proto");
    } else if (strcmp (name, "func") == 0) {
      func_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) != 1)
        scan_error (ctx, "only one label should be used for func");
    } else if (strcmp (name, "endfunc") == 0) {
      end_func_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) != 0)
        scan_error (ctx, "endfunc should have no labels");
    } else if (strcmp (name, "export") == 0) {
      export_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) != 0)
        scan_error (ctx, "export should have no labels");
    } else if (strcmp (name, "import") == 0) {
      import_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) != 0)
        scan_error (ctx, "import should have no labels");
    } else if (strcmp (name, "forward") == 0) {
      forward_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) != 0)
        scan_error (ctx, "forward should have no labels");
    } else if (strcmp (name, "bss") == 0) {
      bss_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) > 1)
        scan_error (ctx, "at most one label should be used for bss");
    } else if (strcmp (name, "ref") == 0) {
      ref_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) > 1)
        scan_error (ctx, "at most one label should be used for ref");
    } else if (strcmp (name, "expr") == 0) {
      expr_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) > 1)
        scan_error (ctx, "at most one label should be used for expr");
    } else if (strcmp (name, "string") == 0) {
      string_p = TRUE;
      if (VARR_LENGTH (label_name_t, label_names) > 1)
        scan_error (ctx, "at most one label should be used for string");
    } else if (strcmp (name, "local") == 0) {
      local_p = TRUE;
      if (func == NULL) scan_error (ctx, "local outside func");
      if (VARR_LENGTH (label_name_t, label_names) != 0)
        scan_error (ctx, "local should have no labels");
    } else if ((data_type = str2type (name)) != MIR_T_BOUND) {
      if (VARR_LENGTH (label_name_t, label_names) > 1)
        scan_error (ctx, "at most one label should be used for data");
    } else {
      in.name = name;
      if (!HTAB_DO (insn_name_t, insn_name_tab, in, HTAB_FIND, el))
        scan_error (ctx, "Unknown insn %s", name);
      insn_code = el.code;
      if (insn_code == MIR_UNSPEC || insn_code == MIR_PHI)
        scan_error (ctx, "UNSPEC or PHI is not portable and can not be scanned", name);
      for (n = 0; n < VARR_LENGTH (label_name_t, label_names); n++) {
        label = create_label_desc (ctx, VARR_GET (label_name_t, label_names, n));
        if (func != NULL) MIR_append_insn (ctx, func, label);
      }
    }
    VARR_TRUNC (MIR_op_t, scan_insn_ops, 0);
    dots_p = FALSE;
    for (;;) { /* ops */
      if (t.code == TC_NL || t.code == TC_SEMICOL) {
        /* insn end */
        break;
      }
      push_op_p = read_p = TRUE;
      switch (t.code) {
      case TC_NAME: {
        name = t.u.name;
        scan_token (ctx, &t, get_string_char, unget_string_char);
        if ((func_p || proto_p) && strcmp (name, "...") == 0) {
          dots_p = TRUE;
          break;
        }
        read_p = FALSE;
        if (t.code != TC_COL && !proto_p && !func_p && !local_p) {
          if (export_p) {
            MIR_new_export (ctx, name);
            push_op_p = FALSE;
          } else if (import_p) {
            MIR_new_import (ctx, name);
            push_op_p = FALSE;
          } else if (forward_p) {
            MIR_new_forward (ctx, name);
            push_op_p = FALSE;
          } else if (!module_p && !end_module_p && !proto_p && !func_p && !end_func_p && !local_p
                     && ((MIR_branch_code_p (insn_code)
                          && VARR_LENGTH (MIR_op_t, scan_insn_ops) == 0)
                         || (insn_code == MIR_SWITCH
                             && VARR_LENGTH (MIR_op_t, scan_insn_ops) > 0))) {
            op = MIR_new_label_op (ctx, create_label_desc (ctx, name));
          } else if (!expr_p && !ref_p && func_reg_p (ctx, func->u.func, name)) {
            op.mode = MIR_OP_REG;
            op.u.reg = MIR_reg (ctx, name, func->u.func);
          } else if ((item = item_tab_find (ctx, name, module)) != NULL) {
            op = MIR_new_ref_op (ctx, item);
          } else {
            scan_error (ctx, "undeclared name %s", name);
          }
          break;
        }
        /* Memory, type only, arg, or var */
        type = str2type (name);
        if (type == MIR_T_BOUND)
          scan_error (ctx, "Unknown type %s", name);
        else if (local_p && type != MIR_T_I64 && type != MIR_T_F && type != MIR_T_D
                 && type != MIR_T_LD)
          scan_error (ctx, "wrong type %s for local var", name);
        op = MIR_new_mem_op (ctx, type, 0, 0, 0, 1);
        if (proto_p || func_p || local_p) {
          if (t.code == TC_COL) {
            scan_token (ctx, &t, get_string_char, unget_string_char);
            if (t.code == TC_NAME) {
              op.u.mem.disp = (MIR_disp_t) t.u.name;
            } else if (local_p || t.code != TC_INT || !MIR_all_blk_type_p (type)) {
              scan_error (ctx, local_p ? "wrong var" : "wrong arg");
            } else {
              op.u.mem.base = t.u.i;
              if (t.u.i <= 0 || t.u.i >= (1ll << sizeof (MIR_reg_t) * 8))
                scan_error (ctx, "invalid block arg size");
              scan_token (ctx, &t, get_string_char, unget_string_char);
              if (t.code != TC_LEFT_PAR) scan_error (ctx, "wrong block arg");
              scan_token (ctx, &t, get_string_char, unget_string_char);
              if (t.code != TC_NAME) scan_error (ctx, "wrong block arg");
              op.u.mem.disp = (MIR_disp_t) t.u.name;
              scan_token (ctx, &t, get_string_char, unget_string_char);
              if (t.code != TC_RIGHT_PAR) scan_error (ctx, "wrong block arg");
            }
            scan_token (ctx, &t, get_string_char, unget_string_char);
          }
        } else {
          scan_token (ctx, &t, get_string_char, unget_string_char);
          disp_p = FALSE;
          if (t.code == TC_INT) {
            op.u.mem.disp = t.u.i;
            scan_token (ctx, &t, get_string_char, unget_string_char);
            disp_p = TRUE;
          } else if (t.code == TC_NAME) {
            op.u.mem.disp = (MIR_disp_t) t.u.name;
            scan_token (ctx, &t, get_string_char, unget_string_char);
            disp_p = TRUE;
          }
          if (t.code == TC_LEFT_PAR) {
            scan_token (ctx, &t, get_string_char, unget_string_char);
            if (t.code == TC_NAME) {
              op.u.mem.base = MIR_reg (ctx, t.u.name, func->u.func);
              scan_token (ctx, &t, get_string_char, unget_string_char);
            }
            if (t.code == TC_COMMA) {
              scan_token (ctx, &t, get_string_char, unget_string_char);
              if (t.code != TC_NAME) scan_error (ctx, "wrong index");
              op.u.mem.index = MIR_reg (ctx, t.u.name, func->u.func);
              scan_token (ctx, &t, get_string_char, unget_string_char);
              if (t.code == TC_COMMA) {
                scan_token (ctx, &t, get_string_char, unget_string_char);
                if (t.code != TC_INT) scan_error (ctx, "wrong scale");
                op.u.mem.scale = t.u.i;
                scan_token (ctx, &t, get_string_char, unget_string_char);
              }
            }
            if (t.code != TC_RIGHT_PAR) scan_error (ctx, "wrong memory op");
            scan_token (ctx, &t, get_string_char, unget_string_char);
          } else if (!disp_p)
            scan_error (ctx, "wrong memory");
        }
        break;
      }
      case TC_INT:
        op.mode = MIR_OP_INT;
        op.u.i = t.u.i;
        break;
      case TC_FLOAT:
        op.mode = MIR_OP_FLOAT;
        op.u.f = t.u.f;
        break;
      case TC_LDOUBLE: op.mode = MIR_OP_LDOUBLE; op.u.ld = t.u.ld;
#if !defined(_WIN32) && __SIZEOF_LONG_DOUBLE__ != 8
        break;
#endif
      case TC_DOUBLE:
        op.mode = MIR_OP_DOUBLE;
        op.u.d = t.u.d;
        break;
      case TC_STR:
        op.mode = MIR_OP_STR;
        op.u.str = t.u.str;
        break;
      default: break;
      }
      if (dots_p) break;
      if (push_op_p) VARR_PUSH (MIR_op_t, scan_insn_ops, op);
      if (read_p) scan_token (ctx, &t, get_string_char, unget_string_char);
      if (t.code != TC_COMMA) break;
      scan_token (ctx, &t, get_string_char, unget_string_char);
    }
    if (t.code != TC_NL && t.code != TC_EOFILE && t.code != TC_SEMICOL)
      scan_error (ctx, "wrong insn end");
    if (module_p) {
      if (module != NULL) scan_error (ctx, "nested module");
      if (VARR_LENGTH (MIR_op_t, scan_insn_ops) != 0)
        scan_error (ctx, "module should have no params");
      module = MIR_new_module (ctx, VARR_GET (label_name_t, label_names, 0));
    } else if (end_module_p) {
      if (module == NULL) scan_error (ctx, "standalone endmodule");
      if (VARR_LENGTH (MIR_op_t, scan_insn_ops) != 0)
        scan_error (ctx, "endmodule should have no params");
      MIR_finish_module (ctx);
      module = NULL;
    } else if (bss_p) {
      if (VARR_LENGTH (MIR_op_t, scan_insn_ops) != 1)
        scan_error (ctx, "bss should have one operand");
      op_addr = VARR_ADDR (MIR_op_t, scan_insn_ops);
      if (op_addr[0].mode != MIR_OP_INT || op_addr[0].u.i < 0)
        scan_error (ctx, "wrong bss operand type or value");
      name
        = (VARR_LENGTH (label_name_t, label_names) == 0 ? NULL
                                                        : VARR_GET (label_name_t, label_names, 0));
      MIR_new_bss (ctx, name, op_addr[0].u.i);
    } else if (ref_p) {
      if (VARR_LENGTH (MIR_op_t, scan_insn_ops) != 2)
        scan_error (ctx, "ref should have two operands");
      op_addr = VARR_ADDR (MIR_op_t, scan_insn_ops);
      if (op_addr[0].mode != MIR_OP_REF) scan_error (ctx, "wrong ref operand");
      if (op_addr[1].mode != MIR_OP_INT) scan_error (ctx, "wrong ref disp operand");
      name
        = (VARR_LENGTH (label_name_t, label_names) == 0 ? NULL
                                                        : VARR_GET (label_name_t, label_names, 0));
      MIR_new_ref_data (ctx, name, op_addr[0].u.ref, op_addr[1].u.i);
    } else if (expr_p) {
      if (VARR_LENGTH (MIR_op_t, scan_insn_ops) != 1)
        scan_error (ctx, "expr should have one operand");
      op_addr = VARR_ADDR (MIR_op_t, scan_insn_ops);
      if (op_addr[0].mode != MIR_OP_REF || op_addr[0].u.ref->item_type != MIR_func_item)
        scan_error (ctx, "wrong expr operand");
      name
        = (VARR_LENGTH (label_name_t, label_names) == 0 ? NULL
                                                        : VARR_GET (label_name_t, label_names, 0));
      MIR_new_expr_data (ctx, name, op_addr[0].u.ref);
    } else if (string_p) {
      if (VARR_LENGTH (MIR_op_t, scan_insn_ops) != 1)
        scan_error (ctx, "string should have one operand");
      op_addr = VARR_ADDR (MIR_op_t, scan_insn_ops);
      if (op_addr[0].mode != MIR_OP_STR) scan_error (ctx, "wrong string data operand type");
      name
        = (VARR_LENGTH (label_name_t, label_names) == 0 ? NULL
                                                        : VARR_GET (label_name_t, label_names, 0));
      MIR_new_string_data (ctx, name, op_addr[0].u.str);
    } else if (proto_p) {
      if (module == NULL) scan_error (ctx, "prototype outside module");
      read_func_proto (ctx, VARR_LENGTH (MIR_op_t, scan_insn_ops),
                       VARR_ADDR (MIR_op_t, scan_insn_ops));
      if (dots_p)
        MIR_new_vararg_proto_arr (ctx, VARR_GET (label_name_t, label_names, 0),
                                  VARR_LENGTH (MIR_type_t, scan_types),
                                  VARR_ADDR (MIR_type_t, scan_types),
                                  VARR_LENGTH (MIR_var_t, scan_vars),
                                  VARR_ADDR (MIR_var_t, scan_vars));
      else
        MIR_new_proto_arr (ctx, VARR_GET (label_name_t, label_names, 0),
                           VARR_LENGTH (MIR_type_t, scan_types), VARR_ADDR (MIR_type_t, scan_types),
                           VARR_LENGTH (MIR_var_t, scan_vars), VARR_ADDR (MIR_var_t, scan_vars));
    } else if (func_p) {
      if (module == NULL) scan_error (ctx, "func outside module");
      if (func != NULL) scan_error (ctx, "nested func");
      read_func_proto (ctx, VARR_LENGTH (MIR_op_t, scan_insn_ops),
                       VARR_ADDR (MIR_op_t, scan_insn_ops));
      if (dots_p)
        func = MIR_new_vararg_func_arr (ctx, VARR_GET (label_name_t, label_names, 0),
                                        VARR_LENGTH (MIR_type_t, scan_types),
                                        VARR_ADDR (MIR_type_t, scan_types),
                                        VARR_LENGTH (MIR_var_t, scan_vars),
                                        VARR_ADDR (MIR_var_t, scan_vars));
      else
        func
          = MIR_new_func_arr (ctx, VARR_GET (label_name_t, label_names, 0),
                              VARR_LENGTH (MIR_type_t, scan_types),
                              VARR_ADDR (MIR_type_t, scan_types),
                              VARR_LENGTH (MIR_var_t, scan_vars), VARR_ADDR (MIR_var_t, scan_vars));
      HTAB_CLEAR (label_desc_t, label_desc_tab);
    } else if (end_func_p) {
      if (func == NULL) scan_error (ctx, "standalone endfunc");
      if (VARR_LENGTH (MIR_op_t, scan_insn_ops) != 0)
        scan_error (ctx, "endfunc should have no params");
      func = NULL;
      MIR_finish_func (ctx);
    } else if (export_p || import_p || forward_p) { /* we already created items, now do nothing: */
      mir_assert (VARR_LENGTH (MIR_op_t, scan_insn_ops) == 0);
    } else if (local_p) {
      op_addr = VARR_ADDR (MIR_op_t, scan_insn_ops);
      n = VARR_LENGTH (MIR_op_t, scan_insn_ops);
      for (i = 0; i < n; i++) {
        if (op_addr[i].mode != MIR_OP_MEM || (const char *) op_addr[i].u.mem.disp == NULL)
          scan_error (ctx, "wrong local var");
        MIR_new_func_reg (ctx, func->u.func, op_addr[i].u.mem.type,
                          (const char *) op_addr[i].u.mem.disp);
      }
    } else if (data_type != MIR_T_BOUND) {
      union {
        uint8_t u8;
        uint16_t u16;
        uint32_t u32;
        uint64_t u64;
        int8_t i8;
        int16_t i16;
        int32_t i32;
        int64_t i64;
      } v;

      n = VARR_LENGTH (MIR_op_t, scan_insn_ops);
      op_addr = VARR_ADDR (MIR_op_t, scan_insn_ops);
      VARR_TRUNC (uint8_t, temp_data, 0);
      for (i = 0; i < n; i++) {
        if (op_addr[i].mode != type2mode (data_type))
          scan_error (ctx, "data operand is not of data type");
        switch (data_type) {
        case MIR_T_I8:
          v.i8 = op_addr[i].u.i;
          push_data (ctx, (uint8_t *) &v.i8, sizeof (int8_t));
          break;
        case MIR_T_U8:
          v.u8 = op_addr[i].u.u;
          push_data (ctx, (uint8_t *) &v.u8, sizeof (uint8_t));
          break;
        case MIR_T_I16:
          v.i16 = op_addr[i].u.i;
          push_data (ctx, (uint8_t *) &v.i16, sizeof (int16_t));
          break;
        case MIR_T_U16:
          v.u16 = op_addr[i].u.u;
          push_data (ctx, (uint8_t *) &v.u16, sizeof (uint16_t));
          break;
        case MIR_T_I32:
          v.i32 = op_addr[i].u.i;
          push_data (ctx, (uint8_t *) &v.i32, sizeof (int32_t));
          break;
        case MIR_T_U32:
          v.u32 = op_addr[i].u.u;
          push_data (ctx, (uint8_t *) &v.u32, sizeof (uint32_t));
          break;
        case MIR_T_I64:
          v.i64 = op_addr[i].u.i;
          push_data (ctx, (uint8_t *) &v.i64, sizeof (int64_t));
          break;
        case MIR_T_U64:
          v.u64 = op_addr[i].u.u;
          push_data (ctx, (uint8_t *) &v.u64, sizeof (uint64_t));
          break;
        case MIR_T_F: push_data (ctx, (uint8_t *) &op_addr[i].u.f, sizeof (float)); break;
        case MIR_T_D: push_data (ctx, (uint8_t *) &op_addr[i].u.d, sizeof (double)); break;
        case MIR_T_LD:
          push_data (ctx, (uint8_t *) &op_addr[i].u.ld, sizeof (long double));
          break;
          /* ptr ??? */
        default: scan_error (ctx, "wrong data clause");
        }
      }
      name
        = (VARR_LENGTH (label_name_t, label_names) == 0 ? NULL
                                                        : VARR_GET (label_name_t, label_names, 0));
      MIR_new_data (ctx, name, data_type,
                    VARR_LENGTH (uint8_t, temp_data) / _MIR_type_size (ctx, data_type),
                    VARR_ADDR (uint8_t, temp_data));
    } else {
      insn = MIR_new_insn_arr (ctx, insn_code, VARR_LENGTH (MIR_op_t, scan_insn_ops),
                               VARR_ADDR (MIR_op_t, scan_insn_ops));
      if (func != NULL) MIR_append_insn (ctx, func, insn);
    }
  }
  if (func != NULL) scan_error (ctx, "absent endfunc");
  if (module != NULL) scan_error (ctx, "absent endmodule");
  if (VARR_LENGTH (char, error_msg_buf) != 0)
    MIR_get_error_func (ctx) (MIR_syntax_error, VARR_ADDR (char, error_msg_buf));
}

static void scan_init (MIR_context_t ctx) {
  insn_name_t in, el;
  size_t i;

  if ((ctx->scan_ctx = malloc (sizeof (struct scan_ctx))) == NULL)
    MIR_get_error_func (ctx) (MIR_alloc_error, "Not enough memory for ctx");
  VARR_CREATE (char, error_msg_buf, 0);
  VARR_CREATE (MIR_var_t, scan_vars, 0);
  VARR_CREATE (MIR_type_t, scan_types, 0);
  VARR_CREATE (MIR_op_t, scan_insn_ops, 0);
  VARR_CREATE (label_name_t, label_names, 0);
  HTAB_CREATE (label_desc_t, label_desc_tab, 100, label_hash, label_eq, NULL);
  HTAB_CREATE (insn_name_t, insn_name_tab, MIR_INSN_BOUND, insn_name_hash, insn_name_eq, NULL);
  for (i = 0; i < MIR_INSN_BOUND; i++) {
    in.code = i;
    in.name = MIR_insn_name (ctx, i);
    HTAB_DO (insn_name_t, insn_name_tab, in, HTAB_INSERT, el);
  }
}

static void scan_finish (MIR_context_t ctx) {
  VARR_DESTROY (char, error_msg_buf);
  VARR_DESTROY (MIR_var_t, scan_vars);
  VARR_DESTROY (MIR_type_t, scan_types);
  VARR_DESTROY (MIR_op_t, scan_insn_ops);
  VARR_DESTROY (label_name_t, label_names);
  HTAB_DESTROY (label_desc_t, label_desc_tab);
  HTAB_DESTROY (insn_name_t, insn_name_tab);
  free (ctx->scan_ctx);
  ctx->scan_ctx = NULL;
}

#endif /* if !MIR_NO_SCAN */

/* New Page */

#if defined(__x86_64__) || defined(_M_AMD64)
#include "mir-x86_64.c"
#elif defined(__aarch64__)
#include "mir-aarch64.c"
#elif defined(__PPC64__)
#include "mir-ppc64.c"
#elif defined(__s390x__)
#include "mir-s390x.c"
#else
#error "undefined or unsupported generation target"
#endif

/* New Page */

#include "mir-interp.c"

/* Local Variables:                */
/* mode: c                         */
/* page-delimiter: "/\\* New Page" */
/* End:                            */
