Skip to content

Commit 71e0261

Browse files
authored
[flang][runtime] Added Fortran::common::optional for use on device.
This is a simplified implementation of std::optional that can be used in the offload builds for the device code. The methods are properly marked with RT_API_ATTRS so that the device compilation succedes. Reviewers: klausler, jeanPerier Reviewed By: jeanPerier Pull Request: #85177
1 parent 0481f04 commit 71e0261

31 files changed

+425
-159
lines changed

flang/include/flang/Common/optional.h

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
//===-- include/flang/Common/optional.h -------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// Implementation of std::optional borrowed from LLVM's
10+
// libc/src/__support/CPP/optional.h with modifications (e.g. value_or, emplace
11+
// methods were added).
12+
//
13+
// The implementation defines optional in Fortran::common namespace.
14+
// This standalone implementation may be used if the target
15+
// does not support std::optional implementation (e.g. CUDA device env),
16+
// otherwise, Fortran::common::optional is an alias for std::optional.
17+
//
18+
// TODO: using libcu++ is the best option for CUDA, but there is a couple
19+
// of issues:
20+
// * Older CUDA toolkits' libcu++ implementations do not support optional.
21+
// * The include paths need to be set up such that all STD header files
22+
// are taken from libcu++.
23+
// * cuda:: namespace need to be forced for all std:: references.
24+
//
25+
//===----------------------------------------------------------------------===//
26+
#ifndef FORTRAN_COMMON_OPTIONAL_H
27+
#define FORTRAN_COMMON_OPTIONAL_H
28+
29+
#include "flang/Runtime/api-attrs.h"
30+
#include <optional>
31+
#include <type_traits>
32+
33+
#if !defined(STD_OPTIONAL_UNSUPPORTED) && \
34+
(defined(__CUDACC__) || defined(__CUDA__)) && defined(__CUDA_ARCH__)
35+
#define STD_OPTIONAL_UNSUPPORTED 1
36+
#endif
37+
38+
#define FORTRAN_OPTIONAL_INLINE_WITH_ATTRS inline RT_API_ATTRS
39+
#define FORTRAN_OPTIONAL_INLINE inline
40+
#define FORTRAN_OPTIONAL_INLINE_VAR inline
41+
42+
namespace Fortran::common {
43+
44+
#if STD_OPTIONAL_UNSUPPORTED
45+
// Trivial nullopt_t struct.
46+
struct nullopt_t {
47+
constexpr explicit nullopt_t() = default;
48+
};
49+
50+
// nullopt that can be used and returned.
51+
FORTRAN_OPTIONAL_INLINE_VAR constexpr nullopt_t nullopt{};
52+
53+
// This is very simple implementation of the std::optional class. It makes
54+
// several assumptions that the underlying type is trivially constructible,
55+
// copyable, or movable.
56+
template <typename T> class optional {
57+
template <typename U, bool = !std::is_trivially_destructible<U>::value>
58+
struct OptionalStorage {
59+
union {
60+
char empty;
61+
U stored_value;
62+
};
63+
64+
bool in_use = false;
65+
66+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS ~OptionalStorage() { reset(); }
67+
68+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr OptionalStorage() : empty() {}
69+
70+
template <typename... Args>
71+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr explicit OptionalStorage(
72+
std::in_place_t, Args &&...args)
73+
: stored_value(std::forward<Args>(args)...) {}
74+
75+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr void reset() {
76+
if (in_use)
77+
stored_value.~U();
78+
in_use = false;
79+
}
80+
};
81+
82+
// The only difference is that this type U doesn't have a nontrivial
83+
// destructor.
84+
template <typename U> struct OptionalStorage<U, false> {
85+
union {
86+
char empty;
87+
U stored_value;
88+
};
89+
90+
bool in_use = false;
91+
92+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr OptionalStorage() : empty() {}
93+
94+
template <typename... Args>
95+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr explicit OptionalStorage(
96+
std::in_place_t, Args &&...args)
97+
: stored_value(std::forward<Args>(args)...) {}
98+
99+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr void reset() {
100+
in_use = false;
101+
}
102+
};
103+
104+
OptionalStorage<T> storage;
105+
106+
public:
107+
// The default methods do not use RT_API_ATTRS, which causes
108+
// warnings in CUDA compilation of form:
109+
// __device__/__host__ annotation is ignored on a function .* that is
110+
// explicitly defaulted on its first declaration
111+
FORTRAN_OPTIONAL_INLINE constexpr optional() = default;
112+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr optional(nullopt_t) {}
113+
114+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr optional(const T &t)
115+
: storage(std::in_place, t) {
116+
storage.in_use = true;
117+
}
118+
FORTRAN_OPTIONAL_INLINE constexpr optional(const optional &) = default;
119+
120+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr optional(T &&t)
121+
: storage(std::in_place, std::move(t)) {
122+
storage.in_use = true;
123+
}
124+
FORTRAN_OPTIONAL_INLINE constexpr optional(optional &&O) = default;
125+
126+
template <typename... ArgTypes>
127+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr optional(
128+
std::in_place_t, ArgTypes &&...Args)
129+
: storage(std::in_place, std::forward<ArgTypes>(Args)...) {
130+
storage.in_use = true;
131+
}
132+
133+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr optional &operator=(T &&t) {
134+
storage.stored_value = std::move(t);
135+
storage.in_use = true;
136+
return *this;
137+
}
138+
139+
FORTRAN_OPTIONAL_INLINE constexpr optional &operator=(optional &&) = default;
140+
141+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr optional &operator=(const T &t) {
142+
storage.stored_value = t;
143+
storage.in_use = true;
144+
return *this;
145+
}
146+
147+
FORTRAN_OPTIONAL_INLINE constexpr optional &operator=(
148+
const optional &) = default;
149+
150+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr void reset() { storage.reset(); }
151+
152+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr const T &value() const & {
153+
return storage.stored_value;
154+
}
155+
156+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr T &value() & {
157+
return storage.stored_value;
158+
}
159+
160+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr explicit operator bool() const {
161+
return storage.in_use;
162+
}
163+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr bool has_value() const {
164+
return storage.in_use;
165+
}
166+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr const T *operator->() const {
167+
return &storage.stored_value;
168+
}
169+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr T *operator->() {
170+
return &storage.stored_value;
171+
}
172+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr const T &operator*() const & {
173+
return storage.stored_value;
174+
}
175+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr T &operator*() & {
176+
return storage.stored_value;
177+
}
178+
179+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr T &&value() && {
180+
return std::move(storage.stored_value);
181+
}
182+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr T &&operator*() && {
183+
return std::move(storage.stored_value);
184+
}
185+
186+
template <typename VT>
187+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr T value_or(
188+
VT &&default_value) const & {
189+
return storage.in_use ? storage.stored_value
190+
: static_cast<T>(std::forward<VT>(default_value));
191+
}
192+
193+
template <typename VT>
194+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr T value_or(
195+
VT &&default_value) && {
196+
return storage.in_use ? std::move(storage.stored_value)
197+
: static_cast<T>(std::forward<VT>(default_value));
198+
}
199+
200+
template <typename... ArgTypes>
201+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS
202+
std::enable_if_t<std::is_constructible_v<T, ArgTypes &&...>, T &>
203+
emplace(ArgTypes &&...args) {
204+
reset();
205+
new (reinterpret_cast<void *>(std::addressof(storage.stored_value)))
206+
T(std::forward<ArgTypes>(args)...);
207+
storage.in_use = true;
208+
return value();
209+
}
210+
211+
template <typename U = T,
212+
std::enable_if_t<(std::is_constructible_v<T, U &&> &&
213+
!std::is_same_v<std::decay_t<U>, std::in_place_t> &&
214+
!std::is_same_v<std::decay_t<U>, optional> &&
215+
std::is_convertible_v<U &&, T>),
216+
bool> = true>
217+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS constexpr optional(U &&value) {
218+
new (reinterpret_cast<void *>(std::addressof(storage.stored_value)))
219+
T(std::forward<U>(value));
220+
storage.in_use = true;
221+
}
222+
223+
template <typename U = T,
224+
std::enable_if_t<(std::is_constructible_v<T, U &&> &&
225+
!std::is_same_v<std::decay_t<U>, std::in_place_t> &&
226+
!std::is_same_v<std::decay_t<U>, optional> &&
227+
!std::is_convertible_v<U &&, T>),
228+
bool> = false>
229+
FORTRAN_OPTIONAL_INLINE_WITH_ATTRS explicit constexpr optional(U &&value) {
230+
new (reinterpret_cast<void *>(std::addressof(storage.stored_value)))
231+
T(std::forward<U>(value));
232+
storage.in_use = true;
233+
}
234+
};
235+
#else // !STD_OPTIONAL_UNSUPPORTED
236+
using std::nullopt;
237+
using std::nullopt_t;
238+
using std::optional;
239+
#endif // !STD_OPTIONAL_UNSUPPORTED
240+
241+
} // namespace Fortran::common
242+
243+
#endif // FORTRAN_COMMON_OPTIONAL_H

