uses of unwind lead to crashes

I have what appears to be a bug in LLVM... I'm deeply hesitant to label it a bug, given my lack of experience with LLVM, but the behaviour of this fragment strongly suggests a bug.

In particular, compiling and running this fragment using a fresh SVN build yields this stderr:

uccello:/tmp clements$ lli a.out.bc
0 lli 0x005e72b6 char const* std::find<char const*,

(char const*, char const*, char const&) + 98

1 lli 0x005e77eb llvm::sys::PrintStackTraceOnErrorSignal() + 593
2 libSystem.B.dylib 0x9623509b _sigtramp + 43
3 libSystem.B.dylib 0xffffffff _sigtramp + 1776070543
4 lli 0x001f421b llvm::ExecutionEngine::runFunctionAsMain(llvm::Function*, std::vector<std::string, std::allocator<std::string> > const&, char const* const*) + 1101
5 lli 0x00002e03 main + 1633
6 lli 0x00002736 start + 54
Segmentation fault

However, running it with the interpreter allows it to finish successfully:

uccello:/tmp clements$ lli -force-interpreter=true a.out.bc
0

Also, the code is insanely sensitive to small changes. In the text below, there's a conditional branch to the %WrongNumberArgs label that leads to the crash. Replacing the test variable with its value (0) causes the error to go away, though this could be due to aggressive dead-code optimization.

Basically, I'm looking for answers to these questions:

1) does this stack trace suggest a compiler bug?
2) does the fact that the behavior is different in the interpreter than with the JIT suggest that this is a compiler bug?
3) are there known issues with 'unwind' in the code in the SVN head?

Perhaps the easiest thing would just be to give up on 'unwind,' but if this is in fact an LLVM bug I figured I'd let someone have a crack at it.

The attached code is large (~200 lines), but most of my attempts to slim it down make the bug go away, unfortunately.

Hey, at least it's reproducible...

My system: Mac OS X 10.5.5, Intel Core Duo (*not* Core 2 duo), LLVM from SVN head.

I also tried running it on an older version of LLVM on a Fedora 9 machine with similar results, so it's not exclusively a mac problem.

Thanks in advance for any advice,

John Clements

target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:128:128"
target triple = "i386-apple-darwin9.5"

@error_code = global i32 0

@error_val = global i32 0

%eframe = type {%eframe*, i32, [0 x i32]}

%closure = type {i32, %eframe*}

%packed_args = type {i32, [0 x i32]}

@empty_env = global i32 0

