from __future__ import annotations
import logging

import claripy
from archinfo.arch_arm import is_arm_arch

from angr import errors
from angr.errors import SimError, SimCCallError
from angr.sim_options import USE_SIMPLIFIED_CCALLS
from angr.state_plugins.sim_action_object import _raw_ast, SimActionObject

l = logging.getLogger(name=__name__)

# pylint: disable=R0911
# pylint: disable=W0613
# pylint: disable=W0612
# pylint: disable=invalid-unary-operand-type

###############
### Helpers ###
###############


# There might be a better way of doing this
def calc_paritybit(p, msb=7, lsb=0):
    p_part = p[msb:lsb] if len(p) > msb else p

    b = claripy.BVV(1, 1)
    for i in range(p_part.size()):
        b = b ^ p_part[i]
    return b


def _cond_flag(condition):
    return claripy.If(condition, claripy.BVV(1, 1), claripy.BVV(0, 1))


def calc_zerobit(p):
    return _cond_flag(p == 0)


def boolean_extend(O, a, b, size):
    return claripy.If(O(a, b), claripy.BVV(1, size), claripy.BVV(0, size))


def op_concretize(op):
    if type(op) is int:
        return op
    op_e = claripy.excavate_ite(op)
    if op_e.op == "If":
        cases = list(claripy.reverse_ite_cases(op_e))
        if all(c.op == "BVV" for _, c in cases):
            raise CCallMultivaluedException(cases, op)
    if op.op != "BVV":
        raise SimError(
            f"Hit a symbolic conditional operation (need If or BVV, got {op.op}). " + "Something has gone wildly wrong."
        )
    return op.args[0]


def strip_simaction(val):
    if isinstance(val, SimActionObject):
        return _raw_ast(val)
    return val


class CCallMultivaluedException(Exception):
    pass


##################
### x86* data ###
##################

data: dict[str, dict[str, dict[str, int | None]]] = {
    "AMD64": {
        "CondTypes": {},
        "CondBitOffsets": {},
        "CondBitMasks": {},
        "OpTypes": {},
    },
    "X86": {
        "CondTypes": {},
        "CondBitOffsets": {},
        "CondBitMasks": {},
        "OpTypes": {},
    },
}

# condition types
data["AMD64"]["CondTypes"]["CondO"] = 0  # /* overflow           */
data["AMD64"]["CondTypes"]["CondNO"] = 1  # /* no overflow        */
data["AMD64"]["CondTypes"]["CondB"] = 2  # /* below              */
data["AMD64"]["CondTypes"]["CondNB"] = 3  # /* not below          */
data["AMD64"]["CondTypes"]["CondZ"] = 4  # /* zero               */
data["AMD64"]["CondTypes"]["CondNZ"] = 5  # /* not zero           */
data["AMD64"]["CondTypes"]["CondBE"] = 6  # /* below or equal     */
data["AMD64"]["CondTypes"]["CondNBE"] = 7  # /* not below or equal */
data["AMD64"]["CondTypes"]["CondS"] = 8  # /* negative           */
data["AMD64"]["CondTypes"]["CondNS"] = 9  # /* not negative       */
data["AMD64"]["CondTypes"]["CondP"] = 10  # /* parity even        */
data["AMD64"]["CondTypes"]["CondNP"] = 11  # /* not parity even    */
data["AMD64"]["CondTypes"]["CondL"] = 12  # /* jump less          */
data["AMD64"]["CondTypes"]["CondNL"] = 13  # /* not less           */
data["AMD64"]["CondTypes"]["CondLE"] = 14  # /* less or equal      */
data["AMD64"]["CondTypes"]["CondNLE"] = 15  # /* not less or equal  */

# condition bit offsets
data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_O"] = 11
data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_S"] = 7
data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_Z"] = 6
data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_A"] = 4
data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_C"] = 0
data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_P"] = 2

# masks
data["AMD64"]["CondBitMasks"]["G_CC_MASK_O"] = 1 << data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_O"]
data["AMD64"]["CondBitMasks"]["G_CC_MASK_S"] = 1 << data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_S"]
data["AMD64"]["CondBitMasks"]["G_CC_MASK_Z"] = 1 << data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_Z"]
data["AMD64"]["CondBitMasks"]["G_CC_MASK_A"] = 1 << data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_A"]
data["AMD64"]["CondBitMasks"]["G_CC_MASK_C"] = 1 << data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_C"]
data["AMD64"]["CondBitMasks"]["G_CC_MASK_P"] = 1 << data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_P"]

# operation types
data["AMD64"]["OpTypes"]["G_CC_OP_COPY"] = 0
data["AMD64"]["OpTypes"]["G_CC_OP_ADDB"] = 1
data["AMD64"]["OpTypes"]["G_CC_OP_ADDW"] = 2
data["AMD64"]["OpTypes"]["G_CC_OP_ADDL"] = 3
data["AMD64"]["OpTypes"]["G_CC_OP_ADDQ"] = 4
data["AMD64"]["OpTypes"]["G_CC_OP_SUBB"] = 5
data["AMD64"]["OpTypes"]["G_CC_OP_SUBW"] = 6
data["AMD64"]["OpTypes"]["G_CC_OP_SUBL"] = 7
data["AMD64"]["OpTypes"]["G_CC_OP_SUBQ"] = 8
data["AMD64"]["OpTypes"]["G_CC_OP_ADCB"] = 9
data["AMD64"]["OpTypes"]["G_CC_OP_ADCW"] = 10
data["AMD64"]["OpTypes"]["G_CC_OP_ADCL"] = 11
data["AMD64"]["OpTypes"]["G_CC_OP_ADCQ"] = 12
data["AMD64"]["OpTypes"]["G_CC_OP_SBBB"] = 13
data["AMD64"]["OpTypes"]["G_CC_OP_SBBW"] = 14
data["AMD64"]["OpTypes"]["G_CC_OP_SBBL"] = 15
data["AMD64"]["OpTypes"]["G_CC_OP_SBBQ"] = 16
data["AMD64"]["OpTypes"]["G_CC_OP_LOGICB"] = 17
data["AMD64"]["OpTypes"]["G_CC_OP_LOGICW"] = 18
data["AMD64"]["OpTypes"]["G_CC_OP_LOGICL"] = 19
data["AMD64"]["OpTypes"]["G_CC_OP_LOGICQ"] = 20
data["AMD64"]["OpTypes"]["G_CC_OP_INCB"] = 21
data["AMD64"]["OpTypes"]["G_CC_OP_INCW"] = 22
data["AMD64"]["OpTypes"]["G_CC_OP_INCL"] = 23
data["AMD64"]["OpTypes"]["G_CC_OP_INCQ"] = 24
data["AMD64"]["OpTypes"]["G_CC_OP_DECB"] = 25
data["AMD64"]["OpTypes"]["G_CC_OP_DECW"] = 26
data["AMD64"]["OpTypes"]["G_CC_OP_DECL"] = 27
data["AMD64"]["OpTypes"]["G_CC_OP_DECQ"] = 28
data["AMD64"]["OpTypes"]["G_CC_OP_SHLB"] = 29
data["AMD64"]["OpTypes"]["G_CC_OP_SHLW"] = 30
data["AMD64"]["OpTypes"]["G_CC_OP_SHLL"] = 31
data["AMD64"]["OpTypes"]["G_CC_OP_SHLQ"] = 32
data["AMD64"]["OpTypes"]["G_CC_OP_SHRB"] = 33
data["AMD64"]["OpTypes"]["G_CC_OP_SHRW"] = 34
data["AMD64"]["OpTypes"]["G_CC_OP_SHRL"] = 35
data["AMD64"]["OpTypes"]["G_CC_OP_SHRQ"] = 36
data["AMD64"]["OpTypes"]["G_CC_OP_ROLB"] = 37
data["AMD64"]["OpTypes"]["G_CC_OP_ROLW"] = 38
data["AMD64"]["OpTypes"]["G_CC_OP_ROLL"] = 39
data["AMD64"]["OpTypes"]["G_CC_OP_ROLQ"] = 40
data["AMD64"]["OpTypes"]["G_CC_OP_RORB"] = 41
data["AMD64"]["OpTypes"]["G_CC_OP_RORW"] = 42
data["AMD64"]["OpTypes"]["G_CC_OP_RORL"] = 43
data["AMD64"]["OpTypes"]["G_CC_OP_RORQ"] = 44
data["AMD64"]["OpTypes"]["G_CC_OP_UMULB"] = 45
data["AMD64"]["OpTypes"]["G_CC_OP_UMULW"] = 46
data["AMD64"]["OpTypes"]["G_CC_OP_UMULL"] = 47
data["AMD64"]["OpTypes"]["G_CC_OP_UMULQ"] = 48
data["AMD64"]["OpTypes"]["G_CC_OP_SMULB"] = 49
data["AMD64"]["OpTypes"]["G_CC_OP_SMULW"] = 50
data["AMD64"]["OpTypes"]["G_CC_OP_SMULL"] = 51
data["AMD64"]["OpTypes"]["G_CC_OP_SMULQ"] = 52
data["AMD64"]["OpTypes"]["G_CC_OP_ANDN32"] = 53
data["AMD64"]["OpTypes"]["G_CC_OP_ANDN64"] = 54
data["AMD64"]["OpTypes"]["G_CC_OP_BLSI32"] = 55
data["AMD64"]["OpTypes"]["G_CC_OP_BLSI64"] = 56
data["AMD64"]["OpTypes"]["G_CC_OP_BLSMSK32"] = 57
data["AMD64"]["OpTypes"]["G_CC_OP_BLSMSK64"] = 58
data["AMD64"]["OpTypes"]["G_CC_OP_BLSR32"] = 59
data["AMD64"]["OpTypes"]["G_CC_OP_BLSR64"] = 60
data["AMD64"]["OpTypes"]["G_CC_OP_ADCXL"] = 61
data["AMD64"]["OpTypes"]["G_CC_OP_ADCXQ"] = 62
data["AMD64"]["OpTypes"]["G_CC_OP_ADOXL"] = 63
data["AMD64"]["OpTypes"]["G_CC_OP_ADOXQ"] = 64
data["AMD64"]["OpTypes"]["G_CC_OP_NUMBER"] = 65

data["X86"]["CondTypes"]["CondO"] = 0
data["X86"]["CondTypes"]["CondNO"] = 1
data["X86"]["CondTypes"]["CondB"] = 2
data["X86"]["CondTypes"]["CondNB"] = 3
data["X86"]["CondTypes"]["CondZ"] = 4
data["X86"]["CondTypes"]["CondNZ"] = 5
data["X86"]["CondTypes"]["CondBE"] = 6
data["X86"]["CondTypes"]["CondNBE"] = 7
data["X86"]["CondTypes"]["CondS"] = 8
data["X86"]["CondTypes"]["CondNS"] = 9
data["X86"]["CondTypes"]["CondP"] = 10
data["X86"]["CondTypes"]["CondNP"] = 11
data["X86"]["CondTypes"]["CondL"] = 12
data["X86"]["CondTypes"]["CondNL"] = 13
data["X86"]["CondTypes"]["CondLE"] = 14
data["X86"]["CondTypes"]["CondNLE"] = 15
data["X86"]["CondTypes"]["CondAlways"] = 16

data["X86"]["CondBitOffsets"]["G_CC_SHIFT_O"] = 11
data["X86"]["CondBitOffsets"]["G_CC_SHIFT_S"] = 7
data["X86"]["CondBitOffsets"]["G_CC_SHIFT_Z"] = 6
data["X86"]["CondBitOffsets"]["G_CC_SHIFT_A"] = 4
data["X86"]["CondBitOffsets"]["G_CC_SHIFT_C"] = 0
data["X86"]["CondBitOffsets"]["G_CC_SHIFT_P"] = 2

# masks
data["X86"]["CondBitMasks"]["G_CC_MASK_O"] = 1 << data["X86"]["CondBitOffsets"]["G_CC_SHIFT_O"]
data["X86"]["CondBitMasks"]["G_CC_MASK_S"] = 1 << data["X86"]["CondBitOffsets"]["G_CC_SHIFT_S"]
data["X86"]["CondBitMasks"]["G_CC_MASK_Z"] = 1 << data["X86"]["CondBitOffsets"]["G_CC_SHIFT_Z"]
data["X86"]["CondBitMasks"]["G_CC_MASK_A"] = 1 << data["X86"]["CondBitOffsets"]["G_CC_SHIFT_A"]
data["X86"]["CondBitMasks"]["G_CC_MASK_C"] = 1 << data["X86"]["CondBitOffsets"]["G_CC_SHIFT_C"]
data["X86"]["CondBitMasks"]["G_CC_MASK_P"] = 1 << data["X86"]["CondBitOffsets"]["G_CC_SHIFT_P"]

