Add machinery to support DFP rounding modes.
Part of fixing BZ 307113.
Patch by Maran <maranp@linux.vnet.ibm.com>


git-svn-id: svn://svn.valgrind.org/vex/trunk@2557 8f6e269a-dfd6-0310-a8e1-e2731360e62c
diff --git a/priv/guest_s390_toIR.c b/priv/guest_s390_toIR.c
index bde121b..0748685 100644
--- a/priv/guest_s390_toIR.c
+++ b/priv/guest_s390_toIR.c
@@ -1465,6 +1465,84 @@
    return mktemp(Ity_I32, rm);
 }
 
+/* Extract the DFP rounding mode from the guest FPC reg and encode it as an
+   IRRoundingMode:
+
+   rounding mode                     | s390  | IR
+   ------------------------------------------------
+   to nearest, ties to even          |  000  | 000
+   to zero                           |  001  | 011
+   to +infinity                      |  010  | 010
+   to -infinity                      |  011  | 001
+   to nearest, ties away from 0      |  100  | 100
+   to nearest, ties toward 0         |  101  | 111
+   to away from 0                    |  110  | 110
+   to prepare for shorter precision  |  111  | 101
+
+   So:  IR = (s390 ^ ((s390 << 1) & 2))
+*/
+#if 0  // fixs390: avoid compiler warnings about unused function
+static IRExpr *
+get_dfp_rounding_mode_from_fpc(void)
+{
+   IRTemp fpc_bits = newTemp(Ity_I32);
+
+   /* The dfp rounding mode is stored in bits [25:27].
+      extract the bits at 25:27 and right shift 4 times. */
+   assign(fpc_bits, binop(Iop_Shr32,
+                          binop(Iop_And32, get_fpc_w0(), mkU32(0x70)),
+                          mkU8(4)));
+
+   IRExpr *rm_s390 = mkexpr(fpc_bits);
+   // rm_IR = (rm_s390 ^ ((rm_s390 << 1) & 2));
+
+   return binop(Iop_Xor32, rm_s390,
+                binop( Iop_And32,
+                       binop(Iop_Shl32, rm_s390, mkU8(1)),
+                       mkU32(2)));
+}
+
+/* Encode the s390 rounding mode as it appears in the m3 field of certain
+   instructions to VEX's IRRoundingMode. */
+static IRTemp
+encode_dfp_rounding_mode(UChar mode)
+{
+   IRExpr *rm;
+
+   switch (mode) {
+   case S390_DFP_ROUND_PER_FPC_0:
+   case S390_DFP_ROUND_PER_FPC_2:
+      rm = get_dfp_rounding_mode_from_fpc(); break;
+   case S390_DFP_ROUND_NEAREST_EVEN_4:
+   case S390_DFP_ROUND_NEAREST_EVEN_8:
+      rm = mkU32(Irrm_DFP_NEAREST); break;
+   case S390_DFP_ROUND_NEAREST_TIE_AWAY_0_1:
+   case S390_DFP_ROUND_NEAREST_TIE_AWAY_0_12:
+      rm = mkU32(Irrm_DFP_NEAREST_TIE_AWAY_0); break;
+   case S390_DFP_ROUND_PREPARE_SHORT_3:
+   case S390_DFP_ROUND_PREPARE_SHORT_15:
+      rm = mkU32(Irrm_DFP_PREPARE_SHORTER); break;
+   case S390_DFP_ROUND_ZERO_5:
+   case S390_DFP_ROUND_ZERO_9:
+      rm = mkU32(Irrm_DFP_ZERO ); break;
+   case S390_DFP_ROUND_POSINF_6:
+   case S390_DFP_ROUND_POSINF_10:
+      rm = mkU32(Irrm_DFP_PosINF); break;
+   case S390_DFP_ROUND_NEGINF_7:
+   case S390_DFP_ROUND_NEGINF_11:
+      rm = mkU32(Irrm_DFP_NegINF); break;
+   case S390_DFP_ROUND_NEAREST_TIE_TOWARD_0:
+      rm = mkU32(Irrm_DFP_NEAREST_TIE_TOWARD_0); break;
+   case S390_DFP_ROUND_AWAY_0:
+      rm = mkU32(Irrm_DFP_AWAY_FROM_ZERO); break;
+   default:
+      vpanic("encode_dfp_rounding_mode");
+   }
+
+   return mktemp(Ity_I32, rm);
+}
+#endif
+
 /*------------------------------------------------------------*/
 /*--- Build IR for formats                                 ---*/
 /*------------------------------------------------------------*/
