Compare commits

...

30 Commits

Author SHA1 Message Date
Daniel Thornburgh
bb4a9036cf LIBC_INLINE for modular fn definitions 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
c1f1797f44 Use LIBC_INLINE_ASM macro 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
aa874c9928 Add missing file header 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
fd1ee30e8c Fix missed SPLIT->MODULAR rename 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
4cb90bf6f1 [libc] Modular printf option (float only)
This adds LIBC_CONF_PRINTF_MODULAR, which causes floating point support
(later, others) to be weakly linked into the implementation.
__printf_modular becomes the main entry point of the implementaiton, an
printf itself wraps __printf_modular. printf it also contains a
BFD_RELOC_NONE relocation to bring in the float aspect.

See issue #146159 for context.
2025-08-21 15:09:04 -07:00
Daniel Thornburgh
ce0760f8f7 Correct typos 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
cfd33c8c0a Emit the new type arg from format attr 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
f272839e84 Add an example to clang attr doc 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
46a0008948 Update docs to account for clang inferring format attribute 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
4c53b27ac2 [clang] "modular_format" attribute for functions using format strings
This provides a C language version of the new IR modular-format
attribute. This, in concert with the format attribute, allows a library
function to declare that a modular version of its implementation is
available.

See issue #146159 for context.
2025-08-21 15:09:04 -07:00
Daniel Thornburgh
9265309f0c llvm.reloc.none takes a GlobalValue again
This avoids avoid modifying Module in ISel
2025-08-21 15:09:04 -07:00
Daniel Thornburgh
b7f8a9c8b4 Add a type arg 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
68761d9bf3 Describe the semantics of the arguments copied from C format attr 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
d11cfe5d18 Correct modular_format to modular-format in docs 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
435ba3597b Emit reloc.none instinsic with metdata string arg 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
dff521f73d issing -> issuing 2025-08-21 15:09:04 -07:00
Daniel Thornburgh
40bafe2ceb [IR] "modular-format" attribute for functions using format strings
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
2025-08-21 15:09:04 -07:00
Daniel Thornburgh
341dd6a3ca Update big-filter.td 2025-08-21 15:08:55 -07:00
Daniel Thornburgh
75c4d315dc Take symbol name by GlobalValue again to avoid modifying Module 2025-08-21 14:28:08 -07:00
Daniel Thornburgh
904c996355 Update tests 2025-08-18 11:37:40 -07:00
Daniel Thornburgh
a6220fef9b Remove arg from emitRelocDirective call 2025-08-18 11:37:40 -07:00
Daniel Thornburgh
20f2ad062c Lower reloc.none in Global ISel 2025-08-18 11:37:40 -07:00
Daniel Thornburgh
6fc858761e Rename reloc_none.ll to reloc-none.ll 2025-08-18 11:37:40 -07:00
Daniel Thornburgh
57ba236e87 Use llvm-as for test 2025-08-18 11:37:40 -07:00
Daniel Thornburgh
07ccb63a1b Remove unneeded assertion from AsmPrinter 2025-08-18 11:37:40 -07:00
Daniel Thornburgh
dd58f21c9e IR verifier check and test 2025-08-18 11:37:40 -07:00
Daniel Thornburgh
bd4dd9ca02 Add a generic reloc_none test 2025-08-18 11:37:40 -07:00
Daniel Thornburgh
5a056672cb Take symbol name by metadata arg rather than ptr to GlobalValue 2025-08-18 11:37:40 -07:00
Daniel Thornburgh
0e830ad120 fake.use -> reloc.none 2025-08-18 11:37:40 -07:00
Daniel Thornburgh
f3fdeff3e3 [IR] llvm.reloc.none intrinsic for no-op symbol references
This intrinsic emits a BFD_RELOC_NONE relocation at the point of call,
which allows optimizations and languages to explicitly pull in symbols
from static libraries without there being any code or data that has an
effectual relocation against such a symbol.

See issue #146159 for context.
2025-08-18 11:37:40 -07:00
38 changed files with 526 additions and 26 deletions

View File

@ -5233,3 +5233,14 @@ def NonString : InheritableAttr {
let Subjects = SubjectList<[Var, Field]>;
let Documentation = [NonStringDocs];
}
def ModularFormat : InheritableAttr {
let Spellings = [Clang<"modular_format">];
let Args = [
IdentifierArgument<"ModularImplFn">,
StringArgument<"ImplName">,
VariadicStringArgument<"Aspects">
];
let Subjects = SubjectList<[Function]>;
let Documentation = [ModularFormatDocs];
}

View File

