Skip to content

Commit 2686c50

Browse files
mysterymathkcloudy0717
authored andcommitted
[clang] "modular_format" attribute for functions using format strings (llvm#147431)
This provides a C language `modular_format` attribute. This combines with information from the existing `format` to set the new IR `modular-format` attribute. The purpose of these attributes is to enable "modular printf". A statically linked libc can provide a modular variant of printf that only weakly references implementation routines. The link-time symbol `printf` would strongly reference aspect symbols (e.g. for float, fixed point, etc.) that are provided by those routines, restoring the status quo. However, the compiler could transform calls with constant format strings to calls to the modular printf instead, and at the same time, it would emit strong references to the aspect symbols that are needed to implement the format string. Then, the printf implementation would contain only the union of the aspects requested. See issue llvm#146159 for context.
1 parent 05dcfcf commit 2686c50

File tree

11 files changed

+236
-0
lines changed

11 files changed

+236
-0
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,12 @@ Attribute Changes in Clang
360360
attribute, but `malloc_span` applies not to functions returning pointers, but to functions returning
361361
span-like structures (i.e. those that contain a pointer field and a size integer field or two pointers).
362362

363+
- Added new attribute ``modular_format`` to allow dynamically selecting at link
364+
time which aspects of a statically linked libc's printf (et al)
365+
implementation are required. This can reduce code size without requiring e.g.
366+
multilibs for printf features. Requires cooperation with the libc
367+
implementation.
368+
363369
Improvements to Clang's diagnostics
364370
-----------------------------------
365371
- Diagnostics messages now refer to ``structured binding`` instead of ``decomposition``,

clang/include/clang/Basic/Attr.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5331,3 +5331,11 @@ def NonString : InheritableAttr {
53315331
let Subjects = SubjectList<[Var, Field]>;
53325332
let Documentation = [NonStringDocs];
53335333
}
5334+
5335+
def ModularFormat : InheritableAttr {
5336+
let Spellings = [Clang<"modular_format">];
5337+
let Args = [IdentifierArgument<"ModularImplFn">, StringArgument<"ImplName">,
5338+
VariadicStringArgument<"Aspects">];
5339+
let Subjects = SubjectList<[Function]>;
5340+
let Documentation = [ModularFormatDocs];
5341+
}

clang/include/clang/Basic/AttrDocs.td

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9642,3 +9642,43 @@ silence diagnostics with code like:
96429642
__attribute__((nonstring)) char NotAStr[3] = "foo"; // Not diagnosed
96439643
}];
96449644
}
9645+
9646+
def ModularFormatDocs : Documentation {
9647+
let Category = DocCatFunction;
9648+
let Content = [{
9649+
The ``modular_format`` attribute can be applied to a function that bears the
9650+
``format`` attribute (or standard library functions) to indicate that the
9651+
implementation is "modular", that is, that the implementation is logically
9652+
divided into a number of named aspects. When the compiler can determine that
9653+
not all aspects of the implementation are needed for a given call, the compiler
9654+
may redirect the call to the identifier given as the first argument to the
9655+
attribute (the modular implementation function).
9656+
9657+
The second argument is a implementation name, and the remaining arguments are
9658+
aspects of the format string for the compiler to report. The implementation
9659+
name is an unevaluated identifier be in the C namespace.
9660+
9661+
The compiler reports that a call requires an aspect by issuing a relocation for
9662+
the symbol ``<impl_name>_<aspect>`` at the point of the call. This arranges for
9663+
code and data needed to support the aspect of the implementation to be brought
9664+
into the link to satisfy weak references in the modular implemenation function.
9665+
If the compiler does not understand an aspect, it must summarily consider any
9666+
call to require that aspect.
9667+
9668+
For example, say ``printf`` is annotated with
9669+
``modular_format(__modular_printf, "__printf", "float")``. Then, a call to
9670+
``printf(var, 42)`` would be untouched. A call to ``printf("%d", 42)`` would
9671+
become a call to ``__modular_printf`` with the same arguments, as would
9672+
``printf("%f", 42.0)``. The latter would be accompanied with a strong
9673+
relocation against the symbol ``__printf_float``, which would bring floating
9674+
point support for ``printf`` into the link.
9675+
9676+
If the attribute appears more than once on a declaration, or across a chain of
9677+
redeclarations, it is an error for the attributes to have different arguments,
9678+
excepting that the aspects may be in any order.
9679+
9680+
The following aspects are currently supported:
9681+
9682+
- ``float``: The call has a floating point argument
9683+
}];
9684+
}

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11277,6 +11277,8 @@ def warn_duplicate_attribute_exact : Warning<
1127711277
def warn_duplicate_attribute : Warning<
1127811278
"attribute %0 is already applied with different arguments">,
1127911279
InGroup<IgnoredAttributes>;
11280+
def err_duplicate_attribute
11281+
: Error<"attribute %0 is already applied with different arguments">;
1128011282
def err_disallowed_duplicate_attribute : Error<
1128111283
"attribute %0 cannot appear more than once on a declaration">;
1128211284