diff --git a/priv/host_s390_defs.c b/priv/host_s390_defs.c
index 279d83d..6fb6d0b 100644
--- a/priv/host_s390_defs.c
+++ b/priv/host_s390_defs.c
@@ -712,6 +712,10 @@
       addHRegUse(u, HRmRead,  insn->variant.set_fpc_bfprm.mode);
       break;
 
+   case S390_INSN_SET_FPC_DFPRM:
+      addHRegUse(u, HRmRead,  insn->variant.set_fpc_dfprm.mode);
+      break;
+
    case S390_INSN_EVCHECK:
       s390_amode_get_reg_usage(u, insn->variant.evcheck.counter);
       s390_amode_get_reg_usage(u, insn->variant.evcheck.fail_addr);
@@ -941,6 +945,11 @@
          lookupHRegRemap(m, insn->variant.set_fpc_bfprm.mode);
       break;
 
+   case S390_INSN_SET_FPC_DFPRM:
+      insn->variant.set_fpc_dfprm.mode =
+         lookupHRegRemap(m, insn->variant.set_fpc_dfprm.mode);
+      break;
+
    case S390_INSN_EVCHECK:
       s390_amode_map_regs(m, insn->variant.evcheck.counter);
       s390_amode_map_regs(m, insn->variant.evcheck.fail_addr);
@@ -4947,6 +4956,21 @@
 
 
 s390_insn *
+s390_insn_set_fpc_dfprm(UChar size, HReg mode)
+{
+   vassert(size == 4);
+
+   s390_insn *insn = LibVEX_Alloc(sizeof(s390_insn));
+
+   insn->tag  = S390_INSN_SET_FPC_DFPRM;
+   insn->size = size;
+   insn->variant.set_fpc_dfprm.mode = mode;
+
+   return insn;
+}
+
+
+s390_insn *
 s390_insn_xdirect(s390_cc_t cond, Addr64 dst, s390_amode *guest_IA,
                   Bool to_fast_entry)
 {
@@ -5456,6 +5480,11 @@
                    insn->variant.set_fpc_bfprm.mode);
       break;
 
+   case S390_INSN_SET_FPC_DFPRM:
+      s390_sprintf(buf, "%M %R", "v-set-fpc-dfprm",
+                   insn->variant.set_fpc_dfprm.mode);
+      break;
+
    case S390_INSN_EVCHECK:
       s390_sprintf(buf, "%M counter = %A, fail-addr = %A", "v-evcheck",
                    insn->variant.evcheck.counter,
@@ -7535,6 +7564,25 @@
 }
 
 
+static UChar *
+s390_insn_set_fpc_dfprm_emit(UChar *buf, const s390_insn *insn)
+{
+   UInt mode = hregNumber(insn->variant.set_fpc_dfprm.mode);
+
+   /* Copy FPC from guest state to R0 and OR in the new rounding mode */
+   buf = s390_emit_L(buf, R0, 0, S390_REGNO_GUEST_STATE_POINTER,
+                     S390X_GUEST_OFFSET(guest_fpc));   // r0 = guest_fpc
+
+   /* DFP rounding mode is set at bit position 25:27 in FPC register */
+   buf = s390_emit_NILL(buf, R0, 0xFF8F); /* Clear out 25:27 bits */
+   buf = s390_emit_SLL(buf, mode, 0, 4);  /* bring mode to 25:27 bits */
+   buf = s390_emit_OR(buf, R0, mode);     /* OR in the new rounding mode */
+   buf = s390_emit_SFPC(buf, R0);         /* Load FPC register from R0 */
+
+   return buf;
+}
+
+
 /* Define convenience functions needed for translation chaining.
    Any changes need to be applied to the functions in concert. */
 
