=====================
== Rase's basement ==
=====================
Simple and functional is beautiful

Intel 8086 - Simulating Sub Cmp Add Instructions and the Flags Register

The assignment

In the Performance-Aware Programming course by Casey Muratori, he gave a homework assignment (listing_0046) where you have to simulate the ADD/SUB/CMP instructions.

In the previous blog posts, I handled the immediate move instruction and register to register moves. This was easy because the MOV instruction does not modify the FLAGS register.

The instructions handled in this blog post require us to modify the zero flag (ZF) and sign flag (SF) bits from the FLAGS register.

The instructions we’re going to be simulating are the following:

; ========================================================================
; LISTING 46
; ========================================================================

bits 16

mov bx, -4093
mov cx, 3841
sub bx, cx

mov sp, 998
mov bp, 999
cmp bp, sp

add bp, 1027
sub bp, 2026

What is the FLAGS register

The FLAGS register is a register that holds the state of a CPU. In practice, the flags are stored in a collection of bits where each bit has its own meaning.

You can extract the bits you care about with a bit mask.

For example, if you want to know the state of the sign flag (SF), you use the mask 0x0080.

You use this mask by performing the bit AND operation on the collection of bits.

let SF_FLAG = FLAGS_REGISTER & 0x0080;
match SF_FLAG {
	// check if bit is 1 (set) or 0 (unset)
	...
}

I’m now going to explain what the flags do that we’re going to be implementing:

Sign flag

Sign flag (SF) gets set to 1 if the result of an operation is negative.

For example:

mov ecx, 20
sub ecx, 25 ; result is -5 so the sign flag gets set to 1

Or:

let result = a - b;

// jns 0x4200 (conditional jump is SF is set to 0, non negative)
if result < 0 { 
		// Sign flag was set, did not hit the conditional jump.
    println!("The result is negative: {}", result);
} else {
		// Scope address: 0x4200.
		// This scope gets hit if the sign flag (SF) was 0 aka not set.
    println!("The result is non-negative: {}", result);
}

Zero flag

ZF (Zero flag) gets set to 1 if the result of an operation is 0.

mov ecx, 30
sub ecx, 30 ; zero flag gets set to 1 because result is 0.

Example:

// Pretending x is in rcx.
// cmp rcx - 0
// jne 0x9000 (conditional jump that checks if ZF == 0 (not set)
if x == 0 {
	...
} else {
	// 0x9000
  ...
}

Implementation

For the add/sub/cmp we need to introduce the concept of a FLAGS register. Our current code has no understanding of what a FLAGS register is and that it should set various flags inside that FLAGS register depending on the instruction.

In the previous post, I created a compile-time array that contains all the registers. I then generated a Register struct from each one and maintained the register state that way. Now I’m going to do the same thing for the FLAGS register.

// The compile-time array for registers.
const REGISTERS: [&str; 16] = [
    "ax", "cx", "dx", "bx", "sp", "bp", "si", "di",
    "al", "cl", "dl", "bl", "ah", "ch", "dh", "bh",
];

Let’s create the array for the FLAGS registers.

“Take the registers from this page, and add them into a compile-time rust array. https://en.wikipedia.org/wiki/FLAGS_register

ChatGPT: Sure. Here’s a ….

// Flags registers that will be used to determine the state of the FLAGS registers.
const FLAGS_REGISTERS: [&str; 2] = [
    // "cf", // carry flag
    // "reserved1",
    // "pf", // parity flag
    // "reserved2",
    // "af", // auxiliary carry flag
    // "reserved3",
    "zf", // zero flag
    "sf", // sign flag
    // "tf", // trap flag
    // "if", // interrupt enable flag
    // "df", // direction flag
    // "of", // overflow flag
    // "iopl", // i/o privilege level
    // "nt", // nested task flag
    // "reserved4",
    // "rf", // resume flag
    // "vm", // virtual 8086 mode flag
    // "ac", // alignment check
    // "vif", // virtual interrupt flag
    // "vip", // virtual interrupt pending
    // "id", // able to use cpuid instruction
    // "reserved5",
    // "reserved6",
];

