https://github.com/AmrDeveloper created 
https://github.com/llvm/llvm-project/pull/137233

This change adds support for the variable-length arrays

Issue https://github.com/llvm/llvm-project/issues/130197

>From b86553a6f9a087c1c064359726e649942f1499d0 Mon Sep 17 00:00:00 2001
From: AmrDeveloper <am...@programmer.net>
Date: Wed, 23 Apr 2025 18:57:35 +0200
Subject: [PATCH] [CIR] Upstream support for Variable size Array

---
 .../CIR/Dialect/Builder/CIRBaseBuilder.h      |  17 ++
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  17 +-
 clang/include/clang/CIR/MissingFeatures.h     |   1 +
 clang/lib/CIR/CodeGen/CIRGenDecl.cpp          |  32 ++--
 clang/lib/CIR/CodeGen/CIRGenExpr.cpp          |  98 +++++++++-
 clang/lib/CIR/CodeGen/CIRGenFunction.cpp      | 175 ++++++++++++++++++
 clang/lib/CIR/CodeGen/CIRGenFunction.h        |  38 ++++
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        |   6 +
 clang/lib/CIR/CodeGen/CIRGenTypeCache.h       |   5 +
 clang/lib/CIR/CodeGen/CIRGenTypes.cpp         |  10 +
 .../lib/CIR/Dialect/Transforms/FlattenCFG.cpp |  53 +++++-
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp |   9 +-
 clang/test/CIR/CodeGen/struct.c               |  39 ++--
 clang/test/CIR/CodeGen/struct.cpp             |  19 +-
 clang/test/CIR/CodeGen/typedef.c              |  10 +-
 clang/test/CIR/CodeGen/union.c                |  20 +-
 clang/test/CIR/CodeGen/vla.cpp                |  71 +++++++
 clang/test/CIR/Lowering/local-vars.cpp        |  18 +-
 18 files changed, 556 insertions(+), 82 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/vla.cpp

diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h 
b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index ef29791ed2783..f0089525136ce 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -175,6 +175,23 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
     return create<cir::AllocaOp>(loc, addrType, type, name, alignment);
   }
 