flang/include/flang/Runtime/type-code.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define FORTRAN_RUNTIME_TYPE_CODE_H_
1111

1212
#include "flang/Common/Fortran.h"
13+
#include "flang/Common/optional.h"
1314
#include "flang/ISO_Fortran_binding_wrapper.h"
1415
#include <optional>
1516
#include <utility>
@@ -54,7 +55,7 @@ class TypeCode {
5455
return IsValid() && !IsDerived();
5556
}
5657

57-
RT_API_ATTRS std::optional<std::pair<TypeCategory, int>>
58+
RT_API_ATTRS Fortran::common::optional<std::pair<TypeCategory, int>>
5859
GetCategoryAndKind() const;
5960

6061
RT_API_ATTRS bool operator==(TypeCode that) const {

flang/runtime/connection.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
#define FORTRAN_RUNTIME_IO_CONNECTION_H_
1313

1414
#include "format.h"
15+
#include "flang/Common/optional.h"
1516
#include <cinttypes>
16-
#include <optional>
1717

1818
namespace Fortran::runtime::io {
1919

@@ -26,10 +26,10 @@ enum class Access { Sequential, Direct, Stream };
2626
// established in an OPEN statement.
2727
struct ConnectionAttributes {
2828
Access access{Access::Sequential}; // ACCESS='SEQUENTIAL', 'DIRECT', 'STREAM'
29-
std::optional<bool> isUnformatted; // FORM='UNFORMATTED' if true
29+
Fortran::common::optional<bool> isUnformatted; // FORM='UNFORMATTED' if true
3030
bool isUTF8{false}; // ENCODING='UTF-8'
3131
unsigned char internalIoCharKind{0}; // 0->external, 1/2/4->internal
32-
std::optional<std::int64_t> openRecl; // RECL= on OPEN
32+
Fortran::common::optional<std::int64_t> openRecl; // RECL= on OPEN
3333

3434
bool IsRecordFile() const {
3535
// Formatted stream files are viewed as having records, at least on input
@@ -63,14 +63,14 @@ struct ConnectionState : public ConnectionAttributes {
6363
unterminatedRecord = false;
6464
}
6565

66-
std::optional<std::int64_t> EffectiveRecordLength() const {
66+
Fortran::common::optional<std::int64_t> EffectiveRecordLength() const {
6767
// When an input record is longer than an explicit RECL= from OPEN
6868
// it is effectively truncated on input.
6969
return openRecl && recordLength && *openRecl < *recordLength ? openRecl
7070
: recordLength;
7171
}
7272

73-
std::optional<std::int64_t> recordLength;
73+
Fortran::common::optional<std::int64_t> recordLength;
7474

7575
std::int64_t currentRecordNumber{1}; // 1 is first
7676

@@ -86,11 +86,12 @@ struct ConnectionState : public ConnectionAttributes {
8686
std::int64_t furthestPositionInRecord{0}; // max(position+bytes)
8787

8888
// Set at end of non-advancing I/O data transfer
89-
std::optional<std::int64_t> leftTabLimit; // offset in current record
89+
Fortran::common::optional<std::int64_t>
90+
leftTabLimit; // offset in current record
9091

9192
// currentRecordNumber value captured after ENDFILE/REWIND/BACKSPACE statement
9293
// or an end-of-file READ condition on a sequential access file
93-
std::optional<std::int64_t> endfileRecordNumber;
94+
Fortran::common::optional<std::int64_t> endfileRecordNumber;
9495

9596
// Mutable modes set at OPEN() that can be overridden in READ/WRITE & FORMAT
9697
MutableModes modes; // BLANK=, DECIMAL=, SIGN=, ROUND=, PAD=, DELIM=, kP

flang/runtime/descriptor-io.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
namespace Fortran::runtime::io::descr {
1313

1414
// Defined formatted I/O (maybe)
15-
std::optional<bool> DefinedFormattedIo(IoStatementState &io,
15+
Fortran::common::optional<bool> DefinedFormattedIo(IoStatementState &io,
1616
const Descriptor &descriptor, const typeInfo::DerivedType &derived,
1717
const typeInfo::SpecialBinding &special,
1818
const SubscriptValue subscripts[]) {
19-
std::optional<DataEdit> peek{io.GetNextDataEdit(0 /*to peek at it*/)};
19+
Fortran::common::optional<DataEdit> peek{
20+
io.GetNextDataEdit(0 /*to peek at it*/)};
2021
if (peek &&
2122
(peek->descriptor == DataEdit::DefinedDerivedType ||
2223
peek->descriptor == DataEdit::ListDirected)) {
@@ -55,7 +56,7 @@ std::optional<bool> DefinedFormattedIo(IoStatementState &io,
5556
int unit{external->unitNumber()};
5657
int ioStat{IostatOk};
5758
char ioMsg[100];
58-
std::optional<std::int64_t> startPos;
59+
Fortran::common::optional<std::int64_t> startPos;
5960
if (edit.descriptor == DataEdit::DefinedDerivedType &&
6061
special.which() == typeInfo::SpecialBinding::Which::ReadFormatted) {
6162
// DT is an edit descriptor so everything that the child
@@ -96,7 +97,7 @@ std::optional<bool> DefinedFormattedIo(IoStatementState &io,
9697
// There's a defined I/O subroutine, but there's a FORMAT present and
9798
// it does not have a DT data edit descriptor, so apply default formatting
9899
// to the components of the derived type as usual.
99-
return std::nullopt;
100+
return Fortran::common::nullopt;
100101
}
101102
}
102103

flang/runtime/descriptor-io.h

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "terminator.h"
2222
#include "type-info.h"
2323
#include "unit.h"
24+
#include "flang/Common/optional.h"
2425
#include "flang/Common/uint128.h"
2526
#include "flang/Runtime/cpp-type.h"
2627
#include "flang/Runtime/descriptor.h"
@@ -321,9 +322,9 @@ static bool DefaultComponentwiseUnformattedIO(IoStatementState &io,
321322
return true;
322323
}
323324

324-
std::optional<bool> DefinedFormattedIo(IoStatementState &, const Descriptor &,
325-
const typeInfo::DerivedType &, const typeInfo::SpecialBinding &,
326-
const SubscriptValue[]);
325+
Fortran::common::optional<bool> DefinedFormattedIo(IoStatementState &,
326+
const Descriptor &, const typeInfo::DerivedType &,
327+
const typeInfo::SpecialBinding &, const SubscriptValue[]);
327328

328329
template <Direction DIR>
329330
static bool FormattedDerivedTypeIO(IoStatementState &io,
@@ -334,7 +335,7 @@ static bool FormattedDerivedTypeIO(IoStatementState &io,
334335
RUNTIME_CHECK(handler, addendum != nullptr);
335336
const typeInfo::DerivedType *type{addendum->derivedType()};
336337
RUNTIME_CHECK(handler, type != nullptr);
337-
std::optional<typeInfo::SpecialBinding> nonTbpSpecial;
338+
Fortran::common::optional<typeInfo::SpecialBinding> nonTbpSpecial;
338339
const typeInfo::SpecialBinding *special{nullptr};
339340
if (table) {
340341
if (const auto *definedIo{table->Find(*type,
@@ -365,7 +366,7 @@ static bool FormattedDerivedTypeIO(IoStatementState &io,
365366
std::size_t numElements{descriptor.Elements()};
366367
for (std::size_t j{0}; j < numElements;
367368
++j, descriptor.IncrementSubscripts(subscripts)) {
368-
std::optional<bool> result;
369+
Fortran::common::optional<bool> result;
369370
if (special) {
370371
result = DefinedFormattedIo(io, descriptor, *type, *special, subscripts);
371372
}
@@ -406,7 +407,7 @@ static bool UnformattedDescriptorIO(IoStatementState &io,
406407
: typeInfo::SpecialBinding::Which::WriteUnformatted,
407408
definedIo->subroutine, definedIo->isDtvArgPolymorphic, false,
408409
false};
409-
if (std::optional<bool> wasDefined{
410+
if (Fortran::common::optional<bool> wasDefined{
410411
DefinedUnformattedIo(io, descriptor, *type, special)}) {
411412
return *wasDefined;
412413
}

0 commit comments

Comments
 (0)