diff --git a/llvm/include/llvm/IR/Function.h b/llvm/include/llvm/IR/Function.h --- a/llvm/include/llvm/IR/Function.h +++ b/llvm/include/llvm/IR/Function.h @@ -381,6 +381,9 @@ void setGC(std::string Str); void clearGC(); + /// Returns true if the function has ssp, sspstrong, or sspreq fn attrs. + bool hasStackProtectorFnAttr() const; + /// adds the attribute to the list of attributes. void addAttribute(unsigned i, Attribute::AttrKind Kind); diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp --- a/llvm/lib/Analysis/InlineCost.cpp +++ b/llvm/lib/Analysis/InlineCost.cpp @@ -2378,6 +2378,15 @@ if (Call.isNoInline()) return InlineResult::failure("noinline call site attribute"); + // Don't inline functions if one does not have any stack protector attribute + // but the other does. + if (Caller->hasStackProtectorFnAttr() && !Callee->hasStackProtectorFnAttr()) + return InlineResult::failure( + "stack protected caller but callee requested no stack protector"); + if (Callee->hasStackProtectorFnAttr() && !Caller->hasStackProtectorFnAttr()) + return InlineResult::failure( + "stack protected callee but caller requested no stack protector"); + return None; } @@ -2419,25 +2428,26 @@ InlineResult llvm::isInlineViable(Function &F) { bool ReturnsTwice = F.hasFnAttribute(Attribute::ReturnsTwice); - for (Function::iterator BI = F.begin(), BE = F.end(); BI != BE; ++BI) { + for (BasicBlock &BB : F) { // Disallow inlining of functions which contain indirect branches. - if (isa(BI->getTerminator())) + if (isa(BB.getTerminator())) return InlineResult::failure("contains indirect branches"); // Disallow inlining of blockaddresses which are used by non-callbr // instructions. - if (BI->hasAddressTaken()) - for (User *U : BlockAddress::get(&*BI)->users()) + if (BB.hasAddressTaken()) + for (User *U : BlockAddress::get(&BB)->users()) if (!isa(*U)) return InlineResult::failure("blockaddress used outside of callbr"); - for (auto &II : *BI) { + for (auto &II : BB) { CallBase *Call = dyn_cast(&II); if (!Call) continue; // Disallow recursive calls. - if (&F == Call->getCalledFunction()) + Function *Callee = Call->getCalledFunction(); + if (&F == Callee) return InlineResult::failure("recursive call"); // Disallow calls which expose returns-twice to a function not previously @@ -2446,8 +2456,8 @@ cast(Call)->canReturnTwice()) return InlineResult::failure("exposes returns-twice attribute"); - if (Call->getCalledFunction()) - switch (Call->getCalledFunction()->getIntrinsicID()) { + if (Callee) + switch (Callee->getIntrinsicID()) { default: break; case llvm::Intrinsic::icall_branch_funnel: diff --git a/llvm/lib/CodeGen/StackProtector.cpp b/llvm/lib/CodeGen/StackProtector.cpp --- a/llvm/lib/CodeGen/StackProtector.cpp +++ b/llvm/lib/CodeGen/StackProtector.cpp @@ -274,7 +274,6 @@ bool StackProtector::RequiresStackProtector() { bool Strong = false; bool NeedsProtector = false; - HasPrologue = findStackProtectorIntrinsic(*F); if (F->hasFnAttribute(Attribute::SafeStack)) return false; @@ -295,8 +294,6 @@ Strong = true; // Use the same heuristic as strong to determine SSPLayout } else if (F->hasFnAttribute(Attribute::StackProtectStrong)) Strong = true; - else if (HasPrologue) - NeedsProtector = true; else if (!F->hasFnAttribute(Attribute::StackProtect)) return false; diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp --- a/llvm/lib/IR/Attributes.cpp +++ b/llvm/lib/IR/Attributes.cpp @@ -1939,6 +1939,14 @@ /// If the inlined function had a higher stack protection level than the /// calling function, then bump up the caller's stack protection level. static void adjustCallerSSPLevel(Function &Caller, const Function &Callee) { + assert(!(!Callee.hasStackProtectorFnAttr() && + Caller.hasStackProtectorFnAttr() && + !Callee.hasFnAttribute(Attribute::AlwaysInline)) && + "stack protected caller but callee requested no stack protector"); + assert(!(!Caller.hasStackProtectorFnAttr() && + Callee.hasStackProtectorFnAttr() && + !Callee.hasFnAttribute(Attribute::AlwaysInline)) && + "stack protected callee but caller requested no stack protector"); // If upgrading the SSP attribute, clear out the old SSP Attributes first. // Having multiple SSP attributes doesn't actually hurt, but it adds useless // clutter to the IR. diff --git a/llvm/lib/IR/Function.cpp b/llvm/lib/IR/Function.cpp --- a/llvm/lib/IR/Function.cpp +++ b/llvm/lib/IR/Function.cpp @@ -610,6 +610,12 @@ setValueSubclassDataBit(14, false); } +bool Function::hasStackProtectorFnAttr() const { + return hasFnAttribute(Attribute::StackProtect) || + hasFnAttribute(Attribute::StackProtectStrong) || + hasFnAttribute(Attribute::StackProtectReq); +} + /// Copy all additional attributes (those not needed to create a Function) from /// the Function Src to this one. void Function::copyAttributesFrom(const Function *Src) { diff --git a/llvm/test/CodeGen/AArch64/stack-guard-remat-bitcast.ll b/llvm/test/CodeGen/AArch64/stack-guard-remat-bitcast.ll --- a/llvm/test/CodeGen/AArch64/stack-guard-remat-bitcast.ll +++ b/llvm/test/CodeGen/AArch64/stack-guard-remat-bitcast.ll @@ -1,22 +1,53 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py ; RUN: llc < %s -mtriple=arm64-apple-ios -relocation-model=pic -frame-pointer=all | FileCheck %s @__stack_chk_guard = external global i64* ; PR20558 -; CHECK: adrp [[R0:x[0-9]+]], ___stack_chk_guard@GOTPAGE -; CHECK: ldr [[R1:x[0-9]+]], {{\[}}[[R0]], ___stack_chk_guard@GOTPAGEOFF{{\]}} +define i32 @test_stack_guard_remat2() ssp { +; CHECK-LABEL: test_stack_guard_remat2: +; CHECK: ; %bb.0: ; %entry +; CHECK-NEXT: sub sp, sp, #64 ; =64 +; CHECK-NEXT: stp x29, x30, [sp, #48] ; 16-byte Folded Spill +; CHECK-NEXT: add x29, sp, #48 ; =48 +; CHECK-NEXT: .cfi_def_cfa w29, 16 +; CHECK-NEXT: .cfi_offset w30, -8 +; CHECK-NEXT: .cfi_offset w29, -16 +; CHECK-NEXT: Lloh0: +; CHECK-NEXT: adrp x8, ___stack_chk_guard@GOTPAGE +; CHECK-NEXT: Lloh1: ; Load the stack guard for the second time, just in case the previous value gets spilled. -; CHECK: adrp [[GUARD_PAGE:x[0-9]+]], ___stack_chk_guard@GOTPAGE -; CHECK: ldr [[R2:x[0-9]+]], {{\[}}[[R1]]{{\]}} -; CHECK: stur [[R2]], {{\[}}x29, [[SLOT0:[0-9#\-]+]]{{\]}} -; CHECK: ldur [[R3:x[0-9]+]], {{\[}}x29, [[SLOT0]]{{\]}} -; CHECK: ldr [[GUARD_ADDR:x[0-9]+]], {{\[}}[[GUARD_PAGE]], ___stack_chk_guard@GOTPAGEOFF{{\]}} -; CHECK: ldr [[GUARD:x[0-9]+]], {{\[}}[[GUARD_ADDR]]{{\]}} -; CHECK: cmp [[GUARD]], [[R3]] -; CHECK: b.ne LBB - -define i32 @test_stack_guard_remat2() { +; CHECK-NEXT: adrp x9, ___stack_chk_guard@GOTPAGE +; CHECK-NEXT: Lloh2: +; CHECK-NEXT: ldr x8, [x8, ___stack_chk_guard@GOTPAGEOFF] +; CHECK-NEXT: Lloh3: +; CHECK-NEXT: ldr x9, [x9, ___stack_chk_guard@GOTPAGEOFF] +; CHECK-NEXT: Lloh4: +; CHECK-NEXT: ldr x8, [x8] +; CHECK-NEXT: Lloh5: +; CHECK-NEXT: ldr x9, [x9] +; CHECK-NEXT: str x8, [sp] +; CHECK-NEXT: stur x9, [x29, #-8] +; CHECK-NEXT: Lloh6: +; CHECK-NEXT: adrp x9, ___stack_chk_guard@GOTPAGE +; CHECK-NEXT: ldur x8, [x29, #-8] +; CHECK-NEXT: Lloh7: +; CHECK-NEXT: ldr x9, [x9, ___stack_chk_guard@GOTPAGEOFF] +; CHECK-NEXT: Lloh8: +; CHECK-NEXT: ldr x9, [x9] +; CHECK-NEXT: cmp x9, x8 +; CHECK-NEXT: b.ne LBB0_2 +; CHECK-NEXT: ; %bb.1: ; %entry +; CHECK-NEXT: ldp x29, x30, [sp, #48] ; 16-byte Folded Reload +; CHECK-NEXT: mov w0, #-1 +; CHECK-NEXT: add sp, sp, #64 ; =64 +; CHECK-NEXT: ret +; CHECK-NEXT: LBB0_2: ; %entry +; CHECK-NEXT: bl ___stack_chk_fail +; CHECK-NEXT: .loh AdrpLdrGotLdr Lloh6, Lloh7, Lloh8 +; CHECK-NEXT: .loh AdrpLdrGotLdr Lloh1, Lloh3, Lloh5 +; CHECK-NEXT: .loh AdrpLdrGotLdr Lloh0, Lloh2, Lloh4 entry: %StackGuardSlot = alloca i8* %StackGuard = load i8*, i8** bitcast (i64** @__stack_chk_guard to i8**) @@ -26,5 +57,5 @@ ret i32 -1 } -declare void @llvm.stackprotector(i8*, i8**) -declare void @llvm.stackprotectorcheck(i8**) +declare void @llvm.stackprotector(i8*, i8**) ssp +declare void @llvm.stackprotectorcheck(i8**) ssp diff --git a/llvm/test/CodeGen/X86/stack-protector-2.ll b/llvm/test/CodeGen/X86/stack-protector-2.ll --- a/llvm/test/CodeGen/X86/stack-protector-2.ll +++ b/llvm/test/CodeGen/X86/stack-protector-2.ll @@ -162,4 +162,31 @@ declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1 immarg) +; Test that the same function body does not get a canary if no ssp fn attrs are +; set. +declare dso_local void @foo(i8*) + +define dso_local void @bar_sspstrong(i64 %0) #0 { +; CHECK-LABEL: @bar_sspstrong +; CHECK-NEXT: %StackGuardSlot = alloca i8* + %2 = alloca i64, align 8 + store i64 %0, i64* %2, align 8 + %3 = load i64, i64* %2, align 8 + %4 = alloca i8, i64 %3, align 16 + call void @foo(i8* %4) + ret void +} + +; Intentionally does not have any fn attrs. +define dso_local void @bar_nossp(i64 %0) { +; CHECK-LABEL: @bar_nossp +; CHECK-NEXT: %2 = alloca i64 + %2 = alloca i64, align 8 + store i64 %0, i64* %2, align 8 + %3 = load i64, i64* %2, align 8 + %4 = alloca i8, i64 %3, align 16 + call void @foo(i8* %4) + ret void +} + attributes #0 = { sspstrong } diff --git a/llvm/test/ThinLTO/X86/Inputs/nossp.ll b/llvm/test/ThinLTO/X86/Inputs/nossp.ll new file mode 100644 --- /dev/null +++ b/llvm/test/ThinLTO/X86/Inputs/nossp.ll @@ -0,0 +1,14 @@ +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-pc-linux-gnu" + +declare void @foo() + +define void @nossp_callee() { + call void @foo() + ret void +} + +define void @ssp_callee() ssp { + call void @foo() + ret void +} diff --git a/llvm/test/ThinLTO/X86/nossp.ll b/llvm/test/ThinLTO/X86/nossp.ll new file mode 100644 --- /dev/null +++ b/llvm/test/ThinLTO/X86/nossp.ll @@ -0,0 +1,51 @@ +; RUN: opt -module-summary %s -o %t1.bc +; RUN: opt -module-summary %p/Inputs/nossp.ll -o %t2.bc +; RUN: llvm-lto2 run %t1.bc %t2.bc -o %t3.bc -save-temps \ +; RUN: -r=%t1.bc,nossp_caller,px \ +; RUN: -r=%t1.bc,ssp_caller,px \ +; RUN: -r=%t1.bc,nossp_caller2,px \ +; RUN: -r=%t1.bc,ssp_caller2,px \ +; RUN: -r=%t1.bc,nossp_callee,x \ +; RUN: -r=%t1.bc,ssp_callee,x \ +; RUN: -r=%t2.bc,nossp_callee,px \ +; RUN: -r=%t2.bc,ssp_callee,px \ +; RUN: -r=%t2.bc,foo +; RUN: llvm-dis %t3.bc.1.4.opt.bc -o - | FileCheck %s + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-pc-linux-gnu" + +declare void @nossp_callee() +declare void @ssp_callee() ssp + +; nossp caller should be able to inline nossp callee. +define void @nossp_caller() { +; CHECK-LABEL: @nossp_caller +; CHECK-NEXT: tail call void @foo + tail call void @nossp_callee() + ret void +} + +; ssp caller should be able to inline ssp callee. +define void @ssp_caller() ssp { +; CHECK-LABEL: @ssp_caller +; CHECK-NEXT: tail call void @foo + tail call void @ssp_callee() + ret void +} + +; nossp caller should *NOT* be able to inline ssp callee. +define void @nossp_caller2() { +; CHECK-LABEL: @nossp_caller2 +; CHECK-NEXT: tail call void @ssp_callee + tail call void @ssp_callee() + ret void +} + +; ssp caller should *NOT* be able to inline nossp callee. +define void @ssp_caller2() ssp { +; CHECK-LABEL: @ssp_caller2 +; CHECK-NEXT: tail call void @nossp_callee + tail call void @nossp_callee() + ret void +} diff --git a/llvm/test/Transforms/CodeExtractor/PartialInlineAttributes.ll b/llvm/test/Transforms/CodeExtractor/PartialInlineAttributes.ll --- a/llvm/test/Transforms/CodeExtractor/PartialInlineAttributes.ll +++ b/llvm/test/Transforms/CodeExtractor/PartialInlineAttributes.ll @@ -37,7 +37,7 @@ ret i32 %add } -define i32 @callee_writeonly(i32 %v) writeonly { +define i32 @callee_writeonly(i32 %v) writeonly ssp { entry: %cmp = icmp sgt i32 %v, 2000 br i1 %cmp, label %if.then, label %if.end @@ -58,7 +58,7 @@ ; CHECK: call void @callee_most.2.if.then(i32 %v ; CHECK: call i32 @callee_noinline(i32 %v) ; CHECK: call void @callee_writeonly.1.if.then(i32 %v -define i32 @caller(i32 %v) { +define i32 @caller(i32 %v) ssp { entry: %c1 = call i32 @callee_most(i32 %v) %c2 = call i32 @callee_noinline(i32 %v) @@ -66,7 +66,7 @@ ret i32 %c3 } -; CHECK: define internal void @callee_writeonly.1.if.then(i32 %v, i32* %sub.out) { +; CHECK: define internal void @callee_writeonly.1.if.then(i32 %v, i32* %sub.out) [[FN_ATTRS0:#[0-9]+]] ; CHECK: define internal void @callee_most.2.if.then(i32 %v, i32* %sub.out) [[FN_ATTRS:#[0-9]+]] ; attributes to preserve @@ -76,6 +76,7 @@ sanitize_thread ssp sspreq sspstrong strictfp uwtable "foo"="bar" "patchable-function"="prologue-short-redirect" "probe-stack"="_foo_guard" "stack-probe-size"="4096" } +; CHECK: attributes [[FN_ATTRS0]] = { ssp ; CHECK: attributes [[FN_ATTRS]] = { inlinehint minsize noduplicate noimplicitfloat norecurse noredzone nounwind nonlazybind optsize safestack sanitize_address sanitize_hwaddress sanitize_memory sanitize_thread ssp sspreq sspstrong strictfp uwtable "foo"="bar" "patchable-function"="prologue-short-redirect" "probe-stack"="_foo_guard" "stack-probe-size"="4096" } ; attributes to drop diff --git a/llvm/test/Transforms/Inline/devirtualize.ll b/llvm/test/Transforms/Inline/devirtualize.ll --- a/llvm/test/Transforms/Inline/devirtualize.ll +++ b/llvm/test/Transforms/Inline/devirtualize.ll @@ -97,7 +97,7 @@ ret i32 4 } -define linkonce_odr i32 @_ZThn8_N1D1fEv(%struct.C* %this) { +define linkonce_odr i32 @_ZThn8_N1D1fEv(%struct.C* %this) ssp { entry: %0 = bitcast %struct.C* %this to i8* ; [#uses=1] %1 = getelementptr inbounds i8, i8* %0, i64 -8 ; [#uses=1] diff --git a/llvm/test/Transforms/Inline/inline-byval-bonus.ll b/llvm/test/Transforms/Inline/inline-byval-bonus.ll --- a/llvm/test/Transforms/Inline/inline-byval-bonus.ll +++ b/llvm/test/Transforms/Inline/inline-byval-bonus.ll @@ -15,7 +15,7 @@ %struct.ray = type { %struct.vec3, %struct.vec3 } %struct.spoint = type { %struct.vec3, %struct.vec3, %struct.vec3, double } -define i32 @caller(%struct.sphere* %i) { +define i32 @caller(%struct.sphere* %i) ssp { %shadow_ray = alloca %struct.ray, align 8 call void @fix(%struct.ray* %shadow_ray) diff --git a/llvm/test/Transforms/Inline/inline_nossp.ll b/llvm/test/Transforms/Inline/inline_nossp.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Inline/inline_nossp.ll @@ -0,0 +1,97 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt -inline -o - -S %s | FileCheck %s +; RUN: opt -passes='cgscc(inline)' %s -S | FileCheck %s +; RUN: opt -always-inline -o - -S %s | FileCheck %s +; RUN: opt -passes=always-inline -o - -S %s | FileCheck %s + +declare dso_local void @foo(i8*) + +; Not interesting to test. +define dso_local void @ssp(i64 %0) #0 { + %2 = alloca i64, align 8 + store i64 %0, i64* %2, align 8 + %3 = load i64, i64* %2, align 8 + %4 = alloca i8, i64 %3, align 16 + call void @foo(i8* %4) + ret void +} + +; Not interesting to test. +define dso_local void @ssp_alwaysinline(i64 %0) #1 { + %2 = alloca i64, align 8 + store i64 %0, i64* %2, align 8 + %3 = load i64, i64* %2, align 8 + %4 = alloca i8, i64 %3, align 16 + call void @foo(i8* %4) + ret void +} + +; @ssp should not be inlined due to mismatch stack protector. +; @ssp_alwaysinline should be inlined due to alwaysinline. +define dso_local void @nossp() { +; CHECK-LABEL: @nossp( +; CHECK-NEXT: [[TMP1:%.*]] = alloca i64, align 8 +; CHECK-NEXT: call void @ssp(i64 1024) +; CHECK-NEXT: [[SAVEDSTACK:%.*]] = call i8* @llvm.stacksave() +; CHECK-NEXT: [[TMP2:%.*]] = bitcast i64* [[TMP1]] to i8* +; CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* [[TMP2]]) +; CHECK-NEXT: store i64 1024, i64* [[TMP1]], align 8 +; CHECK-NEXT: [[TMP3:%.*]] = load i64, i64* [[TMP1]], align 8 +; CHECK-NEXT: [[TMP4:%.*]] = alloca i8, i64 [[TMP3]], align 16 +; CHECK-NEXT: call void @foo(i8* [[TMP4]]) +; CHECK-NEXT: [[TMP5:%.*]] = bitcast i64* [[TMP1]] to i8* +; CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* [[TMP5]]) +; CHECK-NEXT: call void @llvm.stackrestore(i8* [[SAVEDSTACK]]) +; CHECK-NEXT: ret void +; + call void @ssp(i64 1024) + call void @ssp_alwaysinline(i64 1024) + ret void +} + +; This is the same case as @nossp above. That the caller has alwaysinline is +; irrelevant. Not interesting to test. +define dso_local void @nossp_alwaysinline() #2 { + call void @ssp(i64 1024) + call void @ssp_alwaysinline(i64 1024) + ret void +} + +; @nossp_alwaysinline should be inlined due to alwaysinline. +; @ssp should not be inlined due to mismatch stack protector. +; @ssp_alwaysinline should be inlined due to alwaysinline. +define dso_local void @nossp_caller() { +; CHECK-LABEL: @nossp_caller( +; CHECK-NEXT: [[TMP1:%.*]] = alloca i64, align 8 +; CHECK-NEXT: [[SAVEDSTACK:%.*]] = call i8* @llvm.stacksave() +; CHECK-NEXT: call void @ssp(i64 1024) +; CHECK-NEXT: [[SAVEDSTACK_I:%.*]] = call i8* @llvm.stacksave() +; CHECK-NEXT: [[TMP2:%.*]] = bitcast i64* [[TMP1]] to i8* +; CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* [[TMP2]]) +; CHECK-NEXT: store i64 1024, i64* [[TMP1]], align 8 +; CHECK-NEXT: [[TMP3:%.*]] = load i64, i64* [[TMP1]], align 8 +; CHECK-NEXT: [[TMP4:%.*]] = alloca i8, i64 [[TMP3]], align 16 +; CHECK-NEXT: call void @foo(i8* [[TMP4]]) +; CHECK-NEXT: [[TMP5:%.*]] = bitcast i64* [[TMP1]] to i8* +; CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* [[TMP5]]) +; CHECK-NEXT: call void @llvm.stackrestore(i8* [[SAVEDSTACK_I]]) +; CHECK-NEXT: call void @llvm.stackrestore(i8* [[SAVEDSTACK]]) +; CHECK-NEXT: ret void +; + call void @nossp_alwaysinline() + ret void +} + +; @nossp should not be inlined due to mismatch stack protector. +define dso_local void @ssp2() #0 { +; CHECK-LABEL: @ssp2( +; CHECK-NEXT: call void @nossp() +; CHECK-NEXT: ret void +; + call void @nossp() + ret void +} + +attributes #0 = { sspstrong } +attributes #1 = { sspstrong alwaysinline } +attributes #2 = { alwaysinline} diff --git a/llvm/test/Transforms/Inline/inline_ssp.ll b/llvm/test/Transforms/Inline/inline_ssp.ll --- a/llvm/test/Transforms/Inline/inline_ssp.ll +++ b/llvm/test/Transforms/Inline/inline_ssp.ll @@ -61,7 +61,7 @@ define void @inline_req_nossp() nounwind uwtable { entry: -; CHECK: @inline_req_nossp() #0 +; CHECK: @inline_req_nossp() #3 call void @fun_sspreq() ret void } @@ -90,7 +90,7 @@ define void @inline_strong_nossp() nounwind uwtable { entry: -; CHECK: @inline_strong_nossp() #1 +; CHECK: @inline_strong_nossp() #3 call void @fun_sspstrong() ret void } @@ -119,7 +119,7 @@ define void @inline_ssp_nossp() nounwind uwtable { entry: -; CHECK: @inline_ssp_nossp() #2 +; CHECK: @inline_ssp_nossp() #3 call void @fun_ssp() ret void }