r222231 - [OPENMP] Additional processing of 'omp atomic read' directive.

Alexey Bataev a.bataev at hotmail.com
Tue Nov 18 02:14:23 PST 2014


Author: abataev
Date: Tue Nov 18 04:14:22 2014
New Revision: 222231

URL: http://llvm.org/viewvc/llvm-project?rev=222231&view=rev
Log:
[OPENMP] Additional processing of 'omp atomic read' directive.
According to OpenMP standard, Section 2.12.6, atomic Construct, '#pragma omp atomic read' is allowed to be used only for expression statements of form 'v = x;', where x and v (as applicable) are both l-value expressions with scalar type. Patch adds checks for it.

Added:
    cfe/trunk/test/OpenMP/atomic_messages.c   (with props)
Modified:
    cfe/trunk/include/clang/AST/StmtOpenMP.h
    cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
    cfe/trunk/lib/AST/Stmt.cpp
    cfe/trunk/lib/Sema/SemaOpenMP.cpp
    cfe/trunk/lib/Serialization/ASTReaderStmt.cpp
    cfe/trunk/lib/Serialization/ASTWriterStmt.cpp
    cfe/trunk/test/OpenMP/atomic_messages.cpp

Modified: cfe/trunk/include/clang/AST/StmtOpenMP.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/StmtOpenMP.h?rev=222231&r1=222230&r2=222231&view=diff
==============================================================================
--- cfe/trunk/include/clang/AST/StmtOpenMP.h (original)
+++ cfe/trunk/include/clang/AST/StmtOpenMP.h Tue Nov 18 04:14:22 2014
@@ -1439,7 +1439,7 @@ class OMPAtomicDirective : public OMPExe
   OMPAtomicDirective(SourceLocation StartLoc, SourceLocation EndLoc,
                      unsigned NumClauses)
       : OMPExecutableDirective(this, OMPAtomicDirectiveClass, OMPD_atomic,
-                               StartLoc, EndLoc, NumClauses, 1) {}
+                               StartLoc, EndLoc, NumClauses, 4) {}
 
   /// \brief Build an empty directive.
   ///
@@ -1448,20 +1448,33 @@ class OMPAtomicDirective : public OMPExe
   explicit OMPAtomicDirective(unsigned NumClauses)
       : OMPExecutableDirective(this, OMPAtomicDirectiveClass, OMPD_atomic,
                                SourceLocation(), SourceLocation(), NumClauses,
-                               1) {}
+                               4) {}
+
+  /// \brief Set 'x' part of the associated expression/statement.
+  void setX(Expr *X) { *std::next(child_begin()) = X; }
+  /// \brief Set 'v' part of the associated expression/statement.
+  void setV(Expr *V) { *std::next(child_begin(), 2) = V; }
+  /// \brief Set 'expr' part of the associated expression/statement.
+  void setExpr(Expr *E) { *std::next(child_begin(), 3) = E; }
 
 public:
-  /// \brief Creates directive with a list of \a Clauses.
+  /// \brief Creates directive with a list of \a Clauses and 'x', 'v' and 'expr'
+  /// parts of the atomic construct (see Section 2.12.6, atomic Construct, for
+  /// detailed description of 'x', 'v' and 'expr').
   ///
   /// \param C AST context.
   /// \param StartLoc Starting location of the directive kind.
   /// \param EndLoc Ending Location of the directive.
   /// \param Clauses List of clauses.
   /// \param AssociatedStmt Statement, associated with the directive.
+  /// \param X 'x' part of the associated expression/statement.
+  /// \param V 'v' part of the associated expression/statement.
+  /// \param Expr 'expr' part of the associated expression/statement.
   ///
   static OMPAtomicDirective *
   Create(const ASTContext &C, SourceLocation StartLoc, SourceLocation EndLoc,
-         ArrayRef<OMPClause *> Clauses, Stmt *AssociatedStmt);
+         ArrayRef<OMPClause *> Clauses, Stmt *AssociatedStmt, Expr *X, Expr *V,
+         Expr *E);
 
   /// \brief Creates an empty directive with the place for \a NumClauses
   /// clauses.
@@ -1472,6 +1485,22 @@ public:
   static OMPAtomicDirective *CreateEmpty(const ASTContext &C,
                                          unsigned NumClauses, EmptyShell);
 
