1

I am trying to conduct comparison between 2 128 bit integers, and I just can't seem to figure it out!

Specifically, I have a whole bunch of IPv6 IP addresses (start and end address of allocation blocks) and I'm trying to establish if a particular IP address sits between these blocks, so it's not a direct comparison, more like trying to check if greater than start address and lower than end address.

I'm using WinAsm Studio with MASM32 if that makes any difference.

Conducting this against IPv4 addresses is simple, however, I have no clue how to conduct this against 128 bit unsigned integers (4 x DWORD).

Thank you in advance.

colinr
  • 41
  • 3
  • Can you assume SSE2 and use `pcmpgtd` (after range-shifting to signed) to make it maybe more efficient? Otherwise just use scalar code starting from the most-significant chunk for your range check. Look at 32-bit compiler output for comparing 64-bit unsigned integers, and extend that to 2 more chunks. – Peter Cordes Jul 11 '19 at 08:32
  • BTW, usually a block is "aligned" in IP address space so you only need to check mask away the low bits and compare the high bits for equality. – Peter Cordes Jul 11 '19 at 08:33
  • Thanks Peter, let me have a play, the second option is more palatable than the first – colinr Jul 11 '19 at 09:37

1 Answers1

3

Otherwise just use scalar code starting from the most-significant chunk for your range check.

On a CPU that uses cmp and a flags register, this is done the following way:

  • Compare the highest parts (e.g. highest 32 bits) of the numbers (cmp instruction)
  • If not "Equal", jump to "EndOfCompare" (x86: jne instruction)
  • Compare the next parts (e.g. 32 bits) of the numbers
  • If not "Equal", jump to "EndOfCompare"
  • ...
  • Compare the next parts (e.g. 32 bits) of the numbers
  • If not "Equal", jump to "EndOfCompare"
  • Compare the lowest parts (e.g. lowest 32 bits) of the numbers
  • "EndOfCompare":
    At this point the flags register contains information about the order (a<b, a=b or a>b) of the two large numbers just like you did a simple cmp instruction comparing two small numbers.

Unfortunately this simple variant will only work with unsigned numbers.

BTW, usually a block is "aligned" in IP address space so you only need to check mask away the low bits and compare the high bits for equality.

A check for (A AND MASK) = (B AND MASK) can be done the following way on a 32-bit CPU:

mov ecx, part 1 of A
xor ecx, part 1 of B
and ecx, part 1 of MASK

mov eax, part 2 of A
xor eax, part 2 of B
and eax, part 2 of MASK
or  ecx, eax

mov eax, part 3 of A
xor eax, part 3 of B
and eax, part 3 of MASK
or  ecx, eax
...

In the case of a 128-bit number, you need 4 "parts".

It does not matter if "part 1" is the upper or the lower bits of the numbers.

If (A AND MASK) = (B AND MASK) is true, ecx will have the value 0 (and the zero flag will be set because of the or instruction).

Martin Rosenau
  • 17,897
  • 3
  • 19
  • 38
  • SSE2 makes the masked comparison very efficient (if you can efficiently generate the mask from a `/64` or whatever netmask which is actually a shift count). Just mask both then `pcmpeqd` / `pmovmskb`. – Peter Cordes Jul 11 '19 at 12:24
  • Thanks very much Peter and Martin, I like the look of the sample code above, which I'll have a play with on Tuesday when I get home. My x86 skills are lacking to say the least so using SSE2 for me at the moment is going to be way down the line. – colinr Jul 11 '19 at 13:46
  • @colinr: then why are you writing in asm at all, instead of C where a compiler can potentially auto-vectorize? Usually it only makes sense to write in asm if you know how to optimize for your target microarchitecture *better* than the compiler does. – Peter Cordes Jul 11 '19 at 23:34
  • @peter, I used to extensively code in asm on the MC68000, had a long break from coding, then started on x86 which was the logical step for me at the time. I've learned a bit of C++ but I find it really difficult casting. On asm, a byte is simply a byte, regardless of signed, unsigned or an ASCII value. – colinr Jul 16 '19 at 10:46
  • @colinr: Yeah, C++ and signed-overflow UB makes it not really a portable assembly language. But `uint8_t` can store the value of a character literal like `'a'`. It only gets annoying when things like `std::cout << x` print an 8-bit integer as a character. So use C and `printf` where you have to specify a `%d` or `%c` conversion. But generally use `int` or `unsigned` temporaries inside functions, and `uint8_t` for arrays. Unless you actively need wrapping / truncation to 8-bit for a temporary. – Peter Cordes Jul 16 '19 at 11:45