define i32 @scheme_entry () {
  %init_env = bitcast i32* @empty_env to %eframe*
  %main = invoke i32 @main_1(%eframe* %init_env) to label %Done unwind label %Exn
Done:
  %fixed = bitcast i32 %main to i32
  ret i32 %fixed
Exn:
  ret i32 0
}
define i32 @dispatch(%eframe* %env, i32 %fun_val, %packed_args* %args) {
  %reg_1 = and i32 %fun_val, 3
  %reg_2 = icmp eq i32 %reg_1, 1
  br i1 %reg_2, label %L_79, label %L_80
L_80:
  store i32 %fun_val, i32* @error_val
  store i32 5, i32* @error_code
  unwind
L_79:
  %reg_3 = and i32 %fun_val, 4294967292
  %reg_4 = inttoptr i32 %reg_3 to i32*
  %reg_5 = load i32* %reg_4
  %reg_6 = and i32 %reg_5, 3
  %reg_7 = icmp eq i32 %reg_6, 0
    br i1 %reg_7, label %Dispatch, label %Fail
Fail:
  store i32 2, i32* @error_code
  unwind
Dispatch:
  switch i32 %reg_5, label %NoMatch [
      i32 4, label %Jump_to_main_1
      i32 0, label %Jump_to_f_0]
NoMatch:
  store i32 %reg_5, i32* @error_val
  store i32 3, i32* @error_code
  unwind
Jump_to_main_1:
  %reg_8 = getelementptr %packed_args* %args, i32 0, i32 0
  %reg_9 = load i32* %reg_8
  %reg_10 = icmp eq i32 %reg_9, 0
  br i1 %reg_10, label %L_81, label %WrongNumArgs
L_81:
  %reg_11 = call i32 @main_1(%eframe* %env)
  ret i32 %reg_11
Jump_to_f_0:
  %reg_12 = getelementptr %packed_args* %args, i32 0, i32 0
  %reg_13 = load i32* %reg_12
  %reg_14 = icmp eq i32 %reg_13, 3
     br i1 %reg_14, label %L_82, label %WrongNumArgs
;;; br i1 0, label %L_82, label %WrongNumArgs
L_82:
         ret i32 3
WrongNumArgs:
  unwind
}
define i32 @main_1(%eframe* %env) {
  %reg_22 = malloc {%eframe*, i32, [1 x i32]}
  %reg_23 = bitcast {%eframe*, i32, [1 x i32]}* %reg_22 to %eframe*
  %reg_24 = getelementptr %eframe* %reg_23, i32 0, i32 0
  store %eframe* %env, %eframe** %reg_24
  %reg_25 = getelementptr %eframe* %reg_23, i32 0, i32 1
  store i32 1, i32* %reg_25
  %reg_26 = malloc %closure
  %reg_27 = getelementptr %closure* %reg_26, i32 0, i32 0
  store i32 0, i32* %reg_27
  %reg_28 = getelementptr %closure* %reg_26, i32 0, i32 1
  store %eframe* %reg_23, %eframe** %reg_28
  %reg_29 = ptrtoint %closure* %reg_26 to i32
  %reg_30 = or i32 %reg_29, 1
  %reg_34 = malloc {i32, [2 x i32]}
  %reg_35 = getelementptr {i32, [2 x i32]}* %reg_34, i32 0, i32 0
  store i32 2, i32* %reg_35
  %reg_36 = getelementptr {i32, [2 x i32]}* %reg_34, i32 0, i32 1, i32 0
  store i32 12, i32* %reg_36
  %reg_37 = getelementptr {i32, [2 x i32]}* %reg_34, i32 0, i32 1, i32 1
  store i32 16, i32* %reg_37
  %reg_38 = bitcast {i32, [2 x i32]}* %reg_34 to %packed_args*
  %reg_39 = call i32 @dispatch(%eframe* %reg_23,i32 %reg_30,%packed_args* %reg_38)
  ret i32 %reg_39
}

