Rustコンパイラはalignedかつpackedな構造体を表現できない
実世界では微妙に出てくるにもかかわらず、Rustコンパイラはalignedかつpackedな構造体を表現できないためBindgenでコードを生成出来ないライブラリがある
Cでは構造体に対して__attribute__
を用いてアライメントやフィールドの詰め込みを指定できる。__attribute__(aligned(4))
とかで、詰め込みは__attribute__(packed)
で指定可能。#[repr(packed)]
と#[repr(align(n))]
で指定できる。Rustの構造体のpackに関しては公式ドキュメントが分かりやすい。
要約すると、構造体内のフィールドをどのように配置するかを指定するかで、以下の三種類があるということだ。
#[repr(C)]
:フィールドをC言語の順序で配置する。- これはフィールドを愚直に書かれた順序に詰め込む。なのでソースコードから(コンパイラ依存ではあるが)基本的にバイナリレイアウトがそのままわかる。
#[repr(rust)]
: Rust標準- アライメントを跨がないようにしつつ、フィールドの順序を並べ替えてバイナリサイズを最適化する。
#[repr(packed(n))]
:フィールドを詰め込みで配置する。ただし、アライメントをnとしてそれは守る。nを指定しない場合は1とみなされる。
#[repr(rust)]
でわざわざアライメントを考慮した詰め込みを行うのは実行効率が関係している。uint64_t
の場合は8の倍数のアドレスに配置されていないと直接メモリからLOAD命令を実行できない。#[repr(packed)]
を指定した場合は一部ずつ読み取り、bit shiftしてbit orを計算することで元の値をレジスタに入れる必要がある。packed(n)
で指定されたnのアライメントのみを守るようになる。
また、先に述べたようにドキュメントに記載されていないものの#[repr(align(n))]
も安定化されている。
これを指定すると構造体自体はnのアライメントを守って配置されるようになるし、内部のフィールドも当然この指定されたアライメントを跨がないように配置される(大きくなるので別に動作が変わることはない)。
この2つの機能はある意味アライメントを下げる指定と上げる指定と考えることが出来る。#[repr(packed)]
と#[repr(align)]
を両方同時に指定することは禁止されている。#[repr(packed)]
を構造体内部に、#[repr(align)]
を構造体自体のアライメントとする案もあったが、#[repr(align)]
が追加されると#[repr(packed)]
の意味が変更されるややこしいことになるので簡単のために禁止されたようだ。
Cの仕様
ところでGCCとClangでは__attribute__((packed))
と__attribute__((aligned(n)))
を 両方同時に 指定出来る。
こんなん何に使うねんという感じがするし、実際Rustでもこうしたケースは重要だとは考えられなかったが、実際は使われるケースが稀にある。
;
このように指定すると構造体自体は8バイトで並ぶがその内部のビット配列を構造体で分解出来る。便利ですね。そうかな……?mlx5_dv.h
ではこうしたコードがマジで存在する。