656 changes: 656 additions & 0 deletions clang/test/CodeGen/cx-complex-range.c

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions clang/test/CodeGen/tbaa-struct-bitfield-endianness.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// RUN: %clang_cc1 -triple aarch64_be-apple-darwin -emit-llvm -o - -O1 %s | \
// RUN: FileCheck -check-prefixes=CHECK,CHECK-BE %s
// RUN: %clang_cc1 -triple aarch64-apple-darwin -emit-llvm -o - -O1 %s | \
// RUN: FileCheck -check-prefixes=CHECK,CHECK-LE %s
//
// Check that TBAA metadata for structs containing bitfields is
// consistent between big and little endian layouts.
//
// FIXME: The metadata below is invalid for the big endian layout: the
// start offset of 2 is incorrect.

struct NamedBitfields {
int f1 : 8;
int f2 : 8;
unsigned f3 : 1;
unsigned f4 : 15;
int f5;
double f6;
};

// CHECK-LABEL: _Z4copyP14NamedBitfieldsS0_
// CHECK-SAME: ptr nocapture noundef writeonly [[A1:%.*]], ptr nocapture noundef readonly [[A2:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
// CHECK-NEXT: entry:
// CHECK-NEXT: tail call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 8 dereferenceable(16) [[A1]], ptr noundef nonnull align 8 dereferenceable(16) [[A2]], i64 16, i1 false), !tbaa.struct [[TBAA_STRUCT2:![0-9]+]]
// CHECK-NEXT: ret void
//
void copy(NamedBitfields *a1, NamedBitfields *a2) {
*a1 = *a2;
}

// CHECK-BE: [[TBAA_STRUCT2]] = !{i64 2, i64 4, [[META3:![0-9]+]], i64 4, i64 4, [[META6:![0-9]+]], i64 8, i64 8, [[META8:![0-9]+]]}
// CHECK-LE: [[TBAA_STRUCT2]] = !{i64 0, i64 4, [[META3:![0-9]+]], i64 4, i64 4, [[META6:![0-9]+]], i64 8, i64 8, [[META8:![0-9]+]]}
// CHECK: [[META3]] = !{[[META4:![0-9]+]], [[META4]], i64 0}
// CHECK: [[META4]] = !{!"omnipotent char", [[META5:![0-9]+]], i64 0}
// CHECK: [[META5]] = !{!"Simple C++ TBAA"}
// CHECK: [[META6]] = !{[[META7:![0-9]+]], [[META7]], i64 0}
// CHECK: [[META7]] = !{!"int", [[META4]], i64 0}
// CHECK: [[META8]] = !{[[META9:![0-9]+]], [[META9]], i64 0}
// CHECK: [[META9]] = !{!"double", [[META4]], i64 0}
1 change: 1 addition & 0 deletions clang/test/Driver/mcmodel.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// RUN: FileCheck --check-prefix=AIX-MCMEDIUM-OVERRIDE %s < %t.log
// RUN: not %clang -### -c -mcmodel=lager %s 2>&1 | FileCheck --check-prefix=INVALID %s
// RUN: %clang --target=aarch64 -### -S -mcmodel=large -fno-pic %s 2>&1 | FileCheck --check-prefix=LARGE %s
// RUN: %clang --target=aarch64-apple-macosx -### -S -mcmodel=large %s 2>&1 | FileCheck --check-prefix=LARGE %s
// RUN: not %clang --target=aarch64 -### -S -mcmodel=large -fpic %s 2>&1 | FileCheck --check-prefix=AARCH64-PIC-LARGE %s
// RUN: not %clang -### -c --target=aarch64 -mcmodel=medium %s 2>&1 | FileCheck --check-prefix=ERR-MEDIUM %s
// RUN: not %clang -### -c --target=aarch64 -mcmodel=kernel %s 2>&1 | FileCheck --check-prefix=ERR-KERNEL %s
Expand Down
6 changes: 6 additions & 0 deletions clang/test/InstallAPI/driver-invalid-options.test
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@
// RUN: not clang-installapi -target x86_64-apple-ios-simulator %s -o tmp.tbd 2> %t
// RUN: FileCheck --check-prefix INVALID_INSTALL_NAME -input-file %t %s
// INVALID_INSTALL_NAME: error: no install name specified: add -install_name <path>

/// Check invalid verification mode.
// RUN: not clang-installapi -install_name Foo -target arm64-apple-ios13 \
// RUN: --verify-mode=Invalid -o tmp.tbd 2> %t
// RUN: FileCheck --check-prefix INVALID_VERIFY_MODE -input-file %t %s
// INVALID_VERIFY_MODE: error: invalid value 'Invalid' in '--verify-mode=Invalid'
17 changes: 17 additions & 0 deletions clang/test/Interpreter/inline-asm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// REQUIRES: host-supports-jit, x86_64-linux
// UNSUPPORTED: system-aix
//
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t
//
// RUN: cat %t/inline-asm.txt | clang-repl -Xcc="-I%t"

//--- inline-asm.cpp
__asm(".globl _ZSt21ios_base_library_initv");
int x;

//--- inline-asm.txt
#include "inline-asm.cpp"
x = 10;
%quit
1 change: 1 addition & 0 deletions clang/tools/clang-repl/ClangRepl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ int main(int argc, const char **argv) {
llvm::InitializeAllTargets();
llvm::InitializeAllTargetMCs();
llvm::InitializeAllAsmPrinters();
llvm::InitializeAllAsmParsers();

if (OptHostSupportsJit) {
auto J = llvm::orc::LLJITBuilder().create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,25 @@ class NullPointerAnalysis final
else
JoinedVal.setProperty("is_null", JoinedEnv.makeTopBoolValue());
}

std::optional<WidenResult> widen(QualType Type, Value &Prev,
const Environment &PrevEnv, Value &Current,
Environment &CurrentEnv) override {
switch (compare(Type, Prev, PrevEnv, Current, CurrentEnv)) {
case ComparisonResult::Same:
return WidenResult{&Current, LatticeJoinEffect::Unchanged};
case ComparisonResult::Different: {
auto &CurPtr = cast<PointerValue>(Current);
auto &WidenedPtr =
CurrentEnv.create<PointerValue>(CurPtr.getPointeeLoc());
WidenedPtr.setProperty("is_null", CurrentEnv.makeTopBoolValue());
return WidenResult{&WidenedPtr, LatticeJoinEffect::Changed};
}
case ComparisonResult::Unknown:
return std::nullopt;
}
llvm_unreachable("all cases in switch covered");
}
};

class WideningTest : public Test {
Expand Down Expand Up @@ -846,7 +865,6 @@ TEST_F(WideningTest, JoinDistinctValuesWithDistinctProperties) {
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2", "p3"));
const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
const Environment &Env3 = getEnvironmentAtAnnotation(Results, "p3");
Expand Down Expand Up @@ -889,8 +907,6 @@ TEST_F(WideningTest, JoinDistinctValuesWithSameProperties) {
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
ASSERT_THAT(Results.keys(),
UnorderedElementsAre("p1", "p2", "p3", "p4"));
const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
const Environment &Env3 = getEnvironmentAtAnnotation(Results, "p3");
Expand Down Expand Up @@ -929,19 +945,11 @@ TEST_F(WideningTest, DistinctPointersToTheSameLocationAreEquivalent) {
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
ASSERT_THAT(FooDecl, NotNull());

const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
ASSERT_THAT(BarDecl, NotNull());

const auto *FooLoc =
cast<ScalarStorageLocation>(Env.getStorageLocation(*FooDecl));
const auto *BarVal = cast<PointerValue>(Env.getValue(*BarDecl));
EXPECT_EQ(&BarVal->getPointeeLoc(), FooLoc);
const auto &FooLoc =
getLocForDecl<ScalarStorageLocation>(ASTCtx, Env, "Foo");
const auto &BarVal = getValueForDecl<PointerValue>(ASTCtx, Env, "Bar");
EXPECT_EQ(&BarVal.getPointeeLoc(), &FooLoc);
});
}

Expand All @@ -963,18 +971,38 @@ TEST_F(WideningTest, DistinctValuesWithSamePropertiesAreEquivalent) {
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");

const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
ASSERT_THAT(FooDecl, NotNull());

const auto *FooVal = Env.getValue(*FooDecl);
EXPECT_EQ(FooVal->getProperty("is_null"),
const auto &FooVal = getValueForDecl<Value>(ASTCtx, Env, "Foo");
EXPECT_EQ(FooVal.getProperty("is_null"),
&Env.getBoolLiteralValue(false));
});
}

TEST_F(WideningTest, DistinctValuesWithDifferentPropertiesWidenedToTop) {
std::string Code = R"(
void target(bool Cond) {
int *Foo;
int i = 0;
Foo = nullptr;
while (Cond) {
Foo = &i;
}
(void)0;
/*[[p]]*/
}
)";
runDataflow(
Code,
[](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
ASTContext &ASTCtx) {
const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
const auto &FooVal = getValueForDecl<Value>(ASTCtx, Env, "Foo");
ASSERT_THAT(FooVal.getProperty("is_null"), NotNull());
EXPECT_TRUE(areEquivalentValues(*FooVal.getProperty("is_null"),
Env.makeTopBoolValue()));
});
}

class FlowConditionTest : public Test {
protected:
template <typename Matcher>
Expand Down
164 changes: 82 additions & 82 deletions flang/include/flang/Runtime/io-api.h

Large diffs are not rendered by default.

9 changes: 0 additions & 9 deletions flang/lib/Lower/ConvertCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1340,15 +1340,6 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
} else {
addr = hlfir::genVariableRawAddress(loc, builder, entity);
}
// The last extent created for assumed-rank descriptors must be -1 (18.5.3
// point 5.). This should be done when creating the assumed-size shape for
// consistency.
if (auto baseBoxDummy = mlir::dyn_cast<fir::BaseBoxType>(dummyType))
if (baseBoxDummy.isAssumedRank())
if (const Fortran::semantics::Symbol *sym =
Fortran::evaluate::UnwrapWholeSymbolDataRef(*arg.entity))
if (Fortran::semantics::IsAssumedSizeArray(sym->GetUltimate()))
TODO(loc, "passing assumed-size to assumed-rank array");

// For ranked actual passed to assumed-rank dummy, the cast to assumed-rank
// box is inserted when building the fir.call op. Inserting it here would
Expand Down
31 changes: 27 additions & 4 deletions flang/lib/Lower/OpenMP/ReductionProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "ReductionProcessor.h"

