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开发调用约定】内有做过详细描述。

image-20220818201922094

二、汇编指示(assembly directive)

  • 类似 _main:Ltmp0: 的形式被称之为标签(label), 用于辅助定位代码或者资源地址, 便于开发者理解和记忆;
  • 类似 pushq movq的形式被称之为汇编指令, 它们会被汇编器编译为机器代码, 最终被 CPU 所执行;
  • 类似 .section.globl 等以 . 开头的形式被称之为编译器指令(assembly directive), 用于告知编译器相关的信息或者进行特定操作。他们不是汇编指令而是作用于汇编器的,可以忽略所有这样的代码;
  • 关于 directive 和 instruction 这两个词的区别,前者翻译为指示,后者翻译成指令。因为一般 directive 并不会产生代码而是指示编译器的一些行为,而 instruction 则会产生实际的代码;

.loc 5 76 49

**eg. **

image-20221106202447549

说明:

image-20221106202535805

  • fileno : 文件描述符
  • lineno : 行号
  • colum:列数(操作符的位置)

.literal_position

eg.

image-20221106202654001

说明:

image-20221106202729103

.p2align

eg.

image-20221106202759522

说明:

image-20221106202825671

p2align 3 表示 2 的 3 次幂 = 8

p2align 2 表示 2 的 2 次幂 = 4

函数名_

eg.

image-20221106202846674

编译器通常在函数名的前面添加一个下划线。

.cfi_startproc

image-20221106202911293

eh_frame:GCC Exception Frame,也就是eh_frame。这里提到 eh_frame 是 dwarf 调试信息的变体(variant)。eh_frame 段中存储着跟函数入栈相关的关键数据。当函数执行入栈指令后,在该段会保存跟入栈指令一一对应的编码数据,根据这些编码数据,就能计算出当前函数栈大小和cpu的哪些寄存器入栈了,在栈中什么位置。

.cfi_def_cfa

.cfi_def_cfa_offset

image-20221106202947900

prologue_end

image-20221106203003854

.type

eg.

image-20221106203029065

汇编里面所有以 : 结尾的都会视为标签 ( label ),在这里我们定义一个叫做 main 的标签,并且使用 .type 伪指令定义这个标签的类型是一个函数(function),到此我们就定义了我们的 main 函数。

说明:

image-20221106203044974

.Ltmp:

标签,不是指令。是这部分的汇编代码名字。

is_stmt

image-20221106203108647

此选项会将 .debug_line 状态机中的 is_stmt 寄存器设置为 value,该值必须为 0 或 1。

L32R

image-20221106203139344

L32R 指令从指定地址加载 32 位值。因此,“l32r a2,2ec8” 将地址 0x2ec8 处的 32 位值加载到寄存器 a2 中。

L. 本地标签

eg.

image-20221106203207817

所有以 L. 开头的都为本地标签,这些标签只能用于函数内部。

[Ⅱ *1+0]

eg.

image-20221106203305432

[Ⅱ * 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.

image-20221106203349642

👇 call4,call8,call12 列对应窗口 ABI

image-20221106203416052

参考资料

[1] Optimizing Code for HiFi Audio Engines

[2] Using as(The GNU Assembler)

[3] Xtensa® System Software Reference Manual

[4] arm汇编指令_iOS汇编ARM教程

[5] C程序设计语言的汇编解释(第一章节,一些实例)

[6] ARM汇编入门指南

[7] What is the difference between an instruction and a directive in assembly language?

[8] Reading assembly code

[9] x86_64汇编之四:函数调用、调用约定

[10] 函数调用时栈是如何变化的?


DSP 学习笔记|(七)HiFi 汇编说明
https://erenship.com/posts/ekff.html
Author
Eren
Posted on
November 6, 2022
Licensed under