I see ZF and SF are there and that’s enough for me for now. I’ll have to check if the other stuff is correct later.

I’m considering that at the start of the program, we construct these flags into a Vec<FlagRegister>, with FlagRegister being a struct.

pub struct FlagRegister {
    pub register: &'static str,
    pub is_set: bool,
}
pub fn construct_flags_registers() -> [FlagRegister; 2] {
		// Added masks in there in case we for some reason need them in the future.
    let flags = [
        // FlagRegister { register: "CF", is_set: false, mask: 0x0001 },
        // FlagRegister { register: "PF", is_set: false, mask: 0x0004 },
        // FlagRegister { register: "AF", is_set: false, mask: 0x0010 },
        FlagRegister { register: "ZF", is_set: false, /*mask: 0x0040*/ },
        FlagRegister { register: "SF", is_set: false, /*mask: 0x0080*/ },
        // FlagRegister { register: "TF", is_set: false, mask: 0x0100 },
        // FlagRegister { register: "IF", is_set: false, mask: 0x0200 },
        // FlagRegister { register: "DF", is_set: false, mask: 0x0400 },
        // FlagRegister { register: "OF", is_set: false, mask: 0x0800 },
        // FlagRegister { register: "IOPL", is_set: false, mask: 0x3000 },
        // FlagRegister { register: "NT", is_set: false, mask: 0x4000 },
        // FlagRegister { register: "MD", is_set: false, mask: 0x8000 },
        // FlagRegister { register: "RF", is_set: false, mask: 0x0001_0000 },
        // FlagRegister { register: "VM", is_set: false, mask: 0x0002_0000 },
        // FlagRegister { register: "AC", is_set: false, mask: 0x0004_0000 },
        // FlagRegister { register: "VIF", is_set: false, mask: 0x0008_0000 },
        // FlagRegister { register: "VIP", is_set: false, mask: 0x0010_0000 },
        // FlagRegister { register: "ID", is_set: false, mask: 0x0020_0000 },
        // FlagRegister { register: "AI", is_set: false, mask: 0x8000_0000 },
    ];
    return flags;
}

Right, there it is. ChatGPT generated it for me. The reason it’s returned from a function instead of being a global variable is because I believe that could lead to weird bugs in the future, and the function is very cheap regardless because everything is known at compile time, and this function is only called once.

We call this function right alongside the one where we retrieve the registers.

let mut registers = construct_registers();
let mut flag_registers = construct_flags_registers(); // <---

Now, after we have handled an instruction like mov ax, 5, for example, we need to update the appropriate flags. I’ll have to think about how to do that. Oh… and when do I unset the flags? That’s another question that I completely forgot about.

Okay, we have to start somewhere, so let’s create a function that updates a register’s is_set field from the flag registers.

pub fn set_is_set_for_flag_register(flag: &'static str, flag_registers: &mut [FlagRegister], value: bool) -> () {
    for flag_register in flag_registers.iter_mut() {
        if flag_register.register == flag {
                flag_register.is_set = value;
                return
            }
        }
    panic!("Flag {} not found", flag);
}

Something like this might be okay.

Now, I’ll just create a helper function for this function that automatically sets the values.

pub fn set_flags(destination_value: usize, flag_registers: &mut [FlagRegister]) -> () {
    if destination_value == 0 {
        set_is_set_for_flag_register("ZF", flag_registers, true);
        return
    } else {
        set_is_set_for_flag_register("ZF", flag_registers, false);
    }

    if destination_value < 0 {
        set_is_set_for_flag_register("SF", flag_registers, true);
        return
    } else {
        set_is_set_for_flag_register("SF", flag_registers, false);
    }
}

It’s up to the caller to provide the destination_value correctly.

Let’s put these functions to use.

In the main function, I defined new logic to update the flags. MOV instructions don’t modify the flags, so I added a check for that.

if mnemonic != "mov" {
  if reg_is_dest {
      let reg = get_register_state(&reg_register, &registers);
      set_flags(reg.updated_value, &mut flag_registers);
  } else {
      let rm = get_register_state(&rm_register, &registers);
      set_flags(rm.updated_value, &mut flag_registers);
  }
}