+  /// \brief Get 'x' part of the associated expression/statement.
+  Expr *getX() { return cast_or_null<Expr>(*std::next(child_begin())); }
+  const Expr *getX() const {
+    return cast_or_null<Expr>(*std::next(child_begin()));
+  }
+  /// \brief Get 'v' part of the associated expression/statement.
+  Expr *getV() { return cast_or_null<Expr>(*std::next(child_begin(), 2)); }
+  const Expr *getV() const {
+    return cast_or_null<Expr>(*std::next(child_begin(), 2));
+  }
+  /// \brief Get 'expr' part of the associated expression/statement.
+  Expr *getExpr() { return cast_or_null<Expr>(*std::next(child_begin(), 3)); }
+  const Expr *getExpr() const {
+    return cast_or_null<Expr>(*std::next(child_begin(), 3));
+  }
+
   static bool classof(const Stmt *T) {
     return T->getStmtClass() == OMPAtomicDirectiveClass;
   }

Modified: cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td?rev=222231&r1=222230&r2=222231&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td (original)
+++ cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td Tue Nov 18 04:14:22 2014
@@ -7322,7 +7322,9 @@ def err_omp_parallel_reduction_in_task_f
   "argument of a reduction clause of a %0 construct must not appear in a firstprivate clause on a task construct">;
 def err_omp_atomic_read_not_expression_statement : Error<
   "the statement for 'atomic read' must be an expression statement of form 'v = x;',"
-  " where v and x are both l-value expressions with scalar type">;
+  " where v and x are both lvalue expressions with scalar type">;
+def note_omp_atomic_read: Note<
+  "%select{expected an expression statement|expected built-in assignment operator|expected expression of scalar type|expected lvalue expression}0">;
 def err_omp_atomic_write_not_expression_statement : Error<
   "the statement for 'atomic write' must be an expression statement of form 'x = expr;',"
   " where x is an l-value expression with scalar type">;

Modified: cfe/trunk/lib/AST/Stmt.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/Stmt.cpp?rev=222231&r1=222230&r2=222231&view=diff
==============================================================================
--- cfe/trunk/lib/AST/Stmt.cpp (original)
+++ cfe/trunk/lib/AST/Stmt.cpp Tue Nov 18 04:14:22 2014
@@ -1924,19 +1924,21 @@ OMPOrderedDirective *OMPOrderedDirective
   return new (Mem) OMPOrderedDirective();
 }
 
-OMPAtomicDirective *OMPAtomicDirective::Create(const ASTContext &C,
-                                               SourceLocation StartLoc,
-                                               SourceLocation EndLoc,
-                                               ArrayRef<OMPClause *> Clauses,
-                                               Stmt *AssociatedStmt) {
+OMPAtomicDirective *
+OMPAtomicDirective::Create(const ASTContext &C, SourceLocation StartLoc,
+                           SourceLocation EndLoc, ArrayRef<OMPClause *> Clauses,
+                           Stmt *AssociatedStmt, Expr *X, Expr *V, Expr *E) {
   unsigned Size = llvm::RoundUpToAlignment(sizeof(OMPAtomicDirective),
                                            llvm::alignOf<OMPClause *>());
-  void *Mem =
-      C.Allocate(Size + sizeof(OMPClause *) * Clauses.size() + sizeof(Stmt *));
+  void *Mem = C.Allocate(Size + sizeof(OMPClause *) * Clauses.size() +
+                         4 * sizeof(Stmt *));
   OMPAtomicDirective *Dir =
       new (Mem) OMPAtomicDirective(StartLoc, EndLoc, Clauses.size());
   Dir->setClauses(Clauses);
   Dir->setAssociatedStmt(AssociatedStmt);
+  Dir->setX(X);
+  Dir->setV(V);
+  Dir->setExpr(E);
   return Dir;
 }
 
@@ -1946,7 +1948,7 @@ OMPAtomicDirective *OMPAtomicDirective::
   unsigned Size = llvm::RoundUpToAlignment(sizeof(OMPAtomicDirective),
                                            llvm::alignOf<OMPClause *>());
   void *Mem =
-      C.Allocate(Size + sizeof(OMPClause *) * NumClauses + sizeof(Stmt *));
+      C.Allocate(Size + sizeof(OMPClause *) * NumClauses + 4 * sizeof(Stmt *));
   return new (Mem) OMPAtomicDirective(NumClauses);
 }
 

