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

/* Long doubles (-mlong-double=128) are always passed by its address (for args and results) */

/* BLK..BLK5 and RBLK args are always passed by address.  */

#if 0 && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#error "s390x works only in BE mode"
#endif

#define VA_LIST_IS_ARRAY_P 1 /* one element array of struct s390x_va_list */

#define S390X_STACK_HEADER_SIZE 160

static uint8_t *push_insns (VARR (uint8_t) * insn_varr, const uint8_t *pat, size_t pat_len) {
  for (size_t i = 0; i < pat_len; i++) VARR_PUSH (uint8_t, insn_varr, pat[i]);
  return VARR_ADDR (uint8_t, insn_varr) + VARR_LENGTH (uint8_t, insn_varr) - pat_len;
}

static void s390x_gen_mov (VARR (uint8_t) * insn_varr, unsigned to, unsigned from) {
  uint32_t lgr = (0xb904 << 16) | (to << 4) | from; /* lgr to,from: */
  assert (to < 16 && from < 16);
  push_insns (insn_varr, (uint8_t *) &lgr, 4);
}

static void s390x_gen_mvi (VARR (uint8_t) * insn_varr, int val, unsigned base, int disp) {
  uint64_t mvghi /* mvghi disp(base), val: */
    = ((0xe548l << 32) | ((uint64_t) base << 28) | ((disp & 0xfff) << 16) | (val & 0xffff)) << 16;
  assert (base < 16 && 0 <= disp && disp < (1 << 12) && -(1 << 15) < val && val < (1 << 15));
  push_insns (insn_varr, (uint8_t *) &mvghi, 6);
}

static void s390x_gen_ld_st (VARR (uint8_t) * insn_varr, unsigned reg, unsigned base, int disp,
                             MIR_type_t type, int ld_p) {
  int single_p = type == MIR_T_F;
  int double_p = type == MIR_T_D;
  uint64_t dl = disp & 0xfff, dh = (disp >> 12) & 0xff;
  uint64_t common = ((uint64_t) reg << 36) | ((uint64_t) base << 28) | (dl << 16) | (dh << 8);
  uint64_t lgopcode
    = (type == MIR_T_I8
         ? (ld_p ? 0x77 : 0x72)
         : type == MIR_T_U8 ? (ld_p ? 0x90 : 0x72)
                            : type == MIR_T_I16
                                ? (ld_p ? 0x78 : 0x70)
                                : type == MIR_T_U16
                                    ? (ld_p ? 0x91 : 0x70)
                                    : type == MIR_T_I32 ? (ld_p ? 0x14 : 0x50)
                                                        : type == MIR_T_U32 ? (ld_p ? 0x16 : 0x50)
                                                                            : (ld_p ? 0x04 : 0x24));
  uint64_t g = ((0xe3l << 40) | common | lgopcode) << 16;
  uint64_t ey = ((0xedl << 40) | common | (ld_p ? 0x64 : 0x66)) << 16;
  uint64_t dy = ((0xedl << 40) | common | (ld_p ? 0x65 : 0x67)) << 16;
  /* (lg|lgf|llgf|lgb|llgc|lhy|llgh|ley|ldy|stg|sty|sthy|stcy|stey|stdy) reg, disp(base): */
  assert (type != MIR_T_LD && reg < 16 && base < 16 && -(1 << 19) < disp && disp < (1 << 19));
  push_insns (insn_varr, (uint8_t *) (single_p ? &ey : double_p ? &dy : &g), 6);
}

static void s390x_gen_ld (VARR (uint8_t) * insn_varr, unsigned to, unsigned base, int disp,
                          MIR_type_t type) {
  s390x_gen_ld_st (insn_varr, to, base, disp, type, TRUE);
}

static void s390x_gen_st (VARR (uint8_t) * insn_varr, unsigned from, unsigned base, int disp,
                          MIR_type_t type) {
  s390x_gen_ld_st (insn_varr, from, base, disp, type, FALSE);
}

