DSP 学习笔记|(七)HiFi 汇编说明
Last updated on April 30, 2023 am
一、储备知识
汇编构成
基本上 assembly source 由以下三个部分組成,
- instruction set
- assembler syntax
- directive
Instruction set 基本上对于相同的 CPU 来说是固定的,比较不会随着 assembler 不同。
每個 CPU 可能會有不同 version 的 instruction set architecture (ISA) 如,
- ARMv6
- ARMv7-A
- ARMv7-M
- MIPS32r2 等等,及每一個 version 可能有不同的 extension 如,
- Thumb2
- Neon
Assembler syntax 每個不同的 assembler 會有一些自訂的語法,如 symbol 的標記方式、 include header 、如何宣告資料、 註解格式等,請參閱各自的 toolchain 的說明文件。
调用约定
理解汇编首先且最重要的事就是明白汇编代码与代码之间是如何交互的。函数如何调用其他函数。包括参数是如何传递给函数,及函数返回值是如何返回的。
这些事情执行的过程与实现被称为调用约定(Calling Conventions)。编译器必须遵循它预定义的标准,这样才能让编译后的代码能和其他不同编译器编译出的代码能够交互。没有标准,编译器能编译出不相配的代码。
如上讨论,寄存器是和 CPU 联系非常紧密的一小块内存,经常用于存储一些正在使用的数据。
函数的调用过程中有两个参与者,一个是调用方 caller,另一个是被调用方 callee。
调用约定规定了 caller 和 callee 之间如何相互配合来实现函数调用,具体包括的内容如下:
函数的参数存放在哪的问题。是放在寄存器中?还是放在栈中?放在哪个寄存器中?放在栈中的哪个位置?
函数的参数按何种顺序传递的问题。是从左到右将参数入栈,还是从右到左将参数入栈?
返回值如何传递给 caller 的问题。是放在寄存器里面,还是放在其他地方?
ARM处理器有16个寄存器,从r0到r15,每一个都是32位比特。调用约定指定他们其中的一些寄存器有特殊的用途,例如:
- r0-r3:用于存放传递给函数的参数;
- r4-r11:用于存放函数的本地参数;
- r12:是内部程序调用暂时寄存器。这个寄存器很特别是因为可以通过函数调用来改变它;
- r13:栈指针sp(stack pointer)。在计算机科学内栈是非常重要的术语。寄存器存放了一个指向栈顶的指针。看这里了解更多关于栈的信息;
- r14:是链接寄存器lr(link register)。它保存了当目前函数返回时下一个函数的地址;
- r15:是程序计数器pc(program counter)。它存放了当前执行指令的地址。在每个指令执行完成后会自动增加;
你可以在 ARM 文档里了解更多关于 ARM 调用约定的信息。苹果也在文档【iOS开发调用约定】内有做过详细描述。
二、汇编指示(assembly directive)
- 类似
_main:
或Ltmp0:
的形式被称之为标签(label), 用于辅助定位代码或者资源地址, 便于开发者理解和记忆; - 类似
pushq
或movq
的形式被称之为汇编指令, 它们会被汇编器编译为机器代码, 最终被 CPU 所执行; - 类似
.section
或.globl
等以.
开头的形式被称之为编译器指令(assembly directive), 用于告知编译器相关的信息或者进行特定操作。他们不是汇编指令而是作用于汇编器的,可以忽略所有这样的代码; - 关于 directive 和 instruction 这两个词的区别,前者翻译为指示,后者翻译成指令。因为一般 directive 并不会产生代码而是指示编译器的一些行为,而 instruction 则会产生实际的代码;
.loc 5 76 49
**eg. **
说明:
- fileno : 文件描述符
- lineno : 行号
- colum:列数(操作符的位置)
.literal_position
eg.
说明:
.p2align
eg.
说明:
p2align 3 表示 2 的 3 次幂 = 8
p2align 2 表示 2 的 2 次幂 = 4
函数名_
eg.
编译器通常在函数名的前面添加一个下划线。
.cfi_startproc
eh_frame:GCC Exception Frame,也就是eh_frame。这里提到 eh_frame 是 dwarf 调试信息的变体(variant)。eh_frame 段中存储着跟函数入栈相关的关键数据。当函数执行入栈指令后,在该段会保存跟入栈指令一一对应的编码数据,根据这些编码数据,就能计算出当前函数栈大小和cpu的哪些寄存器入栈了,在栈中什么位置。
.cfi_def_cfa
.cfi_def_cfa_offset
prologue_end
.type
eg.
汇编里面所有以 :
结尾的都会视为标签 ( label
),在这里我们定义一个叫做 main
的标签,并且使用 .type
伪指令定义这个标签的类型是一个函数(function
),到此我们就定义了我们的 main
函数。
说明:
.Ltmp:
标签,不是指令。是这部分的汇编代码名字。
is_stmt
此选项会将 .debug_line 状态机中的 is_stmt 寄存器设置为 value,该值必须为 0 或 1。
L32R
L32R 指令从指定地址加载 32 位值。因此,“l32r a2,2ec8” 将地址 0x2ec8 处的 32 位值加载到寄存器 a2 中。
L. 本地标签
eg.
所有以 L.
开头的都为本地标签,这些标签只能用于函数内部。
[Ⅱ *1+0]
eg.
[Ⅱ * 1 + 0]
Ⅱ:数字乘以 II 符号表示正在执行的迭代;
1:乘 1 代表在第二次迭代中的运算,乘 0 就代表在第一次迭代中的运算;
0:最后的数字代表该指令处于第几个 cycle 中。
指示参考表(节选)
GNU Assembler Directive | Note |
---|---|
.text | Tells as to assemble the following statements onto the end of the text subsection numbered subsection, which is an absolute expression. If subsection is omitted, subsection number zero is used. |
.file | .file (which may also be spelled .app-file') tells asthat we are about to start a new logical file. string is the new file name. In general, the filename is recognized whether or not it is surrounded by quotes “‘; |
.globl | .global makes the symbol visible to ld . If you define symbol in your partial program, its value is made available to other partial programs that are linked with it. Otherwise, symbol takes its attributes from a symbol of the same name from another file linked into the same program. |
.p2align | Pad the location counter (in the current subsection) to a particular storage boundary. The first expression (which must be absolute) is the number of low-order zero bits the location counter must have after advancement. For example `.p2align 3’ advances the location counter until it a multiple of 8. If the location counter is already a multiple of 8, no change is needed. |
.section | Use the .section directive to assemble the following code into a section named name. |
.asciz | .asciz is just like .ascii , but each string is followed by a zero byte. The “z” in `.asciz’ stands for “zero”. |
三、调用约定
调用约定,即调用 Call0 ABI 和窗口 ABI,在 Xtensa Instruction Set Architecture (ISA) Reference Manual 中进行了更广泛的描述。
Application Binary Interface (ABI) 应用程序二进制接口;
对于窗口 ABI,入口点上的第一个指令是一个入口指令,它被一个 CALLN 或 CALLXN 指令调用(其中 N 是 4、8 或 12);
第二个入口点总是实现调用 call0 ABI,入口点将用 CALL0 指令调用,并将使用 RET 指令返回。
eg.
👇 call4,call8,call12 列对应窗口 ABI
参考资料
[1] Optimizing Code for HiFi Audio Engines
[2] Using as(The GNU Assembler)
[3] Xtensa® System Software Reference Manual
[6] ARM汇编入门指南
[7] What is the difference between an instruction and a directive in assembly language?
[10] 函数调用时栈是如何变化的?