通过溢出中断实现循环边界检查



我今天有一个想法,可以通过构造一个迭代计数器来在数组上实现一个边界检查循环,该计数器将在最后一个增量上溢出,并根据生成的溢出中断停止执行。

因此,假设您有一个数组,例如int[32],并且您想要迭代它。现在,为了避免每次循环运行中的边界检查,您可以为溢出中断注册一个中断处理程序,并为值MAX - 32分配一个寄存器。该寄存器在每次循环迭代运行时递增,最后一次迭代运行将溢出,即触发中断处理程序。假设中断处理程序可以增加原始函数的程序计数器,则可以使用此机制来避免边界检查。

所以像这样的代码

for (int i = 0; i < array.length; i++) {
// do something
}

可以像一样实现

// setup interrupt somehow
SOME_REGISTER = MAX - array.length;
while (true) {
// do something
SOME_REGISTER++;
}

我不知道这是否可行,但我听说Java正在做类似的事情,以避免在运行时生成的代码中进行null检查。你认为这是可行的吗?或者任何语言运行库都考虑过/尝试过吗?

执行异常非常缓慢;一个数组必须是巨大,才能通过出错来分摊离开循环的额外成本,即使你可以在循环中保存一条指令。

您可能已经了解到使用硬件内存保护对64位硬件进行阵列边界检查:使阵列在虚拟页的末尾结束,并使下一页未映射。如果发生越界异常,VM会捕获SIGSEGV并将异常传递给来宾代码这消除了对随机访问的边界检查的需要,而不是在循环中

只有当来宾代码实际执行数组边界异常时,此技术才会导致无效页面错误(segfault(,对于退出数组循环的正常情况,而不是


让我们看看你的想法是如何运作的:

我想你说的是MIPS,其中有一个addi指令捕获有符号溢出。(而不是在没有条件陷阱的情况下进行加法的普通addiu(。

大多数其他ISAs没有任何有效的方法来对签名溢出进行容错。x86有into(设置了of的中断(,但这是一条与可能设置标志的add不同的指令。你也可以使用条件分支,而不是引起异常并捕获它。或者只使用dec ecx / jnz作为循环计数器,或者将负索引计数为零,并从数组末尾开始索引。

我认为MIPS需要一个额外的addi循环中用于专用计数器:MIPS唯一的寻址模式是reg+16_bit_constant

因此,如果你想迭代一个数组,你需要一个指针增量,否则你需要在循环中增加一个add tmp, base, index

循环需要底部的跳转或分支指令,它可以是一个条件分支,基本上不需要额外开销。因此,在MIPS中,您可以计算循环外的数组末尾,然后编写一个类似的正常循环

addu   $t5, $t0, $t4     ; t5 = int *endp = start + byte_length
.loop:                       ; do{
addiu  $t0, $t0, 4         ;  p++
lw     $t1, ($t0)          ; *p
...
bne    $t0, $t5, .loop    ; }while(p != endp)

循环中没有浪费的指令。任何数组边界检查都可以在循环外进行,方法是检查endp不超过数组末尾的一个。

(在现代x86上,cmp/jne的工作原理等效,解码为单个比较和分支uop。许多其他ISAs都有减量和分支或类似的指令,允许计数器循环条件只有一条指令的开销。(

作为循环条件的i < array.length不仅仅是数组边界检查。

如上所述,在一个迭代arr[i]的简单循环中,您可以将边界检查从循环中提升出来,以保持其有效性。

最新更新