-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path20-codegen.tex
More file actions
1305 lines (1063 loc) · 59.6 KB
/
20-codegen.tex
File metadata and controls
1305 lines (1063 loc) · 59.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\chapter{目标代码生成}
前面的章节中,我们已经建构了抽象语法树 (AST),并进行了类型检查。在此基础上,我们需要通过抽象语法树生成目标代码。
尽管我们的确可以从抽象语法树直接生成目标平台的代码,然而现实情况并非如此。假设我们有
$m$ 门语言,$n$ 个目标平台,那么如果采用直接生成代码,则需要写 $m\times n$
个转换程序,这对于现代编译器来说是无法承受的。但是如果我们选取一门中间语言作为转换的中转,那么我只要实现
$m$ 个从语言到中间语言的转换程序,以及 $n$ 个从中间语言到目标平台的转换程序,就可以满足需求。这样的中间语言被称为
IR (intermediate representation)。此外,对于后续的优化,也可以在 IR 上处理,这可以大大减少编译器优化工作。
本章中,我们以 LLVM Language (version 15) 为例,来展示如何从中间语言转换成目标代码。如需查看
LLVM 的完整文档,请访问 \url{https://llvm.org/docs/LangRef.html}。
\begin{remark}
\begin{enumerate}
\item 本章涉及程序链接的一部分内容。在阅读本章前,强烈建议先行了解关于链接器 (linker) 的知识
(维基百科链接:\url{https://en.wikipedia.org/wiki/Linker_(computing)})。
\item 本章中有很多命令,请在理解的基础上执行。在不经过完全理解的情况下执行代码绝不是安全的。
在执行前,你可能需要对命令做出修改,如加上特定的版本号。本章说列出的命令仅供参考。
\end{enumerate}
\end{remark}
\section{LLVM 15 概览}
LLVM 是一套编译器基础设施项目,包含一系列模块化的编译器组件和工具链,用来开发编译器前端和后端。
IR 是区分前后端的标志。语法检查、建构抽象语法树、生成 IR 都属于前端部分。通过 IR
生成目标代码、针对 IR 层面的优化都属于后端部分。
LLVM 提供的工具链可以检查 IR 问题、运行及调试 IR,因此我们推荐使用 LLVM IR
作为我们编译器的 IR。
LLVM 项目中有很多非常实用的工具——如 clang、llc、lli 等等。一般来是,最常用的工具是
clang,因为 clang 可以将代码编译到 LLVM IR,也可以将 LLVM 代码编译到目标平台。比如
\texttt{clang main.c -S -emit-llvm --target=riscv32-unknown-elf -o main.ll}
可以将 \texttt{main.c} 转换成 LLVM IR 并保存在 \texttt{main.ll} 中;而
\texttt{clang -S main.ll --target=riscv32-unknown-elf -o main.s}
可以将 \texttt{main.ll} 转换成汇编。llc 可以将 LLVM IR 转换成汇编,如
\texttt{llc -march=riscv32 main.ll -o main.s} 会将 \texttt{main.ll}
转换成汇编并存到 \texttt{main.s}。
LLVM IR 语言与高级语言不同,不存在多层嵌套的环境,指令以基础块 (basic block)
的形式组织,与汇编语言非常相近,同时又保留了高级语言的类型属性。选择 LLVM 15
的原因是从 LLVM 15 开始,LLVM IR 默认使用不透明指针
(Opaque Pointers),大大减少了转换时的负担。具体请参见章节 \ref{LLVM-semantic}。
LLVM IR 的一大特性是静态单赋值 (Static Single Assignment,
SSA),即变量能且仅能被赋值一次,这一特性可以让编译器的静态分析更方便。
当然,你也可以使用其他 IR 或是直接一步从 AST
转换成目标代码(因为我们的编译器只有一个目标平台)。
\section{LLVM 15 环境}
\subsection{在线 LLVM 环境}\label{online-LLVM-env}
在学习 LLVM 的过程中,一个方便的使用环境非常重要。Godbolt (
\url{https://godbolt.org/}) 提供了几乎所有的编译器,并且可以编译到大量平台。
\begin{figure}[htb]
\centering
\includegraphics[scale=0.3]{image/godbolt.png}
\caption{Godbolt 使用截图}
\label{godbolt_sreenshot}
\end{figure}
图片 \ref{godbolt_sreenshot} 是一个通过在线 LLVM 环境来了解 LLVM 语言的例子。在
gofbolt 上,页面被分为两栏——左栏是你的输入,右栏是你选择的编译器在你填入的参数下的输入。下面我们将会分别介绍左右栏的使用。
左栏有一个语言选择框(位于左栏的上部)和一个代码输入框。语言选择框可以选择所使用的编程语言。
右栏上部左边部分是编译器选择框,右边部分是编译器参数框。我们此时希望将代码编译到适用于 rv32gc
平台的 LLVM 语言,而且不希望编译器做出优化(否则可能会把一部分代码直接去掉)。由于我们的编译器的目标平台是
rv32gc,且 clang 可以通过 \texttt{-emit-llvm} 来输出 LLVM 语言,因此我们在编译器选择框选择
RISCV rv32gc clang (trunk)(trunk 这里表示最新的 clang),在编译器参数框中输入 \texttt{-emit-llvm -O0}。
关于语言特性的内容,请见章节 \ref{LLVM-semantic}。
\subsection{本地 LLVM 环境}
你可以安装 LLVM 15 及其以上版本。截止 LLVM 17,所有的 LLVM 15 特性均可使用。
对于使用 apt 包管理的用户(debian/Ubuntu/... 用户),请参考 \href{https://apt.llvm.org/}{LLVM apt 安装文档}。
对于使用 pacman 包管理的用户,可以执行 \texttt{sudo pacman -S llvm clang} 安装最新版本的 LLVM。
你可以执行 \texttt{clang --version} 来检查 clang 15 及以上版本是否确实安装到系统中。
正常情况下,该指令会显示你的 clang 版本、目标平台等信息。你需要检查对应的版本是否正确。
\begin{remark}
如果你用的是特定版本的 LLVM(而非最新版本),在使用程序时,需要加上 \texttt{-<version>} 后缀。比如 \texttt{clang-15 ...}。
\end{remark}
\section{LLVM 15 语法}\label{LLVM-semantic}
\begin{remark}
本章节只介绍常用的 LLVM 15 语法。如需了解全部语法或更详细的 LLVM 语法,请访问
\url{https://llvm.org/docs/LangRef.html}。
\end{remark}
\begin{remark}
你可以先大致了解 LLVM 15 语法,然后跳到「从抽象语法树到中间语言」章节
(\ref{AST-to-IR}) 了解如何完成抽象语法树到 IR
的转换。在阅读「从抽象语法树到中间语言」部分的过程中,如果对 LLVM IR
的语法有疑惑,再回到本部分查看详细信息。
\end{remark}
\subsection{LLVM 15 简单例子}
对于下面的代码,
\begin{lstlisting}[language=C]
int c;
int foo(int* a, int b) {
if (a == 0) return 0;
return *a + b + c;
}
\end{lstlisting}
在 \texttt{-emit-llvm -O1 -fno-discard-value-names}
参数下,会生成下面的代码(下面的代码去掉了生成代码的 attribute
部分和一些注释,这些内容我们在编译器大作业中应该用不到)
\begin{lstlisting}[language=llvm]
@c = dso_local local_unnamed_addr global i32 0, align 4, !dbg !0
define dso_local i32 @foo(ptr noundef readonly %a, i32 noundef %b) local_unnamed_addr #0 !dbg !15 {
entry:
call void @llvm.dbg.value(metadata ptr %a, metadata !20, metadata !DIExpression()), !dbg !22
call void @llvm.dbg.value(metadata i32 %b, metadata !21, metadata !DIExpression()), !dbg !22
%cmp = icmp eq ptr %a, null, !dbg !23
br i1 %cmp, label %return, label %if.end, !dbg !25
if.end: ; preds = %entry
%0 = load i32, ptr %a, align 4, !dbg !26
%add = add nsw i32 %0, %b, !dbg !31
%1 = load i32, ptr @c, align 4, !dbg !32
%add1 = add nsw i32 %add, %1, !dbg !33
br label %return, !dbg !34
return: ; preds = %entry, %if.end
%retval.0 = phi i32 [ %add1, %if.end ], [ 0, %entry ], !dbg !22
ret i32 %retval.0, !dbg !35
}
\end{lstlisting}
进一步地,我们还可以忽略一些 debug 参数、一些无用的参数(如
\texttt{dso\_local}、\texttt{local\_unnamed\_addr} 和
\texttt{align})以及不必要的 debug 函数,这样可以得到以下的代码
\begin{lstlisting}[language=llvm]
@c = global i32 0
define i32 @foo(ptr %a, i32 %b) {
entry:
%cmp = icmp eq ptr %a, null
br i1 %cmp, label %return, label %if.end
if.end: ; preds = %entry
%0 = load i32, ptr %a
%add = add i32 %0, %b
%1 = load i32, ptr @c
%add1 = add i32 %add, %1
br label %return
return: ; preds = %entry, %if.end
%retval.0 = phi i32 [ %add1, %if.end ], [ 0, %entry ]
ret i32 %retval.0
}
\end{lstlisting}
我们会在本章每个元素一一解释。如果你设置了 \texttt{-O0},你会看到一些如
\texttt{\%3 = alloca i32} 的指令,我们也会在本章中介绍。
\subsection{LLVM 15 基本语法}
LLVM 15 的基本语法包含
\begin{itemize}
\item 类型,如 \texttt{i1},\texttt{i32},具体参见章节 \ref{LLVM-types};
\item 变量,如 \texttt{@a = global i32 0} 以及
\texttt{\%a = ...},具体参见章节 \ref{LLVM-variables};
\item 常量,具体参见章节 \ref{LLVM-constants};
\item 函数定义及声明,如 \texttt{define i32 \@foo(ptr \%0, i32 \%1) \{...\}},具体参见章节
\ref{LLVM-functions};
\item 标签 (label),如 \texttt{4:};
\item 指令,如 \texttt{\%3 = icmp eq ptr \%0, null},具体参见章节
\ref{LLVM-instructions}。
\end{itemize}
一个基本的 LLVM IR 翻译单元由若干类型声明、若干全局变量和若干函数定义组成。
函数定义由若干标签和若干指令组成。标签是可以被分支指令 (\ref{LLVM-br-instructions})
跳转到的地方。在函数外部的变量为全局变量,在函数内部的变量为局部变量。
所有的变量能且仅能被赋值一次(因此全局变量实际上是指向变量的指针)。
每行的分号 (\texttt{;}) 后的内容为注释。如没有注释,分号是不必要的。
\subsection{类型}\label{LLVM-types}
常用的类型有
\begin{itemize}
\item 整数类型:用 \texttt{i<N>} 表示,如 \texttt{i32},\texttt{i1},
注意整型的长度与内存中的对齐没有必然的联系,\texttt{i1} 是 1 bit 长,不代表八个 \texttt{i1}
占用 1 byte;
\item 指针类型:用 \texttt{ptr} 表示;
\item 数组类型:用 \texttt{[<\# elements> x <elementtype>]} 表示,如
\texttt{[40 x i32]}。数组类型允许嵌套定义,如
\texttt{[3 x [4 x i32]]},表示 $3\times 4$ 的二维数组。另请参见
\url{https://llvm.org/docs/LangRef.html#array-type};
\item 结构类型:对于普通的类,用 \texttt{\%<typename> = type \{ <type list> \}} 表示,如
\texttt{\%mytype = type \{ \%mytype*, i32 \}};对于不能对齐的类
(packed struct),用 \texttt{\%<typename> = type <\{ <type list> \}>}
表示,如 \texttt{<\{ i8, i32 \}>} 表示一个 5 字节的结构体。
\end{itemize}
\subsection{变量}\label{LLVM-variables}
非匿名变量的命名必须符合 \texttt{[\%@][-a-zA-Z\$.\_][-a-zA-Z\$.\_0-9]*},且不能与作用域中的其他变量名或函数名冲突。
匿名变量的命名必须符合 \texttt{[\%@](0|[1-9][0-9]*)},且不能与作用域中的其他变量名或函数名冲突。
变量分为全局变量和局部变量。全局变量以 \texttt @ 开头,如 \texttt{@abc};局部变量以 \texttt \% 开头,如 \texttt{\%abc}。
变量能且只能被赋值一次。
\subsection{常量}\label{LLVM-constants}
常量有
\begin{itemize}
\item boolean 常量: 仅有 \texttt{true},\texttt{false} 两个字符串,类型为 \texttt{i1}。
\item int 常量:支持所有标准的整数。
\item 空指针常量:仅支持字符串 \texttt{null},类型为 \texttt{ptr}。
\end{itemize}
\begin{remark}
如需使用其他常量,请参见\href{https://llvm.org/docs/LangRef.html\#constants}{官方文档}。
\end{remark}
\subsection{函数定义及声明}\label{LLVM-functions}
函数的定义方式如下:
\begin{lstlisting}[language=llvm]
define <ResultType> @<FunctionName>(...) { ... }
\end{lstlisting}
圆括号内为函数参数,参数用逗号分割,每项参数的形式为 \texttt{<type> [name]}。
典型的例子有:
\begin{itemize}
\item 无入参函数 \texttt{int a()},对应的函数为 \texttt{define i32 \@a() \{...\}};
\item 单入参函数 \texttt{void a(int x)},对应的函数为 \texttt{define void \@a(i32 \%x) \{...\}};
\item 双入参函数 \texttt{int a(int x1, int x2)},
对应的函数为 \texttt{define i32 \@a(i32 \%x1, i32 \%x2) \{...\}}。
\end{itemize}
花括号内为函数的指令以及若干标签 (label)。
函数的声明方式如下:
\begin{lstlisting}[language=llvm]
declare <ResultType> @<FunctionName>(...)
\end{lstlisting}
声明的形式基本上和定义一致,只不过去掉了函数体,前面的 \texttt{define} 被换成了
\texttt{declare}。
\begin{remark}
关于函数调用,请参见 call 指令 (\ref{LLVM-call-instructions})。
\end{remark}
\subsection{指令}\label{LLVM-instructions}
\begin{remark}
以下的介绍中的语法只是 LLVM IR 语法的一部分。关于全部语法,请访问
\url{https://llvm.org/docs/LangRef.html\#instruction-reference}。
同时,每个指令的章节也会附上对应的官方文档链接。
\end{remark}
在 LLVM IR 中,我们一般会用到以下指令:(点击括号中的章节号可以跳转到对应章节)
\begin{itemize}
\item 二元运算指令 (\ref{LLVM-binary-instructions})
\begin{itemize}
\item \texttt{add} 指令
\item \texttt{sub} 指令
\item \texttt{mul} 指令
\item \texttt{sdiv} 指令
\item \texttt{srem} 指令
\item \texttt{shl} 指令
\item \texttt{ashr} 指令
\item \texttt{and} 指令
\item \texttt{or} 指令
\item \texttt{xor} 指令
\end{itemize}
\item 控制流相关指令
\begin{itemize}
\item \texttt{br} 指令 (\ref{LLVM-br-instructions})
\item \texttt{ret} 指令 (\ref{LLVM-ret-instructions})
\end{itemize}
\item 内存相关指令
\begin{itemize}
\item \texttt{alloca} 指令 (\ref{LLVM-alloca-instructions})
\item \texttt{load} 指令 (\ref{LLVM-load-instructions})
\item \texttt{store} 指令 (\ref{LLVM-store-instructions})
\item \texttt{getelementptr} 指令 (\ref{LLVM-gep-instructions})
\end{itemize}
\item 其他指令
\begin{itemize}
\item \texttt{icmp} 指令 (\ref{LLVM-icmp-instructions})
\item \texttt{call} 指令 (\ref{LLVM-call-instructions})
\item \texttt{phi} 指令 (\ref{LLVM-phi-instructions})
\item \texttt{select} 指令 (\ref{LLVM-select-instructions})
\end{itemize}
\end{itemize}
\subsubsection{二元运算指令}\label{LLVM-binary-instructions}
\begin{remark}
完整文档位于 \url{https://llvm.org/docs/LangRef.html\#binary-operations}。
\end{remark}
语法:
\begin{lstlisting}[language=llvm]
<result> = <operator> <type> <operand1>, <operand2>
\end{lstlisting}
支持的运算符(对应形式中的 \texttt{operator})有:
\begin{itemize}
\item add:整数加法
\item sub:整数减法
\item mul:整数乘法
\item sdiv:有符号整数除法
\item srem:有符号整数取模
\item shl:左移
\item ashr:算术右移
\item and:按位与
\item or:按位或
\item xor:异或
\end{itemize}
例子:
\begin{lstlisting}[language=llvm]
%_add_result_1 = add i32 %a, %b
\end{lstlisting}
该指令表示将 \texttt{\%a} 与 \texttt{\%b} 之和存入 \texttt{\%\_add\_result\_1}。
\subsubsection{\texttt{br} 指令}\label{LLVM-br-instructions}
\begin{remark}
完整文档位于 \url{https://llvm.org/docs/LangRef.html\#br-instruction}。
\end{remark}
语法:
\begin{lstlisting}[language=llvm]
br i1 <cond>, label <iftrue>, label <iffalse> ; Conditional branch
br label <dest> ; Unconditional branch
\end{lstlisting}
\begin{itemize}
\item \texttt{cond} 必须是一个 \texttt{i1} 类型的变量;
\item \texttt{iftrue}、\texttt{iffalse} 以及 \texttt{dest}
必须是所在函数里的一个标签;
\item 由于 \texttt{br} 指令的下一个执行的指令一定不是下一条指令,因此一个基础块
(basic block) 里的指令中 \texttt{br} 之后的指令是没有意义的。
\end{itemize}
例子:
\begin{lstlisting}[language=llvm]
br i1 %a, label %label_1, label %label_2 ; Conditional branch
br label %label_3 ; Unconditional branch
\end{lstlisting}
第 1 行表示如果 \texttt{\%a} 是 \texttt{true},则跳转到
\texttt{label\_1},否则跳转到 \texttt{label\_2}。第 2 行表示直接跳转到
\texttt{label\_3}。
\subsubsection{\texttt{ret} 指令}\label{LLVM-ret-instructions}
\begin{remark}
完整文档位于 \url{https://llvm.org/docs/LangRef.html\#ret-instruction}。
\end{remark}
语法:
\begin{lstlisting}[language=llvm]
ret <type> <value> ; Return a value from a non-void function
ret void ; Return from void function
\end{lstlisting}
\begin{itemize}
\item \texttt{value} 的类型必须与 \texttt{type} 和函数的返回类型相同;
\item 与 \texttt{br} 指令类似,一个基础块里的 \texttt{ret}
指令之后的所有指令都不可达,因此都没有意义。
\end{itemize}
例子:
\begin{lstlisting}[language=llvm]
ret ptr %a
ret i32 1
\end{lstlisting}
第 1 行表示返回 \texttt{\%a}。第 2 行表示返回一个常数 1。
\subsubsection{\texttt{alloca} 指令}\label{LLVM-alloca-instructions}
\begin{remark}
完整文档位于 \url{https://llvm.org/docs/LangRef.html\#alloca-instruction}。
\end{remark}
语法:
\begin{lstlisting}[language=llvm]
<result> = alloca <type>
\end{lstlisting}
\begin{itemize}
\item \texttt{alloca} 指令可以在当前函数的栈上为 \texttt{type} 类型分配一块的内存空间;
\item \texttt{result} 是指向该空间的指针,类型是指针类型 (\texttt{ptr})。
\end{itemize}
例子:
\begin{lstlisting}[language=llvm]
%ptr_1 = alloca i32
\end{lstlisting}
表示在当前函数的栈上分配一块 \texttt{sizeof(i32)} 字节的空间,\texttt{\%ptr\_1} 指向该空间。
\subsubsection{\texttt{load} 指令}\label{LLVM-load-instructions}
\begin{remark}
完整文档位于 \url{https://llvm.org/docs/LangRef.html\#load-instruction}。
\end{remark}
语法:
\begin{lstlisting}[language=llvm]
<result> = load <ty>, ptr <pointer>
\end{lstlisting}
\begin{itemize}
\item \texttt{result} 的类型是 \texttt{ty};
\item \texttt{result} 被赋值为 \texttt{pointer} 所指向的值。
\end{itemize}
例子:
\begin{lstlisting}[language=llvm]
%ptr = alloca i32
store i32 3, ptr %ptr
%val = load i32, ptr %ptr
\end{lstlisting}
第 3 行表示将 \texttt{\%ptr} 所指向的值赋值给 \texttt{\%val},此处 \texttt{\%val}
的值为 3。
\subsubsection{\texttt{store} 指令}\label{LLVM-store-instructions}
\begin{remark}
完整文档位于 \url{https://llvm.org/docs/LangRef.html\#store-instruction}。
\end{remark}
语法:
\begin{lstlisting}[language=llvm]
store <ty> <value>, ptr <pointer>
\end{lstlisting}
\begin{itemize}
\item \texttt{pointer} 所指向的值会被赋值为 \texttt{value}。
\end{itemize}
例子:
\begin{lstlisting}[language=llvm]
%ptr = alloca i32
store i32 3, ptr %ptr
%val = load i32, ptr %ptr
\end{lstlisting}
第 2 行表示将 \texttt{\%ptr} 所指向的值赋值为 3。因此第 3 行中,\texttt{\%val}
的值为 3。
\subsubsection{\texttt{getelementptr} 指令}\label{LLVM-gep-instructions}
\begin{remark}
完整文档位于 \url{https://llvm.org/docs/LangRef.html\#getelementptr-instruction}。
\end{remark}
在编译器大作业中,以下的语法可以胜任全部要求:
\begin{lstlisting}[language=llvm]
<result> = getelementptr <ty>, ptr <ptrval>{, <ty> <idx>}*
\end{lstlisting}
\texttt{getelementptr} 指令较为复杂,我们先看一个例子。
对于以下的 C 代码:
\begin{lstlisting}[language=C]
struct RT {
char A;
int B[10][20];
char C;
};
struct ST {
int X;
double Y;
struct RT Z;
};
int *foo(struct ST *s) {
return &s[1].Z.B[5][13];
}
\end{lstlisting}
对应的 LLVM IR 为
\begin{lstlisting}[language=llvm]
%struct.RT = type { i8, [10 x [20 x i32]], i8 }
%struct.ST = type { i32, double, %struct.RT }
define ptr @foo(ptr %s) {
entry:
%arrayidx = getelementptr %struct.ST, ptr %s, i64 1, i32 2, i32 1, i64 5, i64 13
ret ptr %arrayidx
}
\end{lstlisting}
可以发现 \texttt{getelementptr} 指令会对类型逐层解析。将 ST 的指针先取下标为 1 的元素,然后访问成员
\texttt{Z}。由于成员是 \texttt{ST} 的第 2 个元素 (0-based),因此取下标 2。接下来是取成员
\texttt{B},同理由于其为第 1 个元素,取下标 1。\texttt{B} 的类型为一个二维数组,因此取下标 5 后再取下标 13
即可获得所需的指针。
上面的代码与下面的代码(逐步解引用)等价:
\begin{lstlisting}[language=llvm]
%struct.RT = type { i8, [10 x [20 x i32]], i8 }
%struct.ST = type { i32, double, %struct.RT }
define ptr @foo(ptr %s) {
%t1 = getelementptr %struct.ST, ptr %s, i32 1
%t2 = getelementptr %struct.ST, ptr %t1, i32 0, i32 2
%t3 = getelementptr %struct.RT, ptr %t2, i32 0, i32 1
%t4 = getelementptr [10 x [20 x i32]], ptr %t3, i32 0, i32 5
%t5 = getelementptr [20 x i32], ptr %t4, i32 0, i32 13
ret ptr %t5
}
\end{lstlisting}
\begin{itemize}
\item \texttt{ty} 为 \texttt{ptrval} 所指向内容的类型,因此
\texttt{\%t1 = getelementptr \%struct.ST, ptr \%p, i32 5}
表示通过 \texttt{\%struct.ST} 指针获得指向第 5 个 \texttt{ST}
的指针(如果需要访问的是指针指向的第 5 个元素,则应当使用
\texttt{\%t1 = getelementptr \%struct.ST, ptr \%p, i32 0, i32 5};
\item \texttt{result} 为指向对应成员的指针(一定要注意这一点);
\item 为了方便编写代码,推荐逐步解引用。
\end{itemize}
\subsubsection{\texttt{icmp} 指令}\label{LLVM-icmp-instructions}
\begin{remark}
完整文档位于 \url{https://llvm.org/docs/LangRef.html\#icmp-instruction}。
\end{remark}
语法:
\begin{lstlisting}[language=llvm]
<result> = icmp <cond> <ty> <op1>, <op2>
\end{lstlisting}
\begin{itemize}
\item \texttt{cond} 可以是
\begin{itemize}
\item \texttt{eq}:相等
\item \texttt{ne}:不相等
\item \texttt{ugt}:无符号大于
\item \texttt{uge}:无符号大于等于
\item \texttt{ult}:无符号小于
\item \texttt{ule}:无符号小于等于
\item \texttt{sgt}:有符号大于
\item \texttt{sge}:有符号大于等于
\item \texttt{slt}:有符号小于
\item \texttt{sle}:有符号小于等于
\end{itemize}
\item \texttt{op1} 和 \texttt{op2} 的类型必须为 \texttt{ty};
\item \texttt{result} 的类型为 \texttt{i1}。
\end{itemize}
例子:
\begin{lstlisting}[language=llvm]
%result = icmp eq i32 4, 5
\end{lstlisting}
该指令的结果为 \texttt{false}。
\subsubsection{\texttt{call} 指令}\label{LLVM-call-instructions}
\begin{remark}
完整文档位于 \url{https://llvm.org/docs/LangRef.html\#call-instruction}。
\end{remark}
语法:
\begin{lstlisting}[language=llvm]
<result> = call <ResultType> @<FunctionName>(<arguments>)
call void @<FunctionName>(<arguments>)
\end{lstlisting}
\begin{itemize}
\item 参数列表 (\texttt{arguments}) 中每个参数的格式为
\texttt{<ty> <val>},参数列表的类型以及参数个数必须与函数定义对应;
\item \texttt{result} 的类型必须为 \texttt{ResultType}(除非函数不会返回)。
\end{itemize}
例子:
\begin{lstlisting}[language=llvm]
%result = call i32 @foo1(i32 %arg1)
call void @foo2(i8 97)
\end{lstlisting}
\subsubsection{\texttt{phi} 指令}\label{LLVM-phi-instructions}
\begin{remark}
完整文档位于 \url{https://llvm.org/docs/LangRef.html\#phi-instruction}。
\end{remark}
语法:
\begin{lstlisting}[language=llvm]
<result> = phi <ty> [ <val0>, <label0>], ...
\end{lstlisting}
\begin{itemize}
\item \texttt{phi} 指令一般放于基本块的开头,用于通过特定的跳转来源来给变量赋值;
\item 如果从特定的 label 跳过来,则赋值为对应的值;
\item \texttt{result} 的类型必须为 \texttt{ty}。
\end{itemize}
例子:
\begin{lstlisting}[language=llvm]
Loop: ; Infinite loop that counts from 0 on up...
%indvar = phi i32 [ 0, %LoopHeader ], [ %nextindvar, %Loop ]
%nextindvar = add i32 %indvar, 1
br label %Loop
\end{lstlisting}
这段代码表示一个从 0 开始不停增加的循环,如果第 2 行的代码是从 \texttt{LoopHeader}
这一基本块跳来的,则赋值为 0;如果是从 \texttt{Loop} 这一基本块跳来的,则赋值为
\texttt{\%nextindvar}。
\texttt{phi} 指令可以大大减少 \texttt{alloca} 的使用,减少内存访问的次数,进而提升效率。因此
\texttt{phi} 指令在编译器优化过程中非常常见。
\subsubsection{\texttt{select} 指令}\label{LLVM-select-instructions}
\begin{remark}
完整文档位于 \url{https://llvm.org/docs/LangRef.html\#select-instruction}。
\end{remark}
语法:
\begin{lstlisting}[language=llvm]
<result> = select i1 <cond>, <ty> <val1>, <ty> <val2>
\end{lstlisting}
\begin{itemize}
\item \texttt{select} 指令基于 \texttt{cond} 选择对应的值。
\item 如果 \texttt{cond} 是 \texttt{true},则 \texttt{result}
为 \texttt{val1};否则为 \texttt{val2}。
\item \texttt{result} 的类型必须为 \texttt{ty}。
\end{itemize}
例子:
\begin{lstlisting}[language=llvm]
%X = select i1 true, i8 17, i8 42
\end{lstlisting}
该指令的结果为 \texttt{17},类型为 \texttt{i8}。
\section{从抽象语法树到中间语言}\label{AST-to-IR}
生成中间语言 (IR) 的过程是非常 “机械” 的,整体的逻辑见章节
\ref{AST-to-IR-details}。主要的工作在于为每个语法特性编写相应的转换逻辑
(\ref{AST-to-IR-specific-grammar})。除此以外,还需要解决
IR 的转换过程的一些小问题——如命名问题 (\ref{AST-to-IR-naming})、
初始化问题 (\ref{AST-to-IR-initializing})、内建功能相关问题
(\ref{AST-to-IR-for-builtin})。
在转换的过程中,你可以使用线上平台来辅助思考转换的逻辑。关于线上平台,参见章节
\ref{online-LLVM-env}。如果你不希望出现匿名变量,可以加上
\texttt{-fno-discard-value-names} 参数。需要注意的是,
线上平台可能会为了语言规范而采取更为复杂的实现逻辑,因此线上平台的解决方法未必是最简洁的。
此阶段中,程序的性能不是我们考虑的重点。比起性能,我们更关注程序的正确性。
你可能会注意到,此阶段的局部变量需要使用 \texttt{alloca}
指令获得一个局部变量的指针,然后使用指针的解引用完成局部变量的取值与赋值,这个方式大大减少了前端的复杂度,
但会造成较为严重的性能损失。不过,我们可以通过 mem2reg(章节 \ref{mem2reg})来处理掉这些
\texttt{alloca} 指令。
虽然本章中所有的 LLVM IR 的例子都是字符串形式的,但是在实际编写代码的时候不需要转换为字符串,而是应该转换为
LLVM IR 的文字形式对应的节点。节点的设计因人而异,但是整体上,每个翻译单元的根节点指向了所有的全局变量和函数,
函数节点一定指向了所有的基本块以及入口点,每个基本块节点一定包含了里面的所有指令,且最后一条指令总是
\texttt{br} 或 \texttt{ret} 指令,每个指令节点都记录了里面需要用到的变量和类型。此外,建议保留转换到
LLVM IR 字符形式的接口,以便调试和测试。
\begin{remark}
\begin{enumerate}
\item 线上平台的在处理 \texttt{bool} 时采取了非常复杂的方法,即用 \texttt{i8} 来储存
\texttt{bool} 型变量。相较于直接采用 \texttt{i1} 来说,这样的方式更为复杂。用 \texttt{i8}
或许是因为 C++ 规范要求的缘故,但 Mx* 不需要考虑这样的要求,因此将 \texttt{bool} 当作
\texttt{i1} 是更为推荐的做法。
\item 如果你在线上平台选择的语言为 C++,那么你会发现函数名和原来的函数名不一样
(一般会有一个下划线前缀和一个与函数参数相关的类型),这是由于
C++ 的 name mangling 机制,具体请上网执行了解。Name mangling
机制主要用于解决函数重载时命名冲突问题,通过改名的方式,让函数实际名称不会冲突。
一个简单的解决方案是全部使用 C 语言而不使用 C++,但是 C 语言没有成员函数,
在处理成员函数时仍然要将语言切换到 C++。
\item 由于现在多数操作系统不兼容 32 位应用,因此运行代码的方式主要依赖我们的模拟器
\href{https://github.com/Engineev/ravel}{ravel}。你可执行
\texttt{llc -march=riscv32} 来编译到汇编,然后使用 ravel 运行程序。
\end{enumerate}
\end{remark}
\subsection{转换方式概览}\label{AST-to-IR-details}
LLVM IR 的全局一共只有类成员结构定义、全局变量和全局函数三种内容。类成员结构对应类中成员的顺序;全局变量对应 Mx*
中的全局变量和字符串字面量,转换时需要注意初始化过程
(\ref{AST-to-IR-initializing});全局函数对应 Mx* 中的函数和成员方法。
Mx* 语言的设计决定了所有的类在使用时的行为与 C++ 的引用一致,而引用在底层设计上是指针。因此,
所有的类在传参和使用的过程中实际上都是指针。因此,类中实际上只存在三种类型——指针、
\texttt{bool} 和 \texttt{int}。
需要说明的一点是 Mx* 不能够在作用域结束之后就把分配的堆空间释放,因为分配的内存有可能会通过返回值传递到作用域外中。
因此程序会发生内存泄漏。但在初级编译阶段,内存泄漏的问题不在我们的考虑范围内。
除此以外,我们还需要处理内建功能,详见章节
\ref{AST-to-IR-for-builtin}。
\subsection{为 IR 的变量、类及函数命名}\label{AST-to-IR-naming}
在 LLVM IR 中,要求全局变量和局部变量内部不能出现命名冲突。但是高级语言中,作用域使得命名会发生冲突,并遵循
variable shadowing 原则。一些语言会允许类名称与变量名重复。此外,在命名过程中,还要防止
IR 中内建的变量、类以及函数与用户的命名不会冲突。
对于命名冲突,一般有以下集中解决方案:
\begin{enumerate}
\item 给变量加上前缀或后缀(通常是后缀)。这在 variable shadowing 下非常常见。比如,有一个作用域中已经有变量
\texttt x,其内部又有一个作用域中声明了另一个变量 \texttt x,可以将其重命名为
\texttt{x.1}。
\item 选择一些高级语言不可使用的名称作为 identifier。这在内建函数和类的成员方法中非常常见。
常见的不可使用的名称有不可出现在 identifier 中的符号(如 \texttt{.})、关键词。
比如类 \texttt A 的成员方法 \texttt{B},就可以命名为 \texttt{A.B};
内建类 \texttt{string} 的成员方法 \texttt{ord},就可以命名为 \texttt{string.ord}
(因为 \texttt{string}是关键词)。
\end{enumerate}
Mx* 中的函数都是全局的,因此不会出现命名冲突。类的方法可以通过
\texttt{<ClassName>.<MethodName>} 来解决冲突。受到 variable shadowing
影响的变量可以通过在后面加上 \texttt{.<n>} 来解决冲突。n 的值可以在语法检查时完成。
\subsection{为全局变量进行初始化}\label{AST-to-IR-initializing}
对于全局变量,由于 LLVM IR 中的全局变量需要指定一个初始的值,因此我们可以利用这个值。
如果变量被一个定值初始化,那么我们就直接将这个常量初始化。比如我们有这样的一个全局变量
\begin{lstlisting}[language=C]
int i = 1;
\end{lstlisting}
对应的 LLVM IR 为
\begin{lstlisting}[language=C]
@i = global i32 1
\end{lstlisting}
如果变量初始化的值无法在此时决定,那么我们需要借助运行期来完成初始化。
一个可行的解决方案是将初始化过程放到初始化函数中。如前文中对命名的解决方案
(\ref{AST-to-IR-naming}),我们可以给这个函数起名为 \texttt{\_init},然后在
\texttt{main} 函数中调用 \texttt{\_init}。比如我们有一个这样的全局变量
\begin{lstlisting}[language=C]
int i = j;
\end{lstlisting}
对应的 LLVM IR 为
\begin{lstlisting}[language=C]
@i = global i32 0
define void @_init() {
entry:
%0 = load i32, ptr @j
store i32 %0, ptr @i
ret void
}
\end{lstlisting}
关于字符串字面量,请参考字符串的实现章节 (\ref{AST-to-IR-for-builtin-string})。
\subsection{为每个语法特性编写相应的转换逻辑}\label{AST-to-IR-specific-grammar}
每个翻译单元有若干全局变量,若干函数,若干类。本章中,我们会一一讲解这些部分的转换逻辑。
推荐在写对应的逻辑的时候,先与 \texttt{clang} 生成的代码比较,避免出现理解上的错误。
全局变量的处理非常简单,LLVM IR 中的全局变量已经是指向对应类型的指针,因此基本上无需操作,
唯一的困难点在于初始化,参见上文变量初始化部分 (\ref{AST-to-IR-initializing})。
函数的处理分为几个部分——函数变量处理、函数的参数和函数名处理、函数体的转换。
对于函数中的变量,我们需要利用 \texttt{alloca} 指令来完成,
具体参见局部变量处理章节 (\ref{AST-to-IR-local-variables})。
函数的参数和函数名处理较为简单,只要将参数的类型转换成 IR 中对应的类型即可,函数名无需更改,
直接使用原函数名。
函数体的转换的转换实际上是一个语句块的转换,
需要对每个语句进行逐句转换。关于语句的处理,参见语句处理章节 (\ref{AST-to-IR-statements})。
类的处理分为类的成员变量、类的方法(成员函数)和构造函数三个部分。
\begin{itemize}
\item Mx* 中,类的所有变量都不是静态的 (static),因此每个类的实例中都必须包含所有变量。
利用 LLVM IR 的结构类型(见 \ref{LLVM-types}),我们可以将所有成员变量打包,如对于类
\texttt{class A \{ int a; B b; \};} 只需要在 IR 的全局声明
\texttt{\%class.A = \{ i32, ptr \}} 即可。
\item 类的方法处理过程其实与普通函数不同,唯一的区别在于需要在函数参数传入指向对应类的指针(即
\texttt{this} 指针),通常的做法是将函数的第一个入参作为对应类指针。
\item 对于构造函数,需要注意的是,如果有在类中标记变量的初始化表达式,
需要先计算出对应的值并将其赋给对应变量,然后再执行 Mx* 中的构造函数中的内容。
另外需要注意空间分配的问题,由于一般将全局变量放在静态部分(不会在运行期申请全局变量的空间),
而构造函数也有可能要处理全局变量,所以一般来说,构造函数并不会为这个类申请空间(如局部变量需要交给
\texttt{new} 表达式来做,全局变量会事先分配对应的空间),因此构造函数需要
\texttt{this} 指针作为入参。
\end{itemize}
\subsubsection{处理局部变量}\label{AST-to-IR-local-variables}
由于 LLVM IR 中的变量在每个基本块中只能被赋值一次,
因此我们无法将 LLVM IR 中的变量直接与 Mx* 对应,需要借助指针来完成局部变量的赋值。
\texttt{alloca} 指令给了我们一个方便的方法来处理全局变量。
具体来说,假设我们有一个 \texttt{int} 型局部变量 \texttt{a},
\begin{lstlisting}[language=C++]
int a;
\end{lstlisting}
我们会将其处理成指向这个 \texttt{i32} 的指针,
\begin{lstlisting}[language=LLVM]
%a = alloca i32
\end{lstlisting}
如果要对 \texttt{a} 赋值,我们可以使用 \texttt{store} 指令。下面的语句表示将 1 赋值给 \texttt{a}
\begin{lstlisting}[language=LLVM]
store i32 1, ptr %a
\end{lstlisting}
如果要取 \texttt{a} 的值,我们可以使用 \texttt{load} 指令。下面的语句中 \texttt{\%b}
表示取 \texttt{a} 的值
\begin{lstlisting}[language=LLVM]
%b = load i32, ptr %5
\end{lstlisting}
虽然这样的设计看起来效率很低,总是要读写内存,但是 Mem2Reg 优化 (\ref{mem2reg}) 可以把这样的
\texttt{alloca} 指令全部转化到寄存器中。
\subsubsection{处理语句}\label{AST-to-IR-statements}
Mx* 中,有五种语句——变量声明语句、条件语句、循环语句、跳转语句、表达式语句。我们会一一解释针对每一种语句如何进行转换。
\begin{itemize}
\item 对于变量声明语句,LLVM IR 专门设计了 \texttt{alloca} 指令 (\ref{LLVM-alloca-instructions}),
以减轻前端编写的难度。因此,每次遇到变量声明语句,我们就将经修改后的变量名(修改变量名是为了避免变量重名,
参见章节 \ref{AST-to-IR-naming})用 \texttt{alloca} 指令声明。为了方便,通常我们会将
\texttt{alloca} 指令放在每个函数的入口基本块中。比如对于以下代码,
\begin{lstlisting}[language=C++]
int foo() {
int a;
{
int a;
}
return a;
}
\end{lstlisting}
其对应的 IR 可以是
\begin{lstlisting}
define i32 @doo() {
entry:
%a = alloca i32
%a.1 = alloca i32
ret i32 %a
}
\end{lstlisting}
\item 对于条件语句,我们需要先计算表达式,然后判断条件是否满足,然后跳到对应的分支。比如对于以下的代码,
\begin{lstlisting}[language=C++]
bool a = true;
int foo() {
int b;
if (a) b = 1;
else b = 0;
return b;
}
\end{lstlisting}
其对应的 IR 可以是
\begin{lstlisting}[language=LLVM]
define i32 @foo() {
entry:
%b = alloca i32
%a.val.1 = load i1, ptr @a
br i1 %a.val.1, label %true_0, label %false_0
true_0:
store i32 1, ptr %b
br label %condition_end_0
false_0:
store i32 0, ptr %b
br label %condition_end_0
condition_end_0:
%b.val.2 = load i32, ptr %b
ret i32 %b.val.2
}
\end{lstlisting}
\item 对于循环语句和跳转语句,最多需要有五个部分——初始化部分(这部分可以不需要一个新基本块,
沿用原基本块即可)、循环条件部分、循环体部分和更新 (step) 部分、循环结束部分,
每个部分都是一个基本块。\texttt{break} 语句需要跳转到循环结束部分,而 \texttt{continue}
需要跳转到更新部分(如果有)或循环条件部分(如果没有更新部分)。\texttt{return}
只需要让函数返回即可。比如对下面的代码,
\begin{lstlisting}[language=C++]
void foo() {
int a = 0;
for (int i = 0; i < 100; ++i) {
if (i == 50) break;
if (i == 51) continue;
a = a + i;
}
}
int main() {}
\end{lstlisting}
其对应的 IR 可以是
\begin{lstlisting}[language=LLVM]
define void @foo() {
entry:
%a = alloca i32
%i = alloca i32
store i32 0, ptr %a
store i32 0, ptr %i
br label %loop_condition_0
loop_condition_0:
%i.val.1 = load i32, ptr %i
%__comp.2 = icmp slt i32 %i.val.1, 100
br i1 %__comp.2, label %loop_body_0, label %loop_end_0
loop_body_0:
%i.val.3 = load i32, ptr %i
%__comp.4 = icmp eq i32 %i.val.3, 50
br i1 %__comp.4, label %true_0, label %condition_end_0
true_0:
br label %loop_end_0
condition_end_0:
%i.val.5 = load i32, ptr %i
%__comp.6 = icmp eq i32 %i.val.5, 51
br i1 %__comp.6, label %true_1, label %condition_end_1
true_1:
br label %step_0
condition_end_1:
%a.val.7 = load i32, ptr %a
%i.val.8 = load i32, ptr %i
%__binary.9 = add i32 %a.val.7, %i.val.8
store i32 %__binary.9, ptr %a
br label %step_0
step_0:
%__update.old.10 = load i32, ptr %i
%__update.new.11 = add i32 %__update.old.10, 1
store i32 %__update.new.11, ptr %i
br label %loop_condition_0
loop_end_0:
ret void
}
\end{lstlisting}
\item 表达式部分只需要处理表达式即可,参见章节 \ref{AST-to-IR-expression}。
\end{itemize}
\subsubsection{处理表达式}\label{AST-to-IR-expression}
绝大多数表达式都是很容易转换的,只要注意好行为以及表达式的类型,一般不会发生问题。不过,\texttt{new}
表达式和逻辑表达式的处理较为复杂。
对于 \texttt{new} 表达式,其最大的难点在于如何解决多层数组。思路一般是在 IR
上手动实现循环,来给各层的数组初始化。另一种实现是在抽象语法树 (AST)
上实现循环。
另外,在处理 \texttt{new} 表达式的过程中,一定要注意如果当前层已经涉及类对象,一定要为类对象分配空间,
并调用构造函数。如果当前层仍然对应数组,最好给所有的数组初始化为
\texttt{null}。
对于逻辑表达式,需要注意的一点是短路求值,如果前一个表达式已经足以确定逻辑表达式的结果时,
就不能够执行后一个表达式了。要想实现这个逻辑,必须将表达式分为两个基础块,分别执行左边和右边的表达式,
首先执行左边的表达式,然后通过 \texttt{br} 指令 (\ref{LLVM-br-instructions})
判断是否需要继续执行后一个表达式,如果要执行,就跳到右边表达式对应的部分,否则跳到之后的语句。
\subsection{内建功能实现}\label{AST-to-IR-for-builtin}
关于 Mx* 内建功能的实现,我们将会分为内建函数及成员方法的实现
(\ref{AST-to-IR-for-builtin-func})、\texttt{string} 的实现
(\ref{AST-to-IR-for-builtin-string})、数组的实现
(\ref{AST-to-IR-for-builtin-array}) 几部分来讲。
\subsubsection{为内建函数及内建成员方法生成对应的 IR}\label{AST-to-IR-for-builtin-func}
为内建函数及内建成员方法生成对应的 IR 的最方便的办法是使用 C
语言编写内建函数和内建成员方法,然后使用 clang 转换,并在用户输入的源代码对应的 IR 中声明对应的函数。
编写内建函数时,我们可以使用一些 ravel 支持的 libc 函数,支持的函数见 ravel 的文档
(\url{https://github.com/Engineev/ravel/blob/master/doc/support.md#libc-functions})。
使用这些函数时,只需要在 C 语言编写的内建函数文件开头声明这些函数即可。
如果你不理解这其中的原理,请自行了解链接器 (linker) 的原理
(\url{https://en.wikipedia.org/wiki/Linker_(computing)})。特别需要注意的是,不建议
\texttt{\#include} 头文件,因为这会导致生成的代码非常大。此外,你不用担心在测试时无法正确找到这些函数,
因为编译器通常都会默认链接 libc。
不过,由于 C 函数名不能包含 \texttt{.},因此对于内建类的内建成员方法,