本文介绍MRS2编译参数中的Small Prologue/Epilogue(-msave-restore)选项,用于在嵌入式开发中优化代码体积。
MRS2的工程配置页中,有一个默认选中的勾选项Small Prologue/Epilogue(-msave-restore),该选项是一个专用于 RISC-V 架构的 GCC 编译器选项,通过将函数的 Prologue(序言)和 Epilogue(尾声)提取为共享的库函数调用,从而显著减小代码体积。
在理解 -msave-restore 之前,需要先了解函数调用中的Prologue(序言)和 Epilogue(尾声)。
Prologue(序言) 是函数入口处的一段固定代码,负责设置函数的执行环境,保存那些按照调用约定需要由本函数保护的寄存器的值。主要完成以下工作:
• 分配栈帧空间(调整 sp 寄存器)
• 保存被调用者保存寄存器(callee-saved registers)到栈上
• 保存返回地址寄存器 ra(对于非叶子函数)
• 设置帧指针 fp/s0(可选)
**Epilogue(尾声)**是函数返回前的一段固定代码,负责恢复执行现场:
• 从栈上恢复之前保存的被调用者保存寄存器
• 恢复返回地址 ra
• 释放栈帧空间(恢复 sp)
• 通过 ret 指令返回
使用MRS2分别在勾选与取消-msave-restore选项两种情况下编译同一段代码,在lst文件中查看生成的对应汇编代码。
未勾选msave-restore时,编译器会在每个需要保存寄存器的函数内部,直接生成相应的汇编指令来完成保存和恢复操作。下图所示为函数demo的汇编语句,明显看到在函数首尾生成了一系列的保存/恢复指令,作为函数体的一部分。
这样的好处是执行的效率较高,但每个函数都要执行自己的指令,当函数较多时空间占用较大。
勾选msave-restore时,编译器会通过调用 libgcc 库中的特定函数(如 __riscv_save 和 __riscv_restore)来完成寄存器的保存和恢复工作。例如下图demo函数首尾分别调用<__riscv_save_4>和<__riscv_restore_4>。
当程序中包含一定数量的函数时,利用特定函数调用取代重复生成Prologue/Epilogue 代码,可以一定程度上减少代码大小,尽管每次函数调用都需要额外进行两次库函数调用,会引入少量的性能损耗,但相较之下,优化代码体积在嵌入式领域有更大的优势。
总结:嵌入式代码使用-msave-restore可以优化代码体积,建议勾选。