@ -9549,3 +9549,37 @@ silence diagnostics with code like:
__attribute__((nonstring)) char NotAStr[3] = "foo"; // Not diagnosed
}];
}
def ModularFormatDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
The ``modular_format`` attribute can be applied to a function that bears the
``format`` attribute (or standard library functions) to indicate that the
implementation is modular on the format string argument. When the format string
for a given call is constant, the compiler may redirect the call to the symbol
given as the first argument to the attribute (the modular implementation
function).
The second argument is a implementation name, and the remaining arguments are
aspects of the format string for the compiler to report. If the compiler does
not understand an aspect, it must summarily report that the format string has
that aspect.
The compiler reports an aspect 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.
For example, say ``printf`` is annotated with
``modular_format(__modular_printf, __printf, float)``. Then, a call to
``printf(var, 42)`` would be untouched. A call to ``printf("%d", 42)`` would
become a call to ``__modular_printf`` with the same arguments, as would
``printf("%f", 42.0)``. The latter would be accompanied with a strong
relocation against the symbol ``__printf_float``, which would bring floating
point support for ``printf`` into the link.
The following aspects are currently supported:
- ``float``: The call has a floating point argument
}];
}

View File

@ -2560,6 +2560,20 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
if (TargetDecl->hasAttr<ArmLocallyStreamingAttr>())
FuncAttrs.addAttribute("aarch64_pstate_sm_body");
if (auto *ModularFormat = TargetDecl->getAttr<ModularFormatAttr>()) {
// TODO: Error checking
FormatAttr *Format = TargetDecl->getAttr<FormatAttr>();
StringRef Type = Format->getType()->getName();
std::string FormatIdx = std::to_string(Format->getFormatIdx());
std::string FirstArg = std::to_string(Format->getFirstArg());
SmallVector<StringRef> Args = {
Type, FormatIdx, FirstArg,
ModularFormat->getModularImplFn()->getName(),
ModularFormat->getImplName()};
llvm::append_range(Args, ModularFormat->aspects());
FuncAttrs.addAttribute("modular-format", llvm::join(Args, ","));
}
}
// Attach "no-builtins" attributes to:

View File

@ -6752,6 +6752,29 @@ static void handleVTablePointerAuthentication(Sema &S, Decl *D,
CustomDiscriminationValue));
}
static void handleModularFormat(Sema &S, Decl *D, const ParsedAttr &AL) {
StringRef ImplName;
if (!S.checkStringLiteralArgumentAttr(AL, 1, ImplName))
return;
SmallVector<StringRef> Aspects;
for (unsigned I = 2, E = AL.getNumArgs(); I != E; ++I) {
StringRef Aspect;
if (!S.checkStringLiteralArgumentAttr(AL, I, Aspect))
return;
Aspects.push_back(Aspect);
}
// Store aspects sorted and without duplicates.
llvm::sort(Aspects);
Aspects.erase(llvm::unique(Aspects), Aspects.end());
// TODO: Type checking on identifier
// TODO: Merge attributes
D->addAttr(::new (S.Context) ModularFormatAttr(
S.Context, AL, AL.getArgAsIdent(0)->getIdentifierInfo(), ImplName,
Aspects.data(), Aspects.size()));
}
//===----------------------------------------------------------------------===//
// Top Level Sema Entry Points
//===----------------------------------------------------------------------===//
@ -7679,6 +7702,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_VTablePointerAuthentication:
handleVTablePointerAuthentication(S, D, AL);
break;
case ParsedAttr::AT_ModularFormat:
handleModularFormat(S, D, AL);
break;
}
}

View File

@ -51,6 +51,10 @@
"LIBC_CONF_PRINTF_RUNTIME_DISPATCH": {
"value": true,
"doc": "Use dynamic dispatch for the output mechanism to reduce code size."
},
"LIBC_CONF_PRINTF_MODULAR": {
"value": true,
"doc": "Split printf implementation into modules that can be lazily linked in."
}
},
"scanf": {

View File

@ -45,6 +45,7 @@ to learn about the defaults for your platform and target.
- ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_DYADIC_FLOAT``: Use dyadic float for faster and smaller but less accurate printf doubles.
- ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_FLOAT320``: Use an alternative printf float implementation based on 320-bit floats
- ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE``: Use large table for better printf long double performance.
- ``LIBC_CONF_PRINTF_MODULAR``: Split printf implementation into modules that can be lazily linked in.
- ``LIBC_CONF_PRINTF_RUNTIME_DISPATCH``: Use dynamic dispatch for the output mechanism to reduce code size.
* **"pthread" options**
- ``LIBC_CONF_RAW_MUTEX_DEFAULT_SPIN_COUNT``: Default number of spins before blocking if a mutex is in contention (default to 100).

View File

@ -412,10 +412,15 @@ if(LLVM_LIBC_FULL_BUILD)
)
endif()
set(printf_srcs printf.cpp)
if (LIBC_CONF_PRINTF_MODULAR)
list(APPEND printf_srcs printf_modular.cpp)
endif()
add_generic_entrypoint_object(
printf
SRCS
printf.cpp
${printf_srcs}
HDRS
../printf.h
DEPENDS

View File

@ -0,0 +1,40 @@
//===-- Implementation of printf_modular-----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "src/stdio/printf.h"
#include "src/__support/File/file.h"
#include "src/__support/arg_list.h"
#include "src/__support/macros/config.h"
#include "src/stdio/printf_core/vfprintf_internal.h"
#include "hdr/types/FILE.h"
#include <stdarg.h>
#ifndef LIBC_COPT_STDIO_USE_SYSTEM_FILE
#define PRINTF_STDOUT LIBC_NAMESPACE::stdout
#else // LIBC_COPT_STDIO_USE_SYSTEM_FILE
#define PRINTF_STDOUT ::stdout
#endif // LIBC_COPT_STDIO_USE_SYSTEM_FILE
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(int, __printf_modular,
(const char *__restrict format, ...)) {
va_list vlist;
va_start(vlist, format);
internal::ArgList args(vlist); // This holder class allows for easier copying
// and pointer semantics, as well as handling
// destruction automatically.
va_end(vlist);
int ret_val = printf_core::vfprintf_internal_modular(
reinterpret_cast<::FILE *>(PRINTF_STDOUT), format, args);
return ret_val;
}
} // namespace LIBC_NAMESPACE_DECL

