Hex values and masks

let a:i32 = 0xFFFF_FFFF;

this assignment gives the following error:

literal out of range for i32
the literal 0xFFFF_FFFF (decimal 4294967295) does not fit into the type i32 and will become -1i32
consider using the type u32 instead
#[deny(overflowing_literals)] on by defaultrustcClick for full compiler diagnostic

Im just trying to assign -1 to this i32, i gave it a 4 byte hex value to assign it to this 4 byte int, why is it giving an error? How is this an actual overflow?

Also this applies to masks

let mut a:i32 = 1;
a &= 0xFFFF_FFFF;

gives the same error even though im using it as a mask and not assigning it, so how can it even overflow?

Don't think of the 0xFFFF_FFFF as defining a bit representation for your a variable, rather it's just another way of writing an (unsigned) integer.

5 Likes

Technically this is not an error but a lint that is set to deny (throw compile time error) by default. If you know what you are doing, you can allow the overflowing_literals lint to avoid the compile-time error:

#![allow(overflowing_literals)]

fn main() {
    let a:i32 = 0xFFFF_FFFF;
}

Playground.


To expand @Michael-F-Bryan point, you wouldn't write something like 0xFFFFFFFF for a signed integer as decimal (you'd write -1 instead or -0x0000_00001 in hex):

let a: i32 = 4294967295;
assert_eq!(a, -1);
4 Likes

Or rather, only use unsigned types for bit manipulation. (And cast the ultimate value only, if you need it as a signed type.)

6 Likes

Well, clearly you're not, because if that's what you were trying to do you'd just write let a: i32 = -1;.

What's your goal here? What are you trying to accomplish?

That said, you might be interested in the discussion in `overflowing_literals` should have an option for "ignore signed overflows" · Issue #99195 · rust-lang/rust · GitHub

3 Likes

As someone who understands two's complement, there are definitely situations where I would write 0xFFFF_FFFF instead of -0x0000_0001. More frequently than I've used -0x.

The behavior of println!("{i:#x}"); (or X or b or o) is consistent with the sign being implicit in the literal; the documentation says "negative values are formatted as the two’s complement representation".

In fact, it seems there's no way to get the formatter to output -0x0000_0001 automatically, so you can't round-trip with the lint active. Perhaps a use case for the - flag.

(But if I was in a situation where I wanted to use 0xFFFF_FFFF, I'd just disable the lint / ignore signed overflows if available. The truncated p-adic two's complement representation has some nice qualities.)

1 Like

Im building a virtual machine for an university project and i need to do a lot of bit masks.
Example:
ADD AH, 2
i would need to do

AH = EAX & 0xFFFF_00FF;
AH >>= 8;
AH <<= 8 * 3;
AH >>= 8 * 3;

to get AH value of the register, its easy in C (obviously its extremely prone to errors if you dont know what you are doing), i want to do the same thing in Rust

Does it makes sense to count it as overflow? I like to be let known if im actually overflowing (as in data being truncated), is there any other option besides disabling overflowing_literals for this case? (negative hex value assignment)

What would be the workaround for using masks?

a &= 0xFFFF_FF00;

why does it matter if im applying a bit mask using an unsigned integer hex value to a signed integer? im not assigning it and thus overflowing, just comparing bits

Not to me with my background (knows 2s complement, would consider lack of such understanding a hole to be filled, considers use of hex to signal "bits are relevant and I know what they mean"), but I could entertain an argument the lint helps others. I think the sublint would be nice.

You could use u32.

You could cast a u32.

1 Like

Ok, great. Sounds like you should store your registers as unsigned types instead of signed ones, then.

Is there some reason you picked i32 instead of u32 in the first place?

3 Likes

Registers are int32, they can hold negative values, if i were to use u32 and i want to do

EAX = 4;
EBX = 5;
EAX -= EBX;

this would throw an error because EAX wouldnt be able to hold negative values.

I would need to check if the second operand is bigger than the first and manually apply 2s complement which is really annoying, and there are a lot of cases different operations that can take positive and negative values so its gonna be a nightmare to implement.

Im extremely knew to Rust, could you please explain to me this line?
let a:i32 = 0xFFFF_FFFF_u32 as _;
i would have tried (which is clearly not gonna work)
let a:i32 = 0xFFFF_FFFF as u32;
what is _? what is it being cast to? why can you put u_32 at the end of a hex value? is it like -1i32?

Use .wrapping_sub and you'll be fine. (Or just make the register type num::Wrapping<u32>.)

They're twos-complement -- as you know from trying to use 0xFFFFFFFF for -1! -- so addition, subtraction, and multiplication do exactly the same thing for signed or unsigned numbers.

Then if you need something like division where signedness matters, you'll need to case the one register type to the other one to be able to implement the other instruction anyway.

1 Like

0xFFFF_FFFF_u32 is the same as 0xFFFFFFFFu32, a literal of type u32. Omitting the type suffix will infer the (integer) type instead. In this case though, it infers i32 and triggers the lint. You can intersperse _ arbitrarily in the value for readability (but the type suffix, if present, must be complete).

https://doc.rust-lang.org/reference/tokens.html#integer-literals

as _ means "infer what to cast to from the wider context". So these are the same.

    let a: i32 = 0xFFFF_FFFF_u32 as _;
    let a = 0xFFFF_FFFF_u32 as i32;
2 Likes

The thing is... they aren't.

In x86 (and every other architecture I know), a register is neither signed nor unsigned. The register just stores 32 bits and it's the instructions you execute which dictate whether those bits should be interpreted as signed or unsigned. Your VM will need to cater for both scenarios.

One way to do this would be to define your own register type with the correct size and alignment, and provide convenience functions for interpreting it as a certain signededness.

#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(align(4))]
struct Register([u8; 4]);

impl Register {
  fn from_bits(bits: [u8; 4]) -> Self {
    Register(bits)
  }

  fn as_signed(self) -> i32 {
    i32::from_ne_bytes(self.0)
  }

  fn as_unsigned(self) -> u32 {
    u32::from_ne_bytes(self.0)
  }
}

impl From<u32> for Register {
  fn from(n: u32) -> Self { Register::from_bits(n.to_ne_bytes()) }
}

fn main() {
    let register = Register::from(0xffff_ffff);
}

(playground)

4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.