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