View File

@ -15,6 +15,7 @@
namespace LIBC_NAMESPACE_DECL {
int printf(const char *__restrict format, ...);
int __printf_modular(const char *__restrict format, ...);
} // namespace LIBC_NAMESPACE_DECL

View File

@ -28,6 +28,9 @@ endif()
if(LIBC_CONF_PRINTF_RUNTIME_DISPATCH)
list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_RUNTIME_DISPATCH")
endif()
if(LIBC_CONF_PRINTF_MODULAR)
list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_MODULAR")
endif()
if(printf_config_copts)
list(PREPEND printf_config_copts "COMPILE_OPTIONS")
endif()
@ -114,10 +117,12 @@ add_header_library(
libc.src.__support.StringUtil.error_to_string
)
add_header_library(
add_object_library(
printf_main
HDRS
printf_main.h
SRCS
float_impl.cpp
DEPENDS
.parser
.converter

View File

@ -1122,6 +1122,18 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
}
}
template <WriteMode write_mode>
LIBC_PRINTF_MODULAR_DECL int
convert_float_decimal(Writer<write_mode> *writer, const FormatSection &to_conv);
template <WriteMode write_mode>
LIBC_PRINTF_MODULAR_DECL int
convert_float_dec_exp(Writer<write_mode> *writer, const FormatSection &to_conv);
template <WriteMode write_mode>
LIBC_PRINTF_MODULAR_DECL int
convert_float_dec_auto(Writer<write_mode> *writer,
const FormatSection &to_conv);
#ifdef LIBC_PRINTF_DEFINE_MODULAR
// TODO: unify the float converters to remove the duplicated checks for inf/nan.
template <WriteMode write_mode>
@ -1189,6 +1201,7 @@ LIBC_INLINE int convert_float_dec_auto(Writer<write_mode> *writer,
return convert_inf_nan(writer, to_conv);
}
#endif
} // namespace printf_core
} // namespace LIBC_NAMESPACE_DECL

View File

@ -675,6 +675,17 @@ LIBC_INLINE int convert_float_dec_auto_typed(Writer<write_mode> *writer,
return convert_float_typed<T>(writer, to_conv, float_bits, ConversionType::G);
}
template <WriteMode write_mode>
LIBC_PRINTF_MODULAR_DECL int convert_float_decimal(Writer<write_mode> *writer,
const FormatSection &to_conv);
template <WriteMode write_mode>
LIBC_PRINTF_MODULAR_DECL int convert_float_dec_exp(Writer<write_mode> *writer,
const FormatSection &to_conv);
template <WriteMode write_mode>
LIBC_PRINTF_MODULAR_DECL int convert_float_dec_auto(Writer<write_mode> *writer,
const FormatSection &to_conv);
#ifdef LIBC_PRINTF_DEFINE_MODULAR
template <WriteMode write_mode>
LIBC_INLINE int convert_float_decimal(Writer<write_mode> *writer,
const FormatSection &to_conv) {
@ -692,6 +703,7 @@ LIBC_INLINE int convert_float_dec_auto(Writer<write_mode> *writer,
const FormatSection &to_conv) {
return convert_float_outer(writer, to_conv, ConversionType::G);
}
#endif
} // namespace printf_core
} // namespace LIBC_NAMESPACE_DECL

View File

@ -25,6 +25,11 @@
namespace LIBC_NAMESPACE_DECL {
namespace printf_core {
template <WriteMode write_mode>
LIBC_PRINTF_MODULAR_DECL int convert_float_hex_exp(Writer<write_mode> *writer,
const FormatSection &to_conv);
#ifdef LIBC_PRINTF_DEFINE_MODULAR
template <WriteMode write_mode>
LIBC_INLINE int convert_float_hex_exp(Writer<write_mode> *writer,
const FormatSection &to_conv) {
@ -254,6 +259,7 @@ LIBC_INLINE int convert_float_hex_exp(Writer<write_mode> *writer,
}
return WRITE_OK;
}
#endif
} // namespace printf_core
} // namespace LIBC_NAMESPACE_DECL

