Skip to content
Closed
6 changes: 6 additions & 0 deletions clang/include/clang/AST/DeclObjC.h
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,12 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext {
/// True if the method is tagged as objc_direct
bool isDirectMethod() const;

// Only direct instance method that have a fixed number of arguments can have
// nil check thunk functions.
bool canHaveNilCheckThunk() const {
return isDirectMethod() && isInstanceMethod() && !isVariadic();
}

/// True if the method has a parameter that's destroyed in the callee.
bool hasParamDestroyedInCallee() const;

Expand Down
10 changes: 9 additions & 1 deletion clang/include/clang/AST/Mangle.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ struct ThisAdjustment;
struct ThunkInfo;
class VarDecl;

/// Extract mangling function name from MangleContext such that swift can call
/// it to prepare for ObjCDirect in swift.
void mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
bool isInstanceMethod, StringRef ClassName,
std::optional<StringRef> CategoryName,
StringRef MethodName, bool isNilCheckThunk);

/// MangleContext - Context for tracking state which persists across multiple
/// calls to the C++ name mangler.
class MangleContext {
Expand Down Expand Up @@ -153,7 +160,8 @@ class MangleContext {

void mangleObjCMethodName(const ObjCMethodDecl *MD, raw_ostream &OS,
bool includePrefixByte = true,
bool includeCategoryNamespace = true) const;
bool includeCategoryNamespace = true,
bool hasNilCheck = true) const;
void mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD,
raw_ostream &) const;

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/CodeGenOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ CODEGENOPT(EmitLLVMUseLists, 1, 0) ///< Control whether to serialize use-lists.

CODEGENOPT(WholeProgramVTables, 1, 0) ///< Whether to apply whole-program
/// vtable optimization.
CODEGENOPT(ObjCEmitNilCheckThunk , 1, 0) ///< Whether objc_direct methods should emit a nil check thunk.

CODEGENOPT(VirtualFunctionElimination, 1, 0) ///< Whether to apply the dead
/// virtual function elimination
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/LangOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,7 @@ LANGOPT(IncludeDefaultHeader, 1, 0, "Include default header file for OpenCL")
LANGOPT(DeclareOpenCLBuiltins, 1, 0, "Declare OpenCL builtin functions")
BENIGN_LANGOPT(DelayedTemplateParsing , 1, 0, "delayed template parsing")
LANGOPT(BlocksRuntimeOptional , 1, 0, "optional blocks runtime")
LANGOPT(ObjCEmitNilCheckThunk, 1, 0, "Emit a thunk to do nil check for objc direct methods")
LANGOPT(
CompleteMemberPointers, 1, 0,
"Require member pointer base types to be complete at the point where the "
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -3049,6 +3049,8 @@ def fthin_link_bitcode_EQ : Joined<["-"], "fthin-link-bitcode=">,
Visibility<[ClangOption, CLOption, CC1Option]>, Group<f_Group>,
HelpText<"Write minimized bitcode to <file> for the ThinLTO thin link only">,
MarshallingInfoString<CodeGenOpts<"ThinLinkBitcodeFile">>;
def fobjc_emit_nil_check_thunk:
Flag<["-"], "fobjc-emit-nil-check-thunk">, Visibility<[ClangOption, CC1Option]>, Group<f_Group>;
defm fat_lto_objects : BoolFOption<"fat-lto-objects",
CodeGenOpts<"FatLTO">, DefaultFalse,
PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable">,
Expand Down
41 changes: 30 additions & 11 deletions clang/lib/AST/Mangle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@

using namespace clang;

void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
bool isInstanceMethod, StringRef ClassName,
std::optional<StringRef> CategoryName,
StringRef MethodName, bool hasNilCheck) {
// \01+[ContainerName(CategoryName) SelectorName]
if (includePrefixByte)
OS << "\01";
OS << (isInstanceMethod ? '-' : '+');
OS << '[';
OS << ClassName;
if (CategoryName)
OS << "(" << *CategoryName << ")";
OS << " ";
OS << MethodName;
OS << ']';
if (!hasNilCheck)
OS << "_nonnull";
}
// FIXME: For blocks we currently mimic GCC's mangling scheme, which leaves
// much to be desired. Come up with a better mangling scheme.

