在MIPS中使用addi vs addiu的特定情况

时间:2019-10-23 09:12:15

标签: assembly mips integer-overflow mars-simulator

我已经阅读了几篇关于addiu的文章,我认为了解一些特定情况/案例的示例对我有帮助,那时候最好使用addiu而不是addi(反之亦然)(为什么更好)

这篇文章指出处理地址和添加地址,使用addiu防止溢出“陷阱”很重要。但是然后似乎可以说陷阱根本没有真正的好处,那么为什么我应该完全使用addi而不是addiu? Why would we use addiu instead of addi?

我的教授似乎只是在互换使用它们,以提醒我们addiu的存在???但这有时使我感到困惑,有时是在想:“在这种情况下,我真的需要使用addiu吗?还是他们只是将它用于'fun'?”

1 个答案:

答案 0 :(得分:1)

  

那我为什么要完全使用addi而不是addiu?

通常不应该这样做,除非像assert()那样,加法运算符中没有2的补码符号溢出。在有符号值的情况下,您可能希望这样做您希望不会溢出,尤其是在手工编写asm时。 (编译器从不使用add,始终使用addu。)

除了这些指令外,这些指令实际上是相同的,包括addiu的16位立即数的符号扩展。 (与ori和其他布尔值零扩展的布尔值不同。MIPS的某些指令确实需要零扩展,但addiu的符号扩展意味着它也不需要subiu操作码来扩展减去小整数。)

在您自己使用的程序中,让您的程序在输入错误的情况下出错可能比让某些不应该包装的东西更好。如果您是为Linux MIPS而不是MARS编写的,则可以为SIGFPE(算术异常)安装信号处理程序,这样至少可以打印出人类友好的错误消息,例如“检测到签名溢出,异常中止”。在MARS中,我认为您只是会进入调试器。


addaddi完全存在的唯一原因(而不是普通的adduaddiu,它们执行大多数ISA,如x86和ARM)呼叫add)是整数溢出检测。

