kubo39's blog

ただの雑記です。

ざっくりバイナリ・メモリの世界 for D言語を考えてみた、elfutils便利、そしてcapacityが重い操作というはなし

KOBA789さんのYouTube配信みてて、似たような内容書くとどうなるかな、と考えてたが所有権もライフタイムもどっかいってしまった。。

Hello, World!?

バイナリの気持ちを考えるところから、なのでハードルが高いんだよな。 なんとかついていこう。

import std.stdio;

void main()
{
    writeln("Hello, World!");
}

LDCの以下のバージョンを使うこととする。

$ ldc2 --version| head -2
LDC - the LLVM D compiler (1.36.0):
  based on DMD v2.106.1 and LLVM 17.0.6
$ ldc2 -c hello.d

readelfを使って眺めてみよう。

$ readelf -W -S hello.o
There are 63 section headers, starting at offset 0x4088:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .strtab           STRTAB          0000000000000000 003228 000e5d 00      0   0  1
  [ 2] .text             PROGBITS        0000000000000000 000040 000000 00  AX  0   0  4
  [ 3] .text._Dmain      PROGBITS        0000000000000000 000040 000019 00  AX  0   0 16
  [ 4] .rela.text._Dmain RELA            0000000000000000 002208 000030 18   I 62   3  8
  [ 5] .text._D3std5stdio__T7writelnTAyaZQnFNfQjZv PROGBITS        0000000000000000 000060 0000c4 00  AX  0   0 16
  [ 6] .rela.text._D3std5stdio__T7writelnTAyaZQnFNfQjZv RELA            0000000000000000 002238 0000a8 18   I 62   5  8
  [ 7] .gcc_except_table._D3std5stdio__T7writelnTAyaZQnFNfQjZv PROGBITS        0000000000000000 000124 000018 00   A  0   0  4
  [ 8] .text.main        PROGBITS        0000000000000000 000140 000028 00  AX  0   0 16
  [ 9] .rela.text.main   RELA            0000000000000000 0022e0 000030 18   I 62   8  8
  [10] .text._D3std5stdio4File17LockingTextWriter__T3putTAyaZQjMFNfMQlZv PROGBITS        0000000000000000 000170 0000ba 00  AX  0   0 16
  [11] .rela.text._D3std5stdio4File17LockingTextWriter__T3putTAyaZQjMFNfMQlZv RELA            0000000000000000 002310 000078 18   I 62  10  8
  [12] .text._D3std5stdio4File17LockingTextWriter__T3putTaZQhMFNfMaZv PROGBITS        0000000000000000 000230 00025b 00  AX  0   0 16
  [13] .rela.text._D3std5stdio4File17LockingTextWriter__T3putTaZQhMFNfMaZv RELA            0000000000000000 002388 000180 18   I 62  12  8
  [14] .text._D3std5stdio__T13trustedFwriteTaZQsFNbNiNePOS4core4stdcQBx8_IO_FILExAaZm PROGBITS        0000000000000000 000490 000030 00  AX  0   0 16
  [15] .rela.text._D3std5stdio__T13trustedFwriteTaZQsFNbNiNePOS4core4stdcQBx8_IO_FILExAaZm RELA            0000000000000000 002508 000018 18   I 62  14  8
  [16] .text._D3std9exception__T7enforceHTCQBcQBb14ErrnoExceptionZ__TQBlTiZQBrFNfiLAxaAyamZi PROGBITS        0000000000000000 0004c0 00006d 00  AX  0   0 16
  [17] .rela.text._D3std9exception__T7enforceHTCQBcQBb14ErrnoExceptionZ__TQBlTiZQBrFNfiLAxaAyamZi RELA            0000000000000000 002520 000018 18   I 62  16  8
  [18] .text._D3std5stdio4File17LockingTextWriter__T3putTAyaZQjMFMQjZ12__dgliteral3MFNaNbNiNfZAxa PROGBITS        0000000000000000 000530 00000d 00  AX  0   0 16
  [19] .text._D3std5stdio4File17LockingTextWriter__T3putTyaZQiMFNfMyaZv PROGBITS        0000000000000000 000540 00025b 00  AX  0   0 16
  [20] .rela.text._D3std5stdio4File17LockingTextWriter__T3putTyaZQiMFNfMyaZv RELA            0000000000000000 002538 000180 18   I 62  19  8
  [21] .text._D3std9exception__T7bailOutHTCQBcQBb14ErrnoExceptionZQBiFNfAyamMAxaZNn PROGBITS        0000000000000000 0007a0 0000c4 00  AX  0   0 16
  [22] .rela.text._D3std9exception__T7bailOutHTCQBcQBb14ErrnoExceptionZQBiFNfAyamMAxaZNn RELA            0000000000000000 0026b8 0000d8 18   I 62  21  8
  [23] .text._D6object__T4idupTxaZQjFNaNbNdNfAxaZAya PROGBITS        0000000000000000 000870 000023 00  AX  0   0 16
  [24] .rela.text._D6object__T4idupTxaZQjFNaNbNdNfAxaZAya RELA            0000000000000000 002790 000018 18   I 62  23  8
  [25] .text._D4core8internal5array11duplication__T4_dupTxaTyaZQmFNaNbNeMAxaZAya PROGBITS        0000000000000000 0008a0 000047 00  AX  0   0 16
  [26] .rela.text._D4core8internal5array11duplication__T4_dupTxaTyaZQmFNaNbNeMAxaZAya RELA            0000000000000000 0027a8 000048 18   I 62  25  8
  [27] .text._D4core8internal5array11duplication__T8_dupCtfeTxaTyaZQqFNaNbNfMAxaZAya PROGBITS        0000000000000000 0008f0 0000cd 00  AX  0   0 16
  [28] .rela.text._D4core8internal5array11duplication__T8_dupCtfeTxaTyaZQqFNaNbNfMAxaZAya RELA            0000000000000000 0027f0 000048 18   I 62  27  8
  [29] .text._D4core8internal5array9appending__T17_d_arrayappendcTXHTAyaTyaZQBcFNaNbNcNeMNkKQxmZQBb PROGBITS        0000000000000000 0009c0 00005c 00  AX  0   0 16
  [30] .rela.text._D4core8internal5array9appending__T17_d_arrayappendcTXHTAyaTyaZQBcFNaNbNcNeMNkKQxmZQBb RELA            0000000000000000 002838 000030 18   I 62  29  8
  [31] .text._D3std3utf__T6strideTAaZQlFNaNfQkZk PROGBITS        0000000000000000 000a20 00006f 00  AX  0   0 16
  [32] .rela.text._D3std3utf__T6strideTAaZQlFNaNfQkZk RELA            0000000000000000 002868 000048 18   I 62  31  8
  [33] .text._D3std3utf__T11decodeFrontVEQBa8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAaZQDnFNaNfKQmZw PROGBITS        0000000000000000 000a90 00001f 00  AX  0   0 16
  [34] .rela.text._D3std3utf__T11decodeFrontVEQBa8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAaZQDnFNaNfKQmZw RELA            0000000000000000 0028b0 000018 18   I 62  33  8
  [35] .text._D3std3utf__T6encodeVEQu8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0ZQDdFNaNfJG1wwZm PROGBITS        0000000000000000 000ab0 0000ca 00  AX  0   0 16
  [36] .rela.text._D3std3utf__T6encodeVEQu8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0ZQDdFNaNfJG1wwZm RELA            0000000000000000 0028c8 000078 18   I 62  35  8
  [37] .text._D3std5range10primitives__T5emptyTAaZQkFNaNbNdNiNfMKQsZb PROGBITS        0000000000000000 000b80 000014 00  AX  0   0 16
  [38] .text._D3std3utf__T11decodeFrontVEQBa8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAaZQDnFNaNeMKQnJmZw PROGBITS        0000000000000000 000ba0 0001ea 00  AX  0   0 16
  [39] .rela.text._D3std3utf__T11decodeFrontVEQBa8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAaZQDnFNaNeMKQnJmZw RELA            0000000000000000 002940 000168 18   I 62  38  8
  [40] .text._D3std3utf__T10decodeImplVbi1VEQBd8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAxaZQDrFNaQkKmZw PROGBITS        0000000000000000 000d90 0002e9 00  AX  0   0 16
  [41] .rela.text._D3std3utf__T10decodeImplVbi1VEQBd8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAxaZQDrFNaQkKmZw RELA            0000000000000000 002aa8 000288 18   I 62  40  8
  [42] .text._D3std3utf__T10decodeImplVbi1VEQBd8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAxaZQDrFQiKmZ10invalidUTFMFNaNbZCQFfQFe12UTFException PROGBITS        0000000000000000 001080 00001e 00  AX  0   0 16
  [43] .rela.text._D3std3utf__T10decodeImplVbi1VEQBd8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAxaZQDrFQiKmZ10invalidUTFMFNaNbZCQFfQFe12UTFException RELA            0000000000000000 002d30 000030 18   I 62  42  8
  [44] .text._D3std3utf__T10decodeImplVbi1VEQBd8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAxaZQDrFQiKmZ__T9exceptionTQBbZQpFNaNbNfQBoQDrZCQFuQFt12UTFException PROGBITS        0000000000000000 0010a0 00023c 00  AX  0   0 16
  [45] .rela.text._D3std3utf__T10decodeImplVbi1VEQBd8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAxaZQDrFQiKmZ__T9exceptionTQBbZQpFNaNbNfQBoQDrZCQFuQFt12UTFException RELA            0000000000000000 002d60 000150 18   I 62  44  8
  [46] .text._D3std3utf__T10decodeImplVbi1VEQBd8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAxaZQDrFQiKmZ11outOfBoundsMFNaNbZCQFgQFf12UTFException PROGBITS        0000000000000000 0012e0 00001e 00  AX  0   0 16
  [47] .rela.text._D3std3utf__T10decodeImplVbi1VEQBd8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0TAxaZQDrFQiKmZ11outOfBoundsMFNaNbZCQFgQFf12UTFException RELA            0000000000000000 002eb0 000030 18   I 62  46  8
  [48] .text._D3std3utf__T13_utfExceptionVEQBc8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0ZQDmFNaNfQCiwZw PROGBITS        0000000000000000 001300 0000cf 00  AX  0   0 16
  [49] .rela.text._D3std3utf__T13_utfExceptionVEQBc8typecons__T4FlagVAyaa19_7573655265706c6163656d656e744463686172ZQCai0ZQDmFNaNfQCiwZw RELA            0000000000000000 002ee0 0000a8 18   I 62  48  8
  [50] .rodata.str1.1    PROGBITS        0000000000000000 0013cf 00000e 01 AMS  0   0  1
  [51] .rodata.str1.16   PROGBITS        0000000000000000 0013e0 000169 01 AMS  0   0 16
  [52] .data._D5hello12__ModuleInfoZ PROGBITS        0000000000000000 001550 000010 00  WA  0   0  8
  [53] __minfo           PROGBITS        0000000000000000 001560 000008 00 WAR  0   0  8
  [54] .rela__minfo      RELA            0000000000000000 002f88 000018 18   I 62  53  8
  [55] .group            GROUP           0000000000000000 0018f8 00000c 04     62  94  4
  [56] .data.DW.ref._d_eh_personality PROGBITS        0000000000000000 001568 000008 00 WAG  0   0  8
  [57] .rela.data.DW.ref._d_eh_personality RELA            0000000000000000 002fa0 000018 18   G 62  56  8
  [58] .comment          PROGBITS        0000000000000000 001570 000014 01  MS  0   0  1
  [59] .note.GNU-stack   PROGBITS        0000000000000000 001584 000000 00      0   0  1
  [60] .eh_frame         X86_64_UNWIND   0000000000000000 001588 000370 00   A  0   0  8
  [61] .rela.eh_frame    RELA            0000000000000000 002fb8 000270 18   I 62  60  8
  [62] .symtab           SYMTAB          0000000000000000 001908 000900 18      1  37  8
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  R (retain), D (mbind), l (large), p (processor specific)

