gfortran para dummies: ¿Qué hace mcmodel = medium exactamente?

Tengo un código que me está dando errores de reubicación al comstackr, a continuación se muestra un ejemplo que ilustra el problema:

program main common/baz/a,b,c real a,b,c b = 0.0 call foo() print*, b end subroutine foo() common/baz/a,b,c real a,b,c integer, parameter :: nx = 450 integer, parameter :: ny = 144 integer, parameter :: nz = 144 integer, parameter :: nf = 23*3 real :: bar(nf,nx*ny*nz) !real, allocatable,dimension(:,:) :: bar !allocate(bar(nf,nx*ny*nz)) bar = 1.0 b = bar(12,32*138*42) return end 

Al comstackr esto con gfortran -O3 -g -o test test.f , gfortran -O3 -g -o test test.f el siguiente error:

 relocation truncated to fit: R_X86_64_PC32 against symbol `baz_' defined in COMMON section in /tmp/ccIkj6tt.o 

Pero funciona si uso gfortran -O3 -mcmodel=medium -g -o test test.f También tenga en cuenta que funciona si hago que la matriz sea asignable y la asigne dentro de la subrutina.

Mi pregunta es qué hace exactamente -mcmodel=medium ? Tenía la impresión de que las dos versiones del código (la que allocatable matrices allocatable y la que no) eran más o menos equivalentes …

Como la bar es bastante grande, el comstackdor genera una asignación estática en lugar de la asignación automática en la stack. Las matrices estáticas se crean con la directiva de conjunto .comm , que crea una asignación en la llamada sección COMÚN. Los símbolos de esa sección se recostackn, los símbolos del mismo nombre se fusionan (se reduce a una solicitud de símbolo con tamaño igual al tamaño solicitado) y luego se asigna el rest a la sección BSS (datos no inicializados) en la mayoría de los formatos ejecutables. Con ejecutables ELF, la sección .bss se ubica en el segmento de datos, justo antes de la parte del segmento de datos del montón (hay otra parte del montón gestionada por asignaciones de memoria anónimas que no reside en el segmento de datos).

Con el modelo de memoria small las instrucciones de direccionamiento de 32 bits se usan para direccionar símbolos en x86_64. Esto hace que el código sea más pequeño y también más rápido. Algunos resultados de ensamblaje cuando se usa small modelo de memoria small :

 movl $bar.1535, %ebx <---- Instruction length saving ... movl %eax, baz_+4(%rip) <---- Problem!! ... .local bar.1535 .comm bar.1535,2575411200,32 ... .comm baz_,12,16 

Utiliza una instrucción de movimiento de 32 bits (5 bytes de longitud) para poner el valor del símbolo bar.1535 (este valor es igual a la dirección de la ubicación del símbolo) en los 32 bits inferiores del registro RBX (los 32 bits superiores obtienen a cero). El símbolo bar.1535 sí se asigna utilizando la directiva .comm . La memoria para el bloque COMMON baz se asigna después. Debido a que bar.1535 es muy grande, baz_ termina más de 2 GiB desde el inicio de la sección .bss . Esto plantea un problema en la segunda instrucción movl , ya que se debe utilizar un desplazamiento de 32 bits (firmado) que no sea de 32 bits para abordar la variable b donde debe moverse el valor de EAX . Esto solo se detecta durante el tiempo de enlace. El ensamblador en sí no conoce el desplazamiento apropiado ya que no sabe cuál sería el valor del puntero de instrucción ( RIP ) (depende de la dirección virtual absoluta donde se carga el código y esto lo determina el enlazador), por lo que simplemente pone un desplazamiento de 0 y luego crea una solicitud de reubicación de tipo R_X86_64_PC32 . Enseña al vinculador a parchar el valor de 0 con el valor de compensación real. Pero no puede hacer eso ya que el valor de compensación no cabría dentro de un entero de 32 bits con signo y, por lo tanto, se rescata.

Con el modelo de memoria medium en su lugar, las cosas se ven así:

 movabsq $bar.1535, %r10 ... movl %eax, baz_+4(%rip) ... .local bar.1535 .largecomm bar.1535,2575411200,32 ... .comm baz_,12,16 

Primero, se usa una instrucción de movimiento inmediato de 64 bits (10 bytes de longitud) para poner el valor de 64 bits que representa la dirección de la bar.1535 en el registro R10 . La memoria para el símbolo bar.1535 se asigna utilizando la directiva .largecomm y por lo tanto termina en la sección .lbss del ELF ejecutable. .lbss se usa para almacenar símbolos que podrían no encajar en los primeros 2 GiB (y por lo tanto no deberían tratarse usando instrucciones de 32 bits o direcciones relativas al RIP), mientras que las cosas más pequeñas van a .bss ( baz_ aún se asigna usando .comm y no .largecomm ). Dado que la sección .lbss se coloca después de la sección .bss en la secuencia de comandos del vinculador ELF, baz_ no terminaría siendo inaccesible mediante el direccionamiento relacionado con el RIP de 32 bits.

Todos los modos de direccionamiento se describen en System V ABI: Suplemento del procesador de architecture AMD64 . Es una lectura técnica pesada, pero una lectura obligada para cualquiera que realmente quiera entender cómo funciona el código de 64 bits en la mayoría de los Unixes x86_64.

Cuando se utiliza una matriz ALLOCATABLE su lugar, gfortran asigna memoria de montón (lo más probable es que se implemente como un mapa de memoria anónimo dado el gran tamaño de la asignación):

 movl $2575411200, %edi ... call malloc movq %rax, %rdi 

Esto es básicamente RDI = malloc(2575411200) . A partir de ese momento bar se accede a los elementos de la bar utilizando compensaciones positivas del valor almacenado en RDI :

 movl 51190040(%rdi), %eax movl %eax, baz_+4(%rip) 

Para ubicaciones que son más de 2 GiB desde el inicio de la bar , se utiliza un método más elaborado. Por ejemplo, para implementar b = bar(12,144*144*450) gfortran emite:

 ; Some computations that leave the offset in RAX movl (%rdi,%rax), %eax movl %eax, baz_+4(%rip) 

Este código no se ve afectado por el modelo de memoria ya que no se asume nada sobre la dirección donde se realizaría la asignación dinámica. Además, dado que la matriz no se pasa, no se está creando ningún descriptor. Si agrega otra función que adopta una matriz de forma asumida y le pasa una bar se crea un descriptor para la bar como una variable automática (es decir, en la stack de foo ). Si la matriz se convierte en estática con el atributo SAVE , el descriptor se coloca en la sección .bss :

 movl $bar.1580, %edi ... ; RAX still holds the address of the allocated memory as returned by malloc ; Computations, computations movl -232(%rax,%rdx,4), %eax movl %eax, baz_+4(%rip) 

El primer movimiento prepara el argumento de una llamada a función (en mi caso de ejemplo call boo(bar) donde boo tiene una interfaz que declara que toma una matriz de forma supuesta). Mueve la dirección del descriptor de matriz de la bar a EDI . Este es un movimiento inmediato de 32 bits, por lo que se espera que el descriptor esté en los primeros 2 GiB. De hecho, se asigna en .bss en modelos de memoria small y medium como este:

 .local bar.1580 .comm bar.1580,72,32 

No, las grandes matrices estáticas (como su bar ) pueden exceder el límite si no usa -mcmodel=medium . Pero los asignables son mejores, por supuesto. Para asignables solo el descriptor de matriz debe caber en 2 GB, no en toda la matriz.

De la referencia de GCC:

 -mcmodel=small Generate code for the small code model: the program and its symbols must be linked in the lower 2 GB of the address space. Pointers are 64 bits. Programs can be statically or dynamically linked. This is the default code model. -mcmodel=kernel Generate code for the kernel code model. The kernel runs in the negative 2 GB of the address space. This model has to be used for Linux kernel code. -mcmodel=medium Generate code for the medium model: The program is linked in the lower 2 GB of the address space but symbols can be located anywhere in the address space. Programs can be statically or dynamically linked, but building of shared libraries are not supported with the medium model. -mcmodel=large Generate code for the large model: This model makes no assumptions about addresses and sizes of sections. Currently GCC does not implement this model.