Search code examples
arraysassemblyruntimeinterruptbounds-check-elimination

Implementing loop bounds check via overflow interrupt


I had this idea today that one could implement a bounds checked loop over an array by constructing an iteration counter that will overflow on the last increment and stop the execution based on the generated overflow interrupt.

So assume you have an array e.g. int[32] and you want to iterate that. What you could do now to avoid the bounds check in every loop run is to register an interrupt handler for the overflow interrupt and assign a register to the value MAX - 32. This register is incremented on every loop iteration run and the last iteration run will overflow i.e. triggering the interrupt handler. Suppose the interrupt handler could increment the program counter of the original function, this mechanism could be used to avoid bounds checks.

So code like

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

could be implemented like

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

I don't know if this is doable, but I heard Java is doing something like that to avoid null checks in the runtime generated code. Do you think this would be doable or has this ever been considered/tried out by any language runtime?


Solution

  • Taking an exception is very slow; an array would have to be huge to amortize the extra cost of leaving the loop by faulting, even if you could save an instruction inside the loop.

    What you've probably read about is Array bounds checks on 64-bit hardware using hardware memory-protection : making arrays end at the end of a virtual page, and leaving the next page unmapped. The VM catches SIGSEGV and delivers an exception to the guest code if an out of bounds exception ever happens. This removes the need for bounds checks for random accesses, not in a loop.

    This technique only causes an invalid page fault (segfault) when the guest code actually takes an array bounds exception, not for the normal case of exiting a loop over an array.


    Let's look at how your idea would (not) work:

    I think you're talking about MIPS where there's an addi instruction that traps on signed overflow. (Instead of the normal addiu that does addition without a conditional trap).

    Most other ISAs don't have any efficient way to fault on signed overflow. x86 has into (interrupt of OF is set) but that's a separate instruction from the add that might set flags. You might as well use a conditional branch instead of causing an exception and catching it. Or just use dec ecx / jnz as a loop counter, or count a negative index up towards zero and index from the end of the array.

    I think MIPS would require an extra addi inside the loop for a dedicated counter: MIPS's only addressing mode is reg+16_bit_constant.

    So if you want to iterate an array you need a pointer increment, or else you'd need an extra add tmp, base, index inside the loop.

    A loop needs a jump or branch instruction at the bottom, and it can be a conditional branch for basically no extra cost. So in MIPS, you'd calculate the end of the array outside the loop, then write a normal loop like

        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)
    

    There are no wasted instructions inside the loop. Any array bounds-checking can be done outside the loop, by checking that endp is no more than one past the end of the array.

    (On modern x86, cmp/jne works equivalently, decoding to a single compare-and-branch uop. Many other ISAs have a decrement-and-branch or similar instruction that allow counter loop conditions to only have one instruction of overhead.)

    i < array.length as a loop condition isn't just an array bounds check.

    As I said above, in a simple loop that iterates arr[i], you hoist the bounds-check out of the loop to keep it efficient that way.