Scratchpad and NoC

导言

解释 Scratchpad 时,我们很快会遇到一个新问题:数据由 DMA 显式搬入片上 SRAM,但它从 HBM 到计算核心附近的 Scratchpad,究竟经过了什么?答案里反复出现的 NoC,又是什么概念?

这篇文章沿着一次数据搬运,把几个容易混在一起的对象摆回各自的位置:Scratchpad 负责本地存取,NoC 负责片上运输,DMA 负责执行显式搬运。

Read more

Memory Consistency and Cache Coherence

导言

许多现代计算机系统,包括同构和异构体系结构,都支持硬件中的共享内存。在共享内存系统中,每个处理器内核可以对单个共享地址空间进行读写。

要拓展Victima模拟器的cache部分,首先要明白一些缓存一致性的基础知识。

Read more

Workload Characterization & Priority & Scheduler to CPU/GPU/PIM

导言

姜师兄想研究不同计算硬件,或者是异构计算的适用领域。在调研时Davinci架构时,我感觉Domain-Specific Architectures是未来,这会导致会模糊CPU,GPU,PIM之间的区别。导致这个问题是在将来是伪需求

在完成了$A^3PIM$的工作后,其实利用了加速器(PIM)的调度的核心就是:

考虑数据移动和同步等负面开销后,来贪心最小耗时的策略。

但是对于这个复杂的问题,我的观点是 No silver bullet

Read more

New Generation Shenwei System

导言

研究生期间,虽然我是HPC方向的。但是我并没有参与过新一代神威系统的软件优化。在毕业前,特地了解相关设计。[^1][^2]

Read more

Disassembly file: objdump

objdump file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Disassembly of section .plt:

0000000000402020 <.plt>:
402020: ff 35 e2 bf 02 00 pushq 0x2bfe2(%rip) # 42e008 <_GLOBAL_OFFSET_TABLE_+0x8>
402026: ff 25 e4 bf 02 00 jmpq *0x2bfe4(%rip) # 42e010 <_GLOBAL_OFFSET_TABLE_+0x10>
40202c: 0f 1f 40 00 nopl 0x0(%rax)

0000000000402030 <_Znam@plt>:
402030: ff 25 e2 bf 02 00 jmpq *0x2bfe2(%rip) # 42e018 <_Znam@GLIBCXX_3.4>
402036: 68 00 00 00 00 pushq $0x0
40203b: e9 e0 ff ff ff jmpq 402020 <.plt>

0000000000402040 <_ZNSo3putEc@plt>:
402040: ff 25 da bf 02 00 jmpq *0x2bfda(%rip) # 42e020 <_ZNSo3putEc@GLIBCXX_3.4>
402046: 68 01 00 00 00 pushq $0x1
40204b: e9 d0 ff ff ff jmpq 402020 <.plt>

.plt节主要实现了使用过程链接表(Procedure Linkage Table)实现延迟绑定的功能。

openmp相关的执行

omp_outlined函数

问题:objdump 程序 有许多 类似 <.omp_outlined..16>: 的函数,但是main函数里并没有调用。实际openmp是怎么执行这些代码的呢?

在使用了OpenMP指令的C/C++程序编译后,编译器会自动生成一些名为.omp_outlined.的函数。这些函数是OpenMP所需要的运行时支持函数,不是直接在main函数中调用的,其执行方式主要有以下几种:

  1. 一些关键的运行时函数,如线程创建、同步等,是在程序启动时由OpenMP运行时库初始化和直接调用的。
  2. 对于并行region,编译器会在主线程发起并行时调用对应的.omp_outlined.函数创建线程并发布工作。
  3. 一些工作分配,同步等调用也是隐式通过运行时库的支持函数实现的。
  4. 对于并行循环,编译器会将循环体移动到.omp_outlined.函数,在循环分配工作时调用。
  5. 减少指令,作用域变化等也会引发这些函数的调用。

所以.omp_outlined.函数的执行是隐式通过运行时库触发和调度的,不需要用户代码直接调用。它们是OpenMP实现所必须的,由编译器和运行时库协调完成。用户只需要编写OpenMP指令,不必关心具体的调用细节。

总体来说,这是一种让并行执行透明化的实现机制,减少了用户的工作量。

OpenMP的汇编代码

不同平台不同,有GOMP_parallel_start开头的。也有如下x86平台的

