assemblyx86x86-64virtual-address-spaceaddress-space

x86-64: canonical addresses and actual available range


Intel and AMD documentation says that for 64 bit mode only 48 bits are actually available for virtual addresses, and bits from 48 to 63 must replicate bit 47 (sign-extension). As far as I know, all current CPU are implemented this way, but nothing (in theory) forbids to extend the available space in future implementations (and this won't break the binary compatibility).

Is there a standard way to programatically determine the number of meaningful bits? (i.e. some specific CPUID, as happens for physical addresses).

I know that in practice 48 bits are far more than enough for any reasonable application and OS; my question is theoretical.


Solution

  • Yes, you can use CPUID.80000008H:EAX[7:0] if supported.

    The algorithm is as follow:

    1. Check the maximum extended E value for cpuid with CPUID.80000000h.EAX.
    2. If E >= 80000008h, use CPUID.80000008H:EAX[7:0] for the number of physical address bits.
      CPUID.80000008H:EAX[15:8] for the number of linear address bits.
    3. Else if CPUID.1:EDX.PAE (bit 6) then the CPU has 36 physical address bits and 32 linear address bits.
    4. Else the CPU has 32 physical and logical address bits.

    The notation, from Intel, CPUID.X:R B means


    AMD set the maximum limits for virtual:physical addresses at 63:52.
    The current implementation is typically 48:40, though the size of the physical address space can be different.


    An example code that can be compiled with NASM

    BITS 64
    
    GLOBAL max_phy_addr
    GLOBAL max_lin_addr
    
    
    SECTION .text
    
    
    max_phy_addr:
     push rbx
    
    
     mov eax, 80000000h
     cpuid
    
     cmp eax, 80000008h
     jae .fromCpuid
    
     mov eax, 1
     cpuid
    
     mov eax, 32
     shr edx, 4
     and edx, 4
    
     add eax, edx
    
     pop rbx
     ret
    
    .fromCpuid:
    
     mov eax, 80000008h
     cpuid
    
     movzx eax, al
    
     pop rbx
     ret
    
    
    max_lin_addr:
     push rbx
    
     mov eax, 80000000h
     cpuid
    
     cmp eax, 80000008h
     jae .fromCpuid
    
     mov eax, 1
     cpuid
    
     mov eax, 32
    
     pop rbx
     ret
    
    .fromCpuid:
    
     mov eax, 80000008h
     cpuid
    
     movzx eax, ah
    
     pop rbx
     ret
    

    And used with a C program such as

    #include <stdio.h>
    
    long max_phy_addr();
    long max_lin_addr();
    
    int main()
    {
       printf("Phy: %llu\nLin: %llu\n", max_phy_addr(), max_lin_addr());
       return 0;
    }
    

    And I've just discovered that my Haswell is a 48:39!