static void s390x_gen_ldstm (VARR (uint8_t) * insn_varr, unsigned from, unsigned to, unsigned base,
                             int disp, int ld_p) {
  uint64_t dl = disp & 0xfff, dh = (disp >> 12) & 0xff;
  uint64_t common = ((uint64_t) from << 36) | ((uint64_t) to << 32) | ((uint64_t) base << 28)
                    | (dl << 16) | (dh << 8);
  uint64_t g = ((0xebl << 40) | common | (ld_p ? 0x4 : 0x24)) << 16;
  /* (lmg|stmg) from,to,disp(base): */
  assert (from < 16 && to < 16 && base < 16 && -(1 << 19) < disp && disp < (1 << 19));
  push_insns (insn_varr, (uint8_t *) &g, 6);
}

static void s390x_gen_jump (VARR (uint8_t) * insn_varr, unsigned int reg, int call_p) {
  uint16_t bcr = (0x7 << 8) | (15 << 4) | reg;  /* bcr 15,reg: */
  uint16_t balr = (0x5 << 8) | (14 << 4) | reg; /* balr 14,reg: */
  assert (reg < 16);
  push_insns (insn_varr, (uint8_t *) (call_p ? &balr : &bcr), 2);
}

static void s390x_gen_addi (VARR (uint8_t) * insn_varr, unsigned dst, unsigned src, int disp) {
  uint64_t dl = disp & 0xfff, dh = (disp >> 12) & 0xff;
  uint64_t ops = ((uint64_t) dst << 36) | ((uint64_t) src << 28) | (dl << 16) | (dh << 8);
  uint64_t lay = ((0xe3l << 40) | ops | 0x71) << 16; /* lay dst,disp(src) */
  assert (dst < 16 && src < 16 && -(1 << 19) < disp && disp < (1 << 19));
  push_insns (insn_varr, (uint8_t *) &lay, 6);
}

static void s390x_gen_3addrs (VARR (uint8_t) * insn_varr, unsigned int r1, void *a1,
                              unsigned int r2, void *a2, unsigned int r3, void *a3) {
  /* 6b:lalr r3,22+align;6b:lg r1,0(r3);6b:lg r2,8(r3);6b:lg r3,16(r3);4b:bc m15,28;align;a1-a3 */
  size_t rem = (VARR_LENGTH (uint8_t, insn_varr) + 28) % 8;
  size_t padding = rem == 0 ? 0 : 8 - rem;
  uint64_t lalr = ((0xc0l << 40) | ((uint64_t) r1 << 36) | (28 + padding) / 2) << 16;
  uint32_t brc = (0xa7 << 24) | (15 << 20) | (4 << 16) | (28 + padding) / 2; /* brc m15,28: */
  assert (r1 != 0);
  push_insns (insn_varr, (uint8_t *) &lalr, 6);
  s390x_gen_ld (insn_varr, r3, r1, 16, MIR_T_I64); /* lg r3,16(r1) */
  s390x_gen_ld (insn_varr, r2, r1, 8, MIR_T_I64);  /* lg r2,8(r1) */
  s390x_gen_ld (insn_varr, r1, r1, 0, MIR_T_I64);  /* lg r1,0(r1) */
  push_insns (insn_varr, (uint8_t *) &brc, 4);
  for (size_t i = 0; i < padding; i++) VARR_PUSH (uint8_t, insn_varr, 0);
  push_insns (insn_varr, (uint8_t *) &a1, 8);
  push_insns (insn_varr, (uint8_t *) &a2, 8);
  push_insns (insn_varr, (uint8_t *) &a3, 8);
}

