Index: include/clang/AST/Type.h =================================================================== --- include/clang/AST/Type.h +++ include/clang/AST/Type.h @@ -2931,14 +2931,16 @@ // you'll need to adjust both the Bits field below and // Type::FunctionTypeBitfields. - // | CC |noreturn|produces|regparm| - // |0 .. 4| 5 | 6 | 7 .. 9| + // | CC |noreturn|produces|nocallersavedregs|regparm| + // |0 .. 4| 5 | 6 | 7 |8 .. 10| // // regparm is either 0 (no regparm attribute) or the regparm value+1. enum { CallConvMask = 0x1F }; enum { NoReturnMask = 0x20 }; enum { ProducesResultMask = 0x40 }; - enum { RegParmMask = ~(CallConvMask | NoReturnMask | ProducesResultMask), + enum { NoCallerSavedRegsMask = 0x80 }; + enum { RegParmMask = ~(CallConvMask | NoReturnMask | + ProducesResultMask | NoCallerSavedRegsMask ), RegParmOffset = 7 }; // Assumed to be the last field uint16_t Bits; @@ -2951,11 +2953,12 @@ // Constructor with no defaults. Use this when you know that you // have all the elements (when reading an AST file for example). ExtInfo(bool noReturn, bool hasRegParm, unsigned regParm, CallingConv cc, - bool producesResult) { + bool producesResult, bool noCallerSavedRegs) { assert((!hasRegParm || regParm < 7) && "Invalid regparm value"); Bits = ((unsigned) cc) | (noReturn ? NoReturnMask : 0) | (producesResult ? ProducesResultMask : 0) | + (noCallerSavedRegs ? NoCallerSavedRegsMask : 0) | (hasRegParm ? ((regParm + 1) << RegParmOffset) : 0); } @@ -2969,6 +2972,7 @@ bool getNoReturn() const { return Bits & NoReturnMask; } bool getProducesResult() const { return Bits & ProducesResultMask; } + bool getNoCallerSavedRegs() const { return Bits & NoCallerSavedRegsMask; } bool getHasRegParm() const { return (Bits >> RegParmOffset) != 0; } unsigned getRegParm() const { unsigned RegParm = Bits >> RegParmOffset; @@ -3002,6 +3006,13 @@ return ExtInfo(Bits & ~ProducesResultMask); } + ExtInfo withNoCallerSavedRegs(bool noCallerSavedRegs) const { + if (noCallerSavedRegs) + return ExtInfo(Bits | NoCallerSavedRegsMask); + else + return ExtInfo(Bits & ~NoCallerSavedRegsMask); + } + ExtInfo withRegParm(unsigned RegParm) const { assert(RegParm < 7 && "Invalid regparm value"); return ExtInfo((Bits & ~RegParmMask) | Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -1812,6 +1812,13 @@ let Documentation = [AnyX86InterruptDocs]; } +def AnyX86NoCallerSavedRegisters : InheritableAttr, + TargetSpecificAttr { + let Spellings = [GCC<"no_caller_saved_registers">]; + let Subjects = SubjectList<[FunctionLike], WarnDiag, "ExpectedFunction">; + let Documentation = [AnyX86NoCallerSavedRegistersDocs]; +} + def X86ForceAlignArgPointer : InheritableAttr, TargetSpecificAttr { let Spellings = [GNU<"force_align_arg_pointer">]; // Technically, this appertains to a FunctionDecl, but the target-specific Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -2657,6 +2657,38 @@ }]; } +def AnyX86NoCallerSavedRegistersDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +Use this attribute to indicate that the specified function has no +caller-saved registers. That is, all registers are callee-saved. +The compiler generates proper function entry and exit sequences to +save and restore any modified registers. + +The user can call functions specified with the 'no_caller_saved_registers' +attribute from an interrupt handler without saving and restoring all +call clobbered registers. + +Note that 'no_caller_saved_registers' attribute is not a calling convention. +In fact, it only override the decision of what registers should be saved by +the caller, but not how the parameters are passed from the caller to the callee. + +For example: + + .. code-block:: c + + __attribute__ ((no_caller_saved_registers, fastcall)) + void f (int arg1, int arg2) { + ... + } + + In this case parameters 'arg1' and 'arg2' will be passed on registers + In this case, on 32-bit x86 targets, the function 'f' will use ECX and EDX as + register parameters. However, it will not assume any scratch registers and + should save and restore any modified register. + }]; +} + def SwiftCallDocs : Documentation { let Category = DocCatVariable; let Content = [{ Index: include/clang/CodeGen/CGFunctionInfo.h =================================================================== --- include/clang/CodeGen/CGFunctionInfo.h +++ include/clang/CodeGen/CGFunctionInfo.h @@ -461,7 +461,7 @@ unsigned EffectiveCallingConvention : 8; /// The clang::CallingConv that this was originally created with. - unsigned ASTCallingConvention : 8; + unsigned ASTCallingConvention : 6; /// Whether this is an instance method. unsigned InstanceMethod : 1; @@ -475,10 +475,16 @@ /// Whether this function is returns-retained. unsigned ReturnsRetained : 1; + /// Whether this function saved caller registers. + unsigned NoCallerSavedRegs : 1; + /// How many arguments to pass inreg. unsigned HasRegParm : 1; unsigned RegParm : 3; + /// Unused bits, available for future use. + unsigned UnusedBits : 1; + RequiredArgs Required; /// The struct representing all arguments passed in memory. Only used when @@ -560,6 +566,9 @@ /// is not always reliable for call sites. bool isReturnsRetained() const { return ReturnsRetained; } + /// Whether this function no longer saves caller registers + bool isNoCallerSavedRegs() const { return NoCallerSavedRegs; } + /// getASTCallingConvention() - Return the AST-specified calling /// convention. CallingConv getASTCallingConvention() const { @@ -586,7 +595,8 @@ return FunctionType::ExtInfo(isNoReturn(), getHasRegParm(), getRegParm(), getASTCallingConvention(), - isReturnsRetained()); + isReturnsRetained(), + isNoCallerSavedRegs()); } CanQualType getReturnType() const { return getArgsBuffer()[0].type; } @@ -623,6 +633,7 @@ ID.AddBoolean(ChainCall); ID.AddBoolean(NoReturn); ID.AddBoolean(ReturnsRetained); + ID.AddBoolean(NoCallerSavedRegs); ID.AddBoolean(HasRegParm); ID.AddInteger(RegParm); ID.AddInteger(Required.getOpaqueData()); @@ -648,6 +659,7 @@ ID.AddBoolean(ChainCall); ID.AddBoolean(info.getNoReturn()); ID.AddBoolean(info.getProducesResult()); + ID.AddBoolean(info.getNoCallerSavedRegs()); ID.AddBoolean(info.getHasRegParm()); ID.AddInteger(info.getRegParm()); ID.AddInteger(required.getOpaqueData()); Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -3150,6 +3150,7 @@ bool CheckCallingConvAttr(const AttributeList &attr, CallingConv &CC, const FunctionDecl *FD = nullptr); bool CheckNoReturnAttr(const AttributeList &attr); + bool CheckNoCallerSavedRegsAttr(const AttributeList &attr); bool checkStringLiteralArgumentAttr(const AttributeList &Attr, unsigned ArgNum, StringRef &Str, SourceLocation *ArgLocation = nullptr); Index: lib/AST/ASTContext.cpp =================================================================== --- lib/AST/ASTContext.cpp +++ lib/AST/ASTContext.cpp @@ -7917,6 +7917,8 @@ if (lbaseInfo.getProducesResult() != rbaseInfo.getProducesResult()) return QualType(); + if (lbaseInfo.getNoCallerSavedRegs() != rbaseInfo.getNoCallerSavedRegs()) + return QualType(); // FIXME: some uses, e.g. conditional exprs, really want this to be 'both'. bool NoReturn = lbaseInfo.getNoReturn() || rbaseInfo.getNoReturn(); Index: lib/AST/TypePrinter.cpp =================================================================== --- lib/AST/TypePrinter.cpp +++ lib/AST/TypePrinter.cpp @@ -750,6 +750,8 @@ if (Info.getRegParm()) OS << " __attribute__((regparm (" << Info.getRegParm() << ")))"; + if (Info.getNoCallerSavedRegs()) + OS << "__attribute__((no_caller_saved_registers))"; if (unsigned quals = T->getTypeQuals()) { OS << ' '; Index: lib/CodeGen/CGCall.cpp =================================================================== --- lib/CodeGen/CGCall.cpp +++ lib/CodeGen/CGCall.cpp @@ -788,6 +788,7 @@ FI->ChainCall = chainCall; FI->NoReturn = info.getNoReturn(); FI->ReturnsRetained = info.getProducesResult(); + FI->NoCallerSavedRegs = info.getNoCallerSavedRegs(); FI->Required = required; FI->HasRegParm = info.getHasRegParm(); FI->RegParm = info.getRegParm(); @@ -1815,6 +1816,8 @@ RetAttrs.addAttribute(llvm::Attribute::NoAlias); if (TargetDecl->hasAttr()) RetAttrs.addAttribute(llvm::Attribute::NonNull); + if (TargetDecl->hasAttr()) + FuncAttrs.addAttribute("no_caller_saved_registers"); HasOptnone = TargetDecl->hasAttr(); if (auto *AllocSize = TargetDecl->getAttr()) { Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -2957,6 +2957,12 @@ RequiresAdjustment = true; } + if (OldTypeInfo.getNoCallerSavedRegs() != + NewTypeInfo.getNoCallerSavedRegs()) { + NewTypeInfo = NewTypeInfo.withNoCallerSavedRegs(true); + RequiresAdjustment = true; + } + if (RequiresAdjustment) { const FunctionType *AdjustedType = New->getType()->getAs(); AdjustedType = Context.adjustFunctionType(AdjustedType, NewTypeInfo); Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -1943,6 +1943,15 @@ attr.getAttributeSpellingListIndex())); } +static void handleNoCallerSavedRegsAttr(Sema &S, Decl *D, + const AttributeList &attr) { + if (S.CheckNoCallerSavedRegsAttr(attr)) + return; + + D->addAttr(::new (S.Context) AnyX86NoCallerSavedRegistersAttr( + attr.getRange(), S.Context, attr.getAttributeSpellingListIndex())); +} + bool Sema::CheckNoReturnAttr(const AttributeList &attr) { if (!checkAttributeNumArgs(*this, attr, 0)) { attr.setInvalid(); @@ -1952,6 +1961,15 @@ return false; } +bool Sema::CheckNoCallerSavedRegsAttr(const AttributeList &attr) { + if (!checkAttributeNumArgs(*this, attr, 0)) { + attr.setInvalid(); + return true; + } + + return false; +} + static void handleAnalyzerNoReturnAttr(Sema &S, Decl *D, const AttributeList &Attr) { @@ -6426,6 +6444,9 @@ case AttributeList::AT_TypeTagForDatatype: handleTypeTagForDatatypeAttr(S, D, Attr); break; + case AttributeList::AT_AnyX86NoCallerSavedRegisters: + handleNoCallerSavedRegsAttr(S, D, Attr); + break; case AttributeList::AT_RenderScriptKernel: handleSimpleAttribute(S, D, Attr); break; Index: lib/Sema/SemaType.cpp =================================================================== --- lib/Sema/SemaType.cpp +++ lib/Sema/SemaType.cpp @@ -121,6 +121,7 @@ #define FUNCTION_TYPE_ATTRS_CASELIST \ case AttributeList::AT_NoReturn: \ case AttributeList::AT_Regparm: \ + case AttributeList::AT_AnyX86NoCallerSavedRegisters: \ CALLING_CONV_ATTRS_CASELIST // Microsoft-specific type qualifiers. @@ -6371,6 +6372,20 @@ return true; } + if (attr.getKind() == AttributeList::AT_AnyX86NoCallerSavedRegisters) { + if (S.CheckNoCallerSavedRegsAttr(attr)) + return true; + + // Delay if this is not a function type. + if (!unwrapped.isFunctionType()) + return false; + + FunctionType::ExtInfo EI = + unwrapped.get()->getExtInfo().withNoCallerSavedRegs(true); + type = unwrapped.wrap(S, S.Context.adjustFunctionType(unwrapped.get(), EI)); + return true; + } + if (attr.getKind() == AttributeList::AT_Regparm) { unsigned value; if (S.CheckRegparmAttr(attr, value)) Index: lib/Serialization/ASTReader.cpp =================================================================== --- lib/Serialization/ASTReader.cpp +++ lib/Serialization/ASTReader.cpp @@ -5755,13 +5755,14 @@ } case TYPE_FUNCTION_NO_PROTO: { - if (Record.size() != 6) { + if (Record.size() != 7) { Error("incorrect encoding of no-proto function type"); return QualType(); } QualType ResultType = readType(*Loc.F, Record, Idx); FunctionType::ExtInfo Info(Record[1], Record[2], Record[3], - (CallingConv)Record[4], Record[5]); + (CallingConv)Record[4], Record[5], + Record[6]); return Context.getFunctionNoProtoType(ResultType, Info); } @@ -5773,9 +5774,10 @@ /*hasregparm*/ Record[2], /*regparm*/ Record[3], static_cast(Record[4]), - /*produces*/ Record[5]); + /*produces*/ Record[5], + /*nocallersavedregs*/ Record[6]); - unsigned Idx = 6; + unsigned Idx = 7; EPI.Variadic = Record[Idx++]; EPI.HasTrailingReturn = Record[Idx++]; Index: lib/Serialization/ASTWriter.cpp =================================================================== --- lib/Serialization/ASTWriter.cpp +++ lib/Serialization/ASTWriter.cpp @@ -255,6 +255,7 @@ // FIXME: need to stabilize encoding of calling convention... Record.push_back(C.getCC()); Record.push_back(C.getProducesResult()); + Record.push_back(C.getNoCallerSavedRegs()); if (C.getHasRegParm() || C.getRegParm() || C.getProducesResult()) AbbrevToUse = 0; @@ -839,6 +840,7 @@ Abv->Add(BitCodeAbbrevOp(0)); // RegParm Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 4)); // CC Abv->Add(BitCodeAbbrevOp(0)); // ProducesResult + Abv->Add(BitCodeAbbrevOp(0)); // NoCallerSavedRegs // FunctionProtoType Abv->Add(BitCodeAbbrevOp(0)); // IsVariadic Abv->Add(BitCodeAbbrevOp(0)); // HasTrailingReturn Index: test/CodeGenCXX/attr-x86-no_caller_saved_registers.cpp =================================================================== --- test/CodeGenCXX/attr-x86-no_caller_saved_registers.cpp +++ test/CodeGenCXX/attr-x86-no_caller_saved_registers.cpp @@ -0,0 +1,34 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 -triple i386-unknown-linux-gnu %s -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-pc-win32 %s -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 -triple i386-pc-win32 %s -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnux32 %s -emit-llvm -o - | FileCheck %s + +// CHECK: foo{{[^#]*}}#[[ATTRS:[0-9]+]] +__attribute__((no_caller_saved_registers)) void foo() {} +namespace S { +// CHECK: bar{{[^#]*}}#[[ATTRS]] +__attribute__((no_caller_saved_registers)) void bar(int *a) { foo(); } +} + +struct St { + static void baz(int *a) __attribute__((no_caller_saved_registers)) { S::bar(a); } +}; + +__attribute((no_caller_saved_registers)) void (*foobar)(void); + +// CHECK-LABEL: @main +int main(int argc, char **argv) { + St::baz(&argc); + // CHECK: [[FOOBAR:%.+]] = load void ()*, void ()** @{{.*}}foobar{{.*}}, + // CHECK-NEXT: call void [[FOOBAR]]() #[[ATTRS1:.+]] + foobar(); + return 0; +} + +// CHECK: baz{{[^#]*}}#[[ATTRS]] + +// CHECK: attributes #[[ATTRS]] = { +// CHECK-SAME: "no_caller_saved_registers" +// CHECK-SAME: } +// CHECK: attributes #[[ATTRS1]] = { "no_caller_saved_registers" } Index: test/SemaCXX/attr-x86-no_caller_saved_registers.cpp =================================================================== --- test/SemaCXX/attr-x86-no_caller_saved_registers.cpp +++ test/SemaCXX/attr-x86-no_caller_saved_registers.cpp @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++11 -triple i386-unknown-linux-gnu -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++11 -triple x86_64-pc-win32 -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++11 -triple i386-pc-win32 -fsyntax-only -verify %s +// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnux32 -fsyntax-only -verify %s + +struct a { + int b __attribute__((no_caller_saved_registers)); // expected-warning {{'no_caller_saved_registers' only applies to function types; type here is 'int'}} + static void foo(int *a) __attribute__((no_caller_saved_registers)) {} +}; + +struct a test __attribute__((no_caller_saved_registers)); // expected-warning {{'no_caller_saved_registers' only applies to function types; type here is 'struct a'}} + +__attribute__((no_caller_saved_registers(999))) void bar(int *) {} // expected-error {{'no_caller_saved_registers' attribute takes no arguments}} + +__attribute__((no_caller_saved_registers)) void foo(int *) {} + +[[gnu::no_caller_saved_registers]] void foo2(int *) {} + +int main(int argc, char **argv) { + void(*fp)(int *) = foo; // expected-error {{cannot initialize a variable of type 'void (*)(int *)' with an lvalue of type 'void (int *)__attribute__((no_caller_saved_registers))'}} + a::foo(&argc); + foo(&argc); + return 0; +}