View File

@ -0,0 +1,56 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file instantiates the functionality needed for supporting floating
/// point arguments in modular printf builds. Non-modular printf builds
/// implicitly instantiate these functions.
///
//===----------------------------------------------------------------------===//
#ifdef LIBC_COPT_PRINTF_MODULAR
#include "src/__support/arg_list.h"
#define LIBC_PRINTF_DEFINE_MODULAR
#include "src/stdio/printf_core/float_dec_converter.h"
#include "src/stdio/printf_core/float_hex_converter.h"
#include "src/stdio/printf_core/parser.h"
namespace LIBC_NAMESPACE_DECL {
namespace printf_core {
template class Parser<internal::ArgList>;
template class Parser<internal::DummyArgList<false>>;
template class Parser<internal::DummyArgList<true>>;
template class Parser<internal::StructArgList<false>>;
template class Parser<internal::StructArgList<true>>;
#define INSTANTIATE_CONVERT_FN(NAME) \
template int NAME<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW>( \
Writer<WriteMode::FILL_BUFF_AND_DROP_OVERFLOW> * writer, \
const FormatSection &to_conv); \
template int NAME<WriteMode::FLUSH_TO_STREAM>( \
Writer<WriteMode::FLUSH_TO_STREAM> * writer, \
const FormatSection &to_conv); \
template int NAME<WriteMode::RESIZE_AND_FILL_BUFF>( \
Writer<WriteMode::RESIZE_AND_FILL_BUFF> * writer, \
const FormatSection &to_conv); \
template int NAME<WriteMode::RUNTIME_DISPATCH>( \
Writer<WriteMode::RUNTIME_DISPATCH> * writer, \
const FormatSection &to_conv)
INSTANTIATE_CONVERT_FN(convert_float_decimal);
INSTANTIATE_CONVERT_FN(convert_float_dec_exp);
INSTANTIATE_CONVERT_FN(convert_float_dec_auto);
INSTANTIATE_CONVERT_FN(convert_float_hex_exp);
} // namespace printf_core
} // namespace LIBC_NAMESPACE_DECL
// Bring this file into the link if __printf_float is referenced.
extern "C" void __printf_float() {}
#endif

View File

@ -236,11 +236,7 @@ public:
case ('A'):
case ('g'):
case ('G'):
if (lm != LengthModifier::L) {
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, double, conv_index);
} else {
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long double, conv_index);
}
write_float_arg_val(section, lm, conv_index);
break;
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
@ -299,6 +295,12 @@ public:
return section;
}
LIBC_PRINTF_MODULAR_DECL void write_float_arg_val(FormatSection &section,
LengthModifier lm,
size_t conv_index);
LIBC_PRINTF_MODULAR_DECL TypeDesc float_type_desc(LengthModifier lm);
LIBC_PRINTF_MODULAR_DECL bool advance_arg_if_float(TypeDesc cur_type_desc);
private:
// parse_flags parses the flags inside a format string. It assumes that
// str[*local_pos] is inside a format specifier, and parses any flags it
@ -474,10 +476,9 @@ private:
args_cur.template next_var<uint64_t>();
#ifndef LIBC_COPT_PRINTF_DISABLE_FLOAT
// Floating point numbers are stored separately from the other arguments.
else if (cur_type_desc == type_desc_from_type<double>())
args_cur.template next_var<double>();
else if (cur_type_desc == type_desc_from_type<long double>())
args_cur.template next_var<long double>();
else if (&Parser::advance_arg_if_float &&
advance_arg_if_float(cur_type_desc))
;
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
// Floating point numbers may be stored separately from the other
@ -630,10 +631,7 @@ private:
case ('A'):
case ('g'):
case ('G'):
if (lm != LengthModifier::L)
conv_size = type_desc_from_type<double>();
else
conv_size = type_desc_from_type<long double>();
conv_size = float_type_desc(lm);
break;
#endif // LIBC_COPT_PRINTF_DISABLE_FLOAT
#ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
@ -682,6 +680,39 @@ private:
#endif // LIBC_COPT_PRINTF_DISABLE_INDEX_MODE
};
#ifdef LIBC_PRINTF_DEFINE_MODULAR
template <typename ArgParser>
LIBC_INLINE void Parser<ArgParser>::write_float_arg_val(FormatSection &section,
LengthModifier lm,
size_t conv_index) {
if (lm != LengthModifier::L) {
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, double, conv_index);
} else {
WRITE_ARG_VAL_SIMPLEST(section.conv_val_raw, long double, conv_index);
}
}
template <typename ArgParser>
LIBC_INLINE TypeDesc Parser<ArgParser>::float_type_desc(LengthModifier lm) {
if (lm != LengthModifier::L)
return type_desc_from_type<double>();
else
return type_desc_from_type<long double>();
}
template <typename ArgParser>
LIBC_INLINE bool
Parser<ArgParser>::advance_arg_if_float(TypeDesc cur_type_desc) {
if (cur_type_desc == type_desc_from_type<double>())
args_cur.template next_var<double>();
else if (cur_type_desc == type_desc_from_type<long double>())
args_cur.template next_var<long double>();
else
return false;
return true;
}
#endif
} // namespace printf_core
} // namespace LIBC_NAMESPACE_DECL