static void s390x_gen_blk_mov (VARR (uint8_t) * insn_varr, uint32_t param_offset,
                               uint32_t addr_offset, uint32_t qwords, uint32_t addr_reg) {
  uint16_t *addr;
  static const uint16_t blk_mov_pat[] = {
    /*0:*/ 0xa7a9,  0x0000,         /* lghi	%r10,<size> */
    /*4:*/ 0xa7ab,  0xfff8,         /* aghi	%r10,-8 */
    /*8:*/ 0xe30a,  0x9000, 0x0004, /* lg %r0,0(%r10,%r9) */
    /*14:*/ 0xe30a, 0x0000, 0x0024, /* stg %r0,0(%r10,<addr_reg:2-6,8>) */
    /*20:*/ 0xb902, 0x00aa,         /* ltgr %r10,%r10 */
    /*24:*/ 0xa724, 0xfff6,         /* jh 4 */
  };
  s390x_gen_addi (insn_varr, addr_reg, 15, addr_offset); /* lay <addr_reg>,addr_offset(r15) */
  if (qwords == 0) return;
  assert (qwords * 8 < (1 << 15) && addr_reg < 16 && addr_offset % 8 == 0);
  s390x_gen_ld (insn_varr, 9, 7, param_offset, MIR_T_I64); /* lg* 9,param_offset(r7) */
  addr = (uint16_t *) push_insns (insn_varr, (uint8_t *) blk_mov_pat, sizeof (blk_mov_pat));
  addr[1] |= qwords * 8;     /* lghi */
  addr[8] |= addr_reg << 12; /* stg */
}

void *_MIR_get_bstart_builtin (MIR_context_t ctx) {
  VARR (uint8_t) * code;
  void *res;

  VARR_CREATE (uint8_t, code, 128);
  s390x_gen_mov (code, 2, 15);      /* lgr r2,15 */
  s390x_gen_jump (code, 14, FALSE); /* bcr m15,r14 */
  res = _MIR_publish_code (ctx, VARR_ADDR (uint8_t, code), VARR_LENGTH (uint8_t, code));
  VARR_DESTROY (uint8_t, code);
  return res;
}

void *_MIR_get_bend_builtin (MIR_context_t ctx) {
  VARR (uint8_t) * code;
  void *res;

  VARR_CREATE (uint8_t, code, 128);
  s390x_gen_ld (code, 0, 15, 0, MIR_T_I64); /* r0 = 0(r15) */
  s390x_gen_st (code, 0, 2, 0, MIR_T_I64);  /* 0(r2) = r0 */
  s390x_gen_mov (code, 15, 2);              /* lgr r15,2 */
  s390x_gen_jump (code, 14, FALSE);         /* bcr m15,r14 */
  res = _MIR_publish_code (ctx, VARR_ADDR (uint8_t, code), VARR_LENGTH (uint8_t, code));
  VARR_DESTROY (uint8_t, code);
  return res;
}

void *_MIR_get_thunk (MIR_context_t ctx) {
  const int max_thunk_len = (4 * 8); /* see _MIR_redirect_thunk */
  VARR (uint8_t) * code;
  void *res;

  VARR_CREATE (uint8_t, code, 128);
  for (int i = 0; i < max_thunk_len; i++) VARR_PUSH (uint8_t, code, 0);
  res = _MIR_publish_code (ctx, VARR_ADDR (uint8_t, code), VARR_LENGTH (uint8_t, code));
  VARR_DESTROY (uint8_t, code);
  return res;
}

void _MIR_redirect_thunk (MIR_context_t ctx, void *thunk, void *to) {
  int64_t offset = (uint8_t *) to - (uint8_t *) thunk;
  VARR (uint8_t) * code;

  VARR_CREATE (uint8_t, code, 128);
  assert (offset % 2 == 0);
  offset /= 2;
  if (-(1l << 31) < offset && offset < (1l << 31)) { /* brcl m15,offset: */
    uint64_t brcl = ((0xc0l << 40) | (15l << 36) | (4l << 32) | offset & 0xffffffff) << 16;
    push_insns (code, (uint8_t *) &brcl, 6);
  } else { /* 6b:lalr r1,8+padding; 6b:lg r1,0(r1); 2b:bcr m15,r1;padding; 64-bit address: */
    size_t rem = (VARR_LENGTH (uint8_t, code) + 14) % 8;
    size_t padding = rem == 0 ? 0 : 8 - rem;
    uint64_t lalr = ((0xc0l << 40) | (1l << 36) | (14 + padding) / 2) << 16;
    uint64_t lg = ((0xe3l << 40) | (1l << 36) | (1l << 28) | 0x4) << 16;
    uint16_t bcr = (0x7 << 8) | (15 << 4) | 1; /* bcr 15,r1: */
    push_insns (code, (uint8_t *) &lalr, 6);
    push_insns (code, (uint8_t *) &lg, 6);
    push_insns (code, (uint8_t *) &bcr, 2);
    for (size_t i = 0; i < padding; i++) VARR_PUSH (uint8_t, code, 0);
    push_insns (code, (uint8_t *) &to, 8);
  }
  _MIR_change_code (ctx, thunk, VARR_ADDR (uint8_t, code), VARR_LENGTH (uint8_t, code));
  VARR_DESTROY (uint8_t, code);
}