@@ -8079,6 +8127,10 @@
       end = s390_insn_set_fpc_bfprm_emit(buf, insn);
       break;
 
+   case S390_INSN_SET_FPC_DFPRM:
+      end = s390_insn_set_fpc_dfprm_emit(buf, insn);
+      break;
+
    case S390_INSN_PROFINC:
       end = s390_insn_profinc_emit(buf, insn);
       /* Tell the caller .. */
diff --git a/priv/host_s390_defs.h b/priv/host_s390_defs.h
index 37a7dc9..d8f7dd6 100644
--- a/priv/host_s390_defs.h
+++ b/priv/host_s390_defs.h
@@ -141,6 +141,7 @@
    S390_INSN_GZERO,   /* Assign zero to a guest register */
    S390_INSN_GADD,    /* Add a value to a guest register */
    S390_INSN_SET_FPC_BFPRM, /* Set the bfp rounding mode in the FPC */
+   S390_INSN_SET_FPC_DFPRM, /* Set the dfp rounding mode in the FPC */
    /* The following 5 insns are mandated by translation chaining */
    S390_INSN_XDIRECT,     /* direct transfer to guest address */
    S390_INSN_XINDIR,      /* indirect transfer to guest address */
@@ -254,7 +255,7 @@
 } s390_cc_t;
 
 
-/* Rounding mode as it is encoded in the m3 field of certain
+/* BFP Rounding mode as it is encoded in the m3 field of certain
    instructions (e.g. CFEBR) */
 typedef enum {
    S390_BFP_ROUND_PER_FPC       = 0,
@@ -268,7 +269,7 @@
 } s390_bfp_round_t;
 
 
-/* Rounding mode as it is encoded in bits [29:31] of the FPC register.
+/* BFP Rounding mode as it is encoded in bits [29:31] of the FPC register.
    Only rounding modes 0..3 are universally supported. Others require
    additional hardware facilities. */
 typedef enum {
@@ -281,6 +282,41 @@
 } s390_fpc_bfp_round_t;
 
 
+/* DFP Rounding mode as it is encoded in the m3 field of certain
+   instructions (e.g. CGDTR) */
+typedef enum {
+   S390_DFP_ROUND_PER_FPC_0             = 0,
+   S390_DFP_ROUND_NEAREST_TIE_AWAY_0_1  = 1,
+   S390_DFP_ROUND_PER_FPC_2             = 2,
+   S390_DFP_ROUND_PREPARE_SHORT_3       = 3,
+   S390_DFP_ROUND_NEAREST_EVEN_4        = 4,
+   S390_DFP_ROUND_ZERO_5                = 5,
+   S390_DFP_ROUND_POSINF_6              = 6,
+   S390_DFP_ROUND_NEGINF_7              = 7,
+   S390_DFP_ROUND_NEAREST_EVEN_8        = 8,
+   S390_DFP_ROUND_ZERO_9                = 9,
+   S390_DFP_ROUND_POSINF_10             = 10,
+   S390_DFP_ROUND_NEGINF_11             = 11,
+   S390_DFP_ROUND_NEAREST_TIE_AWAY_0_12 = 12,
+   S390_DFP_ROUND_NEAREST_TIE_TOWARD_0  = 13,
+   S390_DFP_ROUND_AWAY_0                = 14,
+   S390_DFP_ROUND_PREPARE_SHORT_15      = 15
+} s390_dfp_round_t;
+
+
+/* DFP Rounding mode as it is encoded in bits [25:27] of the FPC register. */
+typedef enum {
+   S390_FPC_DFP_ROUND_NEAREST_EVEN     = 0,
+   S390_FPC_DFP_ROUND_ZERO             = 1,
+   S390_FPC_DFP_ROUND_POSINF           = 2,
+   S390_FPC_DFP_ROUND_NEGINF           = 3,
+   S390_FPC_DFP_ROUND_NEAREST_AWAY_0   = 4,
+   S390_FPC_DFP_ROUND_NEAREST_TOWARD_0 = 5,
+   S390_FPC_DFP_ROUND_AWAY_ZERO        = 6,
+   S390_FPC_DFP_ROUND_PREPARE_SHORT    = 7
+} s390_fpc_dfp_round_t;
+
+
 /* Invert the condition code */
 static __inline__ s390_cc_t
 s390_cc_invert(s390_cc_t cond)
