2012-10-16 12 views
14

ho qualche codice che mi sta dando errori di trasferimento durante la compilazione, di seguito è un esempio che illustra il problema:gfortran for dummies: cosa fa esattamente mcmodel = medium?

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 

compilazione questo con gfortran -O3 -g -o test test.f, ottengo il seguente errore:

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

Ma Funziona se uso gfortran -O3 -mcmodel=medium -g -o test test.f. Si noti inoltre che funziona se si rende l'array allocabile e lo si alloca all'interno della subroutine.

La mia domanda è cosa fa esattamente -mcmodel=medium? Avevo l'impressione che le due versioni del codice (quella con gli array allocatable e quella senza) fossero più o meno equivalenti ...

risposta

27

Da bar è abbastanza grande il compilatore genera allocazione statica invece di assegnazione automatica in pila. Gli array statici vengono creati con la direttiva di assemblaggio .comm che crea un'allocazione nella cosiddetta sezione COMMON. I simboli di quella sezione vengono raccolti, i simboli con lo stesso nome vengono uniti (ridotti a una richiesta di simboli con dimensioni uguali alla dimensione massima richiesta) e quindi ciò che è riposato viene mappato alla sezione BSS (dati non inizializzati) nella maggior parte dei formati eseguibili. Con gli eseguibili ELF la sezione .bss si trova nel segmento dati, appena prima della parte del segmento dati dell'heap (c'è un'altra parte dell'heap gestita da mappature anonimi della memoria che non risiedono nel segmento dati).

Con il modello di memoria small le istruzioni di indirizzamento a 32 bit vengono utilizzate per indirizzare i simboli su x86_64. Questo rende il codice più piccolo e anche più veloce. Alcuni uscita assemblaggio utilizzando small modello di memoria:

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 

Questo utilizza un'istruzione di movimento 32-bit (lunga 5 byte) per mettere il valore del simbolo bar.1535 (questo valore è uguale all'indirizzo della posizione simbolo) in i 32 bit inferiori del registro RBX (i 32 bit superiori vengono azzerati). Il simbolo bar.1535 viene assegnato utilizzando la direttiva .comm. La memoria per il blocco baz COMMON viene assegnata in seguito. Poiché bar.1535 è molto grande, baz_ termina più di 2 GiB dall'inizio della sezione .bss. Ciò pone un problema nella seconda istruzione poiché un offset non 32 bit (firmato) da RIP deve essere utilizzato per indirizzare la variabile b in cui deve essere spostato il valore di EAX. Questo viene rilevato solo durante il tempo di collegamento. L'assemblatore stesso non conosce l'offset appropriato poiché non sa quale sarebbe il valore del puntatore di istruzioni (RIP) (dipende dall'indirizzo virtuale assoluto in cui è caricato il codice e questo è determinato dal linker), quindi mette semplicemente un offset di 0 e quindi crea una richiesta di trasferimento di tipo R_X86_64_PC32. Indica al linker di correggere il valore di 0 con il valore di offset reale. Ma non può farlo poiché il valore di offset non si adatta a un intero con segno a 32 bit e quindi bails out.

Con il modello medium memoria in atto cose simile a questa:

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

Prima di un'istruzione di movimento immediato a 64 bit (lungo 10 byte) viene usato per mettere il valore a 64 bit che rappresenta l'indirizzo bar.1535 nel registro R10. La memoria per il simbolo bar.1535 viene allocata utilizzando la direttiva .largecomm e termina quindi nella sezione .lbss dell'esecutivo ELF. .lbss viene utilizzato per memorizzare simboli che potrebbero non rientrare nei primi 2 GiB (e quindi non devono essere indirizzati utilizzando le istruzioni a 32 bit o l'indirizzamento relativo RIP), mentre gli oggetti più piccoli vanno a .bss (baz_ è ancora assegnato utilizzando .comm e non .largecomm). Poiché la sezione .lbss viene inserita dopo la sezione .bss nello script del linker ELF, baz_ non risulta inaccessibile utilizzando l'indirizzamento RIP a 32 bit.

Tutte le modalità di indirizzamento sono descritte nello System V ABI: AMD64 Architecture Processor Supplement. È una lettura tecnica pesante, ma assolutamente da leggere per chiunque voglia veramente capire come funziona il codice a 64 bit sulla maggior parte degli Unix x86_64.

Quando un array ALLOCATABLE si utilizza invece, gfortran alloca memoria heap (probabilmente implementato come una mappa di memoria anonima data l'ampiezza della dotazione):

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

Questo è fondamentalmente RDI = malloc(2575411200). Da allora in poi gli elementi di bar sono accessibili utilizzando offset positivi dal valore memorizzato in RDI:

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

Per le posizioni che sono più di 2 GiB dall'inizio di bar, un metodo più elaborato viene utilizzato. Per esempio. implementare b = bar(12,144*144*450)gfortran emette:

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

Questo codice non è influenzato dal modello di memoria poiché nulla si presume sull'indirizzo dove l'allocazione dinamica sarebbe fatto. Inoltre, poiché l'array non viene passato in giro, non viene creato alcun descrittore. Se si aggiunge un'altra funzione che accetta una matrice a forma assunta e passa bar ad esso, un descrittore per bar viene creato come una variabile automatica (cioè sulla pila di foo). Se la matrice è fatta statico con l'attributo SAVE, il descrittore viene inserito nella sezione .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) 

La prima mossa prepara l'argomento di una chiamata di funzione (nel mio caso campione call boo(bar) dove boo ha un'interfaccia che dichiara come un array di forma assunta). Sposta l'indirizzo del descrittore di array di bar in EDI. Questa è una mossa immediata a 32 bit, quindi il descrittore dovrebbe essere nei primi 2 GiB. Infatti, è allocato nel .bss in entrambi i modelli small e medium memoria come questo:

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

Questa è una spiegazione molto bella. Grazie. Questo mi dà un buon inizio per guardare molto più in profondità in un mucchio di questa roba (che è quello che stavo cercando). – mgilson

+0

@ mgilson, solo per completezza della risposta, ho aggiunto anche spiegazioni su cosa succede quando 'bar' viene passato dal descrittore ad un'altra subroutine. –

8

No, i grandi array statici (come il tuo bar) possono superare il limite se non usi -mcmodel=medium. Ma gli allocatables sono migliori, naturalmente. Per gli allocataboli, solo il descrittore di array deve essere contenuto in 2 GB, non nell'intero array.

Da riferimento 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. 
+0

suppongo, forse la domanda è qual è la differenza tra un "array statico" e di un "allineamento allocatable"?La mia impressione era che sarebbero stati allocati dal mucchio in entrambi i casi (anche se dovrei ammettere che sto parlando di cose di cui non ho molta conoscenza) – mgilson

+0

Ho appena modificato la risposta quando hai scritto. Gli allocatables hanno un descrittore (puntatore con dati aggiuntivi) e solo questo deve rientrare in 2 GB. L'array statico è completamente nel segmento statico proprio come qualsiasi altra variabile statica. –

+0

(Forse c'è solo un puntatore al descrittore nel segmento statico, ma non cambia la differenza.) –