struct s390x_va_list {
  long __gpr, __fpr;         /* number of args read until now */
  void *__overflow_arg_area; /* argument on the stack to read next */
  void *__reg_save_area;     /* curr func frame start */
};

void *va_arg_builtin (void *p, uint64_t t) {
  struct s390x_va_list *va = p;
  MIR_type_t type = t;
  int fp_p = type == MIR_T_F || type == MIR_T_D;
  void *a;

  if (!fp_p) {
    if (va->__gpr < 5) {
      a = (char *) va->__reg_save_area + 16 + 8 * va->__gpr;
    } else {
      a = va->__overflow_arg_area;
      va->__overflow_arg_area = (char *) va->__overflow_arg_area + 8;
    }
    va->__gpr++;
    if (type == MIR_T_LD) a = *(void **) a; /* always passed by address */
  } else {
    if (va->__fpr < 4) {
      a = (char *) va->__reg_save_area + 128 + 8 * va->__fpr;
    } else {
      a = va->__overflow_arg_area;
      va->__overflow_arg_area = (char *) va->__overflow_arg_area + 8;
    }
    va->__fpr++;
  }
  if (type == MIR_T_F || type == MIR_T_I32) a = (char *) a + 4; /* 2nd word of doubleword */
  return a;
}

void va_block_arg_builtin (void *res, void *p, size_t s, uint64_t ncase) {
  memcpy (res, *(void **) va_arg_builtin (p, MIR_T_I64), s);
}

void va_start_interp_builtin (MIR_context_t ctx, void *p, void *a) {
  struct s390x_va_list *va = p;
  va_list *vap = a;

  assert (sizeof (struct s390x_va_list) == sizeof (va_list));
  *va = *(struct s390x_va_list *) vap;
}

void va_end_interp_builtin (MIR_context_t ctx, void *p) {}

/* Generation: fun (fun_addr, res_arg_addresses):
   save r6, r7, r14 (r15 + 48,112);
   allocate and stack frame (S390X_STACK_HEADER_SIZE + param area size + ld arg values size);
   r1=r2 (fun_addr);
   r7=r3 (res_arg_addresses);
   (arg_reg=mem[r7,arg_offset] or
   (f1,r0)=mem[r7,arg_offset];mem[r15,S390X_STACK_HEADER_SIZE+offset]=(f1,r0)) ... call *r1;
   r0=mem[r7,<res_offset>]; res_reg=mem[r0]; ...
   restore r15; restore r6, r7, r14; return. */
