Search code examples
c++x86-64icc

Reading CF, PF, ZF, SF, OF


I am writing a virtual machine for my own assembly language, I want to be able to set the carry, parity, zero, sign and overflowflags as they are set in the x86-64 architecture, when I perform operations such as addition.

Notes:

  • I am using Microsoft Visual C++ 2015 & Intel C++ Compiler 16.0
  • I am compiling as a Win64 application.
  • My virtual machine (currently) only does arithmetic on 8-bit integers
  • I'm not (currently) interested in any other flags (e.g. AF)

My current solution is using the following function:

void update_flags(uint16_t input)
{
    Registers::flags.carry = (input > UINT8_MAX);
    Registers::flags.zero = (input == 0);
    Registers::flags.sign = (input < 0);
    Registers::flags.overflow = (int16_t(input) > INT8_MAX || int16_t(input) < INT8_MIN);

    // I am assuming that overflow is handled by trunctation
    uint8_t input8 = uint8_t(input);
    // The parity flag
    int ones = 0;
    for (int i = 0; i < 8; ++i)
        if (input8 & (1 << i) != 0) ++ones;

    Registers::flags.parity = (ones % 2 == 0);
}

Which for addition, I would use as follows:

uint8_t a, b;
update_flags(uint16_t(a) + uint16_t(b));
uint8_t c = a + b;

EDIT: To clarify, I want to know if there is a more efficient/neat way of doing this (such as by accessing RFLAGS directly) Also my code may not work for other operations (e.g. multiplication)

EDIT 2 I have updated my code now to this:

void update_flags(uint32_t result)
{
    Registers::flags.carry = (result > UINT8_MAX);
    Registers::flags.zero = (result == 0);
    Registers::flags.sign = (int32_t(result) < 0);
    Registers::flags.overflow = (int32_t(result) > INT8_MAX || int32_t(result) < INT8_MIN);
    Registers::flags.parity = (_mm_popcnt_u32(uint8_t(result)) % 2 == 0);
}

One more question, will my code for the carry flag work properly?, I also want it to be set correctly for "borrows" that occur during subtraction.

Note: The assembly language I am virtualising is of my own design, meant to be simple and based of Intel's implementation of x86-64 (i.e. Intel64), and so I would like these flags to behave in mostly the same way.


Solution

  • I appear to have solved the problem, by splitting the arguments to update flags into an unsigned and signed result as follows:

    void update_flags(int16_t unsigned_result, int16_t signed_result)
    {
        Registers::flags.zero = unsigned_result == 0;
        Registers::flags.sign = signed_result < 0;
        Registers::flags.carry = unsigned_result < 0 || unsigned_result > UINT8_MAX;
        Registers::flags.overflow = signed_result < INT8_MIN || signed_result > INT8_MAX
    }
    

    For addition (which should produce the correct result for both signed & unsigned inputs) I would do the following:

    int8_t a, b;
    int16_t signed_result = int16_t(a) + int16_t(b);
    int16_t unsigned_result = int16_t(uint8_t(a)) + int16_t(uint8_t(b));
    update_flags(unsigned_result, signed_result);
    int8_t c = a + b;
    

    And signed multiplication I would do the following:

    int8_t a, b;
    int16_t result = int16_t(a) * int16_t(b);
    update_flags(result, result);
    int8_t c = a * b;
    

    And so on for the other operations that update the flags

    Note: I am assuming here that int16_t(a) sign extends, and int16_t(uint8_t(a)) zero extends.

    I have also decided against having a parity flag, my _mm_popcnt_u32 solution should work if I change my mind later..

    P.S. Thank you to everyone who responded, it was very helpful. Also if anyone can spot any mistakes in my code, that would be appreciated.