[cfe-commits] r41014 - in /cfe/trunk/CodeGen: CGAggExpr.cpp CGExpr.cpp CGStmt.cpp CodeGenFunction.h
Chris Lattner
sabre at nondot.org
Fri Aug 10 17:04:45 PDT 2007
Author: lattner
Date: Fri Aug 10 19:04:45 2007
New Revision: 41014
URL: http://llvm.org/viewvc/llvm-project?rev=41014&view=rev
Log:
start splitting out aggregate value computation from EmitExpr into EmitAggExpr.
aggregate value and scalar expression computation are very different, this
gets them away from each other. This causes a temporary regression on some
complex number examples.
Modified:
cfe/trunk/CodeGen/CGAggExpr.cpp
cfe/trunk/CodeGen/CGExpr.cpp
cfe/trunk/CodeGen/CGStmt.cpp
cfe/trunk/CodeGen/CodeGenFunction.h
Modified: cfe/trunk/CodeGen/CGAggExpr.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/CodeGen/CGAggExpr.cpp?rev=41014&r1=41013&r2=41014&view=diff
==============================================================================
--- cfe/trunk/CodeGen/CGAggExpr.cpp (original)
+++ cfe/trunk/CodeGen/CGAggExpr.cpp Fri Aug 10 19:04:45 2007
@@ -12,6 +12,286 @@
//===----------------------------------------------------------------------===//
#include "CodeGenFunction.h"
-
+#include "CodeGenModule.h"
+#include "clang/AST/AST.h"
+#include "llvm/Constants.h"
+#include "llvm/Function.h"
using namespace clang;
using namespace CodeGen;
+
+// FIXME: Handle volatility!
+void CodeGenFunction::EmitAggregateCopy(llvm::Value *DestPtr,
+ llvm::Value *SrcPtr, QualType Ty) {
+ // Don't use memcpy for complex numbers.
+ if (Ty->isComplexType()) {
+ llvm::Value *Real, *Imag;
+ EmitLoadOfComplex(RValue::getAggregate(SrcPtr), Real, Imag);
+ EmitStoreOfComplex(Real, Imag, DestPtr);
+ return;
+ }
+
+ // Aggregate assignment turns into llvm.memcpy.
+ const llvm::Type *BP = llvm::PointerType::get(llvm::Type::Int8Ty);
+ if (DestPtr->getType() != BP)
+ DestPtr = Builder.CreateBitCast(DestPtr, BP, "tmp");
+ if (SrcPtr->getType() != BP)
+ SrcPtr = Builder.CreateBitCast(SrcPtr, BP, "tmp");
+
+ // Get size and alignment info for this aggregate.
+ std::pair<uint64_t, unsigned> TypeInfo =
+ getContext().getTypeInfo(Ty, SourceLocation());
+
+ // FIXME: Handle variable sized types.
+ const llvm::Type *IntPtr = llvm::IntegerType::get(LLVMPointerWidth);
+
+ llvm::Value *MemCpyOps[4] = {
+ DestPtr, SrcPtr,
+ llvm::ConstantInt::get(IntPtr, TypeInfo.first),
+ llvm::ConstantInt::get(llvm::Type::Int32Ty, TypeInfo.second)
+ };
+
+ Builder.CreateCall(CGM.getMemCpyFn(), MemCpyOps, MemCpyOps+4);
+}
+
+
+/// EmitAggExpr - Emit the computation of the specified expression of
+/// aggregate type. The result is computed into DestPtr. Note that if
+/// DestPtr is null, the value of the aggregate expression is not needed.
+void CodeGenFunction::EmitAggExpr(const Expr *E, llvm::Value *DestPtr,
+ bool VolatileDest) {
+ assert(E && hasAggregateLLVMType(E->getType()) &&
+ "Invalid aggregate expression to emit");
+
+ switch (E->getStmtClass()) {
+ default:
+ fprintf(stderr, "Unimplemented agg expr!\n");
+ E->dump();
+ return;
+
+ // l-values.
+ case Expr::DeclRefExprClass:
+ return EmitAggLoadOfLValue(E, DestPtr, VolatileDest);
+// case Expr::ArraySubscriptExprClass:
+// return EmitArraySubscriptExprRV(cast<ArraySubscriptExpr>(E));
+
+ // Operators.
+ case Expr::ParenExprClass:
+ return EmitAggExpr(cast<ParenExpr>(E)->getSubExpr(), DestPtr, VolatileDest);
+// case Expr::UnaryOperatorClass:
+// return EmitUnaryOperator(cast<UnaryOperator>(E));
+// case Expr::ImplicitCastExprClass:
+// return EmitCastExpr(cast<ImplicitCastExpr>(E)->getSubExpr(),E->getType());
+// case Expr::CastExprClass:
+// return EmitCastExpr(cast<CastExpr>(E)->getSubExpr(), E->getType());
+// case Expr::CallExprClass:
+// return EmitCallExpr(cast<CallExpr>(E));
+ case Expr::BinaryOperatorClass:
+ return EmitAggBinaryOperator(cast<BinaryOperator>(E), DestPtr,VolatileDest);
+
+ case Expr::ConditionalOperatorClass:
+ return EmitAggConditionalOperator(cast<ConditionalOperator>(E),
+ DestPtr, VolatileDest);
+// case Expr::ChooseExprClass:
+// return EmitChooseExpr(cast<ChooseExpr>(E));
+ }
+}
+
+/// EmitAggLoadOfLValue - Given an expression with aggregate type that
+/// represents a value lvalue, this method emits the address of the lvalue,
+/// then loads the result into DestPtr.
+void CodeGenFunction::EmitAggLoadOfLValue(const Expr *E, llvm::Value *DestPtr,
+ bool VolatileDest) {
+ LValue LV = EmitLValue(E);
+ assert(LV.isSimple() && "Can't have aggregate bitfield, vector, etc");
+ llvm::Value *SrcPtr = LV.getAddress();
+
+ // If the result is ignored, don't copy from the value.
+ if (DestPtr == 0)
+ // FIXME: If the source is volatile, we must read from it.
+ return;
+
+ EmitAggregateCopy(DestPtr, SrcPtr, E->getType());
+}
+
+void CodeGenFunction::EmitAggBinaryOperator(const BinaryOperator *E,
+ llvm::Value *DestPtr,
+ bool VolatileDest) {
+ switch (E->getOpcode()) {
+ default:
+ fprintf(stderr, "Unimplemented aggregate binary expr!\n");
+ E->dump();
+ return;
+#if 0
+ case BinaryOperator::Mul:
+ LHS = EmitExpr(E->getLHS());
+ RHS = EmitExpr(E->getRHS());
+ return EmitMul(LHS, RHS, E->getType());
+ case BinaryOperator::Div:
+ LHS = EmitExpr(E->getLHS());
+ RHS = EmitExpr(E->getRHS());
+ return EmitDiv(LHS, RHS, E->getType());
+ case BinaryOperator::Rem:
+ LHS = EmitExpr(E->getLHS());
+ RHS = EmitExpr(E->getRHS());
+ return EmitRem(LHS, RHS, E->getType());
+ case BinaryOperator::Add:
+ LHS = EmitExpr(E->getLHS());
+ RHS = EmitExpr(E->getRHS());
+ if (!E->getType()->isPointerType())
+ return EmitAdd(LHS, RHS, E->getType());
+
+ return EmitPointerAdd(LHS, E->getLHS()->getType(),
+ RHS, E->getRHS()->getType(), E->getType());
+ case BinaryOperator::Sub:
+ LHS = EmitExpr(E->getLHS());
+ RHS = EmitExpr(E->getRHS());
+
+ if (!E->getLHS()->getType()->isPointerType())
+ return EmitSub(LHS, RHS, E->getType());
+
+ return EmitPointerSub(LHS, E->getLHS()->getType(),
+ RHS, E->getRHS()->getType(), E->getType());
+ case BinaryOperator::Shl:
+ LHS = EmitExpr(E->getLHS());
+ RHS = EmitExpr(E->getRHS());
+ return EmitShl(LHS, RHS, E->getType());
+ case BinaryOperator::Shr:
+ LHS = EmitExpr(E->getLHS());
+ RHS = EmitExpr(E->getRHS());
+ return EmitShr(LHS, RHS, E->getType());
+ case BinaryOperator::And:
+ LHS = EmitExpr(E->getLHS());
+ RHS = EmitExpr(E->getRHS());
+ return EmitAnd(LHS, RHS, E->getType());
+ case BinaryOperator::Xor:
+ LHS = EmitExpr(E->getLHS());
+ RHS = EmitExpr(E->getRHS());
+ return EmitXor(LHS, RHS, E->getType());
+ case BinaryOperator::Or :
+ LHS = EmitExpr(E->getLHS());
+ RHS = EmitExpr(E->getRHS());
+ return EmitOr(LHS, RHS, E->getType());
+#endif
+ case BinaryOperator::Assign:
+ return EmitAggBinaryAssign(E, DestPtr, VolatileDest);
+
+#if 0
+ case BinaryOperator::MulAssign: {
+ const CompoundAssignOperator *CAO = cast<CompoundAssignOperator>(E);
+ LValue LHSLV;
+ EmitCompoundAssignmentOperands(CAO, LHSLV, LHS, RHS);
+ LHS = EmitMul(LHS, RHS, CAO->getComputationType());
+ return EmitCompoundAssignmentResult(CAO, LHSLV, LHS);
+ }
+ case BinaryOperator::DivAssign: {
+ const CompoundAssignOperator *CAO = cast<CompoundAssignOperator>(E);
+ LValue LHSLV;
+ EmitCompoundAssignmentOperands(CAO, LHSLV, LHS, RHS);
+ LHS = EmitDiv(LHS, RHS, CAO->getComputationType());
+ return EmitCompoundAssignmentResult(CAO, LHSLV, LHS);
+ }
+ case BinaryOperator::RemAssign: {
+ const CompoundAssignOperator *CAO = cast<CompoundAssignOperator>(E);
+ LValue LHSLV;
+ EmitCompoundAssignmentOperands(CAO, LHSLV, LHS, RHS);
+ LHS = EmitRem(LHS, RHS, CAO->getComputationType());
+ return EmitCompoundAssignmentResult(CAO, LHSLV, LHS);
+ }
+ case BinaryOperator::AddAssign: {
+ const CompoundAssignOperator *CAO = cast<CompoundAssignOperator>(E);
+ LValue LHSLV;
+ EmitCompoundAssignmentOperands(CAO, LHSLV, LHS, RHS);
+ LHS = EmitAdd(LHS, RHS, CAO->getComputationType());
+ return EmitCompoundAssignmentResult(CAO, LHSLV, LHS);
+ }
+ case BinaryOperator::SubAssign: {
+ const CompoundAssignOperator *CAO = cast<CompoundAssignOperator>(E);
+ LValue LHSLV;
+ EmitCompoundAssignmentOperands(CAO, LHSLV, LHS, RHS);
+ LHS = EmitSub(LHS, RHS, CAO->getComputationType());
+ return EmitCompoundAssignmentResult(CAO, LHSLV, LHS);
+ }
+ case BinaryOperator::ShlAssign: {
+ const CompoundAssignOperator *CAO = cast<CompoundAssignOperator>(E);
+ LValue LHSLV;
+ EmitCompoundAssignmentOperands(CAO, LHSLV, LHS, RHS);
+ LHS = EmitShl(LHS, RHS, CAO->getComputationType());
+ return EmitCompoundAssignmentResult(CAO, LHSLV, LHS);
+ }
+ case BinaryOperator::ShrAssign: {
+ const CompoundAssignOperator *CAO = cast<CompoundAssignOperator>(E);
+ LValue LHSLV;
+ EmitCompoundAssignmentOperands(CAO, LHSLV, LHS, RHS);
+ LHS = EmitShr(LHS, RHS, CAO->getComputationType());
+ return EmitCompoundAssignmentResult(CAO, LHSLV, LHS);
+ }
+ case BinaryOperator::AndAssign: {
+ const CompoundAssignOperator *CAO = cast<CompoundAssignOperator>(E);
+ LValue LHSLV;
+ EmitCompoundAssignmentOperands(CAO, LHSLV, LHS, RHS);
+ LHS = EmitAnd(LHS, RHS, CAO->getComputationType());
+ return EmitCompoundAssignmentResult(CAO, LHSLV, LHS);
+ }
+ case BinaryOperator::OrAssign: {
+ const CompoundAssignOperator *CAO = cast<CompoundAssignOperator>(E);
+ LValue LHSLV;
+ EmitCompoundAssignmentOperands(CAO, LHSLV, LHS, RHS);
+ LHS = EmitOr(LHS, RHS, CAO->getComputationType());
+ return EmitCompoundAssignmentResult(CAO, LHSLV, LHS);
+ }
+ case BinaryOperator::XorAssign: {
+ const CompoundAssignOperator *CAO = cast<CompoundAssignOperator>(E);
+ LValue LHSLV;
+ EmitCompoundAssignmentOperands(CAO, LHSLV, LHS, RHS);
+ LHS = EmitXor(LHS, RHS, CAO->getComputationType());
+ return EmitCompoundAssignmentResult(CAO, LHSLV, LHS);
+ }
+ case BinaryOperator::Comma: return EmitBinaryComma(E);
+#endif
+ }
+}
+
+void CodeGenFunction::EmitAggBinaryAssign(const BinaryOperator *E,
+ llvm::Value *DestPtr,
+ bool VolatileDest) {
+ assert(E->getLHS()->getType().getCanonicalType() ==
+ E->getRHS()->getType().getCanonicalType() && "Invalid assignment");
+ LValue LHS = EmitLValue(E->getLHS());
+
+ // Codegen the RHS so that it stores directly into the LHS.
+ EmitAggExpr(E->getRHS(), LHS.getAddress(), false /*FIXME: VOLATILE LHS*/);
+
+ // If the result of the assignment is used, copy the RHS there also.
+ if (DestPtr) {
+ assert(0 && "FIXME: Chained agg assignment not implemented yet");
+ }
+}
+
+
+void CodeGenFunction::EmitAggConditionalOperator(const ConditionalOperator *E,
+ llvm::Value *DestPtr,
+ bool VolatileDest) {
+ llvm::BasicBlock *LHSBlock = new llvm::BasicBlock("cond.?");
+ llvm::BasicBlock *RHSBlock = new llvm::BasicBlock("cond.:");
+ llvm::BasicBlock *ContBlock = new llvm::BasicBlock("cond.cont");
+
+ llvm::Value *Cond = EvaluateExprAsBool(E->getCond());
+ Builder.CreateCondBr(Cond, LHSBlock, RHSBlock);
+
+ EmitBlock(LHSBlock);
+
+ // Handle the GNU extension for missing LHS.
+ assert(E->getLHS() && "Must have LHS for aggregate value");
+
+ EmitAggExpr(E->getLHS(), DestPtr, VolatileDest);
+ Builder.CreateBr(ContBlock);
+ LHSBlock = Builder.GetInsertBlock();
+
+ EmitBlock(RHSBlock);
+
+ EmitAggExpr(E->getRHS(), DestPtr, VolatileDest);
+ Builder.CreateBr(ContBlock);
+ RHSBlock = Builder.GetInsertBlock();
+
+ EmitBlock(ContBlock);
+}
Modified: cfe/trunk/CodeGen/CGExpr.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/CodeGen/CGExpr.cpp?rev=41014&r1=41013&r2=41014&view=diff
==============================================================================
--- cfe/trunk/CodeGen/CGExpr.cpp (original)
+++ cfe/trunk/CodeGen/CGExpr.cpp Fri Aug 10 19:04:45 2007
@@ -47,10 +47,12 @@
llvm::Constant *Zero = llvm::ConstantInt::get(llvm::Type::Int32Ty, 0);
llvm::Constant *One = llvm::ConstantInt::get(llvm::Type::Int32Ty, 1);
+ // FIXME: It would be nice to make this "Ptr->getName()+realp"
llvm::Value *RealPtr = Builder.CreateGEP(Ptr, Zero, Zero, "realp");
llvm::Value *ImagPtr = Builder.CreateGEP(Ptr, Zero, One, "imagp");
// FIXME: Handle volatility.
+ // FIXME: It would be nice to make this "Ptr->getName()+real"
Real = Builder.CreateLoad(RealPtr, "real");
Imag = Builder.CreateLoad(ImagPtr, "imag");
}
@@ -272,11 +274,8 @@
if (EltTy->isFirstClassType())
return RValue::get(Builder.CreateLoad(Ptr, "tmp"));
- if (ExprType->isFunctionType())
- return RValue::get(Ptr);
-
- // Otherwise, we have an aggregate lvalue.
- return RValue::getAggregate(Ptr);
+ assert(ExprType->isFunctionType() && "Unknown scalar value");
+ return RValue::get(Ptr);
}
if (LV.isVectorElt()) {
@@ -376,48 +375,16 @@
}
llvm::Value *DstAddr = Dst.getAddress();
- if (Src.isScalar()) {
- // FIXME: Handle volatility etc.
- const llvm::Type *SrcTy = Src.getVal()->getType();
- const llvm::Type *AddrTy =
- cast<llvm::PointerType>(DstAddr->getType())->getElementType();
-
- if (AddrTy != SrcTy)
- DstAddr = Builder.CreateBitCast(DstAddr, llvm::PointerType::get(SrcTy),
- "storetmp");
- Builder.CreateStore(Src.getVal(), DstAddr);
- return;
- }
-
- // Don't use memcpy for complex numbers.
- if (Ty->isComplexType()) {
- llvm::Value *Real, *Imag;
- EmitLoadOfComplex(Src, Real, Imag);
- EmitStoreOfComplex(Real, Imag, Dst.getAddress());
- return;
- }
-
- // Aggregate assignment turns into llvm.memcpy.
- const llvm::Type *SBP = llvm::PointerType::get(llvm::Type::Int8Ty);
- llvm::Value *SrcAddr = Src.getAggregateAddr();
-
- if (DstAddr->getType() != SBP)
- DstAddr = Builder.CreateBitCast(DstAddr, SBP, "tmp");
- if (SrcAddr->getType() != SBP)
- SrcAddr = Builder.CreateBitCast(SrcAddr, SBP, "tmp");
-
- unsigned Align = 1; // FIXME: Compute type alignments.
- unsigned Size = 1234; // FIXME: Compute type sizes.
-
- // FIXME: Handle variable sized types.
- const llvm::Type *IntPtr = llvm::IntegerType::get(LLVMPointerWidth);
- llvm::Value *SizeVal = llvm::ConstantInt::get(IntPtr, Size);
-
- llvm::Value *MemCpyOps[4] = {
- DstAddr, SrcAddr, SizeVal,llvm::ConstantInt::get(llvm::Type::Int32Ty, Align)
- };
-
- Builder.CreateCall(CGM.getMemCpyFn(), MemCpyOps, MemCpyOps+4);
+ assert(Src.isScalar() && "Can't emit an agg store with this method");
+ // FIXME: Handle volatility etc.
+ const llvm::Type *SrcTy = Src.getVal()->getType();
+ const llvm::Type *AddrTy =
+ cast<llvm::PointerType>(DstAddr->getType())->getElementType();
+
+ if (AddrTy != SrcTy)
+ DstAddr = Builder.CreateBitCast(DstAddr, llvm::PointerType::get(SrcTy),
+ "storetmp");
+ Builder.CreateStore(Src.getVal(), DstAddr);
}
void CodeGenFunction::EmitStoreThroughOCUComponentLValue(RValue Src, LValue Dst,
@@ -585,7 +552,8 @@
//===--------------------------------------------------------------------===//
RValue CodeGenFunction::EmitExpr(const Expr *E) {
- assert(E && "Null expression?");
+ assert(E && !hasAggregateLLVMType(E->getType()) &&
+ "Invalid scalar expression to emit");
switch (E->getStmtClass()) {
default:
@@ -641,7 +609,6 @@
case Expr::ChooseExprClass:
return EmitChooseExpr(cast<ChooseExpr>(E));
}
-
}
RValue CodeGenFunction::EmitIntegerLiteral(const IntegerLiteral *E) {
@@ -1401,8 +1368,8 @@
// Store the value into the LHS.
EmitStoreThroughLValue(RHS, LHS, E->getType());
-
- // Return the converted RHS.
+
+ // Return the RHS.
return RHS;
}
@@ -1420,8 +1387,6 @@
llvm::Value *Cond = EvaluateExprAsBool(E->getCond());
Builder.CreateCondBr(Cond, LHSBlock, RHSBlock);
- // FIXME: Implement this for aggregate values.
-
EmitBlock(LHSBlock);
// Handle the GNU extension for missing LHS.
llvm::Value *LHSValue = E->getLHS() ? EmitExpr(E->getLHS()).getVal() : Cond;
Modified: cfe/trunk/CodeGen/CGStmt.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/CodeGen/CGStmt.cpp?rev=41014&r1=41013&r2=41014&view=diff
==============================================================================
--- cfe/trunk/CodeGen/CGStmt.cpp (original)
+++ cfe/trunk/CodeGen/CGStmt.cpp Fri Aug 10 19:04:45 2007
@@ -31,7 +31,10 @@
// Must be an expression in a stmt context. Emit the value and ignore the
// result.
if (const Expr *E = dyn_cast<Expr>(S)) {
- EmitExpr(E);
+ if (hasAggregateLLVMType(E->getType()))
+ EmitAggExpr(E, 0, false); // Emit an aggregate, ignoring the result.
+ else
+ EmitExpr(E);
} else {
printf("Unimplemented stmt!\n");
S->dump();
@@ -257,7 +260,7 @@
// If there is an increment, emit it next.
if (S.getInc())
- EmitExpr(S.getInc());
+ EmitStmt(S.getInc());
// Finally, branch back up to the condition for the next iteration.
Builder.CreateBr(CondBlock);
@@ -274,6 +277,7 @@
// Emit the result value, even if unused, to evalute the side effects.
const Expr *RV = S.getRetValue();
+ // FIXME: Handle return of an aggregate!
if (RV)
RetVal = EmitExpr(RV);
else // Silence a bogus GCC warning.
Modified: cfe/trunk/CodeGen/CodeGenFunction.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/CodeGen/CodeGenFunction.h?rev=41014&r1=41013&r2=41014&view=diff
==============================================================================
--- cfe/trunk/CodeGen/CodeGenFunction.h (original)
+++ cfe/trunk/CodeGen/CodeGenFunction.h Fri Aug 10 19:04:45 2007
@@ -336,7 +336,7 @@
LValue EmitOCUVectorElementExpr(const OCUVectorElementExpr *E);
//===--------------------------------------------------------------------===//
- // Expression Emission
+ // Scalar Expression Emission
//===--------------------------------------------------------------------===//
void EmitCompoundAssignmentOperands(const CompoundAssignOperator *CAO,
@@ -344,7 +344,6 @@
RValue EmitCompoundAssignmentResult(const CompoundAssignOperator *E,
LValue LHSLV, RValue ResV);
-
RValue EmitExpr(const Expr *E);
RValue EmitIntegerLiteral(const IntegerLiteral *E);
RValue EmitFloatingLiteral(const FloatingLiteral *E);
@@ -396,6 +395,36 @@
// Conditional Operator.
RValue EmitConditionalOperator(const ConditionalOperator *E);
RValue EmitChooseExpr(const ChooseExpr *E);
+
+ //===--------------------------------------------------------------------===//
+ // Aggregate Expression Emission
+ //===--------------------------------------------------------------------===//
+
+ void EmitAggregateCopy(llvm::Value *DestPtr, llvm::Value *SrcPtr,
+ QualType EltTy);
+
+ /// EmitAggExpr - Emit the computation of the specified expression of
+ /// aggregate type. The result is computed into DestPtr. Note that if
+ /// DestPtr is null, the value of the aggregate expression is not needed.
+ void EmitAggExpr(const Expr *E, llvm::Value *DestPtr, bool VolatileDest);
+
+ /// EmitAggLoadOfLValue - Given an expression with aggregate type that
+ /// represents a value lvalue, this method emits the address of the lvalue,
+ /// then loads the result into DestPtr.
+ void EmitAggLoadOfLValue(const Expr *E, llvm::Value *DestPtr, bool VolDest);
+
+
+
+ // Binary Operators.
+ void EmitAggBinaryOperator(const BinaryOperator *E,
+ llvm::Value *DestPtr, bool VolatileDest);
+
+
+ void EmitAggBinaryAssign(const BinaryOperator *E, llvm::Value *DestPtr,
+ bool VolatileDest);
+
+ void EmitAggConditionalOperator(const ConditionalOperator *E,
+ llvm::Value *DestPtr, bool VolatileDest);
};
} // end namespace CodeGen
} // end namespace clang
More information about the cfe-commits
mailing list