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
は変わってる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