================
@@ -1271,6 +1274,483 @@ getSafeRepackAttrs(Fortran::lower::AbstractConverter
&converter) {
return attrs.empty() ? mlir::ArrayAttr{} : builder.getArrayAttr(attrs);
}
+//===----------------------------------------------------------------------===//
+// -finit-local= helpers
+//===----------------------------------------------------------------------===//
+
+/// Returns true if \p derived or any of its components (recursively) is a
+/// PowerPC vector type. fir::VectorType does not implement
+/// DataLayoutTypeInterface. Two pre-fix failure modes existed:
+/// - Direct vector local: silently initialized to zero regardless of mode
+/// (historical behavior); at the current head genByteSplatInit would
+/// hit llvm_unreachable instead.
+/// - Derived-type local with a vector component: crashed in record-size
+/// calculation when DataLayoutTypeInterface was queried.
+/// Excluding both cases at eligibility time avoids both failure modes.
+static bool
+containsVectorComponent(const Fortran::semantics::DerivedTypeSpec &derived) {
+ if (derived.IsVectorType())
+ return true;
+ const Fortran::semantics::Scope *scope = derived.GetScope();
+ if (!scope)
+ return false;
+ const Fortran::semantics::Symbol &typeSym = derived.typeSymbol();
+ const auto *details =
+ typeSym.detailsIf<Fortran::semantics::DerivedTypeDetails>();
+ if (!details)
+ return false;
+ for (const Fortran::semantics::SourceName &compName :
+ details->componentNames()) {
+ auto it = scope->find(compName);
+ if (it == scope->cend())
+ continue;
+ const Fortran::semantics::Symbol &comp = it->second.get();
+ if (const Fortran::semantics::DeclTypeSpec *compTy = comp.GetType())
+ if (const Fortran::semantics::DerivedTypeSpec *compDerived =
+ compTy->AsDerived())
+ if (containsVectorComponent(*compDerived))
+ return true;
+ }
+ return false;
+}
+
+/// Returns true when \p var is an automatic local variable eligible for
+/// -finit-local= initialization. Excluded: variables without a symbol,
+/// globals, dummy arguments, SAVE'd vars, ALLOCATABLE/POINTER, vars in
+/// an EQUIVALENCE set, vars with explicit or default initialization, and
+/// CUDA variables whose storage is always unreachable by a plain fir.store
+/// (constant, shared, usedevice). The Device case is deferred to genInitLocal
+/// which applies cuf::isCUDADeviceContext to distinguish cuf.alloc from
+/// fir.alloca storage.
+static bool shouldInitLocal(const Fortran::lower::pft::Variable &var) {
+ if (!var.hasSymbol() || var.isGlobal())
+ return false;
+ const Fortran::semantics::Symbol &sym = var.getSymbol();
+ if (Fortran::semantics::IsDummy(sym))
+ return false;
+ // Function result variables own the return-value storage and must not be
+ // pre-initialized: the function body is responsible for setting the result.
+ if (sym.IsFuncResult())
+ return false;
+ // Main-program locals have implicit SAVE semantics (Fortran 2018 8.5.16p4).
+ // IsSaved() does not catch this case because the SAVE attribute is implicit
+ // rather than explicit, so check the enclosing scope kind directly.
+ if (sym.owner().kind() == Fortran::semantics::Scope::Kind::MainProgram)
+ return false;
+ if (Fortran::semantics::IsSaved(sym))
+ return false;
+ if (Fortran::semantics::IsAllocatableOrPointer(sym))
+ return false;
+ if (Fortran::lower::hasDefaultInitialization(sym))
+ return false;
+ if (const auto *obj =
+ sym.detailsIf<Fortran::semantics::ObjectEntityDetails>())
+ if (obj->init())
+ return false;
+ if (Fortran::semantics::FindEquivalenceSet(sym))
+ return false;
+ // Cray pointees own no storage of their own; their FIR base is a
+ // pointer-box descriptor. Initializing it would overwrite the
+ // descriptor, not the pointee storage.
+ if (sym.test(Fortran::semantics::Symbol::Flag::CrayPointee))
+ return false;
+ // PowerPC vector types (vector(real(4)) etc.) lower to fir::VectorType
+ // which does not implement DataLayoutTypeInterface at the HLFIR level.
+ // Without this guard:
+ // - A direct vector local would hit llvm_unreachable in genByteSplatInit
+ // (historically it silently fell back to zero before that assert was
+ // added).
+ // - A derived-type local with a vector component would crash in
+ // record-size calculation when DataLayoutTypeInterface was queried.
+ // Exclude both cases by walking components recursively.
+ if (const Fortran::semantics::DeclTypeSpec *declTy = sym.GetType())
+ if (const Fortran::semantics::DerivedTypeSpec *derived =
+ declTy->AsDerived())
+ if (containsVectorComponent(*derived))
+ return false;
+ // CUDA storage accessibility:
+ // constant / shared / usedevice: always unreachable by a plain fir.store
+ // from the host -- skip.
+ // device: the allocation choice (cuf.alloc vs fir.alloca) depends on
+ // whether the insertion point is in a device context; that check
+ // requires the MLIR builder and is deferred to genInitLocal, which
+ // calls cuf::isCUDADeviceContext(builder.getRegion()) after this
+ // predicate returns true.
+ // managed / unified / pinned: host-accessible unified memory --
initialize.
+ if (auto cudaAttr = Fortran::semantics::GetCUDADataAttr(&sym)) {
+ switch (*cudaAttr) {
+ case Fortran::common::CUDADataAttr::Constant:
+ case Fortran::common::CUDADataAttr::Shared:
+ case Fortran::common::CUDADataAttr::UseDevice:
+ return false;
+ default:
+ break;
+ }
+ }
+ return true;
+}
+
+/// Build a constant whose every byte equals \p bytePat.
+/// Handles: integer, float (bitcast from integer splat), complex (both parts),
+/// and logical (raw integer, stored via bitcasted address by the caller).
+/// Character, derived-type, and sequence types are all intercepted by
+/// genInitLocalStore or initAddr before this function is called and must
+/// not reach it. fir::VectorType (PowerPC vector types, direct or as a
+/// derived-type component) is excluded upstream by shouldInitLocal via
+/// containsVectorComponent and will never reach this function.
+static mlir::Value genByteSplatInit(fir::FirOpBuilder &builder,
+ mlir::Location loc, mlir::Type ty,
+ uint8_t bytePat) {
+ mlir::Type eleTy = fir::unwrapSequenceType(ty);
+
+ // Build a signless integer constant from a byte splat. arith.constant
+ // requires a signless integer type; callers that need a non-signless result
+ // (e.g. unsigned ui32) must fir.convert the returned value themselves.
+ auto makeIntCst = [&](unsigned bits) -> mlir::Value {
+ llvm::APInt byteVal(8, bytePat);
+ llvm::APInt splat = llvm::APInt::getSplat(bits, byteVal);
+ mlir::Type intTy = builder.getIntegerType(bits);
+ return mlir::arith::ConstantOp::create(
+ builder, loc, intTy, builder.getIntegerAttr(intTy, splat));
+ };
+
+ if (auto fpTy = mlir::dyn_cast<mlir::FloatType>(eleTy)) {
+ mlir::Value intCst = makeIntCst(fpTy.getWidth());
+ return mlir::arith::BitcastOp::create(builder, loc, fpTy, intCst);
+ }
+ if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(eleTy)) {
+ mlir::Value cst = makeIntCst(intTy.getWidth());
+ // arith.constant only supports signless integers; fir.convert reinterprets
+ // the bit pattern into the declared signed or unsigned type without
+ // changing any bits, satisfying FIR verification for !fir.ref<ui32> etc.
+ if (!intTy.isSignless())
+ cst = builder.createConvert(loc, intTy, cst);
+ return cst;
+ }
+ // Complex: apply the byte pattern to each (real, imag) part.
+ if (auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(eleTy)) {
+ mlir::Type partTy = cplxTy.getElementType();
+ mlir::Value partVal = genByteSplatInit(builder, loc, partTy, bytePat);
+ return mlir::complex::CreateOp::create(builder, loc, cplxTy, partVal,
+ partVal);
+ }
+ // LOGICAL(k) has a fixed size of k bytes under the default kind mapping,
+ // but a non-default mapping (e.g. --kind-mapping=l4:8) may map LOGICAL(4)
+ // to a single byte. Use KindMapping::getLogicalBitsize so the constant
+ // width matches the actual allocation size.
+ // The caller stores it via a bitcasted address to preserve the bit pattern
+ // (fir.convert from integer to !fir.logical normalizes nonzero -> true).
+ // Sub-byte, non-byte-multiple, and padded LOGICAL mappings are not
supported:
+ // - sub-byte (e.g. l4:1): APInt::getSplat requires destination width >= 8.
+ // - non-byte-multiple (e.g. l4:12): makeIntCst(12) builds an i12 splat of
+ // 0xAA -> 0xAAA, which occupies bytes AA 0A rather than AA AA -- the
+ // high nibble of the second byte is not filled by the byte pattern.
+ // - padded (e.g. l4:24): allocSize (4) > storeSize (3) leaves the trailing
+ // allocation byte uninitialized.
+ if (auto logTy = mlir::dyn_cast<fir::LogicalType>(eleTy)) {
+ unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
+ const mlir::DataLayout &dl = builder.getDataLayout();
+ mlir::Type intTy = builder.getIntegerType(bits);
+ uint64_t storeSize = dl.getTypeSize(intTy);
+ uint64_t allocSize =
+ llvm::alignTo(storeSize, dl.getTypeABIAlignment(intTy));
+ if (bits < 8 || bits % 8 != 0 || allocSize > storeSize)
+ TODO(loc, "-finit-local= with a sub-byte, non-byte-multiple, or padded "
+ "LOGICAL kind mapping");
+ return makeIntCst(bits);
+ }
+ // All types that pass shouldInitLocal and reach genInitLocalStore are
+ // handled explicitly above (integer, float, complex, logical) or are
+ // intercepted before this call (character, record, sequence).
+ // PowerPC vector types (direct or as a derived-type component) are excluded
+ // by shouldInitLocal via containsVectorComponent and never reach here.
+ // A silent zero for an unhandled type would violate the hex-mode contract,
+ // so assert rather than fall back silently.
+ llvm_unreachable("genByteSplatInit: unhandled type in hex mode");
+}
+
+/// Emit a byte-fill loop over [0, nBytes - 1] at \p addr.
+/// Uses an i8 sequence so fir.coordinate_of strides by exactly 1 byte.
+/// Preserves volatility of \p addr so that stores into volatile variables
+/// are emitted as "store volatile".
+static void emitByteLoop(fir::FirOpBuilder &builder, mlir::Location loc,
+ mlir::Value addr, uint64_t nBytes,
+ Fortran::lower::InitLocalKind mode, uint8_t hexByte) {
+ if (nBytes == 0)
+ return;
+ mlir::Type idxTy = builder.getIndexType();
+ mlir::Type i8Ty = builder.getIntegerType(8);
+ mlir::Type i8SeqTy =
+ fir::SequenceType::get({fir::SequenceType::getUnknownExtent()}, i8Ty);
+ bool addrVolatile = fir::isa_volatile_type(addr.getType());
+ mlir::Value byteBase = builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(i8SeqTy, addrVolatile), addr);
+ mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+ mlir::Value last = builder.createIntegerConstant(
+ loc, idxTy, static_cast<int64_t>(nBytes) - 1);
+ mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+ auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
+ /*unordered=*/false,
+ /*finalCount=*/false);
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPointToStart(loop.getBody());
+ mlir::Value iv = loop.getInductionVar();
+ bool byteVolatile = fir::isa_volatile_type(byteBase.getType());
+ mlir::Value byteAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(i8Ty, byteVolatile), byteBase,
+ mlir::ValueRange{iv});
+ int64_t fillByte =
+ (mode == Fortran::lower::InitLocalKind::Zero) ? 0 : hexByte;
+ mlir::Value pat = builder.createIntegerConstant(loc, i8Ty, fillByte);
+ fir::StoreOp::create(builder, loc, pat, byteAddr);
+}
+
+/// Emit initialization for a single scalar address \p addr of type \p ty.
+/// If the allocation size exceeds the typed store size (or for CHARACTER where
+/// kind mappings or string length require byte coverage), an
allocation-derived
+/// byte-fill loop is used. Otherwise, a single typed store is emitted.
+static void genInitLocalStore(fir::FirOpBuilder &builder, mlir::Location loc,
+ mlir::Type ty, mlir::Value addr,
+ Fortran::lower::InitLocalKind mode,
+ uint8_t hexByte) {
+ // Fixed-length CHARACTER: byte-loop over the full allocation stride.
+ if (auto charTy = mlir::dyn_cast<fir::CharacterType>(ty)) {
+ if (charTy.getLen() == 0)
+ return;
+ int64_t nUnits = charTy.hasConstantLen() ? charTy.getLen() : 0;
+ unsigned charBits =
+ builder.getKindMap().getCharacterBitsize(charTy.getFKind());
+ unsigned charByteWidth = std::max(1u, (charBits + 7) / 8);
+ const mlir::DataLayout &charDL = builder.getDataLayout();
+ mlir::Type charElemTy = builder.getIntegerType(charBits);
+ int64_t kindBytes = static_cast<int64_t>(llvm::alignTo(
+ static_cast<uint64_t>(charByteWidth),
+ static_cast<uint64_t>(charDL.getTypeABIAlignment(charElemTy))));
+ emitByteLoop(builder, loc, addr, nUnits * kindBytes, mode, hexByte);
+ return;
+ }
+
+ // REAL and COMPLEX: when allocation size exceeds store size (e.g. x86_fp80),
+ // use the allocation-derived byte loop to cover padding bytes.
+ if (mlir::isa<mlir::FloatType, mlir::ComplexType>(ty)) {
+ const mlir::DataLayout &dl = builder.getDataLayout();
+ uint64_t storeSize = dl.getTypeSize(ty);
+ uint64_t allocSize = llvm::alignTo(storeSize, dl.getTypeABIAlignment(ty));
+ if (allocSize > storeSize) {
+ emitByteLoop(builder, loc, addr, allocSize, mode, hexByte);
+ return;
+ }
+ }
+
+ mlir::Value val;
+ switch (mode) {
+ case Fortran::lower::InitLocalKind::Zero:
+ val = fir::ZeroOp::create(builder, loc, ty);
+ break;
+ case Fortran::lower::InitLocalKind::Hex:
+ val = genByteSplatInit(builder, loc, ty, hexByte);
+ break;
+ default:
+ llvm_unreachable("unexpected InitLocalKind in genInitLocalStore");
+ }
+ // For LOGICAL in hex mode, genByteSplatInit returns a raw integer to
+ // preserve the bit pattern. Store it via a bitcasted address to avoid
+ // fir.convert normalization (which would reduce any nonzero value to
+ // logical true).
+ if (mode == Fortran::lower::InitLocalKind::Hex &&
+ mlir::isa<fir::LogicalType>(ty)) {
+ auto logTy = mlir::cast<fir::LogicalType>(ty);
+ unsigned bits = builder.getKindMap().getLogicalBitsize(logTy.getFKind());
+ bool logVolatile = fir::isa_volatile_type(addr.getType());
+ mlir::Type intRefTy =
+ builder.getRefType(builder.getIntegerType(bits), logVolatile);
+ mlir::Value intAddr =
+ builder.createConvertWithVolatileCast(loc, intRefTy, addr);
+ fir::StoreOp::create(builder, loc, val, intAddr);
+ } else {
+ fir::StoreOp::create(builder, loc, val, addr);
+ }
+}
+
+/// Initialize all storage of the local variable \p var per -finit-local= mode.
+/// Arrays: all modes use a flat fir.do_loop + fir.coordinate_of over a
+/// rank-1 view to avoid both the llvm.mlir.constant crash on non-zero
+/// ArrayAttrs and the quadratic compile time of fir.insert_on_range.
+/// Derived types use a byte-fill loop. All byte-view and coordinate types
+/// carry the source address volatility so that stores into volatile variables
+/// are emitted as "store volatile" end-to-end.
+/// PowerPC vector types (direct or as a derived-type component) are excluded
+/// upstream by shouldInitLocal via
+/// containsVectorComponent and never reach this function.
+/// Scalars store directly via genInitLocalStore.
+static void genInitLocal(Fortran::lower::AbstractConverter &converter,
+ const Fortran::lower::pft::Variable &var,
+ Fortran::lower::SymMap &symMap) {
+ Fortran::lower::InitLocalKind mode =
+ converter.getLoweringOptions().getInitLocalMode();
+ if (mode == Fortran::lower::InitLocalKind::Off)
+ return;
+ if (!shouldInitLocal(var))
+ return;
+
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ mlir::Location loc = converter.getCurrentLocation();
+ uint8_t hexByte = converter.getLoweringOptions().getInitLocalPattern();
+
+ // Mirror the allocation decision: a CUDA Device variable is allocated via
+ // cuf.alloc when cuf::isCUDADeviceContext is false (host or host_device
+ // subprograms), and via fir.alloca when it is true (device/global kernels,
+ // including BLOCK-construct locals inside such kernels). Only the
fir.alloca
+ // path is reachable by a plain fir.store, so skip initialization if the
+ // variable has a Device attribute but the current region is not a device
+ // context (which would mean the storage is a cuf.alloc).
+ if (auto cudaAttr = Fortran::semantics::GetCUDADataAttr(&var.getSymbol()))
+ if (*cudaAttr == Fortran::common::CUDADataAttr::Device &&
+ !cuf::isCUDADeviceContext(builder.getRegion()))
+ return;
+
+ fir::ExtendedValue exv =
+ converter.getSymbolExtendedValue(var.getSymbol(), &symMap);
+ mlir::Value base = fir::getBase(exv);
+ mlir::Type storeTy = fir::unwrapRefType(base.getType());
+
+ // Recursive helper: dispatch on type to initialize the storage at \p addr
+ // of type \p ty. Handles arrays, derived types, and scalars.
+ std::function<void(mlir::Type, mlir::Value)> initAddr =
+ [&](mlir::Type ty, mlir::Value addr) {
+ if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(ty)) {
+ // Array: use a flat fir.do_loop over all elements. Cast to a
+ // rank-1 unknown-extent ref so the flat IV is a valid single-
+ // coordinate index regardless of array rank. This avoids both
+ // the LLVM lowering crash (non-zero ArrayAttr) and the quadratic
+ // compile time of fir.insert_on_range for large arrays.
+ mlir::Type eleTy = seqTy.getEleTy();
+ // Skip arrays with unknown or zero extents, and CHARACTER arrays
+ // (known TODO, pending PR #159788).
+ bool hasUnknown = mlir::isa<fir::CharacterType>(eleTy);
+ int64_t totalElems = 1;
+ for (auto dim : seqTy.getShape()) {
+ if (dim == fir::SequenceType::getUnknownExtent() || dim == 0) {
+ hasUnknown = true;
+ break;
+ }
+ totalElems *= dim;
+ }
+ if (!hasUnknown) {
+ mlir::Type idxTy = builder.getIndexType();
+ mlir::Type rank1SeqTy = fir::SequenceType::get(
+ {fir::SequenceType::getUnknownExtent()}, eleTy);
+ bool rank1Volatile = fir::isa_volatile_type(addr.getType());
+ mlir::Value rank1Addr = builder.createConvertWithVolatileCast(
+ loc, builder.getRefType(rank1SeqTy, rank1Volatile), addr);
+ mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+ mlir::Value last =
+ builder.createIntegerConstant(loc, idxTy, totalElems - 1);
+ mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+ auto loop = fir::DoLoopOp::create(builder, loc, zero, last, one,
+ /*unordered=*/false,
+ /*finalCount=*/false);
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPointToStart(loop.getBody());
+ mlir::Value iv = loop.getInductionVar();
+ bool elemVolatile = fir::isa_volatile_type(rank1Addr.getType());
+ mlir::Value elemAddr = fir::CoordinateOp::create(
+ builder, loc, builder.getRefType(eleTy, elemVolatile),
+ rank1Addr, mlir::ValueRange{iv});
+ initAddr(eleTy, elemAddr);
+ }
+ } else if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
+ // Byte-fill the full allocation (fields + internal padding +
+ // tail padding). A typed fir.zero_bits store would leave tail
+ // padding as 'undef', which LLVM may not zero at -O2.
+ auto [byteSize, _align] = fir::getTypeSizeAndAlignmentOrCrash(
+ loc, recTy, builder.getDataLayout(), builder.getKindMap());
+ emitByteLoop(builder, loc, addr, byteSize, mode, hexByte);
----------------
MattPD wrote:
Both hex and zero modes still fill only 12 bytes of this 16-byte record under
`a1:24`, leaving the fourth character uninitialized:
```fortran
subroutine record_char4(res)
type :: t
character(kind=1, len=4) :: c
end type
type(t), volatile :: x
integer :: res
res = ichar(x%c(4:4))
end subroutine
```
Run `flang -fc1 -emit-llvm -mmlir --kind-mapping=a1:24 -finit-local=0xAA
repro.f90 -o -`. The loop fills 12 bytes of `{ [4 x i24] }`. A direct
`character(4)` local gets all 16 bytes.
The existing CHARACTER size calculation in `FIRType.cpp` multiplies store size
by length. This gap predates the loop extraction. Could the sizing helper use
each code unit's allocation stride?
https://github.com/llvm/llvm-project/pull/216164
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits