リンカ基本事項 リロケーションの導入まで
今回シンボルに関する基本事項とリロケーションの導入まで行っていく。
ローカルシンボルが外部ファイルから参照できないことを確認する。そのために以下のsym0.sとsym1.sという2つのアセンブリファイルを用意する。
_start: mov $local_symbol, %rax mov $global_symbol, %rax
.global global_symbol local_symbol: nop global_symbol: nop
これらのファイルをコンパイルしてからリンクを試みると以下のようなエラーが発生する。
$ as sym0.s -o sym0.o $ as sym1.s -o sym1.o $ ld sym0.o sym1.o ld: warning: cannot find entry symbol _start; defaulting to 0000000000400078 sym0.o: In function `_start': (.text+0x3): undefined reference to `local_symbol'
まず一つ目の警告について、エントリーシンボルである_start
シンボルがないと言っている。プログラムは基本的に_start
というシンボルから始まることが決まっている。つまり最初はプログラムをメモリ上にロードするブートローダが動くようになっており、そのブートローダのプログラムに普通は_start
シンボルが記述されている。普通にgccでCソースコードから実行ファイルを作るときは、自動的にブートローダプログラムもリンクしてくれるのだが、今回はld
コマンドでブートローダプログラムを指定していないので、_start
シンボルが見つかっていない。ブートローダプログラムはcrt.S
というファイルであることが多い。
実際、gcc -v ret.c
を実行すると、リンクのフェイズで/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o
というファイルが最初にリンク対象ファイルに持ってこられていて、この中身を以下のコマンドで見てみると、確かに_start
シンボルが存在していることがわかる。
$ objdump -d /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o Disassembly of section .text: 0000000000000000 <_start>: 0: 31 ed xor %ebp,%ebp 2: 49 89 d1 mov %rdx,%r9 5: 5e pop %rsi 6: 48 89 e2 mov %rsp,%rdx 9: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp d: 50 push %rax e: 54 push %rsp f: 49 c7 c0 00 00 00 00 mov $0x0,%r8 16: 48 c7 c1 00 00 00 00 mov $0x0,%rcx 1d: 48 c7 c7 00 00 00 00 mov $0x0,%rdi 24: e8 00 00 00 00 callq 29 <_start+0x29> 29: f4 hlt
もう一つこれはエラーだが、local_symbol
というシンボルが定義されていないというエラーを出している。これは今回local_symbol
を.global ~
のように定義しておらずローカルシンボルとなっており、別のファイルからは参照できないからである。つまりこのように外部のシンボル(ラベル)に飛びたい時は、グローバルシンボルとして定義する必要があることがわかる。
ここまででラベルについて述べてきたが、ここからはリロケーションについて述べていく。オブジェクトファイルには「セクションXのシンボルYをオフセットZで埋めてね」というようなリロケーション情報が含まれている。このリロケーション情報をもとにリンカはバイナリパッチを行い、プログラムのメモリ上の配置を決める。具体的にメモリ上のどこに配置するかはリンカによって決まっている。
リロケーションを理解するために以下のreloc.s, reloc_labe.sという二つのアセンブリファイルを用意する。
.globl _start _start: movl $ref_32bit, %eax # 32bitラベルを参照 movw $ref_16bit, %ax # 16bitラベルを参照 movb $ref_8bit, %al # 8bitラベルを参照 jmp ref_as_jmp_label # PC 相対アドレスを参照 movl $ref_32bit + 32, %eax # オフセット付き mov $60, %rax syscall
.text .globl ref_32bit .globl ref_16bit .globl ref_8bit .globl ref_as_jmp_label nop ref_32bit: nop ref_16bit: nop ref_8bit: nop ref_as_jmp_label: nop
二つの目のファイルであるreloc_label.s
に、.text
というセクションがあるが、これはプログラムのテキストである命令を表すセクションである。セクションについては、ここによくまとまっっている。
これらのファイルをオブジェクトファイルに変換した後、リロケーション情報について見てみる。
$ readelf -r reloc.o Relocation section '.rela.text' at offset 0x170 contains 5 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000000001 00050000000a R_X86_64_32 0000000000000000 ref_32bit + 0 000000000007 00060000000c R_X86_64_16 0000000000000000 ref_16bit + 0 00000000000a 00070000000e R_X86_64_8 0000000000000000 ref_8bit + 0 000000000011 00050000000a R_X86_64_32 0000000000000000 ref_32bit + 20 00000000000c 000800000002 R_X86_64_PC32 0000000000000000 ref_as_jmp_label - 4
$ readelf -r reloc_label.o There are no relocations in this file.
このようにmovなどの命令を吐いているreloc.o
の方はリロケーションを行う必要があるので、readelf -r
コマンドによりリロケーション情報を出力している。
次回以降リロケーションについて深く調査していく。
参照
リンカ基本事項 シンボルの導入
前回リンクについて軽くまとめたが、リンクする際にはリンカが必要なわけだが、リンカはどのような処理を行っているのかをまとめてみた。
まず大きく分けてリンカが行っていることは以下の二つである。
- オブジェクトファイルをまとめて一つの実行ファイルにする
- ラベルの参照を解決してアドレスを決定する
まずオブジェクトファイルには機械語とデータ以外に、シンボル情報とリロケーション情報が含まれている。このシンボルは、アセンブリで言うところのラベルであり、アセンブルする際にシンボルへと変換される。シンボルは定義ずみシンボルと未定義シンボルの二つに分けられる。
- 定義ずみシンボル:アセンブリでラベルで表されるもので、ファイル内に実体があるシンボルのこと。ファイル内のオフセット、追加情報、文字列表現の情報などを持つ。
- 未定義シンボル:オブジェクトファイルに実体が存在しないシンボルのことで、追加情報や文字列表現の情報は持つが、ファイル内のオフセットに関する情報は持たない。
以下のアセンブリをアセンブルしてオブジェクトファイルを作り、中身をobjdumpで確認してみる。
.globl _start .globl sym0 _start: nop sym0: nop sym1: nop nop nop nop sym2: nop
$ objdump -d hoge.s hoge.o: file format mach-o 64-bit x86-64 Disassembly of section __TEXT,__text: 0000000000000000 <_start>: 0: 90 nop 0000000000000001 <sym0>: 1: 90 nop 0000000000000002 <sym1>: 2: 90 nop 3: 90 nop 4: 90 nop 5: 90 nop 0000000000000006 <sym2>: 6: 90 nop
シンボル値として_start, sym0, sym1, sym2が現れていることが見て取れる。
このオブジェクトファイルのシンボルの種類などをreadelf
コマンドで確認しようとしたが、エラーを吐いてしまった。原因は以下のようにMac上で作成されるオブジェクトファイルはELF
ではなく、Mach-O
という形式であるからだ。この形式については、第12回 Universal Binary【前編】:Undocumented Mac OS X(3/5 ページ) - ITmedia エンタープライズで確認してもらいたい。
$ file hoge.o hoge.o: Mach-O 64-bit object x86_64
そこでLinux環境で実行してみる。
$ readelf -s hoge.o Symbol table '.symtab' contains 8 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 SECTION LOCAL DEFAULT 1 2: 0000000000000000 0 SECTION LOCAL DEFAULT 2 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 4: 0000000000000002 0 NOTYPE LOCAL DEFAULT 1 sym1 5: 0000000000000006 0 NOTYPE LOCAL DEFAULT 1 sym2 6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 1 _start 7: 0000000000000001 0 NOTYPE GLOBAL DEFAULT 1 sym0
Valueというのがファイル内のオフセット、SizeとTypeカラムはデバッグ情報にも使えるカラムを表す。Bindカラムでシンボルが、グローバルシンボル・ローカルシンボル・エクスポートシンボルのどれであるかを表している。ローカルシンボルとはファイル内でのみ参照できるシンボルのことである。逆にいうとグローバルシンボルは外部ファイルからも参照できるシンボルである。エクスポートシンボルとは共有ライブラリを作る時などに用いられるシンボルで、実行時にもファイルを超えて参照される参照される定義ずみシンボルのことである。VisカラムがDEFAULTだとエクスポートシンボルであるらしい。
とりあえず今回はここまでで次回以降、さらに詳しくまとめていくつもりだ。
参照
- リンカ: 今回かなり参照したサイトで、より詳しい情報を知りたかったらこちらのサイトを見てみることをおすすめする。
- リンカーとライブラリ: こちらはリンカについて調べていたらたまたま見つけたサイトだが、リンカにについて非常に詳しくまとめられている。
スタティックリンクとダイナミックリンクによって作成される実行バイナリサイズの違い
前回スタティックリンクとダイナミックリンクの違いを述べ、それぞれの方法でリンクした時のファイルのサイズを見ようとしたが、printfのスタティックライブラリが見つからなかったので、途中で断念してしまった。そこで今回は自分で関数を作ってそれをライブラリにして、スタティック・ダイナミックの二つの方法でリンクしてみようと思う。
注意点
私はMacを使っているのだが、Macでgccを使おうとするとデフォルトでclangを使おうとするようだ。具体的に以下のコマンドを実行するとわかる。
$ gcc -v Apple clang version 13.1.6 (clang-1316.0.21.2.5) Target: x86_64-apple-darwin21.6.0 Thread model: posix InstalledDir: /Library/Developer/CommandLineTools/usr/bin
そこで、brewなどを使ってGNUのgccを使えるようにすることを推奨する。なぜなら今回はgccを想定したリンクを行うのだが、gccのオプションはclangでは使えないからだ。私は/usr/local/bin/gcc-12
を今回使用した。
準備
以下の2つのC言語ソースコードを準備した。add関数の方をライブラリにするつもりだ。
main.c #include "Add.c" int main() { int a = 1; int b = 2; int n = 100; int c; c = add(a, b, n); return c; }
// Add.c int add(int a, int b, int n) { int c = 0; for (int i = 0; i < n; i++) { c -= a; c += b; } return c; }
ダイナミックリンク
まずは、ダイナミックライブラリを以下のコマンドで作る。
gcc-12 -shared -fPIC -o libAdd.so Add.c
ここで、-fPIC
オプションは、共有ライブラリを作るときに使われるオプションで、メインメモリのどこに配置されても絶対アドレスに関わらず正しく実行されることを保証するものである。Position-Independent Codeの略称である。また共有ライブラリを作るため、オプションとして-shared
も忘れずにつける。
最後に以下のコマンでmain.cのコンパイルと共有ライブラリとのダイナミックリンクを行う。
$ gcc-12 -L. -o main.dynamic main.c -lAdd
スタティックリンク
まず、Add.cのオブジェクトファイルを作る。
$ gcc-12 -c Add.c -o Add.o
これを以下のコマンドで静的ライブラリに変換する。
$ ar rcs libAdd.a Add.o
最後に、このライブラリを用いて、スタティックリンクを行う。
$ gcc-12 -o main.static main.o -L. -lAdd
その後、ダイナミックリンクをした実行バイナリとスタティックリンクをした実行バイナリのファイルサイズを比較した結果が以下だ。
16568 11 1 09:44 main.dynamic 49456 11 1 09:49 main.static
スタティックリンクをした実行バイナリの方が3倍程度も大きくなっていることがわかる。ダイナミックリンクの方がメモリ効率が良いことをこれらの実験で確認することができた。
参照
リンク基本事項
コンパイルした後にオブジェクトファイルをリンクして実行ファイルを作成するという作業を一般にリンクと呼ぶが、そのリンクについての理解が曖昧なので今回まとめてみることにした。
まずリンクの種類として、スタティック(静的)リンクとダイナミック(動的)リンクの二つがある。それぞれについて説明する。
スタティックリンク
オブジェクトファイルをライブラリ中のルーチンとリンクする際、そのルーチンも実行ファイルに含めてしまうことである。このため、ファイルサイズはダイナミックリンク時よりも大きくなる。静的ライブラリは基本的には、.a
という拡張子であることが多い。
ダイナミックリンク
リンク時には、オブジェクトファイルが実行時にライブラリ中のルーチンと結合できるように設定ファイルに書き込むだけで、実行ファイルに含めるようなことはしない。そのルーチンを使う時になって初めてそれをメモリ上にロードするので、メモリの節約になる。このため、実行ファイルサイズはスタティックリンク時よりも小さい。動的ライブラリは基本的には、.so
という拡張子であることが多い。
ファイルサイズの比較
スタティックリンクの方がファイルサイズが大きくなることがわかっているので、それを確かめてみる。まずは以下のような標準ライブラリを呼び出すような簡単なHello, Worldコードを想定する。
#include <stdio.h> int main() { printf("Hello, World!\n"); return 0; }
まずは、以下のコマンドでコンパイルとリンクしてみる。オプションをつけずにリンクするとダイナミックリンクとなる。
$ cc -o hello.dynamic hello.c
-v
オプションをつけて上のコマンドを実行すると、-L/usr/local/lib
となっており、ここのライブラリを参照してリンクしていることがわかる。
次にスタティックリンクをしてみる。-static
オプションを付けるとできるらしいので、やってみた。すると以下のようなエラーが出た。
$ cc -static -o hello.static hello.c ld: library not found for -lcrt0.o clang: error: linker command failed with exit code 1 (use -v to see invocation)
どうやら-L
というオプションで、printtfのライブラリが入っているパスを指定する必要があるらしい。しかし、どこにprintfのライブラリがあるのか。。。printfのダイナミックリンクライブラリはあるかもしれないが、もしかしたらprintfのスタティックライブラリはない?
以下にリンクを勉強する上で参考になりそうなサイトを載せておく。これらを参考にまたリンクについて勉強してみようと思う。
参照
RISC-V Vector拡張のアセンブリを読み解く 異なる型同士の演算
以前と同様に、RVVのアセンブリを読み解いてみようと思う。今回は型の大きさが異なる同士の演算を見ていこうと思う。
C言語ソースコード
// Vectorization of Mixed Types #include <stdio.h> int foo(int *A, char *B, int n) { for (int i = 0; i < n; i++) { A[i] += 4 * B[i]; } return 0; } int main() { int n = 2001; int A[n]; char B[n]; for(int i = 0; i < n; i++) { A[i] = i; B[i] = i % 256; } foo(A, B, n); }
RVVアセンブリ
今回も関数のインライン化が行われているので、main関数の中だけを見れば良い。
.text .attribute 4, 16 .attribute 5, "rv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0_v1p0_zvl128b1p0_zvl32b1p0_zvl64b1p0" .file "test0.c" .globl foo .p2align 1 .type foo,@function foo: blez a2, .LBB0_9 slli a3, a2, 32 li a4, 8 srli a6, a3, 32 bgeu a2, a4, .LBB0_3 li a2, 0 j .LBB0_7 .LBB0_3: slli a2, a6, 2 add a2, a2, a0 add a3, a1, a6 sltu a3, a0, a3 sltu a2, a1, a2 and a3, a3, a2 li a2, 0 bnez a3, .LBB0_7 andi a2, a6, -8 mv a4, a2 mv a5, a0 mv a3, a1 .LBB0_5: vsetivli zero, 8, e8, mf4, ta, mu vle8.v v8, (a3) vsetvli zero, zero, e32, m1, ta, mu vle32.v v9, (a5) vzext.vf4 v10, v8 vsll.vi v8, v10, 2 vadd.vv v8, v8, v9 vse32.v v8, (a5) addi a3, a3, 8 addi a4, a4, -8 addi a5, a5, 32 bnez a4, .LBB0_5 beq a2, a6, .LBB0_9 .LBB0_7: slli a3, a2, 2 add a0, a0, a3 add a1, a1, a2 sub a2, a6, a2 .LBB0_8: lbu a3, 0(a1) lw a4, 0(a0) slliw a3, a3, 2 addw a3, a3, a4 sw a3, 0(a0) addi a0, a0, 4 addi a2, a2, -1 addi a1, a1, 1 bnez a2, .LBB0_8 .LBB0_9: li a0, 0 ret .Lfunc_end0: .size foo, .Lfunc_end0-foo .globl main .p2align 1 .type main,@function main: lui a0, 2 addiw a0, a0, 1824 sub sp, sp, a0 li a0, 0 addi a1, sp, 2044 vsetivli zero, 8, e8, mf4, ta, mu vid.v v8 vsetvli zero, zero, e32, m1, ta, mu vid.v v9 addi a2, sp, 11 li a3, 16 li a4, 2000 .LBB1_1: vsetvli zero, zero, e32, m1, ta, mu vadd.vi v10, v9, 8 // mf4=64bitにすることでintと同様に // 8つのデータを扱えるようになる。 // 8bitをオーバーした分は自動的に切り捨てられる vsetvli zero, zero, e8, mf4, ta, mu vadd.vi v11, v8, 8 addi a5, a1, -32 vse32.v v9, (a5) vse32.v v10, (a1) add a5, a2, a0 vse8.v v8, (a5) addi a5, a5, 8 vse8.v v11, (a5) addi a0, a0, 16 vsetvli zero, zero, e32, m1, ta, mu vadd.vx v9, v9, a3 // a3=16 vsetvli zero, zero, e8, mf4, ta, mu vadd.vx v8, v8, a3 // a3=16 addi a1, a1, 64 bne a0, a4, .LBB1_1 li a0, 0 lui a1, 2 addiw a2, a1, -192 addi a1, sp, 2012 add a3, a1, a2 li a2, 2000 sw a2, 0(a3) li a3, 208 sb a3, 2011(sp) addi a3, sp, 11 .LBB1_3: add a4, a3, a0 vsetvli zero, zero, e8, mf4, ta, mu vle8.v v8, (a4) vsetvli zero, zero, e32, m1, ta, mu vle32.v v9, (a1) // SEW/4のソースオペランドをゼロ拡張してSEW幅化し書き込む vzext.vf4 v10, v8 vsll.vi v8, v10, 2 vadd.vv v8, v8, v9 vse32.v v8, (a1) addi a0, a0, 8 addi a1, a1, 32 bne a0, a2, .LBB1_3 lbu a0, 2011(sp) lui a1, 2 addiw a1, a1, -192 addi a2, sp, 2012 add a1, a1, a2 lw a2, 0(a1) slliw a0, a0, 2 addw a0, a0, a2 sw a0, 0(a1) li a0, 0 lui a1, 2 addiw a1, a1, 1824 add sp, sp, a1 ret .Lfunc_end1: .size main, .Lfunc_end1-main .ident "clang version 14.0.0" .section ".note.GNU-stack","",@progbits .addrsig
気づいた点
1週間のまとめ 2022/10/29
1回ブログに書いたことも時間が経つとすぐに忘れてしまうので、内容を振り返って、ついでに軽くまとめることにする。普通にまとめるだけだとなんだか簡単なので、英語で書くのもありだと思ったのだが、とりあえず習慣化することが優先なので日本語でまとめる。
実際まとめてみると、そもそ技術記事は要約することは難しいことがわかったので、主に振り返ることで気づいた感想などが中心となっている。詳しい内容は実際に記事を参照されたい。
- RVV Intrinsicsの一部紹介 その1
- TypeScript 基本型
- RISC-V アセンブリを読む その1 Hello World
アセンブリを眺めていて、わざわざelfファイルをobjdumpする必要はないと感じた。clang -Sでアセンブリを出力して読めばいいかと思った。 takumi9.hatenablog.com
- クイックソート メモ
自分のqsortとライブラリのqsortで性能計測する時、ソート対象の配列を初期化しているが、そこでそれぞれ別の配列を扱っているので、条件が統一されていないと思った。
- fopen_s/fprintf_sなどのセキュアCライブラリ
現在気になっているテーマ
これからもしかしたらまとめるかもしれないテーマを書き出しておく。
- pk(プロキシカーネル)とは?
- Apple clangと普通のclangの違い
- sgemm, dgemmアルゴリズム
- CSR命令
- ループアンローリング
RISC-V Vector拡張のアセンブリを読み解く
RISC-V Vector拡張(RVV)のアセンブリを読むのにまだ慣れないので、実際に出力されたコードを読んで読み解いてみようと思う。
C言語ソースコード
void loops(int n, int A[], int B[]) { for (int i = 0; i < n; i++) { A[i] *= B[i]; } } int main() { int n = 1000; int A[n], B[n]; for(int i = 0; i < n; i++) { A[i] = i; B[i] = i; } loops(n, A, B); return 0; }
出力されたRVV アセンブリ
.text .attribute 4, 16 .attribute 5, "rv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0_v1p0_zvl128b1p0_zvl32b1p0_zvl64b1p0" .file "test0.c" .globl loops .p2align 1 .type loops,@function loops: blez a0, .LBB0_9 slli a3, a0, 32 li a4, 16 srli a6, a3, 32 bgeu a0, a4, .LBB0_3 li a7, 0 j .LBB0_7 .LBB0_3: slli a0, a6, 2 add a3, a1, a0 add a0, a0, a2 sltu a0, a1, a0 sltu a3, a2, a3 and a0, a0, a3 li a7, 0 bnez a0, .LBB0_7 andi a7, a6, -16 addi a4, a2, 32 addi a5, a1, 32 mv a0, a7 .LBB0_5: addi a3, a4, -32 vsetivli zero, 8, e32, m1, ta, mu vle32.v v8, (a3) vle32.v v9, (a4) addi a3, a5, -32 vle32.v v10, (a3) vle32.v v11, (a5) vmul.vv v8, v10, v8 vmul.vv v9, v11, v9 vse32.v v8, (a3) vse32.v v9, (a5) addi a4, a4, 64 addi a0, a0, -16 addi a5, a5, 64 bnez a0, .LBB0_5 beq a7, a6, .LBB0_9 .LBB0_7: slli a3, a7, 2 add a0, a1, a3 add a1, a2, a3 sub a2, a6, a7 .LBB0_8: lw a3, 0(a1) lw a4, 0(a0) mulw a3, a4, a3 sw a3, 0(a0) addi a0, a0, 4 addi a2, a2, -1 addi a1, a1, 4 bnez a2, .LBB0_8 .LBB0_9: ret .Lfunc_end0: .size loops, .Lfunc_end0-loops .globl main .p2align 1 .type main,@function main: // 2を12bit左シフト=4096 x 2 // 4バイト要素を1000個持つ配列が2つなのでちょうどこれくらい lui a0, 2 addiw a0, a0, -176 // 配列の分だけスタック領域を確保 sub sp, sp, a0 lui a0, 1 addiw a1, a0, -128 lui a0, 1 addiw a0, a0, -80 // 以下二つの命令でa0にスタック内の配列の開始位置を割り当てる add a0, a0, sp add a0, a0, a1 addi a2, sp, 16 add a6, a2, a1 lui a1, 1 addiw a1, a1, -48 // もう一つの配列の開始位置をスタック内に割り当てる add a2, sp, a1 addi a3, sp, 48 // 最初のオペランドはzeroだが、本来ここは一回のループで処理される要素数を格納する。 // つまりvlを格納し、いつもはAVLだが最後は余りの数が格納される。 // 二つ目のオペランドは8となっており処理する全ての演算対象要素数を表す。 vsetivli zero, 8, e32, m1, ta, mu // 各要素のインデックスを0, 1, 2, ..., vl-1とv8に書き込む。 vid.v v8 li a4, 992 li a5, 16 .LBB1_1: // 0, 1, 2, ... vl-1(7)となっているインデックス全てに8を加算 vadd.vi v9, v8, 8 addi a1, a2, -32 // 配列Aに0, 1, 2, ... 15を書き込んでSTORE vse32.v v8, (a1) vse32.v v9, (a2) // 配列Bも同じだけSTORE addi a1, a3, -32 vse32.v v8, (a1) vse32.v v9, (a3) // インデックスが入っているv8に16を足して更新 vadd.vx v8, v8, a5 // 配列Aのスタック領域を更新 addi a2, a2, 64 // 処理すべき配列の要素数の更新。bnezで使う。 addi a4, a4, -16 // 配列Bのスタック領域を更新 addi a3, a3, 64 bnez a4, .LBB1_1 // 以下あまり部分。1000%16 = 8回だけ行う。 li a2, 992 sw a2, 0(a0) sw a2, 0(a6) li a1, 993 sw a1, 4(a0) sw a1, 4(a6) li a1, 994 sw a1, 8(a0) sw a1, 8(a6) li a1, 995 sw a1, 12(a0) sw a1, 12(a6) li a1, 996 sw a1, 16(a0) sw a1, 16(a6) li a1, 997 sw a1, 20(a0) sw a1, 20(a6) li a1, 998 sw a1, 24(a0) sw a1, 24(a6) li a1, 999 sw a1, 28(a0) sw a1, 28(a6) // 関数のインライン化が行われていてここからloop関数。 addi a3, sp, 48 lui a1, 1 addiw a1, a1, -48 add a4, sp, a1 .LBB1_3: // 配列AのLOAD addi a1, a3, -32 vle32.v v8, (a1) vle32.v v9, (a3) // 配列BのLOAD addi a1, a4, -32 vle32.v v10, (a1) vle32.v v11, (a4) vmul.vv v8, v10, v8 vmul.vv v9, v11, v9 vse32.v v8, (a1) vse32.v v9, (a4) addi a3, a3, 64 addi a2, a2, -16 // 最初はa2 = 992 addi a4, a4, 64 bnez a2, .LBB1_3 // あまりの乗算 lw a1, 0(a6) lw a2, 0(a0) lw a3, 4(a6) lw a4, 4(a0) mulw a1, a2, a1 sw a1, 0(a0) mulw a1, a4, a3 lw a2, 8(a6) lw a3, 8(a0) lw a4, 12(a6) lw a5, 12(a0) sw a1, 4(a0) mulw a1, a3, a2 sw a1, 8(a0) mulw a1, a5, a4 lw a2, 16(a6) lw a3, 16(a0) lw a4, 20(a6) lw a5, 20(a0) sw a1, 12(a0) mulw a1, a3, a2 sw a1, 16(a0) mulw a1, a5, a4 lw a2, 24(a6) lw a3, 24(a0) lw a4, 28(a6) lw a5, 28(a0) sw a1, 20(a0) mulw a1, a3, a2 sw a1, 24(a0) mulw a1, a5, a4 sw a1, 28(a0) li a0, 0 lui a1, 2 addiw a1, a1, -176 add sp, sp, a1 ret .Lfunc_end1: .size main, .Lfunc_end1-main .ident "clang version 14.0.0" .section ".note.GNU-stack","",@progbits .addrsig
浮かんできた疑問