2012-10-16 9 views
14

mam jakiś kod, który daje mi błędy relokacji podczas kompilacji, poniżej jest przykładem, który ilustruje problem:gfortran for dummies: Co dokładnie robi 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 

zestawiania tej z gfortran -O3 -g -o test test.f, otrzymuję następujący błąd:

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

Ale działa, jeśli używam gfortran -O3 -mcmodel=medium -g -o test test.f. Zauważ również, że działa, jeśli przydzielę tablicę do przydzielenia i przydzielę ją w podprocedurze.

Moje pytanie brzmi: co dokładnie robi -mcmodel=medium? Miałem wrażenie, że dwie wersje kodu (ta z macierzami allocatable i bez) były mniej więcej równoważne ...

Odpowiedz

27

Od bar jest dość duży kompilator generuje statyczne przydzielanie zamiast automatycznej alokacji na stosie. Macierze statyczne są tworzone za pomocą dyrektywy montażu .comm, która tworzy przydział w tak zwanej sekcji WSPÓLNEJ. Symbole z tej sekcji są gromadzone, symbole o tej samej nazwie są scalane (zredukowane do jednego żądania symbolu o rozmiarze równym największemu żądanemu rozmiarowi), a następnie reszta jest odwzorowywana w sekcji BSS (niezainicjowane dane) w większości wykonywalnych formatów. W przypadku plików wykonywalnych ELF sekcja .bss znajduje się w segmencie danych, tuż przed częścią segmentu danych sterty (istnieje inna partia sterty zarządzana przez anonimowe odwzorowania pamięci, które nie znajdują się w segmencie danych).

W modelu pamięci 32-bitowej small używane są instrukcje adresowania symboli na architekturze x86_64. Dzięki temu kod jest mniejszy, a także szybszy. Pewne montażowej przy użyciu small modelu pamięci:

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 

używa się 32-bitowy MOVE (długość 5 bajtów) do wprowadzenia wartości bar.1535 symbolu (wartość ta równa się adres lokalizacji symbolu) do dolne 32 bity rejestru RBX (górne 32 bity zostają wyzerowane). Sam symbol bar.1535 jest przydzielany przy użyciu dyrektywy .comm. Pamięć dla bloku COMMON baz jest przydzielana później. Ponieważ bar.1535 jest bardzo duży, baz_ kończy się więcej niż 2 GiB od początku sekcji .bss. To stanowi problem w drugiej instrukcji movl, ponieważ nie 32-bitowe (podpisane) przesunięcie od RIP powinno być używane do adresowania zmiennej b, do której należy przenieść wartość EAX. Jest to wykrywane tylko w czasie połączenia. Sam asembler nie zna odpowiedniego offsetu, ponieważ nie wie, jaka byłaby wartość wskaźnika instrukcji (RIP) (zależy to od bezwzględnego adresu wirtualnego, w którym kod jest załadowany i jest określany przez łącznik), więc po prostu ustawia przesunięcie 0, a następnie tworzy żądanie relokacji typu R_X86_64_PC32. Instruuje linker, aby poprawił wartość 0 z rzeczywistą wartością offsetu. Ale nie może tego zrobić, ponieważ wartość przesunięcia nie zmieściłaby się wewnątrz podpisanej 32-bitowej liczby całkowitej, a tym samym wygasła.

Z modelu medium pamięci w miejsce rzeczy wyglądać następująco:

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

Pierwszy 64-bitowy natychmiastowe instrukcja ruch (długość 10 bajtów) służy do wprowadzenia 64-bitowa wartość, która reprezentuje adres bar.1535 w rejestrze R10. Pamięć do symbolu bar.1535 jest przydzielana przy użyciu dyrektywy .largecomm, a zatem kończy się w sekcji .lbss z możliwym do wybrania ELF. .lbss służy do przechowywania symboli, które mogą nie pasować do pierwszych 2 GiB (i dlatego nie powinny być adresowane za pomocą instrukcji 32-bitowych lub adresowania względnego RIP), podczas gdy mniejsze rzeczy należą do .bss (baz_ jest nadal przydzielane przy użyciu .comm, a nie .largecomm). Ponieważ sekcja .lbss jest umieszczana po sekcji .bss w skrypcie łącznika ELF, baz_ nie będzie w końcu niedostępna przy użyciu 32-bitowego adresowania związanego z RIP.

