Skip to content

Commit c9ff2df

Browse files
authored
[IR] "modular-format" attribute for functions using format strings (#147429)
A new InstCombine transform uses this attribute to rewrite calls to a modular version of the implementation along with llvm.reloc.none relocations against aspects of the implementation needed by the call. This change only adds support for the 'float' aspect, but it also builds the structure needed for others. See issue #146159
1 parent cc5057c commit c9ff2df

File tree

6 files changed

+254
-0
lines changed

6 files changed

+254
-0
lines changed

llvm/docs/LangRef.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2747,6 +2747,27 @@ For example:
27472747
all arguments are not undef and not poison. Otherwise, it is undefined
27482748
behavior.
27492749

2750+
``"modular-format"="<type>,<string_idx>,<first_arg_idx>,<modular_impl_fn>,<impl_name>,<aspects...>"``
2751+
This attribute indicates that the implementation is modular on a particular
2752+
format string argument. If the compiler can determine that not all aspects
2753+
of the implementation are needed, it can report which aspects were needed
2754+
and redirect the call to a modular implementation function instead.
2755+
2756+
The compiler reports that an implementation aspect is needed by issuing a
2757+
relocation for the symbol `<impl_name>_<aspect>``. This arranges for code
2758+
and data needed to support the aspect of the implementation to be brought
2759+
into the link to satisfy weak references in the modular implemenation
2760+
function.
2761+
2762+
The first three arguments have the same semantics as the arguments to the C
2763+
``format`` attribute.
2764+
2765+
The following aspects are currently supported:
2766+
2767+
- ``float``: The call has a floating point argument
2768+
2769+
2770+
27502771
Call Site Attributes
27512772
----------------------
27522773

llvm/docs/ReleaseNotes.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ Changes to the LLVM IR
7070
* Added `@llvm.reloc.none` intrinsic to emit null relocations to symbols. This
7171
emits an undefined symbol reference without adding any dedicated code or data to
7272
to bear the relocation.
73+
* Added `modular-format` attribute to dynamically pull in aspects of libc
74+
format string function implementations from statically-linked libc's based on
75+
the requirements of each call. Currently only `float` is supported; this can
76+
keep floating point support out of printf if it can be proven unused.
7377

7478
Changes to LLVM infrastructure
7579
------------------------------

llvm/lib/IR/Verifier.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2567,6 +2567,20 @@ void Verifier::verifyFunctionAttrs(FunctionType *FT, AttributeList Attrs,
25672567
CheckFailed("invalid value for 'denormal-fp-math-f32' attribute: " + S,
25682568
V);
25692569
}
2570+
2571+
if (auto A = Attrs.getFnAttr("modular-format"); A.isValid()) {
2572+
StringRef S = A.getValueAsString();
2573+
SmallVector<StringRef> Args;
2574+
S.split(Args, ',');
2575+
Check(Args.size() >= 5,
2576+
"modular-format attribute requires at least 5 arguments", V);
2577+
unsigned FirstArgIdx;
2578+
Check(!Args[2].getAsInteger(10, FirstArgIdx),
2579+
"modular-format attribute first arg index is not an integer", V);
2580+
unsigned UpperBound = FT->getNumParams() + (FT->isVarArg() ? 1 : 0);
2581+
Check(FirstArgIdx > 0 && FirstArgIdx <= UpperBound,
2582+
"modular-format attribute first arg index is out of bounds", V);
2583+
}
25702584
}
25712585
void Verifier::verifyUnknownProfileMetadata(MDNode *MD) {
25722586
Check(MD->getNumOperands() == 2,

llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "llvm/ADT/SmallBitVector.h"
2020
#include "llvm/ADT/SmallVector.h"
2121
#include "llvm/ADT/Statistic.h"
22+
#include "llvm/ADT/StringExtras.h"
2223
#include "llvm/Analysis/AliasAnalysis.h"
2324
#include "llvm/Analysis/AssumeBundleQueries.h"
2425
#include "llvm/Analysis/AssumptionCache.h"
@@ -4091,6 +4092,70 @@ Instruction *InstCombinerImpl::visitCallBrInst(CallBrInst &CBI) {
40914092
return visitCallBase(CBI);
40924093
}
40934094

4095+
static Value *optimizeModularFormat(CallInst *CI, IRBuilderBase &B) {
4096+
if (!CI->hasFnAttr("modular-format"))
4097+
return nullptr;
4098+
4099+
SmallVector<StringRef> Args(
4100+
llvm::split(CI->getFnAttr("modular-format").getValueAsString(), ','));
4101+
// TODO: Make use of the first two arguments
4102+
unsigned FirstArgIdx;
4103+
[[maybe_unused]] bool Error;
4104+
Error = Args[2].getAsInteger(10, FirstArgIdx);
4105+
assert(!Error && "invalid first arg index");
4106+
--FirstArgIdx;
4107+
StringRef FnName = Args[3];
4108+
StringRef ImplName = Args[4];
4109+
ArrayRef<StringRef> AllAspects = ArrayRef<StringRef>(Args).drop_front(5);
4110+
4111+
if (AllAspects.empty())
4112+
return nullptr;
4113+
4114+
SmallVector<StringRef> NeededAspects;
4115+
for (StringRef Aspect : AllAspects) {
4116+
if (Aspect == "float") {
4117+
if (llvm::any_of(
4118+
llvm::make_range(std::next(CI->arg_begin(), FirstArgIdx),
4119+
CI->arg_end()),
4120+
[](Value *V) { return V->getType()->isFloatingPointTy(); }))
4121+
NeededAspects.push_back("float");
4122+
} else {
4123+
// Unknown aspects are always considered to be needed.
4124+
NeededAspects.push_back(Aspect);
4125+
}
4126+
}
4127+
4128+
if (NeededAspects.size() == AllAspects.size())
4129+
return nullptr;
4130+
4131+
Module *M = CI->getModule();
4132+
LLVMContext &Ctx = M->getContext();
4133+
Function *Callee = CI->getCalledFunction();
4134+
FunctionCallee ModularFn = M->getOrInsertFunction(
4135+
FnName, Callee->getFunctionType(),
4136+
Callee->getAttributes().removeFnAttribute(Ctx, "modular-format"));
4137+
CallInst *New = cast<CallInst>(CI->clone());
4138+
New->setCalledFunction(ModularFn);
4139+
New->removeFnAttr("modular-format");
4140+
B.Insert(New);
4141+
4142+
const auto ReferenceAspect = [&](StringRef Aspect) {
4143+
SmallString<20> Name = ImplName;
4144+
Name += '_';
4145+
Name += Aspect;
4146+
Function *RelocNoneFn =
4147+
Intrinsic::getOrInsertDeclaration(M, Intrinsic::reloc_none);
4148+
B.CreateCall(RelocNoneFn,
4149+
{MetadataAsValue::get(Ctx, MDString::get(Ctx, Name))});
4150+
};
4151+
4152+
llvm::sort(NeededAspects);
4153+
for (StringRef Request : NeededAspects)
4154+
ReferenceAspect(Request);
4155+
4156+
return New;
4157+
}
4158+
40944159
Instruction *InstCombinerImpl::tryOptimizeCall(CallInst *CI) {
40954160
if (!CI->getCalledFunction()) return nullptr;
40964161

@@ -4112,6 +4177,10 @@ Instruction *InstCombinerImpl::tryOptimizeCall(CallInst *CI) {
41124177
++NumSimplified;
41134178
return CI->use_empty() ? CI : replaceInstUsesWith(*CI, With);
41144179
}
4180+
if (Value *With = optimizeModularFormat(CI, Builder)) {
4181+
++NumSimplified;
4182+
return CI->use_empty() ? CI : replaceInstUsesWith(*CI, With);
4183+
}
41154184

41164185
return nullptr;
41174186
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
2+
; Test that the modular format string library call simplifier works correctly.
3+
;
4+
; RUN: opt < %s -passes=instcombine -S | FileCheck %s
5+
6+
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:128:128"
7+
8+
@.str.int = constant [3 x i8] c"%d\00"
9+
@.str.float = constant [3 x i8] c"%f\00"
10+
@.str.multi = constant [6 x i8] c"%f %d\00"
11+
@.str.noargs = constant [1 x i8] c"\00"
12+
13+
;; No aspects are specified, so no transformation occurs.
14+
define void @test_basic(i32 %arg) {
15+
; CHECK-LABEL: @test_basic(
16+
; CHECK-NEXT: call void (ptr, ...) @basic(ptr nonnull @.str.int, i32 [[ARG:%.*]])
17+
; CHECK-NEXT: ret void
18+
;
19+
call void (ptr, ...) @basic(ptr @.str.int, i32 %arg)
20+
ret void
21+
}
22+
23+
declare void @basic(ptr, ...) #0
24+
25+
;; The "float" aspect is present and needed, so no transformation occurs.
26+
define void @test_float_present(double %arg) {
27+
; CHECK-LABEL: @test_float_present(
28+
; CHECK-NEXT: call void (ptr, ...) @float_present(ptr nonnull @.str.float, double [[ARG:%.*]])
29+
; CHECK-NEXT: ret void
30+
;
31+
call void (ptr, ...) @float_present(ptr @.str.float, double %arg)
32+
ret void
33+
}
34+
35+
declare void @float_present(ptr, ...) #1
36+
37+
;; The "float" aspect is present but not needed, so the call is transformed.
38+
define void @test_float_absent(i32 %arg) {
39+
; CHECK-LABEL: @test_float_absent(
40+
; CHECK-NEXT: call void (ptr, ...) @float_present_mod(ptr nonnull @.str.int, i32 [[ARG:%.*]])
41+
; CHECK-NEXT: ret void
42+
;
43+
call void (ptr, ...) @float_absent(ptr @.str.int, i32 %arg)
44+
ret void
45+
}
46+
47+
declare void @float_absent(ptr, ...) #1
48+
49+
;; Unknown aspects are always considered needed, so no transformation occurs.
50+
define void @test_unknown_aspects(i32 %arg) {
51+
; CHECK-LABEL: @test_unknown_aspects(
52+
; CHECK-NEXT: call void (ptr, ...) @unknown_aspects(ptr nonnull @.str.int, i32 [[ARG:%.*]])
53+
; CHECK-NEXT: ret void
54+
;
55+
call void (ptr, ...) @unknown_aspects(ptr @.str.int, i32 %arg)
56+
ret void
57+
}
58+
59+
declare void @unknown_aspects(ptr, ...) #2
60+
61+
;; The call has no arguments to check, so the "float" aspect is not needed and
62+
;; the call is transformed.
63+
define void @test_no_args_to_check() {
64+
; CHECK-LABEL: @test_no_args_to_check(
65+
; CHECK-NEXT: call void (ptr, ...) @float_present_mod(ptr nonnull @.str.noargs)
66+
; CHECK-NEXT: ret void
67+
;
68+
call void (ptr, ...) @no_args_to_check(ptr @.str.noargs)
69+
ret void
70+
}
71+
72+
declare void @no_args_to_check(ptr, ...) #1
73+
74+
;; The first argument index is not 2. The "float" aspect is needed, so no
75+
;; transformation occurs.
76+
define void @test_first_arg_idx(i32 %ignored, double %arg) {
77+
; CHECK-LABEL: @test_first_arg_idx(
78+
; CHECK-NEXT: call void (i32, ptr, ...) @first_arg_idx(i32 [[IGNORED:%.*]], ptr nonnull @.str.float, double [[ARG:%.*]])
79+
; CHECK-NEXT: ret void
80+
;
81+
call void (i32, ptr, ...) @first_arg_idx(i32 %ignored, ptr @.str.float, double %arg)
82+
ret void
83+
}
84+
85+
declare void @first_arg_idx(i32, ptr, ...) #3
86+
87+
;; One aspect ("unknown") is needed, but one ("float") is not. The call is
88+
;; transformed, and a reference to the needed aspect is emitted.
89+
define void @test_partial_aspects(i32 %arg) {
90+
; CHECK-LABEL: @test_partial_aspects(
91+
; CHECK-NEXT: call void (ptr, ...) @multiple_aspects_mod(ptr nonnull @.str.int, i32 [[ARG:%.*]])
92+
; CHECK-NEXT: call void @llvm.reloc.none(metadata !"basic_impl_unknown")
93+
; CHECK-NEXT: ret void
94+
;
95+
call void (ptr, ...) @partial_aspects(ptr @.str.int, i32 %arg)
96+
ret void
97+
}
98+
99+
declare void @partial_aspects(ptr, ...) #4
100+
101+
attributes #0 = { "modular-format"="printf,1,2,basic_mod,basic_impl" }
102+
attributes #1 = { "modular-format"="printf,1,2,float_present_mod,basic_impl,float" }
103+
attributes #2 = { "modular-format"="printf,1,2,unknown_aspects_mod,basic_impl,unknown1,unknown2" }
104+
attributes #3 = { "modular-format"="printf,2,3,first_arg_idx_mod,basic_impl,float" }
105+
attributes #4 = { "modular-format"="printf,1,2,multiple_aspects_mod,basic_impl,float,unknown" }
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
; RUN: not llvm-as < %s -o /dev/null 2>&1 | FileCheck %s
2+
3+
define void @test_too_few_arguments(i32 %arg, ...) "modular-format"="printf,1,2,basic_mod" {
4+
ret void
5+
}
6+
; CHECK: modular-format attribute requires at least 5 arguments
7+
; CHECK-NEXT: ptr @test_too_few_arguments
8+
9+
define void @test_first_arg_index_not_integer(i32 %arg, ...) "modular-format"="printf,1,foo,basic_mod,basic_impl" {
10+
ret void
11+
}
12+
; CHECK: modular-format attribute first arg index is not an integer
13+
; CHECK-NEXT: ptr @test_first_arg_index_not_integer
14+
15+
define void @test_first_arg_index_zero(i32 %arg) "modular-format"="printf,1,0,basic_mod,basic_impl" {
16+
ret void
17+
}
18+
; CHECK: modular-format attribute first arg index is out of bounds
19+
; CHECK-NEXT: ptr @test_first_arg_index_zero
20+
21+
define void @test_first_arg_index_out_of_bounds(i32 %arg) "modular-format"="printf,1,2,basic_mod,basic_impl" {
22+
ret void
23+
}
24+
; CHECK: modular-format attribute first arg index is out of bounds
25+
; CHECK-NEXT: ptr @test_first_arg_index_out_of_bounds
26+
27+
define void @test_first_arg_index_out_of_bounds_varargs(i32 %arg, ...) "modular-format"="printf,1,3,basic_mod,basic_impl" {
28+
ret void
29+
}
30+
; CHECK: modular-format attribute first arg index is out of bounds
31+
; CHECK-NEXT: ptr @test_first_arg_index_out_of_bounds_varargs
32+
33+
; CHECK-NOT: ptr @test_first_arg_index_in_bounds
34+
define void @test_first_arg_index_in_bounds(i32 %arg) "modular-format"="printf,1,1,basic_mod,basic_impl" {
35+
ret void
36+
}
37+
38+
; CHECK-NOT: ptr @test_first_arg_index_in_bounds_varargs
39+
define void @test_first_arg_index_in_bounds_varargs(i32 %arg, ...) "modular-format"="printf,1,2,basic_mod,basic_impl" {
40+
ret void
41+
}

0 commit comments

Comments
 (0)