Add llvm/test/CodeGen/Generic/shadow-stack-gc-lowering.ll testing the opt-level behavior of the shadow-stack-gc-lowering module pass, covering: - Single root: frame push/pop at entry and return - Two roots: multi-slot frame, NumRoots=2/NumMeta=0 in the frame map - Root with non-null metadata: NumMeta=1, metadata array in gc_map - Mixed metadata: CollectRoots ordering (metadata roots sorted first) - No roots: pass must leave the function unchanged - Invoke: EscapeEnumerator inserts pop on both normal and unwind exits As requested in https://github.com/llvm/llvm-project/pull/178436, since the only existing tests seem to be that llc doesn't crash (in llvm/test/CodeGen/X86/GC) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
195 lines
11 KiB
LLVM
195 lines
11 KiB
LLVM
; 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 }
|
|
;.
|