| ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-globals |
| ; RUN: opt -S -passes='require<collector-metadata>,shadow-stack-gc-lowering' < %s | FileCheck %s |
| |
| declare void @llvm.gcroot(ptr, ptr) |
| |
| ; A gc "shadow-stack" function with a single root: the pass should create a |
| ; gc_frame alloca and gc_stackentry struct, push the frame onto the chain at |
| ; entry, and pop it before every return. |
| ;. |
| ; CHECK: @type_tag = external constant i8 |
| ; CHECK: @llvm_gc_root_chain = linkonce global ptr null |
| ; CHECK: @__gc_single_root = internal constant %gc_map.0 { %gc_map { i32 1, i32 0 }, [0 x ptr] zeroinitializer } |
| ; CHECK: @__gc_two_roots = internal constant %gc_map.0.0 { %gc_map { i32 2, i32 0 }, [0 x ptr] zeroinitializer } |
| ; CHECK: @__gc_root_with_metadata = internal constant %gc_map.1 { %gc_map { i32 1, i32 1 }, [1 x ptr] [ptr @type_tag] } |
| ; CHECK: @__gc_mixed_metadata = internal constant %gc_map.1.1 { %gc_map { i32 2, i32 1 }, [1 x ptr] [ptr @type_tag] } |
| ; CHECK: @__gc_with_invoke = internal constant %gc_map.0.2 { %gc_map { i32 1, i32 0 }, [0 x ptr] zeroinitializer } |
| ;. |
| define void @single_root(ptr %obj) gc "shadow-stack" { |
| ; CHECK-LABEL: @single_root( |
| ; CHECK-NEXT: entry: |
| ; CHECK-NEXT: [[GC_FRAME:%.*]] = alloca [[GC_STACKENTRY_SINGLE_ROOT:%.*]], align 8 |
| ; CHECK-NEXT: [[GC_CURRHEAD:%.*]] = load ptr, ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: [[GC_FRAME_MAP:%.*]] = getelementptr [[GC_STACKENTRY_SINGLE_ROOT]], ptr [[GC_FRAME]], i32 0, i32 0, i32 1 |
| ; CHECK-NEXT: store ptr @__gc_single_root, ptr [[GC_FRAME_MAP]], align 8 |
| ; CHECK-NEXT: [[ROOT:%.*]] = getelementptr [[GC_STACKENTRY_SINGLE_ROOT]], ptr [[GC_FRAME]], i32 0, i32 1 |
| ; CHECK-NEXT: [[GC_FRAME_NEXT:%.*]] = getelementptr [[GC_STACKENTRY_SINGLE_ROOT]], ptr [[GC_FRAME]], i32 0, i32 0, i32 0 |
| ; CHECK-NEXT: [[GC_NEWHEAD:%.*]] = getelementptr [[GC_STACKENTRY_SINGLE_ROOT]], ptr [[GC_FRAME]], i32 0, i32 0 |
| ; CHECK-NEXT: store ptr [[GC_CURRHEAD]], ptr [[GC_FRAME_NEXT]], align 8 |
| ; CHECK-NEXT: store ptr [[GC_NEWHEAD]], ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: store ptr [[OBJ:%.*]], ptr [[ROOT]], align 8 |
| ; CHECK-NEXT: [[GC_FRAME_NEXT1:%.*]] = getelementptr [[GC_STACKENTRY_SINGLE_ROOT]], ptr [[GC_FRAME]], i32 0, i32 0, i32 0 |
| ; CHECK-NEXT: [[GC_SAVEDHEAD:%.*]] = load ptr, ptr [[GC_FRAME_NEXT1]], align 8 |
| ; CHECK-NEXT: store ptr [[GC_SAVEDHEAD]], ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: ret void |
| ; |
| entry: |
| %root = alloca ptr |
| call void @llvm.gcroot(ptr %root, ptr null) |
| store ptr %obj, ptr %root |
| ret void |
| } |
| |
| ; Two roots with no metadata: the frame map should have NumRoots=2, NumMeta=0 |
| ; and the concrete stack entry should have two root slots. |
| define void @two_roots(ptr %a, ptr %b) gc "shadow-stack" { |
| ; CHECK-LABEL: @two_roots( |
| ; CHECK-NEXT: entry: |
| ; CHECK-NEXT: [[GC_FRAME:%.*]] = alloca [[GC_STACKENTRY_TWO_ROOTS:%.*]], align 8 |
| ; CHECK-NEXT: [[GC_CURRHEAD:%.*]] = load ptr, ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: [[GC_FRAME_MAP:%.*]] = getelementptr [[GC_STACKENTRY_TWO_ROOTS]], ptr [[GC_FRAME]], i32 0, i32 0, i32 1 |
| ; CHECK-NEXT: store ptr @__gc_two_roots, ptr [[GC_FRAME_MAP]], align 8 |
| ; CHECK-NEXT: [[ROOTA:%.*]] = getelementptr [[GC_STACKENTRY_TWO_ROOTS]], ptr [[GC_FRAME]], i32 0, i32 1 |
| ; CHECK-NEXT: [[ROOTB:%.*]] = getelementptr [[GC_STACKENTRY_TWO_ROOTS]], ptr [[GC_FRAME]], i32 0, i32 2 |
| ; CHECK-NEXT: [[GC_FRAME_NEXT:%.*]] = getelementptr [[GC_STACKENTRY_TWO_ROOTS]], ptr [[GC_FRAME]], i32 0, i32 0, i32 0 |
| ; CHECK-NEXT: [[GC_NEWHEAD:%.*]] = getelementptr [[GC_STACKENTRY_TWO_ROOTS]], ptr [[GC_FRAME]], i32 0, i32 0 |
| ; CHECK-NEXT: store ptr [[GC_CURRHEAD]], ptr [[GC_FRAME_NEXT]], align 8 |
| ; CHECK-NEXT: store ptr [[GC_NEWHEAD]], ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: store ptr [[A:%.*]], ptr [[ROOTA]], align 8 |
| ; CHECK-NEXT: store ptr [[B:%.*]], ptr [[ROOTB]], align 8 |
| ; CHECK-NEXT: [[GC_FRAME_NEXT1:%.*]] = getelementptr [[GC_STACKENTRY_TWO_ROOTS]], ptr [[GC_FRAME]], i32 0, i32 0, i32 0 |
| ; CHECK-NEXT: [[GC_SAVEDHEAD:%.*]] = load ptr, ptr [[GC_FRAME_NEXT1]], align 8 |
| ; CHECK-NEXT: store ptr [[GC_SAVEDHEAD]], ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: ret void |
| ; |
| entry: |
| %rootA = alloca ptr |
| %rootB = alloca ptr |
| call void @llvm.gcroot(ptr %rootA, ptr null) |
| call void @llvm.gcroot(ptr %rootB, ptr null) |
| store ptr %a, ptr %rootA |
| store ptr %b, ptr %rootB |
| ret void |
| } |
| |
| ; Root with a non-null metadata argument: NumMeta should be 1, and the |
| ; gc_map struct should include the trailing metadata pointer array. |
| ; Roots with metadata are sorted before null-metadata roots. |
| @type_tag = external constant i8 |
| |
| define void @root_with_metadata(ptr %obj) gc "shadow-stack" { |
| ; CHECK-LABEL: @root_with_metadata( |
| ; CHECK-NEXT: entry: |
| ; CHECK-NEXT: [[GC_FRAME:%.*]] = alloca [[GC_STACKENTRY_ROOT_WITH_METADATA:%.*]], align 8 |
| ; CHECK-NEXT: [[GC_CURRHEAD:%.*]] = load ptr, ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: [[GC_FRAME_MAP:%.*]] = getelementptr [[GC_STACKENTRY_ROOT_WITH_METADATA]], ptr [[GC_FRAME]], i32 0, i32 0, i32 1 |
| ; CHECK-NEXT: store ptr @__gc_root_with_metadata, ptr [[GC_FRAME_MAP]], align 8 |
| ; CHECK-NEXT: [[ROOT:%.*]] = getelementptr [[GC_STACKENTRY_ROOT_WITH_METADATA]], ptr [[GC_FRAME]], i32 0, i32 1 |
| ; CHECK-NEXT: [[GC_FRAME_NEXT:%.*]] = getelementptr [[GC_STACKENTRY_ROOT_WITH_METADATA]], ptr [[GC_FRAME]], i32 0, i32 0, i32 0 |
| ; CHECK-NEXT: [[GC_NEWHEAD:%.*]] = getelementptr [[GC_STACKENTRY_ROOT_WITH_METADATA]], ptr [[GC_FRAME]], i32 0, i32 0 |
| ; CHECK-NEXT: store ptr [[GC_CURRHEAD]], ptr [[GC_FRAME_NEXT]], align 8 |
| ; CHECK-NEXT: store ptr [[GC_NEWHEAD]], ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: store ptr [[OBJ:%.*]], ptr [[ROOT]], align 8 |
| ; CHECK-NEXT: [[GC_FRAME_NEXT1:%.*]] = getelementptr [[GC_STACKENTRY_ROOT_WITH_METADATA]], ptr [[GC_FRAME]], i32 0, i32 0, i32 0 |
| ; CHECK-NEXT: [[GC_SAVEDHEAD:%.*]] = load ptr, ptr [[GC_FRAME_NEXT1]], align 8 |
| ; CHECK-NEXT: store ptr [[GC_SAVEDHEAD]], ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: ret void |
| ; |
| entry: |
| %root = alloca ptr |
| call void @llvm.gcroot(ptr %root, ptr @type_tag) |
| store ptr %obj, ptr %root |
| ret void |
| } |
| |
| ; Mixed: one root with metadata, one without. The metadata root should be |
| ; placed first in the frame (per CollectRoots ordering), NumMeta=1, NumRoots=2. |
| define void @mixed_metadata(ptr %a, ptr %b) gc "shadow-stack" { |
| ; CHECK-LABEL: @mixed_metadata( |
| ; CHECK-NEXT: entry: |
| ; CHECK-NEXT: [[GC_FRAME:%.*]] = alloca [[GC_STACKENTRY_MIXED_METADATA:%.*]], align 8 |
| ; CHECK-NEXT: [[GC_CURRHEAD:%.*]] = load ptr, ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: [[GC_FRAME_MAP:%.*]] = getelementptr [[GC_STACKENTRY_MIXED_METADATA]], ptr [[GC_FRAME]], i32 0, i32 0, i32 1 |
| ; CHECK-NEXT: store ptr @__gc_mixed_metadata, ptr [[GC_FRAME_MAP]], align 8 |
| ; CHECK-NEXT: [[ROOTB:%.*]] = getelementptr [[GC_STACKENTRY_MIXED_METADATA]], ptr [[GC_FRAME]], i32 0, i32 1 |
| ; CHECK-NEXT: [[ROOTA:%.*]] = getelementptr [[GC_STACKENTRY_MIXED_METADATA]], ptr [[GC_FRAME]], i32 0, i32 2 |
| ; CHECK-NEXT: [[GC_FRAME_NEXT:%.*]] = getelementptr [[GC_STACKENTRY_MIXED_METADATA]], ptr [[GC_FRAME]], i32 0, i32 0, i32 0 |
| ; CHECK-NEXT: [[GC_NEWHEAD:%.*]] = getelementptr [[GC_STACKENTRY_MIXED_METADATA]], ptr [[GC_FRAME]], i32 0, i32 0 |
| ; CHECK-NEXT: store ptr [[GC_CURRHEAD]], ptr [[GC_FRAME_NEXT]], align 8 |
| ; CHECK-NEXT: store ptr [[GC_NEWHEAD]], ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: store ptr [[A:%.*]], ptr [[ROOTA]], align 8 |
| ; CHECK-NEXT: store ptr [[B:%.*]], ptr [[ROOTB]], align 8 |
| ; CHECK-NEXT: [[GC_FRAME_NEXT1:%.*]] = getelementptr [[GC_STACKENTRY_MIXED_METADATA]], ptr [[GC_FRAME]], i32 0, i32 0, i32 0 |
| ; CHECK-NEXT: [[GC_SAVEDHEAD:%.*]] = load ptr, ptr [[GC_FRAME_NEXT1]], align 8 |
| ; CHECK-NEXT: store ptr [[GC_SAVEDHEAD]], ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: ret void |
| ; |
| entry: |
| %rootA = alloca ptr |
| %rootB = alloca ptr |
| call void @llvm.gcroot(ptr %rootA, ptr null) |
| call void @llvm.gcroot(ptr %rootB, ptr @type_tag) |
| store ptr %a, ptr %rootA |
| store ptr %b, ptr %rootB |
| ret void |
| } |
| |
| ; A gc "shadow-stack" function with no gcroot calls: the pass must leave the |
| ; function unchanged (no gc_frame alloca, no push/pop of the shadow stack). |
| define void @no_roots() gc "shadow-stack" { |
| ; CHECK-LABEL: @no_roots( |
| ; CHECK-NEXT: entry: |
| ; CHECK-NEXT: ret void |
| ; |
| entry: |
| ret void |
| } |
| |
| ; A function with an invoke: the EscapeEnumerator must insert a shadow stack |
| ; pop on the unwind path as well as on the normal return path. |
| declare void @may_throw() |
| declare ptr @__gxx_personality_v0(...) |
| |
| define void @with_invoke(ptr %obj) gc "shadow-stack" personality ptr @__gxx_personality_v0 { |
| ; CHECK-LABEL: @with_invoke( |
| ; CHECK-NEXT: entry: |
| ; CHECK-NEXT: [[GC_FRAME:%.*]] = alloca [[GC_STACKENTRY_WITH_INVOKE:%.*]], align 8 |
| ; CHECK-NEXT: [[GC_CURRHEAD:%.*]] = load ptr, ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: [[GC_FRAME_MAP:%.*]] = getelementptr [[GC_STACKENTRY_WITH_INVOKE]], ptr [[GC_FRAME]], i32 0, i32 0, i32 1 |
| ; CHECK-NEXT: store ptr @__gc_with_invoke, ptr [[GC_FRAME_MAP]], align 8 |
| ; CHECK-NEXT: [[ROOT:%.*]] = getelementptr [[GC_STACKENTRY_WITH_INVOKE]], ptr [[GC_FRAME]], i32 0, i32 1 |
| ; CHECK-NEXT: [[GC_FRAME_NEXT:%.*]] = getelementptr [[GC_STACKENTRY_WITH_INVOKE]], ptr [[GC_FRAME]], i32 0, i32 0, i32 0 |
| ; CHECK-NEXT: [[GC_NEWHEAD:%.*]] = getelementptr [[GC_STACKENTRY_WITH_INVOKE]], ptr [[GC_FRAME]], i32 0, i32 0 |
| ; CHECK-NEXT: store ptr [[GC_CURRHEAD]], ptr [[GC_FRAME_NEXT]], align 8 |
| ; CHECK-NEXT: store ptr [[GC_NEWHEAD]], ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: store ptr [[OBJ:%.*]], ptr [[ROOT]], align 8 |
| ; CHECK-NEXT: invoke void @may_throw() |
| ; CHECK-NEXT: to label [[NORMAL:%.*]] unwind label [[UNWIND:%.*]] |
| ; CHECK: normal: |
| ; CHECK-NEXT: [[GC_FRAME_NEXT1:%.*]] = getelementptr [[GC_STACKENTRY_WITH_INVOKE]], ptr [[GC_FRAME]], i32 0, i32 0, i32 0 |
| ; CHECK-NEXT: [[GC_SAVEDHEAD:%.*]] = load ptr, ptr [[GC_FRAME_NEXT1]], align 8 |
| ; CHECK-NEXT: store ptr [[GC_SAVEDHEAD]], ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: ret void |
| ; CHECK: unwind: |
| ; CHECK-NEXT: [[LP:%.*]] = landingpad { ptr, i32 } |
| ; CHECK-NEXT: cleanup |
| ; CHECK-NEXT: [[GC_FRAME_NEXT2:%.*]] = getelementptr [[GC_STACKENTRY_WITH_INVOKE]], ptr [[GC_FRAME]], i32 0, i32 0, i32 0 |
| ; CHECK-NEXT: [[GC_SAVEDHEAD3:%.*]] = load ptr, ptr [[GC_FRAME_NEXT2]], align 8 |
| ; CHECK-NEXT: store ptr [[GC_SAVEDHEAD3]], ptr @llvm_gc_root_chain, align 8 |
| ; CHECK-NEXT: resume { ptr, i32 } [[LP]] |
| ; |
| entry: |
| %root = alloca ptr |
| call void @llvm.gcroot(ptr %root, ptr null) |
| store ptr %obj, ptr %root |
| invoke void @may_throw() to label %normal unwind label %unwind |
| normal: |
| ret void |
| unwind: |
| %lp = landingpad { ptr, i32 } cleanup |
| resume { ptr, i32 } %lp |
| } |
| ;. |
| ; CHECK: attributes #[[ATTR0:[0-9]+]] = { nounwind } |
| ;. |