@@ -13070,6 +13072,12 @@ def err_get_vtable_pointer_requires_complete_type
1307013072
: Error<"__builtin_get_vtable_pointer requires an argument with a complete "
1307113073
"type, but %0 is incomplete">;
1307213074

13075+
def err_modular_format_attribute_no_format
13076+
: Error<"'modular_format' attribute requires 'format' attribute">;
13077+
13078+
def err_modular_format_duplicate_aspect
13079+
: Error<"duplicate aspect '%0' in 'modular_format' attribute">;
13080+
1307313081
// SYCL-specific diagnostics
1307413082
def warn_sycl_kernel_num_of_template_params : Warning<
1307513083
"'sycl_kernel' attribute only applies to a function template with at least"

clang/include/clang/Sema/Sema.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4957,6 +4957,11 @@ class Sema final : public SemaBase {
49574957
IdentifierInfo *Format,
49584958
int FormatIdx,
49594959
StringLiteral *FormatStr);
4960+
ModularFormatAttr *mergeModularFormatAttr(Decl *D,
4961+
const AttributeCommonInfo &CI,
4962+
IdentifierInfo *ModularImplFn,
4963+
StringRef ImplName,
4964+
MutableArrayRef<StringRef> Aspects);
49604965

49614966
/// AddAlignedAttr - Adds an aligned attribute to a particular declaration.
49624967
void AddAlignedAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E,

clang/lib/CodeGen/CGCall.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2559,6 +2559,19 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
25592559

25602560
if (TargetDecl->hasAttr<ArmLocallyStreamingAttr>())
25612561
FuncAttrs.addAttribute("aarch64_pstate_sm_body");
2562+
2563+
if (auto *ModularFormat = TargetDecl->getAttr<ModularFormatAttr>()) {
2564+
FormatAttr *Format = TargetDecl->getAttr<FormatAttr>();
2565+
StringRef Type = Format->getType()->getName();
2566+
std::string FormatIdx = std::to_string(Format->getFormatIdx());
2567+
std::string FirstArg = std::to_string(Format->getFirstArg());
2568+
SmallVector<StringRef> Args = {
2569+
Type, FormatIdx, FirstArg,
2570+
ModularFormat->getModularImplFn()->getName(),
2571+
ModularFormat->getImplName()};
2572+
llvm::append_range(Args, ModularFormat->aspects());
2573+
FuncAttrs.addAttribute("modular-format", llvm::join(Args, ","));
2574+
}
25622575
}
25632576

25642577
// Attach "no-builtins" attributes to:

clang/lib/Sema/SemaDecl.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
#include "clang/Sema/SemaSwift.h"
5959
#include "clang/Sema/SemaWasm.h"
6060
#include "clang/Sema/Template.h"
61+
#include "llvm/ADT/ArrayRef.h"
6162
#include "llvm/ADT/STLForwardCompat.h"
6263
#include "llvm/ADT/ScopeExit.h"
6364
#include "llvm/ADT/SmallPtrSet.h"
@@ -2901,6 +2902,10 @@ static bool mergeDeclAttribute(Sema &S, NamedDecl *D,
29012902
else if (const auto *FMA = dyn_cast<FormatMatchesAttr>(Attr))
29022903
NewAttr = S.mergeFormatMatchesAttr(
29032904
D, *FMA, FMA->getType(), FMA->getFormatIdx(), FMA->getFormatString());
2905+
else if (const auto *MFA = dyn_cast<ModularFormatAttr>(Attr))
2906+
NewAttr = S.mergeModularFormatAttr(
2907+
D, *MFA, MFA->getModularImplFn(), MFA->getImplName(),
2908+
MutableArrayRef<StringRef>{MFA->aspects_begin(), MFA->aspects_size()});
29042909
else if (const auto *SA = dyn_cast<SectionAttr>(Attr))
29052910
NewAttr = S.mergeSectionAttr(D, *SA, SA->getName());
29062911
else if (const auto *CSA = dyn_cast<CodeSegAttr>(Attr))
@@ -7217,6 +7222,11 @@ static void checkLifetimeBoundAttr(Sema &S, NamedDecl &ND) {
72177222
}
72187223
}
72197224

