gfortran для чайников: что конкретно делает 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
Компилируя это с gfortran -O3 -g -o test test.f
Я получаю следующую ошибку:
relocation truncated to fit: R_X86_64_PC32 against symbol `baz_' defined in COMMON section in /tmp/ccIkj6tt.o
Но это работает, если я использую gfortran -O3 -mcmodel=medium -g -o test test.f
, Также обратите внимание, что это работает, если я сделаю массив размещаемым и выделю его в подпрограмме.
Мой вопрос, что именно делает -mcmodel=medium
делать? У меня сложилось впечатление, что две версии кода (одна с allocatable
массивы и тот, что без) были более или менее эквивалентны...
2 ответа
Поскольку bar
довольно большой, компилятор генерирует статическое распределение вместо автоматического выделения в стеке. Статические массивы создаются с .comm
ассемблерная директива, которая создает размещение в так называемом разделе COMMON. Символы из этого раздела собираются, символы с одинаковыми именами объединяются (сокращается до одного запроса символов с размером, равным наибольшему запрашиваемому размеру), а затем все остальное отображается в раздел BSS (неинициализированные данные) в большинстве исполняемых форматов. С исполняемыми файлами ELF .bss
раздел расположен в сегменте данных, непосредственно перед частью сегмента данных кучи (есть еще одна часть кучи, управляемая анонимными отображениями памяти, которая не находится в сегменте данных).
С small
32-разрядные инструкции адресации модели памяти используются для адресации символов в x86_64. Это делает код меньше и быстрее. Некоторый сборочный вывод при использовании 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
При этом используется 32-битная инструкция перемещения (длиной 5 байт) для установки значения bar.1535
символ (это значение равно адресу местоположения символа) в младшие 32 бита RBX
регистр (старшие 32 бита обнуляются). bar.1535
Сам символ выделяется с помощью .comm
директивы. Память для baz
ОБЩИЙ блок распределяется впоследствии. Так как bar.1535
очень большой, baz_
в конечном итоге более 2 ГиБ с начала .bss
раздел. Это создает проблему во втором movl
инструкция, поскольку не 32-битное (подписанное) смещение от RIP
следует использовать для решения b
переменная, где значение EAX
должен быть перемещен в. Это обнаруживается только во время соединения. Сам ассемблер не знает соответствующего смещения, так как он не знает, каково значение указателя инструкции (RIP
) будет (это зависит от абсолютного виртуального адреса, куда загружается код, и это определяется компоновщиком), поэтому он просто устанавливает смещение 0
а затем создает запрос на перемещение типа R_X86_64_PC32
, Он инструктирует компоновщик исправлять значение 0
с реальным значением смещения. Но он не может этого сделать, поскольку значение смещения не помещается в 32-разрядное целое число со знаком и, следовательно, выручает.
С medium
Модель памяти на месте вещей выглядит так:
movabsq $bar.1535, %r10
...
movl %eax, baz_+4(%rip)
...
.local bar.1535
.largecomm bar.1535,2575411200,32
...
.comm baz_,12,16
Сначала используется 64-битная инструкция немедленного перемещения (длиной 10 байт) для помещения 64-битного значения, которое представляет адрес bar.1535
в реестр R10
, Память для bar.1535
символ выделяется с помощью .largecomm
директива и, таким образом, она заканчивается в .lbss
секция эльфийская .lbss
используется для хранения символов, которые могут не помещаться в первые 2 ГиБ (и, следовательно, не должны быть адресованы с использованием 32-битных инструкций или RIP-относительной адресации), в то время как меньшие вещи идут на .bss
(baz_
по-прежнему выделяется с помощью .comm
и не .largecomm
). Так как .lbss
раздел размещен после .bss
раздел в скрипте компоновщика ELF, baz_
не будет недоступен при использовании 32-битной RIP-адресации.
Все режимы адресации описаны в System V ABI: Дополнение к процессору архитектуры AMD64. Это тяжелое техническое чтение, но оно обязательно нужно прочитать любому, кто действительно хочет понять, как работает 64-битный код в большинстве x86_64 Unix.
Когда ALLOCATABLE
вместо этого используется массив, gfortran
выделяет кучу памяти (наиболее вероятно реализованную как анонимную карту памяти, учитывая большой размер выделения):
movl $2575411200, %edi
...
call malloc
movq %rax, %rdi
Это в основном RDI = malloc(2575411200)
, С тех пор элементы bar
доступны с помощью положительных смещений от значения, хранящегося в RDI
:
movl 51190040(%rdi), %eax
movl %eax, baz_+4(%rip)
Для мест, которые более 2 ГиБ с начала bar
, используется более сложный метод. Например, реализовать b = bar(12,144*144*450)
gfortran
выделяет:
; Some computations that leave the offset in RAX
movl (%rdi,%rax), %eax
movl %eax, baz_+4(%rip)
Этот код не подвержен влиянию модели памяти, поскольку ничего не предполагается относительно адреса, по которому будет выполняться динамическое распределение. Кроме того, поскольку массив не передается, дескриптор не создается. Если вы добавите еще одну функцию, которая принимает массив в форме предполагаемой формы и передать bar
к нему дескриптор для bar
создается как автоматическая переменная (т.е. в стеке foo
). Если массив сделан статическим с SAVE
атрибут, дескриптор помещается в .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)
Первый шаг готовит аргумент вызова функции (в моем примере call boo(bar)
где boo
имеет интерфейс, который объявляет его как принимающий массив предполагаемой формы). Перемещает адрес дескриптора массива bar
в EDI
, Это 32-битный немедленный ход, поэтому ожидается, что дескриптор будет в первых 2 ГиБ. Действительно, он выделяется в .bss
в обоих small
а также medium
Модели памяти как это:
.local bar.1580
.comm bar.1580,72,32
Нет, большие статические массивы (как ваш bar
) может превышать лимит, если вы не используете -mcmodel=medium
, Но выделяемые ресурсы лучше, конечно. Для выделяемых объектов только 2 дескриптора массива должны умещаться в 2 ГБ, а не весь массив.
Из ссылки 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.