@@ -464,6 +500,9 @@
       struct {
          HReg             mode;
       } set_fpc_bfprm;
+      struct {
+         HReg             mode;
+      } set_fpc_dfprm;
 
       /* The next 5 entries are generic to support translation chaining */
 
@@ -557,6 +596,7 @@
 s390_insn *s390_insn_gzero(UChar size, UInt offset);
 s390_insn *s390_insn_gadd(UChar size, UInt offset, UChar delta, ULong value);
 s390_insn *s390_insn_set_fpc_bfprm(UChar size, HReg mode);
+s390_insn *s390_insn_set_fpc_dfprm(UChar size, HReg mode);
 
 /* Five for translation chaining */
 s390_insn *s390_insn_xdirect(s390_cc_t cond, Addr64 dst, s390_amode *guest_IA,
diff --git a/priv/host_s390_isel.c b/priv/host_s390_isel.c
index d42a3ab..6a958d1 100644
--- a/priv/host_s390_isel.c
+++ b/priv/host_s390_isel.c
@@ -114,6 +114,7 @@
    UInt         hwcaps;
 
    IRExpr      *previous_bfp_rounding_mode;
+   IRExpr      *previous_dfp_rounding_mode;
 
    ULong        old_value[NUM_TRACKED_REGS];
 
@@ -607,6 +608,125 @@
 }
 
 
