forked from OSchip/llvm-project
Add -f[no-]strict-return flag that can be used to avoid undefined behaviour
in non-void functions that fall off at the end without returning a value when compiling C++. Clang uses the new compiler flag to determine when it should treat control flow paths that fall off the end of a non-void function as unreachable. If -fno-strict-return is on, the code generator emits the ureachable and trap IR only when the function returns either a record type with a non-trivial destructor or another non-trivially copyable type. The primary goal of this flag is to avoid treating falling off the end of a non-void function as undefined behaviour. The burden of undefined behaviour is placed on the caller instead: if the caller ignores the returned value then the undefined behaviour is avoided. This kind of behaviour is useful in several cases, e.g. when compiling C code in C++ mode. rdar://13102603 Differential Revision: https://reviews.llvm.org/D27163 llvm-svn: 290960
This commit is contained in:
parent
af16c50639
commit
c1608f7f69
|
@ -1331,6 +1331,12 @@ def funique_section_names : Flag <["-"], "funique-section-names">,
|
|||
def fno_unique_section_names : Flag <["-"], "fno-unique-section-names">,
|
||||
Group<f_Group>, Flags<[CC1Option]>;
|
||||
|
||||
def fstrict_return : Flag<["-"], "fstrict-return">, Group<f_Group>,
|
||||
Flags<[CC1Option]>,
|
||||
HelpText<"Always treat control flow paths that fall off the end of a non-void"
|
||||
"function as unreachable">;
|
||||
def fno_strict_return : Flag<["-"], "fno-strict-return">, Group<f_Group>,
|
||||
Flags<[CC1Option]>;
|
||||
|
||||
def fdebug_types_section: Flag <["-"], "fdebug-types-section">, Group<f_Group>,
|
||||
Flags<[CC1Option]>, HelpText<"Place debug types in their own section (ELF Only)">;
|
||||
|
|
|
@ -251,6 +251,10 @@ CODEGENOPT(DiagnosticsWithHotness, 1, 0)
|
|||
/// Whether copy relocations support is available when building as PIE.
|
||||
CODEGENOPT(PIECopyRelocations, 1, 0)
|
||||
|
||||
/// Whether we should use the undefined behaviour optimization for control flow
|
||||
/// paths that reach the end of a function without executing a required return.
|
||||
CODEGENOPT(StrictReturn, 1, 1)
|
||||
|
||||
#undef CODEGENOPT
|
||||
#undef ENUM_CODEGENOPT
|
||||
#undef VALUE_CODEGENOPT
|
||||
|
|
|
@ -1049,6 +1049,19 @@ QualType CodeGenFunction::BuildFunctionArgList(GlobalDecl GD,
|
|||
return ResTy;
|
||||
}
|
||||
|
||||
static bool
|
||||
shouldUseUndefinedBehaviorReturnOptimization(const FunctionDecl *FD,
|
||||
const ASTContext &Context) {
|
||||
QualType T = FD->getReturnType();
|
||||
// Avoid the optimization for functions that return a record type with a
|
||||
// trivial destructor or another trivially copyable type.
|
||||
if (const RecordType *RT = T.getCanonicalType()->getAs<RecordType>()) {
|
||||
if (const auto *ClassDecl = dyn_cast<CXXRecordDecl>(RT->getDecl()))
|
||||
return !ClassDecl->hasTrivialDestructor();
|
||||
}
|
||||
return !T.isTriviallyCopyableType(Context);
|
||||
}
|
||||
|
||||
void CodeGenFunction::GenerateCode(GlobalDecl GD, llvm::Function *Fn,
|
||||
const CGFunctionInfo &FnInfo) {
|
||||
const FunctionDecl *FD = cast<FunctionDecl>(GD.getDecl());
|
||||
|
@ -1127,17 +1140,23 @@ void CodeGenFunction::GenerateCode(GlobalDecl GD, llvm::Function *Fn,
|
|||
// function call is used by the caller, the behavior is undefined.
|
||||
if (getLangOpts().CPlusPlus && !FD->hasImplicitReturnZero() && !SawAsmBlock &&
|
||||
!FD->getReturnType()->isVoidType() && Builder.GetInsertBlock()) {
|
||||
bool ShouldEmitUnreachable =
|
||||
CGM.getCodeGenOpts().StrictReturn ||
|
||||
shouldUseUndefinedBehaviorReturnOptimization(FD, getContext());
|
||||
if (SanOpts.has(SanitizerKind::Return)) {
|
||||
SanitizerScope SanScope(this);
|
||||
llvm::Value *IsFalse = Builder.getFalse();
|
||||
EmitCheck(std::make_pair(IsFalse, SanitizerKind::Return),
|
||||
SanitizerHandler::MissingReturn,
|
||||
EmitCheckSourceLocation(FD->getLocation()), None);
|
||||
} else if (CGM.getCodeGenOpts().OptimizationLevel == 0) {
|
||||
EmitTrapCall(llvm::Intrinsic::trap);
|
||||
} else if (ShouldEmitUnreachable) {
|
||||
if (CGM.getCodeGenOpts().OptimizationLevel == 0)
|
||||
EmitTrapCall(llvm::Intrinsic::trap);
|
||||
}
|
||||
if (SanOpts.has(SanitizerKind::Return) || ShouldEmitUnreachable) {
|
||||
Builder.CreateUnreachable();
|
||||
Builder.ClearInsertionPoint();
|
||||
}
|
||||
Builder.CreateUnreachable();
|
||||
Builder.ClearInsertionPoint();
|
||||
}
|
||||
|
||||
// Emit the standard function epilogue.
|
||||
|
|
|
@ -4462,6 +4462,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
|
|||
if (Args.hasFlag(options::OPT_fstrict_enums, options::OPT_fno_strict_enums,
|
||||
false))
|
||||
CmdArgs.push_back("-fstrict-enums");
|
||||
if (!Args.hasFlag(options::OPT_fstrict_return, options::OPT_fno_strict_return,
|
||||
true))
|
||||
CmdArgs.push_back("-fno-strict-return");
|
||||
if (Args.hasFlag(options::OPT_fstrict_vtable_pointers,
|
||||
options::OPT_fno_strict_vtable_pointers,
|
||||
false))
|
||||
|
|
|
@ -602,6 +602,7 @@ static bool ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, InputKind IK,
|
|||
Opts.NoDwarfDirectoryAsm = Args.hasArg(OPT_fno_dwarf_directory_asm);
|
||||
Opts.SoftFloat = Args.hasArg(OPT_msoft_float);
|
||||
Opts.StrictEnums = Args.hasArg(OPT_fstrict_enums);
|
||||
Opts.StrictReturn = !Args.hasArg(OPT_fno_strict_return);
|
||||
Opts.StrictVTablePointers = Args.hasArg(OPT_fstrict_vtable_pointers);
|
||||
Opts.UnsafeFPMath = Args.hasArg(OPT_menable_unsafe_fp_math) ||
|
||||
Args.hasArg(OPT_cl_unsafe_math_optimizations) ||
|
||||
|
|
|
@ -1,12 +1,105 @@
|
|||
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple -o - %s | FileCheck %s
|
||||
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple -O -o - %s | FileCheck %s --check-prefix=CHECK-OPT
|
||||
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple -std=c++11 -o - %s | FileCheck --check-prefixes=CHECK,CHECK-COMMON %s
|
||||
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple -std=c++11 -O -o - %s | FileCheck %s --check-prefixes=CHECK-OPT,CHECK-COMMON
|
||||
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple -std=c++11 -fno-strict-return -o - %s | FileCheck %s --check-prefixes=CHECK-NOSTRICT,CHECK-COMMON
|
||||
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple -std=c++11 -fno-strict-return -Wno-return-type -o - %s | FileCheck %s --check-prefixes=CHECK-NOSTRICT,CHECK-COMMON
|
||||
// RUN: %clang_cc1 -emit-llvm -triple %itanium_abi_triple -std=c++11 -fno-strict-return -O -o - %s | FileCheck %s --check-prefixes=CHECK-NOSTRICT-OPT,CHECK-COMMON
|
||||
|
||||
// CHECK: @_Z9no_return
|
||||
// CHECK-OPT: @_Z9no_return
|
||||
// CHECK-COMMON-LABEL: @_Z9no_return
|
||||
int no_return() {
|
||||
// CHECK: call void @llvm.trap
|
||||
// CHECK-NEXT: unreachable
|
||||
|
||||
// CHECK-OPT-NOT: call void @llvm.trap
|
||||
// CHECK-OPT: unreachable
|
||||
|
||||
// -fno-strict-return should not emit trap + unreachable but it should return
|
||||
// an undefined value instead.
|
||||
|
||||
// CHECK-NOSTRICT: entry:
|
||||
// CHECK-NOSTRICT-NEXT: alloca
|
||||
// CHECK-NOSTRICT-NEXT: load
|
||||
// CHECK-NOSTRICT-NEXT: ret i32
|
||||
// CHECK-NOSTRICT-NEXT: }
|
||||
|
||||
// CHECK-NOSTRICT-OPT: entry:
|
||||
// CHECK-NOSTRICT-OPT: ret i32 undef
|
||||
}
|
||||
|
||||
enum Enum {
|
||||
A, B
|
||||
};
|
||||
|
||||
// CHECK-COMMON-LABEL: @_Z27returnNotViableDontOptimize4Enum
|
||||
int returnNotViableDontOptimize(Enum e) {
|
||||
switch (e) {
|
||||
case A: return 1;
|
||||
case B: return 2;
|
||||
}
|
||||
// Undefined behaviour optimization shouldn't be used when -fno-strict-return
|
||||
// is turned on, even if all the enum cases are covered in this function.
|
||||
|
||||
// CHECK-NOSTRICT-NOT: call void @llvm.trap
|
||||
// CHECK-NOSTRICT-NOT: unreachable
|
||||
}
|
||||
|
||||
struct Trivial {
|
||||
int x;
|
||||
};
|
||||
|
||||
// CHECK-NOSTRICT-LABEL: @_Z7trivialv
|
||||
Trivial trivial() {
|
||||
// This function returns a trivial record so -fno-strict-return should avoid
|
||||
// the undefined behaviour optimization.
|
||||
|
||||
// CHECK-NOSTRICT-NOT: call void @llvm.trap
|
||||
// CHECK-NOSTRICT-NOT: unreachable
|
||||
}
|
||||
|
||||
struct NonTrivialCopy {
|
||||
NonTrivialCopy(const NonTrivialCopy &);
|
||||
};
|
||||
|
||||
// CHECK-NOSTRICT-LABEL: @_Z14nonTrivialCopyv
|
||||
NonTrivialCopy nonTrivialCopy() {
|
||||
// CHECK-NOSTRICT-NOT: call void @llvm.trap
|
||||
// CHECK-NOSTRICT-NOT: unreachable
|
||||
}
|
||||
|
||||
struct NonTrivialDefaultConstructor {
|
||||
int x;
|
||||
|
||||
NonTrivialDefaultConstructor() { }
|
||||
};
|
||||
|
||||
// CHECK-NOSTRICT-LABEL: @_Z28nonTrivialDefaultConstructorv
|
||||
NonTrivialDefaultConstructor nonTrivialDefaultConstructor() {
|
||||
// CHECK-NOSTRICT-NOT: call void @llvm.trap
|
||||
// CHECK-NOSTRICT-NOT: unreachable
|
||||
}
|
||||
|
||||
// Functions that return records with non-trivial destructors should always use
|
||||
// the -fstrict-return optimization.
|
||||
|
||||
struct NonTrivialDestructor {
|
||||
~NonTrivialDestructor();
|
||||
};
|
||||
|
||||
// CHECK-NOSTRICT-LABEL: @_Z20nonTrivialDestructorv
|
||||
NonTrivialDestructor nonTrivialDestructor() {
|
||||
// CHECK-NOSTRICT: call void @llvm.trap
|
||||
// CHECK-NOSTRICT-NEXT: unreachable
|
||||
}
|
||||
|
||||
// The behavior for lambdas should be identical to functions.
|
||||
// CHECK-COMMON-LABEL: @_Z10lambdaTestv
|
||||
void lambdaTest() {
|
||||
auto lambda1 = []() -> int {
|
||||
};
|
||||
lambda1();
|
||||
|
||||
// CHECK: call void @llvm.trap
|
||||
// CHECK-NEXT: unreachable
|
||||
|
||||
// CHECK-NOSTRICT-NOT: call void @llvm.trap
|
||||
// CHECK-NOSTRICT-NOT: unreachable
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
// RUN: %clang_cc1 -emit-llvm -fblocks -triple x86_64-apple-darwin -fstrict-return -o - %s | FileCheck %s
|
||||
// RUN: %clang_cc1 -emit-llvm -fblocks -triple x86_64-apple-darwin -fstrict-return -O -o - %s | FileCheck %s
|
||||
|
||||
@interface I
|
||||
@end
|
||||
|
||||
@implementation I
|
||||
|
||||
- (int)method {
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
enum Enum {
|
||||
a
|
||||
};
|
||||
|
||||
int (^block)(Enum) = ^int(Enum e) {
|
||||
switch (e) {
|
||||
case a:
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure that both methods and blocks don't use the -fstrict-return undefined
|
||||
// behaviour optimization.
|
||||
|
||||
// CHECK-NOT: call void @llvm.trap
|
||||
// CHECK-NOT: unreachable
|
|
@ -477,3 +477,8 @@
|
|||
// CHECK-NEW-PM-NOT: -fno-experimental-new-pass-manager
|
||||
// CHECK-NO-NEW-PM: -fno-experimental-new-pass-manager
|
||||
// CHECK-NO-NEW-PM-NOT: -fexperimental-new-pass-manager
|
||||
|
||||
// RUN: %clang -### -S -fstrict-return %s 2>&1 | FileCheck -check-prefix=CHECK-STRICT-RETURN %s
|
||||
// RUN: %clang -### -S -fno-strict-return %s 2>&1 | FileCheck -check-prefix=CHECK-NO-STRICT-RETURN %s
|
||||
// CHECK-STRICT-RETURN-NOT: "-fno-strict-return"
|
||||
// CHECK-NO-STRICT-RETURN: "-fno-strict-return"
|
||||
|
|
Loading…
Reference in New Issue