+  mlir::Value createAlloca(mlir::Location loc, cir::PointerType addrType,
+                           mlir::Type type, llvm::StringRef name,
+                           mlir::IntegerAttr alignment,
+                           mlir::Value dynAllocSize) {
+    return create<cir::AllocaOp>(loc, addrType, type, name, alignment,
+                                 dynAllocSize);
+  }
+
+  mlir::Value createAlloca(mlir::Location loc, cir::PointerType addrType,
+                           mlir::Type type, llvm::StringRef name,
+                           clang::CharUnits alignment,
+                           mlir::Value dynAllocSize) {
+    auto alignmentIntAttr = getSizeFromCharUnits(getContext(), alignment);
+    return createAlloca(loc, addrType, type, name, alignmentIntAttr,
+                        dynAllocSize);
+  }
+
   cir::LoadOp createLoad(mlir::Location loc, mlir::Value ptr,
                          bool isVolatile = false, uint64_t alignment = 0) {
     mlir::IntegerAttr intAttr;
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index bb19de31b4fa5..10a2e10b4140c 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -348,6 +348,7 @@ def AllocaOp : CIR_Op<"alloca", [
   }];
 
   let arguments = (ins
+    Optional<PrimitiveInt>:$dynAllocSize,
     TypeAttr:$allocaType,
     StrAttr:$name,
     UnitAttr:$init,
@@ -364,16 +365,30 @@ def AllocaOp : CIR_Op<"alloca", [
     OpBuilder<(ins "mlir::Type":$addr,
                    "mlir::Type":$allocaType,
                    "llvm::StringRef":$name,
-                   "mlir::IntegerAttr":$alignment)>
+                   "mlir::IntegerAttr":$alignment)>,
+
+    OpBuilder<(ins "mlir::Type":$addr,
+                   "mlir::Type":$allocaType,
+                   "llvm::StringRef":$name,
+                   "mlir::IntegerAttr":$alignment,
+                   "mlir::Value":$dynAllocSize),
+    [{
+      if (dynAllocSize)
+        $_state.addOperands(dynAllocSize);
+      build($_builder, $_state, addr, allocaType, name, alignment);
+    }]>
   ];
 
   let extraClassDeclaration = [{
     // Whether the alloca input type is a pointer.
     bool isPointerType() { return 
::mlir::isa<::cir::PointerType>(getAllocaType()); }
+
+    bool isDynamic() { return (bool)getDynAllocSize(); }
   }];
 
   let assemblyFormat = [{
     $allocaType `,` qualified(type($addr)) `,`
+    ($dynAllocSize^ `:` type($dynAllocSize) `,`)?
     `[` $name
        (`,` `init` $init^)?
        (`,` `const` $constant^)?
diff --git a/clang/include/clang/CIR/MissingFeatures.h 
b/clang/include/clang/CIR/MissingFeatures.h
index 6bfc1199aea55..fbee81362dd53 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -155,6 +155,7 @@ struct MissingFeatures {
   static bool setDLLStorageClass() { return false; }
   static bool openMP() { return false; }
   static bool emitCheckedInBoundsGEP() { return false; }
+  static bool sanitizeVLABound() { return false; }
   static bool preservedAccessIndexRegion() { return false; }
   static bool bitfields() { return false; }
   static bool typeChecks() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp 
b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index d7cbb4f64b2ea..ff75bcd8119c6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -25,11 +25,11 @@ using namespace clang::CIRGen;
 
 CIRGenFunction::AutoVarEmission
 CIRGenFunction::emitAutoVarAlloca(const VarDecl &d) {
-  QualType ty = d.getType();
+  const QualType ty = d.getType();
   if (ty.getAddressSpace() != LangAS::Default)
     cgm.errorNYI(d.getSourceRange(), "emitAutoVarAlloca: address space");
 
-  mlir::Location loc = getLoc(d.getSourceRange());
+  const mlir::Location loc = getLoc(d.getSourceRange());
 
   CIRGenFunction::AutoVarEmission emission(d);
   emission.IsEscapingByRef = d.isEscapingByref();
@@ -37,22 +37,28 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d) {
     cgm.errorNYI(d.getSourceRange(),
                  "emitAutoVarDecl: decl escaping by reference");
 
-  CharUnits alignment = getContext().getDeclAlign(&d);
+  const CharUnits alignment = getContext().getDeclAlign(&d);
 
   // If the type is variably-modified, emit all the VLA sizes for it.
   if (ty->isVariablyModifiedType())
-    cgm.errorNYI(d.getSourceRange(), "emitAutoVarDecl: variably modified 
type");
+    emitVariablyModifiedType(ty);
 
   Address address = Address::invalid();
-  if (!ty->isConstantSizeType())
-    cgm.errorNYI(d.getSourceRange(), "emitAutoVarDecl: non-constant size 
type");
-
-  // A normal fixed sized variable becomes an alloca in the entry block,
-  mlir::Type allocaTy = convertTypeForMem(ty);
-  // Create the temp alloca and declare variable using it.
-  address = createTempAlloca(allocaTy, alignment, loc, d.getName(),
-                             /*insertIntoFnEntryBlock=*/false);
-  declare(address.getPointer(), &d, ty, getLoc(d.getSourceRange()), alignment);
+  if (ty->isConstantSizeType()) {
+    // A normal fixed sized variable becomes an alloca in the entry block,
+    const mlir::Type allocaTy = convertTypeForMem(ty);
+    // Create the temp alloca and declare variable using it.
+    address = createTempAlloca(allocaTy, alignment, loc, d.getName(),
+                               /*insertIntoFnEntryBlock=*/false);
+    declare(address.getPointer(), &d, ty, getLoc(d.getSourceRange()),
+            alignment);
+  } else { // not openmp nor constant sized type
+    const VlaSizePair vlaSize = getVLASize(ty);
+    const mlir::Type mlirType = convertTypeForMem(vlaSize.type);
+    Address allocaAddr = Address::invalid();
+    address = createTempAlloca(mlirType, alignment, loc, "vla", 
vlaSize.numElts,
+                               &allocaAddr, builder.saveInsertionPoint());
+  }
 
   emission.Addr = address;
   setAddrOfLocalVar(&d, address);
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 0a518c0fd935d..e72002e0ff005 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -88,9 +88,7 @@ Address CIRGenFunction::emitPointerWithAlignment(const Expr 
*expr,
 
     // Array-to-pointer decay. TODO(cir): BaseInfo and TBAAInfo.
     case CK_ArrayToPointerDecay: {
-      cgm.errorNYI(expr->getSourceRange(),
-                   "emitPointerWithAlignment: array-to-pointer decay");
-      return Address::invalid();
+      return emitArrayToPointerDecay(ce->getSubExpr());
     }
 
     case CK_UncheckedDerivedToBase:
@@ -742,6 +740,41 @@ LValue CIRGenFunction::emitMemberExpr(const MemberExpr *e) 
{
   llvm_unreachable("Unhandled member declaration!");
 }
 
+Address CIRGenFunction::emitArrayToPointerDecay(const Expr *e) {
+  assert(e->getType()->isArrayType() &&
+         "Array to pointer decay must have array source type!");
+
+  // Expressions of array type can't be bitfields or vector elements.
+  const LValue lv = emitLValue(e);
+  const Address addr = lv.getAddress();
+
+  // If the array type was an incomplete type, we need to make sure
+  // the decay ends up being the right type.
+  const cir::PointerType lvalueAddrTy =
+      mlir::dyn_cast<cir::PointerType>(addr.getPointer().getType());
+  assert(lvalueAddrTy && "expected pointer");
+
+  if (e->getType()->isVariableArrayType())
+    return addr;
+
+  const cir::ArrayType pointeeTy =
+      mlir::dyn_cast<cir::ArrayType>(lvalueAddrTy.getPointee());
+  assert(pointeeTy && "expected array");
+
+  const mlir::Type arrayTy = convertType(e->getType());
+  assert(mlir::isa<cir::ArrayType>(arrayTy) && "expected array");
+  assert(pointeeTy == arrayTy);
+
+  const QualType elementType =
+      e->getType()->castAsArrayTypeUnsafe()->getElementType();
+
+  const mlir::Value ptr = cgm.getBuilder().maybeBuildArrayDecay(
+      cgm.getLoc(e->getSourceRange()), addr.getPointer(),
+      convertTypeForMem(elementType));
+
+  return Address(ptr, addr.getAlignment());
+}
+
 LValue CIRGenFunction::emitBinaryOperatorLValue(const BinaryOperator *e) {
   // Comma expressions just emit their LHS then their RHS as an l-value.
   if (e->getOpcode() == BO_Comma) {
@@ -1059,7 +1092,7 @@ mlir::Value CIRGenFunction::emitAlloca(StringRef name, 
mlir::Type ty,
     mlir::OpBuilder::InsertionGuard guard(builder);
     builder.restoreInsertionPoint(ip);
     addr = builder.createAlloca(loc, /*addr type*/ localVarPtrTy,
-                                /*var type*/ ty, name, alignIntAttr);
+                                /*var type*/ ty, name, alignIntAttr, 
arraySize);
     assert(!cir::MissingFeatures::astVarDeclInterface());
   }
   return addr;
@@ -1082,3 +1115,60 @@ Address CIRGenFunction::createTempAlloca(mlir::Type ty, 
CharUnits align,
       emitAlloca(name.str(), ty, loc, align, insertIntoFnEntryBlock);
   return Address(alloca, ty, align);
 }
+
+Address CIRGenFunction::createTempAlloca(mlir::Type ty, CharUnits align,
+                                         mlir::Location loc, const Twine &name,
+                                         mlir::Value arraySize,
+                                         Address *allocaAddr,
+                                         mlir::OpBuilder::InsertPoint ip) {
+  const Address alloca =
+      createTempAllocaWithoutCast(ty, align, loc, name, arraySize, ip);
+
+  if (allocaAddr)
+    *allocaAddr = alloca;
+
+  const mlir::Value v = alloca.getPointer();
+  // Alloca always returns a pointer in alloca address space, which may
+  // be different from the type defined by the language. For example,
+  // in C++ the auto variables are in the default address space. Therefore
+  // cast alloca to the default address space when necessary.
+  // FIXME: Missing features addrspace
+  return Address(v, ty, align);
+}
+
+/// This creates an alloca and inserts it into the entry block if \p ArraySize
+/// is nullptr, otherwise inserts it at the current insertion point of the
+/// builder.
+cir::AllocaOp CIRGenFunction::createTempAlloca(mlir::Type ty,
+                                               mlir::Location loc,
+                                               const Twine &name,
+                                               mlir::Value arraySize,
+                                               bool insertIntoFnEntryBlock) {
+  return cast<cir::AllocaOp>(emitAlloca(name.str(), ty, loc, CharUnits(),
+                                        insertIntoFnEntryBlock, arraySize)
+                                 .getDefiningOp());
+}
+
+/// This creates an alloca and inserts it into the provided insertion point
+cir::AllocaOp CIRGenFunction::createTempAlloca(mlir::Type ty,
+                                               mlir::Location loc,
+                                               const Twine &name,
+                                               mlir::OpBuilder::InsertPoint ip,
+                                               mlir::Value arraySize) {
+  assert(ip.isSet() && "Insertion point is not set");
+  return cast<cir::AllocaOp>(
+      emitAlloca(name.str(), ty, loc, CharUnits(), ip, arraySize)
+          .getDefiningOp());
+}
+
+/// This creates a alloca and inserts it into the entry block of the
+/// current region.
+Address CIRGenFunction::createTempAllocaWithoutCast(
+    mlir::Type ty, CharUnits align, mlir::Location loc, const Twine &name,
+    mlir::Value arraySize, mlir::OpBuilder::InsertPoint ip) {
+  cir::AllocaOp alloca = ip.isSet()
+                             ? createTempAlloca(ty, loc, name, ip, arraySize)
+                             : createTempAlloca(ty, loc, name, arraySize);
+  alloca.setAlignmentAttr(cgm.getSize(align));
+  return Address(alloca, ty, align);
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp 
b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 5412f9f602711..38d6cbdd2fb1e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -567,4 +567,179 @@ void 
CIRGenFunction::emitNullInitialization(mlir::Location loc, Address destPtr,
   builder.createStore(loc, zeroValue, destPtr.getPointer());
 }
 
+CIRGenFunction::VlaSizePair CIRGenFunction::getVLASize(QualType type) {
+  const VariableArrayType *vla =
+      cgm.getASTContext().getAsVariableArrayType(type);
+  assert(vla && "type was not a variable array type!");
+  return getVLASize(vla);
+}
+
+CIRGenFunction::VlaSizePair
+CIRGenFunction::getVLASize(const VariableArrayType *type) {
+  // The number of elements so far; always size_t.
+  mlir::Value numElements;
+
+  QualType elementType;
+  do {
+    elementType = type->getElementType();
+    mlir::Value vlaSize = vlaSizeMap[type->getSizeExpr()];
+    assert(vlaSize && "no size for VLA!");
+    assert(vlaSize.getType() == SizeTy);
+
+    if (!numElements) {
+      numElements = vlaSize;
+    } else {
+      // It's undefined behavior if this wraps around, so mark it that way.
+      // FIXME: Teach -fsanitize=undefined to trap this.
+      numElements =
+          builder.createMul(numElements.getLoc(), numElements, vlaSize);
+    }
+  } while ((type = getContext().getAsVariableArrayType(elementType)));
+
+  assert(numElements && "Undefined elements number");
+  return {numElements, elementType};
+}
+
+// TODO(cir): most part of this function can be shared between CIRGen
+// and traditional LLVM codegen
+void CIRGenFunction::emitVariablyModifiedType(QualType type) {
+  assert(type->isVariablyModifiedType() &&
+         "Must pass variably modified type to EmitVLASizes!");
+
+  // We're going to walk down into the type and look for VLA
+  // expressions.
+  do {
+    assert(type->isVariablyModifiedType());
+
+    const Type *ty = type.getTypePtr();
+    switch (ty->getTypeClass()) {
+    case clang::Type::CountAttributed:
+    case clang::Type::PackIndexing:
+    case clang::Type::ArrayParameter:
+    case clang::Type::HLSLAttributedResource:
+      llvm_unreachable("NYI");
+
+#define TYPE(Class, Base)
+#define ABSTRACT_TYPE(Class, Base)
+#define NON_CANONICAL_TYPE(Class, Base)
+#define DEPENDENT_TYPE(Class, Base) case Type::Class:
+#define NON_CANONICAL_UNLESS_DEPENDENT_TYPE(Class, Base)
+#include "clang/AST/TypeNodes.inc"
+      llvm_unreachable("unexpected dependent type!");
+
+    // These types are never variably-modified.
+    case Type::Builtin:
+    case Type::Complex:
+    case Type::Vector:
+    case Type::ExtVector:
+    case Type::ConstantMatrix:
+    case Type::Record:
+    case Type::Enum:
+    case Type::Using:
+    case Type::TemplateSpecialization:
+    case Type::ObjCTypeParam:
+    case Type::ObjCObject:
+    case Type::ObjCInterface:
+    case Type::ObjCObjectPointer:
+    case Type::BitInt:
+      llvm_unreachable("type class is never variably-modified!");
+
+    case Type::Elaborated:
+      type = cast<clang::ElaboratedType>(ty)->getNamedType();
+      break;
+
+    case Type::Adjusted:
+      type = cast<clang::AdjustedType>(ty)->getAdjustedType();
+      break;
+
+    case Type::Decayed:
+      type = cast<clang::DecayedType>(ty)->getPointeeType();
+      break;
+
+    case Type::Pointer:
+      type = cast<clang::PointerType>(ty)->getPointeeType();
+      break;
+
+    case Type::BlockPointer:
+      type = cast<clang::BlockPointerType>(ty)->getPointeeType();
+      break;
+
+    case Type::LValueReference:
+    case Type::RValueReference:
+      type = cast<clang::ReferenceType>(ty)->getPointeeType();
+      break;
+
+    case Type::MemberPointer:
+      type = cast<clang::MemberPointerType>(ty)->getPointeeType();
+      break;
+
+    case Type::ConstantArray:
+    case Type::IncompleteArray:
+      // Losing element qualification here is fine.
+      type = cast<clang::ArrayType>(ty)->getElementType();
+      break;
+
+    case Type::VariableArray: {
+      // Losing element qualification here is fine.
+      const VariableArrayType *vat = cast<clang::VariableArrayType>(ty);
+
+      // Unknown size indication requires no size computation.
+      // Otherwise, evaluate and record it.
+      if (const Expr *sizeExpr = vat->getSizeExpr()) {
+        // It's possible that we might have emitted this already,
+        // e.g. with a typedef and a pointer to it.
+        mlir::Value &entry = vlaSizeMap[sizeExpr];
+        if (!entry) {
+          mlir::Value size = emitScalarExpr(sizeExpr);
+          assert(!cir::MissingFeatures::sanitizeVLABound());
+
+          // Always zexting here would be wrong if it weren't
+          // undefined behavior to have a negative bound.
+          // FIXME: What about when size's type is larger than size_t?
+          entry = builder.createIntCast(size, SizeTy);
+        }
+      }
+      type = vat->getElementType();
+      break;
+    }
+
+    case Type::FunctionProto:
+    case Type::FunctionNoProto:
+      type = cast<clang::FunctionType>(ty)->getReturnType();
+      break;
+
+    case Type::Paren:
+    case Type::TypeOf:
+    case Type::UnaryTransform:
+    case Type::Attributed:
+    case Type::BTFTagAttributed:
+    case Type::SubstTemplateTypeParm:
+    case Type::MacroQualified:
+      // Keep walking after single level desugaring.
+      type = type.getSingleStepDesugaredType(getContext());
+      break;
+
+    case Type::Typedef:
+    case Type::Decltype:
+    case Type::Auto:
+    case Type::DeducedTemplateSpecialization:
+      // Stop walking: nothing to do.
+      return;
+
+    case Type::TypeOfExpr:
+      // Stop walking: emit typeof expression.
+      emitIgnoredExpr(cast<clang::TypeOfExprType>(ty)->getUnderlyingExpr());
+      return;
+
+    case Type::Atomic:
+      type = cast<clang::AtomicType>(ty)->getValueType();
+      break;
+
+    case Type::Pipe:
+      type = cast<clang::PipeType>(ty)->getElementType();
+      break;
+    }
+  } while (type->isVariablyModifiedType());
+}
+
 } // namespace clang::CIRGen
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h 
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index f533d0ab53cd2..97bb01298cb49 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -101,6 +101,20 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   mlir::MLIRContext &getMLIRContext() { return cgm.getMLIRContext(); }
 
+public:
+  struct VlaSizePair {
+    mlir::Value numElts;
+    QualType type;
+
+    VlaSizePair(mlir::Value numElts, QualType type)
+        : numElts(numElts), type(type) {}
+  };
+
+  llvm::DenseMap<const Expr *, mlir::Value> vlaSizeMap;
+
+  VlaSizePair getVLASize(const VariableArrayType *type);
+  VlaSizePair getVLASize(QualType type);
+
 private:
   /// Declare a variable in the current scope, return success if the variable
   /// wasn't declared yet.
@@ -446,6 +460,8 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   LValue emitArraySubscriptExpr(const clang::ArraySubscriptExpr *e);
 
+  Address emitArrayToPointerDecay(const Expr *e);
+
   AutoVarEmission emitAutoVarAlloca(const clang::VarDecl &d);
 
   /// Emit code and set up symbol table for a variable declaration with auto,
@@ -607,6 +623,8 @@ class CIRGenFunction : public CIRGenTypeCache {
   /// inside a function, including static vars etc.
   void emitVarDecl(const clang::VarDecl &d);
 
+  void emitVariablyModifiedType(QualType ty);
+
   mlir::LogicalResult emitWhileStmt(const clang::WhileStmt &s);
 
   /// Given an assignment `*lhs = rhs`, emit a test that checks if \p rhs is
@@ -621,6 +639,26 @@ class CIRGenFunction : public CIRGenTypeCache {
   Address createTempAlloca(mlir::Type ty, CharUnits align, mlir::Location loc,
                            const Twine &name, bool insertIntoFnEntryBlock);
 
+  Address createTempAlloca(mlir::Type ty, CharUnits align, mlir::Location loc,
+                           const Twine &name, mlir::Value arraySize = nullptr,
+                           Address *alloca = nullptr,
+                           mlir::OpBuilder::InsertPoint ip = {});
+
+  cir::AllocaOp createTempAlloca(mlir::Type ty, mlir::Location loc,
+                                 const Twine &name,
+                                 mlir::OpBuilder::InsertPoint ip,
+                                 mlir::Value arraySize);
+
+  cir::AllocaOp createTempAlloca(mlir::Type ty, mlir::Location loc,
+                                 const Twine &name, mlir::Value arraySize,
+                                 bool insertIntoFnEntryBlock = false);
+
+  Address createTempAllocaWithoutCast(mlir::Type ty, CharUnits align,
+                                      mlir::Location loc,
+                                      const Twine &name = "tmp",
+                                      mlir::Value arraySize = nullptr,
+                                      mlir::OpBuilder::InsertPoint ip = {});
+
   
//===--------------------------------------------------------------------===//
   //                         OpenACC Emission
   
//===--------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 3b13d495be5e3..8854136a0dc70 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -67,9 +67,15 @@ CIRGenModule::CIRGenModule(mlir::MLIRContext &mlirContext,
   // TODO(CIR): Should be updated once TypeSizeInfoAttr is upstreamed
   const unsigned sizeTypeSize =
       astContext.getTypeSize(astContext.getSignedSizeType());
+
+  SizeTy =
+      cir::IntType::get(&getMLIRContext(), sizeTypeSize, /*isSigned=*/false);
+
   PtrDiffTy =
       cir::IntType::get(&getMLIRContext(), sizeTypeSize, /*isSigned=*/true);
 
+  UInt8PtrTy = builder.getPointerTo(UInt8Ty);
+
   theModule->setAttr(cir::CIRDialect::getTripleAttrName(),
                      builder.getStringAttr(getTriple().str()));
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypeCache.h 
b/clang/lib/CIR/CodeGen/CIRGenTypeCache.h
index a5b7f0c9579b4..fd6897bc9bfb7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypeCache.h
+++ b/clang/lib/CIR/CodeGen/CIRGenTypeCache.h
@@ -49,8 +49,13 @@ struct CIRGenTypeCache {
   cir::FP80Type FP80Ty;
   cir::FP128Type FP128Ty;
 
+  /// intptr_t, size_t, and ptrdiff_t, which we assume are the same size.
+  mlir::Type SizeTy;
+
   mlir::Type PtrDiffTy;
 
+  cir::PointerType UInt8PtrTy;
+
   /// The size and alignment of a pointer into the generic address space.
   union {
     unsigned char PointerAlignInBytes;
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp 
b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
index b11f8466607f8..2034cd54a4261 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
@@ -392,6 +392,16 @@ mlir::Type CIRGenTypes::convertType(QualType type) {
     break;
   }
 
+  case Type::VariableArray: {
+    const VariableArrayType *vla = cast<VariableArrayType>(ty);
+    assert(vla->getIndexTypeCVRQualifiers() == 0 &&
+           "FIXME: We only handle trivial array types so far!");
+    // VLAs resolve to the innermost element type; this matches
+    // the return of alloca, and there isn't any obviously better choice.
+    resultType = convertTypeForMem(vla->getElementType());
+    break;
+  }
+
   case Type::ConstantArray: {
     const ConstantArrayType *arrTy = cast<ConstantArrayType>(ty);
     mlir::Type elemTy = convertTypeForMem(arrTy->getElementType());
diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp 
b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
index 72ccfa8d4e14e..5724ff2374d7a 100644
--- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
@@ -254,10 +254,55 @@ class CIRLoopOpInterfaceFlattening
   }
 };
 
+class CIRVLAAllocaOpFlattening : public mlir::OpRewritePattern<cir::AllocaOp> {
+public:
+  using OpRewritePattern<cir::AllocaOp>::OpRewritePattern;
+
+  mlir::LogicalResult
+  matchAndRewrite(cir::AllocaOp op,
+                  mlir::PatternRewriter &rewriter) const override {
+    if (!op.isDynamic()) {
+      return mlir::failure();
+    }
+
+    const mlir::Location loc = op->getLoc();
+
+    const Type u8Type = IntegerType::get(
+        getContext(), 8, IntegerType::SignednessSemantics::Unsigned);
+
+    const PointerType allocaInt8PtrTy =
+        cir::PointerType::get(getContext(), u8Type);
+
+    const PointerType allocaInt8PtrPtrTy =
+        cir::PointerType::get(getContext(), allocaInt8PtrTy);
+
+    rewriter.setInsertionPointAfter(op);
+
+    const IntegerAttr alignment = rewriter.getI64IntegerAttr(8);
+    const Value stackSavedAlloca = rewriter.create<cir::AllocaOp>(
+        loc, allocaInt8PtrPtrTy, allocaInt8PtrTy, "saved_stack", alignment);
+
+    const Value stackSaveValue =
+        rewriter.create<cir::StackSaveOp>(loc, allocaInt8PtrTy);
+
+    rewriter.create<cir::StoreOp>(loc, stackSaveValue, stackSavedAlloca);
+
+    const Block::iterator blockEndIt = rewriter.getBlock()->end();
+    const Block::iterator it = std::prev(std::prev(blockEndIt));
+    rewriter.setInsertionPointAfter(&*it);
+
+    const Value loadStackSaved =
+        rewriter.create<cir::LoadOp>(loc, allocaInt8PtrTy, stackSavedAlloca);
+    rewriter.create<cir::StackRestoreOp>(loc, loadStackSaved);
+
+    return mlir::success();
+  }
+};
+
 void populateFlattenCFGPatterns(RewritePatternSet &patterns) {
-  patterns
-      .add<CIRIfFlattening, CIRLoopOpInterfaceFlattening, 
CIRScopeOpFlattening>(
-          patterns.getContext());
+  patterns.add<CIRIfFlattening, CIRLoopOpInterfaceFlattening,
+               CIRScopeOpFlattening, CIRVLAAllocaOpFlattening>(
+      patterns.getContext());
 }
 
 void CIRFlattenCFGPass::runOnOperation() {
@@ -271,7 +316,7 @@ void CIRFlattenCFGPass::runOnOperation() {
     assert(!cir::MissingFeatures::switchOp());
     assert(!cir::MissingFeatures::ternaryOp());
     assert(!cir::MissingFeatures::tryOp());
-    if (isa<IfOp, ScopeOp, LoopOpInterface>(op))
+    if (isa<IfOp, ScopeOp, LoopOpInterface, AllocaOp>(op))
       ops.push_back(op);
   });
 
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp 
b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 8bb27942d9646..bc6e8ce98755c 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -597,9 +597,12 @@ mlir::LogicalResult 
CIRToLLVMAllocaOpLowering::matchAndRewrite(
     cir::AllocaOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
   assert(!cir::MissingFeatures::opAllocaDynAllocSize());
-  mlir::Value size = rewriter.create<mlir::LLVM::ConstantOp>(
-      op.getLoc(), typeConverter->convertType(rewriter.getIndexType()),
-      rewriter.getIntegerAttr(rewriter.getIndexType(), 1));
+  const mlir::Value size =
+      op.isDynamic() ? adaptor.getDynAllocSize()
+                     : rewriter.create<mlir::LLVM::ConstantOp>(
+                           op.getLoc(),
+                           typeConverter->convertType(rewriter.getIndexType()),
+                           rewriter.getIntegerAttr(rewriter.getIndexType(), 
1));
   mlir::Type elementTy =
       convertTypeForMemory(*getTypeConverter(), dataLayout, 
op.getAllocaType());
   mlir::Type resultTy = convertTypeForMemory(*getTypeConverter(), dataLayout,
diff --git a/clang/test/CIR/CodeGen/struct.c b/clang/test/CIR/CodeGen/struct.c
index b78a2367bda3a..805f65f74ddb1 100644
--- a/clang/test/CIR/CodeGen/struct.c
+++ b/clang/test/CIR/CodeGen/struct.c
@@ -77,43 +77,36 @@ struct PackedAndPaddedS {
 
 #pragma pack(pop)
 
-void f(void) {
+struct IncompleteS* f(void) {
   struct IncompleteS *p;
+  return p;
 }
 
-// CIR:      cir.func @f()
-// CIR-NEXT:   cir.alloca !cir.ptr<!cir.record<struct "IncompleteS" 
incomplete>>,
-// CIR-SAME:       !cir.ptr<!cir.ptr<!cir.record<struct
-// CIR-SAME:       "IncompleteS" incomplete>>>, ["p"]
-// CIR-NEXT:   cir.return
+// CIR: cir.func @f()
+// CIR:  cir.alloca !cir.ptr<!cir.record<struct "IncompleteS" incomplete>>, 
!cir.ptr<!cir.ptr<!cir.record<struct "IncompleteS" incomplete>>>, ["p"]
 
-// LLVM:      define void @f()
-// LLVM-NEXT:   %[[P:.*]] = alloca ptr, i64 1, align 8
-// LLVM-NEXT:   ret void
+// LLVM: define ptr @f()
+// LLVM:   %[[P:.*]] = alloca ptr, i64 1, align 8
 
-// OGCG:      define{{.*}} void @f()
+// OGCG: define{{.*}} ptr @f()
 // OGCG-NEXT: entry:
-// OGCG-NEXT:   %[[P:.*]] = alloca ptr, align 8
-// OGCG-NEXT:   ret void
+// OGCG:   %[[P:.*]] = alloca ptr, align 8
 
-void f2(void) {
+int f2(void) {
   struct CompleteS s;
+  return s.a;
 }
 
 // CIR:      cir.func @f2()
-// CIR-NEXT:   cir.alloca !cir.record<struct "CompleteS" {!s32i, !s8i}>,
+// CIR:   cir.alloca !cir.record<struct "CompleteS" {!s32i, !s8i}>,
 // CIR-SAME:       !cir.ptr<!cir.record<struct "CompleteS" {!s32i, !s8i}>>,
-// CIR-SAME:       ["s"] {alignment = 4 : i64}
-// CIR-NEXT:   cir.return
+// CIR:       ["s"] {alignment = 4 : i64}
 
-// LLVM:      define void @f2()
-// LLVM-NEXT:   %[[S:.*]] = alloca %struct.CompleteS, i64 1, align 4
-// LLVM-NEXT:   ret void
+// LLVM:  define {{.*}} @f2()
+// LLVM:   %[[S:.*]] = alloca %struct.CompleteS, i64 1, align 4
 
-// OGCG:      define{{.*}} void @f2()
-// OGCG-NEXT: entry:
-// OGCG-NEXT:   %[[S:.*]] = alloca %struct.CompleteS, align 4
-// OGCG-NEXT:   ret void
+// OGCG:      define{{.*}} {{.*}} @f2()
+// OGCG:   %[[S:.*]] = alloca %struct.CompleteS, align 4
 
 char f3(int a) {
   cs.a = a;
diff --git a/clang/test/CIR/CodeGen/struct.cpp 
b/clang/test/CIR/CodeGen/struct.cpp
index 6197340a7d36b..28fe7643e3d2b 100644
--- a/clang/test/CIR/CodeGen/struct.cpp
+++ b/clang/test/CIR/CodeGen/struct.cpp
@@ -12,20 +12,17 @@ IncompleteS *p;
 // LLVM: @p = dso_local global ptr null
 // OGCG: @p = global ptr null, align 8
 
-void f(void) {
+struct IncompleteS* f(void) {
   IncompleteS *p;
+  return p;
 }
 
-// CIR:      cir.func @f()
-// CIR-NEXT:   cir.alloca !cir.ptr<!cir.record<struct "IncompleteS" 
incomplete>>,
-// CIR-SAME:       !cir.ptr<!cir.ptr<!cir.record<struct "IncompleteS" 
incomplete>>>, ["p"]
-// CIR-NEXT:   cir.return
+// CIR: cir.func @f()
+// CIR:  cir.alloca !cir.ptr<!cir.record<struct "IncompleteS" incomplete>>, 
!cir.ptr<!cir.ptr<!cir.record<struct "IncompleteS" incomplete>>>, ["p"]
 
-// LLVM:      define void @f()
-// LLVM-NEXT:   %[[P:.*]] = alloca ptr, i64 1, align 8
-// LLVM-NEXT:   ret void
+// LLVM: define ptr @f()
+// LLVM:   %[[P:.*]] = alloca ptr, i64 1, align 8
 
-// OGCG:      define{{.*}} void @_Z1fv()
+// OGCG: define{{.*}} ptr @_Z1fv()
 // OGCG-NEXT: entry:
-// OGCG-NEXT:   %[[P:.*]] = alloca ptr, align 8
-// OGCG-NEXT:   ret void
+// OGCG:   %[[P:.*]] = alloca ptr, align 8
diff --git a/clang/test/CIR/CodeGen/typedef.c b/clang/test/CIR/CodeGen/typedef.c
index 17fce13abf38a..2bee74dc86ba6 100644
--- a/clang/test/CIR/CodeGen/typedef.c
+++ b/clang/test/CIR/CodeGen/typedef.c
@@ -5,23 +5,21 @@
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value 
-emit-llvm %s -o %t.ll
 // RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
 
-void local_typedef(void) {
+int  local_typedef(void) {
   typedef struct {int a;} Struct;
   Struct s;
+  return s.a;
 }
 
 // CIR:      cir.func @local_typedef()
 // CIR:        cir.alloca !cir.record<struct "Struct" {!s32i}>,
 // CIR-SAME:       !cir.ptr<!cir.record<struct "Struct" {!s32i}>>, ["s"]
 // CIR-SAME:       {alignment = 4 : i64}
-// CIR:        cir.return
 
 // LLVM: %struct.Struct = type { i32 }
-// LLVM: define void @local_typedef()
+// LLVM: define {{.*}} @local_typedef()
 // LLVM:   alloca %struct.Struct, i64 1, align 4
-// LLVM:   ret void
 
 // OGCG: %struct.Struct = type { i32 }
-// OGCG: define{{.*}} void @local_typedef()
+// OGCG: define{{.*}} {{.*}} @local_typedef()
 // OGCG:   alloca %struct.Struct, align 4
-// OGCG:   ret void
diff --git a/clang/test/CIR/CodeGen/union.c b/clang/test/CIR/CodeGen/union.c
index 075d0d2315508..ce89ac733adbc 100644
--- a/clang/test/CIR/CodeGen/union.c
+++ b/clang/test/CIR/CodeGen/union.c
@@ -11,20 +11,16 @@ union IncompleteU *p;
 // LLVM: @p = dso_local global ptr null
 // OGCG: @p = global ptr null, align 8
 
-void f(void) {
+union IncompleteU* f(void) {
   union IncompleteU *p;
+  return p;
 }
 
-// CIR: cir.func @f()
-// CIR-NEXT: cir.alloca !cir.ptr<!cir.record<union "IncompleteU" incomplete>>,
-// CIR-SAME:     !cir.ptr<!cir.ptr<!cir.record<union "IncompleteU" 
incomplete>>>, ["p"]
-// CIR-NEXT: cir.return
+// CIR: cir.func @f() -> !cir.ptr<!cir.record<union "IncompleteU" incomplete>>
+// CIR:   cir.alloca !cir.ptr<!cir.record<union "IncompleteU" incomplete>>, 
!cir.ptr<!cir.ptr<!cir.record<union "IncompleteU" incomplete>>>, ["p"]
 
-// LLVM:      define void @f()
-// LLVM-NEXT:   %[[P:.*]] = alloca ptr, i64 1, align 8
-// LLVM-NEXT:   ret void
+// LLVM: define ptr @f()
+// LLVM:   %[[P:.*]] = alloca ptr, i64 1, align 8
 
-// OGCG:      define{{.*}} void @f()
-// OGCG-NEXT: entry:
-// OGCG-NEXT:   %[[P:.*]] = alloca ptr, align 8
-// OGCG-NEXT:   ret void
+// OGCG: define{{.*}} ptr @f()
+// OGCG:   %[[P:.*]] = alloca ptr, align 8
diff --git a/clang/test/CIR/CodeGen/vla.cpp b/clang/test/CIR/CodeGen/vla.cpp
new file mode 100644
index 0000000000000..80b3dcbac9dfc
--- /dev/null
+++ b/clang/test/CIR/CodeGen/vla.cpp
@@ -0,0 +1,71 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value 
-fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value 
-fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value 
-emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+
+void func(int len) {
+  int arr[len];
+  int e = arr[0];
+}
+
+// CIR: %0 = cir.alloca !s32i, !cir.ptr<!s32i>, ["len", init]
+// CIR: cir.store %arg0, %0 : !s32i, !cir.ptr<!s32i>
+// CIR: %1 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+// CIR: %2 = cir.cast(integral, %1 : !s32i), !u64i
+// CIR: %3 = cir.alloca !s32i, !cir.ptr<!s32i>, %2 : !u64i, ["vla"]
+// CIR: %4 = cir.alloca !s32i, !cir.ptr<!s32i>, ["e", init]
+// CIR: %5 = cir.const #cir.int<0> : !s32i
+// CIR: %6 = cir.ptr_stride(%3 : !cir.ptr<!s32i>, %5 : !s32i), !cir.ptr<!s32i>
+// CIR: %7 = cir.load %6 : !cir.ptr<!s32i>, !s32i
+// CIR: cir.store %7, %4 : !s32i, !cir.ptr<!s32i>
+
+// LLVM: %2 = alloca i32, i64 1
+// LLVM: store i32 %0, ptr %2
+// LLVM: %3 = load i32, ptr %2
+// LLVM: %4 = sext i32 %3 to i64
+// LLVM: %5 = alloca i32, i64 %4
+// LLVM: %6 = alloca ptr, i64 1
+// LLVM: %7 = call ptr @llvm.stacksave.p0()
+// LLVM: store ptr %7, ptr %6
+// LLVM: %8 = alloca i32, i64 1
+// LLVM: %9 = getelementptr i32, ptr %5, i64 0
+// LLVM: %10 = load i32, ptr %9
+// LLVM: store i32 %10, ptr %8
+// LLVM: %11 = load ptr, ptr %6
+// LLVM: call void @llvm.stackrestore.p0(ptr %11)
+
+// OGCG: %len.addr = alloca i32
+// OGCG: %saved_stack = alloca ptr
+// OGCG: %__vla_expr0 = alloca i64
+// OGCG: %e = alloca i32
+// OGCG: store i32 %len, ptr %len.addr
+// OGCG: %0 = load i32, ptr %len.addr
+// OGCG: %1 = zext i32 %0 to i64
+// OGCG: %2 = call ptr @llvm.stacksave.p0()
+// OGCG: store ptr %2, ptr %saved_stack
+// OGCG: %vla = alloca i32, i64 %1
+// OGCG: store i64 %1, ptr %__vla_expr0
+// OGCG: %arrayidx = getelementptr inbounds i32, ptr %vla, i64 0
+// OGCG: %3 = load i32, ptr %arrayidx
+// OGCG: store i32 %3, ptr %e
+// OGCG: %4 = load ptr, ptr %saved_stack
+// OGCG: call void @llvm.stackrestore.p0(ptr %4)
+
+void func2(short width, int data[][width]) {}
+
+// CIR: %0 = cir.alloca !s16i, !cir.ptr<!s16i>, ["width", init]
+// CIR: %1 = cir.alloca !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>, ["data", 
init]
+// CIR: cir.store %arg0, %0 : !s16i, !cir.ptr<!s16i>
+// CIR: cir.store %arg1, %1 : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>
+
+// LLVM: %3 = alloca i16, i64 1
+// LLVM: %4 = alloca ptr, i64 1
+// LLVM: store i16 %0, ptr %3
+// LLVM: store ptr %1, ptr %4
+
+// OGCG: %width.addr = alloca i16
+// OGCG: %data.addr = alloca ptr
+// OGCG: store i16 %width, ptr %width.addr
+// OGCG: store ptr %data, ptr %data.addr
diff --git a/clang/test/CIR/Lowering/local-vars.cpp 
b/clang/test/CIR/Lowering/local-vars.cpp
index bd47ed14065c6..ea3b702e50968 100644
--- a/clang/test/CIR/Lowering/local-vars.cpp
+++ b/clang/test/CIR/Lowering/local-vars.cpp
@@ -13,11 +13,16 @@ void test() {
   const double cd = 4.0;
   const bool cb1 = true;
   const bool cb2 = false;
+
+
+  // TODO: The following variables need to be used for example as function 
return value
+  /*
   int uii;
   long uil;
   float uif;
   double uid;
   bool uib;
+  */
 }
 
 // Note: The alignment of i64 stores below is wrong. That should be fixed
@@ -36,11 +41,14 @@ void test() {
 // CHECK:    %[[CD_PTR:.*]] = alloca double, i64 1, align 8
 // CHECK:    %[[CB1_PTR:.*]] = alloca i8, i64 1, align 1
 // CHECK:    %[[CB2_PTR:.*]] = alloca i8, i64 1, align 1
-// CHECK:    %[[UII_PTR:.*]] = alloca i32, i64 1, align 4
-// CHECK:    %[[UIL_PTR:.*]] = alloca i64, i64 1, align 8
-// CHECK:    %[[UIF_PTR:.*]] = alloca float, i64 1, align 4
-// CHECK:    %[[UID_PTR:.*]] = alloca double, i64 1, align 8
-// CHECK:    %[[UIB_PTR:.*]] = alloca i8, i64 1, align 1
+
+// TODO: those alloca are removed in DCE
+// CHECK    %[[UII_PTR:.*]] = alloca i32, i64 1, align 4
+// CHECK    %[[UIL_PTR:.*]] = alloca i64, i64 1, align 8
+// CHECK    %[[UIF_PTR:.*]] = alloca float, i64 1, align 4
+// CHECK    %[[UID_PTR:.*]] = alloca double, i64 1, align 8
+// CHECK    %[[UIB_PTR:.*]] = alloca i8, i64 1, align 1
+
 // CHECK:    store i32 1, ptr %[[I_PTR]], align 4
 // CHECK:    store i64 2, ptr %[[L_PTR]], align 4
 // CHECK:    store float 3.000000e+00, ptr %[[F_PTR]], align 4

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to