在使用汇编语言编写函数时,是否需要将寄存器初始化为0?
只是为了确保以前的程序中的那些寄存器中没有值。
答案 0 :(得分:4)
总的来说,可能是的。
程序启动时的寄存器状态取决于操作系统。例如,如果您运行的是支持ELF psABI for MIPS的操作系统,那么在程序启动时注册$ 2,$ 29和$ 31会有有意义的值,其他包含未指定的值 - 请参阅“流程初始化”部分。 / p>
听起来你可能会对程序和功能之间的区别感到困惑。 psabI中还记录了函数在输入时可以预期的内容 - 请参阅同一文档中的“函数调用序列”部分 - 短版本为$ 4- $ 7,$ 25,$ 28,$ 29,$ 31 ,$ f12,$ f13,$ f14和$ f15可能包含有用的值,所有其他都未指定,您有义务确保您找到的价值在$ 16- $ 23,$ 28,$ 29,$ 30和$输入时f20- $ f31在退出时保持不变(即如果更改它们,则必须保存旧值并在退出前恢复它们;相反,如果您自己调用函数,则必须假设它已覆盖所有其他在返回之前注册了未指定的值。
如果您使用的操作系统不尊重ELF psABI,那么您需要找到适合您操作系统的等效文档。某处会有一些规范。可以想象,你将不得不对编译器进行逆向工程以获得它。
答案 1 :(得分:3)
不,你不需要将它们归零,但是你需要假设每个寄存器在进入你的函数/程序时都保留随机垃圾,除了保存输入的寄存器(例如函数) args)或者调用约定需要某种有用的值(例如堆栈指针)。
通常情况下这很好;你不需要“早”清除随机垃圾。如果你第一次使用寄存器正在写一些东西,你就不需要写零,然后写下你想要放在那里的任何东西。
但是,如果您打算将它用作计数器(即在循环中递增),那么您需要在循环之前将其归零。对于任何其他使用寄存器作为源操作数的操作也是如此,而不仅仅是目标操作数。
请注意,这是汇编语言中用于每种架构的每种类汇编语言的少数内容之一。
某些体系结构(如x86)具有隐式输入的指令(如DIV),但zeroing EDX before DIV仍然只是在读取之前将寄存器归零。
换句话说:每个寄存器都一个值,因此首次使用寄存器作为只写操作数没有什么特别之处。
您只需确保代码的正确性不依赖于任何不需要具有任何特定值的寄存器或内存的内容。
奖金阅读:
为了提高性能,此规则很少有例外:在某些微架构中,并非所有只写操作都是相同的。有些实际上对输出的旧值有假依赖性!使用廉价的依赖性破坏方式来编写寄存器会导致无序执行,从而避免等待它不需要的“输入” ,如果您的代码在长依赖链结束时可能使用该寄存器的某些东西之后运行(例如涉及缓存未命中)。
我不知道任何MIPS示例(希望没有),所以我将使用x86作为示例:
对于将其结果放在向量寄存器的低位元素中的int->浮点指令(例如CVTSI2SS),英特尔采取了短视的方法来设计它以合并到目标向量中,而不是归零目的地的其余部分。拥有SSE的第一代CPU是Intel Pentium III,它只有64位向量执行单元。我认为归零向量的高64位会使它成为一个额外的uop,或者至少在内部需要转换指令产生向量的两半作为输出,而不仅仅是修改的低半。
用于int-> float转换的绝大多数用例只是希望float作为标量,而不是插入到另一个向量中,因此对目标寄存器的依赖可能是有害的。在运行CVTSI2SS之前,gcc避免使用an extra xor-zeroing instruction将目标XMM寄存器归零。即使在最新的CPU上,这个额外的归零指令也不是完全免费的:它需要代码大小和前端带宽。因此,对于每个int-> float转换来说,这通常是非常小的额外成本,以避免在不可预测的情况下罕见但可能大的减速,当两个独立的依赖链由同一寄存器上的输入依赖关联时。
此外,POPCNT / LZCNT / TZCNT的目标寄存器在体系结构上是只写的,但是英特尔的实现has a false dependency on the output register。因此,在运行POPCNT之前将目标寄存器归零实际上是有意义的,如果有必要,以避免意外地创建循环携带的依赖链。
请参阅gcc在Godbolt Compiler Explorer上的popcnt和int-> float转换之前插入额外的xor-zeroing指令的示例。