Skip to content

Commit e013868

Browse files
committed
[ObjCDirect] Move nil check to a thunk function
Signed-off-by: Peter Rong <[email protected]>
1 parent 2207e3e commit e013868

24 files changed

+1122
-49
lines changed

clang/include/clang/AST/DeclObjC.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,12 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext {
482482
/// True if the method is tagged as objc_direct
483483
bool isDirectMethod() const;
484484

485+
// Only direct instance method that have a fixed number of arguments can have
486+
// nil check thunk functions.
487+
bool canHaveNilCheckThunk() const {
488+
return isDirectMethod() && isInstanceMethod() && !isVariadic();
489+
}
490+
485491
/// True if the method has a parameter that's destroyed in the callee.
486492
bool hasParamDestroyedInCallee() const;
487493

clang/include/clang/AST/Mangle.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ struct ThisAdjustment;
4040
struct ThunkInfo;
4141
class VarDecl;
4242

43+
/// Extract mangling function name from MangleContext such that swift can call
44+
/// it to prepare for ObjCDirect in swift.
45+
void mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
46+
bool isInstanceMethod, StringRef ClassName,
47+
std::optional<StringRef> CategoryName,
48+
StringRef MethodName, bool isThunk);
49+
4350
/// MangleContext - Context for tracking state which persists across multiple
4451
/// calls to the C++ name mangler.
4552
class MangleContext {
@@ -153,7 +160,8 @@ class MangleContext {
153160

154161
void mangleObjCMethodName(const ObjCMethodDecl *MD, raw_ostream &OS,
155162
bool includePrefixByte = true,
156-
bool includeCategoryNamespace = true);
163+
bool includeCategoryNamespace = true,
164+
bool isThunk = true);
157165
void mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD,
158166
raw_ostream &);
159167

clang/include/clang/Basic/CodeGenOptions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ CODEGENOPT(EmitLLVMUseLists, 1, 0) ///< Control whether to serialize use-lists.
359359

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

363364
CODEGENOPT(VirtualFunctionElimination, 1, 0) ///< Whether to apply the dead
364365
/// virtual function elimination

