Skip to content

[ObjCDirect] Move nil check to a thunk function #126639

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
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