+/*---------------------------------------------------------*/
+/*--- DFP helper functions                              ---*/
+/*---------------------------------------------------------*/
+
+/* Set the DFP rounding mode in the FPC. This function is called for
+   all non-conversion DFP instructions as those will always get the
+   rounding mode from the FPC. */
+#if 0  // fixs390: avoid compiler warnings about unused function
+static void
+set_dfp_rounding_mode_in_fpc(ISelEnv *env, IRExpr *irrm)
+{
+   vassert(typeOfIRExpr(env->type_env, irrm) == Ity_I32);
+
+   /* Do we need to do anything? */
+   if (env->previous_dfp_rounding_mode &&
+       env->previous_dfp_rounding_mode->tag == Iex_RdTmp &&
+       irrm->tag == Iex_RdTmp &&
+       env->previous_dfp_rounding_mode->Iex.RdTmp.tmp == irrm->Iex.RdTmp.tmp) {
+      /* No - new mode is identical to previous mode.  */
+      return;
+   }
+
+   /* No luck - we better set it, and remember what we set it to. */
+   env->previous_dfp_rounding_mode = irrm;
+
+   /* The incoming rounding mode is in VEX IR encoding. Need to change
+      to s390.
+
+      rounding mode                     | S390 |  IR
+      -----------------------------------------------
+      to nearest, ties to even          | 000  | 000
+      to zero                           | 001  | 011
+      to +infinity                      | 010  | 010
+      to -infinity                      | 011  | 001
+      to nearest, ties away from 0      | 100  | 100
+      to nearest, ties toward 0         | 101  | 111
+      to away from 0                    | 110  | 110
+      to prepare for shorter precision  | 111  | 101
+
+      So: s390 = (IR ^ ((IR << 1) & 2))
+   */
+   HReg ir = s390_isel_int_expr(env, irrm);
+
+   HReg mode = newVRegI(env);
+
+   addInstr(env, s390_insn_move(4, mode, ir));
+   addInstr(env, s390_insn_alu(4, S390_ALU_LSH, mode, s390_opnd_imm(1)));
+   addInstr(env, s390_insn_alu(4, S390_ALU_AND, mode, s390_opnd_imm(2)));
+   addInstr(env, s390_insn_alu(4, S390_ALU_XOR, mode, s390_opnd_reg(ir)));
+
+   addInstr(env, s390_insn_set_fpc_dfprm(4, mode));
+}
+
+
+/* This function is invoked for insns that support a specification of
+   a rounding mode in the insn itself. In that case there is no need to
+   stick the rounding mode into the FPC -- a good thing. However, the
+   rounding mode must be known.
+   The IR to s390 encoding is chosen in the range 0:7 except
+   S390_DFP_ROUND_NEAREST_TIE_TOWARD_0 and
+   S390_DFP_ROUND_AWAY_0 which have no choice within the range.
+   Since the s390 dfp rounding mode encoding in 8:15 is not used, the
+   quantum excpetion is not suppressed and this is fine as valgrind does
+   not model this exception.
+
+   Translation table of
+   s390 DFP rounding mode to IRRoundingMode to s390 DFP rounding mode
+
+   s390(S390_DFP_ROUND_)  |  IR(Irrm_DFP_)       |  s390(S390_DFP_ROUND_)
+   --------------------------------------------------------------------
+   NEAREST_TIE_AWAY_0_1   |  NEAREST_TIE_AWAY_0  |  NEAREST_TIE_AWAY_0_1
+   NEAREST_TIE_AWAY_0_12  |     "                |     "
+   PREPARE_SHORT_3        |  PREPARE_SHORTER     |  PREPARE_SHORT_3
+   PREPARE_SHORT_15       |     "                |     "
+   NEAREST_EVEN_4         |  NEAREST             |  NEAREST_EVEN_4
+   NEAREST_EVEN_8         |     "                |     "
+   ZERO_5                 |  ZERO                |  ZERO_5
+   ZERO_9                 |     "                |     "
+   POSINF_6               |  PosINF              |  POSINF_6
+   POSINF_10              |     "                |     "
+   NEGINF_7               |  NegINF              |  NEGINF_7
+   NEGINF_11              |     "                |     "
+   NEAREST_TIE_TOWARD_0   |  NEAREST_TIE_TOWARD_0|  NEAREST_TIE_TOWARD_0
+   AWAY_0                 |  AWAY_FROM_ZERO      |  AWAY_0
+*/
+static s390_dfp_round_t
+get_dfp_rounding_mode(ISelEnv *env, IRExpr *irrm)
+{
+   if (irrm->tag == Iex_Const) {          /* rounding mode is known */
+      vassert(irrm->Iex.Const.con->tag == Ico_U32);
+      IRRoundingMode mode = irrm->Iex.Const.con->Ico.U32;
+
+      switch (mode) {
+      case Irrm_DFP_NEAREST:
+         return S390_DFP_ROUND_NEAREST_EVEN_4;
+      case Irrm_DFP_NegINF:
+         return S390_DFP_ROUND_NEGINF_7;
+      case Irrm_DFP_PosINF:
+         return S390_DFP_ROUND_POSINF_6;
+      case Irrm_DFP_ZERO:
+         return S390_DFP_ROUND_ZERO_5;
+      case Irrm_DFP_NEAREST_TIE_AWAY_0:
+         return S390_DFP_ROUND_NEAREST_TIE_AWAY_0_1;
+      case Irrm_DFP_PREPARE_SHORTER:
+          return S390_DFP_ROUND_PREPARE_SHORT_3;
+      case Irrm_DFP_AWAY_FROM_ZERO:
+         return S390_DFP_ROUND_AWAY_0;
+      case Irrm_DFP_NEAREST_TIE_TOWARD_0:
+         return S390_DFP_ROUND_NEAREST_TIE_TOWARD_0;
+      default:
+         vpanic("get_dfp_rounding_mode");
+      }
+   }
+
+   set_dfp_rounding_mode_in_fpc(env, irrm);
+   return S390_DFP_ROUND_PER_FPC_0;
+}
+#endif
+
 /* CC_S390 holds the condition code in s390 encoding. Convert it to
    VEX encoding
 
@@ -2872,6 +2992,7 @@
    env->vregmapHI = LibVEX_Alloc(env->n_vregmap * sizeof(HReg));
 
    env->previous_bfp_rounding_mode = NULL;
+   env->previous_dfp_rounding_mode = NULL;
 
    /* and finally ... */
    env->hwcaps    = hwcaps_host;