[llvm] r257452 - [Orc] Add overloads of RPC::handle and RPC::expect that take member functions as

David Blaikie via llvm-commits llvm-commits at lists.llvm.org
Thu Jan 14 16:14:53 PST 2016


On Mon, Jan 11, 2016 at 10:48 PM, Lang Hames via llvm-commits <
llvm-commits at lists.llvm.org> wrote:

> Author: lhames
> Date: Tue Jan 12 00:48:52 2016
> New Revision: 257452
>
> URL: http://llvm.org/viewvc/llvm-project?rev=257452&view=rev
> Log:
> [Orc] Add overloads of RPC::handle and RPC::expect that take member
> functions as
> handlers.
>
> It is expected that RPC handlers will usually be member functions.
> Accepting them
> directly in handle and expect allows for the remove of a lot of lambdas an
> explicit error variables.
>
> This patch also uses this new feature to substantially tidy up the
> OrcRemoteTargetServer class.
>
> Modified:
>     llvm/trunk/include/llvm/ExecutionEngine/Orc/OrcRemoteTargetServer.h
>     llvm/trunk/include/llvm/ExecutionEngine/Orc/RPCUtils.h
>
> Modified:
> llvm/trunk/include/llvm/ExecutionEngine/Orc/OrcRemoteTargetServer.h
> URL:
> http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/ExecutionEngine/Orc/OrcRemoteTargetServer.h?rev=257452&r1=257451&r2=257452&view=diff
>
> ==============================================================================
> --- llvm/trunk/include/llvm/ExecutionEngine/Orc/OrcRemoteTargetServer.h
> (original)
> +++ llvm/trunk/include/llvm/ExecutionEngine/Orc/OrcRemoteTargetServer.h
> Tue Jan 12 00:48:52 2016
> @@ -43,41 +43,54 @@ public:
>    }
>
>    std::error_code handleKnownProcedure(JITProcId Id) {
> +    typedef OrcRemoteTargetServer ThisT;
> +
>      DEBUG(dbgs() << "Handling known proc: " << getJITProcIdName(Id) <<
> "\n");
>
>      switch (Id) {
>      case CallIntVoidId:
> -      return handleCallIntVoid();
> +      return handle<CallIntVoid>(Channel, *this,
> &ThisT::handleCallIntVoid);
>

Might be able to stamp this switch out with a macro/.def file too.


>      case CallMainId:
> -      return handleCallMain();
> +      return handle<CallMain>(Channel, *this, &ThisT::handleCallMain);
>      case CallVoidVoidId:
> -      return handleCallVoidVoid();
> +      return handle<CallVoidVoid>(Channel, *this,
> &ThisT::handleCallVoidVoid);
>      case CreateRemoteAllocatorId:
> -      return handleCreateRemoteAllocator();
> +      return handle<CreateRemoteAllocator>(Channel, *this,
> +
>  &ThisT::handleCreateRemoteAllocator);
>      case CreateIndirectStubsOwnerId:
> -      return handleCreateIndirectStubsOwner();
> +      return handle<CreateIndirectStubsOwner>(
> +          Channel, *this, &ThisT::handleCreateIndirectStubsOwner);
>      case DestroyRemoteAllocatorId:
> -      return handleDestroyRemoteAllocator();
> +      return handle<DestroyRemoteAllocator>(
> +          Channel, *this, &ThisT::handleDestroyRemoteAllocator);
> +    case DestroyIndirectStubsOwnerId:
> +      return handle<DestroyIndirectStubsOwner>(
> +          Channel, *this, &ThisT::handleDestroyIndirectStubsOwner);
>      case EmitIndirectStubsId:
> -      return handleEmitIndirectStubs();
> +      return handle<EmitIndirectStubs>(Channel, *this,
> +                                       &ThisT::handleEmitIndirectStubs);
>      case EmitResolverBlockId:
> -      return handleEmitResolverBlock();
> +      return handle<EmitResolverBlock>(Channel, *this,
> +                                       &ThisT::handleEmitResolverBlock);
>      case EmitTrampolineBlockId:
> -      return handleEmitTrampolineBlock();
> +      return handle<EmitTrampolineBlock>(Channel, *this,
> +
>  &ThisT::handleEmitTrampolineBlock);
>      case GetSymbolAddressId:
> -      return handleGetSymbolAddress();
> +      return handle<GetSymbolAddress>(Channel, *this,
> +                                      &ThisT::handleGetSymbolAddress);
>      case GetRemoteInfoId:
> -      return handleGetRemoteInfo();
> +      return handle<GetRemoteInfo>(Channel, *this,
> &ThisT::handleGetRemoteInfo);
>      case ReadMemId:
> -      return handleReadMem();
> +      return handle<ReadMem>(Channel, *this, &ThisT::handleReadMem);
>      case ReserveMemId:
> -      return handleReserveMem();
> +      return handle<ReserveMem>(Channel, *this, &ThisT::handleReserveMem);
>      case SetProtectionsId:
> -      return handleSetProtections();
> +      return handle<SetProtections>(Channel, *this,
> +                                    &ThisT::handleSetProtections);
>      case WriteMemId:
> -      return handleWriteMem();
> +      return handle<WriteMem>(Channel, *this, &ThisT::handleWriteMem);
>      case WritePtrId:
> -      return handleWritePtr();
> +      return handle<WritePtr>(Channel, *this, &ThisT::handleWritePtr);
>      default:
>        return orcError(OrcErrorCode::UnexpectedRPCCall);
>      }
> @@ -160,16 +173,10 @@ private:
>      return CompiledFnAddr;
>    }
>
> -  std::error_code handleCallIntVoid() {
> +  std::error_code handleCallIntVoid(TargetAddress Addr) {
>      typedef int (*IntVoidFnTy)();
> -
> -    IntVoidFnTy Fn = nullptr;
> -    if (std::error_code EC =
> -            handle<CallIntVoid>(Channel, [&](TargetAddress Addr) {
> -              Fn =
> reinterpret_cast<IntVoidFnTy>(static_cast<uintptr_t>(Addr));
> -              return std::error_code();
> -            }))
> -      return EC;
> +    IntVoidFnTy Fn =
> +        reinterpret_cast<IntVoidFnTy>(static_cast<uintptr_t>(Addr));
>
>      DEBUG(dbgs() << "  Calling "
>                   << reinterpret_cast<void
> *>(reinterpret_cast<intptr_t>(Fn))
> @@ -180,19 +187,11 @@ private:
>      return call<CallIntVoidResponse>(Channel, Result);
>    }
>
> -  std::error_code handleCallMain() {
> +  std::error_code handleCallMain(TargetAddress Addr,
> +                                 std::vector<std::string> Args) {
>      typedef int (*MainFnTy)(int, const char *[]);
>
> -    MainFnTy Fn = nullptr;
> -    std::vector<std::string> Args;
> -    if (std::error_code EC = handle<CallMain>(
> -            Channel, [&](TargetAddress Addr, std::vector<std::string> &A)
> {
> -              Fn =
> reinterpret_cast<MainFnTy>(static_cast<uintptr_t>(Addr));
> -              Args = std::move(A);
> -              return std::error_code();
> -            }))
> -      return EC;
> -
> +    MainFnTy Fn =
> reinterpret_cast<MainFnTy>(static_cast<uintptr_t>(Addr));
>      int ArgC = Args.size() + 1;
>      int Idx = 1;
>      std::unique_ptr<const char *[]> ArgV(new const char *[ArgC + 1]);
> @@ -207,16 +206,10 @@ private:
>      return call<CallMainResponse>(Channel, Result);
>    }
>
> -  std::error_code handleCallVoidVoid() {
> +  std::error_code handleCallVoidVoid(TargetAddress Addr) {
>      typedef void (*VoidVoidFnTy)();
> -
> -    VoidVoidFnTy Fn = nullptr;
> -    if (std::error_code EC =
> -            handle<CallIntVoid>(Channel, [&](TargetAddress Addr) {
> -              Fn =
> reinterpret_cast<VoidVoidFnTy>(static_cast<uintptr_t>(Addr));
> -              return std::error_code();
> -            }))
> -      return EC;
> +    VoidVoidFnTy Fn =
> +        reinterpret_cast<VoidVoidFnTy>(static_cast<uintptr_t>(Addr));
>
>      DEBUG(dbgs() << "  Calling " << reinterpret_cast<void *>(Fn) << "\n");
>      Fn();
> @@ -225,66 +218,48 @@ private:
>      return call<CallVoidVoidResponse>(Channel);
>    }
>
> -  std::error_code handleCreateRemoteAllocator() {
> -    return handle<CreateRemoteAllocator>(
> -        Channel, [&](ResourceIdMgr::ResourceId Id) {
> -          auto I = Allocators.find(Id);
> -          if (I != Allocators.end())
> -            return orcError(OrcErrorCode::RemoteAllocatorIdAlreadyInUse);
> -          DEBUG(dbgs() << "  Created allocator " << Id << "\n");
> -          Allocators[Id] = Allocator();
> -          return std::error_code();
> -        });
> -  }
> -
> -  std::error_code handleCreateIndirectStubsOwner() {
> -    return handle<CreateIndirectStubsOwner>(
> -        Channel, [&](ResourceIdMgr::ResourceId Id) {
> -          auto I = IndirectStubsOwners.find(Id);
> -          if (I != IndirectStubsOwners.end())
> -            return orcError(
> -                OrcErrorCode::RemoteIndirectStubsOwnerIdAlreadyInUse);
> -          DEBUG(dbgs() << "  Create indirect stubs owner " << Id << "\n");
> -          IndirectStubsOwners[Id] = ISBlockOwnerList();
> -          return std::error_code();
> -        });
> -  }
> -
> -  std::error_code handleDestroyRemoteAllocator() {
> -    return handle<DestroyRemoteAllocator>(
> -        Channel, [&](ResourceIdMgr::ResourceId Id) {
> -          auto I = Allocators.find(Id);
> -          if (I == Allocators.end())
> -            return orcError(OrcErrorCode::RemoteAllocatorDoesNotExist);
> -          Allocators.erase(I);
> -          DEBUG(dbgs() << "  Destroyed allocator " << Id << "\n");
> -          return std::error_code();
> -        });
> -  }
> -
> -  std::error_code handleDestroyIndirectStubsOwner() {
> -    return handle<DestroyIndirectStubsOwner>(
> -        Channel, [&](ResourceIdMgr::ResourceId Id) {
> -          auto I = IndirectStubsOwners.find(Id);
> -          if (I == IndirectStubsOwners.end())
> -            return
> orcError(OrcErrorCode::RemoteIndirectStubsOwnerDoesNotExist);
> -          IndirectStubsOwners.erase(I);
> -          return std::error_code();
> -        });
> -  }
> -
> -  std::error_code handleEmitIndirectStubs() {
> -    ResourceIdMgr::ResourceId ISOwnerId = ~0U;
> -    uint32_t NumStubsRequired = 0;
> -
> -    if (auto EC = handle<EmitIndirectStubs>(
> -            Channel, readArgs(ISOwnerId, NumStubsRequired)))
> -      return EC;
> +  std::error_code handleCreateRemoteAllocator(ResourceIdMgr::ResourceId
> Id) {
> +    auto I = Allocators.find(Id);
> +    if (I != Allocators.end())
> +      return orcError(OrcErrorCode::RemoteAllocatorIdAlreadyInUse);
> +    DEBUG(dbgs() << "  Created allocator " << Id << "\n");
> +    Allocators[Id] = Allocator();
> +    return std::error_code();
> +  }
> +
> +  std::error_code
> handleCreateIndirectStubsOwner(ResourceIdMgr::ResourceId Id) {
> +    auto I = IndirectStubsOwners.find(Id);
> +    if (I != IndirectStubsOwners.end())
> +      return
> orcError(OrcErrorCode::RemoteIndirectStubsOwnerIdAlreadyInUse);
> +    DEBUG(dbgs() << "  Create indirect stubs owner " << Id << "\n");
> +    IndirectStubsOwners[Id] = ISBlockOwnerList();
> +    return std::error_code();
> +  }
> +
> +  std::error_code handleDestroyRemoteAllocator(ResourceIdMgr::ResourceId
> Id) {
> +    auto I = Allocators.find(Id);
> +    if (I == Allocators.end())
> +      return orcError(OrcErrorCode::RemoteAllocatorDoesNotExist);
> +    Allocators.erase(I);
> +    DEBUG(dbgs() << "  Destroyed allocator " << Id << "\n");
> +    return std::error_code();
> +  }
> +
> +  std::error_code
> +  handleDestroyIndirectStubsOwner(ResourceIdMgr::ResourceId Id) {
> +    auto I = IndirectStubsOwners.find(Id);
> +    if (I == IndirectStubsOwners.end())
> +      return orcError(OrcErrorCode::RemoteIndirectStubsOwnerDoesNotExist);
> +    IndirectStubsOwners.erase(I);
> +    return std::error_code();
> +  }
>
> -    DEBUG(dbgs() << "  ISMgr " << ISOwnerId << " request " <<
> NumStubsRequired
> +  std::error_code handleEmitIndirectStubs(ResourceIdMgr::ResourceId Id,
> +                                          uint32_t NumStubsRequired) {
> +    DEBUG(dbgs() << "  ISMgr " << Id << " request " << NumStubsRequired
>                   << " stubs.\n");
>
> -    auto StubOwnerItr = IndirectStubsOwners.find(ISOwnerId);
> +    auto StubOwnerItr = IndirectStubsOwners.find(Id);
>      if (StubOwnerItr == IndirectStubsOwners.end())
>        return orcError(OrcErrorCode::RemoteIndirectStubsOwnerDoesNotExist);
>
> @@ -307,9 +282,6 @@ private:
>    }
>
>    std::error_code handleEmitResolverBlock() {
> -    if (auto EC = handle<EmitResolverBlock>(Channel, doNothing))
> -      return EC;
> -
>      std::error_code EC;
>      ResolverBlock =
> sys::OwningMemoryBlock(sys::Memory::allocateMappedMemory(
>          TargetT::ResolverCodeSize, nullptr,
> @@ -326,11 +298,7 @@ private:
>    }
>
>    std::error_code handleEmitTrampolineBlock() {
> -    if (auto EC = handle<EmitTrampolineBlock>(Channel, doNothing))
> -      return EC;
> -
>      std::error_code EC;
> -
>      auto TrampolineBlock =
>          sys::OwningMemoryBlock(sys::Memory::allocateMappedMemory(
>              sys::Process::getPageSize(), nullptr,
> @@ -358,21 +326,14 @@ private:
>          NumTrampolines);
>    }
>
> -  std::error_code handleGetSymbolAddress() {
> -    std::string SymbolName;
> -    if (auto EC = handle<GetSymbolAddress>(Channel, readArgs(SymbolName)))
> -      return EC;
> -
> -    TargetAddress SymbolAddr = SymbolLookup(SymbolName);
> -    DEBUG(dbgs() << "  Symbol '" << SymbolName
> -                 << "' =  " << format("0x%016x", SymbolAddr) << "\n");
> -    return call<GetSymbolAddressResponse>(Channel, SymbolAddr);
> +  std::error_code handleGetSymbolAddress(const std::string &Name) {
> +    TargetAddress Addr = SymbolLookup(Name);
> +    DEBUG(dbgs() << "  Symbol '" << Name << "' =  " << format("0x%016x",
> Addr)
> +                 << "\n");
> +    return call<GetSymbolAddressResponse>(Channel, Addr);
>    }
>
>    std::error_code handleGetRemoteInfo() {
> -    if (auto EC = handle<GetRemoteInfo>(Channel, doNothing))
> -      return EC;
> -
>      std::string ProcessTriple = sys::getProcessTriple();
>      uint32_t PointerSize = TargetT::PointerSize;
>      uint32_t PageSize = sys::Process::getPageSize();
> @@ -389,16 +350,8 @@ private:
>                                         IndirectStubSize);
>    }
>
> -  std::error_code handleReadMem() {
> -    char *Src = nullptr;
> -    uint64_t Size = 0;
> -    if (std::error_code EC =
> -            handle<ReadMem>(Channel, [&](TargetAddress RSrc, uint64_t
> RSize) {
> -              Src = reinterpret_cast<char
> *>(static_cast<uintptr_t>(RSrc));
> -              Size = RSize;
> -              return std::error_code();
> -            }))
> -      return EC;
> +  std::error_code handleReadMem(TargetAddress RSrc, uint64_t Size) {
> +    char *Src = reinterpret_cast<char *>(static_cast<uintptr_t>(RSrc));
>
>      DEBUG(dbgs() << "  Reading " << Size << " bytes from "
>                   << static_cast<void *>(Src) << "\n");
> @@ -412,62 +365,49 @@ private:
>      return Channel.send();
>    }
>
> -  std::error_code handleReserveMem() {
> +  std::error_code handleReserveMem(ResourceIdMgr::ResourceId Id, uint64_t
> Size,
> +                                   uint32_t Align) {
> +    auto I = Allocators.find(Id);
> +    if (I == Allocators.end())
> +      return orcError(OrcErrorCode::RemoteAllocatorDoesNotExist);
> +    auto &Allocator = I->second;
>      void *LocalAllocAddr = nullptr;
> -
> -    if (std::error_code EC =
> -            handle<ReserveMem>(Channel, [&](ResourceIdMgr::ResourceId Id,
> -                                            uint64_t Size, uint32_t
> Align) {
> -              auto I = Allocators.find(Id);
> -              if (I == Allocators.end())
> -                return
> orcError(OrcErrorCode::RemoteAllocatorDoesNotExist);
> -              auto &Allocator = I->second;
> -              auto EC2 = Allocator.allocate(LocalAllocAddr, Size, Align);
> -              DEBUG(dbgs() << "  Allocator " << Id << " reserved "
> -                           << LocalAllocAddr << " (" << Size
> -                           << " bytes, alignment " << Align << ")\n");
> -              return EC2;
> -            }))
> +    if (auto EC = Allocator.allocate(LocalAllocAddr, Size, Align))
>        return EC;
>
> +    DEBUG(dbgs() << "  Allocator " << Id << " reserved " << LocalAllocAddr
> +                 << " (" << Size << " bytes, alignment " << Align <<
> ")\n");
> +
>      TargetAddress AllocAddr =
>
>  static_cast<TargetAddress>(reinterpret_cast<uintptr_t>(LocalAllocAddr));
>
>      return call<ReserveMemResponse>(Channel, AllocAddr);
>    }
>
> -  std::error_code handleSetProtections() {
> -    return handle<ReserveMem>(Channel, [&](ResourceIdMgr::ResourceId Id,
> -                                           TargetAddress Addr, uint32_t
> Flags) {
> -      auto I = Allocators.find(Id);
> -      if (I == Allocators.end())
> -        return orcError(OrcErrorCode::RemoteAllocatorDoesNotExist);
> -      auto &Allocator = I->second;
> -      void *LocalAddr = reinterpret_cast<void
> *>(static_cast<uintptr_t>(Addr));
> -      DEBUG(dbgs() << "  Allocator " << Id << " set permissions on "
> -                   << LocalAddr << " to "
> -                   << (Flags & sys::Memory::MF_READ ? 'R' : '-')
> -                   << (Flags & sys::Memory::MF_WRITE ? 'W' : '-')
> -                   << (Flags & sys::Memory::MF_EXEC ? 'X' : '-') << "\n");
> -      return Allocator.setProtections(LocalAddr, Flags);
> -    });
> -  }
> -
> -  std::error_code handleWriteMem() {
> -    return handle<WriteMem>(Channel, [&](TargetAddress RDst, uint64_t
> Size) {
> -      char *Dst = reinterpret_cast<char *>(static_cast<uintptr_t>(RDst));
> -      return Channel.readBytes(Dst, Size);
> -    });
> -  }
> -
> -  std::error_code handleWritePtr() {
> -    return handle<WritePtr>(
> -        Channel, [&](TargetAddress Addr, TargetAddress PtrVal) {
> -          uintptr_t *Ptr =
> -              reinterpret_cast<uintptr_t *>(static_cast<uintptr_t>(Addr));
> -          *Ptr = static_cast<uintptr_t>(PtrVal);
> -          return std::error_code();
> -        });
> +  std::error_code handleSetProtections(ResourceIdMgr::ResourceId Id,
> +                                       TargetAddress Addr, uint32_t
> Flags) {
> +    auto I = Allocators.find(Id);
> +    if (I == Allocators.end())
> +      return orcError(OrcErrorCode::RemoteAllocatorDoesNotExist);
> +    auto &Allocator = I->second;
> +    void *LocalAddr = reinterpret_cast<void
> *>(static_cast<uintptr_t>(Addr));
> +    DEBUG(dbgs() << "  Allocator " << Id << " set permissions on " <<
> LocalAddr
> +                 << " to " << (Flags & sys::Memory::MF_READ ? 'R' : '-')
> +                 << (Flags & sys::Memory::MF_WRITE ? 'W' : '-')
> +                 << (Flags & sys::Memory::MF_EXEC ? 'X' : '-') << "\n");
> +    return Allocator.setProtections(LocalAddr, Flags);
> +  }
> +
> +  std::error_code handleWriteMem(TargetAddress RDst, uint64_t Size) {
> +    char *Dst = reinterpret_cast<char *>(static_cast<uintptr_t>(RDst));
> +    return Channel.readBytes(Dst, Size);
> +  }
> +
> +  std::error_code handleWritePtr(TargetAddress Addr, TargetAddress
> PtrVal) {
> +    uintptr_t *Ptr =
> +        reinterpret_cast<uintptr_t *>(static_cast<uintptr_t>(Addr));
> +    *Ptr = static_cast<uintptr_t>(PtrVal);
> +    return std::error_code();
>    }
>
>    ChannelT &Channel;
>
> Modified: llvm/trunk/include/llvm/ExecutionEngine/Orc/RPCUtils.h
> URL:
> http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/ExecutionEngine/Orc/RPCUtils.h?rev=257452&r1=257451&r2=257452&view=diff
>
> ==============================================================================
> --- llvm/trunk/include/llvm/ExecutionEngine/Orc/RPCUtils.h (original)
> +++ llvm/trunk/include/llvm/ExecutionEngine/Orc/RPCUtils.h Tue Jan 12
> 00:48:52 2016
> @@ -69,6 +69,20 @@ protected:
>      }
>    };
>
> +  template <typename ClassT, typename... ArgTs> class MemberFnWrapper {
>

Might be able to use std::bind + std::mem_fn rather than writing your own
wrapper


> +  public:
> +    typedef std::error_code (ClassT::*MethodT)(ArgTs...);
> +    MemberFnWrapper(ClassT &Instance, MethodT Method)
> +        : Instance(Instance), Method(Method) {}
> +    std::error_code operator()(ArgTs &... Args) {
> +      return (Instance.*Method)(Args...);
> +    }
> +
> +  private:
> +    ClassT &Instance;
> +    MethodT Method;
> +  };
> +
>    template <typename... ArgTs> class ReadArgs {
>    public:
>      std::error_code operator()() { return std::error_code(); }
> @@ -193,6 +207,15 @@ public:
>      return HandlerHelper<ChannelT, Proc>::handle(C, Handler);
>    }
>
> +  /// Helper version of 'handle' for calling member functions.
> +  template <typename Proc, typename ClassT, typename... ArgTs>
> +  static std::error_code
> +  handle(ChannelT &C, ClassT &Instance,
> +         std::error_code (ClassT::*HandlerMethod)(ArgTs...)) {
> +    return handle<Proc>(
> +        C, MemberFnWrapper<ClassT, ArgTs...>(Instance, HandlerMethod));
> +  }
> +
>    /// Deserialize a ProcedureIdT from C and verify it matches the id for
> Proc.
>    /// If the id does match, deserialize the arguments and call the handler
>    /// (similarly to handle).
> @@ -208,6 +231,15 @@ public:
>      return handle<Proc>(C, Handler);
>    }
>
> +  /// Helper version of expect for calling member functions.
> +  template <typename Proc, typename ClassT, typename... ArgTs>
> +  static std::error_code
> +  expect(ChannelT &C, ClassT &Instance,
> +         std::error_code (ClassT::*HandlerMethod)(ArgTs...)) {
> +    return expect<Proc>(
> +        C, MemberFnWrapper<ClassT, ArgTs...>(Instance, HandlerMethod));
> +  }
> +
>    /// Helper for handling setter procedures - this method returns a
> functor that
>    /// sets the variables referred to by Args... to values deserialized
> from the
>    /// channel.
>
>
> _______________________________________________
> llvm-commits mailing list
> llvm-commits at lists.llvm.org
> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20160114/326d2bd6/attachment.html>


More information about the llvm-commits mailing list