# How NanoCore's Wrapping Arithmetic Manages Integer Overflow and Underflow

> Discover how NanoCore's wrapping arithmetic handles integer overflow and underflow using Rust's `wrapping_*` methods on u8 types for accurate 8-bit CPU emulation.

- Repository: [Afaan Bilal/nanocore](https://github.com/afaanbilal/nanocore)
- Tags: internals
- Published: 2026-02-23

---

**NanoCore uses Rust's built-in `wrapping_*` and `overflowing_*` methods on `u8` types to emulate true 8-bit CPU behavior, automatically wrapping values modulo 256 while setting the carry flag for overflow detection.**

NanoCore is a Rust-based emulator for a true 8-bit CPU architecture where every register, the program counter (PC), and the stack pointer (SP) are strictly `u8` values. In this constrained environment, NanoCore's wrapping arithmetic ensures that any operation exceeding the 0–255 range wraps around seamlessly, matching hardware behavior exactly.

## Understanding 8-Bit Wrapping Arithmetic in NanoCore

In an authentic 8-bit processor, arithmetic operations do not panic or throw exceptions when exceeding byte limits. Instead, values wrap using modulo 256 arithmetic. NanoCore implements this behavior by leveraging Rust's wrapping arithmetic primitives on unsigned 8-bit integers, ensuring that `0xFF + 1` becomes `0x00` and `0x00 - 1` becomes `0xFF` without runtime overhead.

## How NanoCore Implements Wrapping for PC and SP

### Program Counter Wrapping with `wrapping_add`

The program counter advances after each instruction using `wrapping_add`, allowing execution to flow from address `0xFF` directly to `0x00`, essential for circular memory layouts.

```rust
// src/nanocore.rs – updating PC when no explicit jump occurs
self.cpu.pc = self.cpu.pc.wrapping_add(op.instruction_len());

```

[View source](https://github.com/afaanbilal/nanocore/blob/master/src/nanocore.rs#L206)

### Stack Pointer Management with Bounds Checking

Push and pop operations modify the stack pointer using wrapping arithmetic, but NanoCore first validates against `STACK_MIN` and `STACK_MAX` to raise explicit overflow/underflow errors before the wrap occurs.

```rust
// src/nanocore.rs – PUSH
self.cpu.sp = self.cpu.sp.wrapping_sub(1);

// src/nanocore.rs – POP
self.cpu.sp = self.cpu.sp.wrapping_add(1);

```

[Push source](https://github.com/afaanbilal/nanocore/blob/master/src/nanocore.rs#L420) | [Pop source](https://github.com/afaanbilal/nanocore/blob/master/src/nanocore.rs#L439)

## Register Operations: INC and DEC

The increment and decrement instructions use wrapping arithmetic to ensure seamless rollover at byte boundaries.

```rust
// src/nanocore.rs – INC / DEC
let value = if op == Op::INC {
    self.cpu.registers[reg as usize].wrapping_add(1)
} else {
    self.cpu.registers[reg as usize].wrapping_sub(1)
};

```

[View source](https://github.com/afaanbilal/nanocore/blob/master/src/nanocore.rs#L459-L461)

## Arithmetic Instructions and Overflow Detection

### Using `overflowing_*` Methods for ADD, SUB, MUL

NanoCore's `execute_arithmetic` function in [`src/nanocore.rs`](https://github.com/afaanbilal/nanocore/blob/main/src/nanocore.rs) utilizes Rust's `overflowing_add`, `overflowing_sub`, and `overflowing_mul` methods. These return a tuple containing the wrapped result and a boolean indicating whether overflow occurred.

```rust
fn execute_arithmetic(&self, op: Op, v1: u8, v2: u8) -> crate::EmulatorResult<(u8, bool)> {
    let result = match op {
        Op::ADD | Op::ADDI => v1.overflowing_add(v2),
        Op::SUB | Op::SUBI => v1.overflowing_sub(v2),
        Op::MUL | Op::MULI => v1.overflowing_mul(v2),
        // DIV / MOD use overflowing_div / overflowing_rem after a zero-check
        …
    };
    Ok(result)
}

```

[Source](https://github.com/afaanbilal/nanocore/blob/master/src/nanocore.rs#L13-L27)

### Setting the Carry Flag (`FLAG_C`)

After arithmetic execution, NanoCore updates the destination register with the wrapped result, adjusts the Zero and Negative flags via `update_zn_flags`, and conditionally sets the **carry flag** (`FLAG_C`) based on the overflow boolean.

```rust
self.cpu.registers[rd as usize] = result;
self.cpu.update_zn_flags(result);
if carry { self.cpu.set_flag(CPU::FLAG_C); } else { self.cpu.clear_flag(CPU::FLAG_C); }

```

[Source](https://github.com/afaanbilal/nanocore/blob/master/src/nanocore.rs#L87-L92)

## Bitwise Operations: Shifts and Rotates

Shift operations use `overflowing_shl` and `overflowing_shr` to capture the bit shifted out into the carry flag. Rotate operations manually extract the bit that wraps around to set `FLAG_C` appropriately.

```rust
let (result, carry) = match op {
    Op::SHL => value.overflowing_shl(1),
    Op::SHR => value.overflowing_shr(1),
    Op::ROL => (value.rotate_left(1), (value & 0x80) != 0),
    Op::ROR => (value.rotate_right(1), (value & 0x01) != 0),
};

```

[Source](https://github.com/afaanbilal/nanocore/blob/master/src/nanocore.rs#L96-L100)

## Memory Addressing with Wrapping

When loading programs into the emulated 256-byte memory space, NanoCore uses `wrapping_add` on the start address. This permits programs that cross the `0xFF → 0x00` boundary without manual bounds checks, as the earlier size validation already guarantees the program fits within memory.

```rust
self.cpu.memory[start_address.wrapping_add(i as u8) as usize] = byte;

```

[Source](https://github.com/afaanbilal/nanocore/blob/master/src/nanocore.rs#L101-L103)

## Summary

- **NanoCore** implements true 8-bit CPU emulation by using Rust's `u8` wrapping arithmetic methods throughout the codebase.
- The **program counter** and **stack pointer** use `wrapping_add` and `wrapping_sub` to handle address rollover at byte boundaries.
- **Arithmetic instructions** leverage `overflowing_add`, `overflowing_sub`, and `overflowing_mul` to obtain both the wrapped result and the carry bit for flag updates.
- The **carry flag** (`FLAG_C`) accurately reflects overflow conditions from arithmetic, shifts, and rotates, matching hardware behavior.
- **Memory addressing** uses wrapping addition to support programs that cross the 0xFF boundary seamlessly.

## Frequently Asked Questions

### What happens when the program counter reaches 0xFF in NanoCore?

When the program counter reaches `0xFF` and the next instruction requires incrementing, NanoCore uses `wrapping_add` to advance the PC. This causes the counter to roll over from `0xFF` to `0x00`, accurately emulating the circular memory addressing of real 8-bit processors.

### How does NanoCore detect arithmetic overflow?

NanoCore detects overflow using Rust's `overflowing_*` methods (such as `overflowing_add` and `overflowing_sub`). These methods return a tuple containing the arithmetic result and a boolean indicating whether overflow occurred. The emulator then sets or clears the carry flag (`FLAG_C`) based on this boolean value.

### Why does NanoCore use wrapping arithmetic instead of panicking on overflow?

NanoCore emulates a true 8-bit CPU where hardware registers naturally wrap around at byte boundaries (modulo 256). In real 8-bit processors, overflow does not cause crashes but instead produces defined wraparound behavior with appropriate flag settings. Using Rust's wrapping arithmetic allows NanoCore to match this hardware behavior exactly without runtime overhead.

### How is the stack pointer protected from overflow and underflow?

While the stack pointer uses `wrapping_add` and `wrapping_sub` for arithmetic operations, NanoCore explicitly validates the SP against `STACK_MIN` and `STACK_MAX` constants before performing push or pop operations. This validation raises explicit overflow or underflow errors if the stack would exceed its defined boundaries, preventing silent corruption while still allowing the pointer itself to wrap if needed.