================
@@ -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);
+        } else if (!mlir::isa<fir::BaseBoxType>(ty)) {
+          // Scalar (integer, real, complex, logical, character): delegate to
+          // genInitLocalStore, which handles each type and mode combination.
+          // Skip FIR box types (e.g. a Cray pointee descriptor) that do not
+          // represent initializable value storage.
+          genInitLocalStore(builder, loc, ty, addr, mode, hexByte);
+        }
+      };
+
+  // Runtime-length CHARACTER: emit a byte-by-byte fir.do_loop guarded by
+  // the runtime length so we neither skip bytes (the old single-store
+  // behaviour) nor write through a zero-byte allocation when len == 0.
+  // Both HLFIR and non-HLFIR paths store a CharBoxValue in the symMap for
+  // a scalar character(n) local, so fir::getLen(exv) always returns the
+  // runtime length when the character type has dynamic length.
+  auto getRtCharLen = [&]() -> mlir::Value { return fir::getLen(exv); };
+
+  // Only handle the dynamic-length case here; fixed-length falls through to
+  // initAddr which calls genInitLocalStore directly.
+  if (auto charTy = mlir::dyn_cast<fir::CharacterType>(storeTy);
+      charTy && charTy.hasDynamicLen()) {
+    if (mlir::Value rtLen = getRtCharLen()) {
+      mlir::Type idxTy = builder.getIndexType();
+      mlir::Value lenIdx = builder.createConvert(loc, idxTy, rtLen);
+      mlir::Value zero = builder.createIntegerConstant(loc, idxTy, 0);
+      mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+      // This stride computation applies to all modes (zero and hex), not just
+      // hex.  Unlike the fixed-length path -- where zero mode emits a single
+      // fir.zero_bits over the whole !fir.char<k,n> object -- the runtime-
----------------
MattPD wrote:

Would you be open to dropping the file-by-file Implementation and Tests tables 
from the PR description? The message already provides useful feature-level 
context: the option's behavior, padding rationale, support limits, and RFC link.

For example, the `flang/test/Driver/finit-local.f90` entry says "Driver-level 
pass-through checks", but the file already explains that purpose in its opening 
comment. Keeping test-specific intent and assumptions beside the test helps the 
maintainer changing it, without making them search old PRs for basic context.

The inventories can help during review, but they give you a second description 
to keep synchronized as the patch evolves. Some source and test comments have 
already gone stale. Fixed-length CHARACTER zero initialization now uses a byte 
loop, not the aggregate `fir.zero_bits` store still described. The allocated 
LLVM element types also retain their mapped widths rather than widening as 
described.

I'd drop the file inventories and the outdated implementation descriptions in 
the source and test comments, while keeping the Accepted values table, the 
existing rationale, and support boundaries. That follows [LLVM's commit-message 
guidance](https://llvm.org/docs/DeveloperPolicy.html#commit-messages), which 
emphasizes why and useful context. The RFC can provide further design 
background without making the squash message another account of the diff.

https://github.com/llvm/llvm-project/pull/216164
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to