data["X86"]["OpTypes"]["G_CC_OP_COPY"] = 0
data["X86"]["OpTypes"]["G_CC_OP_ADDB"] = 1
data["X86"]["OpTypes"]["G_CC_OP_ADDW"] = 2
data["X86"]["OpTypes"]["G_CC_OP_ADDL"] = 3
data["X86"]["OpTypes"]["G_CC_OP_SUBB"] = 4
data["X86"]["OpTypes"]["G_CC_OP_SUBW"] = 5
data["X86"]["OpTypes"]["G_CC_OP_SUBL"] = 6
data["X86"]["OpTypes"]["G_CC_OP_ADCB"] = 7
data["X86"]["OpTypes"]["G_CC_OP_ADCW"] = 8
data["X86"]["OpTypes"]["G_CC_OP_ADCL"] = 9
data["X86"]["OpTypes"]["G_CC_OP_SBBB"] = 10
data["X86"]["OpTypes"]["G_CC_OP_SBBW"] = 11
data["X86"]["OpTypes"]["G_CC_OP_SBBL"] = 12
data["X86"]["OpTypes"]["G_CC_OP_LOGICB"] = 13
data["X86"]["OpTypes"]["G_CC_OP_LOGICW"] = 14
data["X86"]["OpTypes"]["G_CC_OP_LOGICL"] = 15
data["X86"]["OpTypes"]["G_CC_OP_INCB"] = 16
data["X86"]["OpTypes"]["G_CC_OP_INCW"] = 17
data["X86"]["OpTypes"]["G_CC_OP_INCL"] = 18
data["X86"]["OpTypes"]["G_CC_OP_DECB"] = 19
data["X86"]["OpTypes"]["G_CC_OP_DECW"] = 20
data["X86"]["OpTypes"]["G_CC_OP_DECL"] = 21
data["X86"]["OpTypes"]["G_CC_OP_SHLB"] = 22
data["X86"]["OpTypes"]["G_CC_OP_SHLW"] = 23
data["X86"]["OpTypes"]["G_CC_OP_SHLL"] = 24
data["X86"]["OpTypes"]["G_CC_OP_SHRB"] = 25
data["X86"]["OpTypes"]["G_CC_OP_SHRW"] = 26
data["X86"]["OpTypes"]["G_CC_OP_SHRL"] = 27
data["X86"]["OpTypes"]["G_CC_OP_ROLB"] = 28
data["X86"]["OpTypes"]["G_CC_OP_ROLW"] = 29
data["X86"]["OpTypes"]["G_CC_OP_ROLL"] = 30
data["X86"]["OpTypes"]["G_CC_OP_RORB"] = 31
data["X86"]["OpTypes"]["G_CC_OP_RORW"] = 32
data["X86"]["OpTypes"]["G_CC_OP_RORL"] = 33
data["X86"]["OpTypes"]["G_CC_OP_UMULB"] = 34
data["X86"]["OpTypes"]["G_CC_OP_UMULW"] = 35
data["X86"]["OpTypes"]["G_CC_OP_UMULL"] = 36
data["X86"]["OpTypes"]["G_CC_OP_SMULB"] = 37
data["X86"]["OpTypes"]["G_CC_OP_SMULW"] = 38
data["X86"]["OpTypes"]["G_CC_OP_SMULL"] = 39
data["X86"]["OpTypes"]["G_CC_OP_NUMBER"] = 40

data["X86"]["OpTypes"]["G_CC_OP_SMULQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_UMULQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_RORQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_ROLQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_SHRQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_SHLQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_DECQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_INCQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_LOGICQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_SBBQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_ADCQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_SUBQ"] = None
data["X86"]["OpTypes"]["G_CC_OP_ADDQ"] = None

data_inverted = {
    k_arch: {k_data_class: {y: x for (x, y) in d_data_class.items()} for k_data_class, d_data_class in d_arch.items()}
    for k_arch, d_arch in data.items()
}

data["AMD64"]["size"] = 64
data["X86"]["size"] = 32


#
# AMD64 internal helpers
#
def pc_preamble(nbits):
    data_mask = claripy.BVV(2**nbits - 1, nbits)
    sign_mask = 1 << (nbits - 1)
    return data_mask, sign_mask


def pc_make_rdata(nbits, cf, pf, af, zf, sf, of, platform=None):
    return cf, pf, af, zf, sf, of


def pc_make_rdata_if_necessary(nbits, cf, pf, af, zf, sf, of, platform=None):
    vec = [
        (data[platform]["CondBitOffsets"]["G_CC_SHIFT_C"], cf),
        (data[platform]["CondBitOffsets"]["G_CC_SHIFT_P"], pf),
        (data[platform]["CondBitOffsets"]["G_CC_SHIFT_A"], af),
        (data[platform]["CondBitOffsets"]["G_CC_SHIFT_Z"], zf),
        (data[platform]["CondBitOffsets"]["G_CC_SHIFT_S"], sf),
        (data[platform]["CondBitOffsets"]["G_CC_SHIFT_O"], of),
    ]
    vec.sort(reverse=True)
    return _concat_flags(nbits, vec)


def pc_actions_ADD(state, nbits, arg_l, arg_r, cc_ndep, platform=None):
    data_mask, sign_mask = pc_preamble(nbits)
    res = arg_l + arg_r

    cf = claripy.If(claripy.ULT(res, arg_l), claripy.BVV(1, 1), claripy.BVV(0, 1))
    pf = calc_paritybit(res)
    af = (res ^ arg_l ^ arg_r)[data[platform]["CondBitOffsets"]["G_CC_SHIFT_A"]]
    zf = calc_zerobit(res)
    sf = res[nbits - 1 : nbits - 1]
    of = ((arg_l ^ arg_r ^ data_mask) & (arg_l ^ res))[nbits - 1 : nbits - 1]

    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_SUB(state, nbits, arg_l, arg_r, cc_ndep, platform=None):
    res = arg_l - arg_r

    cf = claripy.If(claripy.ULT(arg_l, arg_r), claripy.BVV(1, 1), claripy.BVV(0, 1))
    pf = calc_paritybit(res)
    af = (res ^ arg_l ^ arg_r)[data[platform]["CondBitOffsets"]["G_CC_SHIFT_A"]]
    zf = calc_zerobit(res)
    sf = res[nbits - 1 : nbits - 1]
    of = ((arg_l ^ arg_r) & (arg_l ^ res))[nbits - 1 : nbits - 1]

    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_LOGIC(state, nbits, arg_l, arg_r, cc_ndep, platform=None):
    cf = claripy.BVV(0, 1)
    pf = calc_paritybit(arg_l)
    af = claripy.BVV(0, 1)
    zf = calc_zerobit(arg_l)
    sf = arg_l[nbits - 1]
    of = claripy.BVV(0, 1)

    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_DEC(state, nbits, res, _, cc_ndep, platform=None):
    arg_l = res + 1

    cf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_C"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_C"]]
    pf = calc_paritybit(res)
    af = (res ^ arg_l ^ 1)[data[platform]["CondBitOffsets"]["G_CC_SHIFT_A"]]
    zf = calc_zerobit(res)
    sf = res[nbits - 1]
    of = claripy.If(arg_l == (1 << (nbits - 1)), claripy.BVV(1, 1), claripy.BVV(0, 1))
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_ADC(state, nbits, cc_dep1, cc_dep2, cc_ndep, platform=None):
    old_c = cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_C"]
    arg_l = cc_dep1
    arg_r = cc_dep2 ^ old_c
    res = (arg_l + arg_r) + old_c

    cf = claripy.If(
        old_c != 0,
        claripy.If(res <= arg_l, claripy.BVV(1, 1), claripy.BVV(0, 1)),
        claripy.If(res < arg_l, claripy.BVV(1, 1), claripy.BVV(0, 1)),
    )
    pf = calc_paritybit(res)
    af = (res ^ arg_l ^ arg_r)[data[platform]["CondBitOffsets"]["G_CC_SHIFT_A"]]
    zf = calc_zerobit(res)
    sf = res[nbits - 1]
    of = ((arg_l ^ arg_r ^ -1) & (arg_l ^ res))[nbits - 1]

    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_ADCX(state, nbits, cc_dep1, cc_dep2, cc_ndep, is_adc, platform=None):
    pf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_P"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_P"]]
    af = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_A"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_A"]]
    zf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_Z"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_Z"]]
    sf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_S"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_S"]]
    if is_adc:
        carry = claripy.LShR(cc_ndep, data[platform]["CondBitOffsets"]["G_CC_SHIFT_C"]) & 1
        of = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_O"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_O"]]
    else:
        carry = claripy.LShR(cc_ndep, data[platform]["CondBitOffsets"]["G_CC_SHIFT_O"]) & 1
        cf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_C"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_C"]]
    arg_l = cc_dep1
    arg_r = cc_dep2 ^ carry
    res = (arg_l + arg_r) + carry

    carry = claripy.If(
        carry != 0,
        claripy.If(res <= arg_l, claripy.BVV(1, 1), claripy.BVV(0, 1)),
        claripy.If(res < arg_l, claripy.BVV(1, 1), claripy.BVV(0, 1)),
    )
    if is_adc:
        cf = carry
    else:
        of = carry

    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_ANDN(state, nbits, cc_dep1, cc_dep2, cc_ndep, platform=None):
    cf = claripy.BVV(0, 1)
    pf = claripy.BVV(0, 1)
    af = claripy.BVV(0, 1)
    of = claripy.BVV(0, 1)
    zf = _cond_flag(cc_dep1 == 0)
    sf = cc_dep1[nbits - 1]
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_BLSI(state, nbits, cc_dep1, cc_dep2, cc_ndep, platform=None):
    pf = claripy.BVV(0, 1)
    af = claripy.BVV(0, 1)
    of = claripy.BVV(0, 1)
    cf = _cond_flag(cc_dep2 != 0)
    zf = _cond_flag(cc_dep1 == 0)
    sf = cc_dep1[nbits - 1]
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_BLSMSK(state, nbits, cc_dep1, cc_dep2, cc_ndep, platform=None):
    pf = claripy.BVV(0, 1)
    af = claripy.BVV(0, 1)
    of = claripy.BVV(0, 1)
    zf = claripy.BVV(0, 1)
    cf = _cond_flag(cc_dep2 == 0)
    sf = cc_dep1[nbits - 1]
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_BLSR(state, nbits, cc_dep1, cc_dep2, cc_ndep, platform=None):
    pf = claripy.BVV(0, 1)
    af = claripy.BVV(0, 1)
    of = claripy.BVV(0, 1)
    cf = _cond_flag(cc_dep2 == 0)
    zf = _cond_flag(cc_dep1 == 0)
    sf = cc_dep1[nbits - 1]
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_SBB(state, nbits, cc_dep1, cc_dep2, cc_ndep, platform=None):
    old_c = cc_ndep[data[platform]["CondBitOffsets"]["G_CC_SHIFT_C"]].zero_extend(nbits - 1)
    arg_l = cc_dep1
    arg_r = cc_dep2 ^ old_c
    res = (arg_l - arg_r) - old_c

    cf_c = claripy.If(claripy.ULE(arg_l, arg_r), claripy.BVV(1, 1), claripy.BVV(0, 1))
    cf_noc = claripy.If(claripy.ULT(arg_l, arg_r), claripy.BVV(1, 1), claripy.BVV(0, 1))
    cf = claripy.If(old_c == 1, cf_c, cf_noc)
    pf = calc_paritybit(res)
    af = (res ^ arg_l ^ arg_r)[data[platform]["CondBitOffsets"]["G_CC_SHIFT_A"]]
    zf = calc_zerobit(res)
    sf = res[nbits - 1]
    of = ((arg_l ^ arg_r) & (arg_l ^ res))[nbits - 1]
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_INC(state, nbits, res, _, cc_ndep, platform=None):
    arg_l = res - 1

    cf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_C"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_C"]]
    pf = calc_paritybit(res)
    af = (res ^ arg_l ^ 1)[data[platform]["CondBitOffsets"]["G_CC_SHIFT_A"]]
    zf = calc_zerobit(res)
    sf = res[nbits - 1]
    of = claripy.If(res == (1 << (nbits - 1)), claripy.BVV(1, 1), claripy.BVV(0, 1))
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_SHL(state, nbits, remaining, shifted, cc_ndep, platform=None):
    cf = shifted[nbits - 1]
    pf = calc_paritybit(remaining[7:0])
    af = claripy.BVV(0, 1)
    zf = calc_zerobit(remaining)
    sf = remaining[nbits - 1]
    of = (sf ^ cf)[0]
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_SHR(state, nbits, remaining, shifted, cc_ndep, platform=None):
    cf = claripy.If(shifted & 1 != 0, claripy.BVV(1, 1), claripy.BVV(0, 1))
    pf = calc_paritybit(remaining[7:0])
    af = claripy.BVV(0, 1)
    zf = calc_zerobit(remaining)
    sf = remaining[nbits - 1]
    of = (remaining[nbits - 1] ^ shifted[nbits - 1])[0]
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_ROL(state, nbits, res, _, cc_ndep, platform=None):
    cf = res[0]
    pf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_P"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_P"]]
    af = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_A"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_A"]]
    zf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_Z"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_Z"]]
    sf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_S"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_S"]]
    of = (claripy.LShR(res, nbits - 1) ^ res)[0]
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_ROR(state, nbits, res, _, cc_ndep, platform=None):
    cf = res[nbits - 1]
    pf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_P"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_P"]]
    af = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_A"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_A"]]
    zf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_Z"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_Z"]]
    sf = (cc_ndep & data[platform]["CondBitMasks"]["G_CC_MASK_S"])[data[platform]["CondBitOffsets"]["G_CC_SHIFT_S"]]
    of = res[nbits - 1] ^ res[nbits - 2]
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_UMUL(state, nbits, cc_dep1, cc_dep2, cc_ndep, platform=None):
    lo = (cc_dep1 * cc_dep2)[nbits - 1 : 0]
    rr = lo
    hi = (rr >> nbits)[nbits - 1 : 0]
    cf = claripy.If(hi != 0, claripy.BVV(1, 1), claripy.BVV(0, 1))
    zf = calc_zerobit(lo)
    pf = calc_paritybit(lo)
    af = claripy.BVV(0, 1)
    sf = lo[nbits - 1]
    of = cf
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_UMULQ(*args, **kwargs):
    l.error("Unsupported flag action UMULQ")
    raise SimCCallError("Unsupported flag action. Please implement or bug Yan.")