大量のシンボルが出力されている。

Hello, World!はここにある。

$ readelf -x .rodata.str1.1 hello.o

Hex dump of section '.rodata.str1.1':
  0x00000000 48656c6c 6f2c2057 6f726c64 2100     Hello, World!.

セクション.rodata.str1.1の中に置かれている。

次に実行ファイルをみてみよう。

$ readelf -W -S hello                                                [12/17561]There are 35 section headers, starting at offset 0x119f10:                                                                                                                                                                                              Section Headers:                                                                                                              [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000000350 000350 00001c 00   A  0   0  1
  [ 2] .note.gnu.property NOTE            0000000000000370 000370 000020 00   A  0   0  8
  [ 3] .note.gnu.build-id NOTE            0000000000000390 000390 000024 00   A  0   0  4
  [ 4] .note.ABI-tag     NOTE            00000000000003b4 0003b4 000020 00   A  0   0  4
  [ 5] .gnu.hash         GNU_HASH        00000000000003d8 0003d8 000024 00   A  6   0  8
  [ 6] .dynsym           DYNSYM          0000000000000400 000400 000ea0 18   A  7   1  8
  [ 7] .dynstr           STRTAB          00000000000012a0 0012a0 000815 00   A  0   0  1
  [ 8] .gnu.version      VERSYM          0000000000001ab6 001ab6 000138 02   A  6   0  2
  [ 9] .gnu.version_r    VERNEED         0000000000001bf0 001bf0 000100 00   A  7   4  8
  [10] .rela.dyn         RELA            0000000000001cf0 001cf0 021138 18   A  6   0  8
  [11] .rela.plt         RELA            0000000000022e28 022e28 000d68 18  AI  6  27  8
  [12] .init             PROGBITS        0000000000024000 024000 00001b 00  AX  0   0  4
  [13] .plt              PROGBITS        0000000000024020 024020 000900 10  AX  0   0 16
  [14] .plt.got          PROGBITS        0000000000024920 024920 000008 08  AX  0   0  8
  [15] .text             PROGBITS        0000000000024930 024930 057670 00  AX  0   0 16
  [16] .fini             PROGBITS        000000000007bfa0 07bfa0 00000d 00  AX  0   0  4
  [17] .rodata           PROGBITS        000000000007c000 07c000 018e57 00   A  0   0 16
  [18] .eh_frame_hdr     PROGBITS        0000000000094e58 094e58 004994 00   A  0   0  4
  [19] .eh_frame         PROGBITS        00000000000997f0 0997f0 013e60 00   A  0   0  8
  [20] .gcc_except_table PROGBITS        00000000000ad650 0ad650 000bc8 00   A  0   0  4
  [21] .tdata            PROGBITS        00000000000af400 0ae400 000008 00 WATo  0   0 16
  [22] .tbss             NOBITS          00000000000af410 0ae408 0001c1 00 WAT  0   0 16
  [23] .init_array       INIT_ARRAY      00000000000af410 0ae410 000040 08  WA  0   0  8
  [24] .fini_array       FINI_ARRAY      00000000000af450 0ae450 000010 08  WA  0   0  8
  [25] .data.rel.ro      PROGBITS        00000000000af460 0ae460 00a470 00  WA  0   0 16
  [26] .dynamic          DYNAMIC         00000000000b98d0 0b88d0 000220 10  WA  7   0  8
  [27] .got              PROGBITS        00000000000b9af0 0b8af0 000510 08  WA  0   0  8
  [28] .data             PROGBITS        00000000000ba000 0b9000 00db70 00  WA  0   0 64
(...)

.rodata.str1.1はなくなってしまっている。 .rodataは実行ファイルを作るときにはマージされてしまう。

そうすると.rodataのどこにあるんだよという話になるが、これをreadelf -x .rodataで探すとなると折り返しとかもあってなかなか探しにくい。

こういう時はelfutilsのreadelfの--stringsオプションが役に立つ。

$ eu-readelf --strings=.rodata hello| grep Hello
  [     0]  Hello, World!

こいつは.rodataセクションの先頭からオフセット0のところにあることがわかる。

$ readelf -x .rodata hello| head

Hex dump of section '.rodata':
  0x0007c000 48656c6c 6f2c2057 6f726c64 21000000 Hello, World!...

ltrace

D言語はlibcのstdioを使っているので、GoやRustのようにwrite(2)をバッファリングするところを自前でもたずにlibcに丸投げしている。

ltraceを単純にかけてみるとわかる。

$ ldc2 hello.d
$ ltrace ./hello
(...)
fwide(0x7fb53048a780, 0)                                                    = 0
flockfile(0x7fb53048a780, 0, 0, 11)                                         = 0
fwrite("Hello, World!", 1, 13, 0x7fb53048a780)                              = 13
fputc_unlocked(10, 0x7fb53048a780, 0x5616b898eabc, 0x5616b716800dHello, World!
)          = 10                                                                                                             funlockfile(0x7fb53048a780, 0x5616b898eab0, 0xffffffff, 0x7fb530384697)     = 1
(...)

改行込みなのでwrite(2)に渡すのは長さ14になるが、fwrite(3)には長さ13にない。 その下でfputc_unlockedが改行を入れてるのだが、libc内でバッファリングしてるためwrite(2)に渡すときはちゃんと一度に渡せるのだ。

スタック/ヒープ?

ヒープはGCってやつがなんとかしてくれるらしいです(は?)

エイリアス

デフォルト?だと代入構文はポインタのエイリアスになる。 だいたいの言語はそうだろという気になってしまうが、Rust(ムーヴセマンティクス)やNim(デフォルトだとコピーになってそれぞれの変数は別のメモリアドレスへの参照になる)なんかもある。

再割り当てが起きたタイミングで別のメモリアドレスを指し示す。

このへんはGCがうまくやってくれている。

import std.stdio;

void main()
{
    auto a = [1, 2, 3];
    auto b = a;
    assert(a is b);
    b ~= 4;
    writeln(a);  // [1, 2, 3]
    writeln(b);  // [1, 2, 3, 4]
    assert(a !is b);
}

再割り当てが起きない場合は?

先頭のアドレスは同じでも長さ情報が異なるので同値性判定はfalseになる。 このあたりはランタイムが面倒をみてくれる。

import std.stdio;

void main()
{
    auto a = [1, 2, 3];
    auto b = a;
    assert(a is b);
    b = b[0..2];
    writeln(a);  // [1, 2, 3]
    writeln(b);  // [1, 2]
    assert(a.ptr == b.ptr);
    assert(a !is b);  // 長さが異なるので同値性判定はfalse
   
}

あとついでに書いとくとCoWとかではない。

import std.stdio;

void main()
{
    int[] a = [1, 2];
    auto b = a;
    b[0] = 42;
    writeln(a);  // [42, 2]
}

コピー

思ったより書くこと思いつかなくてびっくりした(オワリ)

auto b = a.dup;  // コピー
auto c = a.idup; // immutableなコピー

いやそんなことはないのだが、コピーコンストラクタとか@disable thisとか、そのへんもあるので割愛する。

realloc

動的配列について考えてみよう。

こんな感じのデータがスタック上に置かれてるイメージ。 まああとでわかります。

struct DArray
{
    size_t length;
    void* ptr;
}

以下のようなコードを実行してみる。

import std.stdio;

void f()
{
    int[] s = [1, 2, 3, 4, 5];
    writefln("s = %s", s);

    writeln("s.sizeof: ", s.sizeof);  // --> 16

    writeln("&s: ", &s);
    writeln("*cast(size_t*) &s: ", *cast(size_t*) &s);  // .length
    writeln("*((cast(size_t**) &s) + 1): ", *((cast(size_t**) &s) + 1));  // .ptr

    writeln("&s[0]: ", &s[0]);
    writeln("s.ptr: ", s.ptr);

    writeln("s.length: ", s.length);
    writeln("s.capacity: ", s.capacity);

    // realloc
    s ~= [6, 7, 8];
    writefln("s = %s", s);

    writeln("s.sizeof: ", s.sizeof);  // --> 16

    writeln("&s: ", &s);
    writeln("&s[0]: ", &s[0]);
    writeln("s.ptr: ", s.ptr);

    writeln("s.length: ", s.length);
    writeln("s.capacity: ", s.capacity);
}

void main()
{
    f();
}
$ ldc2 -run main.d
s = [1, 2, 3, 4, 5]
s.sizeof: 16
&s: 7FFE9ACCA400
*cast(size_t*) &s: 5
*((cast(size_t**) &s) + 1): 7FA7F0609000
&s[0]: 7FA7F0609000
s.ptr: 7FA7F0609000
s.length: 5
s.capacity: 7
s = [1, 2, 3, 4, 5, 6, 7, 8]
s.sizeof: 16
&s: 7FFE9ACCA400
&s[0]: 7FA7F060A000
s.ptr: 7FA7F060A000
s.length: 8
s.capacity: 11
  • &sは再割り当て前後で変わっていない(スタック)
  • s.ptrは変わってる
    • reallocが起きたのでもちろん変わる
    • 元のヒープはGCがfreeしてくれる(デフォルトのGCだと割り当てタイミング)
  • s.lengthは変わってる
    • だれかがスタック上の値を書き換えてる!
  • s.capacityはスタック上にない!

てきとうなコードでLLVM-IRみてみる。

pragma(mangle, "foo")
void foo()
{
    int[] s = [1, 2, 3, 4, 5];
    s ~= [6, 7, 8];
}

コンパイルするぞい。

$ ldc2 -O2 --output-ll lowered.d

するとこんなIRになる。

; Function Attrs: uwtable
define void @foo() local_unnamed_addr #0 {
if.i:
  %pxx.i.i = alloca { i64, ptr }, align 8         ; [#uses = 6, size/byte = 16]
  %.gc_mem = tail call { i64, ptr } @_d_newarrayU(ptr nonnull @_D11TypeInfo_Ai6__initZ, i64 5) #1 ; [#uses = 1]
  %.ptr = extractvalue { i64, ptr } %.gc_mem, 1   ; [#uses = 3]
  store <4 x i32> <i32 1, i32 2, i32 3, i32 4>, ptr %.ptr, align 4
  %0 = getelementptr inbounds [5 x i32], ptr %.ptr, i64 0, i64 4 ; [#uses = 1, type = ptr]
  store i32 5, ptr %0, align 4
  call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %pxx.i.i)
  store i64 5, ptr %pxx.i.i, align 8
  %1 = getelementptr inbounds { i64, ptr }, ptr %pxx.i.i, i64 0, i32 1 ; [#uses = 2, type = ptr]
  store ptr %.ptr, ptr %1, align 8
  %2 = call { i64, ptr } @_d_arrayappendcTX(ptr nonnull @_D11TypeInfo_Ai6__initZ, ptr nonnull dereferenceable(16) %pxx.i.i, i64 3) #3 ; [#uses = 0]
  %.ptr1.i.i = load ptr, ptr %1, align 8          ; [#uses = 3]
  %.len2.i.i = load i64, ptr %pxx.i.i, align 8    ; [#uses = 2]
  call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %pxx.i.i)
  %bounds.cmp.i = icmp ugt i64 %.len2.i.i, 5      ; [#uses = 1]
  br i1 %bounds.cmp.i, label %bounds.ok6.i, label %bounds.fail.i

bounds.ok6.i:                                     ; preds = %if.i
  %3 = getelementptr inbounds i32, ptr %.ptr1.i.i, i64 5 ; [#uses = 1, type = ptr]
  store i32 6, ptr %3, align 1
  %__arrayliteral_on_stack3.sroa.2.0..sroa_idx = getelementptr inbounds i32, ptr %.ptr1.i.i, i64 6 ; [#uses = 1, type = ptr]
  store i32 7, ptr %__arrayliteral_on_stack3.sroa.2.0..sroa_idx, align 1
  %__arrayliteral_on_stack3.sroa.3.0..sroa_idx = getelementptr inbounds i32, ptr %.ptr1.i.i, i64 7 ; [#uses = 1, type = ptr]
  store i32 8, ptr %__arrayliteral_on_stack3.sroa.3.0..sroa_idx, align 1
  ret void

bounds.fail.i:                                    ; preds = %if.i
  call void @_d_arraybounds_index({ i64, ptr } { i64 75, ptr @.str }, i32 95, i64 5, i64 %.len2.i.i) #2
  unreachable
}

ランタイムの気持ちを考えてグっと睨むと

  • _d_newarrayUがlengthとptrの組を返してる(これがスタックにおかれてる)
  • _d_arrayappendcTXで再割り当てをしている
    • この関数がスタック上の.lengthの値を書き換えていた
  • capacity分の容量確保はどちらも__arrayAllocでやってる

のようなことがわかる。

capacityは?

こいつ結局スタックいないじゃん。

じゃあちょっとコード変えてみるか。

pragma(mangle, "foo")
auto foo()
{
    int[] s = [1, 2, 3, 4, 5];
    s ~= [6, 7, 8];
    return s.capacity();
}

こんなLLVM IRを吐く。

define i64 @foo() local_unnamed_addr #0 {
if.i:
  %arr.i = alloca { i64, ptr }, align 8           ; [#uses = 5, size/byte = 16]
  %pxx.i.i = alloca { i64, ptr }, align 8         ; [#uses = 6, size/byte = 16]
  %.gc_mem = tail call { i64, ptr } @_d_newarrayU(ptr nonnull @_D11TypeInfo_Ai6__initZ, i64 5) #1 ; [#uses = 1]
  %.ptr = extractvalue { i64, ptr } %.gc_mem, 1   ; [#uses = 3]
  store <4 x i32> <i32 1, i32 2, i32 3, i32 4>, ptr %.ptr, align 4
  %0 = getelementptr inbounds [5 x i32], ptr %.ptr, i64 0, i64 4 ; [#uses = 1, type = ptr]
  store i32 5, ptr %0, align 4
  call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %pxx.i.i)
  store i64 5, ptr %pxx.i.i, align 8
  %1 = getelementptr inbounds { i64, ptr }, ptr %pxx.i.i, i64 0, i32 1 ; [#uses = 2, type = ptr]
  store ptr %.ptr, ptr %1, align 8
  %2 = call { i64, ptr } @_d_arrayappendcTX(ptr nonnull @_D11TypeInfo_Ai6__initZ, ptr nonnull dereferenceable(16) %pxx.i.i, i64 3) #3 ; [#uses = 0]
  %.ptr1.i.i = load ptr, ptr %1, align 8          ; [#uses = 4]
  %.len2.i.i = load i64, ptr %pxx.i.i, align 8    ; [#uses = 3]
  call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %pxx.i.i)
  %bounds.cmp.i = icmp ugt i64 %.len2.i.i, 5      ; [#uses = 1]
  br i1 %bounds.cmp.i, label %bounds.ok6.i, label %bounds.fail.i

bounds.ok6.i:                                     ; preds = %if.i
  %3 = getelementptr inbounds i32, ptr %.ptr1.i.i, i64 5 ; [#uses = 1, type = ptr]
  store i32 6, ptr %3, align 1
  %__arrayliteral_on_stack3.sroa.2.0..sroa_idx = getelementptr inbounds i32, ptr %.ptr1.i.i, i64 6 ; [#uses = 1, type = ptr]
  store i32 7, ptr %__arrayliteral_on_stack3.sroa.2.0..sroa_idx, align 1
  %__arrayliteral_on_stack3.sroa.3.0..sroa_idx = getelementptr inbounds i32, ptr %.ptr1.i.i, i64 7 ; [#uses = 1, type = ptr]
  store i32 8, ptr %__arrayliteral_on_stack3.sroa.3.0..sroa_idx, align 1
  call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %arr.i)
  store i64 %.len2.i.i, ptr %arr.i, align 8
  %arr_arg.fca.1.gep.i = getelementptr inbounds { i64, ptr }, ptr %arr.i, i64 0, i32 1 ; [#uses = 1, type = ptr]
  store ptr %.ptr1.i.i, ptr %arr_arg.fca.1.gep.i, align 8
  %4 = call i64 @_d_arraysetcapacity(ptr nonnull @_D11TypeInfo_Ai6__initZ, i64 0, ptr nonnull %arr.i) #3 ; [#uses = 1]
  call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %arr.i)
  ret i64 %4

bounds.fail.i:                                    ; preds = %if.i
  call void @_d_arraybounds_index({ i64, ptr } { i64 75, ptr @.str }, i32 95, i64 5, i64 %.len2.i.i) #2
  unreachable
}

_d_arraysetcapacity??

こいつの新たにセットするcapacityに0を渡してることがわかる。現在のcapacity情報は配列の実体が割り当てられてるGCのblkinfoから毎回計算してるのだ。

まとめ

  • eu-readelfはときどき便利!
  • 代入のセマンティクスはデフォルトだと参照のエイリアス
  • 再割り当てが起きると指し示すメモリアドレスが変わる
  • capacityは意外と重い操作!
  • もう全部GCに任せたらええ!(ほんまか?(小声))

追記(2024/01/30)

これGNU binutilsのreadelfの-pオプションでいいな。elfutils便利とはなんだったのか。。

$ readelf -p .comment hello

String dump of section '.comment':
  [     0]  GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
  [    2b]  ldc version 1.36.0
  [    3e]  clang version 15.0.6