vendredi 14 septembre 2018

Why is this 64-bit compare being treated like a 32-bit?

I ran into this strange issue when trying to generate some random 64-bit numbers and noticed that this bit of code would only give me numbers where the lower 32-bits were < 0x8000_0000.

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <cstdlib>
#include <ctime>

using namespace std;
void bin_range(uint64_t addr);
void print_range();

uint64_t GiB = 1024*1024*1024;
#define NUM_TRIES 10000
int range[14] = {0};

int main(){
    srand(time(NULL));
    for(int i = 0; i < NUM_TRIES; ++i){
        uint64_t addr = ((uint64_t) rand() << 32) | rand();
        addr &= 0x3FFFFFFFFFull;
        if(addr > ((uint64_t) 14*GiB)){
            --i;
            continue;
        }
        bin_range(addr);
    }
    print_range();

    return 0;
}

void bin_range(uint64_t addr){
    if(addr < GiB)
        range[0]++;
    else if(addr < 2*GiB)
        range[1]++;
    else if(addr < 3*GiB)
        range[2]++;
    else if(addr < 4*GiB)
        range[3]++;
    else if(addr < 5*GiB)
        range[4]++;
    else if(addr < 6*GiB)
        range[5]++;
    else if(addr < 7*GiB)
        range[6]++;
    else if(addr < 8*GiB)
        range[7]++;
    else if(addr < 9*GiB)
        range[8]++;
    else if(addr < 10*GiB)
        range[9]++;
    else if(addr < 11*GiB)
        range[10]++;
    else if(addr < 12*GiB)
        range[11]++;
    else if(addr < 13*GiB)
        range[12]++;
    else if(addr < 14*GiB)
        range[13]++;
}
void print_range(){
    for(int i = 0; i < 14; ++i){
        cout <<dec<< "range["<<i<<"]\thas "<<((float)range[i]/NUM_TRIES)*100.0<<"%"<<endl;
    }
}

When I run this I see that only the address ranges with addr[31:28] < 8 make it through this if statement!

range[0]    has 12.25%
range[1]    has 12.17%
range[2]    has 0%
range[3]    has 0%
range[4]    has 13.22%
range[5]    has 12.19%
range[6]    has 0%
range[7]    has 0%
range[8]    has 12.53%
range[9]    has 11.83%
range[10]   has 0%
range[11]   has 0%
range[12]   has 12.85%
range[13]   has 12.96%

Here's the relevant disassembly for this if statement when compiling with g++ -g -c -fverbose-asm -Wa,-adhln calc.cpp > calc.lst and I'm using g++ version 4.4.6:

  32:calc.cpp      ****         addr &= 0x3FFFFFFFFFull;
 407                    .loc 2 32 0
 408 00ad 48B8FFFF      movabsq $274877906943, %rax #, tmp129
 408      FFFF3F00 
 408      0000
 409 00b7 482145E0      andq    %rax, -32(%rbp) # tmp129, addr
  33:calc.cpp      ****         if(addr > ((uint64_t) 14*GiB)){
 410                    .loc 2 33 0
 411 00bb 488B0500      movq    GiB(%rip), %rax # GiB, GiB.59
 411      000000
 412 00c2 4801C0        addq    %rax, %rax  # tmp131
 413 00c5 488D14C5      leaq    0(,%rax,8), %rdx    #, tmp132
 413      00000000 
 414 00cd 4889D1        movq    %rdx, %rcx  # tmp132,
 415 00d0 4829C1        subq    %rax, %rcx  # tmp130,
 416 00d3 4889C8        movq    %rcx, %rax  #, D.22316
 417 00d6 483B45E0      cmpq    -32(%rbp), %rax # addr, D.22316
 418 00da 7306          jae .L20    #,
  34:calc.cpp      ****             --i;

It seems to me that g++ is making the if statement a 32-bit comparison, but from the disassembly I see it's a quad-word compare so it doesn't make sense to me why this is happening. Any ideas?

Update: I know this isn't an issue with rand() because when I change the for loop to be like this:

for(int i = 0; i < NUM_TRIES; ++i){
    uint64_t addr = ((uint64_t) rand() << 32) | rand();
    addr %= 14*GiB;
    //addr &= 0x3FFFFFFFFFull;
    //if(addr > ((uint64_t) 14*GiB)){
        //--i;
        //continue;
    //}
    bin_range(addr);
}

I do get a full range of numbers for the output:

range[0]    has 7.09%
range[1]    has 6.85%
range[2]    has 7.24%
range[3]    has 7.75%
range[4]    has 7.39%
range[5]    has 7.19%
range[6]    has 6.92%
range[7]    has 6.63%
range[8]    has 7.33%
range[9]    has 6.95%
range[10]   has 7.11%
range[11]   has 7.28%
range[12]   has 7.08%
range[13]   has 7.19%




Aucun commentaire:

Enregistrer un commentaire