clang/include/clang/Basic/LangOptions.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ LANGOPT(IncludeDefaultHeader, 1, 0, "Include default header file for OpenCL")
372372
LANGOPT(DeclareOpenCLBuiltins, 1, 0, "Declare OpenCL builtin functions")
373373
BENIGN_LANGOPT(DelayedTemplateParsing , 1, 0, "delayed template parsing")
374374
LANGOPT(BlocksRuntimeOptional , 1, 0, "optional blocks runtime")
375+
LANGOPT(ObjCEmitNilCheckThunk, 1, 0, "Emit a thunk to do nil check for objc direct methods")
375376
LANGOPT(
376377
CompleteMemberPointers, 1, 0,
377378
"Require member pointer base types to be complete at the point where the "

clang/include/clang/Driver/Options.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3001,6 +3001,8 @@ def fthin_link_bitcode_EQ : Joined<["-"], "fthin-link-bitcode=">,
30013001
Visibility<[ClangOption, CLOption, CC1Option]>, Group<f_Group>,
30023002
HelpText<"Write minimized bitcode to <file> for the ThinLTO thin link only">,
30033003
MarshallingInfoString<CodeGenOpts<"ThinLinkBitcodeFile">>;
3004+
def fobjc_emit_nil_check_thunk:
3005+
Flag<["-"], "fobjc-emit-nil-check-thunk">, Visibility<[ClangOption, CC1Option]>, Group<f_Group>;
30043006
defm fat_lto_objects : BoolFOption<"fat-lto-objects",
30053007
CodeGenOpts<"FatLTO">, DefaultFalse,
30063008
PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable">,

clang/lib/AST/Mangle.cpp

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,24 @@
2929

3030
using namespace clang;
3131

32+
void clang::mangleObjCMethodName(raw_ostream &OS, bool includePrefixByte,
33+
bool isInstanceMethod, StringRef ClassName,
34+
std::optional<StringRef> CategoryName,
35+
StringRef MethodName, bool isThunk) {
36+
// \01+[ContainerName(CategoryName) SelectorName]
37+
if (includePrefixByte)
38+
OS << "\01";
39+
OS << (isInstanceMethod ? '-' : '+');
40+
OS << '[';
41+
OS << ClassName;
42+
if (CategoryName)
43+
OS << "(" << CategoryName.value() << ")";
44+
OS << " ";
45+
OS << MethodName;
46+
OS << ']';
47+
if (!isThunk)
48+
OS << "_inner";
49+
}
3250
// FIXME: For blocks we currently mimic GCC's mangling scheme, which leaves
3351
// much to be desired. Come up with a better mangling scheme.
3452

@@ -327,7 +345,8 @@ void MangleContext::mangleBlock(const DeclContext *DC, const BlockDecl *BD,
327345
void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD,
328346
raw_ostream &OS,
329347
bool includePrefixByte,
330-
bool includeCategoryNamespace) {
348+
bool includeCategoryNamespace,
349+
bool isThunk) {
331350
if (getASTContext().getLangOpts().ObjCRuntime.isGNUFamily()) {
332351
// This is the mangling we've always used on the GNU runtimes, but it
333352
// has obvious collisions in the face of underscores within class
@@ -361,24 +380,24 @@ void MangleContext::mangleObjCMethodName(const ObjCMethodDecl *MD,
361380
}
362381

363382
// \01+[ContainerName(CategoryName) SelectorName]
364-
if (includePrefixByte) {
365-
OS << '\01';
366-
}
367-
OS << (MD->isInstanceMethod() ? '-' : '+') << '[';
383+
auto CategoryName = std::optional<StringRef>();
384+
StringRef ClassName;
368385
if (const auto *CID = MD->getCategory()) {
369-
OS << CID->getClassInterface()->getName();
370-
if (includeCategoryNamespace) {
371-
OS << '(' << *CID << ')';
372-
}
386+
if (includeCategoryNamespace)
387+
CategoryName = CID->getName();
388+
ClassName = CID->getClassInterface()->getName();
389+
373390
} else if (const auto *CD =
374391
dyn_cast<ObjCContainerDecl>(MD->getDeclContext())) {
375-
OS << CD->getName();
392+
ClassName = CD->getName();
376393
} else {
377394
llvm_unreachable("Unexpected ObjC method decl context");
378395
}
379-
OS << ' ';
380-
MD->getSelector().print(OS);
381-
OS << ']';
396+
std::string MethodName;
397+
llvm::raw_string_ostream MethodNameOS(MethodName);
398+
MD->getSelector().print(MethodNameOS);
399+
clang::mangleObjCMethodName(OS, includePrefixByte, MD->isInstanceMethod(),
400+
ClassName, CategoryName, MethodName, isThunk);
382401
}
383402

384403
void MangleContext::mangleObjCMethodNameAsSourceName(const ObjCMethodDecl *MD,

clang/lib/CodeGen/CGCall.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2702,6 +2702,18 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
27022702

27032703
ArgAttrs[IRArgs.first] = llvm::AttributeSet::get(getLLVMContext(), Attrs);
27042704
}
2705+
// Direct method prologue should not contain nil check anymore.
2706+
// As a result, we can set `self` to be NonNull to prepare for further
2707+
// optimizations.
2708+
if (getLangOpts().ObjCEmitNilCheckThunk && TargetDecl) {
2709+
auto OMD = dyn_cast<ObjCMethodDecl>(TargetDecl);
2710+
bool isDirect = OMD && OMD->isDirectMethod();
2711+
if (isDirect && !IsThunk) {
2712+
auto IRArgs = IRFunctionArgs.getIRArgs(0);
2713+
ArgAttrs[IRArgs.first] = ArgAttrs[IRArgs.first].addAttribute(
2714+
getLLVMContext(), llvm::Attribute::NonNull);
2715+
}
2716+
}
27052717

27062718
unsigned ArgNo = 0;
27072719
for (CGFunctionInfo::const_arg_iterator I = FI.arg_begin(),
@@ -3386,7 +3398,13 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI,
33863398
}
33873399
}
33883400

3389-
if (getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
3401+
if (InnerFn) {
3402+
// Don't emit any arg except for `self` if we are in a thunk function.
3403+
// We still need self for nil check, other arguments aren't used in this
3404+
// function and thus is not needed. Avoid emitting them also prevents
3405+
// accidental release/retain.
3406+
EmitParmDecl(*Args[0], ArgVals[0], 1);
3407+
} else if (getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
33903408
for (int I = Args.size() - 1; I >= 0; --I)
33913409
EmitParmDecl(*Args[I], ArgVals[I], I + 1);
33923410
} else {

clang/lib/CodeGen/CGObjC.cpp

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -756,12 +756,13 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
756756
if (OMD->hasAttr<NoDebugAttr>())
757757
DebugInfo = nullptr; // disable debug info indefinitely for this function
758758

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

761762
const CGFunctionInfo &FI = CGM.getTypes().arrangeObjCMethodDeclaration(OMD);
762763
if (OMD->isDirectMethod()) {
763764
Fn->setVisibility(llvm::Function::HiddenVisibility);
764-
CGM.SetLLVMFunctionAttributes(OMD, FI, Fn, /*IsThunk=*/false);
765+
CGM.SetLLVMFunctionAttributes(OMD, FI, Fn, /*IsThunk=*/InnerFn);
765766
CGM.SetLLVMFunctionAttributesForDefinition(OMD, Fn);
766767
} else {
767768
CGM.SetInternalFunctionAttributes(OMD, Fn, FI);
@@ -780,11 +781,21 @@ void CodeGenFunction::StartObjCMethod(const ObjCMethodDecl *OMD,
780781
OMD->getLocation(), StartLoc);
781782

782783
if (OMD->isDirectMethod()) {
783-
// This function is a direct call, it has to implement a nil check
784-
// on entry.
785-
//
786-
// TODO: possibly have several entry points to elide the check
787-
CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
784+
if (CGM.getLangOpts().ObjCRuntime.isNeXTFamily()) {
785+
// Having `InnerFn` indicates that we are generating a nil check thunk.
786+
// In that case our job is done here.
787+
if (InnerFn)
788+
return;
789+
if (CGM.shouldHaveNilCheckThunk(OMD))
790+
// Go generate a nil check thunk around `Fn`
791+
CodeGenFunction(CGM, /*InnerFn=*/Fn).GenerateObjCDirectThunk(OMD, CD);
792+
else
793+
CGM.getObjCRuntime().GenerateObjCDirectNilCheck(*this, OMD, CD);
794+
CGM.getObjCRuntime().GenerateCmdIfNecessary(*this, OMD);
795+
} else {
796+
// For GNU family, since GNU Step2 also supports direct methods now.
797+
CGM.getObjCRuntime().GenerateDirectMethodPrologue(*this, Fn, OMD, CD);
798+
}
788799
}
789800

790801
// In ARC, certain methods get an extra cleanup.
@@ -1637,6 +1648,44 @@ void CodeGenFunction::GenerateObjCSetter(ObjCImplementationDecl *IMP,
16371648
FinishFunction(OMD->getEndLoc());
16381649
}
16391650

1651+
void CodeGenFunction::GenerateObjCDirectThunk(const ObjCMethodDecl *OMD,
1652+
const ObjCContainerDecl *CD) {
1653+
assert(InnerFn && CGM.shouldHaveNilCheckThunk(OMD) &&
1654+
"Should only generate wrapper when the flag is set.");
1655+
StartObjCMethod(OMD, CD);
1656+
1657+
// Manually pop all the clean up that doesn't need to happen in the outer
1658+
// function. InnerFn will do this for us.
1659+
while (EHStack.stable_begin() != PrologueCleanupDepth)
1660+
EHStack.popCleanup();
1661+
1662+
// Generate a nil check.
1663+
CGM.getObjCRuntime().GenerateObjCDirectNilCheck(*this, OMD, CD);
1664+
// Call the InnerFn and pass the return value
1665+
SmallVector<llvm::Value *> Args(CurFn->arg_size());
1666+
std::transform(CurFn->arg_begin(), CurFn->arg_end(), Args.begin(),
1667+
[](llvm::Argument &arg) { return &arg; });
1668+
1669+
// This will be optimized into a tail call.
1670+
auto *CallInst = EmitCallOrInvoke(InnerFn, Args);
1671+
// Preserve the inner function's attributes to the call instruction.
1672+
CallInst->setAttributes(InnerFn->getAttributes());
1673+
llvm::Value *RetVal = CallInst;
1674+
1675+
// If `AutoreleaseResult` is set, the return value is not void.
1676+
if (AutoreleaseResult)
1677+
RetVal = EmitARCRetainAutoreleasedReturnValue(RetVal);
1678+
1679+
// This excessive store is totally unnecessary.
1680+
// But `FinishFunction` really wants us to store the result so it can
1681+
// clean up the function properly.
1682+
// The unnecessary store-load of the ret value will be optimized out anyway.
1683+
if (!CurFn->getReturnType()->isVoidTy())
1684+
Builder.CreateStore(RetVal, ReturnValue);
1685+
1686+
// Nil check's end location is the function's start location.
1687+
FinishFunction(OMD->getBeginLoc());
1688+
}
16401689
namespace {
16411690
struct DestroyIvar final : EHScopeStack::Cleanup {
16421691
private:

clang/lib/CodeGen/CGObjCGNU.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,8 +594,23 @@ class CGObjCGNU : public CGObjCRuntime {
594594
}
595595
llvm::Constant *GetEHType(QualType T) override;
596596

597+
void GenerateObjCDirectNilCheck(CodeGenFunction &CGF,
598+
const ObjCMethodDecl *OMD,
599+
const ObjCContainerDecl *CD) override {
600+
// GNU runtime doesn't support nil check thunks at this time
601+
};
602+
void GenerateCmdIfNecessary(CodeGenFunction &CGF,
603+
const ObjCMethodDecl *OMD) override {
604+
// GNU runtime doesn't support nil check thunks at this time
605+
}
606+
llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD,
607+
const ObjCContainerDecl *CD,
608+
bool isThunk) override {
609+
// isThunk is irrelevent for GNU.
610+
return GenerateMethod(OMD, CD);
611+
};
597612
llvm::Function *GenerateMethod(const ObjCMethodDecl *OMD,
598-
const ObjCContainerDecl *CD) override;
613+
const ObjCContainerDecl *CD);
599614

600615
// Map to unify direct method definitions.
601616
llvm::DenseMap<const ObjCMethodDecl *, llvm::Function *>

0 commit comments

Comments
 (0)