Skip to content
Open
21 changes: 21 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2742,6 +2742,27 @@ For example:
This attribute indicates that outlining passes should not modify the
function.

``"modular-format"="<type>,<string_idx>,<first_arg_idx>,<modular_impl_fn>,<impl_name>,<aspects...>"``
This attribute indicates that the implementation is modular on a particular
format string argument. If the compiler can determine that not all aspects
of the implementation are needed, it can report which aspects were needed
and redirect the call to a modular implementation function instead.

The compiler reports that an implementation aspect is needed by issuing a
relocation for the symbol `<impl_name>_<aspect>``. This arranges for code
and data needed to support the aspect of the implementation to be brought
into the link to satisfy weak references in the modular implemenation
function.

The first three arguments have the same semantics as the arguments to the C
``format`` attribute.

The following aspects are currently supported:

- ``float``: The call has a floating point argument



Call Site Attributes
----------------------

Expand Down
14 changes: 14 additions & 0 deletions llvm/lib/IR/Verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2554,6 +2554,20 @@ void Verifier::verifyFunctionAttrs(FunctionType *FT, AttributeList Attrs,
CheckFailed("invalid value for 'denormal-fp-math-f32' attribute: " + S,
V);
}

if (auto A = Attrs.getFnAttr("modular-format"); A.isValid()) {
StringRef S = A.getValueAsString();
SmallVector<StringRef> Args;
S.split(Args, ',');
Check(Args.size() >= 5,
"modular-format attribute requires at least 5 arguments", V);
unsigned FirstArgIdx;
Check(!Args[2].getAsInteger(10, FirstArgIdx),
"modular-format attribute first arg index is not an integer", V);
unsigned UpperBound = FT->getNumParams() + (FT->isVarArg() ? 1 : 0);
Check(FirstArgIdx > 0 && FirstArgIdx <= UpperBound,
"modular-format attribute first arg index is out of bounds", V);
}
}
void Verifier::verifyUnknownProfileMetadata(MDNode *MD) {
Check(MD->getNumOperands() == 2,
Expand Down
69 changes: 69 additions & 0 deletions llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Analysis/AliasAnalysis.h"
#include "llvm/Analysis/AssumeBundleQueries.h"
#include "llvm/Analysis/AssumptionCache.h"
Expand Down Expand Up @@ -4071,6 +4072,70 @@ Instruction *InstCombinerImpl::visitCallBrInst(CallBrInst &CBI) {
return visitCallBase(CBI);
}

static Value *optimizeModularFormat(CallInst *CI, IRBuilderBase &B) {
if (!CI->hasFnAttr("modular-format"))
return nullptr;

SmallVector<StringRef> Args(
llvm::split(CI->getFnAttr("modular-format").getValueAsString(), ','));
// TODO: Make use of the first two arguments
unsigned FirstArgIdx;
[[maybe_unused]] bool Error;
Error = Args[2].getAsInteger(10, FirstArgIdx);
assert(!Error && "invalid first arg index");
--FirstArgIdx;
StringRef FnName = Args[3];
StringRef ImplName = Args[4];
ArrayRef<StringRef> AllAspects = ArrayRef<StringRef>(Args).drop_front(5);

if (AllAspects.empty())
return nullptr;

SmallVector<StringRef> NeededAspects;
for (StringRef Aspect : AllAspects) {
if (Aspect == "float") {
if (llvm::any_of(
llvm::make_range(std::next(CI->arg_begin(), FirstArgIdx),
CI->arg_end()),
[](Value *V) { return V->getType()->isFloatingPointTy(); }))
NeededAspects.push_back("float");
} else {
// Unknown aspects are always considered to be needed.
NeededAspects.push_back(Aspect);
}
}

if (NeededAspects.size() == AllAspects.size())
return nullptr;

Module *M = CI->getModule();
LLVMContext &Ctx = M->getContext();
Function *Callee = CI->getCalledFunction();
FunctionCallee ModularFn = M->getOrInsertFunction(
FnName, Callee->getFunctionType(),
Callee->getAttributes().removeFnAttribute(Ctx, "modular-format"));
CallInst *New = cast<CallInst>(CI->clone());
New->setCalledFunction(ModularFn);
New->removeFnAttr("modular-format");
B.Insert(New);

const auto ReferenceAspect = [&](StringRef Aspect) {
SmallString<20> Name = ImplName;
Name += '_';
Name += Aspect;
Function *RelocNoneFn =
Intrinsic::getOrInsertDeclaration(M, Intrinsic::reloc_none);
B.CreateCall(RelocNoneFn,
{MetadataAsValue::get(Ctx, MDString::get(Ctx, Name))});
};

llvm::sort(NeededAspects);
for (StringRef Request : NeededAspects)
ReferenceAspect(Request);

return New;
}

Instruction *InstCombinerImpl::tryOptimizeCall(CallInst *CI) {
if (!CI->getCalledFunction()) return nullptr;

Expand All @@ -4092,6 +4157,10 @@ Instruction *InstCombinerImpl::tryOptimizeCall(CallInst *CI) {
++NumSimplified;
return CI->use_empty() ? CI : replaceInstUsesWith(*CI, With);
}
if (Value *With = optimizeModularFormat(CI, Builder)) {
++NumSimplified;
return CI->use_empty() ? CI : replaceInstUsesWith(*CI, With);
}

return nullptr;
}
Expand Down
105 changes: 105 additions & 0 deletions llvm/test/Transforms/InstCombine/modular-format.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; Test that the modular format string library call simplifier works correctly.
;
; RUN: opt < %s -passes=instcombine -S | FileCheck %s

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"

@.str.int = constant [3 x i8] c"%d\00"
@.str.float = constant [3 x i8] c"%f\00"
@.str.multi = constant [6 x i8] c"%f %d\00"
@.str.noargs = constant [1 x i8] c"\00"

; No aspects are specified, so no transformation occurs.
define void @test_basic(i32 %arg) {
; CHECK-LABEL: @test_basic(
; CHECK-NEXT: call void (ptr, ...) @basic(ptr nonnull @.str.int, i32 [[ARG:%.*]])
; CHECK-NEXT: ret void
;
call void (ptr, ...) @basic(ptr @.str.int, i32 %arg)
ret void
}

declare void @basic(ptr, ...) #0

; The "float" aspect is present and needed, so no transformation occurs.
define void @test_float_present(double %arg) {
; CHECK-LABEL: @test_float_present(
; CHECK-NEXT: call void (ptr, ...) @float_present(ptr nonnull @.str.float, double [[ARG:%.*]])
; CHECK-NEXT: ret void
;
call void (ptr, ...) @float_present(ptr @.str.float, double %arg)
ret void
}

declare void @float_present(ptr, ...) #1

; The "float" aspect is present but not needed, so the call is transformed.
define void @test_float_absent(i32 %arg) {
; CHECK-LABEL: @test_float_absent(
; CHECK-NEXT: call void (ptr, ...) @float_present_mod(ptr nonnull @.str.int, i32 [[ARG:%.*]])
; CHECK-NEXT: ret void
;
call void (ptr, ...) @float_absent(ptr @.str.int, i32 %arg)
ret void
}

declare void @float_absent(ptr, ...) #1

; Unknown aspects are always considered needed, so no transformation occurs.
define void @test_unknown_aspects(i32 %arg) {
; CHECK-LABEL: @test_unknown_aspects(
; CHECK-NEXT: call void (ptr, ...) @unknown_aspects(ptr nonnull @.str.int, i32 [[ARG:%.*]])
; CHECK-NEXT: ret void
;
call void (ptr, ...) @unknown_aspects(ptr @.str.int, i32 %arg)
ret void
}

declare void @unknown_aspects(ptr, ...) #2

; The call has no arguments to check, so the "float" aspect is not needed and
; the call is transformed.
define void @test_no_args_to_check() {
; CHECK-LABEL: @test_no_args_to_check(
; CHECK-NEXT: call void (ptr, ...) @float_present_mod(ptr nonnull @.str.noargs)
; CHECK-NEXT: ret void
;
call void (ptr, ...) @no_args_to_check(ptr @.str.noargs)
ret void
}

declare void @no_args_to_check(ptr, ...) #1

; The first argument index is not 2. The "float" aspect is needed, so no
; transformation occurs.
define void @test_first_arg_idx(i32 %ignored, double %arg) {
; CHECK-LABEL: @test_first_arg_idx(
; CHECK-NEXT: call void (i32, ptr, ...) @first_arg_idx(i32 [[IGNORED:%.*]], ptr nonnull @.str.float, double [[ARG:%.*]])
; CHECK-NEXT: ret void
;
call void (i32, ptr, ...) @first_arg_idx(i32 %ignored, ptr @.str.float, double %arg)
ret void
}

declare void @first_arg_idx(i32, ptr, ...) #3

; One aspect ("unknown") is needed, but one ("float") is not. The call is
; transformed, and a reference to the needed aspect is emitted.
define void @test_partial_aspects(i32 %arg) {
; CHECK-LABEL: @test_partial_aspects(
; CHECK-NEXT: call void (ptr, ...) @multiple_aspects_mod(ptr nonnull @.str.int, i32 [[ARG:%.*]])
; CHECK-NEXT: call void @llvm.reloc.none(metadata !"basic_impl_unknown")
; CHECK-NEXT: ret void
;
call void (ptr, ...) @partial_aspects(ptr @.str.int, i32 %arg)
ret void
}

declare void @partial_aspects(ptr, ...) #4

attributes #0 = { "modular-format"="printf,1,2,basic_mod,basic_impl" }
attributes #1 = { "modular-format"="printf,1,2,float_present_mod,basic_impl,float" }
attributes #2 = { "modular-format"="printf,1,2,unknown_aspects_mod,basic_impl,unknown1,unknown2" }
attributes #3 = { "modular-format"="printf,2,3,first_arg_idx_mod,basic_impl,float" }
attributes #4 = { "modular-format"="printf,1,2,multiple_aspects_mod,basic_impl,float,unknown" }
41 changes: 41 additions & 0 deletions llvm/test/Verifier/modular-format.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
; RUN: not llvm-as < %s -o /dev/null 2>&1 | FileCheck %s

define void @test_too_few_arguments(i32 %arg, ...) "modular-format"="printf,1,2,basic_mod" {
ret void
}
; CHECK: modular-format attribute requires at least 5 arguments
; CHECK-NEXT: ptr @test_too_few_arguments

define void @test_first_arg_index_not_integer(i32 %arg, ...) "modular-format"="printf,1,foo,basic_mod,basic_impl" {
ret void
}
; CHECK: modular-format attribute first arg index is not an integer
; CHECK-NEXT: ptr @test_first_arg_index_not_integer

define void @test_first_arg_index_zero(i32 %arg) "modular-format"="printf,1,0,basic_mod,basic_impl" {
ret void
}
; CHECK: modular-format attribute first arg index is out of bounds
; CHECK-NEXT: ptr @test_first_arg_index_zero

define void @test_first_arg_index_out_of_bounds(i32 %arg) "modular-format"="printf,1,2,basic_mod,basic_impl" {
ret void
}
; CHECK: modular-format attribute first arg index is out of bounds
; CHECK-NEXT: ptr @test_first_arg_index_out_of_bounds

define void @test_first_arg_index_out_of_bounds_varargs(i32 %arg, ...) "modular-format"="printf,1,3,basic_mod,basic_impl" {
ret void
}
; CHECK: modular-format attribute first arg index is out of bounds
; CHECK-NEXT: ptr @test_first_arg_index_out_of_bounds_varargs

; CHECK-NOT: ptr @test_first_arg_index_in_bounds
define void @test_first_arg_index_in_bounds(i32 %arg) "modular-format"="printf,1,1,basic_mod,basic_impl" {
ret void
}

; CHECK-NOT: ptr @test_first_arg_index_in_bounds_varargs
define void @test_first_arg_index_in_bounds_varargs(i32 %arg, ...) "modular-format"="printf,1,2,basic_mod,basic_impl" {
ret void
}