void *_MIR_get_ff_call (MIR_context_t ctx, size_t nres, MIR_type_t *res_types, size_t nargs,
                        _MIR_arg_desc_t *arg_descs, int vararg_p) {
  MIR_type_t type;
  int n_gpregs = 0, n_fpregs = 0, res_reg = 7, frame_size, disp, param_offset, blk_offset;
  uint32_t qwords, addr_reg;
  VARR (uint8_t) * code;
  void *res;

  VARR_CREATE (uint8_t, code, 128);
  blk_offset = frame_size = S390X_STACK_HEADER_SIZE;
  if (nres > 0 && res_types[0] == MIR_T_LD) n_gpregs++; /* ld address */
  for (uint32_t i = 0; i < nargs; i++) {                /* calculate param area size: */
    type = arg_descs[i].type;
    if (MIR_blk_type_p (type)) frame_size += (arg_descs[i].size + 7) / 8; /* blk value space */
    if ((type == MIR_T_F || type == MIR_T_D) && n_fpregs < 4) {
      n_fpregs++;
    } else if (type != MIR_T_F && type != MIR_T_D && n_gpregs < 5) { /* RBLK too */
      n_gpregs++;
    } else {
      frame_size += 8;
      blk_offset += 8;
    }
  }
  s390x_gen_ldstm (code, 6, 7, 15, 48, FALSE); /* stmg 6,7,48(r15) : */
  s390x_gen_ldstm (code, 8, 9, 15, 64, FALSE); /* stmg 8,9,64(r15) : */
  s390x_gen_st (code, 10, 15, 80, MIR_T_I64);  /* stg r10,80(r15) */
  s390x_gen_st (code, 14, 15, 112, MIR_T_I64); /* stg r14,112(r15) */
  s390x_gen_addi (code, 15, 15, -frame_size);  /* lay r15,-frame_size(r15) */
  s390x_gen_mov (code, 1, 2);                  /* fun_addr */
  s390x_gen_mov (code, res_reg, 3);            /* results & args */
  n_gpregs = n_fpregs = 0;
  param_offset = nres * 16;                   /* args start */
  disp = S390X_STACK_HEADER_SIZE;             /* param area start */
  if (nres > 0 && res_types[0] == MIR_T_LD) { /* ld address: */
    s390x_gen_mov (code, 2, res_reg);         /* lgr r2,r7 */
    n_gpregs++;
  }
  for (uint32_t i = 0; i < nargs; i++) { /* load args: */
    type = arg_descs[i].type;
    if ((type == MIR_T_F || type == MIR_T_D) && n_fpregs < 4) {
      /* (le,ld) (f0,f2,f4,f6),param_ofset(r7) */
      s390x_gen_ld (code, n_fpregs * 2, res_reg, param_offset, type);
      n_fpregs++;
    } else if (type == MIR_T_F || type == MIR_T_D) {
      s390x_gen_ld (code, 1, res_reg, param_offset, type); /* (le,ld) f1,param_offset(r7) */
      s390x_gen_st (code, 1, 15, disp, type);              /* (ste,std) f1,disp(r15) */
      disp += 8;
    } else if (type == MIR_T_LD && n_gpregs < 5) {                /* ld address */
      s390x_gen_addi (code, n_gpregs + 2, res_reg, param_offset); /* lay rn,param_offset(r7) */
      n_gpregs++;
    } else if (type == MIR_T_LD) {                     /* pass address of location in the result: */
      s390x_gen_addi (code, 0, res_reg, param_offset); /* lay r0,param_offset(r7) */
      s390x_gen_st (code, 0, 15, disp, MIR_T_I64);     /* stg r0,disp(r15) */
      disp += 8;
    } else if (MIR_blk_type_p (type)) {
      qwords = (arg_descs[i].size + 7) / 8;
      addr_reg = n_gpregs < 5 ? n_gpregs + 2 : 8;
      s390x_gen_blk_mov (code, param_offset, blk_offset, qwords, addr_reg);
      blk_offset += qwords * 8;
      if (n_gpregs < 5) {
        n_gpregs++;
      } else {
        s390x_gen_st (code, 8, 15, disp, MIR_T_I64); /* stg r8,disp(r15) */
        disp += 8;
      }
    } else if (n_gpregs < 5) { /* RBLK too */
      s390x_gen_ld (code, n_gpregs + 2, res_reg, param_offset,
                    MIR_T_I64); /* lg* rn,param_offset(r7) */
      n_gpregs++;
    } else {
      s390x_gen_ld (code, 0, res_reg, param_offset, MIR_T_I64); /* lg* r0,param_offset(r7) */
      s390x_gen_st (code, 0, 15, disp, MIR_T_I64);              /* stg* r0,disp(r15) */
      disp += 8;
    }
    param_offset += 16;
  }
  s390x_gen_jump (code, 1, TRUE); /* call *r1 */
  n_gpregs = n_fpregs = 0;
  disp = 0;
  for (uint32_t i = 0; i < nres; i++) {
    type = res_types[i];
    if (type == MIR_T_LD) continue; /* do nothing: the result value is already in results */
    if ((type == MIR_T_F || type == MIR_T_D) && n_fpregs < 4) {
      s390x_gen_st (code, n_fpregs * 2, res_reg, disp, type);
      n_fpregs++;
    } else if (type != MIR_T_F && type != MIR_T_D && n_gpregs < 1) {  // just one gp reg
      s390x_gen_st (code, n_gpregs + 2, res_reg, disp, MIR_T_I64);
      n_gpregs++;
    } else {
      MIR_get_error_func (ctx) (MIR_ret_error,
                                "s390x can not handle this combination of return values");
    }
    disp += 16;
  }
  s390x_gen_addi (code, 15, 15, frame_size);   /* lay 15,frame_size(15) */
  s390x_gen_ldstm (code, 6, 7, 15, 48, TRUE);  /* lmg 6,7,48(r15) : */
  s390x_gen_ldstm (code, 8, 9, 15, 64, TRUE);  /* lmg 8,9,64(r15) : */
  s390x_gen_ld (code, 10, 15, 80, MIR_T_I64);  /* lg 10,80(r15) */
  s390x_gen_ld (code, 14, 15, 112, MIR_T_I64); /* lg 14,112(r15) */
  s390x_gen_jump (code, 14, FALSE);            /* bcr m15,r14 */
  res = _MIR_publish_code (ctx, VARR_ADDR (uint8_t, code), VARR_LENGTH (uint8_t, code));
  VARR_DESTROY (uint8_t, code);
  return res;
}

