尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【2015-03-06】《RealView编译工具编译器用户指南》摘录:内联汇编器

【2015-03-06】《RealView编译工具编译器用户指南》摘录:内联汇编器 [历史归档]本文原发布于 cstriker1407.info 个人博客内容为历史存档仅供参考。发布时间2015-03-06 标题《RealView编译工具编译器用户指南》摘录内联汇编器分类操作系统 / ARM 标签ARM《RealView编译工具开发指南》摘录内联汇编器备注7.1 内联汇编器7.1. 内联汇编器7.1.1. 内联汇编器支持7.1.2. 内联汇编器语法7.1.3. 内联汇编操作的限制7.1.4. 虚拟寄存器7.1.5. 常数7.1.6. 指令扩展7.1.7. 条件标记7.1.8. 操作数7.1.9. 函数调用和跳转7.1.10. 标签7.1.11. 与先前版本 ARM C/C 编译器的差异备注1 本部分文档摘录自ARM网站【 http://infocenter.arm.com/help/index.jsp?topic/com.arm.doc.dui0348bc/CJAHICFI.html 】,版权归属于ARM。2 作者只摘录了部分内容全面详细的内容清参考ARM网址。7.1 内联汇编器7.1. 内联汇编器ARM 编译器提供了内联汇编器利用内联汇编器可以编写优化的汇编语言例程并可使用不能从 C 或 C 获得的目标处理器功能。7.1.1. 内联汇编器支持内联汇编器只支持 ARM 汇编语言而不支持以下语言和指令Thumb 汇编语言Thumb-2 汇编语言ARMv7 指令VFP 指令NEON 指令。您可使用嵌入式汇编器来支持 Thumb 和 Thumb-2。PS:作者尝试了以下发现cortexM3/M4也可以使用内联汇编但是两个MCU都不支持ARM仅供参考。内联汇编器支持大多数 ARMv6 指令包括完整的 ARMv6 SIMD 指令集。内联汇编器不支持的 ARMv6 指令为 SETEND 和一些系统扩展。内联汇编器支持大部分 ARMv5 指令包括通用协处理器指令。内联汇编器不支持的 ARMv5 指令为 BX、BLX 和 BXJ。7.1.2. 内联汇编器语法ARM 编译器支持asm 关键字 (C) 或 __asm 关键字C 和 C引入的一种扩展内联汇编器语法。以下各节介绍了这些关键字的语法含有 __asm 关键字的内联汇编含有 asm 关键字的内联汇编使用 __asm 和 asm 的规则在任何语句位置都可以使用 asm 或 __asm 语句。含有 __asm 关键字的内联汇编内联汇编器使用汇编器说明符进行调用后面跟用大括号或括号括起来的汇编器指令列表。您可以指定使用以下格式的内联汇编器代码在单行中示例如下__asm(“instruction[;instruction]”); // Must be a single string__asm{instruction[;instruction]}不能包括注释。在多行中示例如下__asm{…instruction…}在内联汇编语言块中的任何位置都可以使用 C 或 C 注释。另请参阅使用 __asm 和 asm 的规则。含有 asm 关键字的内联汇编编译 C 时ARM 编译器支持 ISO C 标准中建议的 asm 语法。您可以指定使用以下格式的内联汇编器代码在单行中示例如下asm(“instruction[;instruction]”); // Must be a single stringasm{instruction[;instruction]}不能包括注释。在多行中示例如下asm{…instruction…}在内联汇编语言块中的任何位置都可以使用 C 或 C 注释。使用 __asm 和 asm 的规则使用 __asm 和 asm 关键字时请遵循以下规则如果在同一行中包括多个指令则必须用分号 ( 进行分隔。如果使用双引号则必须将所有指令包含在一对双引号 () 内。如果一条指令需要占用多行则必须用反斜杠符号 () 指定后续行。对于多行格式可以在内联汇编语言块中的任何位置使用 C 或 C 注释。但当一行中包含多个指令时不能嵌入注释。在汇编语言中逗号 (,) 用作分隔符因此使用逗号运算符的 C 表达式必须括在括号中以区分二者__asm{ADD x, y, (f(), z)}asm 语句必须位于 C 函数内。可在任何应该出现 C 语句的地方使用 asm 语句。在内联汇编器中寄存器名视为 C 或 C 变量。它们不一定与相同名称的物理寄存器相关请参阅虚拟寄存器。如果未将寄存器声明为 C 或 C 变量编译器将生成一个警告。在内联汇编器中不要保存和恢复寄存器。编译器会为您完成此操作。此外内联汇编器不提供对物理寄存器的直接访问。请参阅虚拟寄存器。如果未向寄存器写入内容就读取寄存器CPSR 和 SPSR 除外则会发出一条错误消息。例如int f(int x){__asm{STMFD sp!, {r0} // save r0 - illegal: read before writeADD r0, x, 1EOR x, r0, xLDMFD sp!, {r0} // restore r0 - not needed.}return x;}该函数必须按以下形式编写int f(int x){int r0;__asm{ADD r0, x, 1EOR x, r0, x}return x;}请参阅内联汇编操作的限制。7.1.3. 内联汇编操作的限制可在内联汇编代码中执行的操作存在许多限制。这些限制提供了安全的方法并确保在汇编代码中不违反已编译的 C 和 C 代码中的假设。其他限制内联汇编器具有以下限制内联汇编器是一种高级汇编器它生成的代码可能不总是与您编写的代码完全一致。不能用它来生成比编译器生成的代码更有效的代码。应当使用嵌入式汇编器或 ARM 汇编器 armasm 来实现此目的。不支持 ARM 汇编器 armasm 中提供的某些低级功能如跳转和写入 PC。不支持标签表达式。不能使用点表示法 (.)或 {PC} 获取当前指令的地址。不能使用 运算符表示十六进制常数。应改为使用 0x 前缀。例如__asm { AND x, y, 0xF00 }用于指定 8 位常数实际循环的表示法在内联汇编语言中不能使用。这意味着在使用 8 位移位常数时如果更新了 NZCV 标记C 标记必须视为已破坏。不得修改堆栈。这是没有必要的因为编译器会根据需要自动堆叠和恢复任何工作寄存器。编译器不允许显式堆叠和恢复工作寄存器。寄存器必须小心使用寄存器如 r0-r3、sp 和 lr和 CPSR 中的 NZCV 标记。如果使用 C 或 C 表达式这些寄存器可能被用作临时寄存器并且 NZCV 标记可能在计算表达式时被编译器破坏。请参阅虚拟寄存器。因为不能直接访问任何物理寄存器所以无法用内联汇编代码显式读取或修改 pc、lr 和 sp。不过您可以使用《编译器参考指南》 中介绍的以下内在函数访问这些寄存器第 4-74 页的__current_pc第 4-74 页的__current_sp第 4-89 页的__return_address处理器模式****可以更改处理器模式或修改协处理器状态但编译器无法识别这些更改。如果更改处理器模式则直到改回到原模式后才能使用 C 或 C 表达式否则编译器将破坏新处理器模式的寄存器。同样如果通过执行浮点指令更改了浮点协处理器状态则直到恢复原状态后才能使用浮点表达式。Thumb 指令集****为 Thumb 状态编译 C 或 C 时内联汇编器不可用且不汇编 Thumb 指令。相反编译器会自动切换至 ARM 状态。如果要在包含为 Thumb 编译的代码的源文件中包括内联汇编则在 #pragma arm 和 #pragma thumb 语句之间包括含有内联汇编器代码的函数。例如… // Thumb code#pragma arm // ARM code. Switch code generation to the ARM instruction set so// that the inline assembler is available.int add(int i, int j){int res;__asm{ADD res, i, j // add here}return res;}#pragma thumb // Thumb code. Switch back to the Thumb instruction set.// The inline assembler is no longer available.还必须使用 --apcs /interwork 编译器选项编译代码。请参阅交互操作限定符《编译器参考指南》 中第 4-55 页的编译指示。VFP 协处理器****内联汇编器不提供对 VFP 指令的直接支持。不过可以使用通用协处理器指令指定这些指令。不得使用内联汇编代码更改 VFP 向量模式。内联汇编可能包含可使用编译器生成的 VFP 代码进行计算的浮点表达式操作数。因此仅编译器可修改 VFP 状态是非常重要的。不支持的指令****内联汇编器不支持以下指令BKPT 、BX 、BXJ 和 BLX 指令Note可以使用 __breakpoint() 内在函数在 C 和 C 代码中插入 BKPT 指令。LDR Rn, expression 伪指令。应改用 MOV Rn, expression。这可以生成从文字池进行的加载操作。LDRT、LDRBT、STRT 和 STRBT 指令MUL、MLA、UMULL、UMLAL、SMULL 和 SMLAL 标记设置指令MOV 或 MVN 标记设置指令其中第二个操作数为常数用户模式 LDM 指令ADR 和 ADRL 伪指令。7.1.4. 虚拟寄存器内联汇编器不提供对 ARM 处理器物理寄存器的直接访问。如果在内联汇编器中将 ARM 寄存器名称用作操作数它就成为对具有相同名称的虚拟寄存器的引用而不是对物理 ARM 寄存器的引用。在优化和代码生成过程中编译器给每个虚拟寄存器分配相应的物理寄存器。不过汇编代码中使用的物理寄存器可能与在指令中指定的不同。可将这些虚拟寄存器显式定义为标准 C 或 C 变量。如果这些虚拟寄存器未定义编译器会为它们提供隐式定义。编译器定义的虚拟寄存器具有函数局部范围即在单个函数内引用同一虚拟寄存器名称的多个 asm 语句或声明访问的是同一个虚拟寄存器。没有为 sp (r13)、lr (r14) 和 pc (r15) 寄存器创建虚拟寄存器而且不能在内联汇编代码中读取或直接修改它们。有关如何修改源代码的信息请参阅访问 sp、lr 或 pc 的旧内联汇编器。不存在虚拟处理器状态寄存器 (PSR)。任何对 PSR 的引用总是指向物理 PSR。符合以前所述准则的现有内联汇编器代码继续执行与编译器先前版本相同的功能但在每个指令中使用的实际寄存器可能不同。每个虚拟寄存器中的初值是不可预测的。必须在读之前向虚拟寄存器写入初值。如果在写入之前试图读取虚拟寄存器例如试图读取与变量 r1 相关联的虚拟寄存器编译器将会生成错误。您还必须在 C 或 C 代码中明确声明变量的名称。最好将 C 或 C 变量用作指令操作数。在第一次使用虚拟或物理寄存器名称时编译器会生成一个警告每个转换单元仅生成一次。例如如果指定寄存器 r3则将显示一个警告。7.1.5. 常数常数表达式说明符 # 是可选的。如果使用了它其后的表达式必须是常数。7.1.6. 指令扩展内联汇编代码中的 ARM 指令可能会在编译对象中扩展为几条指令。扩展取决于指令、指令中指定的操作数个数以及每个操作数的类型和值。使用常数的指令带有常数操作数的指令中的常数不仅仅局限于该指令所允许的值。相反编译器会将指令转换为具有相同效果的指令序列。例如ADD r0,r0,#1023可能解释为ADD r0,r0,#1024SUB r0,r0,#1除了协处理器指令之外所有带有常数操作数的 ARM 指令都支持指令扩展。此外当 MUL 指令的第三个操作数是常数时该指令可以扩展为一系列加法和移位指令。使用扩展指令更新 CPSR 的效果是算法指令正确设置 NZCV 标记逻辑指令正确设置 NZ 标记不改变 V 标记破坏 C 标记。加载和存储指令LDM、STM、LDRD 和 STRD 指令可替换为等效的 ARM 指令。在这种情况下编译器输出一条警告消息通知您它可能扩展指令。编写内联汇编代码的方式必须是不依赖于期望的指令条数或每条指定指令期望的执行时间。通常约束操作数寄存器对的指令如 LDRD 和 STRD被具有等效功能而并无约束的指令序列替换。不过这些指令可重新组合成 LDRD 和 STRD 指令。所有的 LDM 和 STM 指令被扩展为等效的 LDR 和 STR 指令序列。不过在优化过程中编译器可能因此将单独的指令重新组合为一条 LDM 或 STM 指令。7.1.7. 条件标记内联汇编指令可能显式或隐式地试图更新处理器条件标记。有些内联汇编指令仅包含虚拟寄存器操作数或简单表达式操作数请参阅操作数其行为可以预见。如果指定了隐式或显式更新则由指令设置条件标记。如果未指定更新则条件标记不会更改。指令操作数中的任何一个都不是简单操作数时除非指令更新条件标记否则条件标记可能会被破坏。一般情况下编译器不易诊断出对条件标记的潜在破坏。不过对于需要构造随后析构 C 临时函数的操作数如果指令试图更新条件标记编译器将显示警告。这是因为析构可能会破坏条件标记。7.1.8. 操作数操作数可以是多种类型之一虚拟寄存器在内联汇编指令中指定的寄存器总表示虚拟寄存器而不是物理 ARM 整数寄存器。虚拟寄存器不需要声明其大小与物理寄存器相同。不过汇编代码中使用的物理寄存器可能与在指令中指定的不同。请参阅虚拟寄存器。表达式操作数在内联汇编指令中可将函数参数、C 或 C 变量和其他 C 或 C 表达式指定为寄存器操作数。代替 ARM 整数寄存器的表达式类型必须为除 long long 之外的整数类型即 char、short、int 或 long或指针类型。对 char 或 short 类型不执行符号扩展。您必须对这些类型执行显式符号扩展。编译器可能会添加代码以计算这些表达式并将它们分配给寄存器。当操作数用作目标时如果在修改寄存器的位置将表达式用作操作数则表达式必须为可修改的左值。例如基址寄存器的目标寄存器或基址寄存器发生更新。对于包含多个表达式操作数的指令没有指定表达式操作数求值的顺序。只有满足了指令的条件才能对条件指令的表达式操作数求值。将 C 或 C 表达式用作内联汇编器操作数可能会导致一条指令被扩展为多条指令。如果表达式的值不能满足《ARM 体系结构参考手册》 中阐明的指令操作数约束就会发生这种情况。如果用作操作数的表达式创建需要析构的临时函数则析构发生在执行内联汇编指令之后。这与 C 析构临时函数的规则相类似。简单表达式操作数是以下类型之一变量值变量地址指针变量的反引用编译时常数。包含以下类型之一的任何表达式都不是简单表达式操作数隐式函数调用如除法或显式函数调用C 临时函数的构造算术或逻辑运算。寄存器列表寄存器列表最多可包含 16 个操作数。这些操作数可以是虚拟寄存器或表达式寄存器操作数。在寄存器列表中指定虚拟寄存器和表达式操作数的顺序很重要。寄存器列表中操作数的读写顺序是从左到右。第一个操作数使用最低地址随后的操作数的地址依次在前一地址基础上增加 4。在 LDM 或 STM 指令的普通操作中编号最低的物理寄存器总是存入最低的内存地址此新行为与之相反。这个行为上的区别是寄存器虚拟化的结果。表达式操作数或虚拟寄存器可以在寄存器列表中出现多次并且在每次指定后使用。如果指定基址寄存器则进行更新。在内存加载操作过程中更新操作将改写加载到基址寄存器的所有值。在特权模式下内联汇编器不支持通过在寄存器列表后指定 ^ 来对用户模式下的寄存器进行操作。中间操作数在内联汇编指令中可能将 C 或 C 整型常数表达式用作立即值。用于指定立即数移位的常数表达式的值必须位于《ARM 体系结构参考手册》 中定义的适用于移位操作的范围内。用于为内存或协处理器数据传送指令指定直接偏移量的常数表达式必须有一个适当对齐的值。7.1.9. 函数调用和跳转利用内联汇编器的 BL 和 SVC 指令可在常规指令字段后指定 3 个可选列表。这些指令的格式如下SVC{cond} svc_num, {input_param_list}, {output_value_list}, {corrupt_reg_list}BL{cond} function, {input_param_list}, {output_value_list}, {corrupt_reg_list}NoteSVC 指令以前的名称是 SWI。内联汇编器仍然接受用 SWI 代替 SVC。以下各节介绍这些列表未指定任何列表输入参数列表输出值列表被破坏的寄存器的列表Note内联汇编器不支持 BX、BLX 和 BXJ 指令。不能在任何输入、输出或被破坏的寄存器列表中指定 lr、sp 或 pc 寄存器。任何 SVC 指令或函数调用不能更改 sp 寄存器。未指定任何列表如果未指定任何列表则将 r0-r3 用作输入参数将 r0 用于输出值r12 和 r14 可能损坏。输入参数列表此列表指定作为函数调用或 SVC 指令的输入参数的表达式或变量以及包含表达式或变量的物理寄存器。它们被指定为对物理寄存器的赋值或物理寄存器名称。单一列表中可包含输入寄存器规范的两种类型。内联汇编器确保在输入 BL 或 SVC 指令之前指定的物理寄存器中存在正确的值。指定物理寄存器名称而并不赋值这样确保物理寄存器中可存在相同名称的虚拟寄存器中的值。这确保了与现有内联汇编器代码的向后兼容性。例如指令BL foo, { r0expression1, r1expression2, r2 }生成以下伪代码MOV (physical) r0, expression1MOV (physical) r1, expression2MOV (physical) r2, (virtual) r2BL foo输出值列表此列表指定的物理寄存器包含来自 BL 或 SVC 的输出值以及这些输出值必须存储到的目标位置。输出值被指定为从物理寄存器到可修改左值表达式的赋值或被指定为单个物理寄存器名称。内联汇编器从指定的物理寄存器中取值并赋值到指定的表达式中。指定物理寄存器名称而并不赋值会导致相同名称的虚拟寄存器被物理寄存器中的值更新。例如指令BL foo, { }, { result1r0, r1 }生成以下伪代码BL fooMOV result1, (physical) r0MOV (virtual) r1, (physical) r1被破坏的寄存器的列表此列表指定被所调用函数破坏的物理寄存器。如果条件标记被调用的函数修改则必须在被破坏的寄存器的列表中指定 PSR。BL 和 SVC 指令总是破坏 lr。如果此列表被忽略则对于 BL 和 SVC寄存器 r0-r3、pc、lr 和 PSR 被破坏。跳转指令 B 只能用于跳转到单个 C 或 C 函数内的标签。7.1.10. 标签内联汇编代码中定义的标签可用作跳转或 C 和 C goto 语句的目标。在内联汇编代码中C 和 C 中定义的标签可通过以下形式用作跳转指令的目标BL{cond} label7.1.11. 与先前版本 ARM C/C 编译器的差异ARM 编译器与先前版本 ARM C 和 C 编译器中的内联汇编器有显著差异。本节着重介绍两者的主要差异。有关将现有汇编代码用于内联汇编器的详细信息请参阅《RealView 编译工具开发指南》。ARMv6 指令在所有 ARMv6 指令中内联汇编器只支持 ARMv6 媒体指令。虚拟寄存器编译器的内联汇编代码总是指定虚拟寄存器。编译器选择在代码生成过程中用于每条指令的物理寄存器并让编译器完全优化汇编代码和周围的 C 或 C 代码。pc (r15)、lr (r14) 和 sp (r13) 寄存器根本不能访问。访问这些寄存器时会产生错误消息。虚拟寄存器的初值未定义。因此必须在读之前先写入虚拟寄存器。如果代码在写入之前读取虚拟寄存器编译器将发出警告。如果内联汇编代码起始处出现依赖于物理寄存器中特定值的旧代码编译器也会生成这些警告例如int add(int i, int j){int res;__asm{ADD res, r0, r1 // relies on i passed in r0 and j passed in r1}return res;}此代码生成警告和错误消息。这是由于它在写入虚拟寄存器 r0 和 r1 之前读取它们因而生成了错误消息。生成警告消息是因为 r0 和 r1 必须定义为 C 或 C 变量。改正的代码是int add(int i, int j){int res;__asm{ADD res, i, j}return res;}指令扩展编译器中的内联汇编器将指令 LDM、STM、LDRD 和 STRD 扩展为执行等效功能的单寄存器内存操作的序列。编译器可能优化单寄存器内存操作指令序列回到多寄存器内存操作。寄存器列表LDM 或 STM 指令的寄存器列表中操作数的顺序很重要。它们按给定的顺序依次使用即从左到右并且第一个操作数引用生成的最低内存地址。这与先前编译器的行为相反在先前编译器行为中编号最低的寄存器总是引用由指令生成的最低内存地址。因为现在可以在寄存器列表中与虚拟寄存器一起使用表达式操作数所以会发生这种变化。如果编译器遇到仅包含虚拟寄存器的寄存器列表而在此列表中新的排序结果与先前版本的 ARM C 和 C 编译器有所不同则会显示警告消息。Thumb 指令编译器中的内联汇编器不支持 Thumb® 指令集。除非使用 #pragma arm 和 #pragma thumb 编译指示否则内联汇编器不汇编 Thumb 指令而且在为 Thumb 状态编译 C 或 C 时内联汇编器根本不能使用请参阅Thumb 指令集。
返回列表