7225+
static void checkModularFormatAttr(Sema &S, NamedDecl &ND) {
7226+
if (ND.hasAttr<ModularFormatAttr>() && !ND.hasAttr<FormatAttr>())
7227+
S.Diag(ND.getLocation(), diag::err_modular_format_attribute_no_format);
7228+
}
7229+
72207230
static void checkAttributesAfterMerging(Sema &S, NamedDecl &ND) {
72217231
// Ensure that an auto decl is deduced otherwise the checks below might cache
72227232
// the wrong linkage.
@@ -7229,6 +7239,7 @@ static void checkAttributesAfterMerging(Sema &S, NamedDecl &ND) {
72297239
checkHybridPatchableAttr(S, ND);
72307240
checkInheritableAttr(S, ND);
72317241
checkLifetimeBoundAttr(S, ND);
7242+
checkModularFormatAttr(S, ND);
72327243
}
72337244

72347245
static void checkDLLAttributeRedeclaration(Sema &S, NamedDecl *OldDecl,

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6973,6 +6973,71 @@ static void handleVTablePointerAuthentication(Sema &S, Decl *D,
69736973
CustomDiscriminationValue));
69746974
}
69756975

6976+
static bool modularFormatAttrsEquiv(const ModularFormatAttr *Existing,
6977+
IdentifierInfo *ModularImplFn,
6978+
StringRef ImplName,
6979+
ArrayRef<StringRef> Aspects) {
6980+
return Existing->getModularImplFn() == ModularImplFn &&
6981+
Existing->getImplName() == ImplName &&
6982+
Existing->aspects_size() == Aspects.size() &&
6983+
llvm::equal(Existing->aspects(), Aspects);
6984+
}
6985+
6986+
ModularFormatAttr *
6987+
Sema::mergeModularFormatAttr(Decl *D, const AttributeCommonInfo &CI,
6988+
IdentifierInfo *ModularImplFn, StringRef ImplName,
6989+
MutableArrayRef<StringRef> Aspects) {
6990+
if (const auto *Existing = D->getAttr<ModularFormatAttr>()) {
6991+
if (!modularFormatAttrsEquiv(Existing, ModularImplFn, ImplName, Aspects)) {
6992+
Diag(Existing->getLocation(), diag::err_duplicate_attribute) << *Existing;
6993+
Diag(CI.getLoc(), diag::note_conflicting_attribute);
6994+
}
6995+
return nullptr;
6996+
}
6997+
return ::new (Context) ModularFormatAttr(Context, CI, ModularImplFn, ImplName,
6998+
Aspects.data(), Aspects.size());
6999+
}
7000+
7001+
static void handleModularFormat(Sema &S, Decl *D, const ParsedAttr &AL) {
7002+
bool Valid = true;
7003+
StringRef ImplName;
7004+
if (!S.checkStringLiteralArgumentAttr(AL, 1, ImplName))
7005+
Valid = false;
7006+
SmallVector<StringRef> Aspects;
7007+
llvm::DenseSet<StringRef> SeenAspects;
7008+
for (unsigned I = 2, E = AL.getNumArgs(); I != E; ++I) {
7009+
StringRef Aspect;
7010+
if (!S.checkStringLiteralArgumentAttr(AL, I, Aspect))
7011+
return;
7012+
if (!SeenAspects.insert(Aspect).second) {
7013+
S.Diag(AL.getArgAsExpr(I)->getExprLoc(),
7014+
diag::err_modular_format_duplicate_aspect)
7015+
<< Aspect;
7016+
Valid = false;
7017+
continue;
7018+
}
7019+
Aspects.push_back(Aspect);
7020+
}
7021+
if (!Valid)
7022+
return;
7023+
7024+
// Store aspects sorted.
7025+
llvm::sort(Aspects);
7026+
IdentifierInfo *ModularImplFn = AL.getArgAsIdent(0)->getIdentifierInfo();
7027+
7028+
if (const auto *Existing = D->getAttr<ModularFormatAttr>()) {
7029+
if (!modularFormatAttrsEquiv(Existing, ModularImplFn, ImplName, Aspects)) {
7030+
S.Diag(AL.getLoc(), diag::err_duplicate_attribute) << *Existing;
7031+
S.Diag(Existing->getLoc(), diag::note_conflicting_attribute);
7032+
}
7033+
// Ignore the later declaration in favor of the earlier one.
7034+
return;
7035+
}
7036+
7037+
D->addAttr(::new (S.Context) ModularFormatAttr(
7038+
S.Context, AL, ModularImplFn, ImplName, Aspects.data(), Aspects.size()));
7039+
}
7040+
69767041
//===----------------------------------------------------------------------===//
69777042
// Top Level Sema Entry Points
69787043
//===----------------------------------------------------------------------===//
@@ -7913,6 +7978,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
79137978
case ParsedAttr::AT_VTablePointerAuthentication:
79147979
handleVTablePointerAuthentication(S, D, AL);
79157980
break;
7981+
7982+
case ParsedAttr::AT_ModularFormat:
7983+
handleModularFormat(S, D, AL);
7984+
break;
79167985
}
79177986
}
79187987

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm %s -o - | FileCheck %s
2+
3+
int printf(const char *fmt, ...) __attribute__((modular_format(__modular_printf, "__printf", "float")));
4+
int myprintf(const char *fmt, ...) __attribute__((modular_format(__modular_printf, "__printf", "float"), format(printf, 1, 2)));
5+
6+
// CHECK-LABEL: define dso_local void @test_inferred_format(
7+
// CHECK: {{.*}} = call i32 (ptr, ...) @printf(ptr noundef @.str) #[[ATTR:[0-9]+]]
8+
void test_inferred_format(void) {
9+
printf("hello");
10+
}
11+
12+
// CHECK-LABEL: define dso_local void @test_explicit_format(
13+
// CHECK: {{.*}} = call i32 (ptr, ...) @myprintf(ptr noundef @.str) #[[ATTR:[0-9]+]]
14+
void test_explicit_format(void) {
15+
myprintf("hello");
16+
}
17+
18+
int redecl(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
19+
int redecl(const char *fmt, ...) __attribute__((modular_format(__dupe_impl, "__dupe", "1")));
20+
int redecl(const char *fmt, ...) __attribute__((modular_format(__dupe_impl, "__dupe", "1")));
21+
22+
// CHECK-LABEL: define dso_local void @test_redecl(
23+
// CHECK: {{.*}} = call i32 (ptr, ...) @redecl(ptr noundef @.str) #[[ATTR_DUPE_IDENTICAL:[0-9]+]]
24+
void test_redecl(void) {
25+
redecl("hello");
26+
}
27+
28+
int order1(const char *fmt, ...) __attribute__((modular_format(__modular_printf, "__printf", "a", "b"), format(printf, 1, 2)));
29+
int order2(const char *fmt, ...) __attribute__((modular_format(__modular_printf, "__printf", "b", "a"), format(printf, 1, 2)));
30+
31+
// CHECK-LABEL: define dso_local void @test_order(
32+
// CHECK: {{.*}} = call i32 (ptr, ...) @order1(ptr noundef @.str) #[[ATTR_ORDER:[0-9]+]]
33+
// CHECK: {{.*}} = call i32 (ptr, ...) @order2(ptr noundef @.str) #[[ATTR_ORDER]]
34+
void test_order(void) {
35+
order1("hello");
36+
order2("hello");
37+
}
38+
39+
int duplicate_identical(const char *fmt, ...) __attribute__((modular_format(__dupe_impl, "__dupe", "1"), modular_format(__dupe_impl, "__dupe", "1"), format(printf, 1, 2)));
40+
41+
// CHECK-LABEL: define dso_local void @test_duplicate_identical(
42+
// CHECK: {{.*}} = call i32 (ptr, ...) @duplicate_identical(ptr noundef @.str) #[[ATTR_DUPE_IDENTICAL]]
43+
void test_duplicate_identical(void) {
44+
duplicate_identical("hello");
45+
}
46+
47+
// CHECK: attributes #[[ATTR]] = { "modular-format"="printf,1,2,__modular_printf,__printf,float" }
48+
// CHECK: attributes #[[ATTR_DUPE_IDENTICAL]] = { "modular-format"="printf,1,2,__dupe_impl,__dupe,1" }
49+
// CHECK: attributes #[[ATTR_ORDER]] = { "modular-format"="printf,1,2,__modular_printf,__printf,a,b" }

clang/test/Misc/pragma-attribute-supported-attributes-list.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
// CHECK-NEXT: Mips16 (SubjectMatchRule_function)
112112
// CHECK-NEXT: MipsLongCall (SubjectMatchRule_function)
113113
// CHECK-NEXT: MipsShortCall (SubjectMatchRule_function)
114+
// CHECK-NEXT: ModularFormat (SubjectMatchRule_function)
114115
// CHECK-NEXT: NSConsumed (SubjectMatchRule_variable_is_parameter)
115116
// CHECK-NEXT: NSConsumesSelf (SubjectMatchRule_objc_method)
116117
// CHECK-NEXT: NSErrorDomain (SubjectMatchRule_enum)

0 commit comments

Comments
 (0)