コンパイル時に再配置エラーが発生するコードがいくつかあります。以下は、問題を説明する例です。
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
は正確に何をするのかということです。 2つのバージョンのコード(allocatable
配列があるバージョンとないバージョン)は多かれ少なかれ同等であるという印象を受けました...
bar
は非常に大きいため、コンパイラはスタックでの自動割り当てではなく静的割り当てを生成します。静的配列は、いわゆるCOMMONセクションに割り当てを作成する_.comm
_ Assemblyディレクティブを使用して作成されます。そのセクションのシンボルが収集され、同じ名前のシンボルがマージされ(要求された最大サイズに等しいサイズの1つのシンボル要求に縮小され)、残りはほとんどの実行可能形式で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
_シンボルの値(この値はシンボル位置のアドレスに等しい)をRBX
レジスタの下位32ビット(上位32ビット)に入れます。ビットはゼロになります)。 _bar.1535
_シンボル自体は、_.comm
_ディレクティブを使用して割り当てられます。その後、baz
COMMONブロックのメモリが割り当てられます。 _bar.1535
_は非常に大きいため、_baz_
_は_.bss
_セクションの先頭から2 GiB)を超えてしまいます。これにより、2番目の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
_
最初に、64ビットの即時移動命令(10バイト長)を使用して、_bar.1535
_のアドレスを表す64ビット値をレジスタ_R10
_に入れます。 _bar.1535
_シンボルのメモリは、_.largecomm
_ディレクティブを使用して割り当てられるため、ELF実行可能ファイルの_.lbss
_セクションで終了します。 _.lbss
_は、最初の2 GiB(したがって、32ビット命令またはRIP相対アドレス指定を使用してアドレス指定しないでください)に収まらない可能性のあるシンボルを格納するために使用されますが、小さいものは_.bss
_に移動します(_baz_
_は_.comm
_ではなく_.largecomm
_を使用して割り当てられます)。_.lbss
_セクションはの_.bss
_セクションの後に配置されるためELFリンカースクリプトである_baz_
_は、32ビットのRIP関連のアドレス指定を使用してアクセスできなくなることはありません。
すべてのアドレッシングモードは System V ABI:AMD64アーキテクチャプロセッササプリメント で説明されています。技術的には重い読み物ですが、64ビットコードがほとんどのx86_64Unixでどのように機能するかを本当に理解したい人は必読です。
代わりにALLOCATABLE
配列が使用される場合、gfortran
はヒープメモリを割り当てます(割り当てのサイズが大きい場合、匿名メモリマップとして実装される可能性があります)。
_movl $2575411200, %edi
...
call malloc
movq %rax, %rdi
_
これは基本的にRDI = malloc(2575411200)
です。それ以降、bar
の要素は、RDI
に格納されている値からの正のオフセットを使用してアクセスされます。
_movl 51190040(%rdi), %eax
movl %eax, baz_+4(%rip)
_
bar
の先頭から2 GiBを超える場所の場合、より複雑なメソッドが使用されます。たとえば、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ビットの即時移動であるため、記述子は最初の2GiBにあると予想されます。実際、次のようにsmall
とmedium
の両方のメモリモデルの_.bss
_に割り当てられます。
_.local bar.1580
.comm bar.1580,72,32
_
いいえ、-mcmodel=medium
を使用しない場合、大きな静的配列(bar
など)が制限を超える可能性があります。しかし、もちろん、割り当て可能なものの方が優れています。割り当て可能なものの場合、アレイ全体ではなく、アレイ記述子のみが2GBに収まる必要があります。
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.