1
2
3
4
5
6
7
8
9
10
11
405854:	48 c7 84 24 a0 00 00 	movq   $0x4293b9,0xa0(%rsp)
40585b: 00 b9 93 42 00
405860: 48 8d bc 24 90 00 00 lea 0x90(%rsp),%rdi
405867: 00
405868: ba 10 5f 40 00 mov $0x405f10,%edx
40586d: be 02 00 00 00 mov $0x2,%esi
405872: 4c 89 f9 mov %r15,%rcx
405875: 4c 8b 44 24 20 mov 0x20(%rsp),%r8
40587a: 31 c0 xor %eax,%eax
40587c: e8 ff cb ff ff callq 402480 <__kmpc_fork_call@plt>
405881: 48 8b 7c 24 60 mov 0x60(%rsp),%rdi

这段汇编代码实现了OpenMP中的并行构造,主要执行了以下几个步骤:

  1. 在栈上写入一个常量0x4293b9,可能是team的参数 (48 c7 84 24)
  2. 准备参数,获取rsp+0x90地址到rdi作为第1参数 (%rdi)
  3. 设置edx为0x405f10,可能是kmp_routine函数地址
  4. esi设置为2,可能表示有2个参数
  5. r15设置到rcx,传入线程号参数
  6. r8传入栈上第0x20个参数,可能是void* shareds参数
  7. 清空eax,一些调用约定使用
  8. 调用 __kmpc_fork_call函数,这是OpenMP的runtime库函数,用来并行执行一个函数
    1. kmpc fork multiple parallel call?
  9. 最后将返回值保存在rdi指定的栈空间上

所以这段代码实现了调用OpenMP runtime并行执行一个函数的操作,准备参数,调用runtime API,获取返回值的一个流程。

利用runtime库的支持函数可以实现汇编级别的OpenMP并行性。

readelf

各section位置以及含义,参考文档

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
$ readelf -S bfs.inj
There are 37 section headers, starting at offset 0xbe8e8:
在文件内 0xbe8e8字节开始

Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
序号 节名称 节类型 节的虚拟地址偏移量 节在文件中的偏移量
节大小 每个条目的大小(如果大小固定) 节的标志 节的链接信息 节的额外信息 节的信息对齐方式
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000004002a8 000002a8
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.build-i NOTE 00000000004002c4 000002c4
0000000000000024 0000000000000000 A 0 0 4
[ 3] .note.ABI-tag NOTE 00000000004002e8 000002e8
0000000000000020 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400308 00000308
000000000000005c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000400368 00000368
00000000000007e0 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400b48 00000b48
0000000000000b1d 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000401666 00001666
00000000000000a8 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000401710 00001710
0000000000000110 0000000000000000 A 6 5 8
[ 9] .rela.dyn RELA 0000000000401820 00001820
00000000000000f0 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000401910 00001910
00000000000006c0 0000000000000018 AI 5 24 8
[11] .init PROGBITS 0000000000402000 00002000
000000000000001b 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000402020 00002020
0000000000000490 0000000000000010 AX 0 0 16
[13] .text PROGBITS 00000000004024b0 000024b0
0000000000026475 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 0000000000428928 00028928
000000000000000d 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000429000 00029000
0000000000001180 0000000000000000 A 0 0 16
[16] .eh_frame_hdr PROGBITS 000000000042a180 0002a180
00000000000002ac 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 000000000042a430 0002a430
0000000000001780 0000000000000000 A 0 0 8
[18] .gcc_except_table PROGBITS 000000000042bbb0 0002bbb0
00000000000005d0 0000000000000000 A 0 0 4
[19] .init_array INIT_ARRAY 000000000042dbc8 0002cbc8
0000000000000010 0000000000000008 WA 0 0 8
[20] .fini_array FINI_ARRAY 000000000042dbd8 0002cbd8
0000000000000008 0000000000000008 WA 0 0 8
[21] .data.rel.ro PROGBITS 000000000042dbe0 0002cbe0
00000000000001f0 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 000000000042ddd0 0002cdd0
0000000000000220 0000000000000010 WA 6 0 8
[23] .got PROGBITS 000000000042dff0 0002cff0
0000000000000010 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 000000000042e000 0002d000
0000000000000258 0000000000000008 WA 0 0 8
[25] .data PROGBITS 000000000042e258 0002d258
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 000000000042e280 0002d268
0000000000000180 0000000000000000 WA 0 0 64
[27] .comment PROGBITS 0000000000000000 0002d268
000000000000004a 0000000000000001 MS 0 0 1
[28] .debug_info PROGBITS 0000000000000000 0002d2b2
000000000002a06e 0000000000000000 0 0 1
[29] .debug_abbrev PROGBITS 0000000000000000 00057320
0000000000000a57 0000000000000000 0 0 1
[30] .debug_line PROGBITS 0000000000000000 00057d77
000000000000af9a 0000000000000000 0 0 1
[31] .debug_str PROGBITS 0000000000000000 00062d11
0000000000010328 0000000000000001 MS 0 0 1
[32] .debug_loc PROGBITS 0000000000000000 00073039
0000000000042846 0000000000000000 0 0 1
[33] .debug_ranges PROGBITS 0000000000000000 000b587f
00000000000054c0 0000000000000000 0 0 1
[34] .symtab SYMTAB 0000000000000000 000bad40
00000000000018c0 0000000000000018 35 106 8
[35] .strtab STRTAB 0000000000000000 000bc600
0000000000002177 0000000000000000 0 0 1
[36] .shstrtab STRTAB 0000000000000000 000be777
000000000000016c 0000000000000000 0 0 1
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),
l (large), p (processor specific)

