| 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") |
| 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; | ||
| } |
| 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 |
| 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 ®istry, InternalScopedString &out) { | ||
| ThreadRegistryLock l(®istry); | ||
| // 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 |
| 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 |
| 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=20000000 2>&1 | FileCheck %s | ||
| CHECK: BINGO |
| 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); | ||
| } |
| 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/ |
| 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 |
| 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; | ||
| } |
| 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 | ||
| } |
| 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> |
| 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 |
| 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 |
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| //===-- Exhaustive test for exp2m1f16 -------------------------------------===// | ||
| // | ||
| // 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 "test/UnitTest/FPMatcher.h" | ||
| #include "test/UnitTest/Test.h" | ||
| #include "utils/MPFRWrapper/MPFRUtils.h" | ||
|
|
||
| using LlvmLibcExp2m1f16Test = LIBC_NAMESPACE::testing::FPTest<float16>; | ||
|
|
||
| namespace mpfr = LIBC_NAMESPACE::testing::mpfr; | ||
|
|
||
| // Range: [0, Inf]; | ||
| static constexpr uint16_t POS_START = 0x0000U; | ||
| static constexpr uint16_t POS_STOP = 0x7c00U; | ||
|
|
||
| // Range: [-Inf, 0]; | ||
| static constexpr uint16_t NEG_START = 0x8000U; | ||
| static constexpr uint16_t NEG_STOP = 0xfc00U; | ||
|
|
||
| TEST_F(LlvmLibcExp2m1f16Test, PositiveRange) { | ||
| for (uint16_t v = POS_START; v <= POS_STOP; ++v) { | ||
| float16 x = FPBits(v).get_val(); | ||
| EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Exp2m1, x, | ||
| LIBC_NAMESPACE::exp2m1f16(x), 0.5); | ||
| } | ||
| } | ||
|
|
||
| TEST_F(LlvmLibcExp2m1f16Test, NegativeRange) { | ||
| for (uint16_t v = NEG_START; v <= NEG_STOP; ++v) { | ||
| float16 x = FPBits(v).get_val(); | ||
| EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Exp2m1, x, | ||
| LIBC_NAMESPACE::exp2m1f16(x), 0.5); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| //===-- Unittests for exp2m1f16 -------------------------------------------===// | ||
| // | ||
| // 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 "hdr/fenv_macros.h" | ||
| #include "src/errno/libc_errno.h" | ||
| #include "src/math/exp2m1f16.h" | ||
| #include "test/UnitTest/FPMatcher.h" | ||
| #include "test/UnitTest/Test.h" | ||
|
|
||
| using LlvmLibcExp2m1f16Test = LIBC_NAMESPACE::testing::FPTest<float16>; | ||
|
|
||
| TEST_F(LlvmLibcExp2m1f16Test, SpecialNumbers) { | ||
| LIBC_NAMESPACE::libc_errno = 0; | ||
|
|
||
| EXPECT_FP_EQ_ALL_ROUNDING(aNaN, LIBC_NAMESPACE::exp2m1f16(aNaN)); | ||
| EXPECT_MATH_ERRNO(0); | ||
|
|
||
| EXPECT_FP_EQ_WITH_EXCEPTION(aNaN, LIBC_NAMESPACE::exp2m1f16(sNaN), | ||
| FE_INVALID); | ||
| EXPECT_MATH_ERRNO(0); | ||
|
|
||
| EXPECT_FP_EQ_ALL_ROUNDING(inf, LIBC_NAMESPACE::exp2m1f16(inf)); | ||
| EXPECT_MATH_ERRNO(0); | ||
|
|
||
| EXPECT_FP_EQ_ALL_ROUNDING(-1.0, LIBC_NAMESPACE::exp2m1f16(neg_inf)); | ||
| EXPECT_MATH_ERRNO(0); | ||
|
|
||
| EXPECT_FP_EQ_ALL_ROUNDING(zero, LIBC_NAMESPACE::exp2m1f16(zero)); | ||
| EXPECT_MATH_ERRNO(0); | ||
|
|
||
| EXPECT_FP_EQ_ALL_ROUNDING(neg_zero, LIBC_NAMESPACE::exp2m1f16(neg_zero)); | ||
| EXPECT_MATH_ERRNO(0); | ||
| } | ||
|
|
||
| TEST_F(LlvmLibcExp2m1f16Test, Overflow) { | ||
| LIBC_NAMESPACE::libc_errno = 0; | ||
|
|
||
| EXPECT_FP_EQ_WITH_EXCEPTION(inf, LIBC_NAMESPACE::exp2m1f16(max_normal), | ||
| FE_OVERFLOW | FE_INEXACT); | ||
| EXPECT_MATH_ERRNO(ERANGE); | ||
|
|
||
| float16 x = 16.0; | ||
|
|
||
| EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_NEAREST( | ||
| inf, LIBC_NAMESPACE::exp2m1f16(x), FE_OVERFLOW | FE_INEXACT); | ||
| EXPECT_MATH_ERRNO(ERANGE); | ||
|
|
||
| EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_UPWARD(inf, LIBC_NAMESPACE::exp2m1f16(x), | ||
| FE_OVERFLOW | FE_INEXACT); | ||
| EXPECT_MATH_ERRNO(ERANGE); | ||
|
|
||
| EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_DOWNWARD( | ||
| max_normal, LIBC_NAMESPACE::exp2m1f16(x), FE_INEXACT); | ||
| EXPECT_MATH_ERRNO(0); | ||
|
|
||
| EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_TOWARD_ZERO( | ||
| max_normal, LIBC_NAMESPACE::exp2m1f16(x), FE_INEXACT); | ||
| EXPECT_MATH_ERRNO(0); | ||
| } | ||
|
|
||
| TEST_F(LlvmLibcExp2m1f16Test, ResultNearNegOne) { | ||
| LIBC_NAMESPACE::libc_errno = 0; | ||
|
|
||
| EXPECT_FP_EQ_WITH_EXCEPTION(-1.0, LIBC_NAMESPACE::exp2m1f16(neg_max_normal), | ||
| FE_INEXACT); | ||
|
|
||
| EXPECT_FP_EQ_ALL_ROUNDING(-0x1.ffcp-1, LIBC_NAMESPACE::exp2m1f16(-11.0)); | ||
|
|
||
| float16 x = -12.0; | ||
|
|
||
| EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_NEAREST( | ||
| -1.0, LIBC_NAMESPACE::exp2m1f16(x), FE_INEXACT); | ||
|
|
||
| EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_UPWARD( | ||
| -0x1.ffcp-1, LIBC_NAMESPACE::exp2m1f16(x), FE_INEXACT); | ||
|
|
||
| EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_DOWNWARD( | ||
| -1.0, LIBC_NAMESPACE::exp2m1f16(x), FE_INEXACT); | ||
|
|
||
| EXPECT_FP_EQ_WITH_EXCEPTION_ROUNDING_TOWARD_ZERO( | ||
| -0x1.ffcp-1, LIBC_NAMESPACE::exp2m1f16(x), FE_INEXACT); | ||
| } |