View File

@ -48,4 +48,11 @@
// LIBC_COPT_PRINTF_NO_NULLPTR_CHECKS
#ifdef LIBC_COPT_PRINTF_MODULAR
#define LIBC_PRINTF_MODULAR_DECL [[gnu::weak]]
#else
#define LIBC_PRINTF_MODULAR_DECL LIBC_INLINE
#define LIBC_PRINTF_DEFINE_MODULAR
#endif
#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_PRINTF_CONFIG_H

View File

@ -22,8 +22,8 @@ namespace LIBC_NAMESPACE_DECL {
namespace printf_core {
template <WriteMode write_mode>
int printf_main(Writer<write_mode> *writer, const char *__restrict str,
internal::ArgList &args) {
int printf_main_modular(Writer<write_mode> *writer, const char *__restrict str,
internal::ArgList &args) {
Parser<internal::ArgList> parser(str, args);
int result = 0;
for (FormatSection cur_section = parser.get_next_section();
@ -41,6 +41,15 @@ int printf_main(Writer<write_mode> *writer, const char *__restrict str,
return writer->get_chars_written();
}
template <WriteMode write_mode>
int printf_main(Writer<write_mode> *writer, const char *__restrict str,
internal::ArgList &args) {
#ifdef LIBC_COPT_PRINTF_MODULAR
LIBC_INLINE_ASM(".reloc ., BFD_RELOC_NONE, __printf_float");
#endif
return printf_main_modular(writer, str, args);
}
} // namespace printf_core
} // namespace LIBC_NAMESPACE_DECL

View File

@ -67,7 +67,7 @@ LIBC_INLINE int file_write_hook(cpp::string_view new_str, void *fp) {
return WRITE_OK;
}
LIBC_INLINE int vfprintf_internal(::FILE *__restrict stream,
LIBC_INLINE int vfprintf_internal_modular(::FILE *__restrict stream,
const char *__restrict format,
internal::ArgList &args) {
constexpr size_t BUFF_SIZE = 1024;
@ -76,7 +76,7 @@ LIBC_INLINE int vfprintf_internal(::FILE *__restrict stream,
buffer, BUFF_SIZE, &file_write_hook, reinterpret_cast<void *>(stream));
Writer writer(wb);
internal::flockfile(stream);
int retval = printf_main(&writer, format, args);
int retval = printf_main_modular(&writer, format, args);
int flushval = wb.overflow_write("");
if (flushval != WRITE_OK)
retval = flushval;
@ -84,6 +84,15 @@ LIBC_INLINE int vfprintf_internal(::FILE *__restrict stream,
return retval;
}
LIBC_INLINE int vfprintf_internal(::FILE *__restrict stream,
const char *__restrict format,
internal::ArgList &args) {
#ifdef LIBC_COPT_PRINTF_MODULAR
__asm__ __volatile__(".reloc ., BFD_RELOC_NONE, __printf_float");
#endif
return vfprintf_internal_modular(stream, format, args);
}
} // namespace printf_core
} // namespace LIBC_NAMESPACE_DECL

View File

@ -2635,6 +2635,28 @@ For example:
This attribute indicates that outlining passes should not modify the
function.
``"modular-format"="<type>,<string_idx>,<first_idx_to_check>,<modular_impl_fn>,<impl_name>,<aspects...>"``
This attribute indicates that the implementation is modular on a particular
format string argument . When the argument for a given call is constant, the
compiler may redirect the call to a modular implementation function
instead.
The compiler also emits relocations to report various aspects of the format
string and arguments that were present. The compiler reports an aspect 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
----------------------
@ -30630,6 +30652,38 @@ This intrinsic does nothing, but optimizers must consider it a use of its single
operand and should try to preserve the intrinsic and its position in the
function.
.. _llvm_reloc_none:
'``llvm.reloc.none``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Syntax:
"""""""
::
declare void @llvm.reloc.none(ptrty %ptr)
Overview:
"""""""""
The ``llvm.reloc.none`` intrinsic emits a no-op relocation against a given
operand symbol. This can bring the symbol
definition into the link without emitting any code or data to the binary for
that purpose.
Arguments:
""""""""""
The ``llvm.reloc.none`` intrinsic takes one argument, which may be any global
value.
Semantics:
""""""""""
This intrinsic emits a no-op relocation at the location of the intrinsic call
for the symbol that corresponds to the global value argument.
Stack Map Intrinsics
--------------------

View File

@ -1531,6 +1531,9 @@ enum NodeType {
#define BEGIN_REGISTER_VP_SDNODE(VPSDID, ...) VPSDID,
#include "llvm/IR/VPIntrinsics.def"
// Issue a no-op relocation against a given symbol at the current location.
RELOC_NONE,
// The `llvm.experimental.convergence.*` intrinsics.
CONVERGENCECTRL_ANCHOR,
CONVERGENCECTRL_ENTRY,

View File

@ -473,6 +473,7 @@ private:
void Select_WRITE_REGISTER(SDNode *Op);
void Select_UNDEF(SDNode *N);
void Select_FAKE_USE(SDNode *N);
void Select_RELOC_NONE(SDNode *N);
void CannotYetSelect(SDNode *N);
void Select_FREEZE(SDNode *N);

View File

@ -1913,6 +1913,9 @@ def int_threadlocal_address : DefaultAttrsIntrinsic<[llvm_anyptr_ty], [LLVMMatch
def int_stepvector : DefaultAttrsIntrinsic<[llvm_anyvector_ty],
[], [IntrNoMem]>;
def int_reloc_none : DefaultAttrsIntrinsic<[], [llvm_ptr_ty],
[IntrHasSideEffects, IntrInaccessibleMemOnly, IntrWillReturn]>;
//===---------------- Vector Predication Intrinsics --------------===//
// Memory Intrinsics
def int_vp_store : DefaultAttrsIntrinsic<[],

View File

@ -233,6 +233,9 @@ HANDLE_TARGET_OPCODE(MEMBARRIER)
// using.
HANDLE_TARGET_OPCODE(JUMP_TABLE_DEBUG_INFO)
// Issue a no-op relocation against a given symbol at the current location.
HANDLE_TARGET_OPCODE(RELOC_NONE)
HANDLE_TARGET_OPCODE(CONVERGENCECTRL_ENTRY)
HANDLE_TARGET_OPCODE(CONVERGENCECTRL_ANCHOR)
HANDLE_TARGET_OPCODE(CONVERGENCECTRL_LOOP)

View File

@ -1532,6 +1532,11 @@ def JUMP_TABLE_DEBUG_INFO : StandardPseudoInstruction {
let Size = 0;
let isMeta = true;
}
def RELOC_NONE : StandardPseudoInstruction {
let OutOperandList = (outs);
let InOperandList = (ins unknown:$symbol);
let hasSideEffects = true;
}
let hasSideEffects = false, isMeta = true, isConvergent = true in {
def CONVERGENCECTRL_ANCHOR : StandardPseudoInstruction {

View File

@ -2039,6 +2039,16 @@ void AsmPrinter::emitFunctionBody() {
// This is only used to influence register allocation behavior, no
// actual initialization is needed.
break;
case TargetOpcode::RELOC_NONE: {
// Generate a temporary label for the current PC.
MCSymbol *Sym = OutContext.createTempSymbol("reloc_none");
OutStreamer->emitLabel(Sym);
const MCExpr *Dot = MCSymbolRefExpr::create(Sym, OutContext);
const MCExpr *Value = MCSymbolRefExpr::create(
getSymbol(MI.getOperand(0).getGlobal()), OutContext);
OutStreamer->emitRelocDirective(*Dot, "BFD_RELOC_NONE", Value, SMLoc());
break;
}
default:
emitInstruction(&MI);

View File

@ -2669,6 +2669,11 @@ bool IRTranslator::translateKnownIntrinsic(const CallInst &CI, Intrinsic::ID ID,
case Intrinsic::experimental_convergence_entry:
case Intrinsic::experimental_convergence_loop:
return translateConvergenceControlIntrinsic(CI, ID, MIRBuilder);
case Intrinsic::reloc_none: {
MIRBuilder.buildInstr(TargetOpcode::RELOC_NONE)
.addGlobalAddress(cast<GlobalValue>(CI.getArgOperand(0)));
return true;
}
}
return false;
}

View File

@ -7751,6 +7751,17 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I,
return;
}
case Intrinsic::reloc_none: {
SDValue V = getValue(I.getArgOperand(0));
const auto *GA = cast<GlobalAddressSDNode>(V);
SDValue Ops[2];
Ops[0] = getRoot();
Ops[1] = DAG.getTargetGlobalAddress(GA->getGlobal(), sdl, V.getValueType(),
GA->getOffset());
DAG.setRoot(DAG.getNode(ISD::RELOC_NONE, sdl, MVT::Other, Ops));
return;
}
case Intrinsic::eh_exceptionpointer:
case Intrinsic::eh_exceptioncode: {
// Get the exception pointer vreg, copy from it, and resize it to fit.

View File

@ -471,6 +471,8 @@ std::string SDNode::getOperationName(const SelectionDAG *G) const {
case ISD::LIFETIME_END: return "lifetime.end";
case ISD::FAKE_USE:
return "fake_use";
case ISD::RELOC_NONE:
return "reloc_none";
case ISD::PSEUDO_PROBE:
return "pseudoprobe";
case ISD::GC_TRANSITION_START: return "gc_transition.start";

View File

@ -2521,6 +2521,11 @@ void SelectionDAGISel::Select_FAKE_USE(SDNode *N) {
N->getOperand(1), N->getOperand(0));
}
void SelectionDAGISel::Select_RELOC_NONE(SDNode *N) {
CurDAG->SelectNodeTo(N, TargetOpcode::RELOC_NONE, N->getValueType(0),
N->getOperand(1), N->getOperand(0));
}
void SelectionDAGISel::Select_FREEZE(SDNode *N) {
// TODO: We don't have FREEZE pseudo-instruction in MachineInstr-level now.
// If FREEZE instruction is added later, the code below must be changed as
@ -3296,6 +3301,9 @@ void SelectionDAGISel::SelectCodeCommon(SDNode *NodeToMatch,
case ISD::FAKE_USE:
Select_FAKE_USE(NodeToMatch);
return;
case ISD::RELOC_NONE:
Select_RELOC_NONE(NodeToMatch);
return;
case ISD::FREEZE:
Select_FREEZE(NodeToMatch);
return;

View File

@ -5921,6 +5921,11 @@ void Verifier::visitIntrinsicCall(Intrinsic::ID ID, CallBase &Call) {
Check(cast<ConstantInt>(Call.getArgOperand(3))->getZExtValue() < 2,
"cache type argument to llvm.prefetch must be 0-1", Call);
break;
case Intrinsic::reloc_none: {
Check(isa<GlobalValue>(Call.getArgOperand(0)),
"llvm.reloc.none argument must be a global value", &Call);
break;
}
case Intrinsic::stackprotector:
Check(isa<AllocaInst>(Call.getArgOperand(1)->stripPointerCasts()),
"llvm.stackprotector parameter #2 must resolve to an alloca.", Call);

View File

@ -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"
@ -4001,6 +4002,63 @@ 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
// TODO: Error handling
unsigned FirstArgIdx;
if (!llvm::to_integer(Args[2], FirstArgIdx))
return nullptr;
if (FirstArgIdx == 0)
return nullptr;
--FirstArgIdx;
StringRef FnName = Args[3];
StringRef ImplName = Args[4];
DenseSet<StringRef> Aspects(llvm::from_range,
ArrayRef<StringRef>(Args).drop_front(5));
Module *M = CI->getModule();
Function *Callee = CI->getCalledFunction();
FunctionCallee ModularFn =
M->getOrInsertFunction(FnName, Callee->getFunctionType(),
Callee->getAttributes().removeFnAttribute(
M->getContext(), "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;
Constant *Sym =
M->getOrInsertGlobal(Name, Type::getInt8Ty(M->getContext()));
Function *RelocNoneFn =
Intrinsic::getOrInsertDeclaration(M, Intrinsic::reloc_none);
B.CreateCall(RelocNoneFn, {Sym});
};
if (Aspects.contains("float")) {
Aspects.erase("float");
if (llvm::any_of(
llvm::make_range(std::next(CI->arg_begin(), FirstArgIdx),
CI->arg_end()),
[](Value *V) { return V->getType()->isFloatingPointTy(); }))
ReferenceAspect("float");
}
SmallVector<StringRef> UnknownAspects(Aspects.begin(), Aspects.end());
llvm::sort(UnknownAspects);
for (StringRef Request : UnknownAspects)
ReferenceAspect(Request);
return New;
}
Instruction *InstCombinerImpl::tryOptimizeCall(CallInst *CI) {
if (!CI->getCalledFunction()) return nullptr;
@ -4022,6 +4080,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;
}

View File

@ -70,11 +70,11 @@
# DEBUG-NEXT: .. the first uncovered type index: 1, OK
# DEBUG-NEXT: .. the first uncovered imm index: 0, OK
#
# DEBUG-NEXT: G_ABDS (opcode 65): 1 type index, 0 imm indices
# DEBUG-NEXT: G_ABDS (opcode 66): 1 type index, 0 imm indices
# DEBUG-NEXT: .. type index coverage check SKIPPED: user-defined predicate detected
# DEBUG-NEXT: .. imm index coverage check SKIPPED: user-defined predicate detected
#
# DEBUG-NEXT: G_ABDU (opcode 66): 1 type index, 0 imm indices
# DEBUG-NEXT: G_ABDU (opcode 67): 1 type index, 0 imm indices
# DEBUG-NEXT: .. opcode {{[0-9]+}} is aliased to {{[0-9]+}}
# DEBUG-NEXT: .. type index coverage check SKIPPED: user-defined predicate detected
# DEBUG-NEXT: .. imm index coverage check SKIPPED: user-defined predicate detected

View File

@ -0,0 +1,13 @@
; RUN: llc < %s | FileCheck %s
; CHECK: .reloc {{.*}}, BFD_RELOC_NONE, foo
%1 = type opaque
@foo = external global %1
define void @test_reloc_none() {
call void @llvm.reloc.none(ptr @foo)
ret void
}
declare void @llvm.reloc.none(ptr)

View File

@ -72,11 +72,11 @@
# DEBUG-NEXT: .. type index coverage check SKIPPED: user-defined predicate detected
# DEBUG-NEXT: .. imm index coverage check SKIPPED: user-defined predicate detected
#
# DEBUG-NEXT: G_ABDS (opcode 65): 1 type index, 0 imm indices
# DEBUG-NEXT: G_ABDS (opcode 66): 1 type index, 0 imm indices
# DEBUG-NEXT:.. type index coverage check SKIPPED: no rules defined
# DEBUG-NEXT:.. imm index coverage check SKIPPED: no rules defined
#
# DEBUG-NEXT:G_ABDU (opcode 66): 1 type index, 0 imm indices
# DEBUG-NEXT:G_ABDU (opcode 67): 1 type index, 0 imm indices
# DEBUG-NEXT:.. type index coverage check SKIPPED: no rules defined
# DEBUG-NEXT:.. imm index coverage check SKIPPED: no rules defined
#

View File

@ -0,0 +1,17 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc -mtriple=x86_64-linux-gnu -global-isel -verify-machineinstrs < %s -o - | FileCheck %s --check-prefix=CHECK
%1 = type opaque
@foo = external global %1
define void @test_reloc_none() {
; CHECK-LABEL: test_reloc_none:
; CHECK: # %bb.0:
; CHECK-NEXT: .Lreloc_none0:
; CHECK-NEXT: .reloc .Lreloc_none0, BFD_RELOC_NONE, foo
; CHECK-NEXT: retq
call void @llvm.reloc.none(ptr @foo)
ret void
}
declare void @llvm.reloc.none(ptr)

View File

@ -15,10 +15,10 @@ class I : Instruction {
// CHECK-NEXT: MCD::OPC_ExtractField, 0, 64,
// CHECK-NEXT: MCD::OPC_FilterValue, 1, 8, 0,
// CHECK-NEXT: MCD::OPC_CheckFieldOrFail, 127, 1, 1,
// CHECK-NEXT: MCD::OPC_Decode, 187, 2, 0,
// CHECK-NEXT: MCD::OPC_Decode, 188, 2, 0,
// CHECK-NEXT: MCD::OPC_FilterValueOrFail, 255, 255, 255, 255, 255, 255, 255, 255, 255, 1,
// CHECK-NEXT: MCD::OPC_CheckFieldOrFail, 127, 1, 0,
// CHECK-NEXT: MCD::OPC_Decode, 186, 2, 0,
// CHECK-NEXT: MCD::OPC_Decode, 187, 2, 0,
// CHECK-NEXT: MCD::OPC_Fail,
// CHECK-NEXT: 0
// CHECK-NEXT: };

View File

@ -96,7 +96,7 @@ def MyCombiner: GICombiner<"GenMyCombiner", [
// CHECK: const uint8_t *GenMyCombiner::getMatchTable() const {
// CHECK-NEXT: constexpr static uint8_t MatchTable0[] = {
// CHECK-NEXT: /* 0 */ GIM_SwitchOpcode, /*MI*/0, /*[*/GIMT_Encode2(99), GIMT_Encode2(210), /*)*//*default:*//*Label 5*/ GIMT_Encode4(520),
// CHECK-NEXT: /* 0 */ GIM_SwitchOpcode, /*MI*/0, /*[*/GIMT_Encode2(100), GIMT_Encode2(211), /*)*//*default:*//*Label 5*/ GIMT_Encode4(520),
// CHECK-NEXT: /* 10 */ /*TargetOpcode::G_STORE*//*Label 0*/ GIMT_Encode4(454), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0),
// CHECK-NEXT: /* 182 */ /*TargetOpcode::G_SEXT*//*Label 1*/ GIMT_Encode4(472), GIMT_Encode4(0),
// CHECK-NEXT: /* 190 */ /*TargetOpcode::G_ZEXT*//*Label 2*/ GIMT_Encode4(484), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0), GIMT_Encode4(0),

View File

@ -0,0 +1,13 @@
; RUN: not llvm-as -disable-output 2>&1 %s | FileCheck %s
; CHECK: llvm.reloc.none argument must be a global value
; CHECK-NEXT: call void @llvm.reloc.none(ptr %foo)
define void @test_reloc_none_bad_arg(ptr %foo) {
call void @llvm.reloc.none(ptr %foo)
ret void
}
declare void @llvm.reloc.none(ptr)
!0 = !{}