字段含义

  • Type 字段,具体含义参考文档1-10
  • Link 字段中的值是节头表中节头条目的索引,索引从0开始,表示第一个节头表条目,依此类推。比如5 代表与[ 5] .dynsym 有关

值得注意

One section type, SHT_NOBITS described below, occupies no
space in the file, and its sh_offset member locates the conceptual placement in the
file.

so the number “2d258” remains unchanged.

1
2
3
4
[25] .data             PROGBITS         000000000042e258  0002d258
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 000000000042e280 0002d268
0000000000000180 0000000000000000 WA 0 0 64

.got

global offset table

.plt

This section holds the procedure linkage table. See ‘‘Special Sections’’ in Part 1 and ‘‘Procedure Linkage Table’’ in Part 2 for more information.

Function symbols (those with type STT_FUNC) in shared object files have special significance. When
another object file references a function from a shared object, the link editor automatically creates a procedure linkage table entry for the referenced symbol.

参考文档2-17 page48

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。

llvm

llvm

LLVM项目开始于一种比Java字节码更低层级的IR,因此,初始的首字母缩略词是Low Level Virtual Machine。它的想法是发掘低层优化的机会,采用链接时优化。

插一嘴:链接时优化

  • GCC也支持链接时优化,称为LTO(Link Time Optimization),通过把多个编译单元中分别生成的目标文件在链接时进行全局的优化,可以提高程序的执行效率。
  • 具体内容:大幅度减少可执行文件的体积
    • 冗余代码和变量/函数的消除:对于在多个模块中出现的相同代码/变量/函数,链接时优化可以将它们合并,从而减少可执行文件的体积,提高程序的执行效率。
    • 内联函数:将函数调用直接替换为函数本身的代码,从而减少函数调用的开销,提高程序的执行效率。
    • 循环展开和向量化:内联函数后,或许能进一步将循环展开和向量化,从而减少循环体内的分支判断,优化程序的执行效率。

IR:gcc与llvm的区别

学过编译原理的人都知道,编译过程主要可以划分为前端与后端:

  • 前端把源代码翻译成中间表示 (IR)。
  • 后端把IR编译成目标平台的机器码。当然,IR也可以给解释器解释执行。

经典的编译器如gcc:在设计上前端到后端编写是强耦合的,你不需要知道,无法知道,也没有API来操作它的IR。

  • 好处是:因为不需要暴露中间过程的接口,编译器可以在内部做任何想做的平台相关的优化。

  • 坏处是,每当一个新的平台出现,这些编译器都要各自为政实现一个从自己的IR到新平台的后端。

    • 甚至如果当一种新语言出现,且需要实现一个新的编译器,那么可能需要设计一个新的IR,以及针对大部分平台实现这个IR的后端。
    • 如果有M种语言、N种目标平台,那么最坏情况下要实现 M*N 个前后端。这是很低效的。
  • LLVM的核心设计了一个叫 LLVM IR 的通用中间表示, 并以库(Library) 的方式提供一系列接口, 为你提供诸如操作IR、生成目标平台代码等等后端的功能。

  • 在使用通用IR的情况下,如果有M种语言、N种目标平台,那么最优情况下我们只要实现 M+N 个前后端。

llvm IR

  • LLVM IR 中间表示是适用于多种编程语言的通用中间表示,支持C、C++、Objective-C、Swift、Java、Python等多种编程语言。
  • 它是一种低级别的语言,类似于汇编语言,但比汇编语言更高级,包含了类型、变量、函数、控制流等高级语言的特性。
  • LLVM编译器可以将多种编程语言编译成LLVM IR,从而可以在LLVM IR层面进行各种优化处理,再将LLVM IR转换为目标平台的机器码。
    • 比如要将Python脚本编译成LLVM IR中间表示,可以使用Python LLVM编译器llvmlite和numba。

