34 changes: 29 additions & 5 deletions clang/test/SemaCXX/cxx2b-deducing-this.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ struct Corresponding {
void d(this Corresponding&&);
void d(this const Corresponding&);
void d(this const int&);
void d(this const int);
void d(this int);
void d(this const int); // expected-note {{previous declaration is here}}
void d(this int); // expected-error {{class member cannot be redeclared}}

void e(this const Corresponding&&); // expected-note {{here}}
void e() const &&; // expected-error{{cannot be redeclared}}
Expand Down Expand Up @@ -171,9 +171,8 @@ struct CorrespondingTpl {
void d(this Corresponding&&);
void d(this const Corresponding&);
void d(this const int&);
void d(this const int);
void d(this int);

void d(this const int); // expected-note {{previous declaration is here}}
void d(this int); // expected-error {{class member cannot be redeclared}}
void e(this const CorrespondingTpl&&); // expected-note {{here}}
void e() const &&; // expected-error{{cannot be redeclared}}
};
Expand Down Expand Up @@ -1073,3 +1072,28 @@ int main() {
return foo[]; // expected-error {{no viable overloaded operator[] for type 'Foo'}}
}
}

namespace GH100394 {
struct C1 {
void f(this const C1);
void f() const; // ok
};

struct C2 {
void f(this const C2); // expected-note {{previous declaration is here}}
void f(this volatile C2); // expected-error {{class member cannot be redeclared}} \
// expected-warning {{volatile-qualified parameter type 'volatile C2' is deprecated}}
};

struct C3 {
void f(this volatile C3); // expected-note {{previous declaration is here}} \
// expected-warning {{volatile-qualified parameter type 'volatile C3' is deprecated}}
void f(this const C3); // expected-error {{class member cannot be redeclared}}
};

struct C4 {
void f(this const C4); // expected-note {{previous declaration is here}}
void f(this const volatile C4); // expected-error {{class member cannot be redeclared}} \
// expected-warning {{volatile-qualified parameter type 'const volatile C4' is deprecated}}
};
}
11 changes: 10 additions & 1 deletion clang/unittests/Format/ConfigParseTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ TEST(ConfigParseTest, ParsesConfigurationBools) {
CHECK_PARSE_BOOL(ObjCSpaceAfterProperty);
CHECK_PARSE_BOOL(ObjCSpaceBeforeProtocolList);
CHECK_PARSE_BOOL(Cpp11BracedListStyle);
CHECK_PARSE_BOOL(ReflowComments);
CHECK_PARSE_BOOL(RemoveBracesLLVM);
CHECK_PARSE_BOOL(RemoveSemicolon);
CHECK_PARSE_BOOL(SkipMacroDefinitionBody);
Expand Down Expand Up @@ -381,6 +380,16 @@ TEST(ConfigParseTest, ParsesConfiguration) {
CHECK_PARSE("PointerBindsToType: Middle", PointerAlignment,
FormatStyle::PAS_Middle);

Style.ReflowComments = FormatStyle::RCS_Always;
CHECK_PARSE("ReflowComments: Never", ReflowComments, FormatStyle::RCS_Never);
CHECK_PARSE("ReflowComments: IndentOnly", ReflowComments,
FormatStyle::RCS_IndentOnly);
CHECK_PARSE("ReflowComments: Always", ReflowComments,
FormatStyle::RCS_Always);
// For backward compatibility:
CHECK_PARSE("ReflowComments: false", ReflowComments, FormatStyle::RCS_Never);
CHECK_PARSE("ReflowComments: true", ReflowComments, FormatStyle::RCS_Always);

Style.Standard = FormatStyle::LS_Auto;
CHECK_PARSE("Standard: c++03", Standard, FormatStyle::LS_Cpp03);
CHECK_PARSE("Standard: c++11", Standard, FormatStyle::LS_Cpp11);
Expand Down
6 changes: 3 additions & 3 deletions clang/unittests/Format/FormatTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18108,7 +18108,7 @@ TEST_F(FormatTest, AlignConsecutiveMacros) {

// Test across comments
Style.MaxEmptyLinesToKeep = 10;
Style.ReflowComments = false;
Style.ReflowComments = FormatStyle::RCS_Never;
Style.AlignConsecutiveMacros.AcrossComments = true;
verifyFormat("#define a 3\n"
"// line comment\n"
Expand Down Expand Up @@ -18855,7 +18855,7 @@ TEST_F(FormatTest, AlignConsecutiveAssignmentsAcrossEmptyLinesAndComments) {
"y = 1;",
Alignment);

Alignment.ReflowComments = true;
Alignment.ReflowComments = FormatStyle::RCS_Always;
Alignment.ColumnLimit = 50;
verifyFormat("int x = 0;\n"
"int yy = 1; /// specificlennospace\n"
Expand Down Expand Up @@ -19253,7 +19253,7 @@ TEST_F(FormatTest, AlignConsecutiveAssignments) {
"y = 1;",
Alignment);

EXPECT_EQ(Alignment.ReflowComments, true);
EXPECT_EQ(Alignment.ReflowComments, FormatStyle::RCS_Always);
Alignment.ColumnLimit = 50;
verifyFormat("int x = 0;\n"
"int yy = 1; /// specificlennospace\n"
Expand Down
29 changes: 28 additions & 1 deletion clang/unittests/Format/FormatTestComments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,9 +520,36 @@ TEST_F(FormatTestComments, AlignsBlockComments) {

TEST_F(FormatTestComments, CommentReflowingCanBeTurnedOff) {
FormatStyle Style = getLLVMStyleWithColumns(20);
Style.ReflowComments = false;
Style.ReflowComments = FormatStyle::RCS_Never;
verifyFormat("// aaaaaaaaa aaaaaaaaaa aaaaaaaaaa", Style);
verifyFormat("/* aaaaaaaaa aaaaaaaaaa aaaaaaaaaa */", Style);
verifyNoChange("/* aaaaaaaaa aaaaaaaaaa aaaaaaaaaa\n"
"aaaaaaaaa*/",
Style);
verifyNoChange("/* aaaaaaaaa aaaaaaaaaa aaaaaaaaaa\n"
" aaaaaaaaa*/",
Style);
verifyNoChange("/* aaaaaaaaa aaaaaaaaaa aaaaaaaaaa\n"
" * aaaaaaaaa*/",
Style);
}

TEST_F(FormatTestComments, CommentReflowingCanApplyOnlyToIndents) {
FormatStyle Style = getLLVMStyleWithColumns(20);
Style.ReflowComments = FormatStyle::RCS_IndentOnly;
verifyFormat("// aaaaaaaaa aaaaaaaaaa aaaaaaaaaa", Style);
verifyFormat("/* aaaaaaaaa aaaaaaaaaa aaaaaaaaaa */", Style);
verifyNoChange("/* aaaaaaaaa aaaaaaaaaa aaaaaaaaaa\n"
"aaaaaaaaa*/",
Style);
verifyNoChange("/* aaaaaaaaa aaaaaaaaaa aaaaaaaaaa\n"
" aaaaaaaaa*/",
Style);
verifyFormat("/* aaaaaaaaa aaaaaaaaaa aaaaaaaaaa\n"
" * aaaaaaaaa*/",
"/* aaaaaaaaa aaaaaaaaaa aaaaaaaaaa\n"
" * aaaaaaaaa*/",
Style);
}

TEST_F(FormatTestComments, CorrectlyHandlesLengthOfBlockComments) {
Expand Down
1 change: 1 addition & 0 deletions clang/unittests/Format/FormatTestVerilog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,7 @@ TEST_F(FormatTestVerilog, Instantiation) {
" .qbar(out1),\n"
" .clear(in1),\n"
" .preset(in2));");
verifyNoCrash(", ff1();");
// With breaking between instance ports disabled.
auto Style = getDefaultStyle();
Style.VerilogBreakBetweenInstancePorts = false;
Expand Down
5 changes: 5 additions & 0 deletions compiler-rt/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ endif()
include(${LLVM_COMMON_CMAKE_UTILS}/Modules/CMakePolicy.cmake
NO_POLICY_SCOPE)

# TODO(CMake 3.22): remove
if(POLICY CMP0128)
cmake_policy(SET CMP0128 NEW)
endif()

# Check if compiler-rt is built as a standalone project.
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR OR COMPILER_RT_STANDALONE_BUILD)
project(CompilerRT C CXX ASM)
Expand Down
9 changes: 7 additions & 2 deletions compiler-rt/cmake/Modules/AddCompilerRT.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ endmacro()
# LINK_LIBS <linked libraries> (only for shared library)
# OBJECT_LIBS <object libraries to use as sources>
# PARENT_TARGET <convenience parent target>
# ADDITIONAL_HEADERS <header files>)
# ADDITIONAL_HEADERS <header files>
# EXTENSIONS <boolean>)
function(add_compiler_rt_runtime name type)
if(NOT type MATCHES "^(OBJECT|STATIC|SHARED|MODULE)$")
message(FATAL_ERROR
Expand All @@ -171,7 +172,7 @@ function(add_compiler_rt_runtime name type)
cmake_parse_arguments(LIB
""
"PARENT_TARGET"
"OS;ARCHS;SOURCES;CFLAGS;LINK_FLAGS;DEFS;DEPS;LINK_LIBS;OBJECT_LIBS;ADDITIONAL_HEADERS"
"OS;ARCHS;SOURCES;CFLAGS;LINK_FLAGS;DEFS;DEPS;LINK_LIBS;OBJECT_LIBS;ADDITIONAL_HEADERS;EXTENSIONS"
${ARGN})
set(libnames)
# Until we support this some other way, build compiler-rt runtime without LTO
Expand Down Expand Up @@ -435,6 +436,10 @@ function(add_compiler_rt_runtime name type)
if(type STREQUAL "SHARED")
rt_externalize_debuginfo(${libname})
endif()

if(DEFINED LIB_EXTENSIONS)
set_target_properties(${libname} PROPERTIES C_EXTENSIONS ${LIB_EXTENSIONS})
endif()
endforeach()
if(LIB_PARENT_TARGET)
add_dependencies(${LIB_PARENT_TARGET} ${libnames})
Expand Down
3 changes: 1 addition & 2 deletions compiler-rt/lib/asan/asan_fuchsia.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ static AsanThread *CreateAsanThread(StackTrace *stack, u32 parent_tid,
// In lieu of AsanThread::Create.
AsanThread *thread = (AsanThread *)MmapOrDie(AsanThreadMmapSize(), __func__);

AsanThreadContext::CreateThreadContextArgs args = {thread, stack};
u32 tid = asanThreadRegistry().CreateThread(0, detached, parent_tid, &args);
u32 tid = asanThreadRegistry().CreateThread(0, detached, parent_tid, thread);
asanThreadRegistry().SetThreadName(tid, name);

return thread;
Expand Down
16 changes: 10 additions & 6 deletions compiler-rt/lib/asan/asan_thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,15 @@
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_placement_new.h"
#include "sanitizer_common/sanitizer_stackdepot.h"
#include "sanitizer_common/sanitizer_thread_history.h"
#include "sanitizer_common/sanitizer_tls_get_addr.h"

namespace __asan {

// AsanThreadContext implementation.

void AsanThreadContext::OnCreated(void *arg) {
CreateThreadContextArgs *args = static_cast<CreateThreadContextArgs *>(arg);
if (args->stack)
stack_id = StackDepotPut(*args->stack);
thread = args->thread;
thread = static_cast<AsanThread *>(arg);
thread->set_context(this);
}

Expand Down Expand Up @@ -106,8 +104,8 @@ AsanThread *AsanThread::Create(const void *start_data, uptr data_size,
CHECK_LE(data_size, availible_size);
internal_memcpy(thread->start_data_, start_data, data_size);
}
AsanThreadContext::CreateThreadContextArgs args = {thread, stack};
asanThreadRegistry().CreateThread(0, detached, parent_tid, &args);
asanThreadRegistry().CreateThread(0, detached, parent_tid,
stack ? StackDepotPut(*stack) : 0, thread);

return thread;
}
Expand Down Expand Up @@ -558,6 +556,12 @@ void GetRunningThreadsLocked(InternalMmapVector<tid_t> *threads) {
threads);
}

void PrintThreads() {
InternalScopedString out;
PrintThreadHistory(__asan::asanThreadRegistry(), out);
Report("%s\n", out.data());
}

} // namespace __lsan

// ---------------------- Interface ---------------- {{{1
Expand Down
11 changes: 3 additions & 8 deletions compiler-rt/lib/asan/asan_thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,16 @@ class AsanThread;
class AsanThreadContext final : public ThreadContextBase {
public:
explicit AsanThreadContext(int tid)
: ThreadContextBase(tid), announced(false),
destructor_iterations(GetPthreadDestructorIterations()), stack_id(0),
: ThreadContextBase(tid),
announced(false),
destructor_iterations(GetPthreadDestructorIterations()),
thread(nullptr) {}
bool announced;
u8 destructor_iterations;
u32 stack_id;
AsanThread *thread;

void OnCreated(void *arg) override;
void OnFinished() override;

struct CreateThreadContextArgs {
AsanThread *thread;
StackTrace *stack;
};
};

// AsanThreadContext objects are never freed, so we need many of them.
Expand Down
4 changes: 2 additions & 2 deletions compiler-rt/lib/builtins/cpu_model/riscv.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ struct {

struct {
unsigned mvendorid;
unsigned marchid;
unsigned mimpid;
unsigned long long marchid;
unsigned long long mimpid;
} __riscv_cpu_model __attribute__((visibility("hidden"), nocommon));

// NOTE: Should sync-up with RISCVFeatures.td
Expand Down
7 changes: 6 additions & 1 deletion compiler-rt/lib/hwasan/hwasan_thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ void GetAdditionalThreadContextPtrsLocked(InternalMmapVector<uptr> *ptrs) {
__hwasan::hwasanThreadArgRetval().GetAllPtrsLocked(ptrs);
}

void GetRunningThreadsLocked(InternalMmapVector<tid_t> *threads) {}
void GetRunningThreadsLocked(InternalMmapVector<tid_t> *threads) {
// TODO: implement.
}
void PrintThreads() {
// TODO: implement.
}

} // namespace __lsan
17 changes: 12 additions & 5 deletions compiler-rt/lib/lsan/lsan_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -771,14 +771,15 @@ static bool PrintResults(LeakReport &report) {
}
if (common_flags()->print_suppressions)
GetSuppressionContext()->PrintMatchedSuppressions();
if (unsuppressed_count > 0) {
if (unsuppressed_count)
report.PrintSummary();
return true;
}
return false;
if ((unsuppressed_count && common_flags()->verbosity >= 2) ||
flags()->log_threads)
PrintThreads();
return unsuppressed_count;
}

static bool CheckForLeaks() {
static bool CheckForLeaksOnce() {
if (&__lsan_is_turned_off && __lsan_is_turned_off()) {
VReport(1, "LeakSanitizer is disabled\n");
return false;
Expand Down Expand Up @@ -830,6 +831,12 @@ static bool CheckForLeaks() {
}
}

static bool CheckForLeaks() {
int leaking_tries = 0;
for (int i = 0; i < flags()->tries; ++i) leaking_tries += CheckForLeaksOnce();
return leaking_tries == flags()->tries;
}

static bool has_reported_leaks = false;
bool HasReportedLeaks() { return has_reported_leaks; }

Expand Down
1 change: 1 addition & 0 deletions compiler-rt/lib/lsan/lsan_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ void GetThreadExtraStackRangesLocked(tid_t os_id,
InternalMmapVector<Range> *ranges);
void GetAdditionalThreadContextPtrsLocked(InternalMmapVector<uptr> *ptrs);
void GetRunningThreadsLocked(InternalMmapVector<tid_t> *threads);
void PrintThreads();

//// --------------------------------------------------------------------------
//// Allocator prototypes.
Expand Down
1 change: 1 addition & 0 deletions compiler-rt/lib/lsan/lsan_flags.inc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ LSAN_FLAG(bool, use_poisoned, false,
"Consider pointers found in poisoned memory to be valid.")
LSAN_FLAG(bool, log_pointers, false, "Debug logging")
LSAN_FLAG(bool, log_threads, false, "Debug logging")
LSAN_FLAG(int, tries, 1, "Debug option to repeat leak checking multiple times")
LSAN_FLAG(const char *, suppressions, "", "Suppressions file name.")
LSAN_FLAG(int, thread_suspend_fail, 1,
"Behaviour if thread suspendion all thread (0 - "
Expand Down
7 changes: 7 additions & 0 deletions compiler-rt/lib/lsan/lsan_thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "lsan_common.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_placement_new.h"
#include "sanitizer_common/sanitizer_thread_history.h"
#include "sanitizer_common/sanitizer_thread_registry.h"
#include "sanitizer_common/sanitizer_tls_get_addr.h"

Expand Down Expand Up @@ -109,6 +110,12 @@ void GetRunningThreadsLocked(InternalMmapVector<tid_t> *threads) {
threads);
}

void PrintThreads() {
InternalScopedString out;
PrintThreadHistory(*thread_registry, out);
Report("%s\n", out.data());
}

void GetAdditionalThreadContextPtrsLocked(InternalMmapVector<uptr> *ptrs) {
GetThreadArgRetval().GetAllPtrsLocked(ptrs);
}
Expand Down
9 changes: 3 additions & 6 deletions compiler-rt/lib/memprof/memprof_thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ namespace __memprof {
// MemprofThreadContext implementation.

void MemprofThreadContext::OnCreated(void *arg) {
CreateThreadContextArgs *args = static_cast<CreateThreadContextArgs *>(arg);
if (args->stack)
stack_id = StackDepotPut(*args->stack);
thread = args->thread;
thread = static_cast<MemprofThread *>(arg);
thread->set_context(this);
}

Expand Down Expand Up @@ -79,8 +76,8 @@ MemprofThread *MemprofThread::Create(thread_callback_t start_routine, void *arg,
MemprofThread *thread = (MemprofThread *)MmapOrDie(size, __func__);
thread->start_routine_ = start_routine;
thread->arg_ = arg;
MemprofThreadContext::CreateThreadContextArgs args = {thread, stack};
memprofThreadRegistry().CreateThread(0, detached, parent_tid, &args);
memprofThreadRegistry().CreateThread(
0, detached, parent_tid, stack ? StackDepotPut(*stack) : 0, thread);

return thread;
}
Expand Down
8 changes: 1 addition & 7 deletions compiler-rt/lib/memprof/memprof_thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,14 @@ class MemprofThread;
struct MemprofThreadContext final : public ThreadContextBase {
explicit MemprofThreadContext(int tid)
: ThreadContextBase(tid), announced(false),
destructor_iterations(GetPthreadDestructorIterations()), stack_id(0),
destructor_iterations(GetPthreadDestructorIterations()),
thread(nullptr) {}
bool announced;
u8 destructor_iterations;
u32 stack_id;
MemprofThread *thread;

void OnCreated(void *arg) override;
void OnFinished() override;

struct CreateThreadContextArgs {
MemprofThread *thread;
StackTrace *stack;
};
};

// MemprofThreadContext objects are never freed, so we need many of them.
Expand Down
6 changes: 4 additions & 2 deletions compiler-rt/lib/profile/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -138,13 +138,15 @@ if(APPLE)
CFLAGS ${EXTRA_FLAGS}
SOURCES ${PROFILE_SOURCES}
ADDITIONAL_HEADERS ${PROFILE_HEADERS}
PARENT_TARGET profile)
PARENT_TARGET profile
EXTENSIONS ON)
else()
add_compiler_rt_runtime(clang_rt.profile
STATIC
ARCHS ${PROFILE_SUPPORTED_ARCH}
CFLAGS ${EXTRA_FLAGS}
SOURCES ${PROFILE_SOURCES}
ADDITIONAL_HEADERS ${PROFILE_HEADERS}
PARENT_TARGET profile)
PARENT_TARGET profile
EXTENSIONS ON)
endif()
3 changes: 3 additions & 0 deletions compiler-rt/lib/rtsan/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ set(RTSAN_CXX_SOURCES
rtsan_flags.cpp
rtsan_interceptors.cpp
rtsan_stats.cpp
rtsan_suppressions.cpp
)

set(RTSAN_PREINIT_SOURCES
rtsan_preinit.cpp)

set(RTSAN_HEADERS
rtsan.h
rtsan_checks.inc
rtsan_assertions.h
rtsan_context.h
rtsan_diagnostics.h
rtsan_flags.h
rtsan_flags.inc
rtsan_stats.h
rtsan_suppressions.h
)

set(RTSAN_DEPS)
Expand Down
4 changes: 3 additions & 1 deletion compiler-rt/lib/rtsan/rtsan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
#include "rtsan/rtsan_flags.h"
#include "rtsan/rtsan_interceptors.h"
#include "rtsan/rtsan_stats.h"
#include "rtsan/rtsan_suppressions.h"

#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_mutex.h"
#include "sanitizer_common/sanitizer_stackdepot.h"
#include "sanitizer_common/sanitizer_stacktrace.h"

using namespace __rtsan;
using namespace __sanitizer;
Expand Down Expand Up @@ -85,6 +85,8 @@ SANITIZER_INTERFACE_ATTRIBUTE void __rtsan_init() {
InitializeFlags();
InitializeInterceptors();

InitializeSuppressions();

if (flags().print_stats_on_exit)
Atexit(PrintStatisticsSummary);

Expand Down
4 changes: 4 additions & 0 deletions compiler-rt/lib/rtsan/rtsan_assertions.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "rtsan/rtsan.h"
#include "rtsan/rtsan_context.h"
#include "rtsan/rtsan_diagnostics.h"
#include "rtsan/rtsan_suppressions.h"

#include "sanitizer_common/sanitizer_stacktrace.h"

Expand All @@ -34,6 +35,9 @@ void ExpectNotRealtime(Context &context, const DiagnosticsInfo &info,
stack.Unwind(info.pc, info.bp, nullptr,
__sanitizer::common_flags()->fast_unwind_on_fatal);

if (IsStackTraceSuppressed(stack))
return;

OnViolation(stack, info);
}
}
Expand Down
19 changes: 19 additions & 0 deletions compiler-rt/lib/rtsan/rtsan_checks.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//===-- rtsan_checks.inc ----------------------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// List of suppression checks handled by RTSan runtime.
//
//===----------------------------------------------------------------------===//
#ifndef RTSAN_CHECK
#error "Define RTSAN_CHECK prior to including this file!"
#endif

// RTSAN_CHECK(Name, SummaryKind)
// SummaryKind should be a string literal.

RTSAN_CHECK(CallStackContains, "call-stack-contains")
1 change: 1 addition & 0 deletions compiler-rt/lib/rtsan/rtsan_flags.inc
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@

RTSAN_FLAG(bool, halt_on_error, true, "Exit after first reported error.")
RTSAN_FLAG(bool, print_stats_on_exit, false, "Print stats on exit.")
RTSAN_FLAG(const char *, suppressions, "", "Suppressions file name.")
94 changes: 94 additions & 0 deletions compiler-rt/lib/rtsan/rtsan_suppressions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//===--- rtsan_suppressions.cpp - Realtime Sanitizer ------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file is a part of the RTSan runtime, providing support for suppressions
//
//===----------------------------------------------------------------------===//

#include "rtsan/rtsan_suppressions.h"

#include "rtsan/rtsan_flags.h"

#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_internal_defs.h"
#include "sanitizer_common/sanitizer_suppressions.h"
#include "sanitizer_common/sanitizer_symbolizer.h"

#include <new>

using namespace __sanitizer;
using namespace __rtsan;

namespace {
enum class ErrorType {
#define RTSAN_CHECK(Name, FSanitizeFlagName) Name,
#include "rtsan_checks.inc"
#undef RTSAN_CHECK
};
} // namespace

alignas(64) static char suppression_placeholder[sizeof(SuppressionContext)];
static SuppressionContext *suppression_ctx = nullptr;

static const char *kSuppressionTypes[] = {
#define RTSAN_CHECK(Name, FSanitizeFlagName) FSanitizeFlagName,
#include "rtsan_checks.inc"
#undef RTSAN_CHECK
};

static const char *ConvertTypeToFlagName(ErrorType Type) {
switch (Type) {
#define RTSAN_CHECK(Name, FSanitizeFlagName) \
case ErrorType::Name: \
return FSanitizeFlagName;
#include "rtsan_checks.inc"
#undef RTSAN_CHECK
}
UNREACHABLE("unknown ErrorType!");
}

void __rtsan::InitializeSuppressions() {
CHECK_EQ(nullptr, suppression_ctx);

// We will use suppression_ctx == nullptr as an early out
if (flags().suppressions[0] == '\0')
return;

suppression_ctx = new (suppression_placeholder)
SuppressionContext(kSuppressionTypes, ARRAY_SIZE(kSuppressionTypes));
suppression_ctx->ParseFromFile(flags().suppressions);
}

bool __rtsan::IsStackTraceSuppressed(const StackTrace &stack) {
if (suppression_ctx == nullptr)
return false;

const char *call_stack_flag =
ConvertTypeToFlagName(ErrorType::CallStackContains);
if (!suppression_ctx->HasSuppressionType(call_stack_flag))
return false;

Symbolizer *symbolizer = Symbolizer::GetOrInit();
for (uptr i = 0; i < stack.size && stack.trace[i]; i++) {
const uptr addr = stack.trace[i];

SymbolizedStackHolder symbolized_stack(symbolizer->SymbolizePC(addr));
const SymbolizedStack *frames = symbolized_stack.get();
CHECK(frames);
for (const SymbolizedStack *cur = frames; cur; cur = cur->next) {
const char *function_name = cur->info.function;
if (!function_name)
continue;

Suppression *s;
if (suppression_ctx->Match(function_name, call_stack_flag, &s))
return true;
}
}
return false;
}
22 changes: 22 additions & 0 deletions compiler-rt/lib/rtsan/rtsan_suppressions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//===--- rtsan_suppressions.h - Realtime Sanitizer --------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file is a part of the RTSan runtime, providing support for suppressions
//
//===----------------------------------------------------------------------===//

#pragma once

#include "sanitizer_common/sanitizer_stacktrace.h"

namespace __rtsan {

void InitializeSuppressions();
bool IsStackTraceSuppressed(const __sanitizer::StackTrace &stack);

} // namespace __rtsan
1 change: 1 addition & 0 deletions compiler-rt/lib/sanitizer_common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ set(SANITIZER_SYMBOLIZER_SOURCES
sanitizer_symbolizer_report.cpp
sanitizer_symbolizer_report_fuchsia.cpp
sanitizer_symbolizer_win.cpp
sanitizer_thread_history.cpp
sanitizer_unwind_linux_libcdep.cpp
sanitizer_unwind_fuchsia.cpp
sanitizer_unwind_win.cpp
Expand Down
72 changes: 72 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_thread_history.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//===-- sanitizer_thread_history.cpp --------------------------------------===//
//
// 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 "sanitizer_thread_history.h"

#include "sanitizer_stackdepot.h"
namespace __sanitizer {

void PrintThreadHistory(ThreadRegistry &registry, InternalScopedString &out) {
ThreadRegistryLock l(&registry);
// Stack traces are largest part of printout and they often the same for
// multiple threads, so we will deduplicate them.
InternalMmapVector<const ThreadContextBase *> stacks;

registry.RunCallbackForEachThreadLocked(
[](ThreadContextBase *context, void *arg) {
static_cast<decltype(&stacks)>(arg)->push_back(context);
},
&stacks);

Sort(stacks.data(), stacks.size(),
[](const ThreadContextBase *a, const ThreadContextBase *b) {
if (a->stack_id < b->stack_id)
return true;
if (a->stack_id > b->stack_id)
return false;
return a->unique_id < b->unique_id;
});

auto describe_thread = [&](const ThreadContextBase *context) {
if (!context) {
out.Append("T-1");
return;
}
out.AppendF("T%llu/%llu", context->unique_id, context->os_id);
if (internal_strlen(context->name))
out.AppendF(" (%s)", context->name);
};

auto get_parent =
[&](const ThreadContextBase *context) -> const ThreadContextBase * {
if (!context)
return nullptr;
ThreadContextBase *parent = registry.GetThreadLocked(context->parent_tid);
if (!parent)
return nullptr;
if (parent->unique_id >= context->unique_id)
return nullptr;
return parent;
};

const ThreadContextBase *prev = nullptr;
for (const ThreadContextBase *context : stacks) {
if (prev && prev->stack_id != context->stack_id)
StackDepotGet(prev->stack_id).PrintTo(&out);
prev = context;
out.Append("Thread ");
describe_thread(context);
out.Append(" was created by ");
describe_thread(get_parent(context));
out.Append("\n");
}
if (prev)
StackDepotGet(prev->stack_id).PrintTo(&out);
}

} // namespace __sanitizer
24 changes: 24 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_thread_history.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//===-- sanitizer_thread_history.h ------------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Utility to print thread histroy from ThreadRegistry.
//
//===----------------------------------------------------------------------===//

#ifndef SANITIZER_THREAD_HISTORY_H
#define SANITIZER_THREAD_HISTORY_H

#include "sanitizer_thread_registry.h"

namespace __sanitizer {

void PrintThreadHistory(ThreadRegistry& registry, InternalScopedString& out);

} // namespace __sanitizer

#endif // SANITIZER_THREAD_HISTORY_H
13 changes: 9 additions & 4 deletions compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ ThreadContextBase::ThreadContextBase(u32 tid)
detached(false),
thread_type(ThreadType::Regular),
parent_tid(0),
stack_id(0),
next(0) {
name[0] = '\0';
atomic_store(&thread_destroyed, 0, memory_order_release);
Expand Down Expand Up @@ -88,14 +89,17 @@ void ThreadContextBase::SetStarted(tid_t _os_id, ThreadType _thread_type,
}

void ThreadContextBase::SetCreated(uptr _user_id, u64 _unique_id,
bool _detached, u32 _parent_tid, void *arg) {
bool _detached, u32 _parent_tid,
u32 _stack_tid, void *arg) {
status = ThreadStatusCreated;
user_id = _user_id;
unique_id = _unique_id;
detached = _detached;
// Parent tid makes no sense for the main thread.
if (tid != kMainTid)
if (tid != kMainTid) {
parent_tid = _parent_tid;
stack_id = _stack_tid;
}
OnCreated(arg);
}

Expand Down Expand Up @@ -143,7 +147,7 @@ uptr ThreadRegistry::GetMaxAliveThreads() {
}

u32 ThreadRegistry::CreateThread(uptr user_id, bool detached, u32 parent_tid,
void *arg) {
u32 stack_tid, void *arg) {
ThreadRegistryLock l(this);
u32 tid = kInvalidTid;
ThreadContextBase *tctx = QuarantinePop();
Expand Down Expand Up @@ -181,7 +185,8 @@ u32 ThreadRegistry::CreateThread(uptr user_id, bool detached, u32 parent_tid,
// positives later (e.g. if we join a wrong thread).
CHECK(live_.try_emplace(user_id, tid).second);
}
tctx->SetCreated(user_id, total_threads_++, detached, parent_tid, arg);
tctx->SetCreated(user_id, total_threads_++, detached, parent_tid, stack_tid,
arg);
return tid;
}

Expand Down
9 changes: 7 additions & 2 deletions compiler-rt/lib/sanitizer_common/sanitizer_thread_registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class ThreadContextBase {
ThreadType thread_type;

u32 parent_tid;
u32 stack_id;
ThreadContextBase *next; // For storing thread contexts in a list.

atomic_uint32_t thread_destroyed; // To address race of Joined vs Finished
Expand All @@ -63,7 +64,7 @@ class ThreadContextBase {
void SetFinished();
void SetStarted(tid_t _os_id, ThreadType _thread_type, void *arg);
void SetCreated(uptr _user_id, u64 _unique_id, bool _detached,
u32 _parent_tid, void *arg);
u32 _parent_tid, u32 _stack_tid, void *arg);
void Reset();

void SetDestroyed();
Expand Down Expand Up @@ -106,7 +107,11 @@ class SANITIZER_MUTEX ThreadRegistry {

u32 NumThreadsLocked() const { return threads_.size(); }

u32 CreateThread(uptr user_id, bool detached, u32 parent_tid, void *arg);
u32 CreateThread(uptr user_id, bool detached, u32 parent_tid, u32 stack_tid,
void *arg);
u32 CreateThread(uptr user_id, bool detached, u32 parent_tid, void *arg) {
return CreateThread(user_id, detached, parent_tid, 0, arg);
}

typedef void (*ThreadCallback)(ThreadContextBase *tctx, void *arg);
// Invokes callback with a specified arg for each thread context.
Expand Down
40 changes: 39 additions & 1 deletion compiler-rt/lib/sanitizer_common/sanitizer_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,45 @@ SignalContext::WriteFlag SignalContext::GetWriteFlag() const {
}

void SignalContext::DumpAllRegisters(void *context) {
// FIXME: Implement this.
CONTEXT *ctx = (CONTEXT *)context;
# if defined(__M_X64)
Report("Register values:\n");
Printf("rax = %llx ", ctx->Rax);
Printf("rbx = %llx ", ctx->Rbx);
Printf("rcx = %llx ", ctx->Rcx);
Printf("rdx = %llx ", ctx->Rdx);
Printf("\n");
Printf("rdi = %llx ", ctx->Rdi);
Printf("rsi = %llx ", ctx->Rsi);
Printf("rbp = %llx ", ctx->Rbp);
Printf("rsp = %llx ", ctx->Rsp);
Printf("\n");
Printf("r8 = %llx ", ctx->R8);
Printf("r9 = %llx ", ctx->R9);
Printf("r10 = %llx ", ctx->R10);
Printf("r11 = %llx ", ctx->R11);
Printf("\n");
Printf("r12 = %llx ", ctx->R12);
Printf("r13 = %llx ", ctx->R13);
Printf("r14 = %llx ", ctx->R14);
Printf("r15 = %llx ", ctx->R15);
Printf("\n");
# elif defined(_M_IX86)
Report("Register values:\n");
Printf("eax = %lx ", ctx->Eax);
Printf("ebx = %lx ", ctx->Ebx);
Printf("ecx = %lx ", ctx->Ecx);
Printf("edx = %lx ", ctx->Edx);
Printf("\n");
Printf("edi = %lx ", ctx->Edi);
Printf("esi = %lx ", ctx->Esi);
Printf("ebp = %lx ", ctx->Ebp);
Printf("esp = %lx ", ctx->Esp);
Printf("\n");
# else
// TODO
(void)ctx;
# endif
}

int SignalContext::GetType() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@
//===----------------------------------------------------------------------===//
#include "sanitizer_common/sanitizer_thread_registry.h"

#include <iostream>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_stackdepot.h"
#include "sanitizer_common/sanitizer_stacktrace.h"
#include "sanitizer_common/sanitizer_thread_history.h"
#include "sanitizer_pthread_wrappers.h"

using testing::HasSubstr;

namespace __sanitizer {

static Mutex tctx_allocator_lock;
Expand Down Expand Up @@ -64,11 +72,12 @@ static void MarkUidAsPresent(ThreadContextBase *tctx, void *arg) {

static void TestRegistry(ThreadRegistry *registry, bool has_quarantine) {
// Create and start a main thread.
EXPECT_EQ(0U, registry->CreateThread(get_uid(0), true, -1, 0));
EXPECT_EQ(0U, registry->CreateThread(get_uid(0), true, -1, 0, nullptr));
registry->StartThread(0, 0, ThreadType::Regular, 0);
// Create a bunch of threads.
for (u32 i = 1; i <= 10; i++) {
EXPECT_EQ(i, registry->CreateThread(get_uid(i), is_detached(i), 0, 0));
EXPECT_EQ(i, registry->CreateThread(get_uid(i), is_detached(i), 100 + i,
200 + i, nullptr));
}
CheckThreadQuantity(registry, 11, 1, 11);
// Start some of them.
Expand All @@ -88,19 +97,27 @@ static void TestRegistry(ThreadRegistry *registry, bool has_quarantine) {
std::vector<u32> new_tids;
for (u32 i = 11; i <= 15; i++) {
new_tids.push_back(
registry->CreateThread(get_uid(i), is_detached(i), 0, 0));
registry->CreateThread(get_uid(i), is_detached(i), 0, 0, nullptr));
}
ASSERT_LE(kRegistryQuarantine, 5U);
u32 exp_total = 16 - (has_quarantine ? 5 - kRegistryQuarantine : 0);
u32 exp_total = 16 - (has_quarantine ? 5 - kRegistryQuarantine : 0);
CheckThreadQuantity(registry, exp_total, 6, 11);
// Test SetThreadName and FindThread.
registry->SetThreadName(6, "six");
registry->SetThreadName(7, "seven");
EXPECT_EQ(7U, registry->FindThread(HasName, (void*)"seven"));
EXPECT_EQ(7U, registry->FindThread(HasName, (void *)"seven"));
EXPECT_EQ(kInvalidTid, registry->FindThread(HasName, (void *)"none"));
EXPECT_EQ(0U, registry->FindThread(HasUid, (void*)get_uid(0)));
EXPECT_EQ(10U, registry->FindThread(HasUid, (void*)get_uid(10)));
EXPECT_EQ(0U, registry->FindThread(HasUid, (void *)get_uid(0)));
EXPECT_EQ(10U, registry->FindThread(HasUid, (void *)get_uid(10)));
EXPECT_EQ(kInvalidTid, registry->FindThread(HasUid, (void *)0x1234));
EXPECT_EQ(7U,
registry->FindThread([](ThreadContextBase *tctx,
void *) { return tctx->parent_tid == 107; },
nullptr));
EXPECT_EQ(8U,
registry->FindThread([](ThreadContextBase *tctx,
void *) { return tctx->stack_id == 208; },
nullptr));
// Detach and finish and join remaining threads.
for (u32 i = 6; i <= 10; i++) {
registry->DetachThread(i, 0);
Expand Down Expand Up @@ -230,4 +247,56 @@ TEST(SanitizerCommon, ThreadRegistryThreadedTest) {
ThreadedTestRegistry(&registry);
}

TEST(SanitizerCommon, PrintThreadHistory) {
ThreadRegistry registry(GetThreadContext<TestThreadContext>,
kThreadsPerShard * kNumShards + 1, 10, 0);

UNINITIALIZED BufferedStackTrace stack1;
stack1.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, false,
/*max_depth=*/1);

UNINITIALIZED BufferedStackTrace stack2;
stack2.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, false,
/*max_depth=*/1);

EXPECT_EQ(0U, registry.CreateThread(0, true, -1, 0, nullptr));
for (int i = 0; i < 5; i++) {
registry.CreateThread(0, true, 0, StackDepotPut(stack1), nullptr);
registry.CreateThread(0, true, 0, StackDepotPut(stack2), nullptr);
}

InternalScopedString out;
PrintThreadHistory(registry, out);

std::string substrings[] = {
"Thread T0/0 was created by T-1",
"<empty stack>",
"",
"Thread T1/0 was created by T0/0",
"Thread T3/0 was created by T0/0",
"Thread T5/0 was created by T0/0",
"Thread T7/0 was created by T0/0",
"Thread T9/0 was created by T0/0",
"#0 0x",
"",
"Thread T2/0 was created by T0/0",
"Thread T4/0 was created by T0/0",
"Thread T6/0 was created by T0/0",
"Thread T8/0 was created by T0/0",
"Thread T10/0 was created by T0/0",
"#0 0x",
"",
};

std::stringstream ss(out.data());
std::string line;

for (auto substr : substrings) {
std::getline(ss, line);
EXPECT_THAT(line, HasSubstr(substr)) << line;
}

EXPECT_FALSE(std::getline(ss, line)) << "Unmatched line: " << line;
}

} // namespace __sanitizer
2 changes: 1 addition & 1 deletion compiler-rt/test/fuzzer/strncmp.test
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
UNSUPPORTED: target={{.*freebsd.*}}
RUN: %cpp_compiler %S/StrncmpTest.cpp -o %t-StrncmpTest
RUN: not %run %t-StrncmpTest -seed=2 -runs=10000000 2>&1 | FileCheck %s
RUN: not %run %t-StrncmpTest -seed=2 -runs=20000000 2>&1 | FileCheck %s
CHECK: BINGO
23 changes: 23 additions & 0 deletions compiler-rt/test/lsan/TestCases/flag_tries.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Test retries option of lsan.
// RUN: %clang_lsan %s -o %t
// RUN: %env_lsan_opts=use_stacks=0:use_registers=0:symbolize=0 %run %t foo 2>&1 | FileCheck %s --check-prefixes=CHECK,CHECK1
// RUN: %env_lsan_opts=use_stacks=0:use_registers=0:symbolize=0:tries=12 %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK,CHECK12

#include <assert.h>
#include <sanitizer/lsan_interface.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void *p;

int main(int argc, char *argv[]) {
fprintf(stderr, "Test alloc: %p.\n", malloc(1337));
// CHECK: Test alloc:

assert(__lsan_do_recoverable_leak_check() == 1);
// CHECK1-COUNT-1: SUMMARY: {{.*}}Sanitizer: 1337 byte
// CHECK12-COUNT-12: SUMMARY: {{.*}}Sanitizer: 1337 byte

_exit(0);
}
33 changes: 33 additions & 0 deletions compiler-rt/test/lsan/TestCases/print_threads.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// RUN: %clang_lsan %s -o %t && %env_lsan_opts=log_threads=1 %run %t 2>&1 | FileCheck %s

// XFAIL: hwasan

#include <assert.h>
#include <pthread.h>
#include <sanitizer/lsan_interface.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

pthread_barrier_t bar;

void *threadfn(void *arg) {
pthread_barrier_wait(&bar);
sleep(10000);
return 0;
}

int main(int argc, char *argv[]) {
pthread_t thread_id;
pthread_barrier_init(&bar, 0, 3);

pthread_create(&thread_id, 0, threadfn, 0);
pthread_create(&thread_id, 0, threadfn, 0);

pthread_barrier_wait(&bar);
return 0;
}

// CHECK: Thread T0/{{[0-9]+}} was created by T-1
// CHECK: Thread T1/{{[0-9]+}} was created by T0/
// CHECK: Thread T2/{{[0-9]+}} was created by T0/
53 changes: 53 additions & 0 deletions compiler-rt/test/rtsan/stack_suppressions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// RUN: %clangxx -fsanitize=realtime %s -o %t
// RUN: %env_rtsan_opts=suppressions='%s.supp' not %run %t 2>&1 | FileCheck %s
// UNSUPPORTED: ios

// Intent: Ensure that suppressions work as intended

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <vector>

void *MallocViolation() { return malloc(10); }

void VectorViolations() {
// All of these should be suppressed by *vector*
std::vector<int> v(10);
v.resize(20);
v.clear();
v.resize(0);
v.push_back(1);
v.reserve(10);
}

void BlockFunc() [[clang::blocking]] { usleep(1); }

void *process() [[clang::nonblocking]] {
void *ptr = MallocViolation();
VectorViolations();
BlockFunc();
free(ptr);

// This is the one that should abort the program
// Everything else is suppressed
usleep(1);

return ptr;
}

int main() {
process();
return 0;
}

// CHECK-NOT: failed to open suppressions file
// CHECK: Intercepted call to real-time unsafe function
// CHECK-SAME: usleep

// CHECK-NOT: Intercepted call to real-time unsafe function
// CHECK-NOT: malloc
// CHECK-NOT: vector
// CHECK-NOT: free
// CHECK-NOT: BlockFunc
4 changes: 4 additions & 0 deletions compiler-rt/test/rtsan/stack_suppressions.cpp.supp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
call-stack-contains:MallocViolation
call-stack-contains:std::*vector
call-stack-contains:free
call-stack-contains:BlockFunc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Check that sanitizer prints registers dump_registers on dump_registers=1
// RUN: %clangxx %s -o %t
// RUN: %env_tool_opts=dump_registers=0 not %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK-NODUMP --strict-whitespace
// RUN: not %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK-DUMP --strict-whitespace
//
// REQUIRES: i386-pc-windows-msvc

#include <windows.h>

int main() {
RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, NULL);
// CHECK-DUMP: Register values
// CHECK-DUMP-NEXT: eax = {{0x[0-9a-f]+}} ebx = {{0x[0-9a-f]+}} ecx = {{0x[0-9a-f]+}} edx = {{0x[0-9a-f]+}}
// CHECK-DUMP-NEXT: edi = {{0x[0-9a-f]+}} esi = {{0x[0-9a-f]+}} ebp = {{0x[0-9a-f]+}} esp = {{0x[0-9a-f]+}}
// CHECK-NODUMP-NOT: Register values
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Check that sanitizer prints registers dump_registers on dump_registers=1
// RUN: %clangxx %s -o %t
// RUN: %env_tool_opts=dump_registers=0 not %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK-NODUMP --strict-whitespace
// RUN: not %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK-DUMP --strict-whitespace
//
// REQUIRES: x86_64-pc-windows-msvc

#include <windows.h>

int main() {
RaiseException(EXCEPTION_ACCESS_VIOLATION, 0, 0, NULL);
// CHECK-DUMP: Register values
// CHECK-DUMP-NEXT: rax = {{0x[0-9a-f]+}} rbx = {{0x[0-9a-f]+}} rcx = {{0x[0-9a-f]+}} rdx = {{0x[0-9a-f]+}}
// CHECK-DUMP-NEXT: rdi = {{0x[0-9a-f]+}} rsi = {{0x[0-9a-f]+}} rbp = {{0x[0-9a-f]+}} rsp = {{0x[0-9a-f]+}}
// CHECK-DUMP-NEXT: r8 = {{0x[0-9a-f]+}} r9 = {{0x[0-9a-f]+}} r10 = {{0x[0-9a-f]+}} r11 = {{0x[0-9a-f]+}}
// CHECK-DUMP-NEXT: r12 = {{0x[0-9a-f]+}} r13 = {{0x[0-9a-f]+}} r14 = {{0x[0-9a-f]+}} r15 = {{0x[0-9a-f]+}}
// CHECK-NODUMP-NOT: Register values
return 0;
}
5 changes: 5 additions & 0 deletions flang/include/flang/Optimizer/CodeGen/Target.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ class CodeGenSpecifics {
structArgumentType(mlir::Location loc, fir::RecordType recTy,
const Marshalling &previousArguments) const = 0;

/// Type representation of a `fir.type<T>` type argument when returned by
/// value. Such value may need to be converted to a hidden reference argument.
virtual Marshalling structReturnType(mlir::Location loc,
fir::RecordType eleTy) const = 0;

/// Type representation of a `boxchar<n>` type argument when passed by value.
/// An argument value may need to be passed as a (safe) reference argument.
///
Expand Down
21 changes: 21 additions & 0 deletions flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,27 @@ inline mlir::NamedAttribute getAdaptToByRefAttr(Builder &builder) {
}

bool isDummyArgument(mlir::Value v);

template <fir::FortranProcedureFlagsEnum Flag>
inline bool hasProcedureAttr(fir::FortranProcedureFlagsEnumAttr flags) {
return flags && bitEnumContainsAny(flags.getValue(), Flag);
}

template <fir::FortranProcedureFlagsEnum Flag>
inline bool hasProcedureAttr(mlir::Operation *op) {
if (auto firCallOp = mlir::dyn_cast<fir::CallOp>(op))
return hasProcedureAttr<Flag>(firCallOp.getProcedureAttrsAttr());
if (auto firCallOp = mlir::dyn_cast<fir::DispatchOp>(op))
return hasProcedureAttr<Flag>(firCallOp.getProcedureAttrsAttr());
return hasProcedureAttr<Flag>(
op->getAttrOfType<fir::FortranProcedureFlagsEnumAttr>(
getFortranProcedureFlagsAttrName()));
}

inline bool hasBindcAttr(mlir::Operation *op) {
return hasProcedureAttr<fir::FortranProcedureFlagsEnum::bind_c>(op);
}

} // namespace fir

#endif // FORTRAN_OPTIMIZER_DIALECT_FIROPSSUPPORT_H
4 changes: 4 additions & 0 deletions flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
while (defOp && !breakFromLoop) {
ty = defOp->getResultTypes()[0];
llvm::TypeSwitch<Operation *>(defOp)
.Case<hlfir::AsExprOp>([&](auto op) {
v = op.getVar();
defOp = v.getDefiningOp();
})
.Case<fir::AllocaOp, fir::AllocMemOp>([&](auto op) {
// Unique memory allocation.
type = SourceKind::Allocate;
Expand Down
8 changes: 4 additions & 4 deletions flang/lib/Optimizer/CodeGen/CodeGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2808,14 +2808,14 @@ struct GlobalOpConversion : public fir::FIROpConversion<fir::GlobalOp> {
matchAndRewrite(fir::GlobalOp global, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const override {

mlir::LLVM::DIGlobalVariableExpressionAttr dbgExpr;
llvm::SmallVector<mlir::Attribute> dbgExprs;

if (auto fusedLoc = mlir::dyn_cast<mlir::FusedLoc>(global.getLoc())) {
if (auto gvAttr =
mlir::dyn_cast_or_null<mlir::LLVM::DIGlobalVariableAttr>(
fusedLoc.getMetadata())) {
dbgExpr = mlir::LLVM::DIGlobalVariableExpressionAttr::get(
global.getContext(), gvAttr, mlir::LLVM::DIExpressionAttr());
dbgExprs.push_back(mlir::LLVM::DIGlobalVariableExpressionAttr::get(
global.getContext(), gvAttr, mlir::LLVM::DIExpressionAttr()));
}
}

Expand All @@ -2831,7 +2831,7 @@ struct GlobalOpConversion : public fir::FIROpConversion<fir::GlobalOp> {
llvm::ArrayRef<mlir::NamedAttribute> attrs;
auto g = rewriter.create<mlir::LLVM::GlobalOp>(
loc, tyAttr, isConst, linkage, global.getSymName(), initAttr, 0, 0,
false, false, comdat, attrs, dbgExpr);
false, false, comdat, attrs, dbgExprs);

if (global.getAlignment() && *global.getAlignment() > 0)
g.setAlignment(*global.getAlignment());
Expand Down
68 changes: 62 additions & 6 deletions flang/lib/Optimizer/CodeGen/Target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ struct GenericTarget : public CodeGenSpecifics {
TODO(loc, "passing VALUE BIND(C) derived type for this target");
}

CodeGenSpecifics::Marshalling
structReturnType(mlir::Location loc, fir::RecordType ty) const override {
TODO(loc, "returning BIND(C) derived type for this target");
}

CodeGenSpecifics::Marshalling
integerArgumentType(mlir::Location loc,
mlir::IntegerType argTy) const override {
Expand Down Expand Up @@ -533,14 +538,17 @@ struct TargetX86_64 : public GenericTarget<TargetX86_64> {
/// When \p recTy is a one field record type that can be passed
/// like the field on its own, returns the field type. Returns
/// a null type otherwise.
mlir::Type passAsFieldIfOneFieldStruct(fir::RecordType recTy) const {
mlir::Type passAsFieldIfOneFieldStruct(fir::RecordType recTy,
bool allowComplex = false) const {
auto typeList = recTy.getTypeList();
if (typeList.size() != 1)
return {};
mlir::Type fieldType = typeList[0].second;
if (mlir::isa<mlir::FloatType, mlir::IntegerType, fir::LogicalType>(
fieldType))
return fieldType;
if (allowComplex && mlir::isa<mlir::ComplexType>(fieldType))
return fieldType;
if (mlir::isa<fir::CharacterType>(fieldType)) {
// Only CHARACTER(1) are expected in BIND(C) contexts, which is the only
// contexts where derived type may be passed in registers.
Expand Down Expand Up @@ -593,7 +601,7 @@ struct TargetX86_64 : public GenericTarget<TargetX86_64> {
postMerge(byteOffset, Lo, Hi);
if (Lo == ArgClass::Memory || Lo == ArgClass::X87 ||
Lo == ArgClass::ComplexX87)
return passOnTheStack(loc, recTy);
return passOnTheStack(loc, recTy, /*isResult=*/false);
int neededIntRegisters = 0;
int neededSSERegisters = 0;
if (Lo == ArgClass::SSE)
Expand All @@ -609,7 +617,7 @@ struct TargetX86_64 : public GenericTarget<TargetX86_64> {
// all in registers or all on the stack).
if (!hasEnoughRegisters(loc, neededIntRegisters, neededSSERegisters,
previousArguments))
return passOnTheStack(loc, recTy);
return passOnTheStack(loc, recTy, /*isResult=*/false);

if (auto fieldType = passAsFieldIfOneFieldStruct(recTy)) {
CodeGenSpecifics::Marshalling marshal;
Expand Down Expand Up @@ -641,17 +649,65 @@ struct TargetX86_64 : public GenericTarget<TargetX86_64> {
return marshal;
}

CodeGenSpecifics::Marshalling
structReturnType(mlir::Location loc, fir::RecordType recTy) const override {
std::uint64_t byteOffset = 0;
ArgClass Lo, Hi;
Lo = Hi = ArgClass::NoClass;
byteOffset = classifyStruct(loc, recTy, byteOffset, Lo, Hi);
mlir::MLIRContext *context = recTy.getContext();
postMerge(byteOffset, Lo, Hi);
if (Lo == ArgClass::Memory)
return passOnTheStack(loc, recTy, /*isResult=*/true);

// Note that X87/ComplexX87 are passed in memory, but returned via %st0
// %st1 registers. Here, they are returned as fp80 or {fp80, fp80} by
// passAsFieldIfOneFieldStruct, and LLVM will use the expected registers.

// Note that {_Complex long double} is not 100% clear from an ABI
// perspective because the aggregate post merger rules say it should be
// passed in memory because it is bigger than 2 eight bytes. This has the
// funny effect of
// {_Complex long double} return to be dealt with differently than
// _Complex long double.

if (auto fieldType =
passAsFieldIfOneFieldStruct(recTy, /*allowComplex=*/true)) {
if (auto complexType = mlir::dyn_cast<mlir::ComplexType>(fieldType))
return complexReturnType(loc, complexType.getElementType());
CodeGenSpecifics::Marshalling marshal;
marshal.emplace_back(fieldType, AT{});
return marshal;
}

if (Hi == ArgClass::NoClass || Hi == ArgClass::SSEUp) {
// Return a single integer or floating point argument.
mlir::Type lowType = pickLLVMArgType(loc, context, Lo, byteOffset);
CodeGenSpecifics::Marshalling marshal;
marshal.emplace_back(lowType, AT{});
return marshal;
}
// Will be returned in two different registers. Generate {lowTy, HiTy} for
// the LLVM IR result type.
CodeGenSpecifics::Marshalling marshal;
mlir::Type lowType = pickLLVMArgType(loc, context, Lo, 8u);
mlir::Type hiType = pickLLVMArgType(loc, context, Hi, byteOffset - 8u);
marshal.emplace_back(mlir::TupleType::get(context, {lowType, hiType}),
AT{});
return marshal;
}

/// Marshal an argument that must be passed on the stack.
CodeGenSpecifics::Marshalling passOnTheStack(mlir::Location loc,
mlir::Type ty) const {
CodeGenSpecifics::Marshalling
passOnTheStack(mlir::Location loc, mlir::Type ty, bool isResult) const {
CodeGenSpecifics::Marshalling marshal;
auto sizeAndAlign =
fir::getTypeSizeAndAlignmentOrCrash(loc, ty, getDataLayout(), kindMap);
// The stack is always 8 byte aligned (note 14 in 3.2.3).
unsigned short align =
std::max(sizeAndAlign.second, static_cast<unsigned short>(8));
marshal.emplace_back(fir::ReferenceType::get(ty),
AT{align, /*byval=*/true, /*sret=*/false});
AT{align, /*byval=*/!isResult, /*sret=*/isResult});
return marshal;
}
};
Expand Down
137 changes: 107 additions & 30 deletions flang/lib/Optimizer/CodeGen/TargetRewrite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,20 +142,16 @@ class TargetRewrite : public fir::impl::TargetRewritePassBase<TargetRewrite> {

mlir::ModuleOp getModule() { return getOperation(); }

template <typename A, typename B, typename C>
template <typename Ty, typename Callback>
std::optional<std::function<mlir::Value(mlir::Operation *)>>
rewriteCallComplexResultType(
mlir::Location loc, A ty, B &newResTys,
fir::CodeGenSpecifics::Marshalling &newInTyAndAttrs, C &newOpers,
mlir::Value &savedStackPtr) {
if (noComplexConversion) {
newResTys.push_back(ty);
return std::nullopt;
}
auto m = specifics->complexReturnType(loc, ty.getElementType());
// Currently targets mandate COMPLEX is a single aggregate or packed
// scalar, including the sret case.
assert(m.size() == 1 && "target of complex return not supported");
rewriteCallResultType(mlir::Location loc, mlir::Type originalResTy,
Ty &newResTys,
fir::CodeGenSpecifics::Marshalling &newInTyAndAttrs,
Callback &newOpers, mlir::Value &savedStackPtr,
fir::CodeGenSpecifics::Marshalling &m) {
// Currently, targets mandate COMPLEX or STRUCT is a single aggregate or
// packed scalar, including the sret case.
assert(m.size() == 1 && "return type not supported on this target");
auto resTy = std::get<mlir::Type>(m[0]);
auto attr = std::get<fir::CodeGenSpecifics::Attributes>(m[0]);
if (attr.isSRet()) {
Expand All @@ -170,7 +166,7 @@ class TargetRewrite : public fir::impl::TargetRewritePassBase<TargetRewrite> {
newInTyAndAttrs.push_back(m[0]);
newOpers.push_back(stack);
return [=](mlir::Operation *) -> mlir::Value {
auto memTy = fir::ReferenceType::get(ty);
auto memTy = fir::ReferenceType::get(originalResTy);
auto cast = rewriter->create<fir::ConvertOp>(loc, memTy, stack);
return rewriter->create<fir::LoadOp>(loc, cast);
};
Expand All @@ -180,11 +176,41 @@ class TargetRewrite : public fir::impl::TargetRewritePassBase<TargetRewrite> {
// We are going to generate an alloca, so save the stack pointer.
if (!savedStackPtr)
savedStackPtr = genStackSave(loc);
return this->convertValueInMemory(loc, call->getResult(0), ty,
return this->convertValueInMemory(loc, call->getResult(0), originalResTy,
/*inputMayBeBigger=*/true);
};
}

template <typename Ty, typename Callback>
std::optional<std::function<mlir::Value(mlir::Operation *)>>
rewriteCallComplexResultType(
mlir::Location loc, mlir::ComplexType ty, Ty &newResTys,
fir::CodeGenSpecifics::Marshalling &newInTyAndAttrs, Callback &newOpers,
mlir::Value &savedStackPtr) {
if (noComplexConversion) {
newResTys.push_back(ty);
return std::nullopt;
}
auto m = specifics->complexReturnType(loc, ty.getElementType());
return rewriteCallResultType(loc, ty, newResTys, newInTyAndAttrs, newOpers,
savedStackPtr, m);
}

template <typename Ty, typename Callback>
std::optional<std::function<mlir::Value(mlir::Operation *)>>
rewriteCallStructResultType(
mlir::Location loc, fir::RecordType recTy, Ty &newResTys,
fir::CodeGenSpecifics::Marshalling &newInTyAndAttrs, Callback &newOpers,
mlir::Value &savedStackPtr) {
if (noStructConversion) {
newResTys.push_back(recTy);
return std::nullopt;
}
auto m = specifics->structReturnType(loc, recTy);
return rewriteCallResultType(loc, recTy, newResTys, newInTyAndAttrs,
newOpers, savedStackPtr, m);
}

void passArgumentOnStackOrWithNewType(
mlir::Location loc, fir::CodeGenSpecifics::TypeAndAttr newTypeAndAttr,
mlir::Type oldType, mlir::Value oper,
Expand Down Expand Up @@ -356,6 +382,11 @@ class TargetRewrite : public fir::impl::TargetRewritePassBase<TargetRewrite> {
newInTyAndAttrs, newOpers,
savedStackPtr);
})
.template Case<fir::RecordType>([&](fir::RecordType recTy) {
wrap = rewriteCallStructResultType(loc, recTy, newResTys,
newInTyAndAttrs, newOpers,
savedStackPtr);
})
.Default([&](mlir::Type ty) { newResTys.push_back(ty); });
} else if (fnTy.getResults().size() > 1) {
TODO(loc, "multiple results not supported yet");
Expand Down Expand Up @@ -562,6 +593,24 @@ class TargetRewrite : public fir::impl::TargetRewritePassBase<TargetRewrite> {
}
}

template <typename Ty>
void
lowerStructSignatureRes(mlir::Location loc, fir::RecordType recTy,
Ty &newResTys,
fir::CodeGenSpecifics::Marshalling &newInTyAndAttrs) {
if (noComplexConversion) {
newResTys.push_back(recTy);
return;
} else {
for (auto &tup : specifics->structReturnType(loc, recTy)) {
if (std::get<fir::CodeGenSpecifics::Attributes>(tup).isSRet())
newInTyAndAttrs.push_back(tup);
else
newResTys.push_back(std::get<mlir::Type>(tup));
}
}
}

void
lowerStructSignatureArg(mlir::Location loc, fir::RecordType recTy,
fir::CodeGenSpecifics::Marshalling &newInTyAndAttrs) {
Expand Down Expand Up @@ -595,6 +644,9 @@ class TargetRewrite : public fir::impl::TargetRewritePassBase<TargetRewrite> {
.Case<mlir::ComplexType>([&](mlir::ComplexType ty) {
lowerComplexSignatureRes(loc, ty, newResTys, newInTyAndAttrs);
})
.Case<fir::RecordType>([&](fir::RecordType ty) {
lowerStructSignatureRes(loc, ty, newResTys, newInTyAndAttrs);
})
.Default([&](mlir::Type ty) { newResTys.push_back(ty); });
}
llvm::SmallVector<mlir::Type> trailingInTys;
Expand Down Expand Up @@ -696,7 +748,8 @@ class TargetRewrite : public fir::impl::TargetRewritePassBase<TargetRewrite> {
for (auto ty : func.getResults())
if ((mlir::isa<fir::BoxCharType>(ty) && !noCharacterConversion) ||
(fir::isa_complex(ty) && !noComplexConversion) ||
(mlir::isa<mlir::IntegerType>(ty) && hasCCallingConv)) {
(mlir::isa<mlir::IntegerType>(ty) && hasCCallingConv) ||
(mlir::isa<fir::RecordType>(ty) && !noStructConversion)) {
LLVM_DEBUG(llvm::dbgs() << "rewrite " << signature << " for target\n");
return false;
}
Expand Down Expand Up @@ -770,6 +823,9 @@ class TargetRewrite : public fir::impl::TargetRewritePassBase<TargetRewrite> {
rewriter->getUnitAttr()));
newResTys.push_back(retTy);
})
.Case<fir::RecordType>([&](fir::RecordType recTy) {
doStructReturn(func, recTy, newResTys, newInTyAndAttrs, fixups);
})
.Default([&](mlir::Type ty) { newResTys.push_back(ty); });

// Saved potential shift in argument. Handling of result can add arguments
Expand Down Expand Up @@ -1062,21 +1118,12 @@ class TargetRewrite : public fir::impl::TargetRewritePassBase<TargetRewrite> {
return false;
}

/// Convert a complex return value. This can involve converting the return
/// value to a "hidden" first argument or packing the complex into a wide
/// GPR.
template <typename Ty, typename FIXUPS>
void doComplexReturn(mlir::func::FuncOp func, mlir::ComplexType cmplx,
Ty &newResTys,
fir::CodeGenSpecifics::Marshalling &newInTyAndAttrs,
FIXUPS &fixups) {
if (noComplexConversion) {
newResTys.push_back(cmplx);
return;
}
auto m =
specifics->complexReturnType(func.getLoc(), cmplx.getElementType());
assert(m.size() == 1);
void doReturn(mlir::func::FuncOp func, Ty &newResTys,
fir::CodeGenSpecifics::Marshalling &newInTyAndAttrs,
FIXUPS &fixups, fir::CodeGenSpecifics::Marshalling &m) {
assert(m.size() == 1 &&
"expect result to be turned into single argument or result so far");
auto &tup = m[0];
auto attr = std::get<fir::CodeGenSpecifics::Attributes>(tup);
auto argTy = std::get<mlir::Type>(tup);
Expand Down Expand Up @@ -1117,6 +1164,36 @@ class TargetRewrite : public fir::impl::TargetRewritePassBase<TargetRewrite> {
newResTys.push_back(argTy);
}

/// Convert a complex return value. This can involve converting the return
/// value to a "hidden" first argument or packing the complex into a wide
/// GPR.
template <typename Ty, typename FIXUPS>
void doComplexReturn(mlir::func::FuncOp func, mlir::ComplexType cmplx,
Ty &newResTys,
fir::CodeGenSpecifics::Marshalling &newInTyAndAttrs,
FIXUPS &fixups) {
if (noComplexConversion) {
newResTys.push_back(cmplx);
return;
}
auto m =
specifics->complexReturnType(func.getLoc(), cmplx.getElementType());
doReturn(func, newResTys, newInTyAndAttrs, fixups, m);
}

template <typename Ty, typename FIXUPS>
void doStructReturn(mlir::func::FuncOp func, fir::RecordType recTy,
Ty &newResTys,
fir::CodeGenSpecifics::Marshalling &newInTyAndAttrs,
FIXUPS &fixups) {
if (noStructConversion) {
newResTys.push_back(recTy);
return;
}
auto m = specifics->structReturnType(func.getLoc(), recTy);
doReturn(func, newResTys, newInTyAndAttrs, fixups, m);
}

template <typename FIXUPS>
void
createFuncOpArgFixups(mlir::func::FuncOp func,
Expand Down
69 changes: 65 additions & 4 deletions flang/lib/Optimizer/Transforms/AbstractResult.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,36 @@ using namespace mlir;
namespace fir {
namespace {

// Helper to only build the symbol table if needed because its build time is
// linear on the number of symbols in the module.
struct LazySymbolTable {
LazySymbolTable(mlir::Operation *op)
: module{op->getParentOfType<mlir::ModuleOp>()} {}
void build() {
if (table)
return;
table = std::make_unique<mlir::SymbolTable>(module);
}

template <typename T>
T lookup(llvm::StringRef name) {
build();
return table->lookup<T>(name);
}

private:
std::unique_ptr<mlir::SymbolTable> table;
mlir::ModuleOp module;
};

bool hasScalarDerivedResult(mlir::FunctionType funTy) {
// C_PTR/C_FUNPTR are results to void* in this pass, do not consider
// them as normal derived types.
return funTy.getNumResults() == 1 &&
mlir::isa<fir::RecordType>(funTy.getResult(0)) &&
!fir::isa_builtin_cptr_type(funTy.getResult(0));
}

static mlir::Type getResultArgumentType(mlir::Type resultType,
bool shouldBoxResult) {
return llvm::TypeSwitch<mlir::Type, mlir::Type>(resultType)
Expand Down Expand Up @@ -190,7 +220,15 @@ class SaveResultOpConversion
llvm::LogicalResult
matchAndRewrite(fir::SaveResultOp op,
mlir::PatternRewriter &rewriter) const override {
rewriter.eraseOp(op);
mlir::Operation *call = op.getValue().getDefiningOp();
mlir::Type type = op.getValue().getType();
if (mlir::isa<fir::RecordType>(type) && call && fir::hasBindcAttr(call) &&
!fir::isa_builtin_cptr_type(type)) {
rewriter.replaceOpWithNewOp<fir::StoreOp>(op, op.getValue(),
op.getMemref());
} else {
rewriter.eraseOp(op);
}
return mlir::success();
}
};
Expand Down Expand Up @@ -300,6 +338,12 @@ class AbstractResultOpt
auto *context = &getContext();
// Convert function type itself if it has an abstract result.
auto funcTy = mlir::cast<mlir::FunctionType>(func.getFunctionType());
// Scalar derived result of BIND(C) function must be returned according
// to the C struct return ABI which is target dependent and implemented in
// the target-rewrite pass.
if (hasScalarDerivedResult(funcTy) &&
fir::hasBindcAttr(func.getOperation()))
return;
if (hasAbstractResult(funcTy)) {
if (fir::isa_builtin_cptr_type(funcTy.getResult(0))) {
func.setType(getCPtrFunctionType(funcTy));
Expand Down Expand Up @@ -395,6 +439,8 @@ class AbstractResultOpt
return;
}

LazySymbolTable symbolTable(op);

mlir::RewritePatternSet patterns(context);
mlir::ConversionTarget target = *context;
const bool shouldBoxResult = this->passResultAsBox.getValue();
Expand All @@ -409,14 +455,29 @@ class AbstractResultOpt
mlir::func::FuncDialect>();
target.addIllegalOp<fir::SaveResultOp>();
target.addDynamicallyLegalOp<fir::CallOp>([](fir::CallOp call) {
return !hasAbstractResult(call.getFunctionType());
mlir::FunctionType funTy = call.getFunctionType();
if (hasScalarDerivedResult(funTy) &&
fir::hasBindcAttr(call.getOperation()))
return true;
return !hasAbstractResult(funTy);
});
target.addDynamicallyLegalOp<fir::AddrOfOp>([](fir::AddrOfOp addrOf) {
if (auto funTy = mlir::dyn_cast<mlir::FunctionType>(addrOf.getType()))
target.addDynamicallyLegalOp<fir::AddrOfOp>([&symbolTable](
fir::AddrOfOp addrOf) {
if (auto funTy = mlir::dyn_cast<mlir::FunctionType>(addrOf.getType())) {
if (hasScalarDerivedResult(funTy)) {
auto func = symbolTable.lookup<mlir::func::FuncOp>(
addrOf.getSymbol().getRootReference().getValue());
return func && fir::hasBindcAttr(func.getOperation());
}
return !hasAbstractResult(funTy);
}
return true;
});
target.addDynamicallyLegalOp<fir::DispatchOp>([](fir::DispatchOp dispatch) {
mlir::FunctionType funTy = dispatch.getFunctionType();
if (hasScalarDerivedResult(funTy) &&
fir::hasBindcAttr(dispatch.getOperation()))
return true;
return !hasAbstractResult(dispatch.getFunctionType());
});

Expand Down
14 changes: 10 additions & 4 deletions flang/lib/Semantics/resolve-directives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2418,10 +2418,16 @@ void OmpAttributeVisitor::ResolveOmpObject(
Symbol::Flag::OmpLastPrivate, Symbol::Flag::OmpShared,
Symbol::Flag::OmpLinear};

for (Symbol::Flag ompFlag1 : dataMappingAttributeFlags) {
for (Symbol::Flag ompFlag2 : dataSharingAttributeFlags) {
checkExclusivelists(
hostAssocSym, ompFlag1, symbol, ompFlag2);
// For OMP TARGET TEAMS directive some sharing attribute
// flags and mapping attribute flags can co-exist.
if (!(llvm::omp::allTeamsSet.test(GetContext().directive) ||
llvm::omp::allParallelSet.test(
GetContext().directive))) {
for (Symbol::Flag ompFlag1 : dataMappingAttributeFlags) {
for (Symbol::Flag ompFlag2 : dataSharingAttributeFlags) {
checkExclusivelists(
hostAssocSym, ompFlag1, symbol, ompFlag2);
}
}
}
}
Expand Down
48 changes: 48 additions & 0 deletions flang/test/Analysis/AliasAnalysis/alias-analysis-9.fir
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Use --mlir-disable-threading so that the AA queries are serialized
// as well as its diagnostic output.
// RUN: fir-opt %s -pass-pipeline='builtin.module(func.func(test-fir-alias-analysis))' -split-input-file --mlir-disable-threading 2>&1 | FileCheck %s

// Fortran source code:
// program TestTmpArrayAssignment
// integer :: a, b, c
// integer :: arrayD(3)
//
// arrayD = [ a, b, c ]
// end program TestTmpArrayAssignment

// CHECK-LABEL: Testing : "_QPTestTmpArrayAssignment"
// CHECK-DAG: ArrayD#0 <-> tmp_array#0: NoAlias
func.func @_QPTestTmpArrayAssignment() attributes {fir.bindc_name = "testtmparrayassignment"} {
%0 = fir.alloca i32 {bindc_name = "a", uniq_name = "_QFEa"}
%1:2 = hlfir.declare %0 {uniq_name = "_QFEa"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
%c3 = arith.constant 3 : index
%2 = fir.alloca !fir.array<3xi32> {bindc_name = "arrayd", uniq_name = "_QFEarrayd", test.ptr = "ArrayD" }
%3 = fir.shape %c3 : (index) -> !fir.shape<1>
%4:2 = hlfir.declare %2(%3) {uniq_name = "_QFEarrayd"} : (!fir.ref<!fir.array<3xi32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<3xi32>>, !fir.ref<!fir.array<3xi32>>)
%5 = fir.alloca i32 {bindc_name = "b", uniq_name = "_QFEb"}
%6:2 = hlfir.declare %5 {uniq_name = "_QFEb"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
%7 = fir.alloca i32 {bindc_name = "c", uniq_name = "_QFEc"}
%8:2 = hlfir.declare %7 {uniq_name = "_QFEc"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
%c3_0 = arith.constant 3 : index
%c1 = arith.constant 1 : index
%c1_1 = arith.constant 1 : index
%9 = fir.allocmem !fir.array<3xi32> {bindc_name = ".tmp.arrayctor", uniq_name = ""}
%10 = fir.shape %c3_0 : (index) -> !fir.shape<1>
%11:2 = hlfir.declare %9(%10) {uniq_name = ".tmp.arrayctor"} : (!fir.heap<!fir.array<3xi32>>, !fir.shape<1>) -> (!fir.heap<!fir.array<3xi32>>, !fir.heap<!fir.array<3xi32>>)
%12 = fir.load %1#0 : !fir.ref<i32>
%13 = arith.addi %c1, %c1_1 : index
%14 = hlfir.designate %11#0 (%c1) : (!fir.heap<!fir.array<3xi32>>, index) -> !fir.ref<i32>
hlfir.assign %12 to %14 : i32, !fir.ref<i32>
%15 = fir.load %6#0 : !fir.ref<i32>
%16 = arith.addi %13, %c1_1 : index
%17 = hlfir.designate %11#0 (%13) : (!fir.heap<!fir.array<3xi32>>, index) -> !fir.ref<i32>
hlfir.assign %15 to %17 : i32, !fir.ref<i32>
%18 = fir.load %8#0 : !fir.ref<i32>
%19 = hlfir.designate %11#0 (%16) : (!fir.heap<!fir.array<3xi32>>, index) -> !fir.ref<i32>
hlfir.assign %18 to %19 : i32, !fir.ref<i32>
%true = arith.constant true
%20 = hlfir.as_expr %11#0 move %true {test.ptr = "tmp_array"}: (!fir.heap<!fir.array<3xi32>>, i1) -> !hlfir.expr<3xi32>
hlfir.assign %20 to %4#0 : !hlfir.expr<3xi32>, !fir.ref<!fir.array<3xi32>>
hlfir.destroy %20 : !hlfir.expr<3xi32>
return
}
60 changes: 60 additions & 0 deletions flang/test/Fir/abstract-results-bindc.fir
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Test that bind_c derived type results are not moved to a hidden argument
// by the abstract-result pass. They will be dealt with according to the C
// struct returning ABI for the target in the target-rewrite pass.
// RUN: fir-opt %s --abstract-result | FileCheck %s

!t = !fir.type<t{i:f32, j: i32, k: f32}>
!cptr = !fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>

func.func private @foo() -> !t attributes {fir.proc_attrs = #fir.proc_attrs<bind_c>}

func.func @test_call(%x: !fir.ref<!t>) {
%0 = fir.call @foo() proc_attrs<bind_c> : () -> !t
fir.save_result %0 to %x : !t, !fir.ref<!t>
return
}

func.func @test_addr_of() -> (() -> !t) {
%0 = fir.address_of(@foo) : () -> !t
return %0 : () -> !t
}

func.func @test_dispatch(%x: !fir.ref<!t>, %y : !fir.class<!fir.type<somet>>) {
%0 = fir.dispatch "bar"(%y : !fir.class<!fir.type<somet>>) (%y : !fir.class<!fir.type<somet>>) -> !t proc_attrs<bind_c> {pass_arg_pos = 0 : i32}
fir.save_result %0 to %x : !t, !fir.ref<!t>
return
}


func.func private @return_cptr() -> !cptr attributes {fir.proc_attrs = #fir.proc_attrs<bind_c>}
func.func @test_return_cptr(%x: !fir.ref<!cptr>) {
%0 = fir.call @return_cptr() proc_attrs<bind_c> : () -> !cptr
fir.save_result %0 to %x : !cptr, !fir.ref<!cptr>
return
}


// CHECK-LABEL: func.func @test_call(
// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<!fir.type<t{i:f32,j:i32,k:f32}>>) {
// CHECK: %[[VAL_1:.*]] = fir.call @foo() proc_attrs<bind_c> : () -> !fir.type<t{i:f32,j:i32,k:f32}>
// CHECK: fir.store %[[VAL_1]] to %[[VAL_0]] : !fir.ref<!fir.type<t{i:f32,j:i32,k:f32}>>
// CHECK: return
// CHECK: }
// CHECK-LABEL: func.func @test_addr_of() -> (() -> !fir.type<t{i:f32,j:i32,k:f32}>) {
// CHECK: %[[VAL_0:.*]] = fir.address_of(@foo) : () -> !fir.type<t{i:f32,j:i32,k:f32}>
// CHECK: return %[[VAL_0]] : () -> !fir.type<t{i:f32,j:i32,k:f32}>
// CHECK: }
// CHECK-LABEL: func.func @test_dispatch(
// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<!fir.type<t{i:f32,j:i32,k:f32}>>,
// CHECK-SAME: %[[VAL_1:.*]]: !fir.class<!fir.type<somet>>) {
// CHECK: %[[VAL_2:.*]] = fir.dispatch "bar"(%[[VAL_1]] : !fir.class<!fir.type<somet>>) (%[[VAL_1]] : !fir.class<!fir.type<somet>>) -> !fir.type<t{i:f32,j:i32,k:f32}> proc_attrs <bind_c> {pass_arg_pos = 0 : i32}
// CHECK: fir.store %[[VAL_2]] to %[[VAL_0]] : !fir.ref<!fir.type<t{i:f32,j:i32,k:f32}>>
// CHECK: return
// CHECK: }
// CHECK-LABEL: func.func @test_return_cptr(
// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>) {
// CHECK: %[[VAL_1:.*]] = fir.call @return_cptr() : () -> !fir.ref<none>
// CHECK: %[[VAL_2:.*]] = fir.field_index __address, !fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>
// CHECK: %[[VAL_3:.*]] = fir.coordinate_of %[[VAL_0]], %[[VAL_2]] : (!fir.ref<!fir.type<_QM__fortran_builtinsT__builtin_c_ptr{__address:i64}>>, !fir.field) -> !fir.ref<i64>
// CHECK: %[[VAL_4:.*]] = fir.convert %[[VAL_1]] : (!fir.ref<none>) -> i64
// CHECK: fir.store %[[VAL_4]] to %[[VAL_3]] : !fir.ref<i64>
120 changes: 120 additions & 0 deletions flang/test/Fir/struct-return-x86-64.fir
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Test X86-64 ABI rewrite of struct returned by value (BIND(C), VALUE derived types).
// REQUIRES: x86-registered-target
// RUN: fir-opt --target-rewrite %s | FileCheck %s

!fits_in_reg = !fir.type<t1{i:f32,j:i32,k:f32}>
!too_big = !fir.type<t2{i:!fir.array<5xf32>}>

module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", llvm.target_triple = "x86_64-unknown-linux-gnu"} {

func.func private @test_inreg() -> !fits_in_reg
func.func @test_call_inreg(%arg0: !fir.ref<!fits_in_reg>) {
%0 = fir.call @test_inreg() : () -> !fits_in_reg
fir.store %0 to %arg0 : !fir.ref<!fits_in_reg>
return
}
func.func @test_addr_of_inreg() -> (() -> ()) {
%0 = fir.address_of(@test_inreg) : () -> !fits_in_reg
%1 = fir.convert %0 : (() -> !fits_in_reg) -> (() -> ())
return %1 : () -> ()
}
func.func @test_dispatch_inreg(%arg0: !fir.ref<!fits_in_reg>, %arg1: !fir.class<!fir.type<somet>>) {
%0 = fir.dispatch "bar"(%arg1 : !fir.class<!fir.type<somet>>) (%arg1 : !fir.class<!fir.type<somet>>) -> !fits_in_reg {pass_arg_pos = 0 : i32}
fir.store %0 to %arg0 : !fir.ref<!fits_in_reg>
return
}

func.func private @test_sret() -> !too_big
func.func @test_call_sret(%arg0: !fir.ref<!too_big>) {
%0 = fir.call @test_sret() : () -> !too_big
fir.store %0 to %arg0 : !fir.ref<!too_big>
return
}
func.func @test_addr_of_sret() -> (() -> ()) {
%0 = fir.address_of(@test_sret) : () -> !too_big
%1 = fir.convert %0 : (() -> !too_big) -> (() -> ())
return %1 : () -> ()
}
func.func @test_dispatch_sret(%arg0: !fir.ref<!too_big>, %arg1: !fir.class<!fir.type<somet>>) {
%0 = fir.dispatch "bar"(%arg1 : !fir.class<!fir.type<somet>>) (%arg1 : !fir.class<!fir.type<somet>>) -> !too_big {pass_arg_pos = 0 : i32}
fir.store %0 to %arg0 : !fir.ref<!too_big>
return
}
func.func private @test_fp_80() -> !fir.type<t3{i:f80}>
func.func private @test_complex_80() -> !fir.type<t4{i:complex<f80>}>
func.func private @test_two_fp_80() -> !fir.type<t5{i:f80,j:f80}>
func.func private @test_fp128() -> !fir.type<t6{i:f128}>
}

// CHECK-LABEL: func.func private @test_inreg() -> tuple<i64, f32>

// CHECK-LABEL: func.func @test_call_inreg(
// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<!fir.type<t1{i:f32,j:i32,k:f32}>>) {
// CHECK: %[[VAL_1:.*]] = fir.call @test_inreg() : () -> tuple<i64, f32>
// CHECK: %[[VAL_2:.*]] = llvm.intr.stacksave : !llvm.ptr
// CHECK: %[[VAL_3:.*]] = fir.alloca tuple<i64, f32>
// CHECK: fir.store %[[VAL_1]] to %[[VAL_3]] : !fir.ref<tuple<i64, f32>>
// CHECK: %[[VAL_4:.*]] = fir.convert %[[VAL_3]] : (!fir.ref<tuple<i64, f32>>) -> !fir.ref<!fir.type<t1{i:f32,j:i32,k:f32}>>
// CHECK: %[[VAL_5:.*]] = fir.load %[[VAL_4]] : !fir.ref<!fir.type<t1{i:f32,j:i32,k:f32}>>
// CHECK: llvm.intr.stackrestore %[[VAL_2]] : !llvm.ptr
// CHECK: fir.store %[[VAL_5]] to %[[VAL_0]] : !fir.ref<!fir.type<t1{i:f32,j:i32,k:f32}>>
// CHECK: return
// CHECK: }

// CHECK-LABEL: func.func @test_addr_of_inreg() -> (() -> ()) {
// CHECK: %[[VAL_0:.*]] = fir.address_of(@test_inreg) : () -> tuple<i64, f32>
// CHECK: %[[VAL_1:.*]] = fir.convert %[[VAL_0]] : (() -> tuple<i64, f32>) -> (() -> ())
// CHECK: return %[[VAL_1]] : () -> ()
// CHECK: }

// CHECK-LABEL: func.func @test_dispatch_inreg(
// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<!fir.type<t1{i:f32,j:i32,k:f32}>>,
// CHECK-SAME: %[[VAL_1:.*]]: !fir.class<!fir.type<somet>>) {
// CHECK: %[[VAL_2:.*]] = fir.dispatch "bar"(%[[VAL_1]] : !fir.class<!fir.type<somet>>) (%[[VAL_1]] : !fir.class<!fir.type<somet>>) -> tuple<i64, f32> {pass_arg_pos = 0 : i32}
// CHECK: %[[VAL_3:.*]] = llvm.intr.stacksave : !llvm.ptr
// CHECK: %[[VAL_4:.*]] = fir.alloca tuple<i64, f32>
// CHECK: fir.store %[[VAL_2]] to %[[VAL_4]] : !fir.ref<tuple<i64, f32>>
// CHECK: %[[VAL_5:.*]] = fir.convert %[[VAL_4]] : (!fir.ref<tuple<i64, f32>>) -> !fir.ref<!fir.type<t1{i:f32,j:i32,k:f32}>>
// CHECK: %[[VAL_6:.*]] = fir.load %[[VAL_5]] : !fir.ref<!fir.type<t1{i:f32,j:i32,k:f32}>>
// CHECK: llvm.intr.stackrestore %[[VAL_3]] : !llvm.ptr
// CHECK: fir.store %[[VAL_6]] to %[[VAL_0]] : !fir.ref<!fir.type<t1{i:f32,j:i32,k:f32}>>
// CHECK: return
// CHECK: }
// CHECK: func.func private @test_sret(!fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>> {llvm.align = 8 : i32, llvm.sret = !fir.type<t2{i:!fir.array<5xf32>}>})

// CHECK-LABEL: func.func @test_call_sret(
// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>) {
// CHECK: %[[VAL_1:.*]] = llvm.intr.stacksave : !llvm.ptr
// CHECK: %[[VAL_2:.*]] = fir.alloca !fir.type<t2{i:!fir.array<5xf32>}>
// CHECK: fir.call @test_sret(%[[VAL_2]]) : (!fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>) -> ()
// CHECK: %[[VAL_3:.*]] = fir.convert %[[VAL_2]] : (!fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>) -> !fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>
// CHECK: %[[VAL_4:.*]] = fir.load %[[VAL_3]] : !fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>
// CHECK: llvm.intr.stackrestore %[[VAL_1]] : !llvm.ptr
// CHECK: fir.store %[[VAL_4]] to %[[VAL_0]] : !fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>
// CHECK: return
// CHECK: }

// CHECK-LABEL: func.func @test_addr_of_sret() -> (() -> ()) {
// CHECK: %[[VAL_0:.*]] = fir.address_of(@test_sret) : (!fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>) -> ()
// CHECK: %[[VAL_1:.*]] = fir.convert %[[VAL_0]] : ((!fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>) -> ()) -> (() -> ())
// CHECK: return %[[VAL_1]] : () -> ()
// CHECK: }

// CHECK-LABEL: func.func @test_dispatch_sret(
// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>,
// CHECK-SAME: %[[VAL_1:.*]]: !fir.class<!fir.type<somet>>) {
// CHECK: %[[VAL_2:.*]] = llvm.intr.stacksave : !llvm.ptr
// CHECK: %[[VAL_3:.*]] = fir.alloca !fir.type<t2{i:!fir.array<5xf32>}>
// CHECK: fir.dispatch "bar"(%[[VAL_1]] : !fir.class<!fir.type<somet>>) (%[[VAL_3]], %[[VAL_1]] : !fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>, !fir.class<!fir.type<somet>>) {pass_arg_pos = 1 : i32}
// CHECK: %[[VAL_4:.*]] = fir.convert %[[VAL_3]] : (!fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>) -> !fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>
// CHECK: %[[VAL_5:.*]] = fir.load %[[VAL_4]] : !fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>
// CHECK: llvm.intr.stackrestore %[[VAL_2]] : !llvm.ptr
// CHECK: fir.store %[[VAL_5]] to %[[VAL_0]] : !fir.ref<!fir.type<t2{i:!fir.array<5xf32>}>>
// CHECK: return
// CHECK: }


// CHECK: func.func private @test_fp_80() -> f80
// CHECK: func.func private @test_complex_80(!fir.ref<!fir.type<t4{i:complex<f80>}>> {llvm.align = 16 : i32, llvm.sret = !fir.type<t4{i:complex<f80>}>})
// CHECK: func.func private @test_two_fp_80(!fir.ref<!fir.type<t5{i:f80,j:f80}>> {llvm.align = 16 : i32, llvm.sret = !fir.type<t5{i:f80,j:f80}>})
// CHECK: func.func private @test_fp128() -> f128
4 changes: 2 additions & 2 deletions flang/test/Transforms/debug-module-2.fir
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ module {
// CHECK-DAG: #[[GLR:.*]] = #llvm.di_global_variable<{{.*}}name = "glr", linkageName = "_QMhelperEglr"{{.*}}>
// CHECK-DAG: #[[GLIE:.*]] = #llvm.di_global_variable_expression<var = #[[GLI]]>
// CHECK-DAG: #[[GLRE:.*]] = #llvm.di_global_variable_expression<var = #[[GLR]]>
// CHECK-DAG: llvm.mlir.global{{.*}}@_QMhelperEgli() {{{.*}}dbg_expr = #[[GLIE]]}
// CHECK-DAG: llvm.mlir.global{{.*}}@_QMhelperEglr() {{{.*}}dbg_expr = #[[GLRE]]}
// CHECK-DAG: llvm.mlir.global{{.*}}@_QMhelperEgli() {{{.*}}dbg_exprs = [#[[GLIE]]]}
// CHECK-DAG: llvm.mlir.global{{.*}}@_QMhelperEglr() {{{.*}}dbg_exprs = [#[[GLRE]]]}
1 change: 1 addition & 0 deletions libc/config/linux/x86_64/entrypoints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,7 @@ if(LIBC_TYPES_HAS_FLOAT16)
libc.src.math.copysignf16
libc.src.math.exp10f16
libc.src.math.exp2f16
libc.src.math.exp2m1f16
libc.src.math.expf16
libc.src.math.expm1f16
libc.src.math.f16add
Expand Down
2 changes: 1 addition & 1 deletion libc/docs/math/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ Higher Math Functions
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| exp2 | |check| | |check| | | |check| | | 7.12.6.4 | F.10.3.4 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| exp2m1 | |check| | | | | | 7.12.6.5 | F.10.3.5 |
| exp2m1 | |check| | | | |check| | | 7.12.6.5 | F.10.3.5 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| expm1 | |check| | |check| | | |check| | | 7.12.6.6 | F.10.3.6 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
Expand Down
7 changes: 7 additions & 0 deletions libc/newhdrgen/yaml/math.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,13 @@ functions:
return_type: float
arguments:
- type: float
- name: exp2m1f16
standards:
- stdc
return_type: _Float16
arguments:
- type: _Float16
guard: LIBC_TYPES_HAS_FLOAT16
- name: expf
standards:
- stdc
Expand Down
1 change: 1 addition & 0 deletions libc/spec/stdc.td
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,7 @@ def StdC : StandardSpec<"stdc"> {
GuardedFunctionSpec<"exp2f16", RetValSpec<Float16Type>, [ArgSpec<Float16Type>], "LIBC_TYPES_HAS_FLOAT16">,

FunctionSpec<"exp2m1f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
GuardedFunctionSpec<"exp2m1f16", RetValSpec<Float16Type>, [ArgSpec<Float16Type>], "LIBC_TYPES_HAS_FLOAT16">,

FunctionSpec<"expm1", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
FunctionSpec<"expm1f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,
Expand Down
4 changes: 2 additions & 2 deletions libc/src/__support/CPP/string_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class string_view {

LIBC_INLINE static int compareMemory(const char *Lhs, const char *Rhs,
size_t Length) {
for (size_t I = 0; I < Length; ++I)
if (int Diff = (int)Lhs[I] - (int)Rhs[I])
for (size_t i = 0; i < Length; ++i)
if (int Diff = (int)Lhs[i] - (int)Rhs[i])
return Diff;
return 0;
}
Expand Down
6 changes: 3 additions & 3 deletions libc/src/__support/CPP/utility/in_place.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ template <class T> struct in_place_type_t {
};
template <class T> LIBC_INLINE_VAR constexpr in_place_type_t<T> in_place_type{};

template <size_t I> struct in_place_index_t {
template <size_t IDX> struct in_place_index_t {
LIBC_INLINE explicit in_place_index_t() = default;
};
template <size_t I>
LIBC_INLINE_VAR constexpr in_place_index_t<I> in_place_index{};
template <size_t IDX>
LIBC_INLINE_VAR constexpr in_place_index_t<IDX> in_place_index{};

} // namespace cpp
} // namespace LIBC_NAMESPACE_DECL
Expand Down
41 changes: 23 additions & 18 deletions libc/src/__support/FPUtil/NearestIntegerOperations.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,13 +346,14 @@ fromfpx(T x, int rnd, unsigned int width) {

namespace internal {

template <typename F, typename I,
cpp::enable_if_t<cpp::is_floating_point_v<F> && cpp::is_integral_v<I>,
template <typename FloatType, typename IntType,
cpp::enable_if_t<cpp::is_floating_point_v<FloatType> &&
cpp::is_integral_v<IntType>,
int> = 0>
LIBC_INLINE I rounded_float_to_signed_integer(F x) {
constexpr I INTEGER_MIN = (I(1) << (sizeof(I) * 8 - 1));
constexpr I INTEGER_MAX = -(INTEGER_MIN + 1);
FPBits<F> bits(x);
LIBC_INLINE IntType rounded_float_to_signed_integer(FloatType x) {
constexpr IntType INTEGER_MIN = (IntType(1) << (sizeof(IntType) * 8 - 1));
constexpr IntType INTEGER_MAX = -(INTEGER_MIN + 1);
FPBits<FloatType> bits(x);
auto set_domain_error_and_raise_invalid = []() {
set_errno_if_required(EDOM);
raise_except_if_required(FE_INVALID);
Expand All @@ -364,7 +365,7 @@ LIBC_INLINE I rounded_float_to_signed_integer(F x) {
}

int exponent = bits.get_exponent();
constexpr int EXPONENT_LIMIT = sizeof(I) * 8 - 1;
constexpr int EXPONENT_LIMIT = sizeof(IntType) * 8 - 1;
if (exponent > EXPONENT_LIMIT) {
set_domain_error_and_raise_invalid();
return bits.is_neg() ? INTEGER_MIN : INTEGER_MAX;
Expand All @@ -374,29 +375,33 @@ LIBC_INLINE I rounded_float_to_signed_integer(F x) {
return bits.is_neg() ? INTEGER_MIN : INTEGER_MAX;
}
// If the control reaches here, then it means that the rounded
// value is the most negative number for the signed integer type I.
// value is the most negative number for the signed integer type IntType.
}

// For all other cases, if `x` can fit in the integer type `I`,
// For all other cases, if `x` can fit in the integer type `IntType`,
// we just return `x`. static_cast will convert the floating
// point value to the exact integer value.
return static_cast<I>(x);
return static_cast<IntType>(x);
}

} // namespace internal

template <typename F, typename I,
cpp::enable_if_t<cpp::is_floating_point_v<F> && cpp::is_integral_v<I>,
template <typename FloatType, typename IntType,
cpp::enable_if_t<cpp::is_floating_point_v<FloatType> &&
cpp::is_integral_v<IntType>,
int> = 0>
LIBC_INLINE I round_to_signed_integer(F x) {
return internal::rounded_float_to_signed_integer<F, I>(round(x));
LIBC_INLINE IntType round_to_signed_integer(FloatType x) {
return internal::rounded_float_to_signed_integer<FloatType, IntType>(
round(x));
}

template <typename F, typename I,
cpp::enable_if_t<cpp::is_floating_point_v<F> && cpp::is_integral_v<I>,
template <typename FloatType, typename IntType,
cpp::enable_if_t<cpp::is_floating_point_v<FloatType> &&
cpp::is_integral_v<IntType>,
int> = 0>
LIBC_INLINE I round_to_signed_integer_using_current_rounding_mode(F x) {
return internal::rounded_float_to_signed_integer<F, I>(
LIBC_INLINE IntType
round_to_signed_integer_using_current_rounding_mode(FloatType x) {
return internal::rounded_float_to_signed_integer<FloatType, IntType>(
round_using_current_rounding_mode(x));
}

Expand Down
17 changes: 6 additions & 11 deletions libc/src/__support/str_to_integer.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

#include "src/__support/CPP/limits.h"
#include "src/__support/CPP/type_traits.h"
#include "src/__support/CPP/type_traits/make_unsigned.h"
#include "src/__support/big_int.h"
#include "src/__support/common.h"
#include "src/__support/ctype_utils.h"
#include "src/__support/macros/config.h"
Expand Down Expand Up @@ -77,9 +79,7 @@ template <class T>
LIBC_INLINE StrToNumResult<T>
strtointeger(const char *__restrict src, int base,
const size_t src_len = cpp::numeric_limits<size_t>::max()) {
using ResultType = typename cpp::conditional_t<(cpp::is_same_v<T, UInt128> ||
cpp::is_same_v<T, Int128>),
UInt128, unsigned long long>;
using ResultType = make_integral_or_big_int_unsigned_t<T>;

ResultType result = 0;

Expand Down Expand Up @@ -137,13 +137,13 @@ strtointeger(const char *__restrict src, int base,
result = abs_max;
error_val = ERANGE;
} else {
result = result * base;
result = static_cast<ResultType>(result * base);
}
if (result > abs_max - cur_digit) {
result = abs_max;
error_val = ERANGE;
} else {
result = result + cur_digit;
result = static_cast<ResultType>(result + cur_digit);
}
}

Expand All @@ -156,12 +156,7 @@ strtointeger(const char *__restrict src, int base,
return {cpp::numeric_limits<T>::min(), str_len, error_val};
}

return {
is_positive
? static_cast<T>(result)
: static_cast<T>(
-static_cast<make_integral_or_big_int_unsigned_t<T>>(result)),
str_len, error_val};
return {static_cast<T>(is_positive ? result : -result), str_len, error_val};
}

} // namespace internal
Expand Down
1 change: 1 addition & 0 deletions libc/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ add_math_entrypoint_object(exp2f)
add_math_entrypoint_object(exp2f16)

add_math_entrypoint_object(exp2m1f)
add_math_entrypoint_object(exp2m1f16)

add_math_entrypoint_object(exp10)
add_math_entrypoint_object(exp10f)
Expand Down
21 changes: 21 additions & 0 deletions libc/src/math/exp2m1f16.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===-- Implementation header for exp2m1f16 ---------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_LIBC_SRC_MATH_EXP2M1F16_H
#define LLVM_LIBC_SRC_MATH_EXP2M1F16_H

#include "src/__support/macros/config.h"
#include "src/__support/macros/properties/types.h"

namespace LIBC_NAMESPACE_DECL {

float16 exp2m1f16(float16 x);

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_MATH_EXP2M1F16_H
30 changes: 26 additions & 4 deletions libc/src/math/generic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1500,14 +1500,10 @@ add_entrypoint_object(
.expxf16
libc.hdr.errno_macros
libc.hdr.fenv_macros
libc.src.__support.CPP.array
libc.src.__support.FPUtil.cast
libc.src.__support.FPUtil.except_value_utils
libc.src.__support.FPUtil.fenv_impl
libc.src.__support.FPUtil.fp_bits
libc.src.__support.FPUtil.multiply_add
libc.src.__support.FPUtil.nearest_integer
libc.src.__support.FPUtil.polyeval
libc.src.__support.FPUtil.rounding_mode
libc.src.__support.macros.optimization
COMPILE_OPTIONS
Expand Down Expand Up @@ -1535,6 +1531,30 @@ add_entrypoint_object(
-O3
)

add_entrypoint_object(
exp2m1f16
SRCS
exp2m1f16.cpp
HDRS
../exp2m1f16.h
DEPENDS
.expxf16
libc.hdr.errno_macros
libc.hdr.fenv_macros
libc.src.__support.common
libc.src.__support.FPUtil.cast
libc.src.__support.FPUtil.except_value_utils
libc.src.__support.FPUtil.fenv_impl
libc.src.__support.FPUtil.fp_bits
libc.src.__support.FPUtil.multiply_add
libc.src.__support.FPUtil.polyeval
libc.src.__support.FPUtil.rounding_mode
libc.src.__support.macros.optimization
libc.src.__support.macros.properties.cpu_features
COMPILE_OPTIONS
-O3
)

add_entrypoint_object(
exp10
SRCS
Expand Down Expand Up @@ -5235,7 +5255,9 @@ add_header_library(
expxf16.h
DEPENDS
libc.src.__support.CPP.array
libc.src.__support.FPUtil.fp_bits
libc.src.__support.FPUtil.multiply_add
libc.src.__support.FPUtil.nearest_integer
libc.src.__support.FPUtil.polyeval
libc.src.__support.macros.attributes
)
39 changes: 2 additions & 37 deletions libc/src/math/generic/exp2f16.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,10 @@
#include "expxf16.h"
#include "hdr/errno_macros.h"
#include "hdr/fenv_macros.h"
#include "src/__support/CPP/array.h"
#include "src/__support/FPUtil/FEnvImpl.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/FPUtil/PolyEval.h"
#include "src/__support/FPUtil/cast.h"
#include "src/__support/FPUtil/except_value_utils.h"
#include "src/__support/FPUtil/multiply_add.h"
#include "src/__support/FPUtil/nearest_integer.h"
#include "src/__support/FPUtil/rounding_mode.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
Expand Down Expand Up @@ -89,39 +85,8 @@ LLVM_LIBC_FUNCTION(float16, exp2f16, (float16 x)) {
if (auto r = EXP2F16_EXCEPTS.lookup(x_u); LIBC_UNLIKELY(r.has_value()))
return r.value();

// For -25 < x < 16, to compute 2^x, we perform the following range reduction:
// find hi, mid, lo, such that:
// x = hi + mid + lo, in which
// hi is an integer,
// mid * 2^3 is an integer,
// -2^(-4) <= lo < 2^(-4).
// In particular,
// hi + mid = round(x * 2^3) * 2^(-3).
// Then,
// 2^x = 2^(hi + mid + lo) = 2^hi * 2^mid * 2^lo.
// We store 2^mid in the lookup table EXP2_MID_BITS, and compute 2^hi * 2^mid
// by adding hi to the exponent field of 2^mid. 2^lo is computed using a
// degree-3 minimax polynomial generated by Sollya.

float xf = x;
float kf = fputil::nearest_integer(xf * 0x1.0p+3f);
int x_hi_mid = static_cast<int>(kf);
int x_hi = x_hi_mid >> 3;
int x_mid = x_hi_mid & 0x7;
// lo = x - (hi + mid) = round(x * 2^3) * (-2^(-3)) + x
float lo = fputil::multiply_add(kf, -0x1.0p-3f, xf);

uint32_t exp2_hi_mid_bits =
EXP2_MID_BITS[x_mid] +
static_cast<uint32_t>(x_hi << fputil::FPBits<float>::FRACTION_LEN);
float exp2_hi_mid = fputil::FPBits<float>(exp2_hi_mid_bits).get_val();
// Degree-3 minimax polynomial generated by Sollya with the following
// commands:
// > display = hexadecimal;
// > P = fpminimax((2^x - 1)/x, 2, [|SG...|], [-2^-4, 2^-4]);
// > 1 + x * P;
float exp2_lo = fputil::polyeval(lo, 0x1p+0f, 0x1.62e43p-1f, 0x1.ec0aa6p-3f,
0x1.c6b4a6p-5f);
// exp2(x) = exp2(hi + mid) * exp2(lo)
auto [exp2_hi_mid, exp2_lo] = exp2_range_reduction(x);
return fputil::cast<float16>(exp2_hi_mid * exp2_lo);
}

Expand Down
162 changes: 162 additions & 0 deletions libc/src/math/generic/exp2m1f16.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
//===-- Half-precision 2^x - 1 function -----------------------------------===//
//
// 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/math/exp2m1f16.h"
#include "expxf16.h"
#include "hdr/errno_macros.h"
#include "hdr/fenv_macros.h"
#include "src/__support/FPUtil/FEnvImpl.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/FPUtil/PolyEval.h"
#include "src/__support/FPUtil/cast.h"
#include "src/__support/FPUtil/except_value_utils.h"
#include "src/__support/FPUtil/multiply_add.h"
#include "src/__support/FPUtil/rounding_mode.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.h"
#include "src/__support/macros/optimization.h"
#include "src/__support/macros/properties/cpu_features.h"

namespace LIBC_NAMESPACE_DECL {

static constexpr fputil::ExceptValues<float16, 6> EXP2M1F16_EXCEPTS_LO = {{
// (input, RZ output, RU offset, RD offset, RN offset)
// x = 0x1.cf4p-13, exp2m1f16(x) = 0x1.41p-13 (RZ)
{0x0b3dU, 0x0904U, 1U, 0U, 1U},
// x = 0x1.4fcp-12, exp2m1f16(x) = 0x1.d14p-13 (RZ)
{0x0d3fU, 0x0b45U, 1U, 0U, 1U},
// x = 0x1.63p-11, exp2m1f16(x) = 0x1.ec4p-12 (RZ)
{0x118cU, 0x0fb1U, 1U, 0U, 0U},
// x = 0x1.6fp-7, exp2m1f16(x) = 0x1.fe8p-8 (RZ)
{0x21bcU, 0x1ffaU, 1U, 0U, 1U},
// x = -0x1.c6p-10, exp2m1f16(x) = -0x1.3a8p-10 (RZ)
{0x9718U, 0x94eaU, 0U, 1U, 0U},
// x = -0x1.cfcp-10, exp2m1f16(x) = -0x1.414p-10 (RZ)
{0x973fU, 0x9505U, 0U, 1U, 0U},
}};

#ifdef LIBC_TARGET_CPU_HAS_FMA
static constexpr size_t N_EXP2M1F16_EXCEPTS_HI = 6;
#else
static constexpr size_t N_EXP2M1F16_EXCEPTS_HI = 7;
#endif

static constexpr fputil::ExceptValues<float16, N_EXP2M1F16_EXCEPTS_HI>
EXP2M1F16_EXCEPTS_HI = {{
// (input, RZ output, RU offset, RD offset, RN offset)
// x = 0x1.e58p-3, exp2m1f16(x) = 0x1.6dcp-3 (RZ)
{0x3396U, 0x31b7U, 1U, 0U, 0U},
#ifndef LIBC_TARGET_CPU_HAS_FMA
// x = 0x1.2e8p-2, exp2m1f16(x) = 0x1.d14p-3 (RZ)
{0x34baU, 0x3345U, 1U, 0U, 0U},
#endif
// x = 0x1.ad8p-2, exp2m1f16(x) = 0x1.598p-2 (RZ)
{0x36b6U, 0x3566U, 1U, 0U, 0U},
#ifdef LIBC_TARGET_CPU_HAS_FMA
// x = 0x1.edcp-2, exp2m1f16(x) = 0x1.964p-2 (RZ)
{0x37b7U, 0x3659U, 1U, 0U, 1U},
#endif
// x = -0x1.804p-3, exp2m1f16(x) = -0x1.f34p-4 (RZ)
{0xb201U, 0xafcdU, 0U, 1U, 1U},
// x = -0x1.f3p-3, exp2m1f16(x) = -0x1.3e4p-3 (RZ)
{0xb3ccU, 0xb0f9U, 0U, 1U, 0U},
// x = -0x1.294p-1, exp2m1f16(x) = -0x1.53p-2 (RZ)
{0xb8a5U, 0xb54cU, 0U, 1U, 1U},
#ifndef LIBC_TARGET_CPU_HAS_FMA
// x = -0x1.a34p-1, exp2m1f16(x) = -0x1.bb4p-2 (RZ)
{0xba8dU, 0xb6edU, 0U, 1U, 1U},
#endif
}};

LLVM_LIBC_FUNCTION(float16, exp2m1f16, (float16 x)) {
using FPBits = fputil::FPBits<float16>;
FPBits x_bits(x);

uint16_t x_u = x_bits.uintval();
uint16_t x_abs = x_u & 0x7fffU;

// When |x| <= 2^(-3), or |x| >= 11, or x is NaN.
if (LIBC_UNLIKELY(x_abs <= 0x3000U || x_abs >= 0x4980U)) {
// exp2m1(NaN) = NaN
if (x_bits.is_nan()) {
if (x_bits.is_signaling_nan()) {
fputil::raise_except_if_required(FE_INVALID);
return FPBits::quiet_nan().get_val();
}

return x;
}

// When x >= 16.
if (x_u >= 0x4c00 && x_bits.is_pos()) {
// exp2m1(+inf) = +inf
if (x_bits.is_inf())
return FPBits::inf().get_val();

switch (fputil::quick_get_round()) {
case FE_TONEAREST:
case FE_UPWARD:
fputil::set_errno_if_required(ERANGE);
fputil::raise_except_if_required(FE_OVERFLOW | FE_INEXACT);
return FPBits::inf().get_val();
default:
return FPBits::max_normal().get_val();
}
}

// When x < -11.
if (x_u > 0xc980U) {
// exp2m1(-inf) = -1
if (x_bits.is_inf())
return FPBits::one(Sign::NEG).get_val();

// When -12 < x < -11, round(2^x - 1, HP, RN) = -0x1.ffcp-1.
if (x_u < 0xca00U)
return fputil::round_result_slightly_down(
fputil::cast<float16>(-0x1.ffcp-1));

// When x <= -12, round(2^x - 1, HP, RN) = -1.
switch (fputil::quick_get_round()) {
case FE_TONEAREST:
case FE_DOWNWARD:
return FPBits::one(Sign::NEG).get_val();
default:
return -0x1.ffcp-1;
}
}

// When |x| <= 2^(-3).
if (x_abs <= 0x3000U) {
if (auto r = EXP2M1F16_EXCEPTS_LO.lookup(x_u);
LIBC_UNLIKELY(r.has_value()))
return r.value();

float xf = x;
// Degree-5 minimax polynomial generated by Sollya with the following
// commands:
// > display = hexadecimal;
// > P = fpminimax((2^x - 1)/x, 4, [|SG...|], [-2^-3, 2^-3]);
// > x * P;
return fputil::cast<float16>(
xf * fputil::polyeval(xf, 0x1.62e43p-1f, 0x1.ebfbdep-3f,
0x1.c6af88p-5f, 0x1.3b45d6p-7f,
0x1.641e7cp-10f));
}
}

if (auto r = EXP2M1F16_EXCEPTS_HI.lookup(x_u); LIBC_UNLIKELY(r.has_value()))
return r.value();

// exp2(x) = exp2(hi + mid) * exp2(lo)
auto [exp2_hi_mid, exp2_lo] = exp2_range_reduction(x);
// exp2m1(x) = exp2(hi + mid) * exp2(lo) - 1
return fputil::cast<float16>(
fputil::multiply_add(exp2_hi_mid, exp2_lo, -1.0f));
}

} // namespace LIBC_NAMESPACE_DECL
38 changes: 38 additions & 0 deletions libc/src/math/generic/expxf16.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define LLVM_LIBC_SRC_MATH_GENERIC_EXPXF16_H

#include "src/__support/CPP/array.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/FPUtil/PolyEval.h"
#include "src/__support/FPUtil/multiply_add.h"
#include "src/__support/FPUtil/nearest_integer.h"
Expand Down Expand Up @@ -89,6 +90,43 @@ constexpr cpp::array<uint32_t, 8> EXP2_MID_BITS = {
0x3fb5'04f3U, 0x3fc5'672aU, 0x3fd7'44fdU, 0x3fea'c0c7U,
};

LIBC_INLINE ExpRangeReduction exp2_range_reduction(float16 x) {
// For -25 < x < 16, to compute 2^x, we perform the following range reduction:
// find hi, mid, lo, such that:
// x = hi + mid + lo, in which
// hi is an integer,
// mid * 2^3 is an integer,
// -2^(-4) <= lo < 2^(-4).
// In particular,
// hi + mid = round(x * 2^3) * 2^(-3).
// Then,
// 2^x = 2^(hi + mid + lo) = 2^hi * 2^mid * 2^lo.
// We store 2^mid in the lookup table EXP2_MID_BITS, and compute 2^hi * 2^mid
// by adding hi to the exponent field of 2^mid. 2^lo is computed using a
// degree-3 minimax polynomial generated by Sollya.

float xf = x;
float kf = fputil::nearest_integer(xf * 0x1.0p+3f);
int x_hi_mid = static_cast<int>(kf);
int x_hi = x_hi_mid >> 3;
int x_mid = x_hi_mid & 0x7;
// lo = x - (hi + mid) = round(x * 2^3) * (-2^(-3)) + x
float lo = fputil::multiply_add(kf, -0x1.0p-3f, xf);

uint32_t exp2_hi_mid_bits =
EXP2_MID_BITS[x_mid] +
static_cast<uint32_t>(x_hi << fputil::FPBits<float>::FRACTION_LEN);
float exp2_hi_mid = fputil::FPBits<float>(exp2_hi_mid_bits).get_val();
// Degree-3 minimax polynomial generated by Sollya with the following
// commands:
// > display = hexadecimal;
// > P = fpminimax((2^x - 1)/x, 2, [|SG...|], [-2^-4, 2^-4]);
// > 1 + x * P;
float exp2_lo = fputil::polyeval(lo, 0x1p+0f, 0x1.62e43p-1f, 0x1.ec0aa6p-3f,
0x1.c6b4a6p-5f);
return {exp2_hi_mid, exp2_lo};
}

} // namespace LIBC_NAMESPACE_DECL

#endif // LLVM_LIBC_SRC_MATH_GENERIC_EXPXF16_H
11 changes: 11 additions & 0 deletions libc/test/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,17 @@ add_fp_unittest(
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
exp2m1f16_test
NEED_MPFR
SUITE
libc-math-unittests
SRCS
exp2m1f16_test.cpp
DEPENDS
libc.src.math.exp2m1f16
)

add_fp_unittest(
exp10_test
NEED_MPFR
Expand Down
Loading