/* Transform C call to call of void handler (MIR_context_t ctx, MIR_item_t func_item,
                                             va_list va, MIR_val_t *results):
   Brief: save all C call args to register save area; save r7, r14;
          allocate shim stack frame (S390X_STACK_HEADER_SIZE + space for results and va);
          call handler with args; move results to return regs; restore r7,r14,r15; return */
void *_MIR_get_interp_shim (MIR_context_t ctx, MIR_item_t func_item, void *handler) {
  MIR_func_t func = func_item->u.func;
  uint32_t nres = func->nres, nargs = func->nargs;
  MIR_type_t type, *res_types = func->res_types;
  int disp, frame_size, local_var_size, n_gpregs, n_fpregs, va_list_disp, results_disp;
  VARR (uint8_t) * code;
  void *res;

  VARR_CREATE (uint8_t, code, 128);
  frame_size = S390X_STACK_HEADER_SIZE;        /* register save area */
  s390x_gen_st (code, 14, 15, 112, MIR_T_I64); /* stg 14,112(r15) */
  s390x_gen_ldstm (code, 2, 6, 15, 16, FALSE); /* stmg 2,6,16(r15) : */
  for (unsigned reg = 0; reg <= 6; reg += 2)   /* stdy f0,f2,f4,f6,128(r15) : */
    s390x_gen_st (code, reg, 15, reg * 4 + 128, MIR_T_D);
  local_var_size = sizeof (struct s390x_va_list) + nres * 16; /* allocate va and results */
  va_list_disp = frame_size;
  results_disp = va_list_disp + sizeof (struct s390x_va_list);
  frame_size += local_var_size;
  assert (frame_size % 8 == 0);
  s390x_gen_addi (code, 15, 15, -frame_size);
  /* setup va: mvghi va(15),(0,1): __gpr */
  s390x_gen_mvi (code, nres > 0 && res_types[0] == MIR_T_LD ? 1 : 0, 15, va_list_disp);
  s390x_gen_mvi (code, 0, 15, va_list_disp + 8);            /* mvghi va+8(15),0: __fpr */
  s390x_gen_addi (code, 1, 15, frame_size);                 /* lay 1,frame_size(15) */
  s390x_gen_st (code, 1, 15, va_list_disp + 24, MIR_T_I64); /* stg 1,va+24(r15): __reg_save_area */
  s390x_gen_addi (code, 1, 1, S390X_STACK_HEADER_SIZE);     /* lay 1,S390X_STACK_HEADER_SIZE(1) */
  /* stg 1,va+16(r15):__overflow_arg_area: */
  s390x_gen_st (code, 1, 15, va_list_disp + 16, MIR_T_I64);
  /* call handler: */
  s390x_gen_3addrs (code, 2, ctx, 3, func_item, 1, handler);
  s390x_gen_addi (code, 4, 15, va_list_disp);
  s390x_gen_addi (code, 5, 15, results_disp);
  s390x_gen_jump (code, 1, TRUE);
  /* setup result regs: */
  disp = results_disp;
  n_gpregs = n_fpregs = 0;
  for (uint32_t i = 0; i < nres; i++) {
    type = res_types[i];
    if ((type == MIR_T_F || type == MIR_T_D) && n_fpregs < 4) {
      s390x_gen_ld (code, n_fpregs * 2, 15, disp, type);
      n_fpregs++;
    } else if (type != MIR_T_F && type != MIR_T_D && n_gpregs < 1) {  // just one gp reg
      if (type != MIR_T_LD) {
        s390x_gen_ld (code, n_gpregs + 2, 15, disp, MIR_T_I64);
      } else {
        /* ld address: lg r2,16+frame_size(r15)  */
        s390x_gen_ld (code, 2, 15, 16 + frame_size, MIR_T_I64);
        s390x_gen_ld (code, 0, 15, disp, MIR_T_D);     /* ld f0,disp(r15) */
        s390x_gen_ld (code, 2, 15, disp + 8, MIR_T_D); /* ld f2,disp + 8(r15) */
        s390x_gen_st (code, 0, 2, 0, MIR_T_D);         /* st f0,0(r2) */
        s390x_gen_st (code, 2, 2, 8, MIR_T_D);         /* st f2,8(r2) */
      }
      n_gpregs++;
    } else {
      MIR_get_error_func (ctx) (MIR_ret_error,
                                "s390x can not handle this combination of return values");
    }
    disp += 16;
  }
  s390x_gen_addi (code, 15, 15, frame_size);   /* lay 15,frame_size(15) */
  s390x_gen_ld (code, 6, 15, 48, MIR_T_I64);   /* lg 6,48(r15) : */
  s390x_gen_ld (code, 14, 15, 112, MIR_T_I64); /* lg 14,112(r15) */
  s390x_gen_jump (code, 14, FALSE);            /* bcr m15,r14 */
  res = _MIR_publish_code (ctx, VARR_ADDR (uint8_t, code), VARR_LENGTH (uint8_t, code));
  VARR_DESTROY (uint8_t, code);
  return res;
}