@"\01LC" = internal constant [3 x i8] c"%d\00" ; <[3 x i8]*> [#uses=1]
@"\01LC1" = internal constant [6 x i8] c"false\00" ; <[6 x i8]*> [#uses=1]
@"\01LC2" = internal constant [5 x i8] c"true\00" ; <[5 x i8]*> [#uses=1]
@"\01LC3" = internal constant [5 x i8] c"#\5C%c\00" ; <[5 x i8]*> [#uses=1]
@"\01LC4" = internal constant [16 x i8] c"unknown pointer\00" ; <[16 x i8]*> [#uses=1]
@"\01LC5" = internal constant [5 x i8] c"void\00" ; <[5 x i8]*> [#uses=1]
@"\01LC6" = internal constant [18 x i8] c"#<unknown 0x%08x>\00" ; <[18 x i8]*> [#uses=1]
@"\01LC7" = internal constant [45 x i8] c"evaluation halted with error: %d and value: \00" ; <[45 x i8]*> [#uses=1]

define void @print_val(i32 %x) nounwind {
entry:
  %x_addr = alloca i32 ; <i32*> [#uses=10]
  %"alloca point" = bitcast i32 0 to i32 ; <i32> [#uses=0]
  store i32 %x, i32* %x_addr
  %0 = load i32* %x_addr, align 4 ; <i32> [#uses=1]
  %1 = and i32 %0, 3 ; <i32> [#uses=1]
  %2 = icmp eq i32 %1, 0 ; <i1> [#uses=1]
  br i1 %2, label %bb, label %bb1

bb: ; preds = %entry
  %3 = load i32* %x_addr, align 4 ; <i32> [#uses=1]
  %4 = ashr i32 %3, 2 ; <i32> [#uses=1]
  %5 = call i32 (i8*, ...)* @printf(i8* getelementptr ([3 x i8]* @"\01LC", i32 0, i32 0), i32 %4) nounwind ; <i32> [#uses=0]
  br label %bb12

bb1: ; preds = %entry
  %6 = load i32* %x_addr, align 4 ; <i32> [#uses=1]
  %7 = icmp eq i32 %6, 47 ; <i1> [#uses=1]
  br i1 %7, label %bb2, label %bb3

bb2: ; preds = %bb1
  %8 = call i32 (i8*, ...)* @printf(i8* getelementptr ([6 x i8]* @"\01LC1", i32 0, i32 0)) nounwind ; <i32> [#uses=0]
  br label %bb12

bb3: ; preds = %bb1
  %9 = load i32* %x_addr, align 4 ; <i32> [#uses=1]
  %10 = icmp eq i32 %9, 111 ; <i1> [#uses=1]
  br i1 %10, label %bb4, label %bb5

bb4: ; preds = %bb3
  %11 = call i32 (i8*, ...)* @printf(i8* getelementptr ([5 x i8]* @"\01LC2", i32 0, i32 0)) nounwind ; <i32> [#uses=0]
  br label %bb12

bb5: ; preds = %bb3
  %12 = load i32* %x_addr, align 4 ; <i32> [#uses=1]
  %13 = and i32 %12, 255 ; <i32> [#uses=1]
  %14 = icmp eq i32 %13, 15 ; <i1> [#uses=1]
  br i1 %14, label %bb6, label %bb7

bb6: ; preds = %bb5
  %15 = load i32* %x_addr, align 4 ; <i32> [#uses=1]
  %16 = ashr i32 %15, 8 ; <i32> [#uses=1]
  %17 = call i32 (i8*, ...)* @printf(i8* getelementptr ([5 x i8]* @"\01LC3", i32 0, i32 0), i32 %16) nounwind ; <i32> [#uses=0]
  br label %bb12

bb7: ; preds = %bb5
  %18 = load i32* %x_addr, align 4 ; <i32> [#uses=1]
  %19 = and i32 %18, 3 ; <i32> [#uses=1]
  %20 = icmp eq i32 %19, 1 ; <i1> [#uses=1]
  br i1 %20, label %bb8, label %bb9

bb8: ; preds = %bb7
  %21 = call i32 (i8*, ...)* @printf(i8* getelementptr ([16 x i8]* @"\01LC4", i32 0, i32 0)) nounwind ; <i32> [#uses=0]
  br label %bb12

bb9: ; preds = %bb7
  %22 = load i32* %x_addr, align 4 ; <i32> [#uses=1]
  %23 = icmp eq i32 %22, 63 ; <i1> [#uses=1]
  br i1 %23, label %bb10, label %bb11

bb10: ; preds = %bb9
  %24 = call i32 (i8*, ...)* @printf(i8* getelementptr ([5 x i8]* @"\01LC5", i32 0, i32 0)) nounwind ; <i32> [#uses=0]
  br label %bb12

bb11: ; preds = %bb9
  %25 = load i32* %x_addr, align 4 ; <i32> [#uses=1]
  %26 = call i32 (i8*, ...)* @printf(i8* getelementptr ([18 x i8]* @"\01LC6", i32 0, i32 0), i32 %25) nounwind ; <i32> [#uses=0]
  br label %bb12

bb12: ; preds = %bb11, %bb10, %bb8, %bb6, %bb4, %bb2, %bb
  %27 = call i32 @putchar(i32 10) nounwind ; <i32> [#uses=0]
  br label %return

return: ; preds = %bb12
  ret void
}

declare i32 @printf(i8*, ...) nounwind

declare i32 @putchar(i32)

define i32 @main(i32 %argc, i8** %argv) nounwind {
entry:
  %argc_addr = alloca i32 ; <i32*> [#uses=1]
  %argv_addr = alloca i8** ; <i8***> [#uses=1]
  %retval = alloca i32 ; <i32*> [#uses=2]
  %result = alloca i32 ; <i32*> [#uses=2]
  %0 = alloca i32 ; <i32*> [#uses=2]
  %"alloca point" = bitcast i32 0 to i32 ; <i32> [#uses=0]
  store i32 %argc, i32* %argc_addr
  store i8** %argv, i8*** %argv_addr
  %1 = call i32 @scheme_entry() nounwind ; <i32> [#uses=1]
  store i32 %1, i32* %result, align 4
  %2 = load i32* @error_code, align 4 ; <i32> [#uses=1]
  %3 = icmp ne i32 %2, 0 ; <i1> [#uses=1]
  br i1 %3, label %bb, label %bb1

bb: ; preds = %entry
  %4 = load i32* @error_code, align 4 ; <i32> [#uses=1]
  %5 = call i32 (i8*, ...)* @printf(i8* getelementptr ([45 x i8]* @"\01LC7", i32 0, i32 0), i32 %4) nounwind ; <i32> [#uses=0]
  %6 = load i32* @error_val, align 4 ; <i32> [#uses=1]
  call void @print_val(i32 %6) nounwind
  call void @exit(i32 0) noreturn nounwind
  unreachable

bb1: ; preds = %entry
  %7 = load i32* %result, align 4 ; <i32> [#uses=1]
  call void @print_val(i32 %7) nounwind
  store i32 0, i32* %0, align 4
  %8 = load i32* %0, align 4 ; <i32> [#uses=1]
  store i32 %8, i32* %retval, align 4
  br label %return

return: ; preds = %bb1
  %retval2 = load i32* %retval ; <i32> [#uses=1]
  ret i32 %retval2
}

declare void @exit(i32) noreturn nounwind

Hi John,

I have what appears to be a bug in LLVM... I'm deeply hesitant to
label it a bug, given my lack of experience with LLVM, but the
behaviour of this fragment strongly suggests a bug.

this is a frequently asked question. The LLVM code generators
currently do not support unwind. As far as I know the only
back-end that supports it is the interpreter. You may wonder
how llvm-gcc manages to support exception handling? It calls
the unwind routine in the gcc library, which takes care of
unwinding the stack. The LLVM exception *catching* stuff works
fine, but *throwing* (unwind) is trickier to get working which
is why no one did it yet. I have a plan for implementing it,
but it is low on my list of priorities...

Ciao,

Duncan.

Hello, John

  1. does this stack trace suggest a compiler bug?
  2. does the fact that the behavior is different in the interpreter
    than with the JIT suggest that this is a compiler bug?
  3. are there known issues with ‘unwind’ in the code in the SVN head?

As you already noticed, unwind instruction is supported only in interpreter.

Thanks for your prompt response!

If I understand you correctly, one interim measure would be to update the code generators that don't support unwind to explicitly signal an error, no? You could always have a -compile-anyway command-line flag that tells the code generator to do its best, for users that want to proceed with the unsupported feature.

Thanks again,

John Clements

Hi John,

If I understand you correctly, one interim measure would be to update
the code generators that don't support unwind to explicitly signal an
error, no? You could always have a -compile-anyway command-line flag
that tells the code generator to do its best, for users that want to
proceed with the unsupported feature.

I agree that quietly producing wrong code is bad. If Chris agrees,
I'll teach the code generator to abort if it encounters an "unwind".
This seems a particularly good idea given how often this comes up :slight_smile:

Ciao,

Duncan.

Makes sense to me!

-Chris