mgi*_*son 16 fortran memory-management x86-64 gfortran
我有一些代码在编译时给我重定位错误,下面是一个说明问题的示例:
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
Run Code Online (Sandbox Code Playgroud)
编译这个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
Run Code Online (Sandbox Code Playgroud)
但是如果我使用它会有效gfortran -O3 -mcmodel=medium -g -o test test.f.另请注意,如果我使数组可分配并在子例程中分配它,它就可以工作.
我的问题是究竟做了-mcmodel=medium什么?我的印象是代码的两个版本(带有allocatable数组和没有数组的代码)或多或少等同......
Hri*_*iev 28
由于bar相当大,编译器会生成静态分配而不是堆栈上的自动分配.使用.commassembly指令创建静态数组,该指令在所谓的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
Run Code Online (Sandbox Code Playgroud)
这使用32位移动指令(5个字节长)将bar.1535符号的值(该值等于符号位置的地址)放入RBX寄存器的低32位(高32位变为零).该bar.1535符号本身是使用分配的.comm指令.baz之后会分配COMMON块的内存.因为bar.1535非常大,baz_从该.bss部分的开头结束超过2 GiB .这在第二movl条指令中造成问题,因为RIP应该使用非32位(带符号)的偏移来解决必须移入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
Run Code Online (Sandbox Code Playgroud)
首先,使用64位立即移动指令(10个字节长)来放置表示bar.1535寄存器地址的64位值R10.bar.1535符号的内存使用.largecomm指令分配,因此它以.lbssELF exectuable 的部分结束..lbss用于存储可能不适合前2 GiB的符号(因此不应使用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
Run Code Online (Sandbox Code Playgroud)
这基本上是RDI = malloc(2575411200).从那时起,bar通过使用存储在RDI以下值中的正偏移来访问元素:
movl 51190040(%rdi), %eax
movl %eax, baz_+4(%rip)
Run Code Online (Sandbox Code Playgroud)
对于从一开始就超过2 GiB的位置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)
Run Code Online (Sandbox Code Playgroud)
此代码不受内存模型的影响,因为不会假设将进行动态分配的地址.此外,由于没有传递数组,因此没有构建描述符.如果添加另一个采用假定形状数组并传递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)
Run Code Online (Sandbox Code Playgroud)
第一步准备函数调用的参数(在我的示例中call boo(bar),boo有一个接口声明它采用假定形状的数组).它将数组描述符的地址移动bar到EDI.这是一个32位的立即移动,因此描述符应该在前2 GiB中.实际上,它分配在.bss两个small和medium内存模型中,如下所示:
.local bar.1580
.comm bar.1580,72,32
Run Code Online (Sandbox Code Playgroud)
不,bar如果您不使用,大型静态数组(作为您的)可能会超出限制-mcmodel=medium.但是,可分配的东西当然更好.对于allocatables,只有数组描述符必须适合2 GB,而不是整个数组.
来自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.
Run Code Online (Sandbox Code Playgroud)