nickdesaulniers updated this revision to Diff 443986.
nickdesaulniers added a comment.

- add more links to commit message as they come in


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D129572/new/

https://reviews.llvm.org/D129572

Files:
  clang/docs/ReleaseNotes.rst
  clang/include/clang/Basic/Attr.td
  clang/include/clang/Basic/AttrDocs.td
  clang/include/clang/Basic/CodeGenOptions.def
  clang/include/clang/Driver/Options.td
  clang/lib/CodeGen/CodeGenFunction.cpp
  clang/lib/Driver/ToolChains/Clang.cpp
  clang/lib/Frontend/CompilerInvocation.cpp
  clang/lib/Sema/SemaDeclAttr.cpp
  clang/test/CodeGen/attr-function-return.c
  clang/test/CodeGen/attr-function-return.cpp
  clang/test/Driver/mfunction-return.c
  clang/test/Frontend/mfunction-return.c
  clang/test/Misc/pragma-attribute-supported-attributes-list.test
  clang/test/Sema/attr-function-return-unsupported-target.c
  clang/test/Sema/attr-function-return.c
  llvm/docs/LangRef.rst
  llvm/include/llvm/Bitcode/LLVMBitCodes.h
  llvm/include/llvm/IR/Attributes.td
  llvm/include/llvm/Support/CodeGen.h
  llvm/lib/Bitcode/Reader/BitcodeReader.cpp
  llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
  llvm/lib/Target/X86/CMakeLists.txt
  llvm/lib/Target/X86/X86.h
  llvm/lib/Target/X86/X86ReturnThunks.cpp
  llvm/lib/Target/X86/X86TargetMachine.cpp
  llvm/lib/Transforms/Utils/CodeExtractor.cpp
  llvm/test/Bitcode/attributes.ll
  llvm/test/CodeGen/X86/O0-pipeline.ll
  llvm/test/CodeGen/X86/attr-function-return.ll
  llvm/test/CodeGen/X86/attr-function-return.mir
  llvm/test/CodeGen/X86/opt-pipeline.ll
  llvm/test/Transforms/Inline/attributes.ll

Index: llvm/test/Transforms/Inline/attributes.ll
===================================================================
--- llvm/test/Transforms/Inline/attributes.ll
+++ llvm/test/Transforms/Inline/attributes.ll
@@ -600,6 +600,33 @@
 ; CHECK-NEXT: ret i32
 }
 
