web-dev-qa-db-ja.com

ダミーのための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は正確に何をするのかということです。 2つのバージョンのコード(allocatable配列があるバージョンとないバージョン)は多かれ少なかれ同等であるという印象を受けました...

16
mgilson

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_ディレクティブを使用して割り当てられます。その後、bazCOMMONブロックのメモリが割り当てられます。 _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にあると予想されます。実際、次のようにsmallmediumの両方のメモリモデルの_.bss_に割り当てられます。

_.local  bar.1580
.comm   bar.1580,72,32
_
28
Hristo Iliev

いいえ、-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.
8
Vladimir F