Skip to content

Commit 7046202

Browse files
authored
[flang] Move whole allocatable assignment implicit conversion to lowering (#70317)
The front-end is making implicit conversions explicit in assignment and structure constructors. While this generally helps and is needed by semantics to fold structure constructors correctly, this is incorrect when the LHS or component is an allocatable. The RHS may have non default lower bounds that should be propagated to the LHS, and making the conversion explicit changes the semantics. In the structure constructor, the situation is even worse since Fortran 2018 7.5.10 point 7 allows the value to be a reference to an unallocated allocatable, and adding an explicit conversion in semantics will cause a segfault. This patch removes the explicit convert in semantics when the LHS/component is a whole allocatable, and update lowering to deal with the conversion insertion, dealing with preserving the lower bounds and the tricky structure constructor case.
1 parent fde1ecd commit 7046202

File tree

11 files changed

+309
-174
lines changed

11 files changed

+309
-174
lines changed

flang/include/flang/Optimizer/Builder/Character.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,11 @@ std::pair<mlir::Value, mlir::Value>
235235
extractCharacterProcedureTuple(fir::FirOpBuilder &builder, mlir::Location loc,
236236
mlir::Value tuple, bool openBoxProc = true);
237237

238+
fir::CharBoxValue convertCharacterKind(fir::FirOpBuilder &builder,
239+
mlir::Location loc,
240+
fir::CharBoxValue srcBoxChar,
241+
int toKind);
242+
238243
} // namespace fir::factory
239244

240245
#endif // FORTRAN_OPTIMIZER_BUILDER_CHARACTER_H

flang/include/flang/Optimizer/Builder/HLFIRTools.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,29 @@ std::pair<hlfir::Entity, mlir::Value>
427427
createTempFromMold(mlir::Location loc, fir::FirOpBuilder &builder,
428428
hlfir::Entity mold);
429429

430+
hlfir::EntityWithAttributes convertCharacterKind(mlir::Location loc,
431+
fir::FirOpBuilder &builder,
432+
hlfir::Entity scalarChar,
433+
int toKind);
434+
435+
/// Materialize an implicit Fortran type conversion from \p source to \p toType.
436+
/// This is a no-op if the Fortran category and KIND of \p source are
437+
/// the same as the one in \p toType. This is also a no-op if \p toType is an
438+
/// unlimited polymorphic. For characters, this implies that a conversion is
439+
/// only inserted in case of KIND mismatch (and not in case of length mismatch),
440+
/// and that the resulting entity length is the same as the one from \p source.
441+
/// It is valid to call this helper if \p source is an array. If a conversion is
442+
/// inserted for arrays, a clean-up will be returned. If no conversion is
443+
/// needed, the source is returned.
444+
/// Beware that the resulting entity mlir type may not be toType: it will be a
445+
/// Fortran entity with the same Fortran category and KIND.
446+
/// If preserveLowerBounds is set, the returned entity will have the same lower
447+
/// bounds as \p source.
448+
std::pair<hlfir::Entity, std::optional<hlfir::CleanupFunction>>
449+
genTypeAndKindConvert(mlir::Location loc, fir::FirOpBuilder &builder,
450+
hlfir::Entity source, mlir::Type toType,
451+
bool preserveLowerBounds);
452+
430453
} // namespace hlfir
431454

432455
#endif // FORTRAN_OPTIMIZER_BUILDER_HLFIRTOOLS_H

flang/lib/Lower/Bridge.cpp

Lines changed: 27 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3372,47 +3372,25 @@ class FirConverter : public Fortran::lower::AbstractConverter {
33723372
}
33733373
}
33743374

