/*
 * RISC-V translation routines for the RV64 Zacas Standard Extension.
 *
 * Copyright (c) 2020-2023 PLCT Lab
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2 or later, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
 */

#define REQUIRE_ZACAS(ctx) do {          \
    if (!ctx->cfg_ptr->ext_zacas) {      \
        return false;                     \
    }                                     \
} while (0)

static bool trans_amocas_w(DisasContext *ctx, arg_amocas_w *a)
{
    REQUIRE_ZACAS(ctx);
    return gen_cmpxchg(ctx, a, MO_ALIGN | MO_TESL);
}

static TCGv_i64 get_gpr_pair(DisasContext *ctx, int reg_num)
{
    TCGv_i64 t;

    assert(get_ol(ctx) == MXL_RV32);

    if (reg_num == 0) {
        return tcg_constant_i64(0);
    }

    t = tcg_temp_new_i64();
    tcg_gen_concat_tl_i64(t, cpu_gpr[reg_num], cpu_gpr[reg_num + 1]);
    return t;
}

static void gen_set_gpr_pair(DisasContext *ctx, int reg_num, TCGv_i64 t)
{
    assert(get_ol(ctx) == MXL_RV32);

    if (reg_num != 0) {
#ifdef TARGET_RISCV32
        tcg_gen_extr_i64_i32(cpu_gpr[reg_num], cpu_gpr[reg_num + 1], t);
#else
        tcg_gen_ext32s_i64(cpu_gpr[reg_num], t);
        tcg_gen_sari_i64(cpu_gpr[reg_num + 1], t, 32);
#endif

        if (get_xl_max(ctx) == MXL_RV128) {
            tcg_gen_sari_tl(cpu_gprh[reg_num], cpu_gpr[reg_num], 63);
            tcg_gen_sari_tl(cpu_gprh[reg_num + 1], cpu_gpr[reg_num + 1], 63);
        }
    }
}

static bool gen_cmpxchg64(DisasContext *ctx, arg_atomic *a, MemOp mop)
{
    /*
     * Encodings with odd numbered registers specified in rs2 and rd are
     * reserved.
     */
    if ((a->rs2 | a->rd) & 1) {
        return false;
    }

    TCGv_i64 dest = get_gpr_pair(ctx, a->rd);
    TCGv src1 = get_address(ctx, a->rs1, 0);
    TCGv_i64 src2 = get_gpr_pair(ctx, a->rs2);

    decode_save_opc(ctx);
    tcg_gen_atomic_cmpxchg_i64(dest, src1, dest, src2, ctx->mem_idx, mop);

    gen_set_gpr_pair(ctx, a->rd, dest);
    return true;
}

static bool trans_amocas_d(DisasContext *ctx, arg_amocas_d *a)
{
    REQUIRE_ZACAS(ctx);
    switch (get_ol(ctx)) {
    case MXL_RV32:
        return gen_cmpxchg64(ctx, a, MO_ALIGN | MO_TEUQ);
    case MXL_RV64:
    case MXL_RV128:
        return gen_cmpxchg(ctx, a, MO_ALIGN | MO_TEUQ);
    default:
        g_assert_not_reached();
    }
}

static bool trans_amocas_q(DisasContext *ctx, arg_amocas_q *a)
{
    REQUIRE_ZACAS(ctx);
    REQUIRE_64BIT(ctx);

    /*
     * Encodings with odd numbered registers specified in rs2 and rd are
     * reserved.
     */
    if ((a->rs2 | a->rd) & 1) {
        return false;
    }

#ifdef TARGET_RISCV64
    TCGv_i128 dest = tcg_temp_new_i128();
    TCGv src1 = get_address(ctx, a->rs1, 0);
    TCGv_i128 src2 = tcg_temp_new_i128();
    TCGv_i64 src2l = get_gpr(ctx, a->rs2, EXT_NONE);
    TCGv_i64 src2h = get_gpr(ctx, a->rs2 == 0 ? 0 : a->rs2 + 1, EXT_NONE);
    TCGv_i64 destl = get_gpr(ctx, a->rd, EXT_NONE);
    TCGv_i64 desth = get_gpr(ctx, a->rd == 0 ? 0 : a->rd + 1, EXT_NONE);

    tcg_gen_concat_i64_i128(src2, src2l, src2h);
    tcg_gen_concat_i64_i128(dest, destl, desth);
    decode_save_opc(ctx);
    tcg_gen_atomic_cmpxchg_i128(dest, src1, dest, src2, ctx->mem_idx,
                                (MO_ALIGN | MO_TEUO));

    tcg_gen_extr_i128_i64(destl, desth, dest);

    if (a->rd != 0) {
        gen_set_gpr(ctx, a->rd, destl);
        gen_set_gpr(ctx, a->rd + 1, desth);
    }
#endif

    return true;
}