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 (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;
}
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.