Now, I’ll create a new function that returns all the flags that were set during the last instruction, so I can print them all out.

pub fn get_all_currently_set_flags(flag_registers: &[FlagRegister]) -> Vec<&str> {
    let mut flags: Vec<&str> = Vec::with_capacity(flag_registers.len());
    for flag_register in flag_registers.iter() {
        if flag_register.is_set {
            flags.push(flag_register.register);
        }
    }
    return flags;
}

Now, let’s try the program, does it set the flags correctly on the first try? Let’s try that by printing out the flags.

if reg_is_dest || instruction == ImmediateToRegisterMOV {
    let reg = get_register_state(&reg_register, &registers);
    println!("{} ; {} -> {}, flags: {:?}", formatted_instruction, reg.original_value, reg.updated_value, get_all_currently_set_flags(&flag_registers));
} else {
    let rm = get_register_state(&rm_register, &registers);
    println!("{} ; {} -> {}, flags: {:?}", formatted_instruction, rm.original_value, rm.updated_value, get_all_currently_set_flags(&flag_registers));
}

Then, we run the program:

mov bx, 61443 ; 0 -> 61443, flags: []
mov cx, 3841 ; 0 -> 3841, flags: []
sub bx, cx ; 61443 -> 57602, flags: []
mov sp, 998 ; 0 -> 998, flags: []
mov bp, 999 ; 0 -> 999, flags: []
cmp bp, sp ; 999 -> 999, flags: []
thread 'main' panicked at 'Register not found, this should never happen. Register that was not found was ', src/registers.rs:40:5
stack backtrace:
   0: rust_begin_unwind
             at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/panicking.rs:579:5
   1: core::panicking::panic_fmt
             at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/core/src/panicking.rs:64:14
   2: assembler_8086::registers::get_register_state
             at ./src/registers.rs:40:5
   3: assembler_8086::main
             at ./src/main.rs:358:23
   4: core::ops::function::FnOnce::call_once
             at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Flags are not getting set, and we run into a panic. Great. Nothing works. Let’s debug this. But not today.

The next day, here I am again. I’m going to solve this thing.

Let’s start by setting a breakpoint at the panic.

Untitled

Now I’m going to look at the stack trace to see which part of the main function actually called this function.

Untitled

Line 358 in main.rs… let’s see.

Why is get_register_state called with an empty register?

Untitled

I’m adding a conditional breakpoint at the start of the main loop that stops when the instruction_count is 7 (the instruction the problem appears).

Untitled

Ok. why are we using the reg_register when in the same instruction we initialise reg_immediate with a value?

Untitled

Can you spot the error in the next picture?

Untitled

The let reg = get_register_state(&reg_register, &registers); is called outside the if condition scope. The let rm = get_register_state(&rm_register, &registers) is called from WITHIN the else branch scope.

This leads to the first get_register_state function call to happen even if reg is not the destination. I.e. the reg_register variable is not used.

This was just a silly thing I forgot to move into inside the scope.

Let’s fix it.

Untitled

Right, no more panics, but we are still not setting the flags correctly.

Wait. Why are the last two instructions like that? The roles have been reversed. Why am I moving bp into an immediate value?

} else if memory_mode == RegisterMode {
    if reg_is_dest {
        return format!("{} {}, {}", mnemonic, rm_register, reg_immediate);
    } else {
        return format!("{} {}, {}", mnemonic, reg_immediate, rm_register);
    }

Ah, I see. The branches were accidentally reversed. They should be the other way around.

Fixed.

} else if memory_mode == RegisterMode {
    if reg_is_dest {
        return format!("{} {}, {}", mnemonic, reg_immediate, rm_register);
    } else {
        return format!("{} {}, {}", mnemonic, rm_register, reg_immediate);
    }

The output now looks like this.

Untitled

During the sub bx, cx instruction, the signed bit should be set because even though the value looks like a positive value, it is actually negative, it is just showcased as a positive value because it has the same binary representation as the negative one (-4093).

This means that we have to try a bit harder.

The current set_flags function looks like this:

pub fn set_flags(destination_value: usize, flag_registers: &mut [FlagRegister]) -> () {
    if destination_value == 0 {
        set_is_set_for_flag_register("ZF", flag_registers, true);
        return
    } else {
        set_is_set_for_flag_register("ZF", flag_registers, false);
    }

    if destination_value < 0 {
        set_is_set_for_flag_register("SF", flag_registers, true);
        return
    } else {
        set_is_set_for_flag_register("SF", flag_registers, false);
    }
}

I thought I’d make it work this easily. Think again.

I did some research. To correctly get the numbers negative representation, we have to use **Two’s complement, a mathematical operation to turn a binary number into its negative equivalent. This way we can then check if it’s actually less than 0.

Okay, Wikipedia says we need to do this:

Two’s complement is achieved by:

  • Step 1: starting with the equivalent positive number.
  • Step 2: inverting (or flipping) all bits – changing every 0 to 1, and every 1 to 0;
  • Step 3: adding 1 to the entire inverted number, ignoring any overflow. Failing to ignore overflow will produce the wrong value for the result.

Right.

But do I actually want to convert the number into a negative number or do I just want to check if it’s a negative number?

See, the CPU does not care if the number is negative or not. It stores the number as a regular number. There is however a bit you can check for that will tell you if the number is signed or not. This is called the highest bit.

You get the highest bit from a byte by shifting the bits inside of that byte N times to the right. N is sizeof(number) - 1 so, for example if the number is an 8-bit number, you shift the number to the right 7 times. This will then give you a result of 00000001 or 00000000 depending on if the bit is set to 1 or 0.

This however requires us to know the size of the number. We currently treat the immediate as a usize. This means that we can not differentiate between 8 and 16-bit values. We need to figure out a way to do this correctly.

I’m thinking I do something like this:

fn number_is_signed(value: usize, is_word_size: bool) -> bool {
    let highest_bit = get_highest_bit(value, is_word_size);
    return highest_bit == 1
}

fn get_highest_bit(value: usize, is_word_size: bool) -> usize {
    if is_word_size {
        return value >> 15;
    } else {
        return value >> 7;
    }
}

Then, I just modified set_flags to use these new functions.

pub fn set_flags(destination_value: usize, flag_registers: &mut [FlagRegister], is_word_size: bool) -> () {
    if destination_value == 0 {
        set_is_set_for_flag_register("ZF", flag_registers, true);
        return
    } else {
        set_is_set_for_flag_register("ZF", flag_registers, false);
    }

    if number_is_signed(destination_value, is_word_size) { // ADDED
        set_is_set_for_flag_register("SF", flag_registers, true);
        return
    } else {
        set_is_set_for_flag_register("SF", flag_registers, false);
        return
    }
}

Now the output looks like this:

Untitled

The zero flag is correctly set. Also, the sign flag is set correctly for the sub bx, cx instruction, but it doesn’t get unset after that in the MOV instructions.

I think we’re almost ready here. We just need to correctly unset the flags during MOV instructions because MOV instructions don’t modify the FLAGS register.

So, let’s make a function that clears the FLAGS register.

pub fn clear_flags_registers(flag_registers: &mut [FlagRegister]) -> () {
    for flag_register in flag_registers.iter_mut() {
        flag_register.is_set = false;
    }
}

Now we call this function in the main loop if the mnemonic is MOV.

if mnemonic != "mov" {
    if reg_is_dest {
        let reg = get_register_state(&reg_register, &registers);
        set_flags(reg.updated_value, &mut flag_registers, is_word_size);
    } else {
        let rm = get_register_state(&rm_register, &registers);
        set_flags(rm.updated_value, &mut flag_registers, is_word_size);
    }
} else {
    // if instruction is mov, we clear the FLAGS register because MOV does not use it and it doesn't modify it.
    clear_flags_registers(&mut flag_registers); // WE CALL IT HERE!!!!
}

Output now looks like this:

Untitled

We now correctly clear the FLAGS register when needed. Additionally, we correctly handle setting the different flags in the FLAGS register when an instruction is supposed to set it.

I had lots of fun with this assignment. I hope you guys enjoyed this lengthy blog post.

Listing_0046 is done. See you in the next one! :)