5
votes

I am implementing a frontend compiler for a toy language targeting LLVM-IR and I encounter a stack overflow when running compiled while statements:

For example, this code should run forever but our compiled version stack-overflows after some time.

def run(): Void = {
    i = 0;
    while(true) {
        i = i + 1;
    }
}

And here is the comipled LLVM-IR:

define i32 @run() nounwind ssp {
    ; i = 0
    %i = alloca i32, align 4
    %1 = alloca i32, align 4
    store i32 0, i32* %1, align 4
    %2 = load i32* %1, align 4
    store i32 %2, i32* %i, align 4
    br label %3

; <label>: %3
    ; while(true)
    ; Generated by compileExpression(condition)
    %4 = alloca i1, align 4
    store i1 true, i1* %4, align 4
    %5 = load i1* %4, align 4
    br i1 %5, label %6, label %11

; <label>: %6
    ; i = i + 1
    ; Generated by compileExpression(body)
    %7 = load i32* %i, align 4
    %8 = alloca i32, align 4
    store i32 1, i32* %8, align 4
    %9 = load i32* %8, align 4
    %10 = add nsw i32 %7, %9
    store i32 %10, i32* %i, align 4
    br label %3

; <label>: %11
    %12 = load i32* %i, align 4
    ret i32 %12
}

We think our problem comes from every alloca that are not released because we are still in the same function.

LLVM Documentation:

'alloca'd memory is automatically released when the function returns.

How should we compile the while loop?
Can we avoid this problem?

3

3 Answers

7
votes

You generate bad IR: specifically, alloca in a loop is a bad idea, and can indeed cause a stack overflow.

What I would expect to see is an alloca outside the loop, then a load, add and store sequence inside the loop. Later on you can run the mem2reg pass, which will get rid of the allocas and convert the load and store to a more efficient phi.

Same thing for your alloca for the while condition: You need to do the same, prepare memory in advance and inside the loop only store to it.

0
votes

Use the mem2reg pass to conver the allocas to register values. Register values are released when they reach there last use.

0
votes
define i32 @run() nounwind ssp {
    ; i = 0
    %i = alloca i32, align 4
    %1 = alloca i32, align 4
    store i32 0, i32* %1, align 4
    %2 = load i32* %1, align 4
    store i32 %2, i32* %i, align 4
    %3 = alloca i1, align 4
    store i1 true, i1* %3, align 4
    %4 = alloca i32, align 4
    br label %whilecond

whilecond:
    ; while(true)
    ; Generated by compileExpression(condition)
    %5 = load i1* %3, align 4
    br i1 %5, label %whilebody, label %whileexit

whilebody:
    ; i = i + 1
    ; Generated by compileExpression(body)
    %6 = load i32* %i, align 4
    store i32 1, i32* %4, align 4
    %7 = load i32* %4, align 4
    %8 = add nsw i32 %6, %7
    store i32 %8, i32* %i, align 4
    br label %whilecond

whileexit:
    %9 = load i32* %i, align 4
    ret i32 %9
}

after opt -mem2reg results to :

define i32 @run() #0 {
       br label %whilecond

whilecond:                                        ; preds = %whilebody, %0
       %i.0 = phi i32 [ 0, %0 ], [ %1, %whilebody ]
       br i1 true, label %whilebody, label %whileexit

whilebody:                                        ; preds = %whilecond
       %1 = add nsw i32 %i.0, 1
       br label %whilecond

whileexit:                                        ; preds = %whilecond
       ret i32 %i.0
}