+; Test that fn_ret_thunk_extern has no CompatRule; inlining is permitted.
+; Test that fn_ret_thunk_extern has no MergeRule; fn_ret_thunk_extern is not
+; propagated or dropped on the caller after inlining.
+define i32 @thunk_extern_callee() fn_ret_thunk_extern {
+; CHECK: @thunk_extern_callee() [[FNRETTHUNK_EXTERN:#[0-9]+]]
+  ret i32 42
+}
+
+define i32 @thunk_keep_caller() {
+; CHECK: @thunk_keep_caller() {
+; CHECK-NEXT: ret i32 42
+  %1 = call i32 @thunk_extern_callee()
+  ret i32 %1
+}
+
+define i32 @thunk_keep_callee() {
+; CHECK: @thunk_keep_callee() {
+  ret i32 42
+}
+
+define i32 @thunk_extern_caller() fn_ret_thunk_extern {
+; CHECK: @thunk_extern_caller() [[FNRETTHUNK_EXTERN]]
+; CHECK-NEXT: ret i32 42
+  %1 = call i32 @thunk_keep_callee()
+  ret i32 %1
+}
+
 ; CHECK: attributes [[SLH]] = { speculative_load_hardening }
 ; CHECK: attributes [[FPMAD_FALSE]] = { "less-precise-fpmad"="false" }
 ; CHECK: attributes [[FPMAD_TRUE]] = { "less-precise-fpmad"="true" }
@@ -614,3 +641,4 @@
 ; CHECK: attributes [[NO_SIGNED_ZEROS_FPMATH_TRUE]] = { "no-signed-zeros-fp-math"="true" }
 ; CHECK: attributes [[UNSAFE_FPMATH_FALSE]] = { "unsafe-fp-math"="false" }
 ; CHECK: attributes [[UNSAFE_FPMATH_TRUE]] = { "unsafe-fp-math"="true" }
+; CHECK: attributes [[FNRETTHUNK_EXTERN]] = { fn_ret_thunk_extern }
Index: llvm/test/CodeGen/X86/opt-pipeline.ll
===================================================================
--- llvm/test/CodeGen/X86/opt-pipeline.ll
+++ llvm/test/CodeGen/X86/opt-pipeline.ll
@@ -204,6 +204,7 @@
 ; CHECK-NEXT:       Live DEBUG_VALUE analysis
 ; CHECK-NEXT:       X86 Speculative Execution Side Effect Suppression
 ; CHECK-NEXT:       X86 Indirect Thunks
+; CHECK-NEXT:       X86 Return Thunks
 ; CHECK-NEXT:       Check CFA info and insert CFI instructions if needed
 ; CHECK-NEXT:       X86 Load Value Injection (LVI) Ret-Hardening
 ; CHECK-NEXT:       Pseudo Probe Inserter
Index: llvm/test/CodeGen/X86/attr-function-return.mir
===================================================================
--- /dev/null
+++ llvm/test/CodeGen/X86/attr-function-return.mir
@@ -0,0 +1,62 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py
+# RUN: llc --mtriple=x86_64-linux-gnu -run-pass=x86-return-thunks \
+# RUN:   -verify-machineinstrs %s -o - | FileCheck %s
+--- |
+  ; ModuleID = 'y.ll'
+  source_filename = "y.ll"
+  target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+  define void @x() #0 {
+    ret void
+  }
+
+  attributes #0 = { fn_ret_thunk_extern }
+
+...
+---
+name:            x
+alignment:       16
+exposesReturnsTwice: false
+legalized:       false
+regBankSelected: false
+selected:        false
+failedISel:      false
+tracksRegLiveness: true
+hasWinCFI:       false
+failsVerification: false
+tracksDebugUserValues: true
+registers:       []
+liveins:         []
+frameInfo:
+  isFrameAddressTaken: false
+  isReturnAddressTaken: false
+  hasStackMap:     false
+  hasPatchPoint:   false
+  stackSize:       0
+  offsetAdjustment: 0
+  maxAlignment:    1
+  adjustsStack:    false
+  hasCalls:        false
+  stackProtector:  ''
+  maxCallFrameSize: 0
+  cvBytesOfCalleeSavedRegisters: 0
+  hasOpaqueSPAdjustment: false
+  hasVAStart:      false
+  hasMustTailInVarArgFunc: false
+  hasTailCall:     false
+  localFrameSize:  0
+  savePoint:       ''
+  restorePoint:    ''
+fixedStack:      []
+stack:           []
+callSites:       []
+debugValueSubstitutions: []
+constants:       []
+machineFunctionInfo: {}
+body:             |
+  bb.0 (%ir-block.0):
+    ; CHECK-LABEL: name: x
+    ; CHECK: TAILJMPd &__x86_return_thunk, implicit $esp, implicit $ssp
+    RET64
+
+...
Index: llvm/test/CodeGen/X86/attr-function-return.ll
===================================================================
--- /dev/null
+++ llvm/test/CodeGen/X86/attr-function-return.ll
@@ -0,0 +1,11 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc --mtriple=i386-linux-gnu %s -o - -verify-machineinstrs \
+; RUN:   | FileCheck %s
+; RUN: llc --mtriple=x86_64-linux-gnu %s -o - -verify-machineinstrs \
+; RUN:   | FileCheck %s
+define void @x() fn_ret_thunk_extern {
+; CHECK-LABEL: x:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    jmp __x86_return_thunk
+  ret void
+}
Index: llvm/test/CodeGen/X86/O0-pipeline.ll
===================================================================
--- llvm/test/CodeGen/X86/O0-pipeline.ll
+++ llvm/test/CodeGen/X86/O0-pipeline.ll
@@ -71,8 +71,9 @@
 ; CHECK-NEXT:       Live DEBUG_VALUE analysis
 ; CHECK-NEXT:       X86 Speculative Execution Side Effect Suppression
 ; CHECK-NEXT:       X86 Indirect Thunks
+; CHECK-NEXT:       X86 Return Thunks
 ; CHECK-NEXT:       Check CFA info and insert CFI instructions if needed
-; CHECK-NEXT:       X86 Load Value Injection (LVI) Ret-Hardening  
+; CHECK-NEXT:       X86 Load Value Injection (LVI) Ret-Hardening
 ; CHECK-NEXT:       Pseudo Probe Inserter
 ; CHECK-NEXT:       Lazy Machine Block Frequency Analysis
 ; CHECK-NEXT:       Machine Optimization Remark Emitter
Index: llvm/test/Bitcode/attributes.ll
===================================================================
--- llvm/test/Bitcode/attributes.ll
+++ llvm/test/Bitcode/attributes.ll
@@ -532,6 +532,9 @@
         ret void;
 }
 
+; CHECK: define void @f87() [[FNRETTHUNKEXTERN:#[0-9]+]]
+define void @f87() fn_ret_thunk_extern { ret void }
+
 ; CHECK: attributes #0 = { noreturn }
 ; CHECK: attributes #1 = { nounwind }
 ; CHECK: attributes #2 = { readnone }
@@ -585,4 +588,5 @@
 ; CHECK: attributes #50 = { disable_sanitizer_instrumentation }
 ; CHECK: attributes #51 = { uwtable(sync) }
 ; CHECK: attributes #52 = { nosanitize_bounds }
+; CHECK: attributes [[FNRETTHUNKEXTERN]] = { fn_ret_thunk_extern }
 ; CHECK: attributes #[[NOBUILTIN]] = { nobuiltin }
Index: llvm/lib/Transforms/Utils/CodeExtractor.cpp
===================================================================
--- llvm/lib/Transforms/Utils/CodeExtractor.cpp
+++ llvm/lib/Transforms/Utils/CodeExtractor.cpp
@@ -927,6 +927,7 @@
       case Attribute::AlwaysInline:
       case Attribute::Cold:
       case Attribute::DisableSanitizerInstrumentation:
+      case Attribute::FnRetThunkExtern:
       case Attribute::Hot:
       case Attribute::NoRecurse:
       case Attribute::InlineHint:
Index: llvm/lib/Target/X86/X86TargetMachine.cpp
===================================================================
--- llvm/lib/Target/X86/X86TargetMachine.cpp
+++ llvm/lib/Target/X86/X86TargetMachine.cpp
@@ -100,6 +100,7 @@
   initializeX86OptimizeLEAPassPass(PR);
   initializeX86PartialReductionPass(PR);
   initializePseudoProbeInserterPass(PR);
+  initializeX86ReturnThunksPass(PR);
 }
 
 static std::unique_ptr<TargetLoweringObjectFile> createTLOF(const Triple &TT) {
@@ -575,6 +576,7 @@
   // hand inspection of the codegen output.
   addPass(createX86SpeculativeExecutionSideEffectSuppression());
   addPass(createX86IndirectThunksPass());
+  addPass(createX86ReturnThunksPass());
 
   // Insert extra int3 instructions after trailing call instructions to avoid
   // issues in the unwinder.
Index: llvm/lib/Target/X86/X86ReturnThunks.cpp
===================================================================
--- /dev/null
+++ llvm/lib/Target/X86/X86ReturnThunks.cpp
@@ -0,0 +1,92 @@
+//==- X86ReturnThunks.cpp - Replace rets with thunks or inline thunks --=//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+/// \file
+///
+/// Pass that replaces ret instructions with a jmp to __x86_return_thunk.
+///
+/// This corresponds to -mfunction-return=thunk-extern or
+/// __attribute__((function_return("thunk-extern").
+///
+/// This pass is a minimal implementation necessary to help mitigate
+/// RetBleed for the Linux kernel.
+///
+/// Should support for thunk or thunk-inline be necessary in the future, then
+/// this pass should be combined with x86-retpoline-thunks which already has
+/// machinery to emit thunks. Until then, YAGNI.
+///
+/// This pass is very similar to x86-lvi-ret.
+///
+//===----------------------------------------------------------------------===//
+
+#include "X86.h"
+#include "X86InstrInfo.h"
+#include "X86Subtarget.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/Triple.h"
+#include "llvm/CodeGen/MachineBasicBlock.h"
+#include "llvm/CodeGen/MachineFunction.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineInstr.h"
+#include "llvm/CodeGen/MachineInstrBuilder.h"
+#include "llvm/MC/MCInstrDesc.h"
+#include "llvm/Support/Debug.h"
+
+using namespace llvm;
+
+#define PASS_KEY "x86-return-thunks"
+#define DEBUG_TYPE PASS_KEY
+
+struct X86ReturnThunks final : public MachineFunctionPass {
+  static char ID;
+  X86ReturnThunks() : MachineFunctionPass(ID) {}
+  StringRef getPassName() const override { return "X86 Return Thunks"; }
+  bool runOnMachineFunction(MachineFunction &MF) override;
+};
+
+char X86ReturnThunks::ID = 0;
+
+bool X86ReturnThunks::runOnMachineFunction(MachineFunction &MF) {
+  LLVM_DEBUG(dbgs() << getPassName() << "\n");
+
+  bool Modified = false;
+
+  if (!MF.getFunction().hasFnAttribute(llvm::Attribute::FnRetThunkExtern))
+    return Modified;
+
+  StringRef ThunkName = "__x86_return_thunk";
+  if (MF.getFunction().getName() == ThunkName)
+    return Modified;
+
+  const auto &ST = MF.getSubtarget<X86Subtarget>();
+  const bool Is64Bit = ST.getTargetTriple().getArch() == Triple::x86_64;
+  const unsigned RetOpc = Is64Bit ? X86::RET64 : X86::RET32;
+  SmallVector<MachineInstr *, 16> Rets;
+
+  for (MachineBasicBlock &MBB : MF)
+    for (MachineInstr &Term : MBB.terminators())
+      if (Term.getOpcode() == RetOpc)
+        Rets.push_back(&Term);
+
+  const MCInstrDesc &JMP = ST.getInstrInfo()->get(X86::TAILJMPd);
+
+  for (MachineInstr *Ret : Rets) {
+    BuildMI(Ret->getParent(), Ret->getDebugLoc(), JMP)
+        .addExternalSymbol(ThunkName.data());
+    Ret->eraseFromParent();
+    Modified = true;
+  }
+
+  return Modified;
+}
+
+INITIALIZE_PASS(X86ReturnThunks, PASS_KEY, "X86 Return Thunks", false, false)
+
+FunctionPass *llvm::createX86ReturnThunksPass() {
+  return new X86ReturnThunks();
+}
Index: llvm/lib/Target/X86/X86.h
===================================================================
--- llvm/lib/Target/X86/X86.h
+++ llvm/lib/Target/X86/X86.h
@@ -132,6 +132,9 @@
 /// This pass creates the thunks for the retpoline feature.
 FunctionPass *createX86IndirectThunksPass();
 
+/// This pass replaces ret instructions with jmp's to __x86_return thunk.
+FunctionPass *createX86ReturnThunksPass();
+
 /// This pass ensures instructions featuring a memory operand
 /// have distinctive <LineNumber, Discriminator> (with respect to eachother)
 FunctionPass *createX86DiscriminateMemOpsPass();
@@ -185,6 +188,7 @@
 void initializeX86PreAMXConfigPassPass(PassRegistry &);
 void initializeX86LowerTileCopyPass(PassRegistry &);
 void initializeX86LowerAMXIntrinsicsLegacyPassPass(PassRegistry &);
+void initializeX86ReturnThunksPass(PassRegistry &);
 
 namespace X86AS {
 enum : unsigned {
Index: llvm/lib/Target/X86/CMakeLists.txt
===================================================================
--- llvm/lib/Target/X86/CMakeLists.txt
+++ llvm/lib/Target/X86/CMakeLists.txt
@@ -75,6 +75,7 @@
   X86PartialReduction.cpp
   X86RegisterBankInfo.cpp
   X86RegisterInfo.cpp
+  X86ReturnThunks.cpp
   X86SelectionDAGInfo.cpp
   X86ShuffleDecodeConstantPool.cpp
   X86SpeculativeLoadHardening.cpp
Index: llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
===================================================================
--- llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
+++ llvm/lib/Bitcode/Writer/BitcodeWriter.cpp
@@ -634,6 +634,8 @@
     return bitc::ATTR_KIND_COLD;
   case Attribute::DisableSanitizerInstrumentation:
     return bitc::ATTR_KIND_DISABLE_SANITIZER_INSTRUMENTATION;
+  case Attribute::FnRetThunkExtern:
+    return bitc::ATTR_KIND_FNRETTHUNK_EXTERN;
   case Attribute::Hot:
     return bitc::ATTR_KIND_HOT;
   case Attribute::ElementType:
Index: llvm/lib/Bitcode/Reader/BitcodeReader.cpp
===================================================================
--- llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -1855,6 +1855,8 @@
     return Attribute::DisableSanitizerInstrumentation;
   case bitc::ATTR_KIND_ELEMENTTYPE:
     return Attribute::ElementType;
+  case bitc::ATTR_KIND_FNRETTHUNK_EXTERN:
+    return Attribute::FnRetThunkExtern;
   case bitc::ATTR_KIND_INACCESSIBLEMEM_ONLY:
     return Attribute::InaccessibleMemOnly;
   case bitc::ATTR_KIND_INACCESSIBLEMEM_OR_ARGMEMONLY:
Index: llvm/include/llvm/Support/CodeGen.h
===================================================================
--- llvm/include/llvm/Support/CodeGen.h
+++ llvm/include/llvm/Support/CodeGen.h
@@ -103,6 +103,13 @@
     Async = 2, ///< "Asynchronous" unwind tables (instr precise)
     Default = 2,
   };
+
+  enum class FunctionReturnThunksKind : unsigned int {
+    Keep = 0,    ///< No function return thunk.
+    Extern = 1,  ///< Replace returns with jump to thunk, don't emit thunk.
+    Invalid = 2, ///< Not used.
+  };
+
   } // namespace llvm
 
 #endif
Index: llvm/include/llvm/IR/Attributes.td
===================================================================
--- llvm/include/llvm/IR/Attributes.td
+++ llvm/include/llvm/IR/Attributes.td
@@ -102,6 +102,10 @@
 /// Provide pointer element type to intrinsic.
 def ElementType : TypeAttr<"elementtype", [ParamAttr]>;
 
+/// Whether to keep return instructions, or replace with a jump to an external
+/// symbol.
+def FnRetThunkExtern : EnumAttr<"fn_ret_thunk_extern", [FnAttr]>;
+
 /// Function may only access memory that is inaccessible from IR.
 def InaccessibleMemOnly : EnumAttr<"inaccessiblememonly", [FnAttr]>;
 
Index: llvm/include/llvm/Bitcode/LLVMBitCodes.h
===================================================================
--- llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -688,6 +688,7 @@
   ATTR_KIND_ALLOCATED_POINTER = 81,
   ATTR_KIND_ALLOC_KIND = 82,
   ATTR_KIND_PRESPLIT_COROUTINE = 83,
+  ATTR_KIND_FNRETTHUNK_EXTERN = 84,
 };
 
 enum ComdatSelectionKindCodes {
Index: llvm/docs/LangRef.rst
===================================================================
--- llvm/docs/LangRef.rst
+++ llvm/docs/LangRef.rst
@@ -1678,6 +1678,10 @@
     Front ends can provide optional ``srcloc`` metadata nodes on call sites of
     such callees to attach information about where in the source language such a
     call came from. A string value can be provided as a note.
+``fn_ret_thunk_extern``
+    This attribute tells the code generator that returns from functions should
+    be replaced with jumps to externally-defined architecture-specific symbols.
+    For X86, this symbol's identifier is ``__x86_return_thunk``.
 ``"frame-pointer"``
     This attribute tells the code generator whether the function
     should keep the frame pointer. The code generator may emit the frame pointer
Index: clang/test/Sema/attr-function-return.c
===================================================================
--- /dev/null
+++ clang/test/Sema/attr-function-return.c
@@ -0,0 +1,23 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsyntax-only -verify %s
+
+__attribute__((function_return("keep"))) void x(void) {}
+
+// expected-warning@+1 {{'function_return' attribute argument not supported: thunk}}
+__attribute__((function_return("thunk"))) void y(void) {}
+
+// expected-warning@+1 {{'function_return' attribute argument not supported: thunk-inline}}
+__attribute__((function_return("thunk-inline"))) void z(void) {}
+
+__attribute__((function_return("thunk-extern"))) void w(void) {}
+
+// expected-warning@+1 {{'function_return' attribute argument not supported: invalid}}
+__attribute__((function_return("invalid"))) void v(void) {}
+
+// expected-error@+1 {{'function_return' attribute requires a string}}
+__attribute__((function_return(5))) void a(void) {}
+
+// expected-error@+1 {{'function_return' attribute takes one argument}}
+__attribute__((function_return)) void b(void) {}
+
+// expected-warning@+1 {{'function_return' attribute only applies to functions}}
+__attribute__((function_return)) int c;
Index: clang/test/Sema/attr-function-return-unsupported-target.c
===================================================================
--- /dev/null
+++ clang/test/Sema/attr-function-return-unsupported-target.c
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -triple s390x-linux-gnu -fsyntax-only -verify %s
+
+// expected-warning@+1 {{unknown attribute 'function_return' ignored}}
+__attribute__((function_return("keep"))) void x(void) {}
+
+// expected-warning@+1 {{unknown attribute 'function_return' ignored}}
+__attribute__((function_return("thunk"))) void y(void) {}
+
+// expected-warning@+1 {{unknown attribute 'function_return' ignored}}
+__attribute__((function_return("thunk-inline"))) void z(void) {}
+
+// expected-warning@+1 {{unknown attribute 'function_return' ignored}}
+__attribute__((function_return("thunk-extern"))) void w(void) {}
+
+// expected-warning@+1 {{unknown attribute 'function_return' ignored}}
+__attribute__((function_return("invalid"))) void v(void) {}
Index: clang/test/Misc/pragma-attribute-supported-attributes-list.test
===================================================================
--- clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -69,6 +69,7 @@
 // CHECK-NEXT: ExternalSourceSymbol ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_implementation, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable))
 // CHECK-NEXT: FlagEnum (SubjectMatchRule_enum)
 // CHECK-NEXT: Flatten (SubjectMatchRule_function)
+// CHECK-NEXT: FunctionReturnThunks (SubjectMatchRule_function)
 // CHECK-NEXT: GNUInline (SubjectMatchRule_function)
 // CHECK-NEXT: HIPManaged (SubjectMatchRule_variable)
 // CHECK-NEXT: Hot (SubjectMatchRule_function)
Index: clang/test/Frontend/mfunction-return.c
===================================================================
--- /dev/null
+++ clang/test/Frontend/mfunction-return.c
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -mfunction-return=keep -triple x86_64-linux-gnu %s
+// RUN: %clang_cc1 -mfunction-return=thunk-extern -triple x86_64-linux-gnu %s
+
+// RUN: not %clang_cc1 -mfunction-return=thunk -triple x86_64-linux-gnu %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=CHECK-THUNK %s
+// RUN: not %clang_cc1 -mfunction-return=thunk-inline -triple x86_64-linux-gnu %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=CHECK-INLINE %s
+// RUN: not %clang_cc1 -mfunction-return=invalid -triple x86_64-linux-gnu %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=CHECK-INVALID %s
+// RUN: not %clang_cc1 -mfunction-return=thunk-extern -triple s390x-linux-gnu %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=CHECK-TARGET %s
+// RUN: not %clang_cc1 -mfunction-return=thunk-extern -mcmodel=large \
+// RUN:   -triple x86_64-linux-gnu %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=CHECK-LARGE %s
+
+// CHECK-THUNK: error: invalid value 'thunk' in '-mfunction-return=thunk'
+// CHECK-INLINE: error: invalid value 'thunk-inline' in '-mfunction-return=thunk-inline'
+// CHECK-INVALID: error: invalid value 'invalid' in '-mfunction-return=invalid'
+// CHECK-TARGET: error: invalid argument '-mfunction-return=' not allowed with 's390x-unknown-linux-gnu'
+// CHECK-LARGE: error: invalid argument '-mfunction-return=thunk-extern' not allowed with '-mcmodel=large'
Index: clang/test/Driver/mfunction-return.c
===================================================================
--- /dev/null
+++ clang/test/Driver/mfunction-return.c
@@ -0,0 +1,22 @@
+// RUN: %clang -mfunction-return= -### %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=CHECK-VALID %s
+// RUN: not %clang -mfunction-return -### %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=CHECK-INVALID %s
+
+// RUN: %clang -mfunction-return=keep -### %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=CHECK-KEEP %s
+// RUN: %clang -mfunction-return=thunk-extern -### %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=CHECK-EXTERN %s
+
+// RUN: %clang -mfunction-return=keep -mfunction-return=thunk-extern -### %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=CHECK-EXTERN %s
+// RUN: %clang -mfunction-return=thunk-extern -mfunction-return=keep -### %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=CHECK-KEEP %s
+
+// CHECK-VALID:   "-mfunction-return="
+// CHECK-INVALID: error: unknown argument: '-mfunction-return'
+
+// CHECK-KEEP:       "-mfunction-return=keep"
+// CHECK-KEEP-NOT:   "-mfunction-return=thunk-extern"
+// CHECK-EXTERN:     "-mfunction-return=thunk-extern"
+// CHECK-EXTERN-NOT: "-mfunction-return=keep"
Index: clang/test/CodeGen/attr-function-return.cpp
===================================================================
--- /dev/null
+++ clang/test/CodeGen/attr-function-return.cpp
@@ -0,0 +1,52 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu %s -emit-llvm -o - \
+// RUN:   | FileCheck %s --check-prefixes=CHECK,CHECK-NOM
+// RUN: %clang_cc1 -triple x86_64-linux-gnu %s -emit-llvm -o - \
+// RUN:   -mfunction-return=keep | FileCheck %s \
+// RUN:   --check-prefixes=CHECK,CHECK-KEEP
+// RUN: %clang_cc1 -triple x86_64-linux-gnu %s -emit-llvm -o - \
+// RUN:  -mfunction-return=thunk-extern | FileCheck %s \
+// RUN:  --check-prefixes=CHECK,CHECK-EXTERN
+
+int foo(void) {
+  // CHECK: @"_ZZ3foovENK3$_0clEv"({{.*}}) [[NOATTR:#[0-9]+]]
+  return []() {
+    return 42;
+  }();
+}
+int bar(void) {
+  // CHECK: @"_ZZ3barvENK3$_1clEv"({{.*}}) [[EXTERN:#[0-9]+]]
+  return []() __attribute__((function_return("thunk-extern"))) {
+    return 42;
+  }
+  ();
+}
+int baz(void) {
+  // CHECK: @"_ZZ3bazvENK3$_2clEv"({{.*}}) [[KEEP:#[0-9]+]]
+  return []() __attribute__((function_return("keep"))) {
+    return 42;
+  }
+  ();
+}
+
+class Foo {
+public:
+  // CHECK: @_ZN3Foo3fooEv({{.*}}) [[EXTERN]]
+  __attribute__((function_return("thunk-extern"))) int foo() { return 42; }
+};
+
+int quux() {
+  Foo my_foo;
+  return my_foo.foo();
+}
+
+// CHECK: @extern_c() [[EXTERN]]
+extern "C" __attribute__((function_return("thunk-extern"))) void extern_c() {}
+extern "C" {
+// CHECK: @extern_c2() [[EXTERN]]
+__attribute__((function_return("thunk-extern"))) void extern_c2() {}
+}
+
+// CHECK-NOM-NOT:   [[NOATTR]] = {{.*}}fn_ret_thunk_extern
+// CHECK-KEEP-NOT:  [[NOATTR]] = {{.*}}fn_ret_thunk_extern
+// CHECK-KEEP-NOT:  [[KEEP]] = {{.*}}fn_ret_thunk_extern
+// CHECK-EXTERN:    [[EXTERN]] = {{.*}}fn_ret_thunk_extern
Index: clang/test/CodeGen/attr-function-return.c
===================================================================
--- /dev/null
+++ clang/test/CodeGen/attr-function-return.c
@@ -0,0 +1,96 @@
+// RUN: %clang_cc1 -std=gnu2x -triple x86_64-linux-gnu %s -emit-llvm -o - \
+// RUN:   | FileCheck %s --check-prefixes=CHECK,CHECK-NOM
+// RUN: %clang_cc1 -std=gnu2x -triple x86_64-linux-gnu %s -emit-llvm -o - \
+// RUN:   -mfunction-return=keep | FileCheck %s \
+// RUN:   --check-prefixes=CHECK,CHECK-KEEP
+// RUN: %clang_cc1 -std=gnu2x -triple x86_64-linux-gnu %s -emit-llvm -o - \
+// RUN:  -mfunction-return=thunk-extern | FileCheck %s \
+// RUN:  --check-prefixes=CHECK,CHECK-EXTERN
+
+#if !__has_attribute(function_return)
+#error "missing attribute support for function_return"
+#endif
+
+// CHECK: @keep() [[KEEP:#[0-9]+]]
+__attribute__((function_return("keep"))) void keep(void) {}
+
+// CHECK: @keep2() [[KEEP:#[0-9]+]]
+[[gnu::function_return("keep")]] void keep2(void) {}
+
+// CHECK: @thunk_extern() [[EXTERN:#[0-9]+]]
+__attribute__((function_return("thunk-extern"))) void thunk_extern(void) {}
+
+// CHECK: @thunk_extern2() [[EXTERN:#[0-9]+]]
+[[gnu::function_return("thunk-extern")]] void thunk_extern2(void) {}
+
+// CHECK: @double_thunk_keep() [[KEEP]]
+// clang-format off
+__attribute__((function_return("thunk-extern")))
+__attribute__((function_return("keep")))
+void double_thunk_keep(void) {}
+
+// CHECK: @double_thunk_keep2() [[KEEP]]
+[[gnu::function_return("thunk-extern")]][[gnu::function_return("keep")]]
+void double_thunk_keep2(void) {}
+
+// CHECK: @double_keep_thunk() [[EXTERN]]
+__attribute__((function_return("keep")))
+__attribute__((function_return("thunk-extern")))
+void double_keep_thunk(void) {}
+
+// CHECK: @double_keep_thunk2() [[EXTERN]]
+[[gnu::function_return("thunk-keep")]][[gnu::function_return("thunk-extern")]]
+void double_keep_thunk2(void) {}
+
+// CHECK: @thunk_keep() [[KEEP]]
+__attribute__((function_return("thunk-extern"), function_return("keep")))
+void thunk_keep(void) {}
+
+// CHECK: @thunk_keep2() [[KEEP]]
+[[gnu::function_return("thunk-extern"), gnu::function_return("keep")]]
+void thunk_keep2(void) {}
+
+// CHECK: @keep_thunk() [[EXTERN]]
+__attribute__((function_return("keep"), function_return("thunk-extern")))
+void keep_thunk(void) {}
+
+// CHECK: @keep_thunk2() [[EXTERN]]
+[[gnu::function_return("keep"), gnu::function_return("thunk-extern")]]
+void keep_thunk2(void) {}
+// clang-format on
+
+void undef(void);
+// CHECK: @undef() [[KEEP]]
+__attribute__((function_return("keep"))) void undef(void) {}
+
+void undef2(void);
+// CHECK: @undef2() [[EXTERN]]
+__attribute__((function_return("thunk-extern"))) void undef2(void) {}
+
+__attribute__((function_return("thunk-extern"))) void change_def(void);
+// CHECK: @change_def() [[KEEP]]
+__attribute__((function_return("keep"))) void change_def(void) {}
+
+__attribute__((function_return("keep"))) void change_def2(void);
+// CHECK: @change_def2() [[EXTERN]]
+__attribute__((function_return("thunk-extern"))) void change_def2(void) {}
+
+__attribute__((function_return("thunk-extern"))) void change_def3(void);
+// CHECK: @change_def3() [[KEEP]]
+[[gnu::function_return("keep")]] void change_def3(void) {}
+
+[[gnu::function_return("keep")]] void change_def4(void);
+// CHECK: @change_def4() [[EXTERN]]
+__attribute__((function_return("thunk-extern"))) void change_def4(void) {}
+
+// When there is no -mfunction-return= flag set (NOM) or it's set to keep,
+// we don't emit anything into the IR for unattributed functions.
+
+// CHECK-NOM:    @no_attrs() [[NOATTR:#[0-9]+]]
+// CHECK-KEEP:   @no_attrs() [[NOATTR:#[0-9]+]]
+// CHECK-EXTERN: @no_attrs() [[EXTERN]]
+void no_attrs(void) {}
+
+// CHECK-NOM-NOT:  [[NOATTR]] = {{.*}}fn_ret_thunk_extern
+// CHECK-KEEP-NOT: [[NOATTR]] = {{.*}}fn_ret_thunk_extern
+// CHECK: [[EXTERN]] = {{.*}}fn_ret_thunk_extern
Index: clang/lib/Sema/SemaDeclAttr.cpp
===================================================================
--- clang/lib/Sema/SemaDeclAttr.cpp
+++ clang/lib/Sema/SemaDeclAttr.cpp
@@ -8000,6 +8000,26 @@
   D->addAttr(ZeroCallUsedRegsAttr::Create(S.Context, Kind, AL));
 }
 
+static void handleFunctionReturnThunksAttr(Sema &S, Decl *D,
+                                           const ParsedAttr &AL) {
+  StringRef KindStr;
+  SourceLocation LiteralLoc;
+  if (!S.checkStringLiteralArgumentAttr(AL, 0, KindStr, &LiteralLoc))
+    return;
+
+  FunctionReturnThunksAttr::Kind Kind;
+  if (!FunctionReturnThunksAttr::ConvertStrToKind(KindStr, Kind)) {
+    S.Diag(LiteralLoc, diag::warn_attribute_type_not_supported)
+        << AL << KindStr;
+    return;
+  }
+  // FIXME: it would be good to better handle attribute merging rather than
+  // silently replacing the existing attribute, so long as it does not break
+  // the expected codegen tests.
+  D->dropAttr<FunctionReturnThunksAttr>();
+  D->addAttr(FunctionReturnThunksAttr::Create(S.Context, Kind, AL));
+}
+
 static void handleSYCLKernelAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   // The 'sycl_kernel' attribute applies only to function templates.
   const auto *FD = cast<FunctionDecl>(D);
@@ -8866,6 +8886,9 @@
   case ParsedAttr::AT_ZeroCallUsedRegs:
     handleZeroCallUsedRegsAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_FunctionReturnThunks:
+    handleFunctionReturnThunksAttr(S, D, AL);
+    break;
 
   // Microsoft attributes:
   case ParsedAttr::AT_LayoutVersion:
Index: clang/lib/Frontend/CompilerInvocation.cpp
===================================================================
--- clang/lib/Frontend/CompilerInvocation.cpp
+++ clang/lib/Frontend/CompilerInvocation.cpp
@@ -1485,6 +1485,9 @@
   if (Opts.IBTSeal)
     GenerateArg(Args, OPT_mibt_seal, SA);
 
+  if (Opts.FunctionReturnThunks)
+    GenerateArg(Args, OPT_mfunction_return_EQ, "thunk-extern", SA);
+
   for (const auto &F : Opts.LinkBitcodeFiles) {
     bool Builtint = F.LinkFlags == llvm::Linker::Flags::LinkOnlyNeeded &&
                     F.PropagateAttrs && F.Internalize;
@@ -1825,6 +1828,27 @@
       Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << Name;
   }
 
+  if (const Arg *A = Args.getLastArg(OPT_mfunction_return_EQ)) {
+    auto Val = llvm::StringSwitch<llvm::FunctionReturnThunksKind>(A->getValue())
+                   .Case("keep", llvm::FunctionReturnThunksKind::Keep)
+                   .Case("thunk-extern", llvm::FunctionReturnThunksKind::Extern)
+                   .Default(llvm::FunctionReturnThunksKind::Invalid);
+    // SystemZ might want to add support for "expolines."
+    if (!T.isX86())
+      Diags.Report(diag::err_drv_argument_not_allowed_with)
+          << A->getSpelling() << T.getTriple();
+    else if (Val == llvm::FunctionReturnThunksKind::Invalid)
+      Diags.Report(diag::err_drv_invalid_value)
+          << A->getAsString(Args) << A->getValue();
+    else if (Val == llvm::FunctionReturnThunksKind::Extern &&
+             Args.getLastArgValue(OPT_mcmodel_EQ).equals("large"))
+      Diags.Report(diag::err_drv_argument_not_allowed_with)
+          << A->getAsString(Args)
+          << Args.getLastArg(OPT_mcmodel_EQ)->getAsString(Args);
+    else
+      Opts.FunctionReturnThunks = static_cast<unsigned>(Val);
+  }
+
   if (Opts.PrepareForLTO && Args.hasArg(OPT_mibt_seal))
     Opts.IBTSeal = 1;
 
Index: clang/lib/Driver/ToolChains/Clang.cpp
===================================================================
--- clang/lib/Driver/ToolChains/Clang.cpp
+++ clang/lib/Driver/ToolChains/Clang.cpp
@@ -6344,6 +6344,10 @@
   if (IsUsingLTO)
     Args.AddLastArg(CmdArgs, options::OPT_mibt_seal);
 
+  if (Arg *A = Args.getLastArg(options::OPT_mfunction_return_EQ))
+    CmdArgs.push_back(
+        Args.MakeArgString(Twine("-mfunction-return=") + A->getValue()));
+
   // Forward -f options with positive and negative forms; we translate these by
   // hand.  Do not propagate PGO options to the GPU-side compilations as the
   // profile info is for the host-side compilation only.
Index: clang/lib/CodeGen/CodeGenFunction.cpp
===================================================================
--- clang/lib/CodeGen/CodeGenFunction.cpp
+++ clang/lib/CodeGen/CodeGenFunction.cpp
@@ -898,6 +898,20 @@
   if (D && D->hasAttr<NoProfileFunctionAttr>())
     Fn->addFnAttr(llvm::Attribute::NoProfile);
 
+  if (D) {
+    // Function attributes take precedence over command line flags.
+    if (auto *A = D->getAttr<FunctionReturnThunksAttr>()) {
+      switch (A->getThunkType()) {
+      case FunctionReturnThunksAttr::Kind::Keep:
+        break;
+      case FunctionReturnThunksAttr::Kind::Extern:
+        Fn->addFnAttr(llvm::Attribute::FnRetThunkExtern);
+        break;
+      }
+    } else if (CGM.getCodeGenOpts().FunctionReturnThunks)
+      Fn->addFnAttr(llvm::Attribute::FnRetThunkExtern);
+  }
+
   if (FD && (getLangOpts().OpenCL ||
              (getLangOpts().HIP && getLangOpts().CUDAIsDevice))) {
     // Add metadata for a kernel function.
Index: clang/include/clang/Driver/Options.td
===================================================================
--- clang/include/clang/Driver/Options.td
+++ clang/include/clang/Driver/Options.td
@@ -1998,6 +1998,13 @@
   HelpText<"Enable cf-protection in 'full' mode">;
 def mibt_seal : Flag<["-"], "mibt-seal">, Group<m_Group>, Flags<[CoreOption, CC1Option]>,
   HelpText<"Optimize fcf-protection=branch/full (requires LTO).">;
+def mfunction_return_EQ : Joined<["-"], "mfunction-return=">,
+  Group<m_Group>, Flags<[CoreOption, CC1Option]>,
+  HelpText<"Replace returns with jumps to ``__x86_return_thunk`` (x86 only, error otherwise)">,
+  Values<"keep,thunk-extern">,
+  NormalizedValues<["Keep", "Extern"]>,
+  NormalizedValuesScope<"llvm::FunctionReturnThunksKind">,
+  MarshallingInfoEnum<CodeGenOpts<"FunctionReturnThunks">, "Keep">;
 
 defm xray_instrument : BoolFOption<"xray-instrument",
   LangOpts<"XRayInstrument">, DefaultFalse,
Index: clang/include/clang/Basic/CodeGenOptions.def
===================================================================
--- clang/include/clang/Basic/CodeGenOptions.def
+++ clang/include/clang/Basic/CodeGenOptions.def
@@ -107,6 +107,7 @@
 CODEGENOPT(CFProtectionBranch , 1, 0) ///< if -fcf-protection is
                                       ///< set to full or branch.
 CODEGENOPT(IBTSeal, 1, 0)             ///< set to optimize CFProtectionBranch.
+CODEGENOPT(FunctionReturnThunks, 1, 0) ///< -mfunction-return={keep|thunk-extern}
 
 CODEGENOPT(XRayInstrumentFunctions , 1, 0) ///< Set when -fxray-instrument is
                                            ///< enabled.
Index: clang/include/clang/Basic/AttrDocs.td
===================================================================
--- clang/include/clang/Basic/AttrDocs.td
+++ clang/include/clang/Basic/AttrDocs.td
@@ -6606,6 +6606,27 @@
       }
       return 0;
   }
+  }];
+}
+
+def FunctionReturnThunksDocs : Documentation {
+  let Category = DocCatFunction;
+  let Content = [{
+The attribute ``function_return`` can replace return instructions with jumps to
+target-specific symbols. This attribute supports 2 possible values,
+corresponding to the values supported by the ``-mfunction-return=`` command
+line flag:
+* ``__attribute__((function_return("keep")))`` to disable related transforms.
+  This is useful for undoing global setting from ``-mfunction-return=`` locally
+  for individual functions.
+* ``__attribute__((function_return("thunk-extern")))`` to replace returns with
+  jumps, while NOT emitting the thunk.
+
+The values ``thunk`` and ``thunk-inline`` from GCC are not supported.
+
+The symbol used for ``thunk-extern`` is target specific:
+* X86: ``__x86_return_thunk``
 
+As such, this function attribute is currently only supported on X86 targets.
   }];
 }
