text: /* This file is a part of MIR project.
     Copyright (C) 2018-2021 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;
    void *setjmp_addr; /* used in interpreter to call setjmp directly not from a shim and FFI */
  };
  
  #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
  #define setjmp_addr ctx->setjmp_addr
  
  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 OUT_FLAG (1 << 7)
  
  static const struct insn_desc insn_descs[] = {
    {MIR_MOV, "mov", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FMOV, "fmov", {MIR_OP_FLOAT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DMOV, "dmov", {MIR_OP_DOUBLE | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDMOV, "ldmov", {MIR_OP_LDOUBLE | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_EXT8, "ext8", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_EXT16, "ext16", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_EXT32, "ext32", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UEXT8, "uext8", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UEXT16, "uext16", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UEXT32, "uext32", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_I2F, "i2f", {MIR_OP_FLOAT | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_I2D, "i2d", {MIR_OP_DOUBLE | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_I2LD, "i2ld", {MIR_OP_LDOUBLE | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UI2F, "ui2f", {MIR_OP_FLOAT | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UI2D, "ui2d", {MIR_OP_DOUBLE | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UI2LD, "ui2ld", {MIR_OP_LDOUBLE | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_F2I, "f2i", {MIR_OP_INT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_D2I, "d2i", {MIR_OP_INT | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LD2I, "ld2i", {MIR_OP_INT | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_F2D, "f2d", {MIR_OP_DOUBLE | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_F2LD, "f2ld", {MIR_OP_LDOUBLE | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_D2F, "d2f", {MIR_OP_FLOAT | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_D2LD, "d2ld", {MIR_OP_LDOUBLE | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LD2F, "ld2f", {MIR_OP_FLOAT | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_LD2D, "ld2d", {MIR_OP_DOUBLE | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_NEG, "neg", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_NEGS, "negs", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FNEG, "fneg", {MIR_OP_FLOAT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DNEG, "dneg", {MIR_OP_DOUBLE | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDNEG, "ldneg", {MIR_OP_LDOUBLE | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_ADD, "add", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_ADDS, "adds", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FADD, "fadd", {MIR_OP_FLOAT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DADD, "dadd", {MIR_OP_DOUBLE | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDADD, "ldadd", {MIR_OP_LDOUBLE | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_SUB, "sub", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_SUBS, "subs", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FSUB, "fsub", {MIR_OP_FLOAT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DSUB, "dsub", {MIR_OP_DOUBLE | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDSUB, "ldsub", {MIR_OP_LDOUBLE | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_MUL, "mul", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_MULS, "muls", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FMUL, "fmul", {MIR_OP_FLOAT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DMUL, "dmul", {MIR_OP_DOUBLE | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDMUL, "ldmul", {MIR_OP_LDOUBLE | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_DIV, "div", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_DIVS, "divs", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UDIV, "udiv", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UDIVS, "udivs", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FDIV, "fdiv", {MIR_OP_FLOAT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DDIV, "ddiv", {MIR_OP_DOUBLE | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDDIV, "lddiv", {MIR_OP_LDOUBLE | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_MOD, "mod", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_MODS, "mods", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UMOD, "umod", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UMODS, "umods", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_AND, "and", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_ANDS, "ands", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_OR, "or", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_ORS, "ors", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_XOR, "xor", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_XORS, "xors", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_LSH, "lsh", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_LSHS, "lshs", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_RSH, "rsh", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_RSHS, "rshs", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_URSH, "ursh", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_URSHS, "urshs", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_EQ, "eq", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_EQS, "eqs", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FEQ, "feq", {MIR_OP_INT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DEQ, "deq", {MIR_OP_INT | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDEQ, "ldeq", {MIR_OP_INT | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_NE, "ne", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_NES, "nes", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FNE, "fne", {MIR_OP_INT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DNE, "dne", {MIR_OP_INT | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDNE, "ldne", {MIR_OP_INT | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_LT, "lt", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_LTS, "lts", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_ULT, "ult", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_ULTS, "ults", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FLT, "flt", {MIR_OP_INT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DLT, "dlt", {MIR_OP_INT | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDLT, "ldlt", {MIR_OP_INT | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_LE, "le", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_LES, "les", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_ULE, "ule", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_ULES, "ules", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FLE, "fle", {MIR_OP_INT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DLE, "dle", {MIR_OP_INT | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDLE, "ldle", {MIR_OP_INT | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_GT, "gt", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_GTS, "gts", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UGT, "ugt", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UGTS, "ugts", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FGT, "fgt", {MIR_OP_INT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DGT, "dgt", {MIR_OP_INT | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDGT, "ldgt", {MIR_OP_INT | OUT_FLAG, MIR_OP_LDOUBLE, MIR_OP_LDOUBLE, MIR_OP_BOUND}},
    {MIR_GE, "ge", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_GES, "ges", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UGE, "uge", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_UGES, "uges", {MIR_OP_INT | OUT_FLAG, MIR_OP_INT, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_FGE, "fge", {MIR_OP_INT | OUT_FLAG, MIR_OP_FLOAT, MIR_OP_FLOAT, MIR_OP_BOUND}},
    {MIR_DGE, "dge", {MIR_OP_INT | OUT_FLAG, MIR_OP_DOUBLE, MIR_OP_DOUBLE, MIR_OP_BOUND}},
    {MIR_LDGE, "ldge", {MIR_OP_INT | OUT_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 | OUT_FLAG, MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_BSTART, "bstart", {MIR_OP_INT | OUT_FLAG, MIR_OP_BOUND}},
    {MIR_BEND, "bend", {MIR_OP_INT, MIR_OP_BOUND}},
    {MIR_VA_ARG, "va_arg", {MIR_OP_INT | OUT_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 < OUT_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);
    setjmp_addr = 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_context_t ctx, MIR_type_t tp) {
    int n;
    char str[100];
  
    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_RBLK: return "rblk";
    case MIR_T_UNDEF: return "undef";
    default:
      if (MIR_blk_type_p (tp) && (n = tp - MIR_T_BLK) >= 0 && n < MIR_BLK_NUM) {
        sprintf (str, "blk%d", n);
        return get_ctx_str (ctx, str);
      }
      return "";
    }
  }
  
  const char *MIR_type_str (MIR_context_t ctx, MIR_type_t tp) {
    const char *str = type_str (ctx, 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 (ctx, 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);
  #if defined(MIR_DEBUG)
          fprintf (stderr, "%016llx: %s\n", (unsigned long long) 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);
  }
  
  #define SETJMP_NAME "setjmp"
  #define SETJMP_NAME2 "_setjmp"
  
  void MIR_load_external (MIR_context_t ctx, const char *name, void *addr) {
    if (strcmp (name, SETJMP_NAME) == 0 || (SETJMP_NAME2 != NULL && strcmp (name, SETJMP_NAME2) == 0))
      setjmp_addr = 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 & OUT_FLAG) != 0;
    return *out_p ? mode ^ OUT_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 & OUT_FLAG) != 0;
    return *out_p ? mode ^ OUT_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;
    default: 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 (ctx, 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 (MIR_context_t ctx, FILE *f, MIR_type_t tp) {
    fprintf (f, "%s", MIR_type_str (ctx, 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 (ctx, 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 (MIR_context_t ctx, 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 (ctx, 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 (ctx, var.type), var.name);
      else
        fprintf (f, "%s:%lu(%s)", MIR_type_str (ctx, 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 (ctx, 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 (ctx, 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 (ctx, 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 (ctx, 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>
  
  #if defined(__riscv)
  #define PROT_WRITE_EXEC (PROT_WRITE | PROT_READ | PROT_EXEC)
  #else
  #define PROT_WRITE_EXEC (PROT_WRITE | PROT_EXEC)
  #endif
  #define PROT_READ_EXEC (PROT_READ | PROT_EXEC)
  
  #if defined(__APPLE__) && defined(__aarch64__)
  #include <libkern/OSCacheControl.h>
  #include <pthread.h>
  #endif
  
  static int mem_protect (void *addr, size_t len, int prot) {
  #if !defined(__APPLE__) || !defined(__aarch64__)
    return mprotect (addr, len, prot);
  #else
    int res;
  
    if (!pthread_jit_write_protect_supported_np ()) {
      fprintf (stderr, "unsupported pthread_jit_write_protect_np -- good bye!\n");
      exit (1);
    }
    if (prot & PROT_WRITE) pthread_jit_write_protect_np (FALSE);
    if (prot & PROT_READ) {
      pthread_jit_write_protect_np (TRUE);
      sys_icache_invalidate (addr, len);
    } else if (0) {
      if ((res = mprotect (addr, len, prot)) != 0) {
        perror ("mem_protect");
        fprintf (stderr, "good bye!\n");
        exit (1);
      }
    }
    return res;
  #endif
  }
  
  #define mem_unmap munmap
  
  static void *mem_map (size_t len) {
  #if defined(__APPLE__) && defined(__aarch64__)
    return mmap (NULL, len, PROT_EXEC | PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_JIT,
                 -1, 0);
  #else
    return mmap (NULL, len, PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  #endif
  }
  
  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;
  }
  
  void _MIR_flush_code_cache (void *start, void *bound) {
  #if defined(__GNUC__) && !defined(__MIRC__)
    __builtin___clear_cache (start, bound);
  #endif
  }
  
  #if !defined(MIR_BOOTSTRAP) || !defined(__APPLE__) || !defined(__aarch64__)
  void _MIR_set_code (size_t prot_start, size_t prot_len, uint8_t *base, size_t nloc,
                      const MIR_code_reloc_t *relocs, size_t reloc_size) {
    mem_protect ((uint8_t *) prot_start, prot_len, PROT_WRITE_EXEC);
    if (reloc_size == 0) {
      for (size_t i = 0; i < nloc; i++)
        memcpy (base + relocs[i].offset, &relocs[i].value, sizeof (void *));
    } else {
      for (size_t i = 0; i < nloc; i++) memcpy (base + relocs[i].offset, relocs[i].value, reloc_size);
    }
    mem_protect ((uint8_t *) prot_start, prot_len, PROT_READ_EXEC);
  }
  #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);
    MIR_code_reloc_t reloc;
    reloc.offset = 0;
    reloc.value = code;
    _MIR_set_code ((size_t) ch_ptr->start, ch_ptr->bound - ch_ptr->start, mem, 1, &reloc, code_len);
    _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_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 */
    MIR_code_reloc_t reloc;
    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");
    reloc.offset = 0;
    reloc.value = code;
    _MIR_set_code (start, len, addr, 1, &reloc, code_len);
    _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");
    _MIR_set_code (start, len, base, nloc, relocs, 0);
    _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 */
    va_list args;
    MIR_code_reloc_t relocs[20];
    if (nloc >= 20)
      MIR_get_error_func (ctx) (MIR_wrong_param_value_error, "_MIR_update_code: too many locations");
    va_start (args, nloc);
    for (size_t i = 0; i < nloc; i++) {
      relocs[i].offset = va_arg (args, size_t);
      relocs[i].value = va_arg (args, void *);
    }
    va_end (args);
    _MIR_update_code_arr (ctx, base, nloc, relocs);
  }
  
  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;
  }
  
  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),
    REP5 (TAG_EL, TF, TD, TP, TV, TBLOCK),
    TAG_EL (TRBLOCK) = TAG_EL (TBLOCK) + MIR_BLK_NUM,
    TAG_EL (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)
      REP5 (TAG_CASE, TF, TD, TP, TV, TRBLOCK)
      attr->t = (MIR_type_t) (c - TAG_TI8) + MIR_T_I8;
      break;
    default:
      if (TAG_TBLOCK <= c && c < TAG_TBLOCK + MIR_BLK_NUM) {
        attr->t = (MIR_type_t) (c - TAG_TBLOCK) + MIR_T_BLK;
        break;
      }
      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 (ctx, 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 (ctx, 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 (ctx, 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 (ctx, 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 (ctx, 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) {
    mir_assert (TAG_EOFILE < 127); /* see bin_tag_t */
    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 (strncmp (type_name, "blk", 3) == 0) {
      int i, n = 0;
      for (i = 3; isdigit (type_name[i]) && n < MIR_BLK_NUM; i++) n = n * 10 + (type_name[i] - '0');
      if (type_name[i] == 0 && n < MIR_BLK_NUM) return MIR_T_BLK + n;
    }
    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 */
  
  #ifndef _WIN32
  #include <sys/types.h>
  #include <unistd.h>
  #else
  #define WIN32_LEAN_AND_MEAN
  #include <windows.h>
  #define getpid GetCurrentProcessId
  #define popen _popen
  #define pclose _pclose
  #endif
  
  void _MIR_dump_code (const char *name, int index, uint8_t *code, size_t code_len) {
    size_t i;
    int ch;
    char cfname[50];
    char command[500];
    FILE *f;
  #if !defined(__APPLE__)
    char bfname[30];
    FILE *bf;
  #endif
  
    if (name != NULL) fprintf (stderr, "%s:", name);
    sprintf (cfname, "_mir_%d_%lu.c", index, (unsigned long) getpid ());
    if ((f = fopen (cfname, "w")) == NULL) return;
  #if defined(__APPLE__)
    fprintf (f, "unsigned char code[] = {");
    for (i = 0; i < code_len; i++) {
      if (i != 0) fprintf (f, ", ");
      fprintf (f, "0x%x", code[i]);
    }
    fprintf (f, "};\n");
    fclose (f);
  #if defined(__aarch64__)
    sprintf (command, "gcc -c -o %s.o %s 2>&1 && objdump --section=__data -D %s.o; rm -f %s.o %s",
             cfname, cfname, cfname, cfname, cfname);
  #else
    sprintf (command, "gcc -c -o %s.o %s 2>&1 && objdump --section=.data -D %s.o; rm -f %s.o %s",
             cfname, cfname, cfname, cfname, cfname);
  #endif
  #else
    sprintf (bfname, "_mir_%d_%lu.bin", index, (unsigned long) getpid ());
    if ((bf = fopen (bfname, "w")) == NULL) return;
    fprintf (f, "void code (void) {}\n");
    for (i = 0; i < code_len; i++) fputc (code[i], bf);
    fclose (f);
    fclose (bf);
    sprintf (command,
             "gcc -c -o %s.o %s 2>&1 && objcopy --update-section .text=%s %s.o && objdump "
             "--adjust-vma=0x%llx -d %s.o; rm -f "
             "%s.o %s %s",
             cfname, cfname, bfname, cfname, (unsigned long long) code, cfname, cfname, cfname,
             bfname);
  #endif
    fprintf (stderr, "%s\n", command);
    if ((f = popen (command, "r")) == NULL) return;
    while ((ch = fgetc (f)) != EOF) fprintf (stderr, "%c", ch);
    pclose (f);
  }
  
  /* 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"
  #elif defined(__riscv)
  #if __riscv_xlen != 64 || __riscv_flen < 64 || !__riscv_float_abi_double || !__riscv_mul \
    || !__riscv_div || !__riscv_compressed
  #error "only 64-bit RISCV supported (at least rv64imafdc)"
  #endif
  #if __riscv_flen == 128
  #error "RISCV 128-bit floats (Q set) is not supported"
  #endif
  #include "mir-riscv64.c"
  #else
  #error "undefined or unsupported generation target"
  #endif
  
  /* New Page */
  
  #include "mir-interp.c"
  
  /* Local Variables:                */
  /* mode: c                         */
  /* page-delimiter: "/\\* New Page" */
  /* End:                            */
  