Skip to content

Instantly share code, notes, and snippets.

@folkertdev
Created August 29, 2021 10:38
Show Gist options
  • Save folkertdev/b5d330c0126c16b957b57a3c0cd89879 to your computer and use it in GitHub Desktop.
Save folkertdev/b5d330c0126c16b957b57a3c0cd89879 to your computer and use it in GitHub Desktop.
setjmp/longjmp in LLVM IR

Using setjmp/longjmp in LLVM IR

Using the setjmp and longjmp LLVM instrinsics did not work as expected. There is an undocumented difference between the C implementation and the LLVM intrinsics.

setjmp/longjmp in C

setjmp/longjmp (sjlj for short) is a mechanism in C that is used to handle errors, potentially deep in the call stack.

With setjmp, the current stack frame is saved to some location in memory. Then down the line a lngjmp can jump back to where setjmp was called. The stack is then restored, and the return value of the setjmp call is different between the first call, when normal execution brought use there, and a subsequent call that is the result of a jump. The different return value allows us to distinguish how we got to the setjmp and act accordingly.

// A simple C program to demonstrate working of setjmp() and longjmp()
#include<stdio.h>
#include<setjmp.h>

// a global where setjmp will store (pointers to) the stack frame
jmp_buf buffer;

void helper() {
    // 3. Jump to the point setup by setjmp, it will return `1`
    longjmp(buffer, 1);
  
    // unreachable
}
  
int main() {
    // 1. Setup jump position using buffer and return 0
    if (setjmp(buffer)) {
        // 4. here, setjmp must have returned something non-zero
        // that only happens after a jump, so we must come from
        // `helper` in this case
    } else {
        // 2. setjmp returned 0, so this is the normal execution path
        helper();
    }

    return 0;
}

setjmp/longjmp in LLVM IR

In the roc compiler, we want to be able to use this mechanism for exception handling. That means we have to express sjlj as LLVM IR instructions.

LLVM exposes setjmp and longjmp as intrinstics. We assumed these would behave similarly to their C counterparts.

But, we observed segfaults using just these two LLVM intrinstics. I verified the rest of the code generation was correct by using the libc version of these constructs. So, what could be the difference?

LLVM documentation is notoriously incomplete, so I turned to a github code search to see if anybody actually uses these intrinstics. I hit this file, which shows that in LLVM we must explicitly store the frame address and the stack into the buffer.

%fa = tail call i8* @llvm.frameaddress(i32 0)
store i8* %fa, i8** %arraydecay, align 4
%ss = tail call i8* @llvm.stacksave()
%ssgep = getelementptr [5 x i8*], [5 x i8*]* %buf, i32 0, i32 2
store i8* %ss, i8** %ssgep, align 4

With this in place, the setjump/longjump mechanism works like a charm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment