blob: 6d980b57ffef2720878f5bee82ed9fbf2f2e83b1 [file] [log] [blame]
/* -*- mode: C; c-basic-offset: 3; -*- */
/*---------------------------------------------------------------*/
/*--- begin host_s390_isel.c ---*/
/*---------------------------------------------------------------*/
/*
This file is part of Valgrind, a dynamic binary instrumentation
framework.
Copyright IBM Corp. 2010-2013
Copyright (C) 2012-2013 Florian Krohm (britzel@acm.org)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA.
The GNU General Public License is contained in the file COPYING.
*/
/* Contributed by Florian Krohm */
#include "libvex_basictypes.h"
#include "libvex_ir.h"
#include "libvex.h"
#include "libvex_s390x_common.h"
#include "main_util.h"
#include "main_globals.h"
#include "guest_s390_defs.h" /* S390X_GUEST_OFFSET */
#include "host_generic_regs.h"
#include "host_s390_defs.h"
/*---------------------------------------------------------*/
/*--- ISelEnv ---*/
/*---------------------------------------------------------*/
/* This carries around:
- A mapping from IRTemp to IRType, giving the type of any IRTemp we
might encounter. This is computed before insn selection starts,
and does not change.
- A mapping from IRTemp to HReg. This tells the insn selector
which virtual register(s) are associated with each IRTemp
temporary. This is computed before insn selection starts, and
does not change. We expect this mapping to map precisely the
same set of IRTemps as the type mapping does.
- vregmap holds the primary register for the IRTemp.
- vregmapHI holds the secondary register for the IRTemp,
if any is needed. That's only for Ity_I64 temps
in 32 bit mode or Ity_I128 temps in 64-bit mode.
- The code array, that is, the insns selected so far.
- A counter, for generating new virtual registers.
- The host subarchitecture we are selecting insns for.
This is set at the start and does not change.
- A Bool for indicating whether we may generate chain-me
instructions for control flow transfers, or whether we must use
XAssisted.
- The maximum guest address of any guest insn in this block.
Actually, the address of the highest-addressed byte from any insn
in this block. Is set at the start and does not change. This is
used for detecting jumps which are definitely forward-edges from
this block, and therefore can be made (chained) to the fast entry
point of the destination, thereby avoiding the destination's
event check.
- Values of certain guest registers which are often assigned constants.
*/
/* Symbolic names for guest registers whose value we're tracking */
enum {
GUEST_IA,
GUEST_CC_OP,
GUEST_CC_DEP1,
GUEST_CC_DEP2,
GUEST_CC_NDEP,
GUEST_SYSNO,
GUEST_COUNTER,
GUEST_UNKNOWN /* must be the last entry */
};
/* Number of registers we're tracking. */
#define NUM_TRACKED_REGS GUEST_UNKNOWN
typedef struct {
IRTypeEnv *type_env;
HInstrArray *code;
HReg *vregmap;
HReg *vregmapHI;
UInt n_vregmap;
UInt vreg_ctr;
UInt hwcaps;
IRExpr *previous_bfp_rounding_mode;
IRExpr *previous_dfp_rounding_mode;
ULong old_value[NUM_TRACKED_REGS];
/* The next two are for translation chaining */
Addr64 max_ga;
Bool chaining_allowed;
Bool old_value_valid[NUM_TRACKED_REGS];
} ISelEnv;
/* Forward declarations */
static HReg s390_isel_int_expr(ISelEnv *, IRExpr *);
static s390_amode *s390_isel_amode(ISelEnv *, IRExpr *);
static s390_cc_t s390_isel_cc(ISelEnv *, IRExpr *);
static s390_opnd_RMI s390_isel_int_expr_RMI(ISelEnv *, IRExpr *);
static void s390_isel_int128_expr(HReg *, HReg *, ISelEnv *, IRExpr *);
static HReg s390_isel_float_expr(ISelEnv *, IRExpr *);
static void s390_isel_float128_expr(HReg *, HReg *, ISelEnv *, IRExpr *);
static HReg s390_isel_dfp_expr(ISelEnv *, IRExpr *);
static void s390_isel_dfp128_expr(HReg *, HReg *, ISelEnv *, IRExpr *);
static Int
get_guest_reg(Int offset)
{
switch (offset) {
case S390X_GUEST_OFFSET(guest_IA): return GUEST_IA;
case S390X_GUEST_OFFSET(guest_CC_OP): return GUEST_CC_OP;
case S390X_GUEST_OFFSET(guest_CC_DEP1): return GUEST_CC_DEP1;
case S390X_GUEST_OFFSET(guest_CC_DEP2): return GUEST_CC_DEP2;
case S390X_GUEST_OFFSET(guest_CC_NDEP): return GUEST_CC_NDEP;
case S390X_GUEST_OFFSET(guest_SYSNO): return GUEST_SYSNO;
case S390X_GUEST_OFFSET(guest_counter): return GUEST_COUNTER;
/* Also make sure there is never a partial write to one of
these registers. That would complicate matters. */
case S390X_GUEST_OFFSET(guest_IA)+1 ... S390X_GUEST_OFFSET(guest_IA)+7:
case S390X_GUEST_OFFSET(guest_CC_OP)+1 ... S390X_GUEST_OFFSET(guest_CC_OP)+7:
case S390X_GUEST_OFFSET(guest_CC_DEP1)+1 ... S390X_GUEST_OFFSET(guest_CC_DEP1)+7:
case S390X_GUEST_OFFSET(guest_CC_DEP2)+1 ... S390X_GUEST_OFFSET(guest_CC_DEP2)+7:
case S390X_GUEST_OFFSET(guest_CC_NDEP)+1 ... S390X_GUEST_OFFSET(guest_CC_NDEP)+7:
case S390X_GUEST_OFFSET(guest_SYSNO)+1 ... S390X_GUEST_OFFSET(guest_SYSNO)+7:
/* counter is used both as 4-byte and as 8-byte entity */
case S390X_GUEST_OFFSET(guest_counter)+1 ... S390X_GUEST_OFFSET(guest_counter)+3:
case S390X_GUEST_OFFSET(guest_counter)+5 ... S390X_GUEST_OFFSET(guest_counter)+7:
vpanic("partial update of this guest state register is not allowed");
break;
default: break;
}
return GUEST_UNKNOWN;
}
/* Add an instruction */
static void
addInstr(ISelEnv *env, s390_insn *insn)
{
addHInstr(env->code, insn);
if (vex_traceflags & VEX_TRACE_VCODE) {
vex_printf("%s\n", s390_insn_as_string(insn));
}
}
static __inline__ IRExpr *
mkU64(ULong value)
{
return IRExpr_Const(IRConst_U64(value));
}
/*---------------------------------------------------------*/
/*--- Registers ---*/
/*---------------------------------------------------------*/
/* Return the virtual register to which a given IRTemp is mapped. */
static HReg
lookupIRTemp(ISelEnv *env, IRTemp tmp)
{
vassert(tmp < env->n_vregmap);
vassert(! hregIsInvalid(env->vregmap[tmp]));
return env->vregmap[tmp];
}
/* Return the two virtual registers to which the IRTemp is mapped. */
static void
lookupIRTemp128(HReg *hi, HReg *lo, ISelEnv *env, IRTemp tmp)
{
vassert(tmp < env->n_vregmap);
vassert(! hregIsInvalid(env->vregmapHI[tmp]));
*lo = env->vregmap[tmp];
*hi = env->vregmapHI[tmp];
}
/* Allocate a new integer register */
static HReg
newVRegI(ISelEnv *env)
{
HReg reg = mkHReg(env->vreg_ctr, HRcInt64, True /* virtual */ );
env->vreg_ctr++;
return reg;
}
/* Allocate a new floating point register */
static HReg
newVRegF(ISelEnv *env)
{
HReg reg = mkHReg(env->vreg_ctr, HRcFlt64, True /* virtual */ );
env->vreg_ctr++;
return reg;
}
/* Construct a non-virtual general purpose register */
static __inline__ HReg
make_gpr(UInt regno)
{
return mkHReg(regno, HRcInt64, False /* virtual */ );
}
/* Construct a non-virtual floating point register */
static __inline__ HReg
make_fpr(UInt regno)
{
return mkHReg(regno, HRcFlt64, False /* virtual */ );
}
/*---------------------------------------------------------*/
/*--- Amode ---*/
/*---------------------------------------------------------*/
static __inline__ Bool
ulong_fits_unsigned_12bit(ULong val)
{
return (val & 0xFFFu) == val;
}
static __inline__ Bool
ulong_fits_signed_20bit(ULong val)
{
Long v = val & 0xFFFFFu;
v = (v << 44) >> 44; /* sign extend */
return val == (ULong)v;
}
static __inline__ Bool
ulong_fits_signed_8bit(ULong val)
{
Long v = val & 0xFFu;
v = (v << 56) >> 56; /* sign extend */
return val == (ULong)v;
}
/* EXPR is an expression that is used as an address. Return an s390_amode
for it. */
static s390_amode *
s390_isel_amode_wrk(ISelEnv *env, IRExpr *expr)
{
if (expr->tag == Iex_Binop && expr->Iex.Binop.op == Iop_Add64) {
IRExpr *arg1 = expr->Iex.Binop.arg1;
IRExpr *arg2 = expr->Iex.Binop.arg2;
/* Move constant into right subtree */
if (arg1->tag == Iex_Const) {
IRExpr *tmp;
tmp = arg1;
arg1 = arg2;
arg2 = tmp;
}
/* r + constant: Check for b12 first, then b20 */
if (arg2->tag == Iex_Const && arg2->Iex.Const.con->tag == Ico_U64) {
ULong value = arg2->Iex.Const.con->Ico.U64;
if (ulong_fits_unsigned_12bit(value)) {
return s390_amode_b12((Int)value, s390_isel_int_expr(env, arg1));
}
/* If long-displacement is not available, do not construct B20 or
BX20 amodes because code generation cannot handle them. */
if (s390_host_has_ldisp && ulong_fits_signed_20bit(value)) {
return s390_amode_b20((Int)value, s390_isel_int_expr(env, arg1));
}
}
}
/* Doesn't match anything in particular. Generate it into
a register and use that. */
return s390_amode_b12(0, s390_isel_int_expr(env, expr));
}
static s390_amode *
s390_isel_amode(ISelEnv *env, IRExpr *expr)
{
s390_amode *am;
/* Address computation should yield a 64-bit value */
vassert(typeOfIRExpr(env->type_env, expr) == Ity_I64);
am = s390_isel_amode_wrk(env, expr);
/* Check post-condition */
vassert(s390_amode_is_sane(am));
return am;
}
/*---------------------------------------------------------*/
/*--- Helper functions ---*/
/*---------------------------------------------------------*/
/* Constants and memory accesses should be right operands */
#define order_commutative_operands(left, right) \
do { \
if (left->tag == Iex_Const || left->tag == Iex_Load || \
left->tag == Iex_Get) { \
IRExpr *tmp; \
tmp = left; \
left = right; \
right = tmp; \
} \
} while (0)
/* Copy an RMI operand to the DST register */
static s390_insn *
s390_opnd_copy(UChar size, HReg dst, s390_opnd_RMI opnd)
{
switch (opnd.tag) {
case S390_OPND_AMODE:
return s390_insn_load(size, dst, opnd.variant.am);
case S390_OPND_REG:
return s390_insn_move(size, dst, opnd.variant.reg);
case S390_OPND_IMMEDIATE:
return s390_insn_load_immediate(size, dst, opnd.variant.imm);
default:
vpanic("s390_opnd_copy");
}
}
/* Construct a RMI operand for a register */
static __inline__ s390_opnd_RMI
s390_opnd_reg(HReg reg)
{
s390_opnd_RMI opnd;
opnd.tag = S390_OPND_REG;
opnd.variant.reg = reg;
return opnd;
}
/* Construct a RMI operand for an immediate constant */
static __inline__ s390_opnd_RMI
s390_opnd_imm(ULong value)
{
s390_opnd_RMI opnd;
opnd.tag = S390_OPND_IMMEDIATE;
opnd.variant.imm = value;
return opnd;
}
/* Return 1, if EXPR represents the constant 0 */
static Bool
s390_expr_is_const_zero(IRExpr *expr)
{
ULong value;
if (expr->tag == Iex_Const) {
switch (expr->Iex.Const.con->tag) {
case Ico_U1: value = expr->Iex.Const.con->Ico.U1; break;
case Ico_U8: value = expr->Iex.Const.con->Ico.U8; break;
case Ico_U16: value = expr->Iex.Const.con->Ico.U16; break;
case Ico_U32: value = expr->Iex.Const.con->Ico.U32; break;
case Ico_U64: value = expr->Iex.Const.con->Ico.U64; break;
default:
vpanic("s390_expr_is_const_zero");
}
return value == 0;
}
return 0;
}
/* Return the value of CON as a sign-exteded ULong value */
static ULong
get_const_value_as_ulong(const IRConst *con)
{
Long value;
switch (con->tag) {
case Ico_U1: value = con->Ico.U1; return (ULong) ((value << 63) >> 63);
case Ico_U8: value = con->Ico.U8; return (ULong) ((value << 56) >> 56);
case Ico_U16: value = con->Ico.U16; return (ULong) ((value << 48) >> 48);
case Ico_U32: value = con->Ico.U32; return (ULong) ((value << 32) >> 32);
case Ico_U64: return con->Ico.U64;
default:
vpanic("get_const_value_as_ulong");
}
}
/* Call a helper (clean or dirty)
Arguments must satisfy the following conditions:
(a) they are expressions yielding an integer result
(b) there can be no more than S390_NUM_GPRPARMS arguments
guard is a Ity_Bit expression indicating whether or not the
call happens. If guard == NULL, the call is unconditional.
Calling the helper function proceeds as follows:
(1) The helper arguments are evaluated and their value stored in
virtual registers.
(2) The condition code is evaluated
(3) The argument values are copied from the virtual registers to the
registers mandated by the ABI.
(4) Call the helper function.
This is not the most efficient way as step 3 generates register-to-register
moves. But it is the least fragile way as the only hidden dependency here
is that register-to-register moves (step 3) must not clobber the condition
code. Other schemes (e.g. VEX r2326) that attempt to avoid the register-
to-register add more such dependencies. Not good. Besides, it's the job
of the register allocator to throw out those reg-to-reg moves.
*/
static void
doHelperCall(/*OUT*/UInt *stackAdjustAfterCall,
/*OUT*/RetLoc *retloc,
ISelEnv *env, IRExpr *guard,
IRCallee *callee, IRType retTy, IRExpr **args)
{
UInt n_args, i, argreg, size;
ULong target;
HReg tmpregs[S390_NUM_GPRPARMS];
s390_cc_t cc;
/* Set default returns. We'll update them later if needed. */
*stackAdjustAfterCall = 0;
*retloc = mk_RetLoc_INVALID();
/* The return type can be I{64,32,16,8} or V{128,256}. In the
latter two cases, it is expected that |args| will contain the
special node IRExpr_VECRET(), in which case this routine
generates code to allocate space on the stack for the vector
return value. Since we are not passing any scalars on the
stack, it is enough to preallocate the return space before
marshalling any arguments, in this case.
|args| may also contain IRExpr_BBPTR(), in which case the value
in the guest state pointer register is passed as the
corresponding argument.
These are used for cross-checking that IR-level constraints on
the use of IRExpr_VECRET() and IRExpr_BBPTR() are observed. */
UInt nVECRETs = 0;
UInt nBBPTRs = 0;
n_args = 0;
for (i = 0; args[i]; i++)
++n_args;
if (n_args > S390_NUM_GPRPARMS) {
vpanic("doHelperCall: too many arguments");
}
/* All arguments must have Ity_I64. For two reasons:
(1) We do not handle floating point arguments.
(2) The ABI requires that integer values are sign- or zero-extended
to 64 bit.
*/
Int arg_errors = 0;
for (i = 0; i < n_args; ++i) {
if (UNLIKELY(args[i]->tag == Iex_VECRET)) {
nVECRETs++;
} else if (UNLIKELY(args[i]->tag == Iex_BBPTR)) {
nBBPTRs++;
} else {
IRType type = typeOfIRExpr(env->type_env, args[i]);
if (type != Ity_I64) {
++arg_errors;
vex_printf("calling %s: argument #%d has type ", callee->name, i);
ppIRType(type);
vex_printf("; Ity_I64 is required\n");
}
}
}
if (arg_errors)
vpanic("cannot continue due to errors in argument passing");
/* If this fails, the IR is ill-formed */
vassert(nBBPTRs == 0 || nBBPTRs == 1);
/* If we have a VECRET, allocate space on the stack for the return
value, and record the stack pointer after that. */
HReg r_vecRetAddr = INVALID_HREG;
if (nVECRETs == 1) {
/* we do not handle vector types yet */
vassert(0);
HReg sp = make_gpr(S390_REGNO_STACK_POINTER);
vassert(retTy == Ity_V128 || retTy == Ity_V256);
vassert(retTy != Ity_V256); // we don't handle that yet (if ever)
r_vecRetAddr = newVRegI(env);
addInstr(env, s390_insn_alu(4, S390_ALU_SUB, sp, s390_opnd_imm(16)));
addInstr(env, s390_insn_move(sizeof(ULong), r_vecRetAddr, sp));
} else {
// If either of these fail, the IR is ill-formed
vassert(retTy != Ity_V128 && retTy != Ity_V256);
vassert(nVECRETs == 0);
}
argreg = 0;
/* Compute the function arguments into a temporary register each */
for (i = 0; i < n_args; i++) {
IRExpr *arg = args[i];
if(UNLIKELY(arg->tag == Iex_VECRET)) {
/* we do not handle vector types yet */
vassert(0);
addInstr(env, s390_insn_move(sizeof(ULong), tmpregs[argreg],
r_vecRetAddr));
} else if (UNLIKELY(arg->tag == Iex_BBPTR)) {
/* If we need the guest state pointer put it in a temporary arg reg */
tmpregs[argreg] = newVRegI(env);
addInstr(env, s390_insn_move(sizeof(ULong), tmpregs[argreg],
s390_hreg_guest_state_pointer()));
} else {
tmpregs[argreg] = s390_isel_int_expr(env, args[i]);
}
argreg++;
}
/* Compute the condition */
cc = S390_CC_ALWAYS;
if (guard) {
if (guard->tag == Iex_Const
&& guard->Iex.Const.con->tag == Ico_U1
&& guard->Iex.Const.con->Ico.U1 == True) {
/* unconditional -- do nothing */
} else {
cc = s390_isel_cc(env, guard);
}
}
/* Move the args to the final register. It is paramount, that the
code to move the registers does not clobber the condition code ! */
for (i = 0; i < argreg; i++) {
HReg finalreg;
finalreg = make_gpr(s390_gprno_from_arg_index(i));
size = sizeofIRType(Ity_I64);
addInstr(env, s390_insn_move(size, finalreg, tmpregs[i]));
}
target = Ptr_to_ULong(callee->addr);
/* Do final checks, set the return values, and generate the call
instruction proper. */
vassert(*stackAdjustAfterCall == 0);
vassert(is_RetLoc_INVALID(*retloc));
switch (retTy) {
case Ity_INVALID:
/* Function doesn't return a value. */
*retloc = mk_RetLoc_simple(RLPri_None);
break;
case Ity_I64: case Ity_I32: case Ity_I16: case Ity_I8:
*retloc = mk_RetLoc_simple(RLPri_Int);
break;
case Ity_V128:
/* we do not handle vector types yet */
vassert(0);
*retloc = mk_RetLoc_spRel(RLPri_V128SpRel, 0);
*stackAdjustAfterCall = 16;
break;
case Ity_V256:
/* we do not handle vector types yet */
vassert(0);
*retloc = mk_RetLoc_spRel(RLPri_V256SpRel, 0);
*stackAdjustAfterCall = 32;
break;
default:
/* IR can denote other possible return types, but we don't
handle those here. */
vassert(0);
}
/* Finally, the call itself. */
addInstr(env, s390_insn_helper_call(cc, (Addr64)target, n_args,
callee->name, *retloc));
}
/*---------------------------------------------------------*/
/*--- BFP helper functions ---*/
/*---------------------------------------------------------*/
/* Set the BFP rounding mode in the FPC. This function is called for
all non-conversion BFP instructions as those will always get the
rounding mode from the FPC. */
static void
set_bfp_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_bfp_rounding_mode &&
env->previous_bfp_rounding_mode->tag == Iex_RdTmp &&
irrm->tag == Iex_RdTmp &&
env->previous_bfp_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_bfp_rounding_mode = irrm;
/* The incoming rounding mode is in VEX IR encoding. Need to change
to s390.
rounding mode | s390 | IR
-------------------------
to nearest | 00 | 00
to zero | 01 | 11
to +infinity | 10 | 10
to -infinity | 11 | 01
So: s390 = (4 - IR) & 3
*/
HReg ir = s390_isel_int_expr(env, irrm);
HReg mode = newVRegI(env);
addInstr(env, s390_insn_load_immediate(4, mode, 4));
addInstr(env, s390_insn_alu(4, S390_ALU_SUB, mode, s390_opnd_reg(ir)));
addInstr(env, s390_insn_alu(4, S390_ALU_AND, mode, s390_opnd_imm(3)));
addInstr(env, s390_insn_set_fpc_bfprm(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. */
static s390_bfp_round_t
get_bfp_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_NEAREST: return S390_BFP_ROUND_NEAREST_EVEN;
case Irrm_ZERO: return S390_BFP_ROUND_ZERO;
case Irrm_PosINF: return S390_BFP_ROUND_POSINF;
case Irrm_NegINF: return S390_BFP_ROUND_NEGINF;
default:
vpanic("get_bfp_rounding_mode");
}
}
set_bfp_rounding_mode_in_fpc(env, irrm);
return S390_BFP_ROUND_PER_FPC;
}
/*---------------------------------------------------------*/
/*--- 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. */
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.
When mapping an Irrm_XYZ value to an S390_DFP_ROUND_ value there is
often a choice. For instance, Irrm_ZERO could be mapped to either
S390_DFP_ROUND_ZERO_5 or S390_DFP_ROUND_ZERO_9. The difference between
those two is that with S390_DFP_ROUND_ZERO_9 the recognition of the
quantum exception is suppressed whereas with S390_DFP_ROUND_ZERO_5 it
is not. As the quantum exception is not modelled we can choose either
value. The choice is to use S390_DFP_ROUND_.. values in the range [8:15],
because values in the range [1:7] have unpredictable rounding behaviour
when the floating point exception facility is not installed.
Translation table of
s390 DFP rounding mode to IRRoundingMode to s390 DFP rounding mode
s390(S390_DFP_ROUND_) | IR(Irrm_) | s390(S390_DFP_ROUND_)
--------------------------------------------------------------------
NEAREST_TIE_AWAY_0_1 | NEAREST_TIE_AWAY_0 | NEAREST_TIE_AWAY_0_12
NEAREST_TIE_AWAY_0_12 | " | "
PREPARE_SHORT_3 | PREPARE_SHORTER | PREPARE_SHORT_15
PREPARE_SHORT_15 | " | "
NEAREST_EVEN_4 | NEAREST | NEAREST_EVEN_8
NEAREST_EVEN_8 | " | "
ZERO_5 | ZERO | ZERO_9
ZERO_9 | " | "
POSINF_6 | PosINF | POSINF_10
POSINF_10 | " | "
NEGINF_7 | NegINF | NEGINF_11
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_NEAREST:
return S390_DFP_ROUND_NEAREST_EVEN_8;
case Irrm_NegINF:
return S390_DFP_ROUND_NEGINF_11;
case Irrm_PosINF:
return S390_DFP_ROUND_POSINF_10;
case Irrm_ZERO:
return S390_DFP_ROUND_ZERO_9;
case Irrm_NEAREST_TIE_AWAY_0:
return S390_DFP_ROUND_NEAREST_TIE_AWAY_0_12;
case Irrm_PREPARE_SHORTER:
return S390_DFP_ROUND_PREPARE_SHORT_15;
case Irrm_AWAY_FROM_ZERO:
return S390_DFP_ROUND_AWAY_0;
case Irrm_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;
}
/*---------------------------------------------------------*/
/*--- Condition code helper functions ---*/
/*---------------------------------------------------------*/
/* CC_S390 holds the condition code in s390 encoding. Convert it to
VEX encoding (IRCmpFResult)
s390 VEX b6 b2 b0 cc.1 cc.0
0 0x40 EQ 1 0 0 0 0
1 0x01 LT 0 0 1 0 1
2 0x00 GT 0 0 0 1 0
3 0x45 Unordered 1 1 1 1 1
b0 = cc.0
b2 = cc.0 & cc.1
b6 = ~(cc.0 ^ cc.1) // ((cc.0 - cc.1) + 0x1 ) & 0x1
VEX = b0 | (b2 << 2) | (b6 << 6);
*/
static HReg
convert_s390_to_vex_bfpcc(ISelEnv *env, HReg cc_s390)
{
HReg cc0, cc1, b2, b6, cc_vex;
cc0 = newVRegI(env);
addInstr(env, s390_insn_move(4, cc0, cc_s390));
addInstr(env, s390_insn_alu(4, S390_ALU_AND, cc0, s390_opnd_imm(1)));
cc1 = newVRegI(env);
addInstr(env, s390_insn_move(4, cc1, cc_s390));
addInstr(env, s390_insn_alu(4, S390_ALU_RSH, cc1, s390_opnd_imm(1)));
b2 = newVRegI(env);
addInstr(env, s390_insn_move(4, b2, cc0));
addInstr(env, s390_insn_alu(4, S390_ALU_AND, b2, s390_opnd_reg(cc1)));
addInstr(env, s390_insn_alu(4, S390_ALU_LSH, b2, s390_opnd_imm(2)));
b6 = newVRegI(env);
addInstr(env, s390_insn_move(4, b6, cc0));
addInstr(env, s390_insn_alu(4, S390_ALU_SUB, b6, s390_opnd_reg(cc1)));
addInstr(env, s390_insn_alu(4, S390_ALU_ADD, b6, s390_opnd_imm(1)));
addInstr(env, s390_insn_alu(4, S390_ALU_AND, b6, s390_opnd_imm(1)));
addInstr(env, s390_insn_alu(4, S390_ALU_LSH, b6, s390_opnd_imm(6)));
cc_vex = newVRegI(env);
addInstr(env, s390_insn_move(4, cc_vex, cc0));
addInstr(env, s390_insn_alu(4, S390_ALU_OR, cc_vex, s390_opnd_reg(b2)));
addInstr(env, s390_insn_alu(4, S390_ALU_OR, cc_vex, s390_opnd_reg(b6)));
return cc_vex;
}
/* CC_S390 holds the condition code in s390 encoding. Convert it to
VEX encoding (IRCmpDResult) */
static HReg
convert_s390_to_vex_dfpcc(ISelEnv *env, HReg cc_s390)
{
/* The encodings for IRCmpFResult and IRCmpDResult are the same/ */
return convert_s390_to_vex_bfpcc(env, cc_s390);
}
/*---------------------------------------------------------*/
/*--- ISEL: Integer expressions (128 bit) ---*/
/*---------------------------------------------------------*/
static void
s390_isel_int128_expr_wrk(HReg *dst_hi, HReg *dst_lo, ISelEnv *env,
IRExpr *expr)
{
IRType ty = typeOfIRExpr(env->type_env, expr);
vassert(ty == Ity_I128);
/* No need to consider the following
- 128-bit constants (they do not exist in VEX)
- 128-bit loads from memory (will not be generated)
*/
/* Read 128-bit IRTemp */
if (expr->tag == Iex_RdTmp) {
lookupIRTemp128(dst_hi, dst_lo, env, expr->Iex.RdTmp.tmp);
return;
}
if (expr->tag == Iex_Binop) {
IRExpr *arg1 = expr->Iex.Binop.arg1;
IRExpr *arg2 = expr->Iex.Binop.arg2;
Bool is_signed_multiply, is_signed_divide;
switch (expr->Iex.Binop.op) {
case Iop_MullU64:
is_signed_multiply = False;
goto do_multiply64;
case Iop_MullS64:
is_signed_multiply = True;
goto do_multiply64;
case Iop_DivModU128to64:
is_signed_divide = False;
goto do_divide64;
case Iop_DivModS128to64:
is_signed_divide = True;
goto do_divide64;
case Iop_64HLto128:
*dst_hi = s390_isel_int_expr(env, arg1);
*dst_lo = s390_isel_int_expr(env, arg2);
return;
case Iop_DivModS64to64: {
HReg r10, r11, h1;
s390_opnd_RMI op2;
h1 = s390_isel_int_expr(env, arg1); /* Process 1st operand */
op2 = s390_isel_int_expr_RMI(env, arg2); /* Process 2nd operand */
/* We use non-virtual registers r10 and r11 as pair */
r10 = make_gpr(10);
r11 = make_gpr(11);
/* Move 1st operand into r11 and */
addInstr(env, s390_insn_move(8, r11, h1));
/* Divide */
addInstr(env, s390_insn_divs(8, r10, r11, op2));
/* The result is in registers r10 (remainder) and r11 (quotient).
Move the result into the reg pair that is being returned such
such that the low 64 bits are the quotient and the upper 64 bits
are the remainder. (see libvex_ir.h). */
*dst_hi = newVRegI(env);
*dst_lo = newVRegI(env);
addInstr(env, s390_insn_move(8, *dst_hi, r10));
addInstr(env, s390_insn_move(8, *dst_lo, r11));
return;
}
default:
break;
do_multiply64: {
HReg r10, r11, h1;
s390_opnd_RMI op2;
order_commutative_operands(arg1, arg2);
h1 = s390_isel_int_expr(env, arg1); /* Process 1st operand */
op2 = s390_isel_int_expr_RMI(env, arg2); /* Process 2nd operand */
/* We use non-virtual registers r10 and r11 as pair */
r10 = make_gpr(10);
r11 = make_gpr(11);
/* Move the first operand to r11 */
addInstr(env, s390_insn_move(8, r11, h1));
/* Multiply */
addInstr(env, s390_insn_mul(8, r10, r11, op2, is_signed_multiply));
/* The result is in registers r10 and r11. Assign to two virtual regs
and return. */
*dst_hi = newVRegI(env);
*dst_lo = newVRegI(env);
addInstr(env, s390_insn_move(8, *dst_hi, r10));
addInstr(env, s390_insn_move(8, *dst_lo, r11));
return;
}
do_divide64: {
HReg r10, r11, hi, lo;
s390_opnd_RMI op2;
s390_isel_int128_expr(&hi, &lo, env, arg1);
op2 = s390_isel_int_expr_RMI(env, arg2); /* Process 2nd operand */
/* We use non-virtual registers r10 and r11 as pair */
r10 = make_gpr(10);
r11 = make_gpr(11);
/* Move high 64 bits of the 1st operand into r10 and
the low 64 bits into r11. */
addInstr(env, s390_insn_move(8, r10, hi));
addInstr(env, s390_insn_move(8, r11, lo));
/* Divide */
addInstr(env, s390_insn_div(8, r10, r11, op2, is_signed_divide));
/* The result is in registers r10 (remainder) and r11 (quotient).
Move the result into the reg pair that is being returned such
such that the low 64 bits are the quotient and the upper 64 bits
are the remainder. (see libvex_ir.h). */
*dst_hi = newVRegI(env);
*dst_lo = newVRegI(env);
addInstr(env, s390_insn_move(8, *dst_hi, r10));
addInstr(env, s390_insn_move(8, *dst_lo, r11));
return;
}
}
}
vpanic("s390_isel_int128_expr");
}
/* Compute a 128-bit value into two 64-bit registers. These may be either
real or virtual regs; in any case they must not be changed by subsequent
code emitted by the caller. */
static void
s390_isel_int128_expr(HReg *dst_hi, HReg *dst_lo, ISelEnv *env, IRExpr *expr)
{
s390_isel_int128_expr_wrk(dst_hi, dst_lo, env, expr);
/* Sanity checks ... */
vassert(hregIsVirtual(*dst_hi));
vassert(hregIsVirtual(*dst_lo));
vassert(hregClass(*dst_hi) == HRcInt64);
vassert(hregClass(*dst_lo) == HRcInt64);
}
/*---------------------------------------------------------*/
/*--- ISEL: Integer expressions (64/32/16/8 bit) ---*/
/*---------------------------------------------------------*/
/* Select insns for an integer-typed expression, and add them to the
code list. Return a reg holding the result. This reg will be a
virtual register. THE RETURNED REG MUST NOT BE MODIFIED. If you
want to modify it, ask for a new vreg, copy it in there, and modify
the copy. The register allocator will do its best to map both
vregs to the same real register, so the copies will often disappear
later in the game.
This should handle expressions of 64, 32, 16 and 8-bit type.
All results are returned in a 64bit register.
For 16- and 8-bit expressions, the upper (32/48/56 : 16/24) bits
are arbitrary, so you should mask or sign extend partial values
if necessary.
*/
/* DO NOT CALL THIS DIRECTLY ! */
static HReg
s390_isel_int_expr_wrk(ISelEnv *env, IRExpr *expr)
{
IRType ty = typeOfIRExpr(env->type_env, expr);
UChar size;
s390_bfp_conv_t conv;
s390_dfp_conv_t dconv;
vassert(ty == Ity_I8 || ty == Ity_I16 || ty == Ity_I32 || ty == Ity_I64);
size = sizeofIRType(ty); /* size of the result after evaluating EXPR */
switch (expr->tag) {
/* --------- TEMP --------- */
case Iex_RdTmp:
/* Return the virtual register that holds the temporary. */
return lookupIRTemp(env, expr->Iex.RdTmp.tmp);
/* --------- LOAD --------- */
case Iex_Load: {
HReg dst = newVRegI(env);
s390_amode *am = s390_isel_amode(env, expr->Iex.Load.addr);
if (expr->Iex.Load.end != Iend_BE)
goto irreducible;
addInstr(env, s390_insn_load(size, dst, am));
return dst;
}
/* --------- BINARY OP --------- */
case Iex_Binop: {
IRExpr *arg1 = expr->Iex.Binop.arg1;
IRExpr *arg2 = expr->Iex.Binop.arg2;
HReg h1, res;
s390_alu_t opkind;
s390_opnd_RMI op2, value, opnd;
s390_insn *insn;
Bool is_commutative, is_signed_multiply, is_signed_divide;
is_commutative = True;
switch (expr->Iex.Binop.op) {
case Iop_MullU8:
case Iop_MullU16:
case Iop_MullU32:
is_signed_multiply = False;
goto do_multiply;
case Iop_MullS8:
case Iop_MullS16:
case Iop_MullS32:
is_signed_multiply = True;
goto do_multiply;
do_multiply: {
HReg r10, r11;
UInt arg_size = size / 2;
order_commutative_operands(arg1, arg2);
h1 = s390_isel_int_expr(env, arg1); /* Process 1st operand */
op2 = s390_isel_int_expr_RMI(env, arg2); /* Process 2nd operand */
/* We use non-virtual registers r10 and r11 as pair */
r10 = make_gpr(10);
r11 = make_gpr(11);
/* Move the first operand to r11 */
addInstr(env, s390_insn_move(arg_size, r11, h1));
/* Multiply */
addInstr(env, s390_insn_mul(arg_size, r10, r11, op2, is_signed_multiply));
/* The result is in registers r10 and r11. Combine them into a SIZE-bit
value into the destination register. */
res = newVRegI(env);
addInstr(env, s390_insn_move(arg_size, res, r10));
value = s390_opnd_imm(arg_size * 8);
addInstr(env, s390_insn_alu(size, S390_ALU_LSH, res, value));
value = s390_opnd_imm((((ULong)1) << arg_size * 8) - 1);
addInstr(env, s390_insn_alu(size, S390_ALU_AND, r11, value));
opnd = s390_opnd_reg(r11);
addInstr(env, s390_insn_alu(size, S390_ALU_OR, res, opnd));
return res;
}
case Iop_DivModS64to32:
is_signed_divide = True;
goto do_divide;
case Iop_DivModU64to32:
is_signed_divide = False;
goto do_divide;
do_divide: {
HReg r10, r11;
h1 = s390_isel_int_expr(env, arg1); /* Process 1st operand */
op2 = s390_isel_int_expr_RMI(env, arg2); /* Process 2nd operand */
/* We use non-virtual registers r10 and r11 as pair */
r10 = make_gpr(10);
r11 = make_gpr(11);
/* Split the first operand and put the high 32 bits into r10 and
the low 32 bits into r11. */
addInstr(env, s390_insn_move(8, r10, h1));
addInstr(env, s390_insn_move(8, r11, h1));
value = s390_opnd_imm(32);
addInstr(env, s390_insn_alu(8, S390_ALU_RSH, r10, value));
/* Divide */
addInstr(env, s390_insn_div(4, r10, r11, op2, is_signed_divide));
/* The result is in registers r10 (remainder) and r11 (quotient).
Combine them into a 64-bit value such that the low 32 bits are
the quotient and the upper 32 bits are the remainder. (see
libvex_ir.h). */
res = newVRegI(env);
addInstr(env, s390_insn_move(8, res, r10));
value = s390_opnd_imm(32);
addInstr(env, s390_insn_alu(8, S390_ALU_LSH, res, value));
value = s390_opnd_imm((((ULong)1) << 32) - 1);
addInstr(env, s390_insn_alu(8, S390_ALU_AND, r11, value));
opnd = s390_opnd_reg(r11);
addInstr(env, s390_insn_alu(8, S390_ALU_OR, res, opnd));
return res;
}
case Iop_F32toI32S: conv = S390_BFP_F32_TO_I32; goto do_convert;
case Iop_F32toI64S: conv = S390_BFP_F32_TO_I64; goto do_convert;
case Iop_F32toI32U: conv = S390_BFP_F32_TO_U32; goto do_convert;
case Iop_F32toI64U: conv = S390_BFP_F32_TO_U64; goto do_convert;
case Iop_F64toI32S: conv = S390_BFP_F64_TO_I32; goto do_convert;
case Iop_F64toI64S: conv = S390_BFP_F64_TO_I64; goto do_convert;
case Iop_F64toI32U: conv = S390_BFP_F64_TO_U32; goto do_convert;
case Iop_F64toI64U: conv = S390_BFP_F64_TO_U64; goto do_convert;
case Iop_F128toI32S: conv = S390_BFP_F128_TO_I32; goto do_convert_128;
case Iop_F128toI64S: conv = S390_BFP_F128_TO_I64; goto do_convert_128;
case Iop_F128toI32U: conv = S390_BFP_F128_TO_U32; goto do_convert_128;
case Iop_F128toI64U: conv = S390_BFP_F128_TO_U64; goto do_convert_128;
case Iop_D64toI32S: dconv = S390_DFP_D64_TO_I32; goto do_convert_dfp;
case Iop_D64toI64S: dconv = S390_DFP_D64_TO_I64; goto do_convert_dfp;
case Iop_D64toI32U: dconv = S390_DFP_D64_TO_U32; goto do_convert_dfp;
case Iop_D64toI64U: dconv = S390_DFP_D64_TO_U64; goto do_convert_dfp;
case Iop_D128toI32S: dconv = S390_DFP_D128_TO_I32; goto do_convert_dfp128;
case Iop_D128toI64S: dconv = S390_DFP_D128_TO_I64; goto do_convert_dfp128;
case Iop_D128toI32U: dconv = S390_DFP_D128_TO_U32; goto do_convert_dfp128;
case Iop_D128toI64U: dconv = S390_DFP_D128_TO_U64; goto do_convert_dfp128;
do_convert: {
s390_bfp_round_t rounding_mode;
res = newVRegI(env);
h1 = s390_isel_float_expr(env, arg2); /* Process operand */
rounding_mode = get_bfp_rounding_mode(env, arg1);
addInstr(env, s390_insn_bfp_convert(size, conv, res, h1,
rounding_mode));
return res;
}
do_convert_128: {
s390_bfp_round_t rounding_mode;
HReg op_hi, op_lo, f13, f15;
res = newVRegI(env);
s390_isel_float128_expr(&op_hi, &op_lo, env, arg2); /* operand */
/* We use non-virtual registers r13 and r15 as pair */
f13 = make_fpr(13);
f15 = make_fpr(15);
/* operand --> (f13, f15) */
addInstr(env, s390_insn_move(8, f13, op_hi));
addInstr(env, s390_insn_move(8, f15, op_lo));
rounding_mode = get_bfp_rounding_mode(env, arg1);
addInstr(env, s390_insn_bfp128_convert_from(size, conv, res,
INVALID_HREG, f13, f15,
rounding_mode));
return res;
}
do_convert_dfp: {
s390_dfp_round_t rounding_mode;
res = newVRegI(env);
h1 = s390_isel_dfp_expr(env, arg2); /* Process operand */
rounding_mode = get_dfp_rounding_mode(env, arg1);
addInstr(env, s390_insn_dfp_convert(size, dconv, res, h1,
rounding_mode));
return res;
}
do_convert_dfp128: {
s390_dfp_round_t rounding_mode;
HReg op_hi, op_lo, f13, f15;
res = newVRegI(env);
s390_isel_dfp128_expr(&op_hi, &op_lo, env, arg2); /* operand */
/* We use non-virtual registers r13 and r15 as pair */
f13 = make_fpr(13);
f15 = make_fpr(15);
/* operand --> (f13, f15) */
addInstr(env, s390_insn_move(8, f13, op_hi));
addInstr(env, s390_insn_move(8, f15, op_lo));
rounding_mode = get_dfp_rounding_mode(env, arg1);
addInstr(env, s390_insn_dfp128_convert_from(size, dconv, res,
INVALID_HREG, f13,
f15, rounding_mode));
return res;
}
case Iop_8HLto16:
case Iop_16HLto32:
case Iop_32HLto64: {
HReg h2;
UInt arg_size = size / 2;
res = newVRegI(env);
h1 = s390_isel_int_expr(env, arg1); /* Process 1st operand */
h2 = s390_isel_int_expr(env, arg2); /* Process 2nd operand */
addInstr(env, s390_insn_move(arg_size, res, h1));
value = s390_opnd_imm(arg_size * 8);
addInstr(env, s390_insn_alu(size, S390_ALU_LSH, res, value));
value = s390_opnd_imm((((ULong)1) << arg_size * 8) - 1);
addInstr(env, s390_insn_alu(size, S390_ALU_AND, h2, value));
opnd = s390_opnd_reg(h2);
addInstr(env, s390_insn_alu(size, S390_ALU_OR, res, opnd));
return res;
}
case Iop_Max32U: {
/* arg1 > arg2 ? arg1 : arg2 using uint32_t arguments */
res = newVRegI(env);
h1 = s390_isel_int_expr(env, arg1);
op2 = s390_isel_int_expr_RMI(env, arg2);
addInstr(env, s390_insn_move(size, res, h1));
addInstr(env, s390_insn_compare(size, res, op2, False /* signed */));
addInstr(env, s390_insn_cond_move(size, S390_CC_L, res, op2));
return res;
}
case Iop_CmpF32:
case Iop_CmpF64: {
HReg cc_s390, h2;
h1 = s390_isel_float_expr(env, arg1);
h2 = s390_isel_float_expr(env, arg2);
cc_s390 = newVRegI(env);
size = (expr->Iex.Binop.op == Iop_CmpF32) ? 4 : 8;
addInstr(env, s390_insn_bfp_compare(size, cc_s390, h1, h2));
return convert_s390_to_vex_bfpcc(env, cc_s390);
}
case Iop_CmpF128: {
HReg op1_hi, op1_lo, op2_hi, op2_lo, f12, f13, f14, f15, cc_s390;
s390_isel_float128_expr(&op1_hi, &op1_lo, env, arg1); /* 1st operand */
s390_isel_float128_expr(&op2_hi, &op2_lo, env, arg2); /* 2nd operand */
cc_s390 = newVRegI(env);
/* We use non-virtual registers as pairs (f13, f15) and (f12, f14)) */
f12 = make_fpr(12);
f13 = make_fpr(13);
f14 = make_fpr(14);
f15 = make_fpr(15);
/* 1st operand --> (f12, f14) */
addInstr(env, s390_insn_move(8, f12, op1_hi));
addInstr(env, s390_insn_move(8, f14, op1_lo));
/* 2nd operand --> (f13, f15) */
addInstr(env, s390_insn_move(8, f13, op2_hi));
addInstr(env, s390_insn_move(8, f15, op2_lo));
res = newVRegI(env);
addInstr(env, s390_insn_bfp128_compare(16, cc_s390, f12, f14, f13, f15));
return convert_s390_to_vex_bfpcc(env, cc_s390);
}
case Iop_CmpD64:
case Iop_CmpExpD64: {
HReg cc_s390, h2;
s390_dfp_cmp_t cmp;
h1 = s390_isel_dfp_expr(env, arg1);
h2 = s390_isel_dfp_expr(env, arg2);
cc_s390 = newVRegI(env);
switch(expr->Iex.Binop.op) {
case Iop_CmpD64: cmp = S390_DFP_COMPARE; break;
case Iop_CmpExpD64: cmp = S390_DFP_COMPARE_EXP; break;
default: goto irreducible;
}
addInstr(env, s390_insn_dfp_compare(8, cmp, cc_s390, h1, h2));
return convert_s390_to_vex_dfpcc(env, cc_s390);
}
case Iop_CmpD128:
case Iop_CmpExpD128: {
HReg op1_hi, op1_lo, op2_hi, op2_lo, f12, f13, f14, f15, cc_s390;
s390_dfp_cmp_t cmp;
s390_isel_dfp128_expr(&op1_hi, &op1_lo, env, arg1); /* 1st operand */
s390_isel_dfp128_expr(&op2_hi, &op2_lo, env, arg2); /* 2nd operand */
cc_s390 = newVRegI(env);
/* We use non-virtual registers as pairs (f13, f15) and (f12, f14)) */
f12 = make_fpr(12);
f13 = make_fpr(13);
f14 = make_fpr(14);
f15 = make_fpr(15);
/* 1st operand --> (f12, f14) */
addInstr(env, s390_insn_move(8, f12, op1_hi));
addInstr(env, s390_insn_move(8, f14, op1_lo));
/* 2nd operand --> (f13, f15) */
addInstr(env, s390_insn_move(8, f13, op2_hi));
addInstr(env, s390_insn_move(8, f15, op2_lo));
switch(expr->Iex.Binop.op) {
case Iop_CmpD128: cmp = S390_DFP_COMPARE; break;
case Iop_CmpExpD128: cmp = S390_DFP_COMPARE_EXP; break;
default: goto irreducible;
}
addInstr(env, s390_insn_dfp128_compare(16, cmp, cc_s390, f12, f14,
f13, f15));
return convert_s390_to_vex_dfpcc(env, cc_s390);
}
case Iop_Add8:
case Iop_Add16:
case Iop_Add32:
case Iop_Add64:
opkind = S390_ALU_ADD;
break;
case Iop_Sub8:
case Iop_Sub16:
case Iop_Sub32:
case Iop_Sub64:
opkind = S390_ALU_SUB;
is_commutative = False;
break;
case Iop_And8:
case Iop_And16:
case Iop_And32:
case Iop_And64:
opkind = S390_ALU_AND;
break;
case Iop_Or8:
case Iop_Or16:
case Iop_Or32:
case Iop_Or64:
opkind = S390_ALU_OR;
break;
case Iop_Xor8:
case Iop_Xor16:
case Iop_Xor32:
case Iop_Xor64:
opkind = S390_ALU_XOR;
break;
case Iop_Shl8:
case Iop_Shl16:
case Iop_Shl32:
case Iop_Shl64:
opkind = S390_ALU_LSH;
is_commutative = False;
break;
case Iop_Shr8:
case Iop_Shr16:
case Iop_Shr32:
case Iop_Shr64:
opkind = S390_ALU_RSH;
is_commutative = False;
break;
case Iop_Sar8:
case Iop_Sar16:
case Iop_Sar32:
case Iop_Sar64:
opkind = S390_ALU_RSHA;
is_commutative = False;
break;
default:
goto irreducible;
}
/* Pattern match: 0 - arg1 --> -arg1 */
if (opkind == S390_ALU_SUB && s390_expr_is_const_zero(arg1)) {
res = newVRegI(env);
op2 = s390_isel_int_expr_RMI(env, arg2); /* Process 2nd operand */
insn = s390_insn_unop(size, S390_NEGATE, res, op2);
addInstr(env, insn);
return res;
}
if (is_commutative) {
order_commutative_operands(arg1, arg2);
}
h1 = s390_isel_int_expr(env, arg1); /* Process 1st operand */
op2 = s390_isel_int_expr_RMI(env, arg2); /* Process 2nd operand */
res = newVRegI(env);
/* As right shifts of one/two byte opreands are implemented using a
4-byte shift op, we first need to zero/sign-extend the shiftee. */
switch (expr->Iex.Binop.op) {
case Iop_Shr8:
insn = s390_insn_unop(4, S390_ZERO_EXTEND_8, res, s390_opnd_reg(h1));
break;
case Iop_Shr16:
insn = s390_insn_unop(4, S390_ZERO_EXTEND_16, res, s390_opnd_reg(h1));
break;
case Iop_Sar8:
insn = s390_insn_unop(4, S390_SIGN_EXTEND_8, res, s390_opnd_reg(h1));
break;
case Iop_Sar16:
insn = s390_insn_unop(4, S390_SIGN_EXTEND_16, res, s390_opnd_reg(h1));
break;
default:
insn = s390_insn_move(size, res, h1);
break;
}
addInstr(env, insn);
insn = s390_insn_alu(size, opkind, res, op2);
addInstr(env, insn);
return res;
}
/* --------- UNARY OP --------- */
case Iex_Unop: {
static s390_opnd_RMI mask = { S390_OPND_IMMEDIATE };
static s390_opnd_RMI shift = { S390_OPND_IMMEDIATE };
s390_opnd_RMI opnd;
s390_insn *insn;
IRExpr *arg;
HReg dst, h1;
IROp unop, binop;
arg = expr->Iex.Unop.arg;
/* Special cases are handled here */
/* 32-bit multiply with 32-bit result or
64-bit multiply with 64-bit result */
unop = expr->Iex.Unop.op;
binop = arg->Iex.Binop.op;
if ((arg->tag == Iex_Binop &&
((unop == Iop_64to32 &&
(binop == Iop_MullS32 || binop == Iop_MullU32)) ||
(unop == Iop_128to64 &&
(binop == Iop_MullS64 || binop == Iop_MullU64))))) {
h1 = s390_isel_int_expr(env, arg->Iex.Binop.arg1); /* 1st opnd */
opnd = s390_isel_int_expr_RMI(env, arg->Iex.Binop.arg2); /* 2nd opnd */
dst = newVRegI(env); /* Result goes into a new register */
addInstr(env, s390_insn_move(size, dst, h1));
addInstr(env, s390_insn_alu(size, S390_ALU_MUL, dst, opnd));
return dst;
}
if (unop == Iop_ReinterpF64asI64 || unop == Iop_ReinterpF32asI32) {
dst = newVRegI(env);
h1 = s390_isel_float_expr(env, arg); /* Process the operand */
addInstr(env, s390_insn_move(size, dst, h1));
return dst;
}
if (unop == Iop_ReinterpD64asI64) {
dst = newVRegI(env);
h1 = s390_isel_dfp_expr(env, arg); /* Process the operand */
addInstr(env, s390_insn_move(size, dst, h1));
return dst;
}
if (unop == Iop_ExtractExpD64 || unop == Iop_ExtractSigD64) {
s390_dfp_unop_t dfpop;
switch(unop) {
case Iop_ExtractExpD64: dfpop = S390_DFP_EXTRACT_EXP_D64; break;
case Iop_ExtractSigD64: dfpop = S390_DFP_EXTRACT_SIG_D64; break;
default: goto irreducible;
}
dst = newVRegI(env);
h1 = s390_isel_dfp_expr(env, arg); /* Process the operand */
addInstr(env, s390_insn_dfp_unop(size, dfpop, dst, h1));
return dst;
}
if (unop == Iop_ExtractExpD128 || unop == Iop_ExtractSigD128) {
s390_dfp_unop_t dfpop;
HReg op_hi, op_lo, f13, f15;
switch(unop) {
case Iop_ExtractExpD128: dfpop = S390_DFP_EXTRACT_EXP_D128; break;
case Iop_ExtractSigD128: dfpop = S390_DFP_EXTRACT_SIG_D128; break;
default: goto irreducible;
}
dst = newVRegI(env);
s390_isel_dfp128_expr(&op_hi, &op_lo, env, arg); /* Process operand */
/* We use non-virtual registers r13 and r15 as pair */
f13 = make_fpr(13);
f15 = make_fpr(15);
/* operand --> (f13, f15) */
addInstr(env, s390_insn_move(8, f13, op_hi));
addInstr(env, s390_insn_move(8, f15, op_lo));
addInstr(env, s390_insn_dfp128_unop(size, dfpop, dst, f13, f15));
return dst;
}
/* Expressions whose argument is 1-bit wide */
if (typeOfIRExpr(env->type_env, arg) == Ity_I1) {
s390_cc_t cond = s390_isel_cc(env, arg);
dst = newVRegI(env); /* Result goes into a new register */
addInstr(env, s390_insn_cc2bool(dst, cond));
switch (unop) {
case Iop_1Uto8:
case Iop_1Uto32:
/* Zero extend */
mask.variant.imm = 1;
addInstr(env, s390_insn_alu(4, S390_ALU_AND, dst, mask));
break;
case Iop_1Uto64:
/* Zero extend */
mask.variant.imm = 1;
addInstr(env, s390_insn_alu(8, S390_ALU_AND, dst, mask));
break;
case Iop_1Sto8:
case Iop_1Sto16:
case Iop_1Sto32:
shift.variant.imm = 31;
addInstr(env, s390_insn_alu(4, S390_ALU_LSH, dst, shift));
addInstr(env, s390_insn_alu(4, S390_ALU_RSHA, dst, shift));
break;
case Iop_1Sto64:
shift.variant.imm = 63;
addInstr(env, s390_insn_alu(8, S390_ALU_LSH, dst, shift));
addInstr(env, s390_insn_alu(8, S390_ALU_RSHA, dst, shift));
break;
default:
goto irreducible;
}
return dst;
}
/* Regular processing */
if (unop == Iop_128to64) {
HReg dst_hi, dst_lo;
s390_isel_int128_expr(&dst_hi, &dst_lo, env, arg);
return dst_lo;
}
if (unop == Iop_128HIto64) {
HReg dst_hi, dst_lo;
s390_isel_int128_expr(&dst_hi, &dst_lo, env, arg);
return dst_hi;
}
dst = newVRegI(env); /* Result goes into a new register */
opnd = s390_isel_int_expr_RMI(env, arg); /* Process the operand */
switch (unop) {
case Iop_8Uto16:
case Iop_8Uto32:
case Iop_8Uto64:
insn = s390_insn_unop(size, S390_ZERO_EXTEND_8, dst, opnd);
break;
case Iop_16Uto32:
case Iop_16Uto64:
insn = s390_insn_unop(size, S390_ZERO_EXTEND_16, dst, opnd);
break;
case Iop_32Uto64:
insn = s390_insn_unop(size, S390_ZERO_EXTEND_32, dst, opnd);
break;
case Iop_8Sto16:
case Iop_8Sto32:
case Iop_8Sto64:
insn = s390_insn_unop(size, S390_SIGN_EXTEND_8, dst, opnd);
break;
case Iop_16Sto32:
case Iop_16Sto64:
insn = s390_insn_unop(size, S390_SIGN_EXTEND_16, dst, opnd);
break;
case Iop_32Sto64:
insn = s390_insn_unop(size, S390_SIGN_EXTEND_32, dst, opnd);
break;
case Iop_64to8:
case Iop_64to16:
case Iop_64to32:
case Iop_32to8:
case Iop_32to16:
case Iop_16to8:
/* Down-casts are no-ops. Upstream operations will only look at
the bytes that make up the result of the down-cast. So there
is no point setting the other bytes to 0. */
insn = s390_opnd_copy(8, dst, opnd);
break;
case Iop_64HIto32:
addInstr(env, s390_opnd_copy(8, dst, opnd));
shift.variant.imm = 32;
insn = s390_insn_alu(8, S390_ALU_RSH, dst, shift);
break;
case Iop_32HIto16:
addInstr(env, s390_opnd_copy(4, dst, opnd));
shift.variant.imm = 16;
insn = s390_insn_alu(4, S390_ALU_RSH, dst, shift);
break;
case Iop_16HIto8:
addInstr(env, s390_opnd_copy(2, dst, opnd));
shift.variant.imm = 8;
insn = s390_insn_alu(2, S390_ALU_RSH, dst, shift);
break;
case Iop_Not8:
case Iop_Not16:
case Iop_Not32:
case Iop_Not64:
/* XOR with ffff... */
mask.variant.imm = ~(ULong)0;
addInstr(env, s390_opnd_copy(size, dst, opnd));
insn = s390_insn_alu(size, S390_ALU_XOR, dst, mask);
break;
case Iop_Left8:
case Iop_Left16:
case Iop_Left32:
case Iop_Left64:
addInstr(env, s390_insn_unop(size, S390_NEGATE, dst, opnd));
insn = s390_insn_alu(size, S390_ALU_OR, dst, opnd);
break;
case Iop_CmpwNEZ32:
case Iop_CmpwNEZ64: {
/* Use the fact that x | -x == 0 iff x == 0. Otherwise, either X
or -X will have a 1 in the MSB. */
addInstr(env, s390_insn_unop(size, S390_NEGATE, dst, opnd));
addInstr(env, s390_insn_alu(size, S390_ALU_OR, dst, opnd));
shift.variant.imm = (unop == Iop_CmpwNEZ32) ? 31 : 63;
addInstr(env, s390_insn_alu(size, S390_ALU_RSHA, dst, shift));
return dst;
}
case Iop_Clz64: {
HReg r10, r11;
/* This will be implemented using FLOGR, if possible. So we need to
set aside a pair of non-virtual registers. The result (number of
left-most zero bits) will be in r10. The value in r11 is unspecified
and must not be used. */
r10 = make_gpr(10);
r11 = make_gpr(11);
addInstr(env, s390_insn_clz(8, r10, r11, opnd));
addInstr(env, s390_insn_move(8, dst, r10));
return dst;
}
default:
goto irreducible;
}
addInstr(env, insn);
return dst;
}
/* --------- GET --------- */
case Iex_Get: {
HReg dst = newVRegI(env);
s390_amode *am = s390_amode_for_guest_state(expr->Iex.Get.offset);
/* We never load more than 8 bytes from the guest state, because the
floating point register pair is not contiguous. */
vassert(size <= 8);
addInstr(env, s390_insn_load(size, dst, am));
return dst;
}
case Iex_GetI:
/* not needed */
break;
/* --------- CCALL --------- */
case Iex_CCall: {
HReg dst = newVRegI(env);
HReg ret = make_gpr(S390_REGNO_RETURN_VALUE);
UInt addToSp = 0;
RetLoc rloc = mk_RetLoc_INVALID();
doHelperCall(&addToSp, &rloc, env, NULL, expr->Iex.CCall.cee,
expr->Iex.CCall.retty, expr->Iex.CCall.args);
vassert(is_sane_RetLoc(rloc));
vassert(rloc.pri == RLPri_Int);
vassert(addToSp == 0);
addInstr(env, s390_insn_move(sizeof(ULong), dst, ret));
return dst;
}
/* --------- LITERAL --------- */
/* Load a literal into a register. Create a "load immediate"
v-insn and return the register. */
case Iex_Const: {
ULong value;
HReg dst = newVRegI(env);
const IRConst *con = expr->Iex.Const.con;
/* Bitwise copy of the value. No sign/zero-extension */
switch (con->tag) {
case Ico_U64: value = con->Ico.U64; break;
case Ico_U32: value = con->Ico.U32; break;
case Ico_U16: value = con->Ico.U16; break;
case Ico_U8: value = con->Ico.U8; break;
default: vpanic("s390_isel_int_expr: invalid constant");
}
addInstr(env, s390_insn_load_immediate(size, dst, value));
return dst;
}
/* --------- MULTIPLEX --------- */
case Iex_ITE: {
IRExpr *cond_expr;
HReg dst, r1;
s390_opnd_RMI r0;
cond_expr = expr->Iex.ITE.cond;
vassert(typeOfIRExpr(env->type_env, cond_expr) == Ity_I1);
dst = newVRegI(env);
r0 = s390_isel_int_expr_RMI(env, expr->Iex.ITE.iffalse);
r1 = s390_isel_int_expr(env, expr->Iex.ITE.iftrue);
size = sizeofIRType(typeOfIRExpr(env->type_env, expr->Iex.ITE.iftrue));
s390_cc_t cc = s390_isel_cc(env, cond_expr);
addInstr(env, s390_insn_move(size, dst, r1));
addInstr(env, s390_insn_cond_move(size, s390_cc_invert(cc), dst, r0));
return dst;
}
default:
break;
}
/* We get here if no pattern matched. */
irreducible:
ppIRExpr(expr);
vpanic("s390_isel_int_expr: cannot reduce tree");
}
static HReg
s390_isel_int_expr(ISelEnv *env, IRExpr *expr)
{
HReg dst = s390_isel_int_expr_wrk(env, expr);
/* Sanity checks ... */
vassert(hregClass(dst) == HRcInt64);
vassert(hregIsVirtual(dst));
return dst;
}
static s390_opnd_RMI
s390_isel_int_expr_RMI(ISelEnv *env, IRExpr *expr)
{
IRType ty = typeOfIRExpr(env->type_env, expr);
s390_opnd_RMI dst;
vassert(ty == Ity_I8 || ty == Ity_I16 || ty == Ity_I32 ||
ty == Ity_I64);
if (expr->tag == Iex_Load) {
dst.tag = S390_OPND_AMODE;
dst.variant.am = s390_isel_amode(env, expr->Iex.Load.addr);
} else if (expr->tag == Iex_Get) {
dst.tag = S390_OPND_AMODE;
dst.variant.am = s390_amode_for_guest_state(expr->Iex.Get.offset);
} else if (expr->tag == Iex_Const) {
ULong value;
/* The bit pattern for the value will be stored as is in the least
significant bits of VALUE. */
switch (expr->Iex.Const.con->tag) {
case Ico_U1: value = expr->Iex.Const.con->Ico.U1; break;
case Ico_U8: value = expr->Iex.Const.con->Ico.U8; break;
case Ico_U16: value = expr->Iex.Const.con->Ico.U16; break;
case Ico_U32: value = expr->Iex.Const.con->Ico.U32; break;
case Ico_U64: value = expr->Iex.Const.con->Ico.U64; break;
default:
vpanic("s390_isel_int_expr_RMI");
}
dst.tag = S390_OPND_IMMEDIATE;
dst.variant.imm = value;
} else {
dst.tag = S390_OPND_REG;
dst.variant.reg = s390_isel_int_expr(env, expr);
}
return dst;
}
/*---------------------------------------------------------*/
/*--- ISEL: Floating point expressions (128 bit) ---*/
/*---------------------------------------------------------*/
static void
s390_isel_float128_expr_wrk(HReg *dst_hi, HReg *dst_lo, ISelEnv *env,
IRExpr *expr)
{
IRType ty = typeOfIRExpr(env->type_env, expr);
vassert(ty == Ity_F128);
switch (expr->tag) {
case Iex_RdTmp:
/* Return the virtual registers that hold the temporary. */
lookupIRTemp128(dst_hi, dst_lo, env, expr->Iex.RdTmp.tmp);
return;
/* --------- LOAD --------- */
case Iex_Load: {
IRExpr *addr_hi, *addr_lo;
s390_amode *am_hi, *am_lo;
if (expr->Iex.Load.end != Iend_BE)
goto irreducible;
addr_hi = expr->Iex.Load.addr;
addr_lo = IRExpr_Binop(Iop_Add64, addr_hi, mkU64(8));
am_hi = s390_isel_amode(env, addr_hi);
am_lo = s390_isel_amode(env, addr_lo);
*dst_hi = newVRegF(env);
*dst_lo = newVRegF(env);
addInstr(env, s390_insn_load(8, *dst_hi, am_hi));
addInstr(env, s390_insn_load(8, *dst_hi, am_lo));
return;
}
/* --------- GET --------- */
case Iex_Get:
/* This is not supported because loading 128-bit from the guest
state is almost certainly wrong. Use get_fpr_pair instead. */
vpanic("Iex_Get with F128 data");
/* --------- 4-ary OP --------- */
case Iex_Qop:
vpanic("Iex_Qop with F128 data");
/* --------- TERNARY OP --------- */
case Iex_Triop: {
IRTriop *triop = expr->Iex.Triop.details;
IROp op = triop->op;
IRExpr *left = triop->arg2;
IRExpr *right = triop->arg3;
s390_bfp_binop_t bfpop;
HReg op1_hi, op1_lo, op2_hi, op2_lo, f12, f13, f14, f15;
s390_isel_float128_expr(&op1_hi, &op1_lo, env, left); /* 1st operand */
s390_isel_float128_expr(&op2_hi, &op2_lo, env, right); /* 2nd operand */
/* We use non-virtual registers as pairs (f13, f15) and (f12, f14)) */
f12 = make_fpr(12);
f13 = make_fpr(13);
f14 = make_fpr(14);
f15 = make_fpr(15);
/* 1st operand --> (f12, f14) */
addInstr(env, s390_insn_move(8, f12, op1_hi));
addInstr(env, s390_insn_move(8, f14, op1_lo));
/* 2nd operand --> (f13, f15) */
addInstr(env, s390_insn_move(8, f13, op2_hi));
addInstr(env, s390_insn_move(8, f15, op2_lo));
switch (op) {
case Iop_AddF128: bfpop = S390_BFP_ADD; break;
case Iop_SubF128: bfpop = S390_BFP_SUB; break;
case Iop_MulF128: bfpop = S390_BFP_MUL; break;
case Iop_DivF128: bfpop = S390_BFP_DIV; break;
default:
goto irreducible;
}
set_bfp_rounding_mode_in_fpc(env, triop->arg1);
addInstr(env, s390_insn_bfp128_binop(16, bfpop, f12, f14, f13, f15));
/* Move result to virtual destination register */
*dst_hi = newVRegF(env);
*dst_lo = newVRegF(env);
addInstr(env, s390_insn_move(8, *dst_hi, f12));
addInstr(env, s390_insn_move(8, *dst_lo, f14));
return;
}
/* --------- BINARY OP --------- */
case Iex_Binop: {
switch (expr->Iex.Binop.op) {
case Iop_SqrtF128: {
HReg op_hi, op_lo, f12, f13, f14, f15;
/* We use non-virtual registers as pairs (f13, f15) and (f12, f14)) */
f12 = make_fpr(12);
f13 = make_fpr(13);
f14 = make_fpr(14);
f15 = make_fpr(15);
s390_isel_float128_expr(&op_hi, &op_lo, env, expr->Iex.Binop.arg2);
/* operand --> (f13, f15) */
addInstr(env, s390_insn_move(8, f13, op_hi));
addInstr(env, s390_insn_move(8, f15, op_lo));
set_bfp_rounding_mode_in_fpc(env, expr->Iex.Binop.arg1);
addInstr(env, s390_insn_bfp128_unop(16, S390_BFP_SQRT, f12, f14,
f13, f15));
/* Move result to virtual destination registers */
*dst_hi = newVRegF(env);
*dst_lo = newVRegF(env);
addInstr(env, s390_insn_move(8, *dst_hi, f12));
addInstr(env, s390_insn_move(8, *dst_lo, f14));
return;
}
case Iop_F64HLtoF128:
*dst_hi = s390_isel_float_expr(env, expr->Iex.Binop.arg1);
*dst_lo = s390_isel_float_expr(env, expr->Iex.Binop.arg2);
return;
case Iop_D32toF128:
case Iop_D64toF128: {
IRExpr *irrm;
IRExpr *left;
s390_dfp_round_t rm;
HReg h1; /* virtual reg. to hold source */
HReg f0, f2, f4, r1; /* real registers used by PFPO */
s390_fp_conv_t fpconv;
switch (expr->Iex.Binop.op) {
case Iop_D32toF128:
fpconv = S390_FP_D32_TO_F128;
break;
case Iop_D64toF128:
fpconv = S390_FP_D64_TO_F128;
break;
default: goto irreducible;
}
f4 = make_fpr(4); /* source */
f0 = make_fpr(0); /* destination */
f2 = make_fpr(2); /* destination */
r1 = make_gpr(1); /* GPR #1 clobbered */
irrm = expr->Iex.Binop.arg1;
left = expr->Iex.Binop.arg2;
rm = get_dfp_rounding_mode(env, irrm);
h1 = s390_isel_dfp_expr(env, left);
addInstr(env, s390_insn_move(8, f4, h1));
addInstr(env, s390_insn_fp128_convert(16, fpconv, f0, f2,
f4, INVALID_HREG, r1, rm));
/* (f0, f2) --> destination */
*dst_hi = newVRegF(env);
*dst_lo = newVRegF(env);
addInstr(env, s390_insn_move(8, *dst_hi, f0));
addInstr(env, s390_insn_move(8, *dst_lo, f2));
return;
}
case Iop_D128toF128: {
IRExpr *irrm;
IRExpr *left;
s390_dfp_round_t rm;
HReg op_hi, op_lo;
HReg f0, f2, f4, f6, r1; /* real registers used by PFPO */
f4 = make_fpr(4); /* source */
f6 = make_fpr(6); /* source */
f0 = make_fpr(0); /* destination */
f2 = make_fpr(2); /* destination */
r1 = make_gpr(1); /* GPR #1 clobbered */
irrm = expr->Iex.Binop.arg1;
left = expr->Iex.Binop.arg2;
rm = get_dfp_rounding_mode(env, irrm);
s390_isel_dfp128_expr(&op_hi, &op_lo, env, left);
/* operand --> (f4, f6) */
addInstr(env, s390_insn_move(8, f4, op_hi));
addInstr(env, s390_insn_move(8, f6, op_lo));
addInstr(env, s390_insn_fp128_convert(16, S390_FP_D128_TO_F128, f0, f2,
f4, f6, r1, rm));
/* (f0, f2) --> destination */
*dst_hi = newVRegF(env);
*dst_lo = newVRegF(env);
addInstr(env, s390_insn_move(8, *dst_hi, f0));
addInstr(env, s390_insn_move(8, *dst_lo, f2));
return;
}
default:
goto irreducible;
}
}
/* --------- UNARY OP --------- */
case Iex_Unop: {
IRExpr *left = expr->Iex.Unop.arg;
s390_bfp_unop_t bfpop;
s390_bfp_conv_t conv;
HReg op_hi, op_lo, op, f12, f13, f14, f15;
/* We use non-virtual registers as pairs (f13, f15) and (f12, f14)) */
f12 = make_fpr(12);
f13 = make_fpr(13);
f14 = make_fpr(14);
f15 = make_fpr(15);
switch (expr->Iex.Unop.op) {
case Iop_NegF128:
if (left->tag == Iex_Unop &&
(left->Iex.Unop.op == Iop_AbsF32 ||
left->Iex.Unop.op == Iop_AbsF64))
bfpop = S390_BFP_NABS;
else
bfpop = S390_BFP_NEG;
goto float128_opnd;
case Iop_AbsF128: bfpop = S390_BFP_ABS; goto float128_opnd;
case Iop_I32StoF128: conv = S390_BFP_I32_TO_F128; goto convert_int;
case Iop_I64StoF128: conv = S390_BFP_I64_TO_F128; goto convert_int;
case Iop_I32UtoF128: conv = S390_BFP_U32_TO_F128; goto convert_int;
case Iop_I64UtoF128: conv = S390_BFP_U64_TO_F128; goto convert_int;
case Iop_F32toF128: conv = S390_BFP_F32_TO_F128; goto convert_float;
case Iop_F64toF128: conv = S390_BFP_F64_TO_F128; goto convert_float;
default:
goto irreducible;
}
float128_opnd:
s390_isel_float128_expr(&op_hi, &op_lo, env, left);
/* operand --> (f13, f15) */
addInstr(env, s390_insn_move(8, f13, op_hi));
addInstr(env, s390_insn_move(8, f15, op_lo));
addInstr(env, s390_insn_bfp128_unop(16, bfpop, f12, f14, f13, f15));
goto move_dst;
convert_float:
op = s390_isel_float_expr(env, left);
addInstr(env, s390_insn_bfp128_convert_to(16, conv, f12, f14, op));
goto move_dst;
convert_int:
op = s390_isel_int_expr(env, left);
addInstr(env, s390_insn_bfp128_convert_to(16, conv, f12, f14, op));
goto move_dst;
move_dst:
/* Move result to virtual destination registers */
*dst_hi = newVRegF(env);
*dst_lo = newVRegF(env);
addInstr(env, s390_insn_move(8, *dst_hi, f12));
addInstr(env, s390_insn_move(8, *dst_lo, f14));
return;
}
default:
goto irreducible;
}
/* We get here if no pattern matched. */
irreducible:
ppIRExpr(expr);
vpanic("s390_isel_float128_expr: cannot reduce tree");
}
/* Compute a 128-bit value into two 64-bit registers. These may be either
real or virtual regs; in any case they must not be changed by subsequent
code emitted by the caller. */
static void
s390_isel_float128_expr(HReg *dst_hi, HReg *dst_lo, ISelEnv *env, IRExpr *expr)
{
s390_isel_float128_expr_wrk(dst_hi, dst_lo, env, expr);
/* Sanity checks ... */
vassert(hregIsVirtual(*dst_hi));
vassert(hregIsVirtual(*dst_lo));
vassert(hregClass(*dst_hi) == HRcFlt64);
vassert(hregClass(*dst_lo) == HRcFlt64);
}
/*---------------------------------------------------------*/
/*--- ISEL: Floating point expressions (64 bit) ---*/
/*---------------------------------------------------------*/
static HReg
s390_isel_float_expr_wrk(ISelEnv *env, IRExpr *expr)
{
IRType ty = typeOfIRExpr(env->type_env, expr);
UChar size;
vassert(ty == Ity_F32 || ty == Ity_F64);
size = sizeofIRType(ty);
switch (expr->tag) {
case Iex_RdTmp:
/* Return the virtual register that holds the temporary. */
return lookupIRTemp(env, expr->Iex.RdTmp.tmp);
/* --------- LOAD --------- */
case Iex_Load: {
HReg dst = newVRegF(env);
s390_amode *am = s390_isel_amode(env, expr->Iex.Load.addr);
if (expr->Iex.Load.end != Iend_BE)
goto irreducible;
addInstr(env, s390_insn_load(size, dst, am));
return dst;
}
/* --------- GET --------- */
case Iex_Get: {
HReg dst = newVRegF(env);
s390_amode *am = s390_amode_for_guest_state(expr->Iex.Get.offset);
addInstr(env, s390_insn_load(size, dst, am));
return dst;
}
/* --------- LITERAL --------- */
/* Load a literal into a register. Create a "load immediate"
v-insn and return the register. */
case Iex_Const: {
ULong value;
HReg dst = newVRegF(env);
const IRConst *con = expr->Iex.Const.con;
/* Bitwise copy of the value. No sign/zero-extension */
switch (con->tag) {
case Ico_F32i: value = con->Ico.F32i; break;
case Ico_F64i: value = con->Ico.F64i; break;
default: vpanic("s390_isel_float_expr: invalid constant");
}
if (value != 0) vpanic("cannot load immediate floating point constant");
addInstr(env, s390_insn_load_immediate(size, dst, value));
return dst;
}
/* --------- 4-ary OP --------- */
case Iex_Qop: {
HReg op1, op2, op3, dst;
s390_bfp_triop_t bfpop;
op3 = s390_isel_float_expr(env, expr->Iex.Qop.details->arg2);
op2 = s390_isel_float_expr(env, expr->Iex.Qop.details->arg3);
op1 = s390_isel_float_expr(env, expr->Iex.Qop.details->arg4);
dst = newVRegF(env);
addInstr(env, s390_insn_move(size, dst, op1));
switch (expr->Iex.Qop.details->op) {
case Iop_MAddF32:
case Iop_MAddF64: bfpop = S390_BFP_MADD; break;
case Iop_MSubF32:
case Iop_MSubF64: bfpop = S390_BFP_MSUB; break;
default:
goto irreducible;
}
set_bfp_rounding_mode_in_fpc(env, expr->Iex.Qop.details->arg1);
addInstr(env, s390_insn_bfp_triop(size, bfpop, dst, op2, op3));
return dst;
}
/* --------- TERNARY OP --------- */
case Iex_Triop: {
IRTriop *triop = expr->Iex.Triop.details;
IROp op = triop->op;
IRExpr *left = triop->arg2;
IRExpr *right = triop->arg3;
s390_bfp_binop_t bfpop;
HReg h1, op2, dst;
h1 = s390_isel_float_expr(env, left); /* Process 1st operand */
op2 = s390_isel_float_expr(env, right); /* Process 2nd operand */
dst = newVRegF(env);
addInstr(env, s390_insn_move(size, dst, h1));
switch (op) {
case Iop_AddF32:
case Iop_AddF64: bfpop = S390_BFP_ADD; break;
case Iop_SubF32:
case Iop_SubF64: bfpop = S390_BFP_SUB; break;
case Iop_MulF32:
case Iop_MulF64: bfpop = S390_BFP_MUL; break;
case Iop_DivF32:
case Iop_DivF64: bfpop = S390_BFP_DIV; break;
default:
goto irreducible;
}
set_bfp_rounding_mode_in_fpc(env, triop->arg1);
addInstr(env, s390_insn_bfp_binop(size, bfpop, dst, op2));
return dst;
}
/* --------- BINARY OP --------- */
case Iex_Binop: {
IROp op = expr->Iex.Binop.op;
IRExpr *irrm = expr->Iex.Binop.arg1;
IRExpr *left = expr->Iex.Binop.arg2;
HReg h1, dst;
s390_bfp_conv_t conv;
s390_fp_conv_t fpconv;
switch (op) {
case Iop_SqrtF32:
case Iop_SqrtF64:
h1 = s390_isel_float_expr(env, left);
dst = newVRegF(env);
set_bfp_rounding_mode_in_fpc(env, irrm);
addInstr(env, s390_insn_bfp_unop(size, S390_BFP_SQRT, dst, h1));
return dst;
case Iop_F64toF32: conv = S390_BFP_F64_TO_F32; goto convert_float;
case Iop_I32StoF32: conv = S390_BFP_I32_TO_F32; goto convert_int;
case Iop_I32UtoF32: conv = S390_BFP_U32_TO_F32; goto convert_int;
case Iop_I64StoF32: conv = S390_BFP_I64_TO_F32; goto convert_int;
case Iop_I64StoF64: conv = S390_BFP_I64_TO_F64; goto convert_int;
case Iop_I64UtoF32: conv = S390_BFP_U64_TO_F32; goto convert_int;
case Iop_I64UtoF64: conv = S390_BFP_U64_TO_F64; goto convert_int;
case Iop_D32toF32: fpconv = S390_FP_D32_TO_F32; goto convert_dfp;
case Iop_D32toF64: fpconv = S390_FP_D32_TO_F64; goto convert_dfp;
case Iop_D64toF32: fpconv = S390_FP_D64_TO_F32; goto convert_dfp;
case Iop_D64toF64: fpconv = S390_FP_D64_TO_F64; goto convert_dfp;
case Iop_D128toF32: fpconv = S390_FP_D128_TO_F32; goto convert_dfp128;
case Iop_D128toF64: fpconv = S390_FP_D128_TO_F64; goto convert_dfp128;
convert_float:
h1 = s390_isel_float_expr(env, left);
goto convert;
convert_int:
h1 = s390_isel_int_expr(env, left);
goto convert;
convert: {
s390_bfp_round_t rounding_mode;
/* convert-from-fixed and load-rounded have a rounding mode field
when the floating point extension facility is installed. */
dst = newVRegF(env);
if (s390_host_has_fpext) {
rounding_mode = get_bfp_rounding_mode(env, irrm);
} else {
set_bfp_rounding_mode_in_fpc(env, irrm);
rounding_mode = S390_BFP_ROUND_PER_FPC;
}
addInstr(env, s390_insn_bfp_convert(size, conv, dst, h1,
rounding_mode));
return dst;
}
convert_dfp: {
s390_dfp_round_t rm;
HReg f0, f4, r1; /* real registers used by PFPO */
f4 = make_fpr(4); /* source */
f0 = make_fpr(0); /* destination */
r1 = make_gpr(1); /* GPR #1 clobbered */
h1 = s390_isel_dfp_expr(env, left);
dst = newVRegF(env);
rm = get_dfp_rounding_mode(env, irrm);
/* operand --> f4 */
addInstr(env, s390_insn_move(8, f4, h1));
addInstr(env, s390_insn_fp_convert(size, fpconv, f0, f4, r1, rm));
/* f0 --> destination */
addInstr(env, s390_insn_move(8, dst, f0));
return dst;
}
convert_dfp128: {
s390_dfp_round_t rm;
HReg op_hi, op_lo;
HReg f0, f4, f6, r1; /* real registers used by PFPO */
f4 = make_fpr(4); /* source */
f6 = make_fpr(6); /* source */
f0 = make_fpr(0); /* destination */
r1 = make_gpr(1); /* GPR #1 clobbered */
s390_isel_dfp128_expr(&op_hi, &op_lo, env, left);
dst = newVRegF(env);
rm = get_dfp_rounding_mode(env, irrm);
/* operand --> (f4, f6) */
addInstr(env, s390_insn_move(8, f4, op_hi));
addInstr(env, s390_insn_move(8, f6, op_lo));
addInstr(env, s390_insn_fp128_convert(16, fpconv, f0, INVALID_HREG,
f4, f6, r1, rm));
/* f0 --> destination */
addInstr(env, s390_insn_move(8, dst, f0));
return dst;
}
default:
goto irreducible;
case Iop_F128toF64:
case Iop_F128toF32: {
HReg op_hi, op_lo, f12, f13, f14, f15;
s390_bfp_round_t rounding_mode;
conv = op == Iop_F128toF32 ? S390_BFP_F128_TO_F32
: S390_BFP_F128_TO_F64;
s390_isel_float128_expr(&op_hi, &op_lo, env, left);
/* We use non-virtual registers as pairs (f13, f15) and (f12, f14)) */
f12 = make_fpr(12);
f13 = make_fpr(13);
f14 = make_fpr(14);
f15 = make_fpr(15);
/* operand --> (f13, f15) */
addInstr(env, s390_insn_move(8, f13, op_hi));
addInstr(env, s390_insn_move(8, f15, op_lo));
/* result --> (f12, f14) */
/* load-rounded has a rounding mode field when the floating point
extension facility is installed. */
if (s390_host_has_fpext) {
rounding_mode = get_bfp_rounding_mode(env, irrm);
} else {
set_bfp_rounding_mode_in_fpc(env, irrm);
rounding_mode = S390_BFP_ROUND_PER_FPC;
}
addInstr(env, s390_insn_bfp128_convert_from(size, conv, f12, f14,
f13, f15, rounding_mode));
dst = newVRegF(env);
addInstr(env, s390_insn_move(8, dst, f12));
return dst;
}
}
}
/* --------- UNARY OP --------- */
case Iex_Unop: {
IROp op = expr->Iex.Unop.op;
IRExpr *left = expr->Iex.Unop.arg;
s390_bfp_unop_t bfpop;
s390_bfp_conv_t conv;
HReg h1, dst;
if (op == Iop_F128HItoF64 || op == Iop_F128LOtoF64) {
HReg dst_hi, dst_lo;
s390_isel_float128_expr(&dst_hi, &dst_lo, env, left);
return op == Iop_F128LOtoF64 ? dst_lo : dst_hi;
}
if (op == Iop_ReinterpI64asF64 || op == Iop_ReinterpI32asF32) {
dst = newVRegF(env);
h1 = s390_isel_int_expr(env, left); /* Process the operand */
addInstr(env, s390_insn_move(size, dst, h1));
return dst;
}
switch (op) {
case Iop_NegF32:
case Iop_NegF64:
if (left->tag == Iex_Unop &&
(left->Iex.Unop.op == Iop_AbsF32 ||
left->Iex.Unop.op == Iop_AbsF64))
bfpop = S390_BFP_NABS;
else
bfpop = S390_BFP_NEG;
break;
case Iop_AbsF32:
case Iop_AbsF64:
bfpop = S390_BFP_ABS;
break;
case Iop_I32StoF64: conv = S390_BFP_I32_TO_F64; goto convert_int1;
case Iop_I32UtoF64: conv = S390_BFP_U32_TO_F64; goto convert_int1;
case Iop_F32toF64: conv = S390_BFP_F32_TO_F64; goto convert_float1;
convert_float1:
h1 = s390_isel_float_expr(env, left);
goto convert1;
convert_int1:
h1 = s390_isel_int_expr(env, left);
goto convert1;
convert1:
dst = newVRegF(env);
/* No rounding mode is needed for these conversions. Just stick
one in. It won't be used later on. */
addInstr(env, s390_insn_bfp_convert(size, conv, dst, h1,
S390_BFP_ROUND_NEAREST_EVEN));
return dst;
default:
goto irreducible;
}
/* Process operand */
h1 = s390_isel_float_expr(env, left);
dst = newVRegF(env);
addInstr(env, s390_insn_bfp_unop(size, bfpop, dst, h1));
return dst;
}
default:
goto irreducible;
}
/* We get here if no pattern matched. */
irreducible:
ppIRExpr(expr);
vpanic("s390_isel_float_expr: cannot reduce tree");
}
static HReg
s390_isel_float_expr(ISelEnv *env, IRExpr *expr)
{
HReg dst = s390_isel_float_expr_wrk(env, expr);
/* Sanity checks ... */
vassert(hregClass(dst) == HRcFlt64);
vassert(hregIsVirtual(dst));
return dst;
}
/*---------------------------------------------------------*/
/*--- ISEL: Decimal point expressions (128 bit) ---*/
/*---------------------------------------------------------*/
static void
s390_isel_dfp128_expr_wrk(HReg *dst_hi, HReg *dst_lo, ISelEnv *env,
IRExpr *expr)
{
IRType ty = typeOfIRExpr(env->type_env, expr);
vassert(ty == Ity_D128);
switch (expr->tag) {
case Iex_RdTmp:
/* Return the virtual registers that hold the temporary. */
lookupIRTemp128(dst_hi, dst_lo, env, expr->Iex.RdTmp.tmp);
return;
/* --------- LOAD --------- */
case Iex_Load: {
IRExpr *addr_hi, *addr_lo;
s390_amode *am_hi, *am_lo;
if (expr->Iex.Load.end != Iend_BE)
goto irreducible;
addr_hi = expr->Iex.Load.addr;
addr_lo = IRExpr_Binop(Iop_Add64, addr_hi, mkU64(8));
am_hi = s390_isel_amode(env, addr_hi);
am_lo = s390_isel_amode(env, addr_lo);
*dst_hi = newVRegF(env);
*dst_lo = newVRegF(env);
addInstr(env, s390_insn_load(8, *dst_hi, am_hi));
addInstr(env, s390_insn_load(8, *dst_hi, am_lo));
return;
}
/* --------- GET --------- */
case Iex_Get:
/* This is not supported because loading 128-bit from the guest
state is almost certainly wrong. Use get_dpr_pair instead. */
vpanic("Iex_Get with D128 data");
/* --------- 4-ary OP --------- */
case Iex_Qop:
vpanic("Iex_Qop with D128 data");
/* --------- TERNARY OP --------- */
case Iex_Triop: {
IRTriop *triop = expr->Iex.Triop.details;
IROp op = triop->op;
IRExpr *irrm = triop->arg1;
IRExpr *left = triop->arg2;
IRExpr *right = triop->arg3;
s390_dfp_round_t rounding_mode;
s390_dfp_binop_t dfpop;
HReg op1_hi, op1_lo, op2_hi, op2_lo, f9, f11, f12, f13, f14, f15;
/* We use non-virtual registers as pairs with (f9, f11) as op1,
(f12, f14) as op2 and (f13, f15) as destination) */
f9 = make_fpr(9);
f11 = make_fpr(11);
f12 = make_fpr(12);
f13 = make_fpr(13);
f14 = make_fpr(14);
f15 = make_fpr(15);
switch (op) {
case Iop_AddD128: dfpop = S390_DFP_ADD; goto evaluate_dfp128;
case Iop_SubD128: dfpop = S390_DFP_SUB; goto evaluate_dfp128;
case Iop_MulD128: dfpop = S390_DFP_MUL; goto evaluate_dfp128;
case Iop_DivD128: dfpop = S390_DFP_DIV; goto evaluate_dfp128;
case Iop_QuantizeD128: dfpop = S390_DFP_QUANTIZE; goto evaluate_dfp128;
evaluate_dfp128: {
/* Process 1st operand */
s390_isel_dfp128_expr(&op1_hi, &op1_lo, env, left);
/* 1st operand --> (f9, f11) */
addInstr(env, s390_insn_move(8, f9, op1_hi));
addInstr(env, s390_insn_move(8, f11, op1_lo));
/* Process 2nd operand */
s390_isel_dfp128_expr(&op2_hi, &op2_lo, env, right);
/* 2nd operand --> (f12, f14) */
addInstr(env, s390_insn_move(8, f12, op2_hi));
addInstr(env, s390_insn_move(8, f14, op2_lo));
/* DFP arithmetic ops take rounding mode only when fpext is
installed. But, DFP quantize operation takes rm irrespective
of fpext facility . */
if (s390_host_has_fpext || op == Iop_QuantizeD128) {
rounding_mode =<