/* Brief: save r14 (r15+120); save all param regs r2-r6 (r15+16),f0,f2,f4,f6 (r15+128);
   update r15; allocate and form minimal wrapper stack frame (S390X_STACK_HEADER_SIZE);
   r2 = call hook_address (ctx, called_func); r1=r2; restore all params regs, r15, r14; bcr r1 */
void *_MIR_get_wrapper (MIR_context_t ctx, MIR_item_t called_func, void *hook_address) {
  int frame_size = S390X_STACK_HEADER_SIZE;
  VARR (uint8_t) * code;
  void *res;

  VARR_CREATE (uint8_t, code, 128);
  s390x_gen_st (code, 14, 15, 112, MIR_T_I64); /* stg 14,112(r15) */
  s390x_gen_ldstm (code, 2, 6, 15, 16, FALSE); /* stmg 2,6,16(r15) : */
  for (unsigned reg = 0; reg <= 6; reg += 2)   /* stdy f0,f2,f4,f6,128(r15) : */
    s390x_gen_st (code, reg, 15, reg * 4 + 128, MIR_T_D);
  /* r15 -= frame_size: */
  s390x_gen_addi (code, 15, 15, -frame_size);
  s390x_gen_3addrs (code, 2, ctx, 3, called_func, 4, hook_address);
  s390x_gen_jump (code, 4, TRUE);
  s390x_gen_mov (code, 1, 2);
  s390x_gen_addi (code, 15, 15, frame_size);
  for (unsigned reg = 0; reg <= 6; reg += 2) /* ldy fn,disp(r15) : */
    s390x_gen_ld (code, reg, 15, reg * 4 + 128, MIR_T_D);
  s390x_gen_ldstm (code, 2, 6, 15, 16, TRUE);  /* lmg 2,6,16(r15) : */
  s390x_gen_ld (code, 14, 15, 112, MIR_T_I64); /* lg 14,112(r15) */
  s390x_gen_jump (code, 1, FALSE);
  res = _MIR_publish_code (ctx, VARR_ADDR (uint8_t, code), VARR_LENGTH (uint8_t, code));
  VARR_DESTROY (uint8_t, code);
  return res;
}