Expand Down Expand Up @@ -328,7 +346,8 @@ void MangleContext::mangleBlock(const DeclContext *DC, const BlockDecl *BD,
void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD,
raw_ostream &OS,
bool includePrefixByte,
bool includeCategoryNamespace) const {
bool includeCategoryNamespace,
bool hasNilCheck) const {
if (getASTContext().getLangOpts().ObjCRuntime.isGNUFamily()) {
// This is the mangling we've always used on the GNU runtimes, but it
// has obvious collisions in the face of underscores within class
Expand Down Expand Up @@ -362,26 +381,26 @@ void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD,
}

// \01+[ContainerName(CategoryName) SelectorName]
if (includePrefixByte) {
OS << '\01';
}
OS << (MD->isInstanceMethod() ? '-' : '+') << '[';
auto CategoryName = std::optional<StringRef>();
StringRef ClassName = "";
if (const auto *CID = MD->getCategory()) {
if (const auto *CI = CID->getClassInterface()) {
OS << CI->getName();
ClassName = CI->getName();
if (includeCategoryNamespace) {
OS << '(' << *CID << ')';
CategoryName = CID->getName();
}
}
} else if (const auto *CD =
dyn_cast<ObjCContainerDecl>(MD->getDeclContext())) {
OS << CD->getName();
ClassName = CD->getName();
} else {
llvm_unreachable("Unexpected ObjC method decl context");
}
OS << ' ';
MD->getSelector().print(OS);
OS << ']';
std::string MethodName;
llvm::raw_string_ostream MethodNameOS(MethodName);
MD->getSelector().print(MethodNameOS);
clang::mangleObjCMethodName(OS, includePrefixByte, MD->isInstanceMethod(),
ClassName, CategoryName, MethodName, hasNilCheck);
}

void MangleContext::mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD,
Expand Down
19 changes: 18 additions & 1 deletion clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2771,6 +2771,17 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,

ArgAttrs[IRArgs.first] = llvm::AttributeSet::get(getLLVMContext(), Attrs);
}
// Direct method prologue should not contain nil check anymore.
// As a result, we can set `self` to be NonNull to prepare for further
// optimizations.
if (TargetDecl) {
auto OMD = dyn_cast<ObjCMethodDecl>(TargetDecl);
if (shouldHaveNilCheckThunk(OMD) && !IsThunk) {
auto IRArgs = IRFunctionArgs.getIRArgs(0);
ArgAttrs[IRArgs.first] = ArgAttrs[IRArgs.first].addAttribute(
getLLVMContext(), llvm::Attribute::NonNull);
}
}

unsigned ArgNo = 0;
for (CGFunctionInfo::const_arg_iterator I = FI.arg_begin(),
Expand Down Expand Up @@ -3466,7 +3477,13 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI,
}
}