Index: clang/include/clang/Basic/Attr.td
===================================================================
--- clang/include/clang/Basic/Attr.td
+++ clang/include/clang/Basic/Attr.td
@@ -4036,3 +4036,14 @@
   let LangOpts = [COnly];
 }
 def : MutualExclusions<[RandomizeLayout, NoRandomizeLayout]>;
+
+def FunctionReturnThunks : InheritableAttr,
+    TargetSpecificAttr<TargetAnyX86> {
+  let Spellings = [GCC<"function_return">];
+  let Args = [EnumArgument<"ThunkType", "Kind",
+    ["keep", "thunk-extern"],
+    ["Keep", "Extern"]
+  >];
+  let Subjects = SubjectList<[Function]>;
+  let Documentation = [FunctionReturnThunksDocs];
+}
Index: clang/docs/ReleaseNotes.rst
===================================================================
--- clang/docs/ReleaseNotes.rst
+++ clang/docs/ReleaseNotes.rst
@@ -404,6 +404,12 @@
   format string must correctly format the fixed parameter types of the function.
   Using the attribute this way emits a GCC compatibility diagnostic.
 
+- Support was added for ``__attribute__((function_return("thunk-extern")))``
+  to X86 to replace ``ret`` instructions with ``jmp __x86_return_thunk``. The
+  corresponding attribute to disable this,
+  ``__attribute__((function_return("keep")))`` was added. This is intended to
+  be used by the Linux kernel to mitigate RETBLEED.
+
 Windows Support
 ---------------
 
@@ -556,6 +562,9 @@
   this instruction (see rdpruintrin.h).
 - Support ``-mstack-protector-guard-symbol=[SymbolName]`` to use the given
   symbol for addressing the stack protector guard.
+- ``-mfunction-return=thunk-extern`` support was added to clang for x86. This
+  will be used by Linux kernel mitigations for RETBLEED. The corresponding flag
+  ``-mfunction-return=keep`` may be appended to disable the feature.
 
 DWARF Support in Clang
 ----------------------
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to