Skip to content

Commit b25ecac

Browse files
authored
[flang] Implement conditional expressions lowering (F2023) (#186490)
## Implement Lowering for Fortran 2023 Conditional Expressions (R1002) ***This PR contains the lowering steps only for ease of review. DO NOT MERGE until #186489 is merged.*** Implements Fortran 2023 conditional expressions with syntax: `result = (condition ? value1 : condition2 ? value2 : ... : elseValue)` Issue: #176999 Discourse: https://discourse.llvm.org/t/rfc-adding-conditional-expressions-in-flang-f2023/89869/1 -- note that some of the details provided in the RFC post are no longer accurate ### Implementation Details **Lowering to HLFIR:** - Lazy evaluation via nested if-then-else control flow - Only the selected branch is evaluated - Temporary allocation with proper cleanup - Special handling for: - CHARACTER types with deferred length - Arrays (shape determined by selected branch per F2023 10.1.4(7)) - Derived types **LIT Testing:** - Lowering tests: HLFIR code generation verification - Note: Executable tests will be added to the llvm-test-suite repo (llvm/llvm-test-suite#369) **Limitations** - Conditional arguments are not yet supported. This work is planned - #180592 - Polymorphic types (CLASS) not yet supported in lowering - Both limitations will emit clear error message if encountered ### Examples ``` ! Simple conditional x = (flag ? 10 : 20) ! Chained result = (x > 0 ? 1 : x < 0 ? -1 : 0) ! Examples from F2023 ( ABS (RESIDUAL)<=TOLERANCE ? ’ok’ : ’did not converge’ ) ( I>0 .AND. I<=SIZE (A) ? A (I) : PRESENT (VAL) ? VAL : 0.0 ) ``` AI Usage Disclosure: AI tools (Claude Sonnet 4.5) were used to assist with implementation of this feature and test code generation. I have reviewed, modified, and tested all AI-generated code.
1 parent 1b2ccc1 commit b25ecac

File tree

2 files changed

+422
-6
lines changed

2 files changed

+422
-6
lines changed

flang/lib/Lower/ConvertExprToHLFIR.cpp

Lines changed: 149 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1814,12 +1814,6 @@ class HlfirBuilder {
18141814
TODO(getLoc(), "lowering type parameter inquiry to HLFIR");
18151815
}
18161816

1817-
template <typename T>
1818-
hlfir::EntityWithAttributes
1819-
gen(const Fortran::evaluate::ConditionalExpr<T> &) {
1820-
TODO(getLoc(), "lowering conditional expression to HLFIR");
1821-
}
1822-
18231817
hlfir::EntityWithAttributes
18241818
gen(const Fortran::evaluate::DescriptorInquiry &desc) {
18251819
mlir::Location loc = getLoc();
@@ -1853,6 +1847,155 @@ class HlfirBuilder {
18531847
llvm_unreachable("unknown descriptor inquiry");
18541848
}
18551849

1850+
/// Build nested if-then-else chain by walking the right-skewed
1851+
/// ConditionalExpr tree. The assignValue callback generates and assigns
1852+
/// each value to avoid evaluating non-taken branches.
1853+
template <typename T, typename Callback>
1854+
void
1855+
buildConditionalIfChain(const Fortran::evaluate::ConditionalExpr<T> &condExpr,
1856+
const Callback &assignValue) {
1857+
const mlir::Location loc{getLoc()};
1858+
fir::FirOpBuilder &builder{getBuilder()};
1859+
getStmtCtx().pushScope();
1860+
const hlfir::EntityWithAttributes condEntity{gen(condExpr.condition())};
1861+
mlir::Value condition{hlfir::loadTrivialScalar(loc, builder, condEntity)};
1862+
condition = builder.createConvert(loc, builder.getI1Type(), condition);
1863+
builder.genIfOp(loc, {}, condition, /*withElseRegion=*/true)
1864+
.genThen([&]() {
1865+
getStmtCtx().pushScope();
1866+
assignValue(condExpr.thenValue());
1867+
getStmtCtx().finalizeAndPop();
1868+
})
1869+
.genElse([&]() {
1870+
getStmtCtx().pushScope();
1871+
assignValue(condExpr.elseValue());
1872+
getStmtCtx().finalizeAndPop();
1873+
})
1874+
.end();
1875+
getStmtCtx().finalizeAndPop();
1876+
}
1877+
1878+
/// Generate scalar conditional with lazy evaluation using assignment.
1879+
/// Creates a temporary and assigns the selected branch value to it.
1880+
template <typename T>
1881+
hlfir::Entity
1882+
genScalarConditional(const Fortran::evaluate::ConditionalExpr<T> &condExpr,
1883+
mlir::Type elementType,
1884+
const llvm::SmallVector<mlir::Value, 1> &typeParams) {
1885+
const mlir::Location loc{getLoc()};
1886+
fir::FirOpBuilder &builder{getBuilder()};
1887+
const mlir::Value tempStorage{builder.createTemporary(
1888+
loc, elementType, ".cond.scalar",
1889+
/*shape=*/mlir::ValueRange{}, /*typeParams=*/typeParams)};
1890+
const hlfir::DeclareOp tempDecl{hlfir::DeclareOp::create(
1891+
builder, loc, tempStorage, ".cond.result",
1892+
/*shape=*/mlir::Value{}, /*typeParams=*/typeParams)};
1893+
const hlfir::Entity temp{tempDecl};
1894+
buildConditionalIfChain(
1895+
condExpr, [&](const Fortran::evaluate::Expr<T> &expr) {
1896+
hlfir::Entity entity{gen(expr)};
1897+
entity = hlfir::loadTrivialScalar(loc, builder, entity);
1898+
hlfir::AssignOp::create(builder, loc, entity, temp);
1899+
});
1900+
return temp;
1901+
}
1902+
1903+
/// Generate conditional expression using an allocatable temporary with lazy
1904+
/// evaluation. Creates an unallocated allocatable, then uses assignment to
1905+
/// set the value from the chosen branch (allocation/reallocation handled by
1906+
/// runtime).
1907+
template <typename T>
1908+
hlfir::Entity genAllocatableConditional(
1909+
const Fortran::evaluate::ConditionalExpr<T> &condExpr,
1910+
mlir::Type resultType, llvm::StringRef debugName) {
1911+
const mlir::Location loc{getLoc()};
1912+
fir::FirOpBuilder &builder{getBuilder()};
1913+
const mlir::Type heapType{fir::HeapType::get(resultType)};
1914+
const mlir::Type boxHeapType{fir::BoxType::get(heapType)};
1915+
const mlir::Value tempStorage{
1916+
builder.createTemporary(loc, boxHeapType, debugName)};
1917+
const mlir::Value unallocBox{fir::factory::createUnallocatedBox(
1918+
builder, loc, boxHeapType, /*nonDeferredParams=*/{})};
1919+
builder.createStoreWithConvert(loc, unallocBox, tempStorage);
1920+
const hlfir::DeclareOp tempDecl{
1921+
hlfir::DeclareOp::create(builder, loc, tempStorage, ".cond.result")};
1922+
const hlfir::Entity temp{tempDecl};
1923+
// Lazy evaluation: only the selected branch is evaluated and assigned.
1924+
buildConditionalIfChain(
1925+
condExpr, [&](const Fortran::evaluate::Expr<T> &expr) {
1926+
const hlfir::Entity entity{gen(expr)};
1927+
hlfir::AssignOp::create(builder, loc, entity, temp,
1928+
/*isWholeAllocatableAssignment=*/true,
1929+
/*keepLhsLengthIfRealloc=*/false,
1930+
/*temporary_lhs=*/true);
1931+
});
1932+
fir::FirOpBuilder *const bldr{&builder};
1933+
getStmtCtx().attachCleanup([=]() {
1934+
fir::factory::genFreememIfAllocated(
1935+
*bldr, loc,
1936+
fir::MutableBoxValue{tempStorage, /*lenParams=*/{},
1937+
fir::MutableProperties{}});
1938+
});
1939+
return temp;
1940+
}
1941+
1942+
/// Generate scalar CHARACTER conditional with proper length handling.
1943+
template <typename T>
1944+
std::optional<hlfir::EntityWithAttributes> genCharacterConditional(
1945+
const Fortran::evaluate::ConditionalExpr<T> &condExpr) {
1946+
const mlir::Location loc{getLoc()};
1947+
fir::FirOpBuilder &builder{getBuilder()};
1948+
const mlir::Type resultType{Fortran::lower::translateSomeExprToFIRType(
1949+
converter, toEvExpr(condExpr))};
1950+
const mlir::Type elementType{hlfir::getFortranElementType(resultType)};
1951+
if (auto charType = mlir::dyn_cast<fir::CharacterType>(elementType)) {
1952+
if (charType.hasConstantLen()) {
1953+
llvm::SmallVector<mlir::Value, 1> typeParams;
1954+
const mlir::Value len{builder.createIntegerConstant(
1955+
loc, builder.getCharacterLengthType(), charType.getLen())};
1956+
typeParams.push_back(len);
1957+
return hlfir::EntityWithAttributes{
1958+
genScalarConditional(condExpr, elementType, typeParams)};
1959+
}
1960+
// Non-constant/varying length: use allocatable conditional to get length
1961+
// from selected branch.
1962+
return hlfir::EntityWithAttributes{
1963+
genAllocatableConditional(condExpr, elementType, ".cond.char")};
1964+
}
1965+
return std::nullopt;
1966+
}
1967+
1968+
/// Conditional expression (Fortran 2023)
1969+
template <typename T>
1970+
hlfir::EntityWithAttributes
1971+
gen(const Fortran::evaluate::ConditionalExpr<T> &condExpr) {
1972+
const int rank{condExpr.Rank()};
1973+
mlir::Type resultType{Fortran::lower::translateSomeExprToFIRType(
1974+
converter, toEvExpr(condExpr))};
1975+
if (fir::isPolymorphicType(resultType))
1976+
TODO(getLoc(), "polymorphic conditional expression");
1977+
if (fir::isRecordWithTypeParameters(
1978+
hlfir::getFortranElementType(resultType)))
1979+
TODO(getLoc(), "conditional expression with length-parameterized "
1980+
"derived type");
1981+
// Arrays: handle early to avoid unnecessary type checks.
1982+
// Per F2023 10.1.4(7), the shape is determined by the chosen branch.
1983+
if (rank != 0) {
1984+
const mlir::Type condResultType{
1985+
hlfir::getFortranElementOrSequenceType(resultType)};
1986+
return hlfir::EntityWithAttributes{
1987+
genAllocatableConditional(condExpr, condResultType, ".cond.array")};
1988+
}
1989+
// CHARACTER scalars require special handling for type parameters.
1990+
if constexpr (T::category == Fortran::common::TypeCategory::Character) {
1991+
if (auto result = genCharacterConditional(condExpr))
1992+
return *result;
1993+
}
1994+
// Scalar types (INTEGER, REAL, COMPLEX, LOGICAL, UNSIGNED, Derived).
1995+
return hlfir::EntityWithAttributes{genScalarConditional(
1996+
condExpr, hlfir::getFortranElementType(resultType), {})};
1997+
}
1998+
18561999
hlfir::EntityWithAttributes
18572000
gen(const Fortran::evaluate::ImpliedDoIndex &var) {
18582001
mlir::Value value = symMap.lookupImpliedDo(toStringRef(var.name));

0 commit comments

Comments
 (0)