Modified: cfe/trunk/lib/Sema/SemaOpenMP.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaOpenMP.cpp?rev=222231&r1=222230&r2=222231&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/SemaOpenMP.cpp (original)
+++ cfe/trunk/lib/Sema/SemaOpenMP.cpp Tue Nov 18 04:14:22 2014
@@ -3223,13 +3223,94 @@ StmtResult Sema::ActOnOpenMPAtomicDirect
       }
     }
   }
+
   auto Body = CS->getCapturedStmt();
+  Expr *X = nullptr;
+  Expr *V = nullptr;
+  Expr *E = nullptr;
+  // OpenMP [2.12.6, atomic Construct]
+  // In the next expressions:
+  // * x and v (as applicable) are both l-value expressions with scalar type.
+  // * During the execution of an atomic region, multiple syntactic
+  // occurrences of x must designate the same storage location.
+  // * Neither of v and expr (as applicable) may access the storage location
+  // designated by x.
+  // * Neither of x and expr (as applicable) may access the storage location
+  // designated by v.
+  // * expr is an expression with scalar type.
+  // * binop is one of +, *, -, /, &, ^, |, <<, or >>.
+  // * binop, binop=, ++, and -- are not overloaded operators.
+  // * The expression x binop expr must be numerically equivalent to x binop
+  // (expr). This requirement is satisfied if the operators in expr have
+  // precedence greater than binop, or by using parentheses around expr or
+  // subexpressions of expr.
+  // * The expression expr binop x must be numerically equivalent to (expr)
+  // binop x. This requirement is satisfied if the operators in expr have
+  // precedence equal to or greater than binop, or by using parentheses around
+  // expr or subexpressions of expr.
+  // * For forms that allow multiple occurrences of x, the number of times
+  // that x is evaluated is unspecified.
   if (AtomicKind == OMPC_read) {
-    if (!isa<Expr>(Body)) {
-      Diag(Body->getLocStart(),
-           diag::err_omp_atomic_read_not_expression_statement);
-      return StmtError();
+    enum {
+      NotAnExpression,
+      NotAnAssignmentOp,
+      NotAScalarType,
+      NotAnLValue,
+      NoError
+    } ErrorFound = NoError;
+    SourceLocation ErrorLoc, NoteLoc;
+    SourceRange ErrorRange, NoteRange;
+    // If clause is read:
+    //  v = x;
+    if (auto AtomicBody = dyn_cast<Expr>(Body)) {
+      auto AtomicBinOp =
+          dyn_cast<BinaryOperator>(AtomicBody->IgnoreParenImpCasts());
+      if (AtomicBinOp && AtomicBinOp->getOpcode() == BO_Assign) {
+        X = AtomicBinOp->getRHS()->IgnoreParenImpCasts();
+        V = AtomicBinOp->getLHS()->IgnoreParenImpCasts();
+        if ((X->isInstantiationDependent() || X->getType()->isScalarType()) &&
+            (V->isInstantiationDependent() || V->getType()->isScalarType())) {
+          if (!X->isLValue() || !V->isLValue()) {
+            auto NotLValueExpr = X->isLValue() ? V : X;
+            ErrorFound = NotAnLValue;
+            ErrorLoc = AtomicBinOp->getExprLoc();
+            ErrorRange = AtomicBinOp->getSourceRange();
+            NoteLoc = NotLValueExpr->getExprLoc();
+            NoteRange = NotLValueExpr->getSourceRange();
+          }
+        } else if (!X->isInstantiationDependent() ||
+                   !V->isInstantiationDependent()) {
+          auto NotScalarExpr =
+              (X->isInstantiationDependent() || X->getType()->isScalarType())
+                  ? V
+                  : X;
+          ErrorFound = NotAScalarType;
+          ErrorLoc = AtomicBinOp->getExprLoc();
+          ErrorRange = AtomicBinOp->getSourceRange();
+          NoteLoc = NotScalarExpr->getExprLoc();
+          NoteRange = NotScalarExpr->getSourceRange();
+        }
+      } else {
+        ErrorFound = NotAnAssignmentOp;
+        ErrorLoc = AtomicBody->getExprLoc();
+        ErrorRange = AtomicBody->getSourceRange();
+        NoteLoc = AtomicBinOp ? AtomicBinOp->getOperatorLoc()
+                              : AtomicBody->getExprLoc();
+        NoteRange = AtomicBinOp ? AtomicBinOp->getSourceRange()
+                                : AtomicBody->getSourceRange();
+      }
+    } else {
+      ErrorFound = NotAnExpression;
+      NoteLoc = ErrorLoc = Body->getLocStart();
+      NoteRange = ErrorRange = SourceRange(NoteLoc, NoteLoc);
     }
+    if (ErrorFound != NoError) {
+      Diag(ErrorLoc, diag::err_omp_atomic_read_not_expression_statement)
+          << ErrorRange;
+      Diag(NoteLoc, diag::note_omp_atomic_read) << ErrorFound << NoteRange;
+      return StmtError();
+    } else if (CurContext->isDependentContext())
+      V = X = nullptr;
   } else if (AtomicKind == OMPC_write) {
     if (!isa<Expr>(Body)) {
       Diag(Body->getLocStart(),
@@ -3257,7 +3338,8 @@ StmtResult Sema::ActOnOpenMPAtomicDirect
 
   getCurFunction()->setHasBranchProtectedScope();
 
-  return OMPAtomicDirective::Create(Context, StartLoc, EndLoc, Clauses, AStmt);
+  return OMPAtomicDirective::Create(Context, StartLoc, EndLoc, Clauses, AStmt,
+                                    X, V, E);
 }
 
 StmtResult Sema::ActOnOpenMPTargetDirective(ArrayRef<OMPClause *> Clauses,

Modified: cfe/trunk/lib/Serialization/ASTReaderStmt.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Serialization/ASTReaderStmt.cpp?rev=222231&r1=222230&r2=222231&view=diff
==============================================================================
--- cfe/trunk/lib/Serialization/ASTReaderStmt.cpp (original)
+++ cfe/trunk/lib/Serialization/ASTReaderStmt.cpp Tue Nov 18 04:14:22 2014
@@ -2123,6 +2123,9 @@ void ASTStmtReader::VisitOMPAtomicDirect
   // The NumClauses field was read in ReadStmtFromStream.
   ++Idx;
   VisitOMPExecutableDirective(D);
+  D->setX(Reader.ReadSubExpr());
+  D->setV(Reader.ReadSubExpr());
+  D->setExpr(Reader.ReadSubExpr());
 }
 
 void ASTStmtReader::VisitOMPTargetDirective(OMPTargetDirective *D) {

Modified: cfe/trunk/lib/Serialization/ASTWriterStmt.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Serialization/ASTWriterStmt.cpp?rev=222231&r1=222230&r2=222231&view=diff
==============================================================================
--- cfe/trunk/lib/Serialization/ASTWriterStmt.cpp (original)
+++ cfe/trunk/lib/Serialization/ASTWriterStmt.cpp Tue Nov 18 04:14:22 2014
@@ -1978,6 +1978,9 @@ void ASTStmtWriter::VisitOMPAtomicDirect
   VisitStmt(D);
   Record.push_back(D->getNumClauses());
   VisitOMPExecutableDirective(D);
+  Writer.AddStmt(D->getX());
+  Writer.AddStmt(D->getV());
+  Writer.AddStmt(D->getExpr());
   Code = serialization::STMT_OMP_ATOMIC_DIRECTIVE;
 }
 

Added: cfe/trunk/test/OpenMP/atomic_messages.c
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/OpenMP/atomic_messages.c?rev=222231&view=auto
==============================================================================
--- cfe/trunk/test/OpenMP/atomic_messages.c (added)
+++ cfe/trunk/test/OpenMP/atomic_messages.c Tue Nov 18 04:14:22 2014
@@ -0,0 +1,65 @@
+// RUN: %clang_cc1 -verify -fopenmp=libiomp5 -ferror-limit 100 %s
+
+int foo() {
+L1:
+  foo();
+#pragma omp atomic
+  // expected-error at +1 {{the statement for 'atomic' must be an expression statement of form '++x;', '--x;', 'x++;', 'x--;', 'x binop= expr;', 'x = x binop expr' or 'x = expr binop x', where x is an l-value expression with scalar type}}
+  {
+    foo();
+    goto L1; // expected-error {{use of undeclared label 'L1'}}
+  }
+  goto L2; // expected-error {{use of undeclared label 'L2'}}
+#pragma omp atomic
+  // expected-error at +1 {{the statement for 'atomic' must be an expression statement of form '++x;', '--x;', 'x++;', 'x--;', 'x binop= expr;', 'x = x binop expr' or 'x = expr binop x', where x is an l-value expression with scalar type}}
+  {
+    foo();
+  L2:
+    foo();
+  }
+
+  return 0;
+}
+
+struct S {
+  int a;
+};
+
+int readint() {
+  int a = 0, b = 0;
+// Test for atomic read
+#pragma omp atomic read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected an expression statement}}
+  ;
+#pragma omp atomic read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected built-in assignment operator}}
+  foo();
+#pragma omp atomic read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected built-in assignment operator}}
+  a += b;
+#pragma omp atomic read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected lvalue expression}}
+  a = 0;
+#pragma omp atomic read
+  a = b;
+  // expected-error at +1 {{directive '#pragma omp atomic' cannot contain more than one 'read' clause}}
+#pragma omp atomic read read
+  a = b;
+
+  return 0;
+}
+
+int readS() {
+  struct S a, b;
+  // expected-error at +1 {{directive '#pragma omp atomic' cannot contain more than one 'read' clause}}
+#pragma omp atomic read read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected expression of scalar type}}
+  a = b;
+
+  return a.a;
+}

Propchange: cfe/trunk/test/OpenMP/atomic_messages.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cfe/trunk/test/OpenMP/atomic_messages.c
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Rev URL

Propchange: cfe/trunk/test/OpenMP/atomic_messages.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: cfe/trunk/test/OpenMP/atomic_messages.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/OpenMP/atomic_messages.cpp?rev=222231&r1=222230&r2=222231&view=diff
==============================================================================
--- cfe/trunk/test/OpenMP/atomic_messages.cpp (original)
+++ cfe/trunk/test/OpenMP/atomic_messages.cpp Tue Nov 18 04:14:22 2014
@@ -21,31 +21,78 @@ L1:
   return 0;
 }
 
+struct S {
+  int a;
+  S &operator=(int v) {
+    a = v;
+    return *this;
+  }
+  S &operator+=(const S &s) {
+    a += s.a;
+    return *this;
+  }
+};
+
 template <class T>
 T read() {
-  T a, b = 0;
+  T a = T(), b = T();
 // Test for atomic read
 #pragma omp atomic read
-  // expected-error at +1 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both l-value expressions with scalar type}}
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected an expression statement}}
   ;
-// expected-error at +1 {{directive '#pragma omp atomic' cannot contain more than one 'read' clause}}
+#pragma omp atomic read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected built-in assignment operator}}
+  foo();
+#pragma omp atomic read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected built-in assignment operator}}
+  a += b;
+#pragma omp atomic read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected lvalue expression}}
+  a = 0;
+#pragma omp atomic read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected built-in assignment operator}}
+  a = b;
+  // expected-error at +1 {{directive '#pragma omp atomic' cannot contain more than one 'read' clause}}
 #pragma omp atomic read read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected built-in assignment operator}}
   a = b;
 
-  return T();
+  return a;
 }
 
 int read() {
-  int a, b = 0;
+  int a = 0, b = 0;
 // Test for atomic read
 #pragma omp atomic read
-  // expected-error at +1 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both l-value expressions with scalar type}}
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected an expression statement}}
   ;
-// expected-error at +1 {{directive '#pragma omp atomic' cannot contain more than one 'read' clause}}
+#pragma omp atomic read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected built-in assignment operator}}
+  foo();
+#pragma omp atomic read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected built-in assignment operator}}
+  a += b;
+#pragma omp atomic read
+  // expected-error at +2 {{the statement for 'atomic read' must be an expression statement of form 'v = x;', where v and x are both lvalue expressions with scalar type}}
+  // expected-note at +1 {{expected lvalue expression}}
+  a = 0;
+#pragma omp atomic read
+  a = b;
+  // expected-error at +1 {{directive '#pragma omp atomic' cannot contain more than one 'read' clause}}
 #pragma omp atomic read read
   a = b;
 
-  return read<int>();
+  // expected-note at +1 {{in instantiation of function template specialization 'read<S>' requested here}}
+  return read<int>() + read<S>().a;
 }
 
 template <class T>





More information about the cfe-commits mailing list