#include "flang/Lower/AbstractConverter.h"
#include "flang/Lower/SymbolMap.h"
#include "flang/Optimizer/Builder/HLFIRTools.h"
#include "flang/Optimizer/Builder/Todo.h"
#include "flang/Optimizer/Dialect/FIRType.h"
Expand Down Expand Up @@ -522,12 +523,20 @@ void ReductionProcessor::addDeclareReduction(
if (reductionSymbols)
reductionSymbols->push_back(symbol);
mlir::Value symVal = converter.getSymbolAddress(*symbol);
auto redType = mlir::cast<fir::ReferenceType>(symVal.getType());
mlir::Type eleType;
auto refType = mlir::dyn_cast_or_null<fir::ReferenceType>(symVal.getType());
if (refType)
eleType = refType.getEleTy();
else
eleType = symVal.getType();

// all arrays must be boxed so that we have convenient access to all the
// information needed to iterate over the array
if (mlir::isa<fir::SequenceType>(redType.getEleTy())) {
hlfir::Entity entity{symVal};
if (mlir::isa<fir::SequenceType>(eleType)) {
// For Host associated symbols, use `SymbolBox` instead
Fortran::lower::SymbolBox symBox =
converter.lookupOneLevelUpSymbol(*symbol);
hlfir::Entity entity{symBox.getAddr()};
entity = genVariableBox(currentLocation, builder, entity);
mlir::Value box = entity.getBase();

Expand All @@ -538,11 +547,25 @@ void ReductionProcessor::addDeclareReduction(
builder.create<fir::StoreOp>(currentLocation, box, alloca);

symVal = alloca;
redType = mlir::cast<fir::ReferenceType>(symVal.getType());
} else if (mlir::isa<fir::BaseBoxType>(symVal.getType())) {
// boxed arrays are passed as values not by reference. Unfortunately,
// we can't pass a box by value to omp.redution_declare, so turn it
// into a reference

auto alloca =
builder.create<fir::AllocaOp>(currentLocation, symVal.getType());
builder.create<fir::StoreOp>(currentLocation, symVal, alloca);
symVal = alloca;
} else if (auto declOp = symVal.getDefiningOp<hlfir::DeclareOp>()) {
symVal = declOp.getBase();
}

// this isn't the same as the by-val and by-ref passing later in the
// pipeline. Both styles assume that the variable is a reference at
// this point
assert(mlir::isa<fir::ReferenceType>(symVal.getType()) &&
"reduction input var is a reference");

reductionVars.push_back(symVal);
}
const bool isByRef = doReductionByRef(reductionVars);
Expand Down
9 changes: 5 additions & 4 deletions flang/lib/Lower/Runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ static void genUnreachable(fir::FirOpBuilder &builder, mlir::Location loc) {
void Fortran::lower::genStopStatement(
Fortran::lower::AbstractConverter &converter,
const Fortran::parser::StopStmt &stmt) {
const bool isError = std::get<Fortran::parser::StopStmt::Kind>(stmt.t) ==
Fortran::parser::StopStmt::Kind::ErrorStop;
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
mlir::Location loc = converter.getCurrentLocation();
Fortran::lower::StatementContext stmtCtx;
Expand Down Expand Up @@ -94,13 +96,12 @@ void Fortran::lower::genStopStatement(
} else {
callee = fir::runtime::getRuntimeFunc<mkRTKey(StopStatement)>(loc, builder);
calleeType = callee.getFunctionType();
operands.push_back(
builder.createIntegerConstant(loc, calleeType.getInput(0), 0));
// Default to values are advised in F'2023 11.4 p2.
operands.push_back(builder.createIntegerConstant(
loc, calleeType.getInput(0), isError ? 1 : 0));
}

// Second operand indicates ERROR STOP
bool isError = std::get<Fortran::parser::StopStmt::Kind>(stmt.t) ==
Fortran::parser::StopStmt::Kind::ErrorStop;
operands.push_back(builder.createIntegerConstant(
loc, calleeType.getInput(operands.size()), isError));

Expand Down
2 changes: 0 additions & 2 deletions flang/runtime/environment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ static void SetEnvironmentDefaults(const EnvironmentDefaultList *envDefaults) {
}
}

RT_OFFLOAD_API_GROUP_BEGIN
Fortran::common::optional<Convert> GetConvertFromString(
const char *x, std::size_t n) {
static const char *keywords[]{
Expand All @@ -69,7 +68,6 @@ Fortran::common::optional<Convert> GetConvertFromString(
return Fortran::common::nullopt;
}
}
RT_OFFLOAD_API_GROUP_END

void ExecutionEnvironment::Configure(int ac, const char *av[],
const char *env[], const EnvironmentDefaultList *envDefaults) {
Expand Down
2 changes: 1 addition & 1 deletion flang/runtime/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ RT_OFFLOAD_VAR_GROUP_END
// External unformatted I/O data conversions
enum class Convert { Unknown, Native, LittleEndian, BigEndian, Swap };

RT_API_ATTRS Fortran::common::optional<Convert> GetConvertFromString(
Fortran::common::optional<Convert> GetConvertFromString(
const char *, std::size_t);

struct ExecutionEnvironment {
Expand Down
19 changes: 0 additions & 19 deletions flang/runtime/freestanding-tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,6 @@
#define STD_STRCPY_UNSUPPORTED 1
#endif

#if !defined(STD_STRCMP_UNSUPPORTED) && \
(defined(__CUDACC__) || defined(__CUDA__)) && defined(__CUDA_ARCH__)
#define STD_STRCMP_UNSUPPORTED 1
#endif

namespace Fortran::runtime {

#if STD_FILL_N_UNSUPPORTED
Expand Down Expand Up @@ -181,19 +176,5 @@ static inline RT_API_ATTRS char *strcpy(char *dest, const char *src) {
using std::strcpy;
#endif // !STD_STRCPY_UNSUPPORTED

#if STD_STRCMP_UNSUPPORTED
// Provides alternative implementation for std::strcmp(), if
// it is not supported.
static inline RT_API_ATTRS int strcmp(const char *lhs, const char *rhs) {
while (*lhs != '\0' && *lhs == *rhs) {
++lhs;
++rhs;
}
return static_cast<unsigned char>(*lhs) - static_cast<unsigned char>(*rhs);
}
#else // !STD_STRCMP_UNSUPPORTED
using std::strcmp;
#endif // !STD_STRCMP_UNSUPPORTED

} // namespace Fortran::runtime
#endif // FORTRAN_RUNTIME_FREESTANDING_TOOLS_H_
204 changes: 105 additions & 99 deletions flang/runtime/io-api.cpp

Large diffs are not rendered by default.

9 changes: 3 additions & 6 deletions flang/runtime/io-error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ void IoErrorHandler::SignalPendingError() {
SignalError(error);
}

RT_OFFLOAD_API_GROUP_END

void IoErrorHandler::SignalErrno() { SignalError(errno); }

bool IoErrorHandler::GetIoMsg(char *buffer, std::size_t bufferLength) {
Expand All @@ -125,10 +127,7 @@ bool IoErrorHandler::GetIoMsg(char *buffer, std::size_t bufferLength) {
// in LLVM v9.0.1 with inadequate modification for Fortran,
// since rectified.
bool ok{false};
#if defined(RT_DEVICE_COMPILATION)
// strerror_r is not available on device.
msg = "errno description is not available on device";
#elif HAVE_STRERROR_R
#if HAVE_STRERROR_R
// strerror_r is thread-safe.
#if defined(__GLIBC__) && defined(_GNU_SOURCE)
// glibc defines its own incompatible version of strerror_r
Expand Down Expand Up @@ -158,6 +157,4 @@ bool IoErrorHandler::GetIoMsg(char *buffer, std::size_t bufferLength) {
return false;
}
}

RT_OFFLOAD_API_GROUP_END
} // namespace Fortran::runtime::io
2 changes: 1 addition & 1 deletion flang/runtime/io-error.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class IoErrorHandler : public Terminator {
RT_API_ATTRS void SignalPendingError();

RT_API_ATTRS int GetIoStat() const { return ioStat_; }
RT_API_ATTRS bool GetIoMsg(char *, std::size_t);
bool GetIoMsg(char *, std::size_t);

private:
enum Flag : std::uint8_t {
Expand Down
46 changes: 21 additions & 25 deletions flang/runtime/namelist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,16 @@

namespace Fortran::runtime::io {

RT_VAR_GROUP_BEGIN
// Max size of a group, symbol or component identifier that can appear in
// NAMELIST input, plus a byte for NUL termination.
static constexpr RT_CONST_VAR_ATTRS std::size_t nameBufferSize{201};
RT_VAR_GROUP_END
static constexpr std::size_t nameBufferSize{201};

RT_OFFLOAD_API_GROUP_BEGIN

static inline RT_API_ATTRS char32_t GetComma(IoStatementState &io) {
static inline char32_t GetComma(IoStatementState &io) {
return io.mutableModes().editingFlags & decimalComma ? char32_t{';'}
: char32_t{','};
}

bool IODEF(OutputNamelist)(Cookie cookie, const NamelistGroup &group) {
bool IONAME(OutputNamelist)(Cookie cookie, const NamelistGroup &group) {
IoStatementState &io{*cookie};
io.CheckFormattedStmtType<Direction::Output>("OutputNamelist");
io.mutableModes().inNamelist = true;
Expand All @@ -44,8 +40,7 @@ bool IODEF(OutputNamelist)(Cookie cookie, const NamelistGroup &group) {
if ((connection.NeedAdvance(prefixLen) &&
!(io.AdvanceRecord() && EmitAscii(io, " ", 1))) ||
!EmitAscii(io, prefix, prefixLen) ||
(connection.NeedAdvance(
Fortran::runtime::strlen(str) + (suffix != ' ')) &&
(connection.NeedAdvance(std::strlen(str) + (suffix != ' ')) &&
!(io.AdvanceRecord() && EmitAscii(io, " ", 1)))) {
return false;
}
Expand Down Expand Up @@ -89,20 +84,20 @@ bool IODEF(OutputNamelist)(Cookie cookie, const NamelistGroup &group) {
return EmitUpperCase("/", 1, "", ' ');
}

static constexpr RT_API_ATTRS bool IsLegalIdStart(char32_t ch) {
static constexpr bool IsLegalIdStart(char32_t ch) {
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' ||
ch == '@';
}

static constexpr RT_API_ATTRS bool IsLegalIdChar(char32_t ch) {
static constexpr bool IsLegalIdChar(char32_t ch) {
return IsLegalIdStart(ch) || (ch >= '0' && ch <= '9');
}

static constexpr RT_API_ATTRS char NormalizeIdChar(char32_t ch) {
static constexpr char NormalizeIdChar(char32_t ch) {
return static_cast<char>(ch >= 'A' && ch <= 'Z' ? ch - 'A' + 'a' : ch);
}

static RT_API_ATTRS bool GetLowerCaseName(
static bool GetLowerCaseName(
IoStatementState &io, char buffer[], std::size_t maxLength) {
std::size_t byteLength{0};
if (auto ch{io.GetNextNonBlank(byteLength)}) {
Expand All @@ -124,7 +119,7 @@ static RT_API_ATTRS bool GetLowerCaseName(
return false;
}

static RT_API_ATTRS Fortran::common::optional<SubscriptValue> GetSubscriptValue(
static Fortran::common::optional<SubscriptValue> GetSubscriptValue(
IoStatementState &io) {
Fortran::common::optional<SubscriptValue> value;
std::size_t byteCount{0};
Expand Down Expand Up @@ -157,8 +152,8 @@ static RT_API_ATTRS Fortran::common::optional<SubscriptValue> GetSubscriptValue(
return value;
}

static RT_API_ATTRS bool HandleSubscripts(IoStatementState &io,
Descriptor &desc, const Descriptor &source, const char *name) {
static bool HandleSubscripts(IoStatementState &io, Descriptor &desc,
const Descriptor &source, const char *name) {
IoErrorHandler &handler{io.GetIoErrorHandler()};
// Allow for blanks in subscripts; they're nonstandard, but not
// ambiguous within the parentheses.
Expand Down Expand Up @@ -257,7 +252,7 @@ static RT_API_ATTRS bool HandleSubscripts(IoStatementState &io,
return false;
}

static RT_API_ATTRS void StorageSequenceExtension(
static void StorageSequenceExtension(
Descriptor &desc, const Descriptor &source) {
// Support the near-universal extension of NAMELIST input into a
// designatable storage sequence identified by its initial scalar array
Expand All @@ -279,7 +274,7 @@ static RT_API_ATTRS void StorageSequenceExtension(
}
}

static RT_API_ATTRS bool HandleSubstring(
static bool HandleSubstring(
IoStatementState &io, Descriptor &desc, const char *name) {
IoErrorHandler &handler{io.GetIoErrorHandler()};
auto pair{desc.type().GetCategoryAndKind()};
Expand Down Expand Up @@ -340,7 +335,7 @@ static RT_API_ATTRS bool HandleSubstring(
return false;
}

static RT_API_ATTRS bool HandleComponent(IoStatementState &io, Descriptor &desc,
static bool HandleComponent(IoStatementState &io, Descriptor &desc,
const Descriptor &source, const char *name) {
IoErrorHandler &handler{io.GetIoErrorHandler()};
char compName[nameBufferSize];
Expand All @@ -349,8 +344,7 @@ static RT_API_ATTRS bool HandleComponent(IoStatementState &io, Descriptor &desc,
if (const typeInfo::DerivedType *
type{addendum ? addendum->derivedType() : nullptr}) {
if (const typeInfo::Component *
comp{type->FindDataComponent(
compName, Fortran::runtime::strlen(compName))}) {
comp{type->FindDataComponent(compName, std::strlen(compName))}) {
bool createdDesc{false};
if (comp->rank() > 0 && source.rank() > 0) {
// If base and component are both arrays, the component name
Expand Down Expand Up @@ -414,7 +408,7 @@ static RT_API_ATTRS bool HandleComponent(IoStatementState &io, Descriptor &desc,

// Advance to the terminal '/' of a namelist group or leading '&'/'$'
// of the next.
static RT_API_ATTRS void SkipNamelistGroup(IoStatementState &io) {
static void SkipNamelistGroup(IoStatementState &io) {
std::size_t byteCount{0};
while (auto ch{io.GetNextNonBlank(byteCount)}) {
io.HandleRelativePosition(byteCount);
Expand All @@ -437,7 +431,7 @@ static RT_API_ATTRS void SkipNamelistGroup(IoStatementState &io) {
}
}

bool IODEF(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
bool IONAME(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
IoStatementState &io{*cookie};
io.CheckFormattedStmtType<Direction::Input>("InputNamelist");
io.mutableModes().inNamelist = true;
Expand Down Expand Up @@ -476,7 +470,7 @@ bool IODEF(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
handler.SignalError("NAMELIST input group has no name");
return false;
}
if (Fortran::runtime::strcmp(group.groupName, name) == 0) {
if (std::strcmp(group.groupName, name) == 0) {
break; // found it
}
SkipNamelistGroup(io);
Expand All @@ -495,7 +489,7 @@ bool IODEF(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
}
std::size_t itemIndex{0};
for (; itemIndex < group.items; ++itemIndex) {
if (Fortran::runtime::strcmp(name, group.item[itemIndex].name) == 0) {
if (std::strcmp(name, group.item[itemIndex].name) == 0) {
break;
}
}
Expand Down Expand Up @@ -596,6 +590,8 @@ bool IODEF(InputNamelist)(Cookie cookie, const NamelistGroup &group) {
return true;
}

RT_OFFLOAD_API_GROUP_BEGIN

bool IsNamelistNameOrSlash(IoStatementState &io) {
if (auto *listInput{
io.get_if<ListDirectedStatementState<Direction::Input>>()}) {
Expand Down
23 changes: 17 additions & 6 deletions flang/test/Lower/HLFIR/assumed-rank-iface.f90
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,20 @@ subroutine int_allocatable_to_assumed_rank_opt(x)
! CHECK: %[[VAL_11:.*]] = fir.convert %[[VAL_7]] : (!fir.box<!fir.array<?x?xi32>>) -> !fir.box<!fir.array<*:i32>>
! CHECK: fir.call @_QPint_opt_assumed_rank(%[[VAL_11]]) fastmath<contract> : (!fir.box<!fir.array<*:i32>>) -> ()

! TODO: set assumed size last extent to -1.
!subroutine int_r2_assumed_size_to_assumed_rank(x)
! use ifaces, only : int_assumed_rank
! integer :: x(10, *)
! call int_assumed_rank(x)
!end subroutine
subroutine int_r2_assumed_size_to_assumed_rank(x)
use ifaces, only : int_assumed_rank
integer :: x(10, *)
call int_assumed_rank(x)
end subroutine
! CHECK-LABEL: func.func @_QPint_r2_assumed_size_to_assumed_rank(
! CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<!fir.array<10x?xi32>> {fir.bindc_name = "x"}) {
! CHECK: %[[VAL_1:.*]] = arith.constant 10 : i64
! CHECK: %[[VAL_2:.*]] = fir.convert %[[VAL_1]] : (i64) -> index
! CHECK: %[[VAL_3:.*]] = arith.constant 0 : index
! CHECK: %[[VAL_4:.*]] = arith.cmpi sgt, %[[VAL_2]], %[[VAL_3]] : index
! CHECK: %[[VAL_5:.*]] = arith.select %[[VAL_4]], %[[VAL_2]], %[[VAL_3]] : index
! CHECK: %[[VAL_6:.*]] = arith.constant -1 : index
! CHECK: %[[VAL_7:.*]] = fir.shape %[[VAL_5]], %[[VAL_6]] : (index, index) -> !fir.shape<2>
! CHECK: %[[VAL_8:.*]]:2 = hlfir.declare %[[VAL_0]](%[[VAL_7]]) {uniq_name = "_QFint_r2_assumed_size_to_assumed_rankEx"} : (!fir.ref<!fir.array<10x?xi32>>, !fir.shape<2>) -> (!fir.box<!fir.array<10x?xi32>>, !fir.ref<!fir.array<10x?xi32>>)
! CHECK: %[[VAL_9:.*]] = fir.convert %[[VAL_8]]#0 : (!fir.box<!fir.array<10x?xi32>>) -> !fir.box<!fir.array<*:i32>>
! CHECK: fir.call @_QPint_assumed_rank(%[[VAL_9]]) fastmath<contract> : (!fir.box<!fir.array<*:i32>>) -> ()
2 changes: 1 addition & 1 deletion flang/test/Lower/OpenMP/parallel-reduction-array.f90
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ program reduce
! CHECK: %[[VAL_1:.*]] = arith.constant 3 : index
! CHECK: %[[VAL_2:.*]] = fir.shape %[[VAL_1]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_3:.*]]:2 = hlfir.declare %[[VAL_0]](%[[VAL_2]]) {uniq_name = "_QFEi"} : (!fir.ref<!fir.array<3xi32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<3xi32>>, !fir.ref<!fir.array<3xi32>>)
! CHECK: %[[VAL_4:.*]] = fir.embox %[[VAL_3]]#1(%[[VAL_2]]) : (!fir.ref<!fir.array<3xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<3xi32>>
! CHECK: %[[VAL_4:.*]] = fir.embox %[[VAL_3]]#0(%[[VAL_2]]) : (!fir.ref<!fir.array<3xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<3xi32>>
! CHECK: %[[VAL_5:.*]] = fir.alloca !fir.box<!fir.array<3xi32>>
! CHECK: fir.store %[[VAL_4]] to %[[VAL_5]] : !fir.ref<!fir.box<!fir.array<3xi32>>>
! CHECK: omp.parallel byref reduction(@add_reduction_byref_box_3xi32 %[[VAL_5]] -> %[[VAL_6:.*]] : !fir.ref<!fir.box<!fir.array<3xi32>>>) {
Expand Down
2 changes: 1 addition & 1 deletion flang/test/Lower/OpenMP/parallel-reduction-array2.f90
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ program reduce
! CHECK: %[[VAL_1:.*]] = arith.constant 3 : index
! CHECK: %[[VAL_2:.*]] = fir.shape %[[VAL_1]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_3:.*]]:2 = hlfir.declare %[[VAL_0]](%[[VAL_2]]) {uniq_name = "_QFEi"} : (!fir.ref<!fir.array<3xi32>>, !fir.shape<1>) -> (!fir.ref<!fir.array<3xi32>>, !fir.ref<!fir.array<3xi32>>)
! CHECK: %[[VAL_4:.*]] = fir.embox %[[VAL_3]]#1(%[[VAL_2]]) : (!fir.ref<!fir.array<3xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<3xi32>>
! CHECK: %[[VAL_4:.*]] = fir.embox %[[VAL_3]]#0(%[[VAL_2]]) : (!fir.ref<!fir.array<3xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<3xi32>>
! CHECK: %[[VAL_5:.*]] = fir.alloca !fir.box<!fir.array<3xi32>>
! CHECK: fir.store %[[VAL_4]] to %[[VAL_5]] : !fir.ref<!fir.box<!fir.array<3xi32>>>
! CHECK: omp.parallel byref reduction(@add_reduction_byref_box_3xi32 %[[VAL_5]] -> %[[VAL_6:.*]] : !fir.ref<!fir.box<!fir.array<3xi32>>>) {
Expand Down
125 changes: 125 additions & 0 deletions flang/test/Lower/OpenMP/parallel-reduction3.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
! NOTE: Assertions have been autogenerated by utils/generate-test-checks.py

! The script is designed to make adding checks to
! a test case fast, it is *not* designed to be authoritative
! about what constitutes a good test! The CHECK should be
! minimized and named to reflect the test intent.

! RUN: bbc -emit-hlfir -fopenmp -o - %s 2>&1 | FileCheck %s
! RUN: %flang_fc1 -emit-hlfir -fopenmp -o - %s 2>&1 | FileCheck %s



! CHECK-LABEL: omp.declare_reduction @add_reduction_byref_box_Uxi32 : !fir.ref<!fir.box<!fir.array<?xi32>>> init {
! CHECK: ^bb0(%[[VAL_0:.*]]: !fir.ref<!fir.box<!fir.array<?xi32>>>):
! CHECK: %[[VAL_1:.*]] = arith.constant 0 : i32
! CHECK: %[[VAL_2:.*]] = fir.load %[[VAL_0]] : !fir.ref<!fir.box<!fir.array<?xi32>>>
! CHECK: %[[VAL_3:.*]] = arith.constant 0 : index
! CHECK: %[[VAL_4:.*]]:3 = fir.box_dims %[[VAL_2]], %[[VAL_3]] : (!fir.box<!fir.array<?xi32>>, index) -> (index, index, index)
! CHECK: %[[VAL_5:.*]] = fir.shape %[[VAL_4]]#1 : (index) -> !fir.shape<1>
! CHECK: %[[VAL_6:.*]] = fir.alloca !fir.array<?xi32>, %[[VAL_4]]#1 {bindc_name = ".tmp"}
! CHECK: %[[VAL_7:.*]]:2 = hlfir.declare %[[VAL_6]](%[[VAL_5]]) {uniq_name = ".tmp"} : (!fir.ref<!fir.array<?xi32>>, !fir.shape<1>) -> (!fir.box<!fir.array<?xi32>>, !fir.ref<!fir.array<?xi32>>)
! CHECK: hlfir.assign %[[VAL_1]] to %[[VAL_7]]#0 : i32, !fir.box<!fir.array<?xi32>>
! CHECK: %[[VAL_8:.*]] = fir.alloca !fir.box<!fir.array<?xi32>>
! CHECK: fir.store %[[VAL_7]]#0 to %[[VAL_8]] : !fir.ref<!fir.box<!fir.array<?xi32>>>
! CHECK: omp.yield(%[[VAL_8]] : !fir.ref<!fir.box<!fir.array<?xi32>>>)

! CHECK-LABEL: } combiner {
! CHECK: ^bb0(%[[VAL_0:.*]]: !fir.ref<!fir.box<!fir.array<?xi32>>>, %[[VAL_1:.*]]: !fir.ref<!fir.box<!fir.array<?xi32>>>):
! CHECK: %[[VAL_2:.*]] = fir.load %[[VAL_0]] : !fir.ref<!fir.box<!fir.array<?xi32>>>
! CHECK: %[[VAL_3:.*]] = fir.load %[[VAL_1]] : !fir.ref<!fir.box<!fir.array<?xi32>>>
! CHECK: %[[VAL_4:.*]] = arith.constant 0 : index
! CHECK: %[[VAL_5:.*]]:3 = fir.box_dims %[[VAL_2]], %[[VAL_4]] : (!fir.box<!fir.array<?xi32>>, index) -> (index, index, index)
! CHECK: %[[VAL_6:.*]] = fir.shape_shift %[[VAL_5]]#0, %[[VAL_5]]#1 : (index, index) -> !fir.shapeshift<1>
! CHECK: %[[VAL_7:.*]] = arith.constant 1 : index
! CHECK: fir.do_loop %[[VAL_8:.*]] = %[[VAL_7]] to %[[VAL_5]]#1 step %[[VAL_7]] unordered {
! CHECK: %[[VAL_9:.*]] = fir.array_coor %[[VAL_2]](%[[VAL_6]]) %[[VAL_8]] : (!fir.box<!fir.array<?xi32>>, !fir.shapeshift<1>, index) -> !fir.ref<i32>
! CHECK: %[[VAL_10:.*]] = fir.array_coor %[[VAL_3]](%[[VAL_6]]) %[[VAL_8]] : (!fir.box<!fir.array<?xi32>>, !fir.shapeshift<1>, index) -> !fir.ref<i32>
! CHECK: %[[VAL_11:.*]] = fir.load %[[VAL_9]] : !fir.ref<i32>
! CHECK: %[[VAL_12:.*]] = fir.load %[[VAL_10]] : !fir.ref<i32>
! CHECK: %[[VAL_13:.*]] = arith.addi %[[VAL_11]], %[[VAL_12]] : i32
! CHECK: fir.store %[[VAL_13]] to %[[VAL_9]] : !fir.ref<i32>
! CHECK: }
! CHECK: omp.yield(%[[VAL_0]] : !fir.ref<!fir.box<!fir.array<?xi32>>>)
! CHECK: }

! CHECK-LABEL: func.func @_QPs(
! CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<i32> {fir.bindc_name = "x"}) {
! CHECK: %[[VAL_1:.*]]:2 = hlfir.declare %[[VAL_0]] {uniq_name = "_QFsEx"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
! CHECK: %[[VAL_2:.*]] = fir.alloca i32 {bindc_name = "i", uniq_name = "_QFsEi"}
! CHECK: %[[VAL_3:.*]]:2 = hlfir.declare %[[VAL_2]] {uniq_name = "_QFsEi"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
! CHECK: %[[VAL_4:.*]] = fir.load %[[VAL_1]]#0 : !fir.ref<i32>
! CHECK: %[[VAL_5:.*]] = fir.convert %[[VAL_4]] : (i32) -> i64
! CHECK: %[[VAL_6:.*]] = fir.convert %[[VAL_5]] : (i64) -> index
! CHECK: %[[VAL_7:.*]] = arith.constant 0 : index
! CHECK: %[[VAL_8:.*]] = arith.cmpi sgt, %[[VAL_6]], %[[VAL_7]] : index
! CHECK: %[[VAL_9:.*]] = arith.select %[[VAL_8]], %[[VAL_6]], %[[VAL_7]] : index
! CHECK: %[[VAL_10:.*]] = fir.alloca !fir.array<?xi32>, %[[VAL_9]] {bindc_name = "c", uniq_name = "_QFsEc"}
! CHECK: %[[VAL_11:.*]] = fir.shape %[[VAL_9]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_12:.*]]:2 = hlfir.declare %[[VAL_10]](%[[VAL_11]]) {uniq_name = "_QFsEc"} : (!fir.ref<!fir.array<?xi32>>, !fir.shape<1>) -> (!fir.box<!fir.array<?xi32>>, !fir.ref<!fir.array<?xi32>>)
! CHECK: %[[VAL_13:.*]] = arith.constant 0 : i32
! CHECK: hlfir.assign %[[VAL_13]] to %[[VAL_12]]#0 : i32, !fir.box<!fir.array<?xi32>>
! CHECK: omp.parallel {
! CHECK: %[[VAL_14:.*]] = fir.alloca i32 {adapt.valuebyref, pinned}
! CHECK: %[[VAL_15:.*]]:2 = hlfir.declare %[[VAL_14]] {uniq_name = "_QFsEi"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
! CHECK: %[[VAL_16:.*]] = arith.constant 1 : i32
! CHECK: %[[VAL_17:.*]] = arith.constant 100 : i32
! CHECK: %[[VAL_18:.*]] = arith.constant 1 : i32
! CHECK: %[[VAL_19:.*]] = fir.alloca !fir.box<!fir.array<?xi32>>
! CHECK: fir.store %[[VAL_12]]#0 to %[[VAL_19]] : !fir.ref<!fir.box<!fir.array<?xi32>>>
! CHECK: omp.wsloop byref reduction(@add_reduction_byref_box_Uxi32 %[[VAL_19]] -> %[[VAL_20:.*]] : !fir.ref<!fir.box<!fir.array<?xi32>>>) for (%[[VAL_21:.*]]) : i32 = (%[[VAL_16]]) to (%[[VAL_17]]) inclusive step (%[[VAL_18]]) {
! CHECK: fir.store %[[VAL_21]] to %[[VAL_15]]#1 : !fir.ref<i32>
! CHECK: %[[VAL_22:.*]]:2 = hlfir.declare %[[VAL_20]] {uniq_name = "_QFsEc"} : (!fir.ref<!fir.box<!fir.array<?xi32>>>) -> (!fir.ref<!fir.box<!fir.array<?xi32>>>, !fir.ref<!fir.box<!fir.array<?xi32>>>)
! CHECK: %[[VAL_23:.*]] = fir.load %[[VAL_22]]#0 : !fir.ref<!fir.box<!fir.array<?xi32>>>
! CHECK: %[[VAL_24:.*]] = fir.load %[[VAL_15]]#0 : !fir.ref<i32>
! CHECK: %[[VAL_25:.*]] = arith.constant 0 : index
! CHECK: %[[VAL_26:.*]]:3 = fir.box_dims %[[VAL_23]], %[[VAL_25]] : (!fir.box<!fir.array<?xi32>>, index) -> (index, index, index)
! CHECK: %[[VAL_27:.*]] = fir.shape %[[VAL_26]]#1 : (index) -> !fir.shape<1>
! CHECK: %[[VAL_28:.*]] = hlfir.elemental %[[VAL_27]] unordered : (!fir.shape<1>) -> !hlfir.expr<?xi32> {
! CHECK: ^bb0(%[[VAL_29:.*]]: index):
! CHECK: %[[VAL_30:.*]] = arith.constant 0 : index
! CHECK: %[[VAL_31:.*]]:3 = fir.box_dims %[[VAL_23]], %[[VAL_30]] : (!fir.box<!fir.array<?xi32>>, index) -> (index, index, index)
! CHECK: %[[VAL_32:.*]] = arith.constant 1 : index
! CHECK: %[[VAL_33:.*]] = arith.subi %[[VAL_31]]#0, %[[VAL_32]] : index
! CHECK: %[[VAL_34:.*]] = arith.addi %[[VAL_29]], %[[VAL_33]] : index
! CHECK: %[[VAL_35:.*]] = hlfir.designate %[[VAL_23]] (%[[VAL_34]]) : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
! CHECK: %[[VAL_36:.*]] = fir.load %[[VAL_35]] : !fir.ref<i32>
! CHECK: %[[VAL_37:.*]] = arith.addi %[[VAL_36]], %[[VAL_24]] : i32
! CHECK: hlfir.yield_element %[[VAL_37]] : i32
! CHECK: }
! CHECK: %[[VAL_38:.*]] = fir.load %[[VAL_22]]#0 : !fir.ref<!fir.box<!fir.array<?xi32>>>
! CHECK: hlfir.assign %[[VAL_28]] to %[[VAL_38]] : !hlfir.expr<?xi32>, !fir.box<!fir.array<?xi32>>
! CHECK: hlfir.destroy %[[VAL_28]] : !hlfir.expr<?xi32>
! CHECK: omp.yield
! CHECK: }
! CHECK: omp.terminator
! CHECK: }
! CHECK: %[[VAL_39:.*]] = arith.constant 1 : index
! CHECK: %[[VAL_40:.*]] = hlfir.designate %[[VAL_12]]#0 (%[[VAL_39]]) : (!fir.box<!fir.array<?xi32>>, index) -> !fir.ref<i32>
! CHECK: %[[VAL_41:.*]] = fir.load %[[VAL_40]] : !fir.ref<i32>
! CHECK: %[[VAL_42:.*]] = arith.constant 5050 : i32
! CHECK: %[[VAL_43:.*]] = arith.cmpi ne, %[[VAL_41]], %[[VAL_42]] : i32
! CHECK: cf.cond_br %[[VAL_43]], ^bb1, ^bb2
! CHECK: ^bb1:
! CHECK: %[[VAL_44:.*]] = arith.constant 1 : i32
! CHECK: %[[VAL_45:.*]] = arith.constant false
! CHECK: %[[VAL_46:.*]] = arith.constant false
! CHECK: %[[VAL_47:.*]] = fir.call @_FortranAStopStatement(%[[VAL_44]], %[[VAL_45]], %[[VAL_46]]) fastmath<contract> : (i32, i1, i1) -> none
! CHECK: fir.unreachable
! CHECK: ^bb2:
! CHECK: return
! CHECK: }
! CHECK: func.func private @_FortranAStopStatement(i32, i1, i1) -> none attributes {fir.runtime}

subroutine s(x)
integer :: x
integer :: c(x)
c = 0
!$omp parallel do reduction(+:c)
do i = 1, 100
c = c + i
end do
!$omp end parallel do

if (c(1) /= 5050) stop 1
end subroutine s
90 changes: 90 additions & 0 deletions flang/test/Lower/OpenMP/wsloop-reduction-array-assumed-shape.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
! RUN: bbc -emit-hlfir -fopenmp -o - %s | FileCheck %s
! RUN: %flang_fc1 -emit-hlfir -fopenmp -o - %s | FileCheck %s

program reduce_assumed_shape
real(8), dimension(2) :: r
r = 0
call reduce(r)
print *, r

contains
subroutine reduce(r)
implicit none
real(8),intent(inout) :: r(:)
integer :: i = 0

!$omp parallel do reduction(+:r)
do i=0,10
r(1) = i
r(2) = 1
enddo
!$omp end parallel do
end subroutine
end program

! CHECK-LABEL: omp.declare_reduction @add_reduction_byref_box_Uxf64 : !fir.ref<!fir.box<!fir.array<?xf64>>> init {
! CHECK: ^bb0(%[[VAL_0:.*]]: !fir.ref<!fir.box<!fir.array<?xf64>>>):
! CHECK: %[[VAL_1:.*]] = arith.constant 0.000000e+00 : f64
! CHECK: %[[VAL_2:.*]] = fir.load %[[VAL_0]] : !fir.ref<!fir.box<!fir.array<?xf64>>>
! CHECK: %[[VAL_3:.*]] = arith.constant 0 : index
! CHECK: %[[VAL_4:.*]]:3 = fir.box_dims %[[VAL_2]], %[[VAL_3]] : (!fir.box<!fir.array<?xf64>>, index) -> (index, index, index)
! CHECK: %[[VAL_5:.*]] = fir.shape %[[VAL_4]]#1 : (index) -> !fir.shape<1>
! CHECK: %[[VAL_6:.*]] = fir.alloca !fir.array<?xf64>, %[[VAL_4]]#1 {bindc_name = ".tmp"}
! CHECK: %[[VAL_7:.*]]:2 = hlfir.declare %[[VAL_6]](%[[VAL_5]]) {uniq_name = ".tmp"} : (!fir.ref<!fir.array<?xf64>>, !fir.shape<1>) -> (!fir.box<!fir.array<?xf64>>, !fir.ref<!fir.array<?xf64>>)
! CHECK: hlfir.assign %[[VAL_1]] to %[[VAL_7]]#0 : f64, !fir.box<!fir.array<?xf64>>
! CHECK: %[[VAL_8:.*]] = fir.alloca !fir.box<!fir.array<?xf64>>
! CHECK: fir.store %[[VAL_7]]#0 to %[[VAL_8]] : !fir.ref<!fir.box<!fir.array<?xf64>>>
! CHECK: omp.yield(%[[VAL_8]] : !fir.ref<!fir.box<!fir.array<?xf64>>>)

! CHECK-LABEL: } combiner {
! CHECK: ^bb0(%[[VAL_0:.*]]: !fir.ref<!fir.box<!fir.array<?xf64>>>, %[[VAL_1:.*]]: !fir.ref<!fir.box<!fir.array<?xf64>>>):
! CHECK: %[[VAL_2:.*]] = fir.load %[[VAL_0]] : !fir.ref<!fir.box<!fir.array<?xf64>>>
! CHECK: %[[VAL_3:.*]] = fir.load %[[VAL_1]] : !fir.ref<!fir.box<!fir.array<?xf64>>>
! CHECK: %[[VAL_4:.*]] = arith.constant 0 : index
! CHECK: %[[VAL_5:.*]]:3 = fir.box_dims %[[VAL_2]], %[[VAL_4]] : (!fir.box<!fir.array<?xf64>>, index) -> (index, index, index)
! CHECK: %[[VAL_6:.*]] = fir.shape_shift %[[VAL_5]]#0, %[[VAL_5]]#1 : (index, index) -> !fir.shapeshift<1>
! CHECK: %[[VAL_7:.*]] = arith.constant 1 : index
! CHECK: fir.do_loop %[[VAL_8:.*]] = %[[VAL_7]] to %[[VAL_5]]#1 step %[[VAL_7]] unordered {
! CHECK: %[[VAL_9:.*]] = fir.array_coor %[[VAL_2]](%[[VAL_6]]) %[[VAL_8]] : (!fir.box<!fir.array<?xf64>>, !fir.shapeshift<1>, index) -> !fir.ref<f64>
! CHECK: %[[VAL_10:.*]] = fir.array_coor %[[VAL_3]](%[[VAL_6]]) %[[VAL_8]] : (!fir.box<!fir.array<?xf64>>, !fir.shapeshift<1>, index) -> !fir.ref<f64>
! CHECK: %[[VAL_11:.*]] = fir.load %[[VAL_9]] : !fir.ref<f64>
! CHECK: %[[VAL_12:.*]] = fir.load %[[VAL_10]] : !fir.ref<f64>
! CHECK: %[[VAL_13:.*]] = arith.addf %[[VAL_11]], %[[VAL_12]] fastmath<contract> : f64
! CHECK: fir.store %[[VAL_13]] to %[[VAL_9]] : !fir.ref<f64>
! CHECK: }
! CHECK: omp.yield(%[[VAL_0]] : !fir.ref<!fir.box<!fir.array<?xf64>>>)
! CHECK: }

! CHECK-LABEL: func.func private @_QFPreduce(
! CHECK-SAME: %[[VAL_0:.*]]: !fir.box<!fir.array<?xf64>> {fir.bindc_name = "r"}) attributes {{.*}} {
! CHECK: %[[VAL_1:.*]] = fir.address_of(@_QFFreduceEi) : !fir.ref<i32>
! CHECK: %[[VAL_2:.*]]:2 = hlfir.declare %[[VAL_1]] {uniq_name = "_QFFreduceEi"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
! CHECK: %[[VAL_3:.*]]:2 = hlfir.declare %[[VAL_0]] {fortran_attrs = {{.*}}, uniq_name = "_QFFreduceEr"} : (!fir.box<!fir.array<?xf64>>) -> (!fir.box<!fir.array<?xf64>>, !fir.box<!fir.array<?xf64>>)
! CHECK: omp.parallel {
! CHECK: %[[VAL_4:.*]] = fir.alloca i32 {adapt.valuebyref, pinned}
! CHECK: %[[VAL_5:.*]]:2 = hlfir.declare %[[VAL_4]] {uniq_name = "_QFFreduceEi"} : (!fir.ref<i32>) -> (!fir.ref<i32>, !fir.ref<i32>)
! CHECK: %[[VAL_6:.*]] = arith.constant 0 : i32
! CHECK: %[[VAL_7:.*]] = arith.constant 10 : i32
! CHECK: %[[VAL_8:.*]] = arith.constant 1 : i32
! CHECK: %[[VAL_9:.*]] = fir.alloca !fir.box<!fir.array<?xf64>>
! CHECK: fir.store %[[VAL_3]]#1 to %[[VAL_9]] : !fir.ref<!fir.box<!fir.array<?xf64>>>
! CHECK: omp.wsloop byref reduction(@add_reduction_byref_box_Uxf64 %[[VAL_9]] -> %[[VAL_10:.*]] : !fir.ref<!fir.box<!fir.array<?xf64>>>) for (%[[VAL_11:.*]]) : i32 = (%[[VAL_6]]) to (%[[VAL_7]]) inclusive step (%[[VAL_8]]) {
! CHECK: fir.store %[[VAL_11]] to %[[VAL_5]]#1 : !fir.ref<i32>
! CHECK: %[[VAL_12:.*]]:2 = hlfir.declare %[[VAL_10]] {fortran_attrs = {{.*}}, uniq_name = "_QFFreduceEr"} : (!fir.ref<!fir.box<!fir.array<?xf64>>>) -> (!fir.ref<!fir.box<!fir.array<?xf64>>>, !fir.ref<!fir.box<!fir.array<?xf64>>>)
! CHECK: %[[VAL_13:.*]] = fir.load %[[VAL_5]]#0 : !fir.ref<i32>
! CHECK: %[[VAL_14:.*]] = fir.convert %[[VAL_13]] : (i32) -> f64
! CHECK: %[[VAL_15:.*]] = fir.load %[[VAL_12]]#0 : !fir.ref<!fir.box<!fir.array<?xf64>>>
! CHECK: %[[VAL_16:.*]] = arith.constant 1 : index
! CHECK: %[[VAL_17:.*]] = hlfir.designate %[[VAL_15]] (%[[VAL_16]]) : (!fir.box<!fir.array<?xf64>>, index) -> !fir.ref<f64>
! CHECK: hlfir.assign %[[VAL_14]] to %[[VAL_17]] : f64, !fir.ref<f64>
! CHECK: %[[VAL_18:.*]] = arith.constant 1.000000e+00 : f64
! CHECK: %[[VAL_19:.*]] = fir.load %[[VAL_12]]#0 : !fir.ref<!fir.box<!fir.array<?xf64>>>
! CHECK: %[[VAL_20:.*]] = arith.constant 2 : index
! CHECK: %[[VAL_21:.*]] = hlfir.designate %[[VAL_19]] (%[[VAL_20]]) : (!fir.box<!fir.array<?xf64>>, index) -> !fir.ref<f64>
! CHECK: hlfir.assign %[[VAL_18]] to %[[VAL_21]] : f64, !fir.ref<f64>
! CHECK: omp.yield
! CHECK: }
! CHECK: omp.terminator
! CHECK: }
! CHECK: return
! CHECK: }
2 changes: 1 addition & 1 deletion flang/test/Lower/OpenMP/wsloop-reduction-array.f90
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ program reduce
! CHECK: %[[VAL_8:.*]] = arith.constant 0 : i32
! CHECK: %[[VAL_9:.*]] = arith.constant 10 : i32
! CHECK: %[[VAL_10:.*]] = arith.constant 1 : i32
! CHECK: %[[VAL_11:.*]] = fir.embox %[[VAL_5]]#1(%[[VAL_4]]) : (!fir.ref<!fir.array<2xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<2xi32>>
! CHECK: %[[VAL_11:.*]] = fir.embox %[[VAL_5]]#0(%[[VAL_4]]) : (!fir.ref<!fir.array<2xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<2xi32>>
! CHECK: %[[VAL_12:.*]] = fir.alloca !fir.box<!fir.array<2xi32>>
! CHECK: fir.store %[[VAL_11]] to %[[VAL_12]] : !fir.ref<!fir.box<!fir.array<2xi32>>>
! CHECK: omp.wsloop byref reduction(@add_reduction_byref_box_2xi32 %[[VAL_12]] -> %[[VAL_13:.*]] : !fir.ref<!fir.box<!fir.array<2xi32>>>) for (%[[VAL_14:.*]]) : i32 = (%[[VAL_8]]) to (%[[VAL_9]]) inclusive step (%[[VAL_10]]) {
Expand Down
2 changes: 1 addition & 1 deletion flang/test/Lower/OpenMP/wsloop-reduction-array2.f90
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ program reduce
! CHECK: %[[VAL_8:.*]] = arith.constant 0 : i32
! CHECK: %[[VAL_9:.*]] = arith.constant 10 : i32
! CHECK: %[[VAL_10:.*]] = arith.constant 1 : i32
! CHECK: %[[VAL_11:.*]] = fir.embox %[[VAL_5]]#1(%[[VAL_4]]) : (!fir.ref<!fir.array<2xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<2xi32>>
! CHECK: %[[VAL_11:.*]] = fir.embox %[[VAL_5]]#0(%[[VAL_4]]) : (!fir.ref<!fir.array<2xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<2xi32>>
! CHECK: %[[VAL_12:.*]] = fir.alloca !fir.box<!fir.array<2xi32>>
! CHECK: fir.store %[[VAL_11]] to %[[VAL_12]] : !fir.ref<!fir.box<!fir.array<2xi32>>>
! CHECK: omp.wsloop byref reduction(@add_reduction_byref_box_2xi32 %[[VAL_12]] -> %[[VAL_13:.*]] : !fir.ref<!fir.box<!fir.array<2xi32>>>) for (%[[VAL_14:.*]]) : i32 = (%[[VAL_8]]) to (%[[VAL_9]]) inclusive step (%[[VAL_10]]) {
Expand Down
4 changes: 2 additions & 2 deletions flang/test/Lower/stop-statement.f90
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ subroutine stop_code()
! CHECK-LABEL: stop_error
subroutine stop_error()
error stop
! CHECK-DAG: %[[c0:.*]] = arith.constant 0 : i32
! CHECK-DAG: %[[c_1:.*]] = arith.constant 1 : i32
! CHECK-DAG: %[[true:.*]] = arith.constant true
! CHECK-DAG: %[[false:.*]] = arith.constant false
! CHECK: fir.call @_Fortran{{.*}}StopStatement(%[[c0]], %[[true]], %[[false]])
! CHECK: fir.call @_Fortran{{.*}}StopStatement(%[[c_1]], %[[true]], %[[false]])
! CHECK-NEXT: fir.unreachable
end subroutine

Expand Down
35 changes: 15 additions & 20 deletions flang/unittests/Runtime/Time.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#include "flang/Runtime/time-intrinsic.h"
#include <algorithm>
#include <cctype>
#include <charconv>
#include <cerrno>
#include <string>

using namespace Fortran::runtime;
Expand Down Expand Up @@ -104,10 +104,9 @@ TEST(TimeIntrinsics, DateAndTime) {
EXPECT_TRUE(true);
} else {
count_t number{-1};
auto [_, ec]{
std::from_chars(date.data(), date.data() + date.size(), number)};
ASSERT_TRUE(ec != std::errc::invalid_argument &&
ec != std::errc::result_out_of_range);
// Use stol to allow GCC 7.5 to build tests
number = std::stol(date);
ASSERT_TRUE(errno != ERANGE);
EXPECT_GE(number, 0);
auto year = number / 10000;
auto month = (number - year * 10000) / 100;
Expand All @@ -121,14 +120,15 @@ TEST(TimeIntrinsics, DateAndTime) {
}

// Validate time is hhmmss.sss or blank.
std::string acceptedPattern("hhmmss.sss");
if (isBlank(time)) {
EXPECT_TRUE(true);
} else {
count_t number{-1};
auto [next, ec]{
std::from_chars(time.data(), time.data() + date.size(), number)};
ASSERT_TRUE(ec != std::errc::invalid_argument &&
ec != std::errc::result_out_of_range);
// Use stol to allow GCC 7.5 to build tests
auto dotPosition = acceptedPattern.find('.');
number = std::stol(time.substr(0, dotPosition));
ASSERT_TRUE(errno != ERANGE);
ASSERT_GE(number, 0);
auto hours = number / 10000;
auto minutes = (number - hours * 10000) / 100;
Expand All @@ -137,15 +137,11 @@ TEST(TimeIntrinsics, DateAndTime) {
EXPECT_LE(minutes, 59);
// Accept 60 for leap seconds.
EXPECT_LE(seconds, 60);
ASSERT_TRUE(next != time.data() + time.size());
EXPECT_EQ(*next, '.');
EXPECT_EQ(time.substr(dotPosition, 1), ".");

count_t milliseconds{-1};
ASSERT_TRUE(next + 1 != time.data() + time.size());
auto [_, ec2]{
std::from_chars(next + 1, time.data() + date.size(), milliseconds)};
ASSERT_TRUE(ec2 != std::errc::invalid_argument &&
ec2 != std::errc::result_out_of_range);
milliseconds = std::stol(time.substr(dotPosition + 1, 3));
ASSERT_TRUE(errno != ERANGE);
EXPECT_GE(milliseconds, 0);
EXPECT_LE(milliseconds, 999);
}
Expand All @@ -157,10 +153,9 @@ TEST(TimeIntrinsics, DateAndTime) {
ASSERT_TRUE(zone.size() > 1);
EXPECT_TRUE(zone[0] == '+' || zone[0] == '-');
count_t number{-1};
auto [next, ec]{
std::from_chars(zone.data() + 1, zone.data() + zone.size(), number)};
ASSERT_TRUE(ec != std::errc::invalid_argument &&
ec != std::errc::result_out_of_range);
// Use stol to allow GCC 7.5 to build tests
number = std::stol(zone.substr(1, 4));
ASSERT_TRUE(errno != ERANGE);
ASSERT_GE(number, 0);
auto hours = number / 100;
auto minutes = number % 100;
Expand Down
5 changes: 1 addition & 4 deletions libc/config/baremetal/api.td
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ def MathAPI : PublicAPI<"math.h"> {
}

def StdIOAPI : PublicAPI<"stdio.h"> {
let Types = [
"size_t",
"off_t",
];
let Types = ["size_t"];
}

def StdlibAPI : PublicAPI<"stdlib.h"> {
Expand Down
6 changes: 1 addition & 5 deletions libc/config/gpu/api.td
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,7 @@ def StdIOAPI : PublicAPI<"stdio.h"> {
SimpleMacroDef<"_IOLBF", "1">,
SimpleMacroDef<"_IONBF", "2">,
];
let Types = [
"FILE",
"off_t",
"size_t",
];
let Types = ["size_t", "FILE"];
}

def IntTypesAPI : PublicAPI<"inttypes.h"> {
Expand Down
12 changes: 2 additions & 10 deletions libc/config/linux/api.td
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,7 @@ def CTypeAPI : PublicAPI<"ctype.h"> {
}

def FCntlAPI : PublicAPI<"fcntl.h"> {
let Types = [
"mode_t",
"off_t",
];
let Types = ["mode_t"];
}

def IntTypesAPI : PublicAPI<"inttypes.h"> {
Expand Down Expand Up @@ -80,12 +77,7 @@ def StdIOAPI : PublicAPI<"stdio.h"> {
SimpleMacroDef<"_IOLBF", "1">,
SimpleMacroDef<"_IONBF", "2">,
];
let Types = [
"FILE",
"cookie_io_functions_t",
"off_t",
"size_t",
];
let Types = ["size_t", "FILE", "cookie_io_functions_t"];
}

def StdlibAPI : PublicAPI<"stdlib.h"> {
Expand Down
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 @@ -370,6 +370,7 @@ set(TARGET_LIBM_ENTRYPOINTS
libc.src.math.exp10f
libc.src.math.exp2
libc.src.math.exp2f
libc.src.math.exp2m1f
libc.src.math.expm1
libc.src.math.expm1f
libc.src.math.fabs
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 @@ -270,7 +270,7 @@ Higher Math Functions
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| exp2 | |check| | |check| | | | | 7.12.6.4 | F.10.3.4 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| exp2m1 | | | | | | 7.12.6.5 | F.10.3.5 |
| exp2m1 | |check| | | | | | 7.12.6.5 | F.10.3.5 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
| expm1 | |check| | |check| | | | | 7.12.6.6 | F.10.3.6 |
+-----------+------------------+-----------------+------------------------+----------------------+------------------------+------------------------+----------------------------+
Expand Down
1 change: 1 addition & 0 deletions libc/fuzzing/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=fuzzer")
add_custom_target(libc-fuzzer)

add_subdirectory(__support)
# TODO(#85680): Re-enable math fuzzing after headers are sorted out
# add_subdirectory(math)
add_subdirectory(stdlib)
Expand Down
7 changes: 7 additions & 0 deletions libc/fuzzing/__support/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
add_libc_fuzzer(
uint_fuzz
SRCS
uint_fuzz.cpp
DEPENDS
libc.src.__support.uint
)
70 changes: 70 additions & 0 deletions libc/fuzzing/__support/uint_fuzz.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include "src/__support/CPP/bit.h"
#include "src/__support/UInt.h"
#include "src/string/memory_utils/inline_memcpy.h"

using namespace LIBC_NAMESPACE;

// Helper function when using gdb / lldb to set a breakpoint and inspect values.
template <typename T> void debug_and_trap(const char *msg, T a, T b) {
__builtin_trap();
}

#define DEBUG_AND_TRAP()

#define TEST_BINOP(OP) \
if ((a OP b) != (static_cast<T>(BigInt(a) OP BigInt(b)))) \
debug_and_trap(#OP, a, b);

#define TEST_SHIFTOP(OP) \
if ((a OP b) != (static_cast<T>(BigInt(a) OP b))) \
debug_and_trap(#OP, a, b);

#define TEST_FUNCTION(FUN) \
if (FUN(a) != FUN(BigInt(a))) \
debug_and_trap(#FUN, a, b);

// Test that basic arithmetic operations of BigInt behave like their scalar
// counterparts.
template <typename T, typename BigInt> void run_tests(T a, T b) {
TEST_BINOP(+)
TEST_BINOP(-)
TEST_BINOP(*)
if (b != 0)
TEST_BINOP(/)
if (b >= 0 && b < cpp::numeric_limits<T>::digits) {
TEST_SHIFTOP(<<)
TEST_SHIFTOP(>>)
}
if constexpr (!BigInt::SIGNED) {
TEST_FUNCTION(cpp::has_single_bit)
TEST_FUNCTION(cpp::countr_zero)
TEST_FUNCTION(cpp::countl_zero)
TEST_FUNCTION(cpp::countl_one)
TEST_FUNCTION(cpp::countr_one)
}
}

// Reads a T from libfuzzer data.
template <typename T> T read(const uint8_t *data, size_t &remainder) {
T out = 0;
constexpr size_t T_SIZE = sizeof(T);
const size_t copy_size = remainder < T_SIZE ? remainder : T_SIZE;
inline_memcpy(&out, data, copy_size);
remainder -= copy_size;
return out;
}

template <typename T, typename BigInt>
void run_tests(const uint8_t *data, size_t size) {
const auto a = read<T>(data, size);
const auto b = read<T>(data, size);
run_tests<T, BigInt>(a, b);
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// unsigned
run_tests<uint64_t, BigInt<64, false, uint16_t>>(data, size);
// signed
run_tests<int64_t, BigInt<64, true, uint16_t>>(data, size);
return 0;
}
10 changes: 4 additions & 6 deletions libc/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@ add_gen_header(
DEF_FILE fcntl.h.def
GEN_HDR fcntl.h
DEPENDS
.llvm_libc_common_h
.llvm-libc-macros.fcntl_macros
.llvm-libc-types.mode_t
.llvm-libc-types.off_t
.llvm_libc_common_h
)

add_gen_header(
Expand Down Expand Up @@ -265,14 +264,13 @@ add_gen_header(
DEF_FILE stdio.h.def
GEN_HDR stdio.h
DEPENDS
.llvm_libc_common_h
.llvm-libc-macros.file_seek_macros
.llvm-libc-macros.stdio_macros
.llvm-libc-types.FILE
.llvm-libc-types.cookie_io_functions_t
.llvm-libc-types.off_t
.llvm-libc-types.size_t
.llvm-libc-types.ssize_t
.llvm_libc_common_h
.llvm-libc-types.FILE
.llvm-libc-types.cookie_io_functions_t
)

add_gen_header(
Expand Down
7 changes: 2 additions & 5 deletions libc/spec/posix.td
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,7 @@ def POSIX : StandardSpec<"POSIX"> {
HeaderSpec FCntl = HeaderSpec<
"fcntl.h",
[], // Macros
[
ModeTType,
OffTType,
],
[ModeTType],
[], // Enumerations
[
FunctionSpec<
Expand Down Expand Up @@ -1183,7 +1180,7 @@ def POSIX : StandardSpec<"POSIX"> {
HeaderSpec StdIO = HeaderSpec<
"stdio.h",
[], // Macros
[OffTType], // Types
[], // Types
[], // Enumerations
[
FunctionSpec<
Expand Down
2 changes: 2 additions & 0 deletions libc/spec/stdc.td
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,8 @@ def StdC : StandardSpec<"stdc"> {
FunctionSpec<"exp2", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
FunctionSpec<"exp2f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,

FunctionSpec<"exp2m1f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,

FunctionSpec<"expm1", RetValSpec<DoubleType>, [ArgSpec<DoubleType>]>,
FunctionSpec<"expm1f", RetValSpec<FloatType>, [ArgSpec<FloatType>]>,

Expand Down
6 changes: 3 additions & 3 deletions libc/src/__support/FPUtil/dyadic_float.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ template <size_t Bits> struct DyadicFloat {
// significant bit.
LIBC_INLINE constexpr DyadicFloat &normalize() {
if (!mantissa.is_zero()) {
int shift_length = static_cast<int>(mantissa.clz());
int shift_length = cpp::countl_zero(mantissa);
exponent -= shift_length;
mantissa.shift_left(static_cast<size_t>(shift_length));
mantissa <<= static_cast<size_t>(shift_length);
}
return *this;
}
Expand Down Expand Up @@ -233,7 +233,7 @@ LIBC_INLINE constexpr DyadicFloat<Bits> quick_add(DyadicFloat<Bits> a,
result.sign = a.sign;
result.exponent = a.exponent;
result.mantissa = a.mantissa;
if (result.mantissa.add(b.mantissa)) {
if (result.mantissa.add_overflow(b.mantissa)) {
// Mantissa addition overflow.
result.shift_right(1);
result.mantissa.val[DyadicFloat<Bits>::MantissaType::WORD_COUNT - 1] |=
Expand Down
8 changes: 4 additions & 4 deletions libc/src/__support/RPC/rpc.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,9 @@ template <bool Invert> struct Process {
/// convergent, otherwise the compiler will sink the store and deadlock.
[[clang::convergent]] LIBC_INLINE void unlock(uint64_t lane_mask,
uint32_t index) {
// Do not move any writes past the unlock
// Do not move any writes past the unlock.
atomic_thread_fence(cpp::MemoryOrder::RELEASE);

// Wait for other threads in the warp to finish using the lock
gpu::sync_lane(lane_mask);

// Use exactly one thread to clear the nth bit in the lock array Must
// restrict to a single thread to avoid one thread dropping the lock, then
// an unrelated warp claiming the lock, then a second thread in this warp
Expand Down Expand Up @@ -331,6 +328,9 @@ template <bool T> struct Port {
LIBC_INLINE uint16_t get_index() const { return index; }

LIBC_INLINE void close() {
// Wait for all lanes to finish using the port.
gpu::sync_lane(lane_mask);

// The server is passive, if it own the buffer when it closes we need to
// give ownership back to the client.
if (owns_buffer && T)
Expand Down
1,129 changes: 568 additions & 561 deletions libc/src/__support/UInt.h

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions libc/src/__support/float_to_string.h
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ template <> class FloatToString<long double> {

wide_int float_as_int = mantissa;

float_as_int.shift_left(exponent);
float_as_int <<= exponent;
int_block_index = 0;

while (float_as_int > 0) {
Expand All @@ -708,10 +708,11 @@ template <> class FloatToString<long double> {

const int SHIFT_AMOUNT = FLOAT_AS_INT_WIDTH + exponent;
static_assert(EXTRA_INT_WIDTH >= sizeof(long double) * 8);
float_as_fixed.shift_left(SHIFT_AMOUNT);
float_as_fixed <<= SHIFT_AMOUNT;

// If there are still digits above the decimal point, handle those.
if (float_as_fixed.clz() < static_cast<int>(EXTRA_INT_WIDTH)) {
if (cpp::countl_zero(float_as_fixed) <
static_cast<int>(EXTRA_INT_WIDTH)) {
UInt<EXTRA_INT_WIDTH> above_decimal_point =
float_as_fixed >> FLOAT_AS_INT_WIDTH;

Expand Down
25 changes: 19 additions & 6 deletions libc/src/__support/integer_literals.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,15 @@ template <size_t N> struct Parser<LIBC_NAMESPACE::UInt<N>> {
template <typename T>
LIBC_INLINE constexpr T parse_with_prefix(const char *ptr) {
using P = Parser<T>;
if (ptr[0] == '0' && ptr[1] == 'x')
return P::template parse<16>(ptr + 2);
else if (ptr[0] == '0' && ptr[1] == 'b')
return P::template parse<2>(ptr + 2);
else
return P::template parse<10>(ptr);
if (ptr == nullptr)
return T();
if (ptr[0] == '0') {
if (ptr[1] == 'b')
return P::template parse<2>(ptr + 2);
if (ptr[1] == 'x')
return P::template parse<16>(ptr + 2);
}
return P::template parse<10>(ptr);
}

} // namespace internal
Expand All @@ -169,6 +172,16 @@ LIBC_INLINE constexpr auto operator""_u256(const char *x) {
return internal::parse_with_prefix<UInt<256>>(x);
}

template <typename T> LIBC_INLINE constexpr T parse_bigint(const char *ptr) {
if (ptr == nullptr)
return T();
if (ptr[0] == '-' || ptr[0] == '+') {
auto positive = internal::parse_with_prefix<T>(ptr + 1);
return ptr[0] == '-' ? -positive : positive;
}
return internal::parse_with_prefix<T>(ptr);
}

} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC___SUPPORT_INTEGER_LITERALS_H
249 changes: 72 additions & 177 deletions libc/src/__support/math_extras.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
#ifndef LLVM_LIBC_SRC___SUPPORT_MATH_EXTRAS_H
#define LLVM_LIBC_SRC___SUPPORT_MATH_EXTRAS_H

#include "src/__support/CPP/bit.h" // countl_one, countr_zero
#include "src/__support/CPP/limits.h" // CHAR_BIT, numeric_limits
#include "src/__support/CPP/type_traits.h" // is_unsigned_v
#include "src/__support/CPP/bit.h" // countl_one, countr_zero
#include "src/__support/CPP/limits.h" // CHAR_BIT, numeric_limits
#include "src/__support/CPP/type_traits.h" // is_unsigned_v, is_constant_evaluated
#include "src/__support/macros/attributes.h" // LIBC_INLINE

namespace LIBC_NAMESPACE {
Expand All @@ -32,199 +32,94 @@ mask_trailing_ones() {
template <typename T, size_t count>
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T>
mask_leading_ones() {
constexpr T MASK(mask_trailing_ones<T, CHAR_BIT * sizeof(T) - count>());
return T(~MASK); // bitwise NOT performs integer promotion.
return T(~mask_trailing_ones<T, CHAR_BIT * sizeof(T) - count>());
}

// Add with carry
template <typename T> struct SumCarry {
T sum;
T carry;
};

// This version is always valid for constexpr.
template <typename T>
LIBC_INLINE constexpr cpp::enable_if_t<
cpp::is_integral_v<T> && cpp::is_unsigned_v<T>, SumCarry<T>>
add_with_carry_const(T a, T b, T carry_in) {
T tmp = a + carry_in;
T sum = b + tmp;
T carry_out = (sum < b) + (tmp < a);
return {sum, carry_out};
}

template <typename T>
LIBC_INLINE constexpr cpp::enable_if_t<
cpp::is_integral_v<T> && cpp::is_unsigned_v<T>, SumCarry<T>>
add_with_carry(T a, T b, T carry_in) {
return add_with_carry_const<T>(a, b, carry_in);
}

#if __has_builtin(__builtin_addc)
// https://clang.llvm.org/docs/LanguageExtensions.html#multiprecision-arithmetic-builtins

template <>
LIBC_INLINE constexpr SumCarry<unsigned char>
add_with_carry<unsigned char>(unsigned char a, unsigned char b,
unsigned char carry_in) {
if (__builtin_is_constant_evaluated()) {
return add_with_carry_const<unsigned char>(a, b, carry_in);
} else {
SumCarry<unsigned char> result{0, 0};
result.sum = __builtin_addcb(a, b, carry_in, &result.carry);
return result;
}
}

template <>
LIBC_INLINE constexpr SumCarry<unsigned short>
add_with_carry<unsigned short>(unsigned short a, unsigned short b,
unsigned short carry_in) {
if (__builtin_is_constant_evaluated()) {
return add_with_carry_const<unsigned short>(a, b, carry_in);
} else {
SumCarry<unsigned short> result{0, 0};
result.sum = __builtin_addcs(a, b, carry_in, &result.carry);
return result;
}
}

template <>
LIBC_INLINE constexpr SumCarry<unsigned int>
add_with_carry<unsigned int>(unsigned int a, unsigned int b,
unsigned int carry_in) {
if (__builtin_is_constant_evaluated()) {
return add_with_carry_const<unsigned int>(a, b, carry_in);
} else {
SumCarry<unsigned int> result{0, 0};
result.sum = __builtin_addc(a, b, carry_in, &result.carry);
return result;
}
}

template <>
LIBC_INLINE constexpr SumCarry<unsigned long>
add_with_carry<unsigned long>(unsigned long a, unsigned long b,
unsigned long carry_in) {
if (__builtin_is_constant_evaluated()) {
return add_with_carry_const<unsigned long>(a, b, carry_in);
} else {
SumCarry<unsigned long> result{0, 0};
result.sum = __builtin_addcl(a, b, carry_in, &result.carry);
return result;
}
// Create a bitmask with the count right-most bits set to 0, and all other bits
// set to 1. Only unsigned types are allowed.
template <typename T, size_t count>
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T>
mask_trailing_zeros() {
return mask_leading_ones<T, CHAR_BIT * sizeof(T) - count>();
}

template <>
LIBC_INLINE constexpr SumCarry<unsigned long long>
add_with_carry<unsigned long long>(unsigned long long a, unsigned long long b,
unsigned long long carry_in) {
if (__builtin_is_constant_evaluated()) {
return add_with_carry_const<unsigned long long>(a, b, carry_in);
} else {
SumCarry<unsigned long long> result{0, 0};
result.sum = __builtin_addcll(a, b, carry_in, &result.carry);
return result;
}
// Create a bitmask with the count left-most bits set to 0, and all other bits
// set to 1. Only unsigned types are allowed.
template <typename T, size_t count>
LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T>
mask_leading_zeros() {
return mask_trailing_ones<T, CHAR_BIT * sizeof(T) - count>();
}

#endif // __has_builtin(__builtin_addc)

// Subtract with borrow
template <typename T> struct DiffBorrow {
T diff;
T borrow;
};

// This version is always valid for constexpr.
// Returns whether 'a + b' overflows, the result is stored in 'res'.
template <typename T>
LIBC_INLINE constexpr cpp::enable_if_t<
cpp::is_integral_v<T> && cpp::is_unsigned_v<T>, DiffBorrow<T>>
sub_with_borrow_const(T a, T b, T borrow_in) {
T tmp = a - b;
T diff = tmp - borrow_in;
T borrow_out = (diff > tmp) + (tmp > a);
return {diff, borrow_out};
[[nodiscard]] LIBC_INLINE constexpr bool add_overflow(T a, T b, T &res) {
return __builtin_add_overflow(a, b, &res);
}

// This version is not always valid for constepxr because it's overriden below
// if builtins are available.
// Returns whether 'a - b' overflows, the result is stored in 'res'.
template <typename T>
LIBC_INLINE constexpr cpp::enable_if_t<
cpp::is_integral_v<T> && cpp::is_unsigned_v<T>, DiffBorrow<T>>
sub_with_borrow(T a, T b, T borrow_in) {
return sub_with_borrow_const<T>(a, b, borrow_in);
}

#if __has_builtin(__builtin_subc)
// https://clang.llvm.org/docs/LanguageExtensions.html#multiprecision-arithmetic-builtins

template <>
LIBC_INLINE constexpr DiffBorrow<unsigned char>
sub_with_borrow<unsigned char>(unsigned char a, unsigned char b,
unsigned char borrow_in) {
if (__builtin_is_constant_evaluated()) {
return sub_with_borrow_const<unsigned char>(a, b, borrow_in);
} else {
DiffBorrow<unsigned char> result{0, 0};
result.diff = __builtin_subcb(a, b, borrow_in, &result.borrow);
return result;
}
[[nodiscard]] LIBC_INLINE constexpr bool sub_overflow(T a, T b, T &res) {
return __builtin_sub_overflow(a, b, &res);
}

template <>
LIBC_INLINE constexpr DiffBorrow<unsigned short>
sub_with_borrow<unsigned short>(unsigned short a, unsigned short b,
unsigned short borrow_in) {
if (__builtin_is_constant_evaluated()) {
return sub_with_borrow_const<unsigned short>(a, b, borrow_in);
} else {
DiffBorrow<unsigned short> result{0, 0};
result.diff = __builtin_subcs(a, b, borrow_in, &result.borrow);
return result;
}
}
#define RETURN_IF(TYPE, BUILTIN) \
if constexpr (cpp::is_same_v<T, TYPE>) \
return BUILTIN(a, b, carry_in, carry_out);

template <>
LIBC_INLINE constexpr DiffBorrow<unsigned int>
sub_with_borrow<unsigned int>(unsigned int a, unsigned int b,
unsigned int borrow_in) {
if (__builtin_is_constant_evaluated()) {
return sub_with_borrow_const<unsigned int>(a, b, borrow_in);
} else {
DiffBorrow<unsigned int> result{0, 0};
result.diff = __builtin_subc(a, b, borrow_in, &result.borrow);
return result;
}
}

template <>
LIBC_INLINE constexpr DiffBorrow<unsigned long>
sub_with_borrow<unsigned long>(unsigned long a, unsigned long b,
unsigned long borrow_in) {
if (__builtin_is_constant_evaluated()) {
return sub_with_borrow_const<unsigned long>(a, b, borrow_in);
} else {
DiffBorrow<unsigned long> result{0, 0};
result.diff = __builtin_subcl(a, b, borrow_in, &result.borrow);
return result;
// Returns the result of 'a + b' taking into account 'carry_in'.
// The carry out is stored in 'carry_out' it not 'nullptr', dropped otherwise.
// We keep the pass by pointer interface for consistency with the intrinsic.
template <typename T>
[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T>
add_with_carry(T a, T b, T carry_in, T &carry_out) {
if constexpr (!cpp::is_constant_evaluated()) {
#if __has_builtin(__builtin_addcb)
RETURN_IF(unsigned char, __builtin_addcb)
#elif __has_builtin(__builtin_addcs)
RETURN_IF(unsigned short, __builtin_addcs)
#elif __has_builtin(__builtin_addc)
RETURN_IF(unsigned int, __builtin_addc)
#elif __has_builtin(__builtin_addcl)
RETURN_IF(unsigned long, __builtin_addcl)
#elif __has_builtin(__builtin_addcll)
RETURN_IF(unsigned long long, __builtin_addcll)
#endif
}
T sum = {};
T carry1 = add_overflow(a, b, sum);
T carry2 = add_overflow(sum, carry_in, sum);
carry_out = carry1 | carry2;
return sum;
}

template <>
LIBC_INLINE constexpr DiffBorrow<unsigned long long>
sub_with_borrow<unsigned long long>(unsigned long long a, unsigned long long b,
unsigned long long borrow_in) {
if (__builtin_is_constant_evaluated()) {
return sub_with_borrow_const<unsigned long long>(a, b, borrow_in);
} else {
DiffBorrow<unsigned long long> result{0, 0};
result.diff = __builtin_subcll(a, b, borrow_in, &result.borrow);
return result;
// Returns the result of 'a - b' taking into account 'carry_in'.
// The carry out is stored in 'carry_out' it not 'nullptr', dropped otherwise.
// We keep the pass by pointer interface for consistency with the intrinsic.
template <typename T>
[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, T>
sub_with_borrow(T a, T b, T carry_in, T &carry_out) {
if constexpr (!cpp::is_constant_evaluated()) {
#if __has_builtin(__builtin_subcb)
RETURN_IF(unsigned char, __builtin_subcb)
#elif __has_builtin(__builtin_subcs)
RETURN_IF(unsigned short, __builtin_subcs)
#elif __has_builtin(__builtin_subc)
RETURN_IF(unsigned int, __builtin_subc)
#elif __has_builtin(__builtin_subcl)
RETURN_IF(unsigned long, __builtin_subcl)
#elif __has_builtin(__builtin_subcll)
RETURN_IF(unsigned long long, __builtin_subcll)
#endif
}
T sub = {};
T carry1 = sub_overflow(a, b, sub);
T carry2 = sub_overflow(sub, carry_in, sub);
carry_out = carry1 | carry2;
return sub;
}

#endif // __has_builtin(__builtin_subc)
#undef RETURN_IF

template <typename T>
[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t<cpp::is_unsigned_v<T>, int>
Expand Down
11 changes: 0 additions & 11 deletions libc/src/__support/number_pair.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,6 @@ template <typename T> struct NumberPair {
T hi = T(0);
};

template <typename T>
cpp::enable_if_t<cpp::is_integral_v<T> && cpp::is_unsigned_v<T>,
NumberPair<T>> constexpr split(T a) {
constexpr size_t HALF_BIT_WIDTH = sizeof(T) * 4;
constexpr T LOWER_HALF_MASK = (T(1) << HALF_BIT_WIDTH) - T(1);
NumberPair<T> result;
result.lo = a & LOWER_HALF_MASK;
result.hi = a >> HALF_BIT_WIDTH;
return result;
}

} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC___SUPPORT_NUMBER_PAIR_H
2 changes: 2 additions & 0 deletions libc/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ add_math_entrypoint_object(expf)
add_math_entrypoint_object(exp2)
add_math_entrypoint_object(exp2f)

add_math_entrypoint_object(exp2m1f)

add_math_entrypoint_object(exp10)
add_math_entrypoint_object(exp10f)

Expand Down
18 changes: 18 additions & 0 deletions libc/src/math/exp2m1f.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//===-- Implementation header for exp2m1f -----------------------*- 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_EXP2M1F_H
#define LLVM_LIBC_SRC_MATH_EXP2M1F_H

namespace LIBC_NAMESPACE {

float exp2m1f(float x);

} // namespace LIBC_NAMESPACE

#endif // LLVM_LIBC_SRC_MATH_EXP2M1F_H
21 changes: 21 additions & 0 deletions libc/src/math/generic/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,27 @@ add_entrypoint_object(
-O3
)

add_entrypoint_object(
exp2m1f
SRCS
exp2m1f.cpp
HDRS
../exp2m1f.h
DEPENDS
.explogxf
libc.src.errno.errno
libc.src.__support.common
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
183 changes: 183 additions & 0 deletions libc/src/math/generic/exp2m1f.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
//===-- Implementation of exp2m1f 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/exp2m1f.h"
#include "src/__support/FPUtil/FEnvImpl.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/__support/FPUtil/PolyEval.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/optimization.h"
#include "src/__support/macros/properties/cpu_features.h"
#include "src/errno/libc_errno.h"

#include "explogxf.h"

namespace LIBC_NAMESPACE {

static constexpr size_t N_EXCEPTS_LO = 8;

static constexpr fputil::ExceptValues<float, N_EXCEPTS_LO> EXP2M1F_EXCEPTS_LO =
{{
// (input, RZ output, RU offset, RD offset, RN offset)
// x = 0x1.36dc8ep-36, exp2m1f(x) = 0x1.aef212p-37 (RZ)
{0x2d9b'6e47U, 0x2d57'7909U, 1U, 0U, 0U},
// x = 0x1.224936p-19, exp2m1f(x) = 0x1.926c0ep-20 (RZ)
{0x3611'249bU, 0x35c9'3607U, 1U, 0U, 1U},
// x = 0x1.d16d2p-20, exp2m1f(x) = 0x1.429becp-20 (RZ)
{0x35e8'b690U, 0x35a1'4df6U, 1U, 0U, 1U},
// x = 0x1.17949ep-14, exp2m1f(x) = 0x1.8397p-15 (RZ)
{0x388b'ca4fU, 0x3841'cb80U, 1U, 0U, 1U},
// x = -0x1.9c3e1ep-38, exp2m1f(x) = -0x1.1dbeacp-38 (RZ)
{0xacce'1f0fU, 0xac8e'df56U, 0U, 1U, 0U},
// x = -0x1.4d89b4p-32, exp2m1f(x) = -0x1.ce61b6p-33 (RZ)
{0xafa6'c4daU, 0xaf67'30dbU, 0U, 1U, 1U},
// x = -0x1.a6eac4p-10, exp2m1f(x) = -0x1.24fadap-10 (RZ)
{0xbad3'7562U, 0xba92'7d6dU, 0U, 1U, 1U},
// x = -0x1.e7526ep-6, exp2m1f(x) = -0x1.4e53dep-6 (RZ)
{0xbcf3'a937U, 0xbca7'29efU, 0U, 1U, 1U},
}};

static constexpr size_t N_EXCEPTS_HI = 3;

static constexpr fputil::ExceptValues<float, N_EXCEPTS_HI> EXP2M1F_EXCEPTS_HI =
{{
// (input, RZ output, RU offset, RD offset, RN offset)
// x = 0x1.16a972p-1, exp2m1f(x) = 0x1.d545b2p-2 (RZ)
{0x3f0b'54b9U, 0x3eea'a2d9U, 1U, 0U, 0U},
// x = -0x1.9f12acp-5, exp2m1f(x) = -0x1.1ab68cp-5 (RZ)
{0xbd4f'8956U, 0xbd0d'5b46U, 0U, 1U, 0U},
// x = -0x1.de7b9cp-5, exp2m1f(x) = -0x1.4508f4p-5 (RZ)
{0xbd6f'3dceU, 0xbd22'847aU, 0U, 1U, 1U},
}};

LLVM_LIBC_FUNCTION(float, exp2m1f, (float x)) {
using FPBits = fputil::FPBits<float>;
FPBits xbits(x);

uint32_t x_u = xbits.uintval();
uint32_t x_abs = x_u & 0x7fff'ffffU;

// When |x| >= 128, or x is nan, or |x| <= 2^-5
if (LIBC_UNLIKELY(x_abs >= 0x4300'0000U || x_abs <= 0x3d00'0000U)) {
// |x| <= 2^-5
if (x_abs <= 0x3d00'0000U) {
if (auto r = EXP2M1F_EXCEPTS_LO.lookup(x_u); LIBC_UNLIKELY(r.has_value()))
return r.value();

// Minimax polynomial generated by Sollya with:
// > display = hexadecimal;
// > fpminimax((2^x - 1)/x, 5, [|D...|], [-2^-5, 2^-5]);
constexpr double COEFFS[] = {
0x1.62e42fefa39f3p-1, 0x1.ebfbdff82c57bp-3, 0x1.c6b08d6f2d7aap-5,
0x1.3b2ab6fc92f5dp-7, 0x1.5d897cfe27125p-10, 0x1.43090e61e6af1p-13};
double xd = x;
double xsq = xd * xd;
double c0 = fputil::multiply_add(xd, COEFFS[1], COEFFS[0]);
double c1 = fputil::multiply_add(xd, COEFFS[3], COEFFS[2]);
double c2 = fputil::multiply_add(xd, COEFFS[5], COEFFS[4]);
double p = fputil::polyeval(xsq, c0, c1, c2);
return static_cast<float>(p * xd);
}

// x >= 128, or x is nan
if (xbits.is_pos()) {
if (xbits.is_finite()) {
int rounding = fputil::quick_get_round();
if (rounding == FE_DOWNWARD || rounding == FE_TOWARDZERO)
return FPBits::max_normal().get_val();

fputil::set_errno_if_required(ERANGE);
fputil::raise_except_if_required(FE_OVERFLOW);
}

// x >= 128 and 2^x - 1 rounds to +inf, or x is +inf or nan
return x + FPBits::inf().get_val();
}
}

if (LIBC_UNLIKELY(x <= -25.0f)) {
// 2^(-inf) - 1 = -1
if (xbits.is_inf())
return -1.0f;
// 2^nan - 1 = nan
if (xbits.is_nan())
return x;

int rounding = fputil::quick_get_round();
if (rounding == FE_UPWARD || rounding == FE_TOWARDZERO)
return -0x1.ffff'fep-1f; // -1.0f + 0x1.0p-24f
fputil::set_errno_if_required(ERANGE);
fputil::raise_except_if_required(FE_UNDERFLOW);
return -1.0f;
}
if (auto r = EXP2M1F_EXCEPTS_HI.lookup(x_u); LIBC_UNLIKELY(r.has_value()))
return r.value();
// For -25 < x < 128, 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,
// 0 <= mid * 2^5 < 32 is an integer,
// -2^(-6) <= lo <= 2^(-6).
// In particular,
// hi + mid = round(x * 2^5) * 2^(-5).
// Then,
// 2^x = 2^(hi + mid + lo) = 2^hi * 2^mid * 2^lo.
// 2^mid is stored in the lookup table of 32 elements.
// 2^lo is computed using a degree-4 minimax polynomial generated by Sollya.
// We perform 2^hi * 2^mid by simply add hi to the exponent field of 2^mid.
// kf = (hi + mid) * 2^5 = round(x * 2^5)
float kf;
int k;
#ifdef LIBC_TARGET_CPU_HAS_NEAREST_INT
kf = fputil::nearest_integer(x * 32.0f);
k = static_cast<int>(kf);
#else
constexpr float HALF[2] = {0.5f, -0.5f};
k = static_cast<int>(fputil::multiply_add(x, 32.0f, HALF[x < 0.0f]));
kf = static_cast<float>(k);
#endif // LIBC_TARGET_CPU_HAS_NEAREST_INT
// lo = x - (hi + mid) = x - kf * 2^(-5)
double lo = fputil::multiply_add(-0x1.0p-5f, kf, x);
// hi = floor(kf * 2^(-4))
// exp2_hi = shift hi to the exponent field of double precision.
int64_t exp2_hi =
static_cast<int64_t>(static_cast<uint64_t>(k >> ExpBase::MID_BITS)
<< fputil::FPBits<double>::FRACTION_LEN);
// mh = 2^hi * 2^mid
// mh_bits = bit field of mh
int64_t mh_bits = ExpBase::EXP_2_MID[k & ExpBase::MID_MASK] + exp2_hi;
double mh = fputil::FPBits<double>(static_cast<uint64_t>(mh_bits)).get_val();
// Degree-4 polynomial approximating (2^x - 1)/x generated by Sollya with:
// > display = hexadecimal;
// > fpminimax((2^x - 1)/x, 4, [|D...|], [-2^-6, 2^-6]);
constexpr double COEFFS[5] = {0x1.62e42fefa39efp-1, 0x1.ebfbdff8131c4p-3,
0x1.c6b08d7061695p-5, 0x1.3b2b1bee74b2ap-7,
0x1.5d88091198529p-10};
double lo_sq = lo * lo;
double c1 = fputil::multiply_add(lo, COEFFS[0], 1.0);
double c2 = fputil::multiply_add(lo, COEFFS[2], COEFFS[1]);
double c3 = fputil::multiply_add(lo, COEFFS[4], COEFFS[3]);
double exp2_lo = fputil::polyeval(lo_sq, c1, c2, c3);
// 2^x - 1 = 2^(hi + mid + lo) - 1
// = 2^(hi + mid) * 2^lo - 1
// ~ mh * (1 + lo * P(lo)) - 1
// = mh * exp2_lo - 1
return static_cast<float>(fputil::multiply_add(exp2_lo, mh, -1.0));
}
} // namespace LIBC_NAMESPACE
1 change: 1 addition & 0 deletions libc/src/stdio/fseeko.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define LLVM_LIBC_SRC_STDIO_FSEEKO_H

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

namespace LIBC_NAMESPACE {

Expand Down
1 change: 1 addition & 0 deletions libc/src/stdio/ftello.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define LLVM_LIBC_SRC_STDIO_FTELLO_H

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

namespace LIBC_NAMESPACE {

Expand Down
21 changes: 21 additions & 0 deletions libc/test/src/__support/integer_literals_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,24 @@ TEST(LlvmLibcIntegerLiteralTest, u256) {
U256_MAX,
0xFFFFFFFF'FFFFFFFF'FFFFFFFF'FFFFFFFF'FFFFFFFF'FFFFFFFF'FFFFFFFF'FFFFFFFF_u256);
}

TEST(LlvmLibcIntegerLiteralTest, parse_bigint) {
using T = LIBC_NAMESPACE::Int<128>;
struct {
const char *str;
T expected;
} constexpr TEST_CASES[] = {
{"0", 0}, {"-1", -1}, {"+1", 1}, {"-0xFF", -255}, {"-0b11", -3},
};
for (auto tc : TEST_CASES) {
T actual = LIBC_NAMESPACE::parse_bigint<T>(tc.str);
EXPECT_EQ(actual, tc.expected);
}
}

TEST(LlvmLibcIntegerLiteralTest, parse_bigint_invalid) {
using T = LIBC_NAMESPACE::Int<128>;
const T expected; // default construction
EXPECT_EQ(LIBC_NAMESPACE::parse_bigint<T>(nullptr), expected);
EXPECT_EQ(LIBC_NAMESPACE::parse_bigint<T>(""), expected);
}
57 changes: 57 additions & 0 deletions libc/test/src/__support/math_extras_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,61 @@ TYPED_TEST(LlvmLibcBitTest, CountZeros, UnsignedTypesNoBigInt) {
EXPECT_EQ(count_zeros<T>(cpp::numeric_limits<T>::max() >> i), i);
}

using UnsignedTypes = testing::TypeList<
#if defined(__SIZEOF_INT128__)
__uint128_t,
#endif
unsigned char, unsigned short, unsigned int, unsigned long,
unsigned long long>;

TYPED_TEST(LlvmLibcBlockMathExtrasTest, add_overflow, UnsignedTypes) {
constexpr T ZERO = cpp::numeric_limits<T>::min();
constexpr T ONE(1);
constexpr T MAX = cpp::numeric_limits<T>::max();
constexpr T BEFORE_MAX = MAX - 1;

const struct {
T lhs;
T rhs;
T sum;
bool carry;
} TESTS[] = {
{ZERO, ONE, ONE, false}, // 0x00 + 0x01 = 0x01
{BEFORE_MAX, ONE, MAX, false}, // 0xFE + 0x01 = 0xFF
{MAX, ONE, ZERO, true}, // 0xFF + 0x01 = 0x00 (carry)
{MAX, MAX, BEFORE_MAX, true}, // 0xFF + 0xFF = 0xFE (carry)
};
for (auto tc : TESTS) {
T sum;
bool carry = add_overflow<T>(tc.lhs, tc.rhs, sum);
EXPECT_EQ(sum, tc.sum);
EXPECT_EQ(carry, tc.carry);
}
}

TYPED_TEST(LlvmLibcBlockMathExtrasTest, sub_overflow, UnsignedTypes) {
constexpr T ZERO = cpp::numeric_limits<T>::min();
constexpr T ONE(1);
constexpr T MAX = cpp::numeric_limits<T>::max();
constexpr T BEFORE_MAX = MAX - 1;

const struct {
T lhs;
T rhs;
T sub;
bool carry;
} TESTS[] = {
{ONE, ZERO, ONE, false}, // 0x01 - 0x00 = 0x01
{MAX, MAX, ZERO, false}, // 0xFF - 0xFF = 0x00
{ZERO, ONE, MAX, true}, // 0x00 - 0x01 = 0xFF (carry)
{BEFORE_MAX, MAX, MAX, true}, // 0xFE - 0xFF = 0xFF (carry)
};
for (auto tc : TESTS) {
T sub;
bool carry = sub_overflow<T>(tc.lhs, tc.rhs, sub);
EXPECT_EQ(sub, tc.sub);
EXPECT_EQ(carry, tc.carry);
}
}

} // namespace LIBC_NAMESPACE
192 changes: 191 additions & 1 deletion libc/test/src/__support/uint_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,203 @@

#include "src/__support/CPP/optional.h"
#include "src/__support/UInt.h"
#include "src/__support/integer_literals.h" // parse_unsigned_bigint
#include "src/__support/macros/properties/types.h" // LIBC_TYPES_HAS_INT128

#include "include/llvm-libc-macros/math-macros.h" // HUGE_VALF, HUGE_VALF
#include "test/UnitTest/Test.h"

namespace LIBC_NAMESPACE {

enum Value { ZERO, ONE, TWO, MIN, MAX };

template <typename T> auto create(Value value) {
switch (value) {
case ZERO:
return T(0);
case ONE:
return T(1);
case TWO:
return T(2);
case MIN:
return T::min();
case MAX:
return T::max();
}
}

using Types = testing::TypeList< //
#ifdef LIBC_TYPES_HAS_INT64
BigInt<64, false, uint64_t>, // 64-bits unsigned (1 x uint64_t)
BigInt<64, true, uint64_t>, // 64-bits signed (1 x uint64_t)
#endif
#ifdef LIBC_TYPES_HAS_INT128
BigInt<128, false, __uint128_t>, // 128-bits unsigned (1 x __uint128_t)
BigInt<128, true, __uint128_t>, // 128-bits signed (1 x __uint128_t)
#endif
BigInt<16, false, uint16_t>, // 16-bits unsigned (1 x uint16_t)
BigInt<16, true, uint16_t>, // 16-bits signed (1 x uint16_t)
BigInt<64, false, uint16_t>, // 64-bits unsigned (4 x uint16_t)
BigInt<64, true, uint16_t> // 64-bits signed (4 x uint16_t)
>;

#define ASSERT_SAME(A, B) ASSERT_TRUE((A) == (B))

TYPED_TEST(LlvmLibcUIntClassTest, Additions, Types) {
ASSERT_SAME(create<T>(ZERO) + create<T>(ZERO), create<T>(ZERO));
ASSERT_SAME(create<T>(ONE) + create<T>(ZERO), create<T>(ONE));
ASSERT_SAME(create<T>(ZERO) + create<T>(ONE), create<T>(ONE));
ASSERT_SAME(create<T>(ONE) + create<T>(ONE), create<T>(TWO));
// 2's complement addition works for signed and unsigned types.
// - unsigned : 0xff + 0x01 = 0x00 (255 + 1 = 0)
// - signed : 0xef + 0x01 = 0xf0 (127 + 1 = -128)
ASSERT_SAME(create<T>(MAX) + create<T>(ONE), create<T>(MIN));
}

TYPED_TEST(LlvmLibcUIntClassTest, Subtraction, Types) {
ASSERT_SAME(create<T>(ZERO) - create<T>(ZERO), create<T>(ZERO));
ASSERT_SAME(create<T>(ONE) - create<T>(ONE), create<T>(ZERO));
ASSERT_SAME(create<T>(ONE) - create<T>(ZERO), create<T>(ONE));
// 2's complement subtraction works for signed and unsigned types.
// - unsigned : 0x00 - 0x01 = 0xff ( 0 - 1 = 255)
// - signed : 0xf0 - 0x01 = 0xef (-128 - 1 = 127)
ASSERT_SAME(create<T>(MIN) - create<T>(ONE), create<T>(MAX));
}

TYPED_TEST(LlvmLibcUIntClassTest, Multiplication, Types) {
ASSERT_SAME(create<T>(ZERO) * create<T>(ZERO), create<T>(ZERO));
ASSERT_SAME(create<T>(ZERO) * create<T>(ONE), create<T>(ZERO));
ASSERT_SAME(create<T>(ONE) * create<T>(ZERO), create<T>(ZERO));
ASSERT_SAME(create<T>(ONE) * create<T>(ONE), create<T>(ONE));
ASSERT_SAME(create<T>(ONE) * create<T>(TWO), create<T>(TWO));
ASSERT_SAME(create<T>(TWO) * create<T>(ONE), create<T>(TWO));
// - unsigned : 0xff x 0xff = 0x01 (mod 0xff)
// - signed : 0xef x 0xef = 0x01 (mod 0xff)
ASSERT_SAME(create<T>(MAX) * create<T>(MAX), create<T>(ONE));
}

template <typename T> void print(const char *msg, T value) {
testing::tlog << msg;
IntegerToString<T, radix::Hex> buffer(value);
testing::tlog << buffer.view() << "\n";
}

TEST(LlvmLibcUIntClassTest, SignedAddSub) {
// Computations performed by https://www.wolframalpha.com/
using T = BigInt<128, true, uint32_t>;
const T a = parse_bigint<T>("1927508279017230597");
const T b = parse_bigint<T>("278789278723478925");
const T s = parse_bigint<T>("2206297557740709522");
// Addition
ASSERT_SAME(a + b, s);
ASSERT_SAME(b + a, s); // commutative
// Subtraction
ASSERT_SAME(a - s, -b);
ASSERT_SAME(s - a, b);
}

TEST(LlvmLibcUIntClassTest, SignedMulDiv) {
// Computations performed by https://www.wolframalpha.com/
using T = BigInt<128, true, uint16_t>;
struct {
const char *a;
const char *b;
const char *mul;
} const test_cases[] = {{"-4", "3", "-12"},
{"-3", "-3", "9"},
{"1927508279017230597", "278789278723478925",
"537368642840747885329125014794668225"}};
for (auto tc : test_cases) {
const T a = parse_bigint<T>(tc.a);
const T b = parse_bigint<T>(tc.b);
const T mul = parse_bigint<T>(tc.mul);
// Multiplication
ASSERT_SAME(a * b, mul);
ASSERT_SAME(b * a, mul); // commutative
ASSERT_SAME(a * -b, -mul); // sign
ASSERT_SAME(-a * b, -mul); // sign
ASSERT_SAME(-a * -b, mul); // sign
// Division
ASSERT_SAME(mul / a, b);
ASSERT_SAME(mul / b, a);
ASSERT_SAME(-mul / a, -b); // sign
ASSERT_SAME(mul / -a, -b); // sign
ASSERT_SAME(-mul / -a, b); // sign
}
}

TYPED_TEST(LlvmLibcUIntClassTest, Division, Types) {
ASSERT_SAME(create<T>(ZERO) / create<T>(ONE), create<T>(ZERO));
ASSERT_SAME(create<T>(MAX) / create<T>(ONE), create<T>(MAX));
ASSERT_SAME(create<T>(MAX) / create<T>(MAX), create<T>(ONE));
ASSERT_SAME(create<T>(ONE) / create<T>(ONE), create<T>(ONE));
if constexpr (T::SIGNED) {
// Special case found by fuzzing.
ASSERT_SAME(create<T>(MIN) / create<T>(MIN), create<T>(ONE));
}
// - unsigned : 0xff / 0x02 = 0x7f
// - signed : 0xef / 0x02 = 0x77
ASSERT_SAME(create<T>(MAX) / create<T>(TWO), (create<T>(MAX) >> 1));

using word_type = typename T::word_type;
const T zero_one_repeated = T::all_ones() / T(0xff);
const word_type pattern = word_type(~0) / word_type(0xff);
for (const word_type part : zero_one_repeated.val) {
if constexpr (T::SIGNED == false) {
EXPECT_EQ(part, pattern);
}
}
}

TYPED_TEST(LlvmLibcUIntClassTest, is_neg, Types) {
EXPECT_FALSE(create<T>(ZERO).is_neg());
EXPECT_FALSE(create<T>(ONE).is_neg());
EXPECT_FALSE(create<T>(TWO).is_neg());
EXPECT_EQ(create<T>(MIN).is_neg(), T::SIGNED);
EXPECT_FALSE(create<T>(MAX).is_neg());
}

TYPED_TEST(LlvmLibcUIntClassTest, Masks, Types) {
if constexpr (!T::SIGNED) {
constexpr size_t BITS = T::BITS;
// mask_trailing_ones
ASSERT_SAME((mask_trailing_ones<T, 0>()), T::zero());
ASSERT_SAME((mask_trailing_ones<T, 1>()), T::one());
ASSERT_SAME((mask_trailing_ones<T, BITS - 1>()), T::all_ones() >> 1);
ASSERT_SAME((mask_trailing_ones<T, BITS>()), T::all_ones());
// mask_leading_ones
ASSERT_SAME((mask_leading_ones<T, 0>()), T::zero());
ASSERT_SAME((mask_leading_ones<T, 1>()), T::one() << (BITS - 1));
ASSERT_SAME((mask_leading_ones<T, BITS - 1>()), T::all_ones() - T::one());
ASSERT_SAME((mask_leading_ones<T, BITS>()), T::all_ones());
// mask_trailing_zeros
ASSERT_SAME((mask_trailing_zeros<T, 0>()), T::all_ones());
ASSERT_SAME((mask_trailing_zeros<T, 1>()), T::all_ones() - T::one());
ASSERT_SAME((mask_trailing_zeros<T, BITS - 1>()), T::one() << (BITS - 1));
ASSERT_SAME((mask_trailing_zeros<T, BITS>()), T::zero());
// mask_trailing_zeros
ASSERT_SAME((mask_leading_zeros<T, 0>()), T::all_ones());
ASSERT_SAME((mask_leading_zeros<T, 1>()), T::all_ones() >> 1);
ASSERT_SAME((mask_leading_zeros<T, BITS - 1>()), T::one());
ASSERT_SAME((mask_leading_zeros<T, BITS>()), T::zero());
}
}

TYPED_TEST(LlvmLibcUIntClassTest, CountBits, Types) {
if constexpr (!T::SIGNED) {
for (size_t i = 0; i <= T::BITS; ++i) {
const auto l_one = T::all_ones() << i; // 0b111...000
const auto r_one = T::all_ones() >> i; // 0b000...111
const int zeros = i;
const int ones = T::BITS - zeros;
ASSERT_EQ(cpp::countr_one(r_one), ones);
ASSERT_EQ(cpp::countl_one(l_one), ones);
ASSERT_EQ(cpp::countr_zero(l_one), zeros);
ASSERT_EQ(cpp::countl_zero(r_one), zeros);
}
}
}

using LL_UInt64 = UInt<64>;
// We want to test UInt<128> explicitly. So, for
// convenience, we use a sugar which does not conflict with the UInt128 type
Expand Down Expand Up @@ -561,7 +751,7 @@ TEST(LlvmLibcUIntClassTest, FullMulTests) {
LL_UInt##Bits a = ~LL_UInt##Bits(0); \
LL_UInt##Bits hi = a.quick_mul_hi(a); \
LL_UInt##Bits trunc = static_cast<LL_UInt##Bits>(a.ful_mul(a) >> Bits); \
uint64_t overflow = trunc.sub(hi); \
uint64_t overflow = trunc.sub_overflow(hi); \
EXPECT_EQ(overflow, uint64_t(0)); \
EXPECT_LE(uint64_t(trunc), uint64_t(Error)); \
} while (0)
Expand Down
15 changes: 15 additions & 0 deletions libc/test/src/math/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,21 @@ add_fp_unittest(
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
exp2m1f_test
NEED_MPFR
SUITE
libc-math-unittests
SRCS
exp2m1f_test.cpp
DEPENDS
libc.include.llvm-libc-macros.math_macros
libc.src.errno.errno
libc.src.math.exp2m1f
libc.src.__support.CPP.array
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
exp10f_test
NEED_MPFR
Expand Down
15 changes: 15 additions & 0 deletions libc/test/src/math/exhaustive/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ add_fp_unittest(
-lpthread
)

add_fp_unittest(
exp2m1f_test
NO_RUN_POSTBUILD
NEED_MPFR
SUITE
libc_math_exhaustive_tests
SRCS
exp2m1f_test.cpp
DEPENDS
.exhaustive_test
libc.src.math.exp2m1f
LINK_LIBRARIES
-lpthread
)

add_fp_unittest(
exp10f_test
NO_RUN_POSTBUILD
Expand Down
33 changes: 33 additions & 0 deletions libc/test/src/math/exhaustive/exp2m1f_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//===-- Exhaustive test for exp2m1f ---------------------------------------===//
//
// 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 "exhaustive_test.h"
#include "src/math/exp2m1f.h"
#include "utils/MPFRWrapper/MPFRUtils.h"

namespace mpfr = LIBC_NAMESPACE::testing::mpfr;

using LlvmLibcExp2m1fExhaustiveTest =
LlvmLibcUnaryOpExhaustiveMathTest<float, mpfr::Operation::Exp2m1,
LIBC_NAMESPACE::exp2m1f>;

// Range: [0, Inf];
static constexpr uint32_t POS_START = 0x0000'0000U;
static constexpr uint32_t POS_STOP = 0x7f80'0000U;

TEST_F(LlvmLibcExp2m1fExhaustiveTest, PostiveRange) {
test_full_range_all_roundings(POS_START, POS_STOP);
}

// Range: [-Inf, 0];
static constexpr uint32_t NEG_START = 0x8000'0000U;
static constexpr uint32_t NEG_STOP = 0xff80'0000U;

TEST_F(LlvmLibcExp2m1fExhaustiveTest, NegativeRange) {
test_full_range_all_roundings(NEG_START, NEG_STOP);
}
66 changes: 66 additions & 0 deletions libc/test/src/math/exp2m1f_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//===-- Unittests for exp2m1f ---------------------------------------------===//
//
// 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 "include/llvm-libc-macros/math-macros.h"
#include "src/__support/CPP/array.h"
#include "src/__support/FPUtil/FPBits.h"
#include "src/errno/libc_errno.h"
#include "src/math/exp2m1f.h"
#include "test/UnitTest/FPMatcher.h"
#include "test/UnitTest/Test.h"
#include "utils/MPFRWrapper/MPFRUtils.h"

#include <stdint.h>

using LlvmLibcExp2m1fTest = LIBC_NAMESPACE::testing::FPTest<float>;

namespace mpfr = LIBC_NAMESPACE::testing::mpfr;

TEST_F(LlvmLibcExp2m1fTest, TrickyInputs) {
constexpr LIBC_NAMESPACE::cpp::array<float, 10> INPUTS = {
// EXP2M1F_EXCEPTS_LO
0x1.36dc8ep-36,
0x1.224936p-19,
0x1.d16d2p-20,
0x1.17949ep-14,
-0x1.9c3e1ep-38,
-0x1.4d89b4p-32,
-0x1.a6eac4p-10,
-0x1.e7526ep-6,
// EXP2M1F_EXCEPTS_HI
0x1.16a972p-1,
-0x1.9f12acp-5,
};

for (float x : INPUTS) {
LIBC_NAMESPACE::libc_errno = 0;
EXPECT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Exp2m1, x,
LIBC_NAMESPACE::exp2m1f(x), 0.5);
}
}

TEST_F(LlvmLibcExp2m1fTest, InFloatRange) {
constexpr uint32_t COUNT = 100'000;
constexpr uint32_t STEP = UINT32_MAX / COUNT;
for (uint32_t i = 0, v = 0; i <= COUNT; ++i, v += STEP) {
float x = FPBits(v).get_val();
if (isnan(x) || isinf(x))
continue;
LIBC_NAMESPACE::libc_errno = 0;
float result = LIBC_NAMESPACE::exp2m1f(x);

// If the computation resulted in an error or did not produce valid result
// in the single-precision floating point range, then ignore comparing with
// MPFR result as MPFR can still produce valid results because of its
// wider precision.
if (isnan(result) || isinf(result) || LIBC_NAMESPACE::libc_errno != 0)
continue;
ASSERT_MPFR_MATCH_ALL_ROUNDING(mpfr::Operation::Exp2m1, x,
LIBC_NAMESPACE::exp2m1f(x), 0.5);
}
}
11 changes: 11 additions & 0 deletions libc/test/src/math/smoke/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,17 @@ add_fp_unittest(
libc.src.__support.FPUtil.fp_bits
)

add_fp_unittest(
exp2m1f_test
SUITE
libc-math-smoke-tests
SRCS
exp2m1f_test.cpp
DEPENDS
libc.src.errno.errno
libc.src.math.exp2m1f
)

add_fp_unittest(
exp10f_test
SUITE
Expand Down
63 changes: 63 additions & 0 deletions libc/test/src/math/smoke/exp2m1f_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//===-- Unittests for exp2m1f ---------------------------------------------===//
//
// 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/errno/libc_errno.h"
#include "src/math/exp2m1f.h"
#include "test/UnitTest/FPMatcher.h"
#include "test/UnitTest/Test.h"

using LlvmLibcExp2m1fTest = LIBC_NAMESPACE::testing::FPTest<float>;
using LIBC_NAMESPACE::fputil::testing::ForceRoundingMode;
using LIBC_NAMESPACE::fputil::testing::RoundingMode;

TEST_F(LlvmLibcExp2m1fTest, SpecialNumbers) {
LIBC_NAMESPACE::libc_errno = 0;

EXPECT_FP_EQ_ALL_ROUNDING(aNaN, LIBC_NAMESPACE::exp2m1f(aNaN));
EXPECT_FP_EQ_ALL_ROUNDING(inf, LIBC_NAMESPACE::exp2m1f(inf));
EXPECT_FP_EQ_ALL_ROUNDING(-1.0f, LIBC_NAMESPACE::exp2m1f(neg_inf));
EXPECT_FP_EQ_ALL_ROUNDING(0.0f, LIBC_NAMESPACE::exp2m1f(0.0f));
EXPECT_FP_EQ_ALL_ROUNDING(-0.0f, LIBC_NAMESPACE::exp2m1f(-0.0f));

EXPECT_FP_EQ_ALL_ROUNDING(1.0f, LIBC_NAMESPACE::exp2m1f(1.0f));
EXPECT_FP_EQ_ALL_ROUNDING(-0.5f, LIBC_NAMESPACE::exp2m1f(-1.0f));
EXPECT_FP_EQ_ALL_ROUNDING(3.0f, LIBC_NAMESPACE::exp2m1f(2.0f));
EXPECT_FP_EQ_ALL_ROUNDING(-0.75f, LIBC_NAMESPACE::exp2m1f(-2.0f));
}

TEST_F(LlvmLibcExp2m1fTest, Overflow) {
LIBC_NAMESPACE::libc_errno = 0;

EXPECT_FP_EQ_WITH_EXCEPTION(inf, LIBC_NAMESPACE::exp2m1f(0x1.fffffep+127),
FE_OVERFLOW);
EXPECT_MATH_ERRNO(ERANGE);

EXPECT_FP_EQ_WITH_EXCEPTION(inf, LIBC_NAMESPACE::exp2m1f(128.0f),
FE_OVERFLOW);
EXPECT_MATH_ERRNO(ERANGE);

EXPECT_FP_EQ_WITH_EXCEPTION(inf, LIBC_NAMESPACE::exp2m1f(0x1.000002p+7),
FE_OVERFLOW);
EXPECT_MATH_ERRNO(ERANGE);
}

TEST_F(LlvmLibcExp2m1fTest, Underflow) {
LIBC_NAMESPACE::libc_errno = 0;

EXPECT_FP_EQ_WITH_EXCEPTION(-1.0f, LIBC_NAMESPACE::exp2m1f(-0x1.fffffep+127),
FE_UNDERFLOW);
EXPECT_MATH_ERRNO(ERANGE);

EXPECT_FP_EQ_WITH_EXCEPTION(-1.0f, LIBC_NAMESPACE::exp2m1f(-25.0f),
FE_UNDERFLOW);
EXPECT_MATH_ERRNO(ERANGE);

EXPECT_FP_EQ_WITH_EXCEPTION(-1.0f, LIBC_NAMESPACE::exp2m1f(-0x1.900002p4),
FE_UNDERFLOW);
EXPECT_MATH_ERRNO(ERANGE);
}
50 changes: 46 additions & 4 deletions libc/utils/MPFRWrapper/MPFRUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ class MPFRNumber {
// precision.
template <typename XType,
cpp::enable_if_t<cpp::is_same_v<float, XType>, int> = 0>
explicit MPFRNumber(XType x, int precision = ExtraPrecision<XType>::VALUE,
explicit MPFRNumber(XType x,
unsigned int precision = ExtraPrecision<XType>::VALUE,
RoundingMode rounding = RoundingMode::Nearest)
: mpfr_precision(precision),
mpfr_rounding(get_mpfr_rounding_mode(rounding)) {
Expand All @@ -99,7 +100,8 @@ class MPFRNumber {

template <typename XType,
cpp::enable_if_t<cpp::is_same_v<double, XType>, int> = 0>
explicit MPFRNumber(XType x, int precision = ExtraPrecision<XType>::VALUE,
explicit MPFRNumber(XType x,
unsigned int precision = ExtraPrecision<XType>::VALUE,
RoundingMode rounding = RoundingMode::Nearest)
: mpfr_precision(precision),
mpfr_rounding(get_mpfr_rounding_mode(rounding)) {
Expand All @@ -109,7 +111,8 @@ class MPFRNumber {

template <typename XType,
cpp::enable_if_t<cpp::is_same_v<long double, XType>, int> = 0>
explicit MPFRNumber(XType x, int precision = ExtraPrecision<XType>::VALUE,
explicit MPFRNumber(XType x,
unsigned int precision = ExtraPrecision<XType>::VALUE,
RoundingMode rounding = RoundingMode::Nearest)
: mpfr_precision(precision),
mpfr_rounding(get_mpfr_rounding_mode(rounding)) {
Expand All @@ -119,7 +122,8 @@ class MPFRNumber {

template <typename XType,
cpp::enable_if_t<cpp::is_integral_v<XType>, int> = 0>
explicit MPFRNumber(XType x, int precision = ExtraPrecision<float>::VALUE,
explicit MPFRNumber(XType x,
unsigned int precision = ExtraPrecision<float>::VALUE,
RoundingMode rounding = RoundingMode::Nearest)
: mpfr_precision(precision),
mpfr_rounding(get_mpfr_rounding_mode(rounding)) {
Expand All @@ -134,6 +138,12 @@ class MPFRNumber {
mpfr_set(value, other.value, mpfr_rounding);
}

MPFRNumber(const MPFRNumber &other, unsigned int precision)
: mpfr_precision(precision), mpfr_rounding(other.mpfr_rounding) {
mpfr_init2(value, mpfr_precision);
mpfr_set(value, other.value, mpfr_rounding);
}

~MPFRNumber() { mpfr_clear(value); }

MPFRNumber &operator=(const MPFRNumber &rhs) {
Expand Down Expand Up @@ -229,6 +239,36 @@ class MPFRNumber {
return result;
}

MPFRNumber exp2m1() const {
// TODO: Only use mpfr_exp2m1 once CI and buildbots get MPFR >= 4.2.0.
#if MPFR_VERSION_MAJOR > 4 || \
(MPFR_VERSION_MAJOR == 4 && MPFR_VERSION_MINOR >= 2)
MPFRNumber result(*this);
mpfr_exp2m1(result.value, value, mpfr_rounding);
return result;
#else
unsigned int prec = mpfr_precision * 3;
MPFRNumber result(*this, prec);

float f = mpfr_get_flt(abs().value, mpfr_rounding);
if (f > 0.5f && f < 0x1.0p30f) {
mpfr_exp2(result.value, value, mpfr_rounding);
mpfr_sub_ui(result.value, result.value, 1, mpfr_rounding);
return result;
}

MPFRNumber ln2(2.0f, prec);
// log(2)
mpfr_log(ln2.value, ln2.value, mpfr_rounding);
// x * log(2)
mpfr_mul(result.value, value, ln2.value, mpfr_rounding);
// e^(x * log(2)) - 1
int ex = mpfr_expm1(result.value, result.value, mpfr_rounding);
mpfr_subnormalize(result.value, ex, mpfr_rounding);
return result;
#endif
}

MPFRNumber exp10() const {
MPFRNumber result(*this);
mpfr_exp10(result.value, value, mpfr_rounding);
Expand Down Expand Up @@ -570,6 +610,8 @@ unary_operation(Operation op, InputType input, unsigned int precision,
return mpfrInput.exp();
case Operation::Exp2:
return mpfrInput.exp2();
case Operation::Exp2m1:
return mpfrInput.exp2m1();
case Operation::Exp10:
return mpfrInput.exp10();
case Operation::Expm1:
Expand Down
1 change: 1 addition & 0 deletions libc/utils/MPFRWrapper/MPFRUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ enum class Operation : int {
Erf,
Exp,
Exp2,
Exp2m1,
Exp10,
Expm1,
Floor,
Expand Down
Loading