整数溢出检测是一个难题。 (另请参见https://en.wikipedia.org/wiki/Integer_overflow)。 ISA在为它提供硬件支持方面进行了各种尝试,这将使软件(尤其是高级编译语言)可以例行使用它。 MIPS的add / sub指令就是这样的一种尝试,但是没有涵盖左移之类的情况。因此,想要对每个操作提供溢出检查的高级语言将需要单独的机制。或者他们可能只是使用其他机制,而根本不使用add / sub

与MIPS相比,请考虑使用x86或ARM之类的ISA:它们具有带有标志溢出位的FLAGS或状态寄存器,并且如果最后一条指令将该位设置为1,则可以执行条件转移。 x86甚至有一条into(如果在FLAGS中设置了OF则为陷阱)指令。

但是MIPS根本没有标志寄存器,只有整数寄存器。因此,除了通过add之类的加法和分支指令之外,没有地方可以记录加法运算溢出的事实。无需花费任何比特来为溢出情况编码分支目标,唯一的选择就是陷阱/异常。

(好吧,另一个易于使用的选择本来是可以设置的特殊寄存器,但是上下文切换将不得不保存/恢复它。它必须像JAL一样工作,以记录添加了错误的错误这可能需要微码,而MIPS避免了这种微码,或者至少对这种特殊情况进行了更复杂的处理,因为它就像一个异常但不是异常,并且无法像beq这样的早期条件被检测到,因为它确实需要完整的32位加法结果。)


为什么MIPS的架构师将普通的加法指令命名为addu

我不知道,也许他们是硬件人员,而不是软件人员,并且过分乐观,因为他们将迎来新的检查算术时代,并使许多整数溢出错误成为过去。

从现在起,将add助记符赋予编译器始终使用的标准非陷阱版本是最有意义的。然后,您需要为陷印版本使用另一个名称。可能是addt(陷印)或addc(已添加检查)。但是addc太糟糕了,因为与其他ISA(通常为adc)上的“随身携带”相混淆。 adds(加号)是可能的。命名很难!


基本知识:在某些情况下,您需要使用addu

对于(可能)大的无符号整数,使用addu显然很重要。 0x7fffffff + 1对于未签名没有什么特别的。但这是从INT_MAX到INT_MIN的2的补码符号溢出,因此add会被捕获。

对于指针,使用addu可能很重要。除非您使用“上半内核”内存模型,否则一个数组不可能从0x80000000开始并在其之上结束。


以下是我回答此问题的第一个版本。上面的部分内容是多余的。但我认为并非全部。 (TODO:完成编辑;我现在没有时间,但是我认为现在发帖要比等到以后再讲。)

  

最好使用addiu而不是addi,反之亦然(以及为什么更好)。

如果您特别希望计算机捕获陷阱,请使用add / addi (也称为故障,引发异常) 2的补码签名溢出(例如,像INT_MAX +1这样的换行变为INT_MIN)。

大多数时候,没有人希望这样做,但是也许如果您是手工编写asm并想防御某些整数溢出错误,则可以使用add,如果引发异常比继续操作更好。不好的价值。当然,这仅适用于add,不能左移1个以上或其他指令。因此,如果您真的想在一些防御性代码中进行全面的整数溢出检查,则仍然需要另一种机制。

这是一个好主意,您可能想为该硬件异常安装一个中断处理程序。 (或者在OS下的用户空间中,用于SIGFPE的信号处理程序,用于算术异常的POSIX信号。)

或者在开发过程中,也许您希望整数溢出突然停止在您期望溢出的添加指令上。即像assert。编译器没有,因为有时只进行检查而不对所有内容都进行整数运算会很奇怪。但是用手可能总比没有好。

经常发生整数溢出错误的地方是内存分配大小计算。 (例如,导致分配量少,程序结束时可能是DOS错误或依赖于分配程序的内存覆盖错误)。但是那些经常使用无符号整数。使用未签名的0x7fffffff + 1 = 0x80000000并不是错误。

否则,请始终使用addu / addiu

addu是MIPS的常规添加指令。

正如Difference between add and addu所指出的,这些名称具有误导性。 u可能与C中未定义行为的有符号溢出相对应,但是无符号溢出被明确定义为环绕。但您知道,2的补码加法与普通无符号二进制加法相同。 addi仅检查签名溢出。

(不是C编译器将永远使用addaddi:他们不想编写有错误的代码。带符号的溢出是UB,但这并不意味着它们必须进行特别的操作。此外,在优化之后,制作具有C抽象机中不存在的临时值的asm并不罕见,可以像(w+x)+(y+z)那样执行w + x + y + z即使(((w+x)+y)+z)都没有签名溢出;二进制加法的确是有关联的,而与签名溢出无关。)


MIPS32r6甚至删除了addi,仅保留了无故障的addiu(因此,如果您想捕获带符号的溢出,仍可以将操作数放在一个注册并使用add。)这在删除的不常用指令的项目符号列表on Wikipedia中列为具有16位立即数的整数溢出陷阱指令关于addi在现实世界中的使用量。


  

我的教授似乎只是在互换使用它们,以提醒我们addiu可能存在??

很难以此来判断这是否真的是真的,或者它们是否有您尚未接受的微妙原因。例如在绝对安全的情况下使用addi

或者每当他们考虑将整数视为带符号时,即使代码中仅包含非负值?例如就C抽象机而言,for(int i=0 ; i<100; i++)signed int

在已知不可能发生签名溢出的情况下(例如在lui之后),这并不重要。但是IMO除非需要,仍然始终使用addiu。对我来说,作为人类阅读代码,看到add / addi理想上是我们有意进行签名溢出检查的标志。但是在SO问题中,更常见的是初学者只是使用add,因为他们没有考虑甚至不知道addu。因此,它迫使您寻找“误报”陷阱错误的风险:add在您不希望出现错误的地方。


我认为add / addi的实际速度没有addu / addiu慢的MIPS CPU。可能不会;根据寄存器输入,任何lwsw都可能发生故障,因此MIPS管道需要能够有效地处理可能发生故障的指令。

在通常情况下,它们永远不会出错。您为此优化了管道,而采取故障的效率实际上没有多大关系。 (或者,在具有软件TLB缺失处理的MIPS上,这确实很重要;这种情况发生的频率可能比页面错误要高得多。