3375-
/// Given converted LHS and RHS of the assignment, generate
3376-
/// explicit type conversion for implicit Logical<->Integer
3377-
/// conversion. Return Value representing the converted RHS,
3378-
/// if the implicit Logical<->Integer is detected, otherwise,
3379-
/// return nullptr. The caller is responsible for inserting
3380-
/// DestroyOp in case the returned value has hlfir::ExprType.
3381-
mlir::Value
3382-
genImplicitLogicalConvert(const Fortran::evaluate::Assignment &assign,
3383-
hlfir::Entity rhs,
3384-
Fortran::lower::StatementContext &stmtCtx) {
3385-
mlir::Type fromTy = rhs.getFortranElementType();
3386-
if (!fromTy.isa<mlir::IntegerType, fir::LogicalType>())
3387-
return nullptr;
3388-
3389-
mlir::Type toTy = hlfir::getFortranElementType(genType(assign.lhs));
3390-
if (fromTy == toTy)
3391-
return nullptr;
3392-
if (!toTy.isa<mlir::IntegerType, fir::LogicalType>())
3393-
return nullptr;
3394-
3375+
/// Given converted LHS and RHS of the assignment, materialize any
3376+
/// implicit conversion of the RHS to the LHS type. The front-end
3377+
/// usually already makes those explicit, except for non-standard
3378+
/// LOGICAL <-> INTEGER, or if the LHS is a whole allocatable
3379+
/// (making the conversion explicit in the front-end would prevent
3380+
/// propagation of the LHS lower bound in the reallocation).
3381+
/// If array temporaries or values are created, the cleanups are
3382+
/// added in the statement context.
3383+
hlfir::Entity genImplicitConvert(const Fortran::evaluate::Assignment &assign,
3384+
hlfir::Entity rhs, bool preserveLowerBounds,
3385+
Fortran::lower::StatementContext &stmtCtx) {
33953386
mlir::Location loc = toLocation();
33963387
auto &builder = getFirOpBuilder();
3397-
if (assign.rhs.Rank() == 0)
3398-
return builder.createConvert(loc, toTy, rhs);
3399-
3400-
mlir::Value shape = hlfir::genShape(loc, builder, rhs);
3401-
auto genKernel =
3402-
[&rhs, &toTy](mlir::Location loc, fir::FirOpBuilder &builder,
3403-
mlir::ValueRange oneBasedIndices) -> hlfir::Entity {
3404-
auto elementPtr = hlfir::getElementAt(loc, builder, rhs, oneBasedIndices);
3405-
auto val = hlfir::loadTrivialScalar(loc, builder, elementPtr);
3406-
return hlfir::EntityWithAttributes{builder.createConvert(loc, toTy, val)};
3407-
};
3408-
mlir::Value convertedRhs = hlfir::genElementalOp(
3409-
loc, builder, toTy, shape, /*typeParams=*/{}, genKernel,
3410-
/*isUnordered=*/true);
3411-
fir::FirOpBuilder *bldr = &builder;
3412-
stmtCtx.attachCleanup([loc, bldr, convertedRhs]() {
3413-
bldr->create<hlfir::DestroyOp>(loc, convertedRhs);
3414-
});
3415-
return convertedRhs;
3388+
mlir::Type toType = genType(assign.lhs);
3389+
auto valueAndPair = hlfir::genTypeAndKindConvert(loc, builder, rhs, toType,
3390+
preserveLowerBounds);
3391+
if (valueAndPair.second)
3392+
stmtCtx.attachCleanup(*valueAndPair.second);
3393+
return hlfir::Entity{valueAndPair.first};
34163394
}
34173395

34183396
static void
@@ -3476,14 +3454,17 @@ class FirConverter : public Fortran::lower::AbstractConverter {
34763454
// loops early if possible. This also dereferences pointer and
34773455
// allocatable RHS: the target is being assigned from.
34783456
rhs = hlfir::loadTrivialScalar(loc, builder, rhs);
3479-
// In intrinsic assignments, Logical<->Integer assignments are allowed as
3480-
// an extension, but there is no explicit Convert expression for the RHS.
3481-
// Recognize the type mismatch here and insert explicit scalar convert or
3482-
// ElementalOp for array assignment.
3457+
// In intrinsic assignments, the LHS type may not match the RHS type, in
3458+
// which case an implicit conversion of the LHS must be done. The
3459+
// front-end usually makes it explicit, unless it cannot (whole
3460+
// allocatable LHS or Logical<->Integer assignment extension). Recognize
3461+
// any type mismatches here and insert explicit scalar convert or
3462+
// ElementalOp for array assignment. Preserve the RHS lower bounds on the
3463+
// converted entity in case of assignment to whole allocatables so to
3464+
// propagate the lower bounds to the LHS in case of reallocation.
34833465
if (!userDefinedAssignment)
3484-
if (mlir::Value conversion =
3485-
genImplicitLogicalConvert(assign, rhs, stmtCtx))
3486-
rhs = hlfir::Entity{conversion};
3466+
rhs = genImplicitConvert(assign, rhs, isWholeAllocatableAssignment,
3467+
stmtCtx);
34873468
return rhs;
34883469
};
34893470

flang/lib/Lower/ConvertExpr.cpp

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,41 +1237,8 @@ class ScalarExprLowering {
12371237
[&](const fir::CharBoxValue &boxchar) -> ExtValue {
12381238
if constexpr (TC1 == Fortran::common::TypeCategory::Character &&
12391239
TC2 == TC1) {
1240-
// Use char_convert. Each code point is translated from a
1241-
// narrower/wider encoding to the target encoding. For example, 'A'
1242-
// may be translated from 0x41 : i8 to 0x0041 : i16. The symbol
1243-
// for euro (0x20AC : i16) may be translated from a wide character
1244-
// to "0xE2 0x82 0xAC" : UTF-8.
1245-
mlir::Value bufferSize = boxchar.getLen();
1246-
auto kindMap = builder.getKindMap();
1247-
mlir::Value boxCharAddr = boxchar.getAddr();
1248-
auto fromTy = boxCharAddr.getType();
1249-
if (auto charTy = fromTy.dyn_cast<fir::CharacterType>()) {
1250-
// boxchar is a value, not a variable. Turn it into a temporary.
1251-
// As a value, it ought to have a constant LEN value.
1252-
assert(charTy.hasConstantLen() && "must have constant length");
1253-
mlir::Value tmp = builder.createTemporary(loc, charTy);
1254-
builder.create<fir::StoreOp>(loc, boxCharAddr, tmp);
1255-
boxCharAddr = tmp;
1256-
}
1257-
auto fromBits =
1258-
kindMap.getCharacterBitsize(fir::unwrapRefType(fromTy)
1259-
.cast<fir::CharacterType>()
1260-
.getFKind());
1261-
auto toBits = kindMap.getCharacterBitsize(
1262-
ty.cast<fir::CharacterType>().getFKind());
1263-
if (toBits < fromBits) {
1264-
// Scale by relative ratio to give a buffer of the same length.
1265-
auto ratio = builder.createIntegerConstant(
1266-
loc, bufferSize.getType(), fromBits / toBits);
1267-
bufferSize =
1268-
builder.create<mlir::arith::MulIOp>(loc, bufferSize, ratio);
1269-
}
1270-
auto dest = builder.create<fir::AllocaOp>(
1271-
loc, ty, mlir::ValueRange{bufferSize});
1272-
builder.create<fir::CharConvertOp>(loc, boxCharAddr,
1273-
boxchar.getLen(), dest);
1274-
return fir::CharBoxValue{dest, boxchar.getLen()};
1240+
return fir::factory::convertCharacterKind(builder, loc, boxchar,
1241+
KIND);
12751242
} else {
12761243
fir::emitFatalError(
12771244
loc, "unsupported evaluate::Convert between CHARACTER type "
@@ -3965,7 +3932,7 @@ class ArrayExprLowering {
39653932
auto castTo = builder.createConvert(loc, memrefTy, origVal);
39663933
origVal = builder.create<fir::EmboxOp>(loc, eleTy, castTo);
39673934
}
3968-
mlir::Value val = builder.createConvert(loc, eleTy, origVal);
3935+
mlir::Value val = builder.convertWithSemantics(loc, eleTy, origVal);
39693936
if (isBoundsSpec()) {
39703937
assert(lbounds.has_value());
39713938
auto lbs = *lbounds;

flang/lib/Lower/ConvertExprToHLFIR.cpp

Lines changed: 36 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,37 +1367,7 @@ struct UnaryOp<
13671367
hlfir::Entity lhs) {
13681368
if constexpr (TC1 == Fortran::common::TypeCategory::Character &&
13691369
TC2 == TC1) {
1370-
auto kindMap = builder.getKindMap();
1371-
mlir::Type fromTy = lhs.getFortranElementType();
1372-
mlir::Value origBufferSize = genCharLength(loc, builder, lhs);
1373-
mlir::Value bufferSize{origBufferSize};
1374-
auto fromBits = kindMap.getCharacterBitsize(
1375-
fir::unwrapRefType(fromTy).cast<fir::CharacterType>().getFKind());
1376-
mlir::Type toTy = Fortran::lower::getFIRType(
1377-
builder.getContext(), TC1, KIND, /*params=*/std::nullopt);
1378-
auto toBits = kindMap.getCharacterBitsize(
1379-
toTy.cast<fir::CharacterType>().getFKind());
1380-
if (toBits < fromBits) {
1381-
// Scale by relative ratio to give a buffer of the same length.
1382-
auto ratio = builder.createIntegerConstant(loc, bufferSize.getType(),
1383-
fromBits / toBits);
1384-
bufferSize =
1385-
builder.create<mlir::arith::MulIOp>(loc, bufferSize, ratio);
1386-
}
1387-
// allocate space on the stack for toBuffer
1388-
auto dest = builder.create<fir::AllocaOp>(loc, toTy,
1389-
mlir::ValueRange{bufferSize});
1390-
auto src = hlfir::convertToAddress(loc, builder, lhs,
1391-
lhs.getFortranElementType());
1392-
builder.create<fir::CharConvertOp>(loc, src.first.getCharBox()->getAddr(),
1393-
origBufferSize, dest);
1394-
if (src.second.has_value())
1395-
src.second.value()();
1396-
1397-
return hlfir::EntityWithAttributes{builder.create<hlfir::DeclareOp>(
1398-
loc, dest, "ctor.temp", /*shape=*/nullptr,
1399-
/*typeparams=*/mlir::ValueRange{origBufferSize},
1400-
fir::FortranVariableFlagsAttr{})};
1370+
return hlfir::convertCharacterKind(loc, builder, lhs, KIND);
14011371
}
14021372
mlir::Type type = Fortran::lower::getFIRType(builder.getContext(), TC1,
14031373
KIND, /*params=*/std::nullopt);
@@ -1789,7 +1759,7 @@ class HlfirBuilder {
17891759
// If it is allocatable, then using AssignOp for unallocated RHS
17901760
// will cause illegal dereference. When an unallocated allocatable
17911761
// value is used to construct an allocatable component, the component
1792-
// must just stay unallocated.
1762+
// must just stay unallocated (see Fortran 2018 7.5.10 point 7).
17931763

17941764
// If the component is allocatable and RHS is NULL() expression, then
17951765
// we can just skip it: the LHS must remain unallocated with its
@@ -1798,56 +1768,44 @@ class HlfirBuilder {
17981768
Fortran::evaluate::UnwrapExpr<Fortran::evaluate::NullPointer>(expr))
17991769
continue;
18001770

1771+
bool keepLhsLength = false;
1772+
if (allowRealloc)
1773+
if (const Fortran::semantics::DeclTypeSpec *declType = sym.GetType())
1774+
keepLhsLength =
1775+
declType->category() ==
1776+
Fortran::semantics::DeclTypeSpec::Category::Character &&
1777+
!declType->characterTypeSpec().length().isDeferred();
18011778
// Handle special case when the initializer expression is
18021779
// '{%SET_LENGTH(x,const_kind)}'. In structure constructor,
1803-
// SET_LENGTH is used for initializers of character allocatable
1804-
// components with *explicit* length, because they have to keep
1805-
// their length regardless of the initializer expression's length.
1806-
// We cannot just lower SET_LENGTH into hlfir.set_length in case
1807-
// when 'x' is allocatable: if 'x' is unallocated, it is not clear
1808-
// what hlfir.expr should be produced by hlfir.set_length.
1809-
// So whenever the initializer expression is SET_LENGTH we
1810-
// recognize it as the directive to keep the explicit length
1811-
// of the LHS component, and we completely ignore 'const_kind'
1812-
// operand assuming that it matches the LHS component's explicit
1813-
// length. Note that in case when LHS component has deferred length,
1814-
// the FE does not produce SET_LENGTH expression.
1815-
//
1816-
// When SET_LENGTH is recognized, we use 'x' as the initializer
1817-
// for the LHS component. If 'x' is allocatable, the dynamic
1818-
// isAllocated check will guard the assign operation as usual.
1819-
bool keepLhsLength = false;
1820-
hlfir::Entity rhs = std::visit(
1821-
[&](const auto &x) -> hlfir::Entity {
1822-
using T = std::decay_t<decltype(x)>;
1823-
if constexpr (Fortran::common::HasMember<
1824-
T, Fortran::lower::CategoryExpression>) {
1825-
if constexpr (T::Result::category ==
1826-
Fortran::common::TypeCategory::Character) {
1827-
return std::visit(
1828-
[&](const auto &someKind) -> hlfir::Entity {
1829-
using T = std::decay_t<decltype(someKind)>;
1830-
if (const auto *setLength = std::get_if<
1831-
Fortran::evaluate::SetLength<T::Result::kind>>(
1832-
&someKind.u)) {
1833-
keepLhsLength = true;
1834-
return gen(setLength->left());
1835-
}
1836-
1837-
return gen(someKind);
1838-
},
1839-
x.u);
1840-
}
1841-
}
1842-
return gen(x);
1843-
},
1844-
expr.u);
1845-
1846-
if (!allowRealloc || !rhs.isMutableBox()) {
1780+
// SET_LENGTH is used for initializers of non-allocatable character
1781+
// components so that the front-end can better
1782+
// fold and work with these structure constructors.
1783+
// Here, they are just noise since the assignment semantics will deal
1784+
// with any length mismatch, and creating an extra temp with the lhs
1785+
// length is useless.
1786+
// TODO: should this be moved into an hlfir.assign + hlfir.set_length
1787+
// pattern rewrite?
1788+
hlfir::Entity rhs = gen(expr);
1789+
if (auto set_length = rhs.getDefiningOp<hlfir::SetLengthOp>())
1790+
rhs = hlfir::Entity{set_length.getString()};
1791+
1792+
// lambda to generate `lhs = rhs` and deal with potential rhs implicit
1793+
// cast
1794+
auto genAssign = [&] {
18471795
rhs = hlfir::loadTrivialScalar(loc, builder, rhs);
1848-
builder.create<hlfir::AssignOp>(loc, rhs, lhs, allowRealloc,
1796+
auto rhsCastAndCleanup =
1797+
hlfir::genTypeAndKindConvert(loc, builder, rhs, lhs.getType(),
1798+
/*preserveLowerBounds=*/allowRealloc);
1799+
builder.create<hlfir::AssignOp>(loc, rhsCastAndCleanup.first, lhs,
1800+
allowRealloc,
18491801
allowRealloc ? keepLhsLength : false,
18501802
/*temporary_lhs=*/true);
1803+
if (rhsCastAndCleanup.second)
1804+
(*rhsCastAndCleanup.second)();
1805+
};
1806+
1807+
if (!allowRealloc || !rhs.isMutableBox()) {
1808+
genAssign();
18511809
continue;
18521810
}
18531811

@@ -1860,14 +1818,7 @@ class HlfirBuilder {
18601818
"to mutable box");
18611819
mlir::Value isAlloc =
18621820
fir::factory::genIsAllocatedOrAssociatedTest(builder, loc, *fromBox);
1863-
builder.genIfThen(loc, isAlloc)
1864-
.genThen([&]() {
1865-
rhs = hlfir::loadTrivialScalar(loc, builder, rhs);
1866-
builder.create<hlfir::AssignOp>(loc, rhs, lhs, allowRealloc,
1867-
keepLhsLength,
1868-
/*temporary_lhs=*/true);
1869-
})
1870-
.end();
1821+
builder.genIfThen(loc, isAlloc).genThen(genAssign).end();
18711822
}
18721823

18731824
return varOp;

flang/lib/Optimizer/Builder/Character.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,3 +851,42 @@ fir::CharBoxValue fir::factory::CharacterExprHelper::createCharExtremum(
851851
createAssign(toBuf, fromBuf);
852852
return temp;
853853
}
854+
855+
fir::CharBoxValue
856+
fir::factory::convertCharacterKind(fir::FirOpBuilder &builder,
857+
mlir::Location loc,
858+
fir::CharBoxValue srcBoxChar, int toKind) {
859+
// Use char_convert. Each code point is translated from a
860+
// narrower/wider encoding to the target encoding. For example, 'A'
861+
// may be translated from 0x41 : i8 to 0x0041 : i16. The symbol
862+
// for euro (0x20AC : i16) may be translated from a wide character
863+
// to "0xE2 0x82 0xAC" : UTF-8.
864+
mlir::Value bufferSize = srcBoxChar.getLen();
865+
auto kindMap = builder.getKindMap();
866+
mlir::Value boxCharAddr = srcBoxChar.getAddr();
867+
auto fromTy = boxCharAddr.getType();
868+
if (auto charTy = fromTy.dyn_cast<fir::CharacterType>()) {
869+
// boxchar is a value, not a variable. Turn it into a temporary.
870+
// As a value, it ought to have a constant LEN value.
871+
assert(charTy.hasConstantLen() && "must have constant length");
872+
mlir::Value tmp = builder.createTemporary(loc, charTy);
873+
builder.create<fir::StoreOp>(loc, boxCharAddr, tmp);
874+
boxCharAddr = tmp;
875+
}
876+
auto fromBits = kindMap.getCharacterBitsize(
877+
fir::unwrapRefType(fromTy).cast<fir::CharacterType>().getFKind());
878+
auto toBits = kindMap.getCharacterBitsize(toKind);
879+
if (toBits < fromBits) {
880+
// Scale by relative ratio to give a buffer of the same length.
881+
auto ratio = builder.createIntegerConstant(loc, bufferSize.getType(),
882+
fromBits / toBits);
883+
bufferSize = builder.create<mlir::arith::MulIOp>(loc, bufferSize, ratio);
884+
}
885+
mlir::Type toType =
886+
fir::CharacterType::getUnknownLen(builder.getContext(), toKind);
887+
auto dest = builder.createTemporary(loc, toType, /*name=*/{}, /*shape=*/{},
888+
mlir::ValueRange{bufferSize});
889+
builder.create<fir::CharConvertOp>(loc, boxCharAddr, srcBoxChar.getLen(),
890+
dest);
891+
return fir::CharBoxValue{dest, srcBoxChar.getLen()};
892+
}

0 commit comments

Comments
 (0)