target-hppa: Implement branches

Signed-off-by: Richard Henderson <rth@twiddle.net>
This commit is contained in:
Richard Henderson 2016-12-15 13:50:28 -08:00
parent b2167459ae
commit 98cd9ca7cc
1 changed files with 477 additions and 0 deletions

View File

@ -455,6 +455,14 @@ static target_long low_sextract(uint32_t val, int pos, int len)
return x;
}
static target_long assemble_12(uint32_t insn)
{
target_ulong x = -(target_ulong)(insn & 1);
x = (x << 1) | extract32(insn, 2, 1);
x = (x << 10) | extract32(insn, 3, 10);
return x;
}
static target_long assemble_16(uint32_t insn)
{
/* Take the name from PA2.0, which produces a 16-bit number
@ -463,6 +471,15 @@ static target_long assemble_16(uint32_t insn)
return low_sextract(insn, 0, 14);
}
static target_long assemble_17(uint32_t insn)
{
target_ulong x = -(target_ulong)(insn & 1);
x = (x << 5) | extract32(insn, 16, 5);
x = (x << 1) | extract32(insn, 2, 1);
x = (x << 10) | extract32(insn, 3, 10);
return x << 2;
}
static target_long assemble_21(uint32_t insn)
{
target_ulong x = -(target_ulong)(insn & 1);
@ -473,6 +490,15 @@ static target_long assemble_21(uint32_t insn)
return x << 11;
}
static target_long assemble_22(uint32_t insn)
{
target_ulong x = -(target_ulong)(insn & 1);
x = (x << 10) | extract32(insn, 16, 10);
x = (x << 1) | extract32(insn, 2, 1);
x = (x << 10) | extract32(insn, 3, 10);
return x << 2;
}
/* The parisc documentation describes only the general interpretation of
the conditions, without describing their exact implementation. The
interpretations do not stand up well when considering ADD,C and SUB,B.
@ -574,6 +600,24 @@ static DisasCond do_log_cond(unsigned cf, TCGv res)
return do_cond(cf, res, res, res);
}
/* Similar, but for shift/extract/deposit conditions. */
static DisasCond do_sed_cond(unsigned orig, TCGv res)
{
unsigned c, f;
/* Convert the compressed condition codes to standard.
0-2 are the same as logicals (nv,<,<=), while 3 is OD.
4-7 are the reverse of 0-3. */
c = orig & 3;
if (c == 3) {
c = 7;
}
f = (orig & 4) / 4;
return do_log_cond(c * 2 + f, res);
}
/* Similar, but for unit conditions. */
static DisasCond do_unit_cond(unsigned cf, TCGv res, TCGv in1, TCGv in2)
@ -897,6 +941,188 @@ static ExitStatus do_unit(DisasContext *ctx, unsigned rt, TCGv in1,
return NO_EXIT;
}
/* Emit an unconditional branch to a direct target, which may or may not
have already had nullification handled. */
static ExitStatus do_dbranch(DisasContext *ctx, target_ulong dest,
unsigned link, bool is_n)
{
if (ctx->null_cond.c == TCG_COND_NEVER && ctx->null_lab == NULL) {
if (link != 0) {
copy_iaoq_entry(cpu_gr[link], ctx->iaoq_n, ctx->iaoq_n_var);
}
ctx->iaoq_n = dest;
if (is_n) {
ctx->null_cond.c = TCG_COND_ALWAYS;
}
return NO_EXIT;
} else {
nullify_over(ctx);
if (link != 0) {
copy_iaoq_entry(cpu_gr[link], ctx->iaoq_n, ctx->iaoq_n_var);
}
if (is_n && use_nullify_skip(ctx)) {
nullify_set(ctx, 0);
gen_goto_tb(ctx, 0, dest, dest + 4);
} else {
nullify_set(ctx, is_n);
gen_goto_tb(ctx, 0, ctx->iaoq_b, dest);
}
nullify_end(ctx, NO_EXIT);
nullify_set(ctx, 0);
gen_goto_tb(ctx, 1, ctx->iaoq_b, ctx->iaoq_n);
return EXIT_GOTO_TB;
}
}
/* Emit a conditional branch to a direct target. If the branch itself
is nullified, we should have already used nullify_over. */
static ExitStatus do_cbranch(DisasContext *ctx, target_long disp, bool is_n,
DisasCond *cond)
{
target_ulong dest = iaoq_dest(ctx, disp);
TCGLabel *taken = NULL;
TCGCond c = cond->c;
int which = 0;
bool n;
assert(ctx->null_cond.c == TCG_COND_NEVER);
/* Handle TRUE and NEVER as direct branches. */
if (c == TCG_COND_ALWAYS) {
return do_dbranch(ctx, dest, 0, is_n && disp >= 0);
}
if (c == TCG_COND_NEVER) {
return do_dbranch(ctx, ctx->iaoq_n, 0, is_n && disp < 0);
}
taken = gen_new_label();
cond_prep(cond);
tcg_gen_brcond_tl(c, cond->a0, cond->a1, taken);
cond_free(cond);
/* Not taken: Condition not satisfied; nullify on backward branches. */
n = is_n && disp < 0;
if (n && use_nullify_skip(ctx)) {
nullify_set(ctx, 0);
gen_goto_tb(ctx, which++, ctx->iaoq_n, ctx->iaoq_n + 4);
} else {
if (!n && ctx->null_lab) {
gen_set_label(ctx->null_lab);
ctx->null_lab = NULL;
}
nullify_set(ctx, n);
gen_goto_tb(ctx, which++, ctx->iaoq_b, ctx->iaoq_n);
}
gen_set_label(taken);
/* Taken: Condition satisfied; nullify on forward branches. */
n = is_n && disp >= 0;
if (n && use_nullify_skip(ctx)) {
nullify_set(ctx, 0);
gen_goto_tb(ctx, which++, dest, dest + 4);
} else {
nullify_set(ctx, n);
gen_goto_tb(ctx, which++, ctx->iaoq_b, dest);
}
/* Not taken: the branch itself was nullified. */
if (ctx->null_lab) {
gen_set_label(ctx->null_lab);
ctx->null_lab = NULL;
if (which < 2) {
nullify_set(ctx, 0);
gen_goto_tb(ctx, which, ctx->iaoq_b, ctx->iaoq_n);
return EXIT_GOTO_TB;
} else {
return EXIT_IAQ_N_STALE;
}
} else {
return EXIT_GOTO_TB;
}
}
/* Emit an unconditional branch to an indirect target. This handles
nullification of the branch itself. */
static ExitStatus do_ibranch(DisasContext *ctx, TCGv dest,
unsigned link, bool is_n)
{
TCGv a0, a1, next, tmp;
TCGCond c;
assert(ctx->null_lab == NULL);
if (ctx->null_cond.c == TCG_COND_NEVER) {
if (link != 0) {
copy_iaoq_entry(cpu_gr[link], ctx->iaoq_n, ctx->iaoq_n_var);
}
next = get_temp(ctx);
tcg_gen_mov_tl(next, dest);
ctx->iaoq_n = -1;
ctx->iaoq_n_var = next;
if (is_n) {
ctx->null_cond.c = TCG_COND_ALWAYS;
}
} else if (is_n && use_nullify_skip(ctx)) {
/* The (conditional) branch, B, nullifies the next insn, N,
and we're allowed to skip execution N (no single-step or
tracepoint in effect). Since the exit_tb that we must use
for the indirect branch consumes no special resources, we
can (conditionally) skip B and continue execution. */
/* The use_nullify_skip test implies we have a known control path. */
tcg_debug_assert(ctx->iaoq_b != -1);
tcg_debug_assert(ctx->iaoq_n != -1);
/* We do have to handle the non-local temporary, DEST, before
branching. Since IOAQ_F is not really live at this point, we
can simply store DEST optimistically. Similarly with IAOQ_B. */
tcg_gen_mov_tl(cpu_iaoq_f, dest);
tcg_gen_addi_tl(cpu_iaoq_b, dest, 4);
nullify_over(ctx);
if (link != 0) {
tcg_gen_movi_tl(cpu_gr[link], ctx->iaoq_n);
}
tcg_gen_exit_tb(0);
return nullify_end(ctx, NO_EXIT);
} else {
cond_prep(&ctx->null_cond);
c = ctx->null_cond.c;
a0 = ctx->null_cond.a0;
a1 = ctx->null_cond.a1;
tmp = tcg_temp_new();
next = get_temp(ctx);
copy_iaoq_entry(tmp, ctx->iaoq_n, ctx->iaoq_n_var);
tcg_gen_movcond_tl(c, next, a0, a1, tmp, dest);
ctx->iaoq_n = -1;
ctx->iaoq_n_var = next;
if (link != 0) {
tcg_gen_movcond_tl(c, cpu_gr[link], a0, a1, cpu_gr[link], tmp);
}
if (is_n) {
/* The branch nullifies the next insn, which means the state of N
after the branch is the inverse of the state of N that applied
to the branch. */
tcg_gen_setcond_tl(tcg_invert_cond(c), cpu_psw_n, a0, a1);
cond_free(&ctx->null_cond);
ctx->null_cond = cond_make_n();
ctx->psw_n_nonzero = true;
} else {
cond_free(&ctx->null_cond);
}
}
return NO_EXIT;
}
static ExitStatus trans_nop(DisasContext *ctx, uint32_t insn,
const DisasInsn *di)
{
@ -1310,6 +1536,224 @@ static ExitStatus trans_ldo(DisasContext *ctx, uint32_t insn)
return NO_EXIT;
}
static ExitStatus trans_cmpb(DisasContext *ctx, uint32_t insn,
bool is_true, bool is_imm, bool is_dw)
{
target_long disp = assemble_12(insn) * 4;
unsigned n = extract32(insn, 1, 1);
unsigned c = extract32(insn, 13, 3);
unsigned r = extract32(insn, 21, 5);
unsigned cf = c * 2 + !is_true;
TCGv dest, in1, in2, sv;
DisasCond cond;
nullify_over(ctx);
if (is_imm) {
in1 = load_const(ctx, low_sextract(insn, 16, 5));
} else {
in1 = load_gpr(ctx, extract32(insn, 16, 5));
}
in2 = load_gpr(ctx, r);
dest = get_temp(ctx);
tcg_gen_sub_tl(dest, in1, in2);
TCGV_UNUSED(sv);
if (c == 6) {
sv = do_sub_sv(ctx, dest, in1, in2);
}
cond = do_sub_cond(cf, dest, in1, in2, sv);
return do_cbranch(ctx, disp, n, &cond);
}
static ExitStatus trans_addb(DisasContext *ctx, uint32_t insn,
bool is_true, bool is_imm)
{
target_long disp = assemble_12(insn) * 4;
unsigned n = extract32(insn, 1, 1);
unsigned c = extract32(insn, 13, 3);
unsigned r = extract32(insn, 21, 5);
unsigned cf = c * 2 + !is_true;
TCGv dest, in1, in2, sv, cb_msb;
DisasCond cond;
nullify_over(ctx);
if (is_imm) {
in1 = load_const(ctx, low_sextract(insn, 16, 5));
} else {
in1 = load_gpr(ctx, extract32(insn, 16, 5));
}
in2 = load_gpr(ctx, r);
dest = dest_gpr(ctx, r);
TCGV_UNUSED(sv);
TCGV_UNUSED(cb_msb);
switch (c) {
default:
tcg_gen_add_tl(dest, in1, in2);
break;
case 4: case 5:
cb_msb = get_temp(ctx);
tcg_gen_movi_tl(cb_msb, 0);
tcg_gen_add2_tl(dest, cb_msb, in1, cb_msb, in2, cb_msb);
break;
case 6:
tcg_gen_add_tl(dest, in1, in2);
sv = do_add_sv(ctx, dest, in1, in2);
break;
}
cond = do_cond(cf, dest, cb_msb, sv);
return do_cbranch(ctx, disp, n, &cond);
}
static ExitStatus trans_bb(DisasContext *ctx, uint32_t insn)
{
target_long disp = assemble_12(insn) * 4;
unsigned n = extract32(insn, 1, 1);
unsigned c = extract32(insn, 15, 1);
unsigned r = extract32(insn, 16, 5);
unsigned p = extract32(insn, 21, 5);
unsigned i = extract32(insn, 26, 1);
TCGv tmp, tcg_r;
DisasCond cond;
nullify_over(ctx);
tmp = tcg_temp_new();
tcg_r = load_gpr(ctx, r);
if (i) {
tcg_gen_shli_tl(tmp, tcg_r, p);
} else {
tcg_gen_shl_tl(tmp, tcg_r, cpu_sar);
}
cond = cond_make_0(c ? TCG_COND_GE : TCG_COND_LT, tmp);
tcg_temp_free(tmp);
return do_cbranch(ctx, disp, n, &cond);
}
static ExitStatus trans_movb(DisasContext *ctx, uint32_t insn, bool is_imm)
{
target_long disp = assemble_12(insn) * 4;
unsigned n = extract32(insn, 1, 1);
unsigned c = extract32(insn, 13, 3);
unsigned t = extract32(insn, 16, 5);
unsigned r = extract32(insn, 21, 5);
TCGv dest;
DisasCond cond;
nullify_over(ctx);
dest = dest_gpr(ctx, r);
if (is_imm) {
tcg_gen_movi_tl(dest, low_sextract(t, 0, 5));
} else if (t == 0) {
tcg_gen_movi_tl(dest, 0);
} else {
tcg_gen_mov_tl(dest, cpu_gr[t]);
}
cond = do_sed_cond(c, dest);
return do_cbranch(ctx, disp, n, &cond);
}
static ExitStatus trans_be(DisasContext *ctx, uint32_t insn, bool is_l)
{
unsigned n = extract32(insn, 1, 1);
unsigned b = extract32(insn, 21, 5);
target_long disp = assemble_17(insn);
/* unsigned s = low_uextract(insn, 13, 3); */
/* ??? It seems like there should be a good way of using
"be disp(sr2, r0)", the canonical gateway entry mechanism
to our advantage. But that appears to be inconvenient to
manage along side branch delay slots. Therefore we handle
entry into the gateway page via absolute address. */
/* Since we don't implement spaces, just branch. Do notice the special
case of "be disp(*,r0)" using a direct branch to disp, so that we can
goto_tb to the TB containing the syscall. */
if (b == 0) {
return do_dbranch(ctx, disp, is_l ? 31 : 0, n);
} else {
TCGv tmp = get_temp(ctx);
tcg_gen_addi_tl(tmp, load_gpr(ctx, b), disp);
return do_ibranch(ctx, tmp, is_l ? 31 : 0, n);
}
}
static ExitStatus trans_bl(DisasContext *ctx, uint32_t insn,
const DisasInsn *di)
{
unsigned n = extract32(insn, 1, 1);
unsigned link = extract32(insn, 21, 5);
target_long disp = assemble_17(insn);
return do_dbranch(ctx, iaoq_dest(ctx, disp), link, n);
}
static ExitStatus trans_bl_long(DisasContext *ctx, uint32_t insn,
const DisasInsn *di)
{
unsigned n = extract32(insn, 1, 1);
target_long disp = assemble_22(insn);
return do_dbranch(ctx, iaoq_dest(ctx, disp), 2, n);
}
static ExitStatus trans_blr(DisasContext *ctx, uint32_t insn,
const DisasInsn *di)
{
unsigned n = extract32(insn, 1, 1);
unsigned rx = extract32(insn, 16, 5);
unsigned link = extract32(insn, 21, 5);
TCGv tmp = get_temp(ctx);
tcg_gen_shli_tl(tmp, load_gpr(ctx, rx), 3);
tcg_gen_addi_tl(tmp, tmp, ctx->iaoq_f + 8);
return do_ibranch(ctx, tmp, link, n);
}
static ExitStatus trans_bv(DisasContext *ctx, uint32_t insn,
const DisasInsn *di)
{
unsigned n = extract32(insn, 1, 1);
unsigned rx = extract32(insn, 16, 5);
unsigned rb = extract32(insn, 21, 5);
TCGv dest;
if (rx == 0) {
dest = load_gpr(ctx, rb);
} else {
dest = get_temp(ctx);
tcg_gen_shli_tl(dest, load_gpr(ctx, rx), 3);
tcg_gen_add_tl(dest, dest, load_gpr(ctx, rb));
}
return do_ibranch(ctx, dest, 0, n);
}
static ExitStatus trans_bve(DisasContext *ctx, uint32_t insn,
const DisasInsn *di)
{
unsigned n = extract32(insn, 1, 1);
unsigned rb = extract32(insn, 21, 5);
unsigned link = extract32(insn, 13, 1) ? 2 : 0;
return do_ibranch(ctx, load_gpr(ctx, rb), link, n);
}
static const DisasInsn table_branch[] = {
{ 0xe8000000u, 0xfc006000u, trans_bl }, /* B,L and B,L,PUSH */
{ 0xe800a000u, 0xfc00e000u, trans_bl_long },
{ 0xe8004000u, 0xfc00fffdu, trans_blr },
{ 0xe800c000u, 0xfc00fffdu, trans_bv },
{ 0xe800d000u, 0xfc00dffcu, trans_bve },
};
static ExitStatus translate_table_int(DisasContext *ctx, uint32_t insn,
const DisasInsn table[], size_t n)
{
@ -1338,13 +1782,46 @@ static ExitStatus translate_one(DisasContext *ctx, uint32_t insn)
return trans_addil(ctx, insn);
case 0x0D:
return trans_ldo(ctx, insn);
case 0x20:
return trans_cmpb(ctx, insn, true, false, false);
case 0x21:
return trans_cmpb(ctx, insn, true, true, false);
case 0x22:
return trans_cmpb(ctx, insn, false, false, false);
case 0x23:
return trans_cmpb(ctx, insn, false, true, false);
case 0x24:
return trans_cmpiclr(ctx, insn);
case 0x25:
return trans_subi(ctx, insn);
case 0x27:
return trans_cmpb(ctx, insn, true, false, true);
case 0x28:
return trans_addb(ctx, insn, true, false);
case 0x29:
return trans_addb(ctx, insn, true, true);
case 0x2A:
return trans_addb(ctx, insn, false, false);
case 0x2B:
return trans_addb(ctx, insn, false, true);
case 0x2C:
case 0x2D:
return trans_addi(ctx, insn);
case 0x2F:
return trans_cmpb(ctx, insn, false, false, true);
case 0x30:
case 0x31:
return trans_bb(ctx, insn);
case 0x32:
return trans_movb(ctx, insn, false);
case 0x33:
return trans_movb(ctx, insn, true);
case 0x38:
return trans_be(ctx, insn, false);
case 0x39:
return trans_be(ctx, insn, true);
case 0x3A:
return translate_table(ctx, insn, table_branch);
default:
break;
}