19

コンパイル時に再配置エラーが発生するコードがいくつかあります。以下は、問題を説明する例です。

  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配列があるものとないもの)が多かれ少なかれ同等であるという印象を受けました...

4

2 に答える 2

33

非常に大きいためbar、コンパイラはスタックでの自動割り当てではなく静的割り当てを生成します。静的配列は、.commいわゆるCOMMONセクションに割り当てを作成するアセンブリディレクティブを使用して作成されます。そのセクションのシンボルが収集され、同じ名前のシンボルがマージされ(要求された最大サイズに等しいサイズの1つのシンボル要求に縮小され)、残りはほとんどの実行可能形式でBSS(初期化されていないデータ)セクションにマップされます。ELF実行可能ファイルでは、.bssセクションはヒープのデータセグメント部分の直前のデータセグメントにあります(データセグメントに存在しない匿名メモリマッピングによって管理される別のヒープ部分があります)。

メモリモデルでは、small32ビットアドレス指定命令を使用して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_、セクションの最初から2GiB以上になり.bssます。movlからの非32ビット(符号付き)オフセットを使用して変数RIPをアドレス指定する必要があるため、これは2番目の命令で問題を引き起こします。bEAXに移動する必要があります。これは、リンク時間中にのみ検出されます。アセンブラ自体は、命令ポインタ()の値がわからないため、適切なオフセットを知りません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レジスタに入れますR10bar.1535シンボルのメモリは.largecommディレクティブを使用して割り当てられるため.lbss、ELF実行可能セクションで終了します。.lbss最初の2GiBに収まらない可能性のあるシンボルを格納するために使用されます(したがって、32ビット命令またはRIP相対アドレス指定を使用してアドレス指定し.bssないbaz_.commください.largecomm)。このセクションはELFリンカースクリプト.lbssのセクションの後に配置されるため、 32ビットのRIP関連のアドレス指定を使用してアクセスできなくなることはありません。.bssbaz_

すべてのアドレッシングモードは、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)

の開始から2GiBを超える場所では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ビットの即時移動であるため、記述子は最初の2GiBにあると予想されます。実際、次のように、メモリモデルとメモリモデル.bssの両方に割り当てられます。smallmedium

.local  bar.1580
.comm   bar.1580,72,32
于 2012-10-18T14:49:51.377 に答える
10

barいいえ、を使用しない場合、(のように)大きな静的配列が制限を超える可能性があります-mcmodel=medium。しかし、もちろん、割り当て可能なものの方が優れています。割り当て可能なものの場合、アレイ記述子のみが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.
于 2012-10-16T14:37:24.457 に答える