if (getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
if (InnerFn) {
// Don't emit any arg except for `self` if we are in a thunk function.
// We still need self for nil check, other arguments aren't used in this
// function and thus are not needed. Avoid emitting them also prevents
// accidental release/retain.
EmitParmDecl(*Args[0], ArgVals[0], 1);
} else if (getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
for (int I = Args.size() - 1; I >= 0; --I)
EmitParmDecl(*Args[I], ArgVals[I], I + 1);
} else {
Expand Down
55 changes: 49 additions & 6 deletions clang/lib/CodeGen/CGObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -756,12 +756,13 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
if (OMD->hasAttr<NoDebugAttr>())
DebugInfo = nullptr; // disable debug info indefinitely for this function

llvm::Function *Fn = CGM.getObjCRuntime().GenerateMethod(OMD, CD);
bool isInner = CGM.shouldHaveNilCheckThunk(OMD) && !InnerFn;
llvm::Function *Fn = CGM.getObjCRuntime().GenerateMethod(OMD, CD, !isInner);

const CGFunctionInfo &FI = CGM.getTypes().arrangeObjCMethodDeclaration(OMD);
if (OMD->isDirectMethod()) {
Fn->setVisibility(llvm::Function::HiddenVisibility);
CGM.SetLLVMFunctionAttributes(OMD, FI, Fn, /*IsThunk=*/false);
CGM.SetLLVMFunctionAttributes(OMD, FI, Fn, /*IsThunk=*/InnerFn);
CGM.SetLLVMFunctionAttributesForDefinition(OMD, Fn);
} else {
CGM.SetInternalFunctionAttributes(OMD, Fn, FI);
Expand All @@ -780,10 +781,14 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
OMD->getLocation(), StartLoc);

if (OMD->isDirectMethod()) {
// This function is a direct call, it has to implement a nil check
// on entry.
//
// TODO: possibly have several entry points to elide the check
// Having `InnerFn` indicates that we are generating a nil check thunk.
// In that case our job is done here.
if (InnerFn)
return;
// Only NeXTFamily can have nil check thunk.
if (CGM.shouldHaveNilCheckThunk(OMD))
// Go generate a nil check thunk around `Fn`
CodeGenFunction(CGM, /*InnerFn=*/Fn).GenerateObjCDirectThunk(OMD, CD);
CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
}

Expand Down Expand Up @@ -1637,6 +1642,44 @@ void CodeGenFunction::GenerateObjCSetter(ObjCImplementationDecl *IMP,
FinishFunction(OMD->getEndLoc());
}

void CodeGenFunction::GenerateObjCDirectThunk(const ObjCMethodDecl *OMD,
const ObjCContainerDecl *CD) {
assert(InnerFn && CGM.shouldHaveNilCheckThunk(OMD) &&
"Should only generate wrapper when the flag is set.");
StartObjCMethod(OMD, CD);

// Manually pop all the clean up that doesn't need to happen in the outer
// function. InnerFn will do this for us.
while (EHStack.stable_begin() != PrologueCleanupDepth)
EHStack.popCleanup();

// Generate a nil check.
CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, CurFn, OMD, CD);
// Call the InnerFn and pass the return value
SmallVector<llvm::Value *> Args(CurFn->arg_size());
std::transform(CurFn->arg_begin(), CurFn->arg_end(), Args.begin(),
[](llvm::Argument &arg) { return &arg; });

// This will be optimized into a tail call.
auto *CallInst = EmitCallOrInvoke(InnerFn, Args);
// Preserve the inner function's attributes to the call instruction.
CallInst->setAttributes(InnerFn->getAttributes());
llvm::Value *RetVal = CallInst;

// If `AutoreleaseResult` is set, the return value is not void.
if (AutoreleaseResult)
RetVal = EmitARCRetainAutoreleasedReturnValue(RetVal);

// This excessive store is totally unnecessary.
// But `FinishFunction` really wants us to store the result so it can
// clean up the function properly.
// The unnecessary store-load of the ret value will be optimized out anyway.
if (!CurFn->getReturnType()->isVoidTy())
Builder.CreateStore(RetVal, ReturnValue);

// Nil check's end location is the function's start location.
FinishFunction(OMD->getBeginLoc());
}
namespace {
struct DestroyIvar final : EHScopeStack::Cleanup {
private:
Expand Down
8 changes: 7 additions & 1 deletion clang/lib/CodeGen/CGObjCGNU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,13 @@ class CGObjCGNU : public CGObjCRuntime {
llvm::Constant *GetEHType(QualType T) override;

llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD,
const ObjCContainerDecl *CD) override;
const ObjCContainerDecl *CD,
bool isThunk) override {
// isThunk is irrelevent for GNU.
return GenerateMethod(OMD, CD);
};
llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD,
const ObjCContainerDecl *CD);

// Map to unify direct method definitions.
llvm::DenseMap<const ObjCMethodDecl *, llvm::Function *>
Expand Down
Loading