[37/52] target-m68k: add cas/cas2 ops
diff mbox

Message ID 1462396135-20925-5-git-send-email-laurent@vivier.eu
State New
Headers show

Commit Message

Laurent Vivier May 4, 2016, 9:08 p.m. UTC
Signed-off-by: Laurent Vivier <laurent@vivier.eu>
---
 linux-user/main.c       | 193 ++++++++++++++++++++++++++++++++++++++++++++++++
 target-m68k/cpu.h       |   9 +++
 target-m68k/qregs.def   |   5 ++
 target-m68k/translate.c | 175 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 382 insertions(+)

Comments

Richard Henderson May 6, 2016, 9:29 p.m. UTC | #1
On 05/04/2016 11:08 AM, Laurent Vivier wrote:
> Signed-off-by: Laurent Vivier <laurent@vivier.eu>
> ---
>  linux-user/main.c       | 193 ++++++++++++++++++++++++++++++++++++++++++++++++
>  target-m68k/cpu.h       |   9 +++
>  target-m68k/qregs.def   |   5 ++
>  target-m68k/translate.c | 175 +++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 382 insertions(+)
>
> diff --git a/linux-user/main.c b/linux-user/main.c
> index 74b02c7..3c51afe 100644
> --- a/linux-user/main.c
> +++ b/linux-user/main.c
> @@ -2994,6 +2994,194 @@ void cpu_loop(CPUMBState *env)
>
>  #ifdef TARGET_M68K
>
> +static int do_cas(CPUM68KState *env)
> +{
> +    int size, is_cas;
> +    int cmp1_reg, upd1_reg;
> +    int cmp2_reg, upd2_reg;
> +    uint32_t dest1, cmp1, addr1;
> +    uint32_t dest2, cmp2, addr2;
> +    int segv = 0;
> +    int z;
> +
> +    start_exclusive();
> +
> +    /* cas_param bits
> +     * 31    -> CAS(0) / CAS2(1)
> +     * 11:13 -> update reg 2
> +     * 8:10  -> cmp reg 2
> +     * 5:7   -> update reg 1
> +     * 2:4   -> cmp reg 1
> +     * 0:1   -> opsize
> +     */
> +
> +    is_cas = (env->cas_param & 0x80000000) == 0;
> +
> +    size = env->cas_param & 0x3;
> +
> +    cmp1_reg = (env->cas_param >> 2) & 7;
> +    upd1_reg = (env->cas_param >> 5) & 7;
> +    cmp2_reg = (env->cas_param >> 8) & 7;
> +    upd2_reg = (env->cas_param >> 11) & 7;
> +
> +    addr1 = env->cas_addr1;
> +    addr2 = env->cas_addr2;
> +
> +    switch (size) {
> +    case OS_BYTE:
> +        segv = get_user_u8(dest1, addr1);
> +        cmp1 = (uint8_t)env->dregs[cmp1_reg];
> +        break;
> +    case OS_WORD:
> +        segv = get_user_u16(dest1, addr1);
> +        cmp1 = (uint16_t)env->dregs[cmp1_reg];
> +        break;
> +    case OS_LONG:
> +    default:
> +        segv = get_user_u32(dest1, addr1);
> +        cmp1 = env->dregs[cmp1_reg];
> +        break;
> +    }
> +    if (segv) {
> +        env->mmu.ar = addr1;
> +        goto done;
> +    }
> +    env->cc_n = dest1;
> +    env->cc_v = cmp1;
> +    z = dest1 - cmp1;
> +    env->cc_op = CC_OP_CMPB + size;

Incorrect setting of CC_* before checking for all of the possible segv?

> +    dest = gen_load(s, opsize, addr, 0);
> +
> +    zero = tcg_const_i32(0);
> +    cmp = gen_extend(DREG(ext, 0), opsize, 0);
> +
> +    /* if  dest - cmp == 0 */
> +
> +    res = tcg_temp_new();
> +    tcg_gen_sub_i32(res, dest, cmp);
> +
> +    /* then dest = update1 */
> +
> +    tcg_gen_movcond_i32(TCG_COND_EQ, dest,
> +                        res, zero,
> +                        DREG(ext, 6), dest);
> +
> +    /* else cmp = dest */
> +
> +    tcg_gen_movcond_i32(TCG_COND_NE, cmp,
> +                        res, zero,
> +                        dest, cmp);

Note that you don't need movcond for cmp here.  If the condition is false, we 
know that cmp == dest anyway.  So just a plain store into cmp.

You also don't need to compute RES, since you can perform the first movcond 
based on dest and cmp directly.  Although this does require an extra temp:

   load = gen_load(...)
   cmp = gen_extend(...)
   store = tcg_temp_new();

   tcg_gen_movcond_i32(TCG_COND_EQ, store, load, cmp, DREG(ext, 6), load);
   tcg_gen_mov_i32(cmp, load);

   gen_partset_reg(..., cmp);
   gen_store(..., store);

> +    gen_logic_cc(s, res, opsize);

gen_logic_cc is wrong, afaics, and clobbers the proper CMPB + opsize that you 
set up earlier.

> +    TCGv cmp1, cmp2;
> +    TCGv dest1, dest2;
> +    TCGv res1, res2;
> +    TCGv zero;
> +    zero = tcg_const_i32(0);
> +    dest1 = gen_load(s, opsize, addr1, 0);
> +    cmp1 = gen_extend(DREG(ext1, 0), opsize, 0);
> +
> +    res1 = tcg_temp_new();
> +    tcg_gen_sub_i32(res1, dest1, cmp1);
> +    dest2 = gen_load(s, opsize, addr2, 0);
> +    cmp2 = gen_extend(DREG(ext2, 0), opsize, 0);
> +
> +    res2 = tcg_temp_new();
> +    tcg_gen_sub_i32(res2, dest2, cmp2);
> +
> +    /* if dest1 - cmp1 == 0 and dest2 - cmp2 == 0 */
> +
> +    tcg_gen_movcond_i32(TCG_COND_EQ, res1,
> +                        res1, zero,
> +                        res2, res1);

   z = (cmp1 - load1) | (cmp2 - load2)

would be a better computation here than sub+sub+movcond.

Of course, we also need to correctly compute N, C and V, so...

   N = (cmp1 == load1 ? cmp2 : cmp1);
   V = (cmp1 == load1 ? load2 : load1);
   cc_op = CC_OP_CMP;

   store1 = (N == V ? update1 : load1);
   store2 = (N == V ? update2 : load2);

   cmp1 = load1;
   cmp2 = load2;

Oh, and we need to think about delaying all flag and register updates until 
after the stores, for both CAS and CAS2.  Otherwise we don't get the right 
results at the exception handler for a write-protected page.


r~

Patch
diff mbox

diff --git a/linux-user/main.c b/linux-user/main.c
index 74b02c7..3c51afe 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -2994,6 +2994,194 @@  void cpu_loop(CPUMBState *env)
 
 #ifdef TARGET_M68K
 
+static int do_cas(CPUM68KState *env)
+{
+    int size, is_cas;
+    int cmp1_reg, upd1_reg;
+    int cmp2_reg, upd2_reg;
+    uint32_t dest1, cmp1, addr1;
+    uint32_t dest2, cmp2, addr2;
+    int segv = 0;
+    int z;
+
+    start_exclusive();
+
+    /* cas_param bits
+     * 31    -> CAS(0) / CAS2(1)
+     * 11:13 -> update reg 2
+     * 8:10  -> cmp reg 2
+     * 5:7   -> update reg 1
+     * 2:4   -> cmp reg 1
+     * 0:1   -> opsize
+     */
+
+    is_cas = (env->cas_param & 0x80000000) == 0;
+
+    size = env->cas_param & 0x3;
+
+    cmp1_reg = (env->cas_param >> 2) & 7;
+    upd1_reg = (env->cas_param >> 5) & 7;
+    cmp2_reg = (env->cas_param >> 8) & 7;
+    upd2_reg = (env->cas_param >> 11) & 7;
+
+    addr1 = env->cas_addr1;
+    addr2 = env->cas_addr2;
+
+    switch (size) {
+    case OS_BYTE:
+        segv = get_user_u8(dest1, addr1);
+        cmp1 = (uint8_t)env->dregs[cmp1_reg];
+        break;
+    case OS_WORD:
+        segv = get_user_u16(dest1, addr1);
+        cmp1 = (uint16_t)env->dregs[cmp1_reg];
+        break;
+    case OS_LONG:
+    default:
+        segv = get_user_u32(dest1, addr1);
+        cmp1 = env->dregs[cmp1_reg];
+        break;
+    }
+    if (segv) {
+        env->mmu.ar = addr1;
+        goto done;
+    }
+    env->cc_n = dest1;
+    env->cc_v = cmp1;
+    z = dest1 - cmp1;
+    env->cc_op = CC_OP_CMPB + size;
+
+    if (is_cas) {
+        /* CAS */
+
+        /* if (addr1) == cmp1 then (addr1) = upd1 */
+
+        if (z == 0) {
+            switch (size) {
+            case OS_BYTE:
+                segv = put_user_u8(env->dregs[upd1_reg], addr1);
+                break;
+            case OS_WORD:
+                segv = put_user_u16(env->dregs[upd1_reg], addr1);
+                break;
+            case OS_LONG:
+                segv = put_user_u32(env->dregs[upd1_reg], addr1);
+                break;
+            default:
+                break;
+            }
+            if (segv) {
+                env->mmu.ar = addr1;
+            }
+            goto done;
+        }
+        /* else cmp1 = (addr1) */
+        switch (size) {
+        case OS_BYTE:
+            env->dregs[cmp1_reg] = deposit32(env->dregs[cmp1_reg],
+                                            0, 8, dest1);
+            break;
+        case OS_WORD:
+            env->dregs[cmp1_reg] = deposit32(env->dregs[cmp1_reg],
+                                            0, 16, dest1);
+            break;
+        case OS_LONG:
+            env->dregs[cmp1_reg] = dest1;
+            break;
+        default:
+            break;
+        }
+    } else {
+        /* CAS2 */
+        switch (size) {
+        case OS_BYTE:
+            segv = get_user_u8(dest2, addr2);
+            cmp2 = (uint8_t)env->dregs[cmp2_reg];
+            break;
+        case OS_WORD:
+            segv = get_user_u16(dest2, addr2);
+            cmp2 = (uint16_t)env->dregs[cmp2_reg];
+            break;
+        case OS_LONG:
+        default:
+            segv = get_user_u32(dest2, addr2);
+            cmp2 = env->dregs[cmp2_reg];
+            break;
+        }
+        if (segv) {
+            env->mmu.ar = addr2;
+            goto done;
+        }
+        /* if (addr1) == cmp1 && (addr2) == cmp2 then
+         *    (addr1) = upd1, (addr2) = udp2
+         */
+        if (z == 0) {
+            z = dest2 - cmp2;
+        }
+        if (z == 0) {
+            switch (size) {
+            case OS_BYTE:
+                segv = put_user_u8(env->dregs[upd1_reg], addr1);
+                break;
+            case OS_WORD:
+                segv = put_user_u16(env->dregs[upd1_reg], addr1);
+                break;
+            case OS_LONG:
+                segv = put_user_u32(env->dregs[upd1_reg], addr1);
+                break;
+            default:
+                break;
+            }
+            if (segv) {
+                env->mmu.ar = addr1;
+            }
+            switch (size) {
+            case OS_BYTE:
+                segv = put_user_u8(env->dregs[upd2_reg], addr2);
+                break;
+            case OS_WORD:
+                segv = put_user_u16(env->dregs[upd2_reg], addr2);
+                break;
+            case OS_LONG:
+                segv = put_user_u32(env->dregs[upd2_reg], addr2);
+                break;
+            default:
+                break;
+            }
+            if (segv) {
+                env->mmu.ar = addr2;
+            }
+            goto done;
+        }
+        /* else cmp1 = (addr1), cmp2 = (addr2) */
+        switch (size) {
+        case OS_BYTE:
+            env->dregs[cmp1_reg] = deposit32(env->dregs[cmp1_reg],
+                                            0, 8, dest1);
+            env->dregs[cmp2_reg] = deposit32(env->dregs[cmp2_reg],
+                                            0, 8, dest2);
+            break;
+        case OS_WORD:
+            env->dregs[cmp1_reg] = deposit32(env->dregs[cmp1_reg],
+                                            0, 16, dest1);
+            env->dregs[cmp2_reg] = deposit32(env->dregs[cmp2_reg],
+                                            0, 16, dest2);
+            break;
+        case OS_LONG:
+            env->dregs[cmp1_reg] = dest1;
+            env->dregs[cmp2_reg] = dest2;
+            break;
+        default:
+            break;
+        }
+    }
+
+done:
+    end_exclusive();
+
+    return segv;
+}
+
 void cpu_loop(CPUM68KState *env)
 {
     CPUState *cs = CPU(m68k_env_get_cpu(env));
@@ -3060,6 +3248,11 @@  void cpu_loop(CPUM68KState *env)
         case EXCP_INTERRUPT:
             /* just indicate that signals should be handled asap */
             break;
+        case EXCP_CAS:
+            if (!do_cas(env)) {
+                break;
+            }
+            /* fall through for segv */
         case EXCP_ACCESS:
             {
                 info.si_signo = TARGET_SIGSEGV;
diff --git a/target-m68k/cpu.h b/target-m68k/cpu.h
index b300a92..5ce77e4 100644
--- a/target-m68k/cpu.h
+++ b/target-m68k/cpu.h
@@ -58,6 +58,7 @@ 
 
 #define EXCP_RTE            0x100
 #define EXCP_HALT_INSN      0x101
+#define EXCP_CAS            0x102
 
 #define NB_MMU_MODES 2
 #define TARGET_INSN_START_EXTRA_WORDS 1
@@ -110,6 +111,14 @@  typedef struct CPUM68KState {
 
     uint32_t qregs[MAX_QREGS];
 
+#ifdef CONFIG_USER_ONLY
+    /* CAS/CAS2 parameters */
+
+    uint32_t cas_param;
+    uint32_t cas_addr1;
+    uint32_t cas_addr2;
+#endif
+
     CPU_COMMON
 
     /* Fields from here on are preserved across CPU reset. */
diff --git a/target-m68k/qregs.def b/target-m68k/qregs.def
index 51ff43b..c4581b1 100644
--- a/target-m68k/qregs.def
+++ b/target-m68k/qregs.def
@@ -9,3 +9,8 @@  DEFO32(CC_V, cc_v)
 DEFO32(CC_Z, cc_z)
 DEFO32(MACSR, macsr)
 DEFO32(MAC_MASK, mac_mask)
+#ifdef CONFIG_USER_ONLY
+DEFO32(CAS_PARAM, cas_param)
+DEFO32(CAS_ADDR1, cas_addr1)
+DEFO32(CAS_ADDR2, cas_addr2)
+#endif
diff --git a/target-m68k/translate.c b/target-m68k/translate.c
index d48ab66..80033fc 100644
--- a/target-m68k/translate.c
+++ b/target-m68k/translate.c
@@ -1846,6 +1846,179 @@  DISAS_INSN(arith_im)
     }
 }
 
+DISAS_INSN(cas)
+{
+    int opsize;
+    TCGv addr;
+    uint16_t ext;
+
+    switch ((insn >> 9) & 3) {
+    case 1:
+        opsize = OS_BYTE;
+        break;
+    case 2:
+        opsize = OS_WORD;
+        break;
+    case 3:
+        opsize = OS_LONG;
+        break;
+    default:
+        abort();
+    }
+
+    ext = read_im16(env, s);
+
+    addr = gen_lea(env, s, insn, opsize);
+    if (IS_NULL_QREG(addr)) {
+        gen_addr_fault(s);
+        return;
+    }
+
+#ifdef CONFIG_USER_ONLY
+    tcg_gen_mov_i32(QREG_CAS_ADDR1, addr);
+    tcg_gen_movi_i32(QREG_CAS_PARAM,
+                     (REG(ext, 6) << 5) | (REG(ext, 0) << 2) |
+                     opsize);
+    gen_exception(s, s->pc, EXCP_CAS);
+    s->is_jmp = DISAS_JUMP;
+#else
+    TCGv dest;
+    TCGv res;
+    TCGv cmp;
+    TCGv zero;
+
+    dest = gen_load(s, opsize, addr, 0);
+
+    zero = tcg_const_i32(0);
+    cmp = gen_extend(DREG(ext, 0), opsize, 0);
+
+    /* if  dest - cmp == 0 */
+
+    res = tcg_temp_new();
+    tcg_gen_sub_i32(res, dest, cmp);
+
+    /* then dest = update1 */
+
+    tcg_gen_movcond_i32(TCG_COND_EQ, dest,
+                        res, zero,
+                        DREG(ext, 6), dest);
+
+    /* else cmp = dest */
+
+    tcg_gen_movcond_i32(TCG_COND_NE, cmp,
+                        res, zero,
+                        dest, cmp);
+
+    gen_partset_reg(opsize, DREG(ext, 0), cmp);
+    gen_store(s, opsize, addr, dest);
+    gen_logic_cc(s, res, opsize);
+
+    tcg_temp_free_i32(res);
+    tcg_temp_free_i32(zero);
+#endif
+}
+
+DISAS_INSN(cas2)
+{
+    int opsize;
+    uint16_t ext1, ext2;
+    TCGv addr1, addr2;
+
+    switch ((insn >> 9) & 3) {
+    case 1:
+        opsize = OS_BYTE;
+        break;
+    case 2:
+        opsize = OS_WORD;
+        break;
+    case 3:
+        opsize = OS_LONG;
+        break;
+    default:
+        abort();
+    }
+
+    ext1 = read_im16(env, s);
+
+    if (ext1 & 0x8000) {
+        /* Address Register */
+        addr1 = AREG(ext1, 12);
+    } else {
+        /* Data Register */
+        addr1 = DREG(ext1, 12);
+    }
+
+    ext2 = read_im16(env, s);
+    if (ext2 & 0x8000) {
+        /* Address Register */
+        addr2 = AREG(ext2, 12);
+    } else {
+        /* Data Register */
+        addr2 = DREG(ext2, 12);
+    }
+#ifdef CONFIG_USER_ONLY
+    tcg_gen_mov_i32(QREG_CAS_ADDR1, addr1);
+    tcg_gen_mov_i32(QREG_CAS_ADDR2, addr2);
+    tcg_gen_movi_i32(QREG_CAS_PARAM,
+                     (REG(ext2, 6) << 11) | (REG(ext2, 0) << 8) |
+                     (REG(ext1, 6) << 5) | (REG(ext1, 0) << 2) |
+                     0x80000000 | opsize);
+    gen_exception(s, s->pc, EXCP_CAS);
+    s->is_jmp = DISAS_JUMP;
+#else
+    TCGv cmp1, cmp2;
+    TCGv dest1, dest2;
+    TCGv res1, res2;
+    TCGv zero;
+    zero = tcg_const_i32(0);
+    dest1 = gen_load(s, opsize, addr1, 0);
+    cmp1 = gen_extend(DREG(ext1, 0), opsize, 0);
+
+    res1 = tcg_temp_new();
+    tcg_gen_sub_i32(res1, dest1, cmp1);
+    dest2 = gen_load(s, opsize, addr2, 0);
+    cmp2 = gen_extend(DREG(ext2, 0), opsize, 0);
+
+    res2 = tcg_temp_new();
+    tcg_gen_sub_i32(res2, dest2, cmp2);
+
+    /* if dest1 - cmp1 == 0 and dest2 - cmp2 == 0 */
+
+    tcg_gen_movcond_i32(TCG_COND_EQ, res1,
+                        res1, zero,
+                        res2, res1);
+
+    /* then dest1 = update1, dest2 = update2 */
+
+    tcg_gen_movcond_i32(TCG_COND_EQ, dest1,
+                        res1, zero,
+                        DREG(ext1, 6), dest1);
+    tcg_gen_movcond_i32(TCG_COND_EQ, dest2,
+                        res1, zero,
+                        DREG(ext2, 6), dest2);
+
+    /* else cmp1 = dest1, cmp2 = dest2 */
+
+    tcg_gen_movcond_i32(TCG_COND_NE, cmp1,
+                        res1, zero,
+                        dest1, cmp1);
+    tcg_gen_movcond_i32(TCG_COND_NE, cmp2,
+                        res1, zero,
+                        dest2, cmp2);
+
+    gen_partset_reg(opsize, DREG(ext1, 0), cmp1);
+    gen_partset_reg(opsize, DREG(ext2, 0), cmp2);
+    gen_store(s, opsize, addr1, dest1);
+    gen_store(s, opsize, addr2, dest2);
+
+    gen_logic_cc(s, res1, opsize);
+
+    tcg_temp_free_i32(res2);
+    tcg_temp_free_i32(res1);
+    tcg_temp_free_i32(zero);
+#endif
+}
+
 DISAS_INSN(byterev)
 {
     TCGv reg;
@@ -4457,6 +4630,8 @@  void register_m68k_insns (CPUM68KState *env)
     INSN(arith_im,  0680, fff8, CF_ISA_A);
     INSN(arith_im,  0c00, ff38, CF_ISA_A);
     INSN(arith_im,  0c00, ff00, M68000);
+    INSN(cas,       08c0, f9c0, CAS);
+    INSN(cas2,      08fc, f9ff, CAS);
     BASE(bitop_im,  0800, ffc0);
     BASE(bitop_im,  0840, ffc0);
     BASE(bitop_im,  0880, ffc0);