LLVM IR表示与转换

LVM IR实际上有三种表示:

  • .ll 格式:人类可以阅读的文本。
  • .bc 格式:适合机器存储的二进制文件。
  • 内存表示

各种格式是如何生成并相互转换:

格式 转换命令
.c -> .ll clang -emit-llvm -S a.c -o a.ll
.c -> .bc clang -emit-llvm -c a.c -o a.bc
.ll -> .bc llvm-as a.ll -o a.bc
.bc -> .ll llvm-dis a.bc -o a.ll
.bc -> .s llc a.bc -o a.s

对于LLVM IR来说,.ll文件就相当于汇编,.bc文件就相当于机器码。 这也是llvm-as和llvm-dis指令为什么叫as和dis的缘故。

llvm 前端

前端

clang实现的前端包括

  • 词法分析(识别标记)
    • 处理源代码的文本输入,将语言结构分解为一组单词和标记,去除注释、空白、制表符等。每个单词或者标记必须属于语言子集,语言的保留字被变换为编译器内部表示。
    • 词法分析报错的例子包括:拼写错误、注释没有正确结束、字符串没有正确结束等。
  • 语法分析(标记结构完整)
    • 语法分析器会根据语法规则验证程序的正确性,如缺少右括号、是否缺少关键字、变量未定义、函数应该有返回值等。
  • 语义分析(有无语义矛盾)
    • 借助符号表检验代码没有违背语言类型系统。这个表存储标识符(符号)和它们各自的类型之间的映射,以及其它内容。
    • 类型检查的一种直觉的方法是,在解析之后,遍历AST的同时从符号表收集关于类型的信息。
    • 例子:定义了两个变量 a 冲突。

llvm 后端

见 llvm Backend 一文

clang

clang 与 llvm的关系

Clang 是 LLVM 项目中的一个 C/C++/Objective-C 编译器,它使用 LLVM 的前端和后端进行代码生成和优化。它可以将 C/C++/Objective-C 代码编译为 LLVM 的中间表示(LLVM IR),然后进一步将其转换为目标平台的机器码。Clang 拥有很好的错误信息展示和提示,支持多平台使用,是许多开发者的首选编译器之一。同时,Clang 也作为 LLVM 项目的一个前端,为 LLVM 的生态系统提供了广泛的支持和应用。

clang 的开发与苹果公司的关系

Clang 的开发起源于苹果公司的一个项目,即 LLVM/Clang 项目。在 2005 年,苹果公司希望能够使用一种更加灵活、可扩展、优化的编译器来替代 GCC 作为其操作系统 macOS (Mac OS X) 开发环境的默认编译器。由于当时的 GCC 开发被其维护者们认为变得缓慢和难以维护,苹果公司决定开发一款新的编译器,这就是 Clang 诞生的原因。Clang 的开发团队由该项目的创立者 Chris Lattner 领导,他带领团队将 Clang 发展为一款可扩展、模块化、高效的编译器,并成功地将其嵌入到苹果公司的开发工具链 Xcode 中,成为了 macOS 开发环境中默认的编译器之一。

Clang 是一个开源项目,在苹果公司的支持下,Clang 的开发得到了全球各地的开发者们的广泛参与和贡献。现在,Clang 成为了 LLVM 生态中的一个重要组成部分,被广泛地应用于多平台的编译器开发中。

clang-cannot-find-iostream

Clang and Clang++ “borrow” the header files from GCC & G++. It looks for the directories these usually live in and picks the latest one. If you’ve installed a later GCC without the corresponding G++, Clang++ gets confused and can’t find header files. In your instance, for example, if you’ve installed gcc 11 or 12.

You can use clang-10 -v -E or clang++-10 -v -E to get details on what versions of header files it’s trying to use.

安装g++-12解决

常用工具

github/tools目录下有许多实用工具

  • llvm-as:把LLVM IR从人类能看懂的文本格式汇编成二进制格式。注意:此处得到的不是目标平台的机器码。
  • llvm-dis:llvm-as的逆过程,即反汇编。 不过这里的反汇编的对象是LLVM IR的二进制格式,而不是机器码。
  • opt:优化LLVM IR。输出新的LLVM IR。
  • llc:把LLVM IR编译成汇编码。需要用as进一步得到机器码。
  • lli:解释执行LLVM IR。

需要进一步的研究学习

暂无

遇到的问题

暂无

开题缘由、总结、反思、吐槽~~

参考文献

文章部分内容来自ChatGPT-3.5,暂时没有校验其可靠性(看上去貌似说得通)。

https://zhuanlan.zhihu.com/p/122522485