Wszystkie tryby adresowania są opisane w System V ABI: AMD64 Architecture Processor Supplement. Jest to ciężki techniczny odczyt, ale musi być przeczytany dla każdego, kto naprawdę chce zrozumieć, jak działa 64-bitowy kod na większości Uniksów x86_64.

Gdy ALLOCATABLE tablica jest używana zamiast gfortran przydziela pamięć sterty (najprawdopodobniej realizowany jako anonimowego mapy pamięci ze względu na duży rozmiar alokacji):

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

Jest to w zasadzie RDI = malloc(2575411200). Odtąd elementów bar są dostępne za pomocą pozytywnych przesunięcia od wartości zapisanej w RDI:

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

Na miejscach, które są więcej niż 2 Gib od początku bar, stosuje się metody bardziej skomplikowane. Na przykład. wdrożyć b = bar(12,144*144*450)gfortran emituje:

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

Kod ten nie ma wpływu na modelu pamięci, ponieważ nic nie zakłada się o adres gdzie dynamiczny przydział zostanie wykonany. Ponadto, ponieważ tablica nie jest przekazywana, nie buduje się deskryptora. Jeśli dodasz inną funkcję, która przyjmuje tablicę o założonym kształcie i przekazuje do niej bar, tworzony jest deskryptor dla bar jako zmienna automatyczna (tj. Na stosie foo). Jeśli tablica jest statyczna z atrybutem SAVE, deskryptor jest umieszczony w sekcji .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) 

Pierwszy ruch przygotowuje argument wywołania funkcji (w moim przykładowym przypadku call boo(bar) gdzie boo posiada interfejs, który deklaruje to jako przyjmowanie tablicy o założonym kształcie). Przenosi adres deskryptora tablic z bar do EDI. Jest to 32-bitowy natychmiastowy ruch, więc deskryptor powinien znajdować się w pierwszych 2 GiB. Rzeczywiście, to jest alokowana w .bss w obu small i medium pamięci modeli tak:

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

To bardzo miłe wyjaśnienie. Dzięki. Daje mi to dobry początek, aby zagłębić się w gąszcz tych rzeczy (czego właśnie szukałem). – mgilson

+0

@mgilson, dla uzupełnienia odpowiedzi, dodałem również wyjaśnienia do tego, co dzieje się, gdy 'bar' jest przekazywany przez deskryptor do innej podprocedury. –

8

Nie, duże statyczne tablice (jako bar) mogą przekroczyć limit, jeśli nie używasz -mcmodel=medium. Ale alokacje są oczywiście lepsze. W przypadku allocatables tylko deskryptor tablicy musi mieścić się w 2 GB, a nie w całej tablicy.

Z odniesieniem 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

Przypuszczam, może pytanie jest jaka jest różnica między „statycznej tablicy” i „allocatable tablicy”?Miałem wrażenie, że zostaną one przydzielone z kupy w obu przypadkach (chociaż muszę przyznać, że mówię o rzeczach, o których nie mam zbyt wiele wiedzy). – mgilson

+0

Właśnie zredagowałem odpowiedź, kiedy pisałeś. Alokacje mają deskryptor (wskaźnik z dodatkowymi danymi) i tylko to musi pasować do 2 GB. Tablica statyczna znajduje się całkowicie w segmencie statycznym, podobnie jak każda inna zmienna statyczna. –

+0

(Być może istnieje tylko wskaźnik do deskryptora w segmencie statycznym, ale nie zmienia to różnicy.) –

Powiązane problemy