def pc_actions_SMUL(state, nbits, cc_dep1, cc_dep2, cc_ndep, platform=None):
    ir = cc_dep1.sign_extend(nbits) * cc_dep2.sign_extend(nbits)
    hi = ir[2 * nbits - 1 : nbits]
    lo = ir[nbits - 1 : 0]
    cf = claripy.If(hi != (lo >> (nbits - 1)), claripy.BVV(1, 1), claripy.BVV(0, 1))
    zf = calc_zerobit(lo)
    pf = calc_paritybit(lo)
    af = claripy.BVV(0, 1)
    sf = lo[nbits - 1]
    of = cf
    return pc_make_rdata(data[platform]["size"], cf, pf, af, zf, sf, of, platform=platform)


def pc_actions_SMULQ(*args, **kwargs):
    l.error("Unsupported flag action SMULQ")
    raise SimCCallError("Unsupported flag action. Please implement or bug Yan.")


def pc_calculate_rdata_all_WRK(state, cc_op, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=None):
    # sanity check
    cc_op = op_concretize(cc_op)

    if cc_op == data[platform]["OpTypes"]["G_CC_OP_COPY"]:
        l.debug("cc_op == data[platform]['OpTypes']['G_CC_OP_COPY']")
        return cc_dep1_formal & (
            data[platform]["CondBitMasks"]["G_CC_MASK_O"]
            | data[platform]["CondBitMasks"]["G_CC_MASK_S"]
            | data[platform]["CondBitMasks"]["G_CC_MASK_Z"]
            | data[platform]["CondBitMasks"]["G_CC_MASK_A"]
            | data[platform]["CondBitMasks"]["G_CC_MASK_C"]
            | data[platform]["CondBitMasks"]["G_CC_MASK_P"]
        )

    cc_str = data_inverted[platform]["OpTypes"][cc_op]

    nbits = _get_nbits(cc_str)
    l.debug("nbits == %d", nbits)

    cc_dep1_formal = cc_dep1_formal[nbits - 1 : 0]
    cc_dep2_formal = cc_dep2_formal[nbits - 1 : 0]
    cc_ndep_formal = cc_ndep_formal[nbits - 1 : 0]

    if cc_str in ["G_CC_OP_ADDB", "G_CC_OP_ADDW", "G_CC_OP_ADDL", "G_CC_OP_ADDQ"]:
        l.debug("cc_str: ADD")
        return pc_actions_ADD(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)

    if cc_str in ["G_CC_OP_ADCB", "G_CC_OP_ADCW", "G_CC_OP_ADCL", "G_CC_OP_ADCQ"]:
        l.debug("cc_str: ADC")
        return pc_actions_ADC(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)

    if cc_str in ["G_CC_OP_SUBB", "G_CC_OP_SUBW", "G_CC_OP_SUBL", "G_CC_OP_SUBQ"]:
        l.debug("cc_str: SUB")
        return pc_actions_SUB(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)

    if cc_str in ["G_CC_OP_SBBB", "G_CC_OP_SBBW", "G_CC_OP_SBBL", "G_CC_OP_SBBQ"]:
        l.debug("cc_str: SBB")
        return pc_actions_SBB(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)

    if cc_str in ["G_CC_OP_LOGICB", "G_CC_OP_LOGICW", "G_CC_OP_LOGICL", "G_CC_OP_LOGICQ"]:
        l.debug("cc_str: LOGIC")
        return pc_actions_LOGIC(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)

    if cc_str in ["G_CC_OP_INCB", "G_CC_OP_INCW", "G_CC_OP_INCL", "G_CC_OP_INCQ"]:
        l.debug("cc_str: INC")
        return pc_actions_INC(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)

    if cc_str in ["G_CC_OP_DECB", "G_CC_OP_DECW", "G_CC_OP_DECL", "G_CC_OP_DECQ"]:
        l.debug("cc_str: DEC")
        return pc_actions_DEC(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)

    if cc_str in ["G_CC_OP_SHLB", "G_CC_OP_SHLW", "G_CC_OP_SHLL", "G_CC_OP_SHLQ"]:
        l.debug("cc_str: SHL")
        return pc_actions_SHL(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)

    if cc_str in ["G_CC_OP_SHRB", "G_CC_OP_SHRW", "G_CC_OP_SHRL", "G_CC_OP_SHRQ"]:
        l.debug("cc_str: SHR")
        return pc_actions_SHR(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)

    if cc_str in ["G_CC_OP_ROLB", "G_CC_OP_ROLW", "G_CC_OP_ROLL", "G_CC_OP_ROLQ"]:
        l.debug("cc_str: ROL")
        return pc_actions_ROL(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)

    if cc_str in ["G_CC_OP_RORB", "G_CC_OP_RORW", "G_CC_OP_RORL", "G_CC_OP_RORQ"]:
        l.debug("cc_str: ROR")
        return pc_actions_ROR(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)

    if cc_str in ["G_CC_OP_UMULB", "G_CC_OP_UMULW", "G_CC_OP_UMULL", "G_CC_OP_UMULQ"]:
        l.debug("cc_str: UMUL")
        return pc_actions_UMUL(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)
    if cc_str == "G_CC_OP_UMULQ":
        l.debug("cc_str: UMULQ")
        return pc_actions_UMULQ(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)
    if cc_str in ["G_CC_OP_SMULB", "G_CC_OP_SMULW", "G_CC_OP_SMULL", "G_CC_OP_SMULQ"]:
        l.debug("cc_str: SMUL")
        return pc_actions_SMUL(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)
    if cc_str == "G_CC_OP_SMULQ":
        l.debug("cc_str: SMULQ")
        return pc_actions_SMULQ(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)
    if cc_str in ["G_CC_OP_ANDN32", "G_CC_OP_ANDN64"]:
        l.debug("cc_str: ANDN")
        return pc_actions_ANDN(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)
    if cc_str in ["G_CC_OP_BLSI32", "G_CC_OP_BLSI64"]:
        l.debug("cc_str: BLSI")
        return pc_actions_BLSI(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)
    if cc_str in ["G_CC_OP_BLSMSK32", "G_CC_OP_BLSMSK64"]:
        l.debug("cc_str: BLSMSK")
        return pc_actions_BLSMSK(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)
    if cc_str in ["G_CC_OP_BLSR32", "G_CC_OP_BLSR64"]:
        l.debug("cc_str: BLSR")
        return pc_actions_BLSR(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, platform=platform)
    if cc_str in ["G_CC_OP_ADOXL", "G_CC_OP_ADOXQ"]:
        l.debug("cc_str: ADOX")
        return pc_actions_ADCX(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, False, platform=platform)
    if cc_str in ["G_CC_OP_ADCXL", "G_CC_OP_ADCXQ"]:
        l.debug("cc_str: ADCX")
        return pc_actions_ADCX(state, nbits, cc_dep1_formal, cc_dep2_formal, cc_ndep_formal, True, platform=platform)

    l.error("Unsupported cc_op %d in in pc_calculate_rdata_all_WRK", cc_op)
    raise SimCCallError("Unsupported cc_op in pc_calculate_rdata_all_WRK")


# This function returns all the data
def pc_calculate_rdata_all(state, cc_op, cc_dep1, cc_dep2, cc_ndep, platform=None):
    rdata_all = pc_calculate_rdata_all_WRK(state, cc_op, cc_dep1, cc_dep2, cc_ndep, platform=platform)
    if isinstance(rdata_all, tuple):
        return pc_make_rdata_if_necessary(data[platform]["size"], *rdata_all, platform=platform)
    return rdata_all


# This function takes a condition that is being checked (ie, zero bit), and basically
# returns that bit
def pc_calculate_condition(state, cond, cc_op, cc_dep1, cc_dep2, cc_ndep, platform=None):
    rdata_all = pc_calculate_rdata_all_WRK(state, cc_op, cc_dep1, cc_dep2, cc_ndep, platform=platform)
    if isinstance(rdata_all, tuple):
        cf, pf, af, zf, sf, of = rdata_all
        v = op_concretize(cond)

        inv = v & 1
        l.debug("inv: %d", inv)

        if v in [data[platform]["CondTypes"]["CondO"], data[platform]["CondTypes"]["CondNO"]]:
            l.debug("CondO")
            # of = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_O'])
            r = 1 & (inv ^ of)

        elif v in [data[platform]["CondTypes"]["CondZ"], data[platform]["CondTypes"]["CondNZ"]]:
            l.debug("CondZ")
            # zf = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_Z'])
            r = 1 & (inv ^ zf)

        elif v in [data[platform]["CondTypes"]["CondB"], data[platform]["CondTypes"]["CondNB"]]:
            l.debug("CondB")
            # cf = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_C'])
            r = 1 & (inv ^ cf)

        elif v in [data[platform]["CondTypes"]["CondBE"], data[platform]["CondTypes"]["CondNBE"]]:
            l.debug("CondBE")
            # cf = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_C'])
            # zf = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_Z'])
            r = 1 & (inv ^ (cf | zf))

        elif v in [data[platform]["CondTypes"]["CondS"], data[platform]["CondTypes"]["CondNS"]]:
            l.debug("CondS")
            # sf = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_S'])
            r = 1 & (inv ^ sf)

        elif v in [data[platform]["CondTypes"]["CondP"], data[platform]["CondTypes"]["CondNP"]]:
            l.debug("CondP")
            # pf = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_P'])
            r = 1 & (inv ^ pf)

        elif v in [data[platform]["CondTypes"]["CondL"], data[platform]["CondTypes"]["CondNL"]]:
            l.debug("CondL")
            # sf = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_S'])
            # of = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_O'])
            r = 1 & (inv ^ (sf ^ of))

        elif v in [data[platform]["CondTypes"]["CondLE"], data[platform]["CondTypes"]["CondNLE"]]:
            l.debug("CondLE")
            # sf = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_S'])
            # of = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_O'])
            # zf = claripy.LShR(rdata, data[platform]['G_CC_SHIFT_Z'])
            r = 1 & (inv ^ ((sf ^ of) | zf))
        else:
            raise SimCCallError("Unrecognized condition in pc_calculate_condition. Panic.")

        return claripy.Concat(claripy.BVV(0, data[platform]["size"] - 1), r)
    rdata = rdata_all
    v = op_concretize(cond)
    inv = v & 1
    l.debug("inv: %d", inv)

    # THIS IS A FUCKING HACK
    if v == 0xE:
        # jle
        pass
    if v in [data[platform]["CondTypes"]["CondO"], data[platform]["CondTypes"]["CondNO"]]:
        l.debug("CondO")
        of = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_O"])
        return 1 & (inv ^ of)

    if v in [data[platform]["CondTypes"]["CondZ"], data[platform]["CondTypes"]["CondNZ"]]:
        l.debug("CondZ")
        zf = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_Z"])
        return 1 & (inv ^ zf)

    if v in [data[platform]["CondTypes"]["CondB"], data[platform]["CondTypes"]["CondNB"]]:
        l.debug("CondB")
        cf = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_C"])
        return 1 & (inv ^ cf)

    if v in [data[platform]["CondTypes"]["CondBE"], data[platform]["CondTypes"]["CondNBE"]]:
        l.debug("CondBE")
        cf = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_C"])
        zf = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_Z"])
        return 1 & (inv ^ (cf | zf))

    if v in [data[platform]["CondTypes"]["CondS"], data[platform]["CondTypes"]["CondNS"]]:
        l.debug("CondS")
        sf = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_S"])
        return 1 & (inv ^ sf)

    if v in [data[platform]["CondTypes"]["CondP"], data[platform]["CondTypes"]["CondNP"]]:
        l.debug("CondP")
        pf = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_P"])
        return 1 & (inv ^ pf)

    if v in [data[platform]["CondTypes"]["CondL"], data[platform]["CondTypes"]["CondNL"]]:
        l.debug("CondL")
        sf = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_S"])
        of = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_O"])
        return 1 & (inv ^ (sf ^ of))

    if v in [data[platform]["CondTypes"]["CondLE"], data[platform]["CondTypes"]["CondNLE"]]:
        l.debug("CondLE")
        sf = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_S"])
        of = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_O"])
        zf = claripy.LShR(rdata, data[platform]["CondBitOffsets"]["G_CC_SHIFT_Z"])
        return 1 & (inv ^ ((sf ^ of) | zf))

    l.error("Unsupported condition %d in in pc_calculate_condition", v)
    raise SimCCallError("Unrecognized condition in pc_calculate_condition")


