1; RUN: opt < %s -wasm-lower-em-ehsjlj -enable-emscripten-cxx-exceptions -enable-emscripten-sjlj -S | FileCheck %s 2; RUN: llc < %s -enable-emscripten-cxx-exceptions -enable-emscripten-sjlj -verify-machineinstrs 3 4; Tests for cases when exception handling and setjmp/longjmp handling are mixed. 5 6target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" 7target triple = "wasm32-unknown-unknown" 8 9%struct.__jmp_buf_tag = type { [6 x i32], i32, [32 x i32] } 10@_ZTIi = external constant ptr 11 12; There is a function call (@foo) that can either throw an exception or longjmp 13; and there is also a setjmp call. When @foo throws, we have to check both for 14; exception and longjmp and jump to exception or longjmp handling BB depending 15; on the result. 16define void @setjmp_longjmp_exception() personality ptr @__gxx_personality_v0 { 17; CHECK-LABEL: @setjmp_longjmp_exception 18entry: 19 %buf = alloca [1 x %struct.__jmp_buf_tag], align 16 20 %call = call i32 @setjmp(ptr %buf) #0 21 invoke void @foo() 22 to label %try.cont unwind label %lpad 23 24; CHECK: entry.split.split: 25; CHECK: %[[CMP0:.*]] = icmp ne i32 %__THREW__.val, 0 26; CHECK-NEXT: %__threwValue.val = load i32, ptr @__threwValue 27; CHECK-NEXT: %[[CMP1:.*]] = icmp ne i32 %__threwValue.val, 0 28; CHECK-NEXT: %[[CMP:.*]] = and i1 %[[CMP0]], %[[CMP1]] 29; CHECK-NEXT: br i1 %[[CMP]], label %if.then1, label %if.else1 30 31; This is exception checking part. %if.else1 leads here 32; CHECK: entry.split.split.split: 33; CHECK-NEXT: %[[CMP:.*]] = icmp eq i32 %__THREW__.val, 1 34; CHECK-NEXT: br i1 %[[CMP]], label %lpad, label %try.cont 35 36; CHECK: lpad: 37lpad: ; preds = %entry 38 %0 = landingpad { ptr, i32 } 39 catch ptr null 40 %1 = extractvalue { ptr, i32 } %0, 0 41 %2 = extractvalue { ptr, i32 } %0, 1 42; CHECK-NOT: call {{.*}} void @__invoke_void(ptr @__cxa_end_catch) 43 %3 = call ptr @__cxa_begin_catch(ptr %1) #2 44 call void @__cxa_end_catch() 45 br label %try.cont 46 47try.cont: ; preds = %lpad, %entry 48 ret void 49 50; longjmp checking part 51; CHECK: if.then1: 52; CHECK: call i32 @__wasm_setjmp_test 53} 54 55; @foo can either throw an exception or longjmp. Because this function doesn't 56; have any setjmp calls, we only handle exceptions in this function. But because 57; sjlj is enabled, we check if the thrown value is longjmp and if so rethrow it 58; by calling @emscripten_longjmp. 59define void @rethrow_longjmp() personality ptr @__gxx_personality_v0 { 60; CHECK-LABEL: @rethrow_longjmp 61entry: 62 invoke void @foo() 63 to label %try.cont unwind label %lpad 64; CHECK: entry: 65; CHECK: %cmp.eq.one = icmp eq i32 %__THREW__.val, 1 66; CHECK-NEXT: %cmp.eq.zero = icmp eq i32 %__THREW__.val, 0 67; CHECK-NEXT: %or = or i1 %cmp.eq.zero, %cmp.eq.one 68; CHECK-NEXT: br i1 %or, label %tail, label %rethrow.longjmp 69 70; CHECK: try.cont: 71; CHECK-NEXT: %phi = phi i32 [ undef, %tail ], [ undef, %lpad ] 72; CHECK-NEXT: ret void 73 74; CHECK: rethrow.longjmp: 75; CHECK-NEXT: %threw.phi = phi i32 [ %__THREW__.val, %entry ] 76; CHECK-NEXT: %__threwValue.val = load i32, ptr @__threwValue, align 4 77; CHECK-NEXT: call void @emscripten_longjmp(i32 %threw.phi, i32 %__threwValue.val 78; CHECK-NEXT: unreachable 79 80; CHECK: tail: 81; CHECK-NEXT: %cmp = icmp eq i32 %__THREW__.val, 1 82; CHECK-NEXT: br i1 %cmp, label %lpad, label %try.cont 83 84lpad: ; preds = %entry 85 %0 = landingpad { ptr, i32 } 86 catch ptr null 87 %1 = extractvalue { ptr, i32 } %0, 0 88 %2 = extractvalue { ptr, i32 } %0, 1 89 %3 = call ptr @__cxa_begin_catch(ptr %1) #5 90 call void @__cxa_end_catch() 91 br label %try.cont 92 93try.cont: ; preds = %lpad, %entry 94 %phi = phi i32 [ undef, %entry ], [ undef, %lpad ] 95 ret void 96} 97 98; This function contains a setjmp call and no invoke, so we only handle longjmp 99; here. But @foo can also throw an exception, so we check if an exception is 100; thrown and if so rethrow it by calling @__resumeException. Also we have to 101; free the setjmpTable buffer before calling @__resumeException. 102define void @rethrow_exception() { 103; CHECK-LABEL: @rethrow_exception 104entry: 105 %buf = alloca [1 x %struct.__jmp_buf_tag], align 16 106 %call = call i32 @setjmp(ptr %buf) #0 107 %cmp = icmp ne i32 %call, 0 108 br i1 %cmp, label %return, label %if.end 109 110if.end: ; preds = %entry 111 call void @foo() 112 br label %return 113 114; CHECK: if.end: 115; CHECK: %cmp.eq.one = icmp eq i32 %__THREW__.val, 1 116; CHECK-NEXT: br i1 %cmp.eq.one, label %rethrow.exn, label %normal 117 118; CHECK: rethrow.exn: 119; CHECK-NEXT: %exn = call ptr @__cxa_find_matching_catch_2() 120; CHECK-NEXT: call void @__resumeException(ptr %exn) 121; CHECK-NEXT: unreachable 122 123; CHECK: normal: 124; CHECK-NEXT: icmp ne i32 %__THREW__.val, 0 125 126return: ; preds = %if.end, %entry 127 ret void 128} 129 130; The same as 'rethrow_exception' but contains a __cxa_throw call. We have to 131; free the setjmpTable buffer before calling __cxa_throw. 132define void @rethrow_exception2() { 133; CHECK-LABEL: @rethrow_exception2 134entry: 135 %buf = alloca [1 x %struct.__jmp_buf_tag], align 16 136 %call = call i32 @setjmp(ptr %buf) #0 137 %cmp = icmp ne i32 %call, 0 138 br i1 %cmp, label %throw, label %if.end 139 140if.end: ; preds = %entry 141 call void @foo() 142 br label %throw 143 144throw: ; preds = %if.end, %entry 145 call void @__cxa_throw(ptr null, ptr null, ptr null) #1 146 unreachable 147 148; CHECK: throw: 149; CHECK-NEXT: call void @__cxa_throw(ptr null, ptr null, ptr null) 150; CHECK-NEXT: unreachable 151} 152 153; The same case with @rethrow_longjmp, but there are multiple function calls 154; that can possibly longjmp (instead of throwing exception) so we have to 155; rethrow them. Here we test if we correclty generate only one 'rethrow.longjmp' 156; BB and share it for multiple calls. 157define void @rethrow_longjmp_multi() personality ptr @__gxx_personality_v0 { 158; CHECK-LABEL: @rethrow_longjmp_multi 159entry: 160 invoke void @foo() 161 to label %bb unwind label %lpad 162 163bb: ; preds = %entry 164 invoke void @foo() 165 to label %try.cont unwind label %lpad 166 167lpad: ; preds = %bb, %entry 168 %0 = landingpad { ptr, i32 } 169 catch ptr null 170 %1 = extractvalue { ptr, i32 } %0, 0 171 %2 = extractvalue { ptr, i32 } %0, 1 172 %3 = call ptr @__cxa_begin_catch(ptr %1) #5 173 call void @__cxa_end_catch() 174 br label %try.cont 175 176try.cont: ; preds = %lpad, %bb 177 %phi = phi i32 [ undef, %bb ], [ undef, %lpad ] 178 ret void 179 180; CHECK: rethrow.longjmp: 181; CHECK-NEXT: %threw.phi = phi i32 [ %__THREW__.val, %entry ], [ %__THREW__.val1, %bb ] 182; CHECK-NEXT: %__threwValue.val = load i32, ptr @__threwValue, align 4 183; CHECK-NEXT: call void @emscripten_longjmp(i32 %threw.phi, i32 %__threwValue.val) 184; CHECK-NEXT: unreachable 185} 186 187; The same case with @rethrow_exception, but there are multiple function calls 188; that can possibly throw (instead of longjmping) so we have to rethrow them. 189; Here we test if correctly we generate only one 'rethrow.exn' BB and share it 190; for multiple calls. 191define void @rethrow_exception_multi() { 192; CHECK-LABEL: @rethrow_exception_multi 193entry: 194 %buf = alloca [1 x %struct.__jmp_buf_tag], align 16 195 %call = call i32 @setjmp(ptr %buf) #0 196 %cmp = icmp ne i32 %call, 0 197 br i1 %cmp, label %return, label %if.end 198 199if.end: ; preds = %entry 200 call void @foo() 201 call void @foo() 202 br label %return 203 204return: ; preds = %entry, %if.end 205 ret void 206 207; CHECK: rethrow.exn: 208; CHECK-NEXT: %exn = call ptr @__cxa_find_matching_catch_2() 209; CHECK-NEXT: call void @__resumeException(ptr %exn) 210; CHECK-NEXT: unreachable 211} 212 213; int jmpval = setjmp(buf); 214; if (jmpval != 0) 215; return; 216; try { 217; throw 3; 218; } catch (...) { 219; } 220define void @setjmp_with_throw_try_catch() personality ptr @__gxx_personality_v0 { 221; CHECK-LABEL: @setjmp_with_throw_try_catch 222entry: 223 %buf = alloca [1 x %struct.__jmp_buf_tag], align 16 224 %call = call i32 @setjmp(ptr %buf) #0 225 %cmp = icmp ne i32 %call, 0 226 br i1 %cmp, label %try.cont, label %if.end 227 228if.end: ; preds = %entry 229 %exception = call ptr @__cxa_allocate_exception(i32 4) #2 230 store i32 3, ptr %exception, align 16 231 invoke void @__cxa_throw(ptr %exception, ptr @_ZTIi, ptr null) #1 232 to label %unreachable unwind label %lpad 233; When invoke @__cxa_throw is converted to a call to the invoke wrapper, 234; "noreturn" attribute should be removed, and there should be no 'free' call 235; before the call. We insert a 'free' call that frees 'setjmpTable' before every 236; function-exiting instruction. And invoke wrapper calls shouldn't be treated as 237; noreturn instructions, because they are supposed to return. 238; CHECK: if.end: 239; CHECK-NOT: tail call void @free 240; CHECK-NOT: call cc99 void @__invoke_void_ptr_ptr_ptr(ptr @__cxa_throw, ptr %exception, ptr @_ZTIi, ptr null) # 241; CHECK: call cc99 void @__invoke_void_ptr_ptr_ptr(ptr @__cxa_throw, ptr %exception, ptr @_ZTIi, ptr null) 242 243lpad: ; preds = %if.end 244 %0 = landingpad { ptr, i32 } 245 catch ptr null 246 %1 = extractvalue { ptr, i32 } %0, 0 247 %2 = extractvalue { ptr, i32 } %0, 1 248 %3 = call ptr @__cxa_begin_catch(ptr %1) #2 249 call void @__cxa_end_catch() 250 br label %try.cont 251 252try.cont: ; preds = %entry, %lpad 253 ret void 254 255unreachable: ; preds = %if.end 256 unreachable 257} 258 259declare void @foo() 260; Function Attrs: returns_twice 261declare i32 @setjmp(ptr) 262; Function Attrs: noreturn 263declare void @longjmp(ptr, i32) 264declare i32 @__gxx_personality_v0(...) 265declare ptr @__cxa_begin_catch(ptr) 266declare void @__cxa_end_catch() 267declare void @__cxa_throw(ptr, ptr, ptr) 268declare ptr @__cxa_allocate_exception(i32) 269 270attributes #0 = { returns_twice } 271attributes #1 = { noreturn } 272attributes #2 = { nounwind } 273