#
# Simplified CCalls
#

# Simplified CCalls (whose names look like `pc_actions_<operation>_<condition>`) are a bunch of methods that generate
# straight-forward ASTs based on the operation and the condition, instead of blindly following the way that a CPU does
# the conditional flags calculation and generating messy and meaningless ASTs. It allows us to have a meaningful AST
# for each conditional flag, which greatly helps static analysis (like VSA).

# TODO: Implement the missing ones


# General ops
def pc_actions_op_SUB(arg_l, arg_r, cc_ndep):
    return arg_l - arg_r


def pc_actions_op_DEC(arg_l, arg_r, cc_ndep):
    return arg_l - 1


def pc_actions_op_INC(arg_l, arg_r, cc_ndep):
    return arg_l + 1


def pc_actions_op_SHR(arg_l, arg_r, cc_ndep):
    return arg_l >> arg_r


def pc_actions_op_SHL(arg_l, arg_r, cc_ndep):
    return arg_l << arg_r


def pc_actions_op_ADD(arg_l, arg_r, cc_ndep):
    return arg_l + arg_r


def pc_actions_op_LOGIC(arg_l, arg_r, cc_ndep):
    return arg_l


# General conditions
def pc_actions_cond_CondZ(state, cc_expr):
    return _cond_flag(cc_expr == 0)


def pc_actions_cond_CondNZ(state, cc_expr):
    return _cond_flag(cc_expr != 0)


def pc_actions_cond_CondS(state, cc_expr):
    return _cond_flag(claripy.SLT(cc_expr, 0))


def pc_actions_cond_CondB(state, cc_expr):
    return _cond_flag(claripy.ULT(cc_expr, 0))


def pc_actions_cond_CondBE(state, cc_expr):
    return _cond_flag(claripy.ULE(cc_expr, 0))


def pc_actions_cond_CondNBE(state, cc_expr):
    return _cond_flag(claripy.UGT(cc_expr, 0))


def pc_actions_cond_CondL(state, cc_expr):
    return _cond_flag(claripy.SLT(cc_expr, 0))


def pc_actions_cond_CondLE(state, cc_expr):
    return _cond_flag(claripy.SLE(cc_expr, 0))


def pc_actions_cond_CondNLE(state, cc_expr):
    return _cond_flag(claripy.SGT(cc_expr, 0))


# Specialized versions of (op,cond) to make claripy happy
def pc_actions_SUB_CondZ(state, arg_l, arg_r, cc_ndep):
    return _cond_flag(arg_l == arg_r)


def pc_actions_SUB_CondNZ(state, arg_l, arg_r, cc_ndep):
    return _cond_flag(arg_l != arg_r)


def pc_actions_SUB_CondB(state, arg_l, arg_r, cc_ndep):
    return _cond_flag(claripy.ULT(arg_l, arg_r))


def pc_actions_SUB_CondBE(state, arg_l, arg_r, cc_ndep):
    return _cond_flag(claripy.ULE(arg_l, arg_r))


def pc_actions_SUB_CondNBE(state, arg_l, arg_r, cc_ndep):
    return _cond_flag(claripy.UGT(arg_l, arg_r))


def pc_actions_SUB_CondL(state, arg_l, arg_r, cc_ndep):
    return _cond_flag(claripy.SLT(arg_l, arg_r))


def pc_actions_SUB_CondLE(state, arg_l, arg_r, cc_ndep):
    return _cond_flag(claripy.SLE(arg_l, arg_r))


def pc_actions_SUB_CondNLE(state, arg_l, arg_r, cc_ndep):
    return _cond_flag(claripy.SGT(arg_l, arg_r))


def pc_calculate_condition_simple(state, cond, cc_op, cc_dep1, cc_dep2, cc_ndep, platform=None):
    """
    A simplified version of pc_calculate_condition(). Please refer to the documentation of Simplified CCalls above.

    Limitation: symbolic flags are not supported for now.
    """

    # Extract the operation
    v = op_concretize(cond)
    cc_op = op_concretize(cc_op)

    if cc_op == data[platform]["OpTypes"]["G_CC_OP_COPY"]:
        raise SimCCallError("G_CC_OP_COPY is not supported in pc_calculate_condition_simple(). Consider implementing.")
    if cc_op == data[platform]["OpTypes"]["G_CC_OP_NUMBER"]:
        raise SimCCallError(
            "G_CC_OP_NUMBER is not supported in pc_calculate_condition_simple(). Consider implementing."
        )

    op = data_inverted[platform]["OpTypes"][cc_op]
    nbits = _get_nbits(op)
    op = op[8:-1]

    # Extract the condition
    cond = None
    # TODO: Convert it to a table-lookup later
    for key, cond_val in data[platform]["CondTypes"].items():
        if cond_val == v:
            cond = key
            break

    cc_dep1_nbits = cc_dep1[nbits - 1 : 0]
    cc_dep2_nbits = cc_dep2[nbits - 1 : 0]

    # check for a specialized version first
    funcname = f"pc_actions_{op}_{cond}"
    if funcname in globals():
        r = globals()[funcname](state, cc_dep1_nbits, cc_dep2_nbits, cc_ndep)
    else:
        op_funcname = f"pc_actions_op_{op}"
        cond_funcname = f"pc_actions_cond_{cond}"
        if op_funcname in globals() and cond_funcname in globals():
            cc_expr = globals()[op_funcname](cc_dep1_nbits, cc_dep2_nbits, cc_ndep)
            r = globals()[cond_funcname](state, cc_expr)
        else:
            l.warning(
                "Operation %s with condition %s is not supported in pc_calculate_condition_simple(). "
                "Consider implementing.",
                op,
                cond,
            )
            raise SimCCallError(f"Operation {op} with condition {cond} not found.")

    return claripy.Concat(claripy.BVV(0, data[platform]["size"] - 1), r)


def pc_calculate_rdata_c(state, cc_op, cc_dep1, cc_dep2, cc_ndep, platform=None):
    cc_op = op_concretize(cc_op)

    if cc_op == data[platform]["OpTypes"]["G_CC_OP_COPY"]:
        return claripy.LShR(cc_dep1, data[platform]["CondBitOffsets"]["G_CC_SHIFT_C"]) & 1  # TODO: actual constraints
    if cc_op in (
        data[platform]["OpTypes"]["G_CC_OP_LOGICQ"],
        data[platform]["OpTypes"]["G_CC_OP_LOGICL"],
        data[platform]["OpTypes"]["G_CC_OP_LOGICW"],
        data[platform]["OpTypes"]["G_CC_OP_LOGICB"],
    ):
        return claripy.BVV(0, data[platform]["size"])  # TODO: actual constraints

    rdata_all = pc_calculate_rdata_all_WRK(state, cc_op, cc_dep1, cc_dep2, cc_ndep, platform=platform)

    if isinstance(rdata_all, tuple):
        cf, pf, af, zf, sf, of = rdata_all
        return claripy.Concat(claripy.BVV(0, data[platform]["size"] - 1), cf & 1)
    return claripy.LShR(rdata_all, data[platform]["CondBitOffsets"]["G_CC_SHIFT_C"]) & 1


def generic_rotate_with_carry(state, left, arg, rot_amt, carry_bit_in, sz):
    # returns cf, of, result
    # make sure sz is not symbolic
    if sz.op != "BVV":
        raise SimError('Hit a symbolic "sz" in an x86 rotate with carry instruction. Panic.')

    # convert sz to concrete value
    sz = sz.args[0]
    bits = sz * 8
    bits_in = len(arg)

    # construct bitvec to use for rotation amount - 9/17/33/65 bits
    if bits > len(rot_amt):
        raise SimError("Got a rotate instruction for data larger than the provided word size. Panic.")

    sized_amt = (rot_amt & bits_in - 1).zero_extend(1) if bits == len(rot_amt) else (rot_amt & bits_in - 1)[bits:0]

    assert len(sized_amt) == bits + 1

    # construct bitvec to use for rotating value - 9/17/33/65 bits
    sized_arg_in = arg[bits - 1 : 0]
    rotatable_in = carry_bit_in.concat(sized_arg_in)

    # compute and extract
    op = claripy.RotateLeft if left else claripy.RotateRight
    rotatable_out = op(rotatable_in, sized_amt)
    sized_arg_out = rotatable_out[bits - 1 : 0]
    carry_bit_out = rotatable_out[bits]
    arg_out = sized_arg_out.zero_extend(bits_in - bits)

    if left:
        overflow_bit_out = carry_bit_out ^ sized_arg_out[bits - 1]
    else:
        overflow_bit_out = sized_arg_out[bits - 1] ^ sized_arg_out[bits - 2]

    # construct final answer
    return carry_bit_out, overflow_bit_out, arg_out


###########################
### AMD64-specific ones ###
###########################
# https://github.com/angr/vex/blob/master/priv/guest_amd64_helpers.c#L2272
def amd64g_check_ldmxcsr(state, mxcsr):
    # /* Decide on a rounding mode.  mxcsr[14:13] holds it. */
    # /* NOTE, encoded exactly as per enum IRRoundingMode. */
    rmode = (mxcsr >> 13) & 3

    # /* Detect any required emulation warnings. */
    ew = EmNote_NONE

    cond = (mxcsr & 0x1F80) != 0x1F80
    if not mxcsr.symbolic:  # Fast path
        if cond.is_true:
            # /* unmasked exceptions! */
            ew = EmWarn_X86_sseExns

        elif (mxcsr & (1 << 15)).is_true:
            # /* FZ is set */
            ew = EmWarn_X86_fz
        elif (mxcsr & (1 << 6)).is_true:
            # /* DAZ is set */
            ew = EmWarn_X86_daz

        return (ew << 32) | rmode

    return (
        claripy.If(
            cond,
            claripy.BVV(EmWarn_X86_sseExns, 64),
            claripy.If(
                mxcsr & (1 << 15) != 0,
                claripy.BVV(EmWarn_X86_fz, 64),
                claripy.If(mxcsr & (1 << 6) != 0, claripy.BVV(EmWarn_X86_daz, 64), claripy.BVV(EmNote_NONE, 64)),
            ),
        )
        << 32
    ) | rmode


# https://github.com/angr/vex/blob/master/priv/guest_amd64_helpers.c#L2304
def amd64g_create_mxcsr(state, sseround):
    return 0x1F80 | ((sseround & 3) << 13)


# https://github.com/angr/vex/blob/master/priv/guest_amd64_helpers.c#L2316
def amd64g_check_fldcw(state, fpucw):
    rmode = (fpucw >> 10) & 3
    ew = EmNote_NONE
    if ((fpucw & 0x3F) != 0x3F).is_true:
        # unmasked exceptions
        ew = EmWarn_X86_x87exns
    elif (((fpucw >> 8) & 3) != 3).is_true:
        ew = EmWarn_X86_x87precision
    return (ew << 32) | rmode


# https://github.com/angr/vex/blob/master/priv/guest_amd64_helpers.c#L2342
def amd64g_create_fpucw(state, fpround):
    fpround &= 3
    return 0x037F | (fpround << 10)


def amd64g_calculate_RCL(state, arg, rot_amt, eflags_in, sz):
    if sz.op != "BVV":
        raise SimError('Hit a symbolic "sz" in an x86 rotate with carry instruction. Panic.')

    want_flags = claripy.SLT(sz, 0).is_true()
    if want_flags:
        sz = -sz
    carry_bit_in = eflags_in[data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_C"]]
    carry_bit_out, overflow_bit_out, arg_out = generic_rotate_with_carry(state, True, arg, rot_amt, carry_bit_in, sz)

    if want_flags:
        cf = carry_bit_out.zero_extend(63)
        of = overflow_bit_out.zero_extend(63)
        eflags_out = eflags_in
        eflags_out &= ~(data["AMD64"]["CondBitMasks"]["G_CC_MASK_C"] | data["AMD64"]["CondBitMasks"]["G_CC_MASK_O"])
        eflags_out |= (cf << data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_C"]) | (
            of << data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_O"]
        )
        return eflags_out
    return arg_out


def amd64g_calculate_RCR(state, arg, rot_amt, eflags_in, sz):
    if sz.op != "BVV":
        raise SimError('Hit a symbolic "sz" in an x86 rotate with carry instruction. Panic.')

    want_flags = claripy.SLT(sz, 0).is_true()
    if want_flags:
        sz = -sz
    carry_bit_in = eflags_in[data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_C"]]
    carry_bit_out, overflow_bit_out, arg_out = generic_rotate_with_carry(state, False, arg, rot_amt, carry_bit_in, sz)

    if want_flags:
        cf = carry_bit_out.zero_extend(63)
        of = overflow_bit_out.zero_extend(63)
        eflags_out = eflags_in
        eflags_out &= ~(data["AMD64"]["CondBitMasks"]["G_CC_MASK_C"] | data["AMD64"]["CondBitMasks"]["G_CC_MASK_O"])
        eflags_out |= (cf << data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_C"]) | (
            of << data["AMD64"]["CondBitOffsets"]["G_CC_SHIFT_O"]
        )
        return eflags_out
    return arg_out


def amd64g_calculate_mmx_pmaddwd(_state, xx, yy):
    xx_3, xx_2, xx_1, xx_0 = xx.chop(16)
    yy_3, yy_2, yy_1, yy_0 = yy.chop(16)
    xx_3 = xx_3.sign_extend(16)
    xx_2 = xx_2.sign_extend(16)
    xx_1 = xx_1.sign_extend(16)
    xx_0 = xx_0.sign_extend(16)
    yy_3 = yy_3.sign_extend(16)
    yy_2 = yy_2.sign_extend(16)
    yy_1 = yy_1.sign_extend(16)
    yy_0 = yy_0.sign_extend(16)

    res_1 = xx_3 * yy_3 + xx_2 * yy_2
    res_0 = xx_1 * yy_1 + xx_0 * yy_0

    return claripy.Concat(res_1, res_0)


def amd64g_calculate_condition(state, cond, cc_op, cc_dep1, cc_dep2, cc_ndep):
    if USE_SIMPLIFIED_CCALLS in state.options:
        try:
            return pc_calculate_condition_simple(state, cond, cc_op, cc_dep1, cc_dep2, cc_ndep, platform="AMD64")
        except KeyError:
            pass
    return pc_calculate_condition(state, cond, cc_op, cc_dep1, cc_dep2, cc_ndep, platform="AMD64")


def amd64g_calculate_rflags_all(state, cc_op, cc_dep1, cc_dep2, cc_ndep):
    return pc_calculate_rdata_all(state, cc_op, cc_dep1, cc_dep2, cc_ndep, platform="AMD64")


def amd64g_calculate_rflags_c(state, cc_op, cc_dep1, cc_dep2, cc_ndep):
    return pc_calculate_rdata_c(state, cc_op, cc_dep1, cc_dep2, cc_ndep, platform="AMD64")


###########################
### X86-specific ones ###
###########################


def x86g_calculate_RCL(state, arg, rot_amt, eflags_in, sz):
    carry_bit_in = eflags_in[data["X86"]["CondBitOffsets"]["G_CC_SHIFT_C"]]
    carry_bit_out, overflow_bit_out, arg_out = generic_rotate_with_carry(state, True, arg, rot_amt, carry_bit_in, sz)

    cf = carry_bit_out.zero_extend(31)
    of = overflow_bit_out.zero_extend(31)
    eflags_out = eflags_in
    eflags_out &= ~(data["X86"]["CondBitMasks"]["G_CC_MASK_C"] | data["X86"]["CondBitMasks"]["G_CC_MASK_O"])
    eflags_out |= (cf << data["X86"]["CondBitOffsets"]["G_CC_SHIFT_C"]) | (
        of << data["X86"]["CondBitOffsets"]["G_CC_SHIFT_O"]
    )

    return eflags_out.concat(arg_out)


def x86g_calculate_RCR(state, arg, rot_amt, eflags_in, sz):
    carry_bit_in = eflags_in[data["X86"]["CondBitOffsets"]["G_CC_SHIFT_C"]]
    carry_bit_out, overflow_bit_out, arg_out = generic_rotate_with_carry(state, False, arg, rot_amt, carry_bit_in, sz)

    cf = carry_bit_out.zero_extend(31)
    of = overflow_bit_out.zero_extend(31)
    eflags_out = eflags_in
    eflags_out &= ~(data["X86"]["CondBitMasks"]["G_CC_MASK_C"] | data["X86"]["CondBitMasks"]["G_CC_MASK_O"])
    eflags_out |= (cf << data["X86"]["CondBitOffsets"]["G_CC_SHIFT_C"]) | (
        of << data["X86"]["CondBitOffsets"]["G_CC_SHIFT_O"]
    )

    return eflags_out.concat(arg_out)


def x86g_calculate_condition(state, cond, cc_op, cc_dep1, cc_dep2, cc_ndep):
    if USE_SIMPLIFIED_CCALLS in state.options:
        return pc_calculate_condition_simple(state, cond, cc_op, cc_dep1, cc_dep2, cc_ndep, platform="X86")
    return pc_calculate_condition(state, cond, cc_op, cc_dep1, cc_dep2, cc_ndep, platform="X86")


def x86g_calculate_eflags_all(state, cc_op, cc_dep1, cc_dep2, cc_ndep):
    return pc_calculate_rdata_all(state, cc_op, cc_dep1, cc_dep2, cc_ndep, platform="X86")


def x86g_calculate_eflags_c(state, cc_op, cc_dep1, cc_dep2, cc_ndep):
    return pc_calculate_rdata_c(state, cc_op, cc_dep1, cc_dep2, cc_ndep, platform="X86")


def x86g_check_fldcw(state, fpucw):
    return ((fpucw >> 10) & 3).zero_extend(32)


def x86g_create_fpucw(state, fpround):
    return 0x037F | ((fpround & 3) << 10)


def x86g_calculate_daa_das_aaa_aas(state, flags_and_AX, opcode):
    assert len(flags_and_AX) == 32
    assert opcode.op == "BVV"
    opcode = opcode.args[0]

    r_O = flags_and_AX[data["X86"]["CondBitOffsets"]["G_CC_SHIFT_O"] + 16].zero_extend(31)
    r_S = flags_and_AX[data["X86"]["CondBitOffsets"]["G_CC_SHIFT_S"] + 16].zero_extend(31)
    r_Z = flags_and_AX[data["X86"]["CondBitOffsets"]["G_CC_SHIFT_Z"] + 16].zero_extend(31)
    r_A = flags_and_AX[data["X86"]["CondBitOffsets"]["G_CC_SHIFT_A"] + 16].zero_extend(31)
    r_C = flags_and_AX[data["X86"]["CondBitOffsets"]["G_CC_SHIFT_C"] + 16].zero_extend(31)
    r_P = flags_and_AX[data["X86"]["CondBitOffsets"]["G_CC_SHIFT_P"] + 16].zero_extend(31)

    r_AL = (flags_and_AX >> 0) & 0xFF
    r_AH = (flags_and_AX >> 8) & 0xFF

    zero = claripy.BVV(0, 32)
    one = claripy.BVV(1, 32)

    if opcode == 0x27:  # DAA
        old_AL = r_AL
        old_C = r_C

        condition = claripy.Or((r_AL & 0xF) > 9, r_A == 1)
        r_AL = claripy.If(condition, r_AL + 6, old_AL)
        r_C = claripy.If(condition, claripy.If(r_AL >= 0x100, one, old_C), zero)
        r_A = claripy.If(condition, one, zero)

        condition = claripy.Or(old_AL > 0x99, old_C == 1)
        r_AL = claripy.If(condition, r_AL + 0x60, r_AL)
        r_C = claripy.If(condition, one, zero)

        r_AL = r_AL & 0xFF
        r_O = zero
        r_S = claripy.If((r_AL & 0x80) != 0, one, zero)
        r_Z = claripy.If(r_AL == 0, one, zero)
        r_P = calc_paritybit(r_AL).zero_extend(31)

    elif opcode == 0x2F:  # DAS
        old_AL = r_AL
        old_C = r_C

        condition = claripy.Or((r_AL & 0xF) > 9, r_A == 1)
        r_AL = claripy.If(condition, r_AL - 6, old_AL)
        r_C = claripy.If(condition, claripy.If(r_AL < 6, one, zero), zero)
        r_A = claripy.If(condition, one, zero)

        condition = claripy.Or(old_AL > 0x99, old_C == 1)
        r_AL = claripy.If(condition, r_AL - 0x60, r_AL)
        r_C = claripy.If(condition, one, zero)

        r_AL &= 0xFF
        r_O = zero
        r_S = claripy.If((r_AL & 0x80) != 0, one, zero)
        r_Z = claripy.If(r_AL == 0, one, zero)
        r_P = calc_paritybit(r_AL).zero_extend(31)

    elif opcode == 0x37:  # AAA
        nudge = r_AL > 0xF9
        condition = claripy.Or((r_AL & 0xF) > 9, r_A == 1)
        r_AL = claripy.If(condition, (r_AL + 6) & 0xF, r_AL & 0xF)
        r_AH = claripy.If(condition, claripy.If(nudge, r_AH + 2, r_AH + 1), r_AH)
        r_A = claripy.If(condition, one, zero)
        r_C = claripy.If(condition, one, zero)
        r_O = r_S = r_Z = r_P = 0
    elif opcode == 0x3F:  # AAS
        nudge = r_AL < 0x06
        condition = claripy.Or((r_AL & 0xF) > 9, r_A == 1)
        r_AL = claripy.If(condition, (r_AL - 6) & 0xF, r_AL & 0xF)
        r_AH = claripy.If(condition, claripy.If(nudge, r_AH - 2, r_AH - 1), r_AH)
        r_A = claripy.If(condition, one, zero)
        r_C = claripy.If(condition, one, zero)
        r_O = r_S = r_Z = r_P = 0

    return (
        ((r_O & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_O"]))
        | ((r_S & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_S"]))
        | ((r_Z & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_Z"]))
        | ((r_A & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_A"]))
        | ((r_C & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_C"]))
        | ((r_P & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_P"]))
        | ((r_AH & 0xFF) << 8)
        | ((r_AL & 0xFF) << 0)
    )


def x86g_calculate_aad_aam(state, flags_and_AX, opcode):
    assert len(flags_and_AX) == 32
    assert opcode.op == "BVV"
    opcode = opcode.args[0]

    r_AL = (flags_and_AX >> 0) & 0xFF
    r_AH = (flags_and_AX >> 8) & 0xFF

    if opcode == 0xD4:  # AAM
        r_AH = r_AL // 10
        r_AL = r_AL % 10
    elif opcode == 0xD5:  # AAD
        r_AL = ((r_AH * 10) + r_AL) & 0xFF
        r_AH = claripy.BVV(0, 32)
    else:
        raise SimCCallError(f"Unknown opcode {opcode:#x} in AAD/AAM ccall")

    r_O = claripy.BVV(0, 32)
    r_C = claripy.BVV(0, 32)
    r_A = claripy.BVV(0, 32)
    r_S = r_AL[7].zero_extend(31)
    r_Z = claripy.If(r_AL == 0, claripy.BVV(1, 32), claripy.BVV(0, 32))
    r_P = calc_paritybit(r_AL).zero_extend(31)

    return (
        ((r_O & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_O"]))
        | ((r_S & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_S"]))
        | ((r_Z & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_Z"]))
        | ((r_A & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_A"]))
        | ((r_C & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_C"]))
        | ((r_P & 1) << (16 + data["X86"]["CondBitOffsets"]["G_CC_SHIFT_P"]))
        | ((r_AH & 0xFF) << 8)
        | ((r_AL & 0xFF) << 0)
    )


#
# x86 segment selection
#


# Reference for the GDT entry layout
# https://wiki.osdev.org/Global_Descriptor_Table
def get_segdescr_base(state, descriptor):
    lo = descriptor[31:16]
    mid = descriptor[39:32]
    hi = descriptor[63:56]
    return claripy.Concat(hi, mid, lo)


def get_segdescr_limit(state, descriptor):
    granularity = descriptor[55]
    lo = descriptor[15:0]
    hi = descriptor[51:48]
    limit = claripy.Concat(hi, lo).zero_extend(12)
    if (granularity == 0).is_true():
        return limit
    return (limit << 12) | 0xFFF


def x86g_use_seg_selector(state, ldt, gdt, seg_selector, virtual_addr):
    # TODO Read/write/exec bit handling
    def bad(msg):
        if msg:
            l.warning("x86g_use_seg_selector: %s", msg)
        return claripy.BVV(1 << 32, 32).zero_extend(32)

    if (seg_selector & ~0xFFFF != 0).is_true():
        return bad("invalid selector (" + str(seg_selector) + ")")

    if virtual_addr.length == 16:
        virtual_addr = virtual_addr.zero_extend(16)

    # are we in real mode?
    if state.arch.vex_archinfo["x86_cr0"] & 1 == 0:
        return ((seg_selector << 4) + virtual_addr).zero_extend(32)

    seg_selector &= 0x0000FFFF

    segment_selector_val = seg_selector >> 3

    if (
        state.project.simos.name == "Win32"
        and (segment_selector_val == 0x6).is_true()
        and state.project.concrete_target is not None
    ):
        return bad(
            "angr doesn't support Windows Heaven's gate calls "
            "https://rce.co/knockin-on-heavens-gate-dynamic-processor-mode-switching/ \n"
            "Please use the native 32 bit libs (not WoW64) or implement a simprocedure to avoid executing these "
            "instructions"
        )

    # RPL=11 check
    # if state.solver.is_true((seg_selector & 3) != 3):
    #    return bad()

    tiBit = (seg_selector >> 2) & 1
    if (tiBit == 0).is_true():
        # GDT access
        gdt_value = state.solver.eval_one(gdt)
        if gdt_value == 0:
            return ((seg_selector << 16) + virtual_addr).zero_extend(32)

        seg_selector >>= 3  # bit 3 to 15 are the index in the table
        seg_selector = seg_selector.zero_extend(32)

        gdt_limit = gdt[15:0]
        if (seg_selector >= gdt_limit.zero_extend(48)).is_true():
            return bad("index out of range")

        gdt_base = gdt[47:16]
        gdt_base_value = state.solver.eval_one(gdt_base)
        descriptor = state.memory.load(gdt_base_value + seg_selector * 8, 8, endness="Iend_LE")
    else:
        # LDT access
        ldt_value = state.solver.eval_one(ldt)
        if ldt_value == 0:
            return ((seg_selector << 16) + virtual_addr).zero_extend(32)

        seg_selector >>= 3  # bit 3 to 15 are the index in the table
        seg_selector = seg_selector.zero_extend(32)

        ldt_limit = ldt[15:0]
        if (seg_selector >= ldt_limit.zero_extend(48)).is_true():
            return bad("index out of range")

        ldt_base = ldt[47:16]
        state.solver.eval_one(ldt_base)

        ldt_value = state.solver.eval_one(ldt_base)
        descriptor = state.memory.load(ldt_value + seg_selector * 8, 8, endness="Iend_LE")

    present = descriptor[47]
    if (present == 0).is_true():
        return bad("present bit set to 0")

    base = get_segdescr_base(state, descriptor)
    get_segdescr_limit(state, descriptor)

    # When a concrete target is set and memory is read directly from the process sometimes a negative offset
    # from a segment register is used
    # if state.solver.is_true(virtual_addr >= limit) and state.project.concrete_target is None:
    #     return bad("virtual_addr >= limit")

    r = (base + virtual_addr).zero_extend(32)
    l.debug("x86g_use_seg_selector: addr=%s", str(r))

    return r


#
# other amd64 craziness
#

EmNote_NONE = 0
EmWarn_X86_x87exns = 1
EmWarn_X86_x87precision = 2
EmWarn_X86_sseExns = 3
EmWarn_X86_fz = 4
EmWarn_X86_daz = 5
EmWarn_X86_acFlag = 6
EmWarn_PPCexns = 7
EmWarn_PPC64_redir_overflow = 8
EmWarn_PPC64_redir_underflow = 9
EmWarn_S390X_fpext_rounding = 10
EmWarn_S390X_invalid_rounding = 11
EmFail_S390X_stfle = 12
EmFail_S390X_stckf = 13
EmFail_S390X_ecag = 14
EmFail_S390X_pfpo = 15
EmFail_S390X_DFP_insn = 16
EmFail_S390X_fpext = 17
EmFail_S390X_invalid_PFPO_rounding_mode = 18
EmFail_S390X_invalid_PFPO_function = 19


#################
### ARM Flags ###
#################

ARMCondEQ = 0  #   /* equal                         : Z=1 */
ARMCondNE = 1  #   /* not equal                     : Z=0 */
ARMCondHS = 2  #   /* >=u (higher or same)          : C=1 */
ARMCondLO = 3  #   /* <u  (lower)                   : C=0 */
ARMCondMI = 4  #   /* minus (negative)              : N=1 */
ARMCondPL = 5  #   /* plus (zero or +ve)            : N=0 */
ARMCondVS = 6  #   /* overflow                      : V=1 */
ARMCondVC = 7  #   /* no overflow                   : V=0 */
ARMCondHI = 8  #   /* >u   (higher)                 : C=1 && Z=0 */
ARMCondLS = 9  #   /* <=u  (lower or same)          : C=0 || Z=1 */
ARMCondGE = 10  #  /* >=s (signed greater or equal) : N=V */
ARMCondLT = 11  #  /* <s  (signed less than)        : N!=V */
ARMCondGT = 12  #  /* >s  (signed greater)          : Z=0 && N=V */
ARMCondLE = 13  #  /* <=s (signed less or equal)    : Z=1 || N!=V */
ARMCondAL = 14  #  /* always (unconditional)        : 1 */
ARMCondNV = 15  #   /* never (unconditional):        : 0 */

ARMG_CC_OP_COPY = 0  # /* DEP1 = NZCV in 31:28, DEP2 = 0, DEP3 = 0 just copy DEP1 to output */
ARMG_CC_OP_ADD = 1  # /* DEP1 = argL (Rn) =  DEP2 = argR (shifter_op) =  DEP3 = 0 */
ARMG_CC_OP_SUB = 2  # /* DEP1 = argL (Rn) =  DEP2 = argR (shifter_op) =  DEP3 = 0 */
ARMG_CC_OP_ADC = 3  # /* DEP1 = argL (Rn) =  DEP2 = arg2 (shifter_op) =  DEP3 = oldC (in LSB) */
ARMG_CC_OP_SBB = 4  # /* DEP1 = argL (Rn) =  DEP2 = arg2 (shifter_op) =  DEP3 = oldC (in LSB) */
ARMG_CC_OP_LOGIC = 5  # /* DEP1 = result =  DEP2 = shifter_carry_out (in LSB) =  DEP3 = old V flag (in LSB) */
ARMG_CC_OP_MUL = 6  # /* DEP1 = result =  DEP2 = 0 =  DEP3 = oldC:old_V (in bits 1:0) */
ARMG_CC_OP_MULL = 7  # /* DEP1 = resLO32 =  DEP2 = resHI32 =  DEP3 = oldC:old_V (in bits 1:0) */
ARMG_CC_OP_NUMBER = 8

ARMG_CC_SHIFT_N = 31
ARMG_CC_SHIFT_Z = 30
ARMG_CC_SHIFT_C = 29
ARMG_CC_SHIFT_V = 28
ARMG_CC_SHIFT_Q = 27

ARMG_NBITS = 32


def armg_calculate_flag_n(state, cc_op, cc_dep1, cc_dep2, cc_dep3):
    concrete_op = op_concretize(cc_op)
    flag = None

    if concrete_op == ARMG_CC_OP_COPY:
        flag = claripy.LShR(cc_dep1, ARMG_CC_SHIFT_N) & 1
    elif concrete_op == ARMG_CC_OP_ADD:
        res = cc_dep1 + cc_dep2
        flag = claripy.LShR(res, 31)
    elif concrete_op == ARMG_CC_OP_SUB:
        res = cc_dep1 - cc_dep2
        flag = claripy.LShR(res, 31)
    elif concrete_op == ARMG_CC_OP_ADC:
        res = cc_dep1 + cc_dep2 + cc_dep3
        flag = claripy.LShR(res, 31)
    elif concrete_op == ARMG_CC_OP_SBB:
        res = cc_dep1 - cc_dep2 - (cc_dep3 ^ 1)
        flag = claripy.LShR(res, 31)
    elif concrete_op in (ARMG_CC_OP_LOGIC, ARMG_CC_OP_MUL):
        flag = claripy.LShR(cc_dep1, 31)
    elif concrete_op == ARMG_CC_OP_MULL:
        flag = claripy.LShR(cc_dep2, 31)

    if flag is not None:
        return flag
    l.error("Unknown cc_op %s (armg_calculate_flag_n)", cc_op)
    raise SimCCallError(f"Unknown cc_op {cc_op}")


def arm_zerobit(state, x):
    return calc_zerobit(x).zero_extend(31)


def armg_calculate_flag_z(state, cc_op, cc_dep1, cc_dep2, cc_dep3):
    concrete_op = op_concretize(cc_op)
    flag = None

    if concrete_op == ARMG_CC_OP_COPY:
        flag = claripy.LShR(cc_dep1, ARMG_CC_SHIFT_Z) & 1
    elif concrete_op == ARMG_CC_OP_ADD:
        res = cc_dep1 + cc_dep2
        flag = arm_zerobit(state, res)
    elif concrete_op == ARMG_CC_OP_SUB:
        res = cc_dep1 - cc_dep2
        flag = arm_zerobit(state, res)
    elif concrete_op == ARMG_CC_OP_ADC:
        res = cc_dep1 + cc_dep2 + cc_dep3
        flag = arm_zerobit(state, res)
    elif concrete_op == ARMG_CC_OP_SBB:
        res = cc_dep1 - cc_dep2 - (cc_dep3 ^ 1)
        flag = arm_zerobit(state, res)
    elif concrete_op in (ARMG_CC_OP_LOGIC, ARMG_CC_OP_MUL):
        flag = arm_zerobit(state, cc_dep1)
    elif concrete_op == ARMG_CC_OP_MULL:
        flag = arm_zerobit(state, cc_dep1 | cc_dep2)

    if flag is not None:
        return flag

    l.error("Unknown cc_op %s (armg_calculate_flag_z)", concrete_op)
    raise SimCCallError(f"Unknown cc_op {concrete_op}")


def armg_calculate_flag_c(state, cc_op, cc_dep1, cc_dep2, cc_dep3):
    concrete_op = op_concretize(cc_op)
    flag = None

    if concrete_op == ARMG_CC_OP_COPY:
        flag = claripy.LShR(cc_dep1, ARMG_CC_SHIFT_C) & 1
    elif concrete_op == ARMG_CC_OP_ADD:
        res = cc_dep1 + cc_dep2
        flag = boolean_extend(claripy.ULT, res, cc_dep1, 32)
    elif concrete_op == ARMG_CC_OP_SUB:
        flag = boolean_extend(claripy.UGE, cc_dep1, cc_dep2, 32)
    elif concrete_op == ARMG_CC_OP_ADC:
        res = cc_dep1 + cc_dep2 + cc_dep3
        flag = claripy.If(
            cc_dep3 != 0, boolean_extend(claripy.ULE, res, cc_dep1, 32), boolean_extend(claripy.ULT, res, cc_dep1, 32)
        )
    elif concrete_op == ARMG_CC_OP_SBB:
        flag = claripy.If(
            cc_dep3 != 0,
            boolean_extend(claripy.UGE, cc_dep1, cc_dep2, 32),
            boolean_extend(claripy.UGT, cc_dep1, cc_dep2, 32),
        )
    elif concrete_op == ARMG_CC_OP_LOGIC:
        flag = cc_dep2
    elif concrete_op in (ARMG_CC_OP_MUL, ARMG_CC_OP_MULL):
        flag = (claripy.LShR(cc_dep3, 1)) & 1

    if flag is not None:
        return flag

    l.error("Unknown cc_op %s (armg_calculate_flag_c)", cc_op)
    raise SimCCallError(f"Unknown cc_op {cc_op}")


def armg_calculate_flag_v(state, cc_op, cc_dep1, cc_dep2, cc_dep3):
    concrete_op = op_concretize(cc_op)
    flag = None

    if concrete_op == ARMG_CC_OP_COPY:
        flag = claripy.LShR(cc_dep1, ARMG_CC_SHIFT_V) & 1
    elif concrete_op == ARMG_CC_OP_ADD:
        res = cc_dep1 + cc_dep2
        v = (res ^ cc_dep1) & (res ^ cc_dep2)
        flag = claripy.LShR(v, 31)
    elif concrete_op == ARMG_CC_OP_SUB:
        res = cc_dep1 - cc_dep2
        v = (cc_dep1 ^ cc_dep2) & (cc_dep1 ^ res)
        flag = claripy.LShR(v, 31)
    elif concrete_op == ARMG_CC_OP_ADC:
        res = cc_dep1 + cc_dep2 + cc_dep3
        v = (res ^ cc_dep1) & (res ^ cc_dep2)
        flag = claripy.LShR(v, 31)
    elif concrete_op == ARMG_CC_OP_SBB:
        res = cc_dep1 - cc_dep2 - (cc_dep3 ^ 1)
        v = (cc_dep1 ^ cc_dep2) & (cc_dep1 ^ res)
        flag = claripy.LShR(v, 31)
    elif concrete_op == ARMG_CC_OP_LOGIC:
        flag = cc_dep3
    elif concrete_op in (ARMG_CC_OP_MUL, ARMG_CC_OP_MULL):
        flag = cc_dep3 & 1

    if flag is not None:
        return flag

    l.error("Unknown cc_op %s (armg_calculate_flag_v)", cc_op)
    raise SimCCallError(f"Unknown cc_op {cc_op}")


def armg_calculate_flags_nzcv(state, cc_op, cc_dep1, cc_dep2, cc_dep3):
    # NOTE: adding constraints afterwards works here *only* because the constraints are actually useless, because we
    # require cc_op to be unique. If we didn't, we'd need to pass the constraints into any functions called after the
    # constraints were created.
    n = armg_calculate_flag_n(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
    z = armg_calculate_flag_z(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
    c = armg_calculate_flag_c(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
    v = armg_calculate_flag_v(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
    vec = [
        (ARMG_CC_SHIFT_N, claripy.Extract(0, 0, n)),
        (ARMG_CC_SHIFT_Z, claripy.Extract(0, 0, z)),
        (ARMG_CC_SHIFT_C, claripy.Extract(0, 0, c)),
        (ARMG_CC_SHIFT_V, claripy.Extract(0, 0, v)),
    ]
    return _concat_flags(ARMG_NBITS, vec)


def armg_calculate_condition(state, cond_n_op, cc_dep1, cc_dep2, cc_dep3):
    concrete_cond_n_op = op_concretize(cond_n_op)

    cond = concrete_cond_n_op >> 4
    cc_op = concrete_cond_n_op & 0xF
    inv = cond & 1

    concrete_cond = op_concretize(cond)
    flag = None

    # NOTE: adding constraints afterwards works here *only* because the constraints are actually useless, because we
    # require cc_op to be unique. If we didn't, we'd need to pass the constraints into any functions called after the
    # constraints were created.

    if concrete_cond == ARMCondAL:
        flag = claripy.BVV(1, 32)
    elif concrete_cond in [ARMCondEQ, ARMCondNE]:
        zf = armg_calculate_flag_z(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ zf
    elif concrete_cond in [ARMCondHS, ARMCondLO]:
        cf = armg_calculate_flag_c(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ cf
    elif concrete_cond in [ARMCondMI, ARMCondPL]:
        nf = armg_calculate_flag_n(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ nf
    elif concrete_cond in [ARMCondVS, ARMCondVC]:
        vf = armg_calculate_flag_v(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ vf
    elif concrete_cond in [ARMCondHI, ARMCondLS]:
        cf = armg_calculate_flag_c(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        zf = armg_calculate_flag_z(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ (cf & ~zf)
    elif concrete_cond in [ARMCondGE, ARMCondLT]:
        nf = armg_calculate_flag_n(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        vf = armg_calculate_flag_v(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ (1 & ~(nf ^ vf))
    elif concrete_cond in [ARMCondGT, ARMCondLE]:
        nf = armg_calculate_flag_n(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        vf = armg_calculate_flag_v(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        zf = armg_calculate_flag_z(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ (1 & ~(zf | (nf ^ vf)))

    if flag is not None:
        return flag

    l.error("Unrecognized condition %d in armg_calculate_condition", concrete_cond)
    raise SimCCallError(f"Unrecognized condition {concrete_cond} in armg_calculate_condition")


ARM64G_CC_SHIFT_N = 31
ARM64G_CC_SHIFT_Z = 30
ARM64G_CC_SHIFT_C = 29
ARM64G_CC_SHIFT_V = 28

ARM64G_CC_OP_COPY = 0  # /* DEP1 = NZCV in 31:28, DEP2 = 0, DEP3 = 0 just copy DEP1 to output */
ARM64G_CC_OP_ADD32 = 1  # /* DEP1 = argL (Rn), DEP2 = argR (shifter_op), DEP3 = 0 */
ARM64G_CC_OP_ADD64 = 2  # /* DEP1 = argL (Rn), DEP2 = argR (shifter_op), DEP3 = 0 */
ARM64G_CC_OP_SUB32 = 3  # /* DEP1 = argL (Rn), DEP2 = argR (shifter_op), DEP3 = 0 */
ARM64G_CC_OP_SUB64 = 4  # /* DEP1 = argL (Rn), DEP2 = argR (shifter_op), DEP3 = 0 */
ARM64G_CC_OP_ADC32 = 5  # /* DEP1 = argL (Rn), DEP2 = arg2 (shifter_op), DEP3 = oldC (in LSB) */
ARM64G_CC_OP_ADC64 = 6  # /* DEP1 = argL (Rn), DEP2 = arg2 (shifter_op), DEP3 = oldC (in LSB) */
ARM64G_CC_OP_SBC32 = 7  # /* DEP1 = argL (Rn), DEP2 = arg2 (shifter_op), DEP3 = oldC (in LSB) */
ARM64G_CC_OP_SBC64 = 8  # /* DEP1 = argL (Rn), DEP2 = arg2 (shifter_op), DEP3 = oldC (in LSB) */
ARM64G_CC_OP_LOGIC32 = 9  # /* DEP1 = result, DEP2 = 0, DEP3 = 0 */
ARM64G_CC_OP_LOGIC64 = 10  # /* DEP1 = result, DEP2 = 0, DEP3 = 0 */
ARM64G_CC_OP_NUMBER = 11  #

ARM64CondEQ = 0  # /* equal                         : Z=1 */
ARM64CondNE = 1  # /* not equal                     : Z=0 */
ARM64CondCS = 2  # /* >=u (higher or same) (aka HS) : C=1 */
ARM64CondCC = 3  # /* <u  (lower)          (aka LO) : C=0 */
ARM64CondMI = 4  # /* minus (negative)              : N=1 */
ARM64CondPL = 5  # /* plus (zero or +ve)            : N=0 */
ARM64CondVS = 6  # /* overflow                      : V=1 */
ARM64CondVC = 7  # /* no overflow                   : V=0 */
ARM64CondHI = 8  # /* >u   (higher)                 : C=1 && Z=0 */
ARM64CondLS = 9  # /* <=u  (lower or same)          : C=0 || Z=1 */
ARM64CondGE = 10  # /* >=s (signed greater or equal) : N=V */
ARM64CondLT = 11  # /* <s  (signed less than)        : N!=V */
ARM64CondGT = 12  # /* >s  (signed greater)          : Z=0 && N=V */
ARM64CondLE = 13  # /* <=s (signed less or equal)    : Z=1 || N!=V */
ARM64CondAL = 14  # /* always (unconditional)        : 1 */
ARM64CondNV = 15  # /* always (unconditional)        : 1 */

ARM64G_NBITS = 64


def arm64g_calculate_flag_n(state, cc_op, cc_dep1, cc_dep2, cc_dep3):
    concrete_op = op_concretize(cc_op)
    flag = None

    cc_dep1, cc_dep2, cc_dep3 = arm64g_32bit_truncate_operands(concrete_op, cc_dep1, cc_dep2, cc_dep3)

    if concrete_op == ARM64G_CC_OP_COPY:
        flag = claripy.LShR(cc_dep1, ARM64G_CC_SHIFT_N) & 1
    elif concrete_op == ARM64G_CC_OP_ADD32:
        res = cc_dep1 + cc_dep2
        flag = claripy.LShR(res, 31)
    elif concrete_op == ARM64G_CC_OP_ADD64:
        res = cc_dep1 + cc_dep2
        flag = claripy.LShR(res, 63)
    elif concrete_op == ARM64G_CC_OP_SUB32:
        res = cc_dep1 - cc_dep2
        flag = claripy.LShR(res, 31)
    elif concrete_op == ARM64G_CC_OP_SUB64:
        res = cc_dep1 - cc_dep2
        flag = claripy.LShR(res, 63)
    elif concrete_op == ARM64G_CC_OP_ADC32:
        res = cc_dep1 + cc_dep2 + cc_dep3
        flag = claripy.LShR(res, 31)
    elif concrete_op == ARM64G_CC_OP_ADC64:
        res = cc_dep1 + cc_dep2 + cc_dep3
        flag = claripy.LShR(res, 63)
    elif concrete_op == ARM64G_CC_OP_SBC32:
        res = cc_dep1 - cc_dep2 - (cc_dep3 ^ 1)
        flag = claripy.LShR(res, 31)
    elif concrete_op == ARM64G_CC_OP_SBC64:
        res = cc_dep1 - cc_dep2 - (cc_dep3 ^ 1)
        flag = claripy.LShR(res, 63)
    elif concrete_op == ARM64G_CC_OP_LOGIC32:
        flag = claripy.LShR(cc_dep1, 31)
    elif concrete_op == ARM64G_CC_OP_LOGIC64:
        flag = claripy.LShR(cc_dep1, 63)

    if flag is not None:
        if len(flag) == 32:
            flag = flag.zero_extend(32)
        return flag
    l.error("Unknown cc_op %s (arm64g_calculate_flag_n)", cc_op)
    raise SimCCallError(f"Unknown cc_op {cc_op}")


def arm64_zerobit(state, x):
    return calc_zerobit(x).zero_extend(63)


def u64_to_u32(n):
    return n[31:0]


def arm64g_32bit_truncate_operands(cc_op, cc_dep1, cc_dep2, cc_dep3):
    # Truncate operands if in 32-bit mode
    if cc_op in {ARM64G_CC_OP_ADD32, ARM64G_CC_OP_SUB32}:
        cc_dep1 = u64_to_u32(cc_dep1)
        cc_dep2 = u64_to_u32(cc_dep2)
    elif cc_op in {ARM64G_CC_OP_ADC32, ARM64G_CC_OP_SBC32}:
        cc_dep1 = u64_to_u32(cc_dep1)
        cc_dep2 = u64_to_u32(cc_dep2)
        cc_dep3 = u64_to_u32(cc_dep3)
    elif cc_op == ARM64G_CC_OP_LOGIC32:
        cc_dep1 = u64_to_u32(cc_dep1)
    return cc_dep1, cc_dep2, cc_dep3


def arm64g_calculate_flag_z(state, cc_op, cc_dep1, cc_dep2, cc_dep3):
    concrete_op = op_concretize(cc_op)
    flag = None

    cc_dep1, cc_dep2, cc_dep3 = arm64g_32bit_truncate_operands(concrete_op, cc_dep1, cc_dep2, cc_dep3)

    if concrete_op == ARM64G_CC_OP_COPY:
        flag = claripy.LShR(cc_dep1, ARM64G_CC_SHIFT_Z) & 1
    elif concrete_op in (ARM64G_CC_OP_ADD32, ARM64G_CC_OP_ADD64):
        res = cc_dep1 + cc_dep2
        flag = arm64_zerobit(state, res)
    elif concrete_op in (ARM64G_CC_OP_SUB32, ARM64G_CC_OP_SUB64):
        res = cc_dep1 - cc_dep2
        flag = arm64_zerobit(state, res)
    elif concrete_op in (ARM64G_CC_OP_ADC32, ARM64G_CC_OP_ADC64):
        res = cc_dep1 + cc_dep2 + cc_dep3
        flag = arm64_zerobit(state, res)
    elif concrete_op in (ARM64G_CC_OP_SBC32, ARM64G_CC_OP_SBC64):
        res = cc_dep1 - cc_dep2 - (cc_dep3 ^ 1)
        flag = arm64_zerobit(state, res)
    elif concrete_op in (ARM64G_CC_OP_LOGIC32, ARM64G_CC_OP_LOGIC64):
        flag = arm64_zerobit(state, cc_dep1)

    if flag is not None:
        if len(flag) == 32:
            flag = flag.zero_extend(32)
        return flag

    l.error("Unknown cc_op %s (arm64g_calculate_flag_z)", concrete_op)
    raise SimCCallError(f"Unknown cc_op {concrete_op}")


def arm64g_calculate_flag_c(state, cc_op, cc_dep1, cc_dep2, cc_dep3):
    concrete_op = op_concretize(cc_op)
    flag = None

    cc_dep1, cc_dep2, cc_dep3 = arm64g_32bit_truncate_operands(concrete_op, cc_dep1, cc_dep2, cc_dep3)

    if concrete_op == ARM64G_CC_OP_COPY:
        flag = claripy.LShR(cc_dep1, ARM64G_CC_SHIFT_C) & 1
    elif concrete_op in (ARM64G_CC_OP_ADD32, ARM64G_CC_OP_ADD64):
        res = cc_dep1 + cc_dep2
        flag = boolean_extend(claripy.ULT, res, cc_dep1, 64)
    elif concrete_op in (ARM64G_CC_OP_SUB32, ARM64G_CC_OP_SUB64):
        flag = boolean_extend(claripy.UGE, cc_dep1, cc_dep2, 64)
    elif concrete_op in (ARM64G_CC_OP_ADC32, ARM64G_CC_OP_ADC64):
        res = cc_dep1 + cc_dep2 + cc_dep3
        flag = claripy.If(
            cc_dep2 != 0, boolean_extend(claripy.ULE, res, cc_dep1, 64), boolean_extend(claripy.ULT, res, cc_dep1, 64)
        )
    elif concrete_op in (ARM64G_CC_OP_SBC32, ARM64G_CC_OP_SBC64):
        flag = claripy.If(
            cc_dep2 != 0,
            boolean_extend(claripy.UGE, cc_dep1, cc_dep2, 64),
            boolean_extend(claripy.UGT, cc_dep1, cc_dep2, 64),
        )
    elif concrete_op in (ARM64G_CC_OP_LOGIC32, ARM64G_CC_OP_LOGIC64):
        flag = claripy.BVV(0, 64)  # C after logic is zero on arm64

    if flag is not None:
        return flag

    l.error("Unknown cc_op %s (arm64g_calculate_flag_c)", cc_op)
    raise SimCCallError(f"Unknown cc_op {cc_op}")


def arm64g_calculate_flag_v(state, cc_op, cc_dep1, cc_dep2, cc_dep3):
    concrete_op = op_concretize(cc_op)
    flag = None

    cc_dep1, cc_dep2, cc_dep3 = arm64g_32bit_truncate_operands(concrete_op, cc_dep1, cc_dep2, cc_dep3)

    if concrete_op == ARM64G_CC_OP_COPY:
        flag = claripy.LShR(cc_dep1, ARM64G_CC_SHIFT_V) & 1
    elif concrete_op == ARM64G_CC_OP_ADD32:
        cc_dep1 = cc_dep1[31:0]
        cc_dep2 = cc_dep2[31:0]
        res = cc_dep1 + cc_dep2
        v = (res ^ cc_dep1) & (res ^ cc_dep2)
        flag = claripy.LShR(v, 31).zero_extend(32)
    elif concrete_op == ARM64G_CC_OP_ADD64:
        res = cc_dep1 + cc_dep2
        v = (res ^ cc_dep1) & (res ^ cc_dep2)
        flag = claripy.LShR(v, 63)
    elif concrete_op == ARM64G_CC_OP_SUB32:
        cc_dep1 = cc_dep1[31:0]
        cc_dep2 = cc_dep2[31:0]
        res = cc_dep1 - cc_dep2
        v = (cc_dep1 ^ cc_dep2) & (cc_dep1 ^ res)
        flag = claripy.LShR(v, 31).zero_extend(32)
    elif concrete_op == ARM64G_CC_OP_SUB64:
        res = cc_dep1 - cc_dep2
        v = (cc_dep1 ^ cc_dep2) & (cc_dep1 ^ res)
        flag = claripy.LShR(v, 63)
    elif concrete_op == ARM64G_CC_OP_ADC32:
        cc_dep1 = cc_dep1[31:0]
        cc_dep2 = cc_dep2[31:0]
        res = cc_dep1 + cc_dep2 + cc_dep3
        v = (res ^ cc_dep1) & (res ^ cc_dep2)
        flag = claripy.LShR(v, 31).zero_extend(32)
    elif concrete_op == ARM64G_CC_OP_ADC64:
        res = cc_dep1 + cc_dep2 + cc_dep3
        v = (res ^ cc_dep1) & (res ^ cc_dep2)
        flag = claripy.LShR(v, 63)
    elif concrete_op == ARM64G_CC_OP_SBC32:
        cc_dep1 = cc_dep1[31:0]
        cc_dep2 = cc_dep2[31:0]
        res = cc_dep1 - cc_dep2 - (cc_dep3 ^ 1)
        v = (cc_dep1 ^ cc_dep2) & (cc_dep1 ^ res)
        flag = claripy.LShR(v, 31).zero_extend(32)
    elif concrete_op == ARM64G_CC_OP_SBC64:
        res = cc_dep1 - cc_dep2 - (cc_dep3 ^ 1)
        v = (cc_dep1 ^ cc_dep2) & (cc_dep1 ^ res)
        flag = claripy.LShR(v, 63)
    elif concrete_op in (ARM64G_CC_OP_LOGIC32, ARM64G_CC_OP_LOGIC64):
        flag = claripy.BVV(0, 64)

    if flag is not None:
        return flag

    l.error("Unknown cc_op %s (arm64g_calculate_flag_v)", cc_op)
    raise SimCCallError(f"Unknown cc_op {cc_op}")


def arm64g_calculate_flags_nzcv(state, cc_op, cc_dep1, cc_dep2, cc_dep3):
    # NOTE: adding constraints afterwards works here *only* because the constraints are actually useless, because we
    # require cc_op to be unique. If we didn't, we'd need to pass the constraints into any functions called after the
    # constraints were created.
    n = arm64g_calculate_flag_n(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
    z = arm64g_calculate_flag_z(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
    c = arm64g_calculate_flag_c(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
    v = arm64g_calculate_flag_v(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
    vec = [
        (ARM64G_CC_SHIFT_N, claripy.Extract(0, 0, n)),
        (ARM64G_CC_SHIFT_Z, claripy.Extract(0, 0, z)),
        (ARM64G_CC_SHIFT_C, claripy.Extract(0, 0, c)),
        (ARM64G_CC_SHIFT_V, claripy.Extract(0, 0, v)),
    ]
    return _concat_flags(ARM64G_NBITS, vec)


def arm64g_calculate_condition(state, cond_n_op, cc_dep1, cc_dep2, cc_dep3):
    concretize_cond_n_op = op_concretize(cond_n_op)
    cond = concretize_cond_n_op >> 4
    cc_op = concretize_cond_n_op & 0xF
    inv = cond & 1

    concrete_cond = cond
    flag = None

    if concrete_cond in (ARM64CondAL, ARM64CondNV):
        flag = claripy.BVV(1, 64)
    elif concrete_cond in (ARM64CondEQ, ARM64CondNE):
        zf = arm64g_calculate_flag_z(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ zf
    elif concrete_cond in (ARM64CondCS, ARM64CondCC):
        cf = arm64g_calculate_flag_c(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ cf
    elif concrete_cond in (ARM64CondMI, ARM64CondPL):
        nf = arm64g_calculate_flag_n(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ nf
    elif concrete_cond in (ARM64CondVS, ARM64CondVC):
        vf = arm64g_calculate_flag_v(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ vf
    elif concrete_cond in (ARM64CondHI, ARM64CondLS):
        cf = arm64g_calculate_flag_c(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        zf = arm64g_calculate_flag_z(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ (1 & (cf & ~zf))
    elif concrete_cond in (ARM64CondGE, ARM64CondLT):
        nf = arm64g_calculate_flag_n(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        vf = arm64g_calculate_flag_v(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ (1 & ~(nf ^ vf))
    elif concrete_cond in (ARM64CondGT, ARM64CondLE):
        nf = arm64g_calculate_flag_n(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        vf = arm64g_calculate_flag_v(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        zf = arm64g_calculate_flag_z(state, cc_op, cc_dep1, cc_dep2, cc_dep3)
        flag = inv ^ (1 & ~(zf | (nf ^ vf)))

    if flag is not None:
        return flag

    l.error("Unrecognized condition %d in arm64g_calculate_condition", concrete_cond)
    raise SimCCallError(f"Unrecognized condition {concrete_cond} in arm64g_calculate_condition")


#
# Some helpers
#


def _get_flags(state) -> claripy.ast.bv.BV:
    cc_op = strip_simaction(state.regs.cc_op)
    cc_dep1 = strip_simaction(state.regs.cc_dep1)
    cc_dep2 = strip_simaction(state.regs.cc_dep2)
    cc_ndep = strip_simaction(state.regs.cc_ndep)
    if state.arch.name == "X86":
        func = x86g_calculate_eflags_all
    elif state.arch.name == "AMD64":
        func = amd64g_calculate_rflags_all
    elif is_arm_arch(state.arch):
        func = armg_calculate_flags_nzcv
    elif state.arch.name == "AARCH64":
        func = arm64g_calculate_flags_nzcv
    else:
        l.warning("No such thing as a flags register for arch %s", state.arch.name)
        return None
    try:
        return func(state, cc_op, cc_dep1, cc_dep2, cc_ndep)
    except CCallMultivaluedException as e:
        cases, to_replace = e.args
        args = [cc_op, cc_dep1, cc_dep2, cc_ndep]
        try:
            i = args.index(to_replace)
        except ValueError as ve:
            raise errors.UnsupportedCCallError("Trying to concretize a value which is not an argument") from ve
        return claripy.ite_cases([(case, func(state, *args[:i], value_, *args[i + 1 :])) for case, value_ in cases], 0)


def _concat_flags(nbits, flags_vec):
    """
    Concatenate different flag BVs to a single BV. Currently used for ARM, X86
    and AMD64.
    :param nbits    : platform size in bits.
    :param flags_vec: vector of flag BVs and their offset in the resulting BV.

    :type nbits     : int
    :type flags_vec : list

    :return         : the resulting flag BV.
    :rtype          : claripy.BVV
    """

    result = claripy.BVV(0, 0)
    for offset, bit in flags_vec:
        current_position = nbits - 1 - result.length
        result = result.concat(claripy.BVV(0, current_position - offset), bit)
    return result.concat(claripy.BVV(0, nbits - result.length))


def _get_nbits(cc_str):
    nbits = None
    if cc_str.endswith("B"):
        nbits = 8
    elif cc_str.endswith("W"):
        nbits = 16
    elif cc_str.endswith("L"):
        nbits = 32
    elif cc_str.endswith("Q"):
        nbits = 64
    elif cc_str.endswith("32"):
        nbits = 32
    elif cc_str.endswith("64"):
        nbits = 64
    return nbits
