Home | History | Annotate | Download | only in AST
      1 //===--- ExternalASTSource.h - Abstract External AST Interface --*- C++ -*-===//
      2 //
      3 //                     The LLVM Compiler Infrastructure
      4 //
      5 // This file is distributed under the University of Illinois Open Source
      6 // License. See LICENSE.TXT for details.
      7 //
      8 //===----------------------------------------------------------------------===//
      9 //
     10 //  This file defines the ExternalASTSource interface, which enables
     11 //  construction of AST nodes from some external source.
     12 //
     13 //===----------------------------------------------------------------------===//
     14 #ifndef LLVM_CLANG_AST_EXTERNALASTSOURCE_H
     15 #define LLVM_CLANG_AST_EXTERNALASTSOURCE_H
     16 
     17 #include "clang/AST/CharUnits.h"
     18 #include "clang/AST/DeclBase.h"
     19 #include "clang/Basic/Module.h"
     20 #include "llvm/ADT/DenseMap.h"
     21 
     22 namespace clang {
     23 
     24 class ASTConsumer;
     25 class CXXBaseSpecifier;
     26 class CXXCtorInitializer;
     27 class DeclarationName;
     28 class ExternalSemaSource; // layering violation required for downcasting
     29 class FieldDecl;
     30 class Module;
     31 class NamedDecl;
     32 class RecordDecl;
     33 class Selector;
     34 class Stmt;
     35 class TagDecl;
     36 
     37 /// \brief Abstract interface for external sources of AST nodes.
     38 ///
     39 /// External AST sources provide AST nodes constructed from some
     40 /// external source, such as a precompiled header. External AST
     41 /// sources can resolve types and declarations from abstract IDs into
     42 /// actual type and declaration nodes, and read parts of declaration
     43 /// contexts.
     44 class ExternalASTSource : public RefCountedBase<ExternalASTSource> {
     45   /// Generation number for this external AST source. Must be increased
     46   /// whenever we might have added new redeclarations for existing decls.
     47   uint32_t CurrentGeneration;
     48 
     49   /// \brief Whether this AST source also provides information for
     50   /// semantic analysis.
     51   bool SemaSource;
     52 
     53   friend class ExternalSemaSource;
     54 
     55 public:
     56   ExternalASTSource() : CurrentGeneration(0), SemaSource(false) { }
     57 
     58   virtual ~ExternalASTSource();
     59 
     60   /// \brief RAII class for safely pairing a StartedDeserializing call
     61   /// with FinishedDeserializing.
     62   class Deserializing {
     63     ExternalASTSource *Source;
     64   public:
     65     explicit Deserializing(ExternalASTSource *source) : Source(source) {
     66       assert(Source);
     67       Source->StartedDeserializing();
     68     }
     69     ~Deserializing() {
     70       Source->FinishedDeserializing();
     71     }
     72   };
     73 
     74   /// \brief Get the current generation of this AST source. This number
     75   /// is incremented each time the AST source lazily extends an existing
     76   /// entity.
     77   uint32_t getGeneration() const { return CurrentGeneration; }
     78 
     79   /// \brief Resolve a declaration ID into a declaration, potentially
     80   /// building a new declaration.
     81   ///
     82   /// This method only needs to be implemented if the AST source ever
     83   /// passes back decl sets as VisibleDeclaration objects.
     84   ///
     85   /// The default implementation of this method is a no-op.
     86   virtual Decl *GetExternalDecl(uint32_t ID);
     87 
     88   /// \brief Resolve a selector ID into a selector.
     89   ///
     90   /// This operation only needs to be implemented if the AST source
     91   /// returns non-zero for GetNumKnownSelectors().
     92   ///
     93   /// The default implementation of this method is a no-op.
     94   virtual Selector GetExternalSelector(uint32_t ID);
     95 
     96   /// \brief Returns the number of selectors known to the external AST
     97   /// source.
     98   ///
     99   /// The default implementation of this method is a no-op.
    100   virtual uint32_t GetNumExternalSelectors();
    101 
    102   /// \brief Resolve the offset of a statement in the decl stream into
    103   /// a statement.
    104   ///
    105   /// This operation is meant to be used via a LazyOffsetPtr.  It only
    106   /// needs to be implemented if the AST source uses methods like
    107   /// FunctionDecl::setLazyBody when building decls.
    108   ///
    109   /// The default implementation of this method is a no-op.
    110   virtual Stmt *GetExternalDeclStmt(uint64_t Offset);
    111 
    112   /// \brief Resolve the offset of a set of C++ constructor initializers in
    113   /// the decl stream into an array of initializers.
    114   ///
    115   /// The default implementation of this method is a no-op.
    116   virtual CXXCtorInitializer **GetExternalCXXCtorInitializers(uint64_t Offset);
    117 
    118   /// \brief Resolve the offset of a set of C++ base specifiers in the decl
    119   /// stream into an array of specifiers.
    120   ///
    121   /// The default implementation of this method is a no-op.
    122   virtual CXXBaseSpecifier *GetExternalCXXBaseSpecifiers(uint64_t Offset);
    123 
    124   /// \brief Update an out-of-date identifier.
    125   virtual void updateOutOfDateIdentifier(IdentifierInfo &II) { }
    126 
    127   /// \brief Find all declarations with the given name in the given context,
    128   /// and add them to the context by calling SetExternalVisibleDeclsForName
    129   /// or SetNoExternalVisibleDeclsForName.
    130   /// \return \c true if any declarations might have been found, \c false if
    131   /// we definitely have no declarations with tbis name.
    132   ///
    133   /// The default implementation of this method is a no-op returning \c false.
    134   virtual bool
    135   FindExternalVisibleDeclsByName(const DeclContext *DC, DeclarationName Name);
    136 
    137   /// \brief Ensures that the table of all visible declarations inside this
    138   /// context is up to date.
    139   ///
    140   /// The default implementation of this function is a no-op.
    141   virtual void completeVisibleDeclsMap(const DeclContext *DC);
    142 
    143   /// \brief Retrieve the module that corresponds to the given module ID.
    144   virtual Module *getModule(unsigned ID) { return nullptr; }
    145 
    146   /// Abstracts clang modules and precompiled header files and holds
    147   /// everything needed to generate debug info for an imported module
    148   /// or PCH.
    149   class ASTSourceDescriptor {
    150     StringRef PCHModuleName;
    151     StringRef Path;
    152     StringRef ASTFile;
    153     ASTFileSignature Signature;
    154     const Module *ClangModule = nullptr;
    155 
    156   public:
    157     ASTSourceDescriptor(){};
    158     ASTSourceDescriptor(StringRef Name, StringRef Path, StringRef ASTFile,
    159                         ASTFileSignature Signature)
    160         : PCHModuleName(std::move(Name)), Path(std::move(Path)),
    161           ASTFile(std::move(ASTFile)), Signature(Signature){};
    162     ASTSourceDescriptor(const Module &M);
    163     std::string getModuleName() const;
    164     StringRef getPath() const { return Path; }
    165     StringRef getASTFile() const { return ASTFile; }
    166     ASTFileSignature getSignature() const { return Signature; }
    167     const Module *getModuleOrNull() const { return ClangModule; }
    168   };
    169 
    170   /// Return a descriptor for the corresponding module, if one exists.
    171   virtual llvm::Optional<ASTSourceDescriptor> getSourceDescriptor(unsigned ID);
    172 
    173   enum ExtKind { EK_Always, EK_Never, EK_ReplyHazy };
    174 
    175   virtual ExtKind hasExternalDefinitions(const Decl *D);
    176 
    177   /// \brief Finds all declarations lexically contained within the given
    178   /// DeclContext, after applying an optional filter predicate.
    179   ///
    180   /// \param IsKindWeWant a predicate function that returns true if the passed
    181   /// declaration kind is one we are looking for.
    182   ///
    183   /// The default implementation of this method is a no-op.
    184   virtual void
    185   FindExternalLexicalDecls(const DeclContext *DC,
    186                            llvm::function_ref<bool(Decl::Kind)> IsKindWeWant,
    187                            SmallVectorImpl<Decl *> &Result);
    188 
    189   /// \brief Finds all declarations lexically contained within the given
    190   /// DeclContext.
    191   void FindExternalLexicalDecls(const DeclContext *DC,
    192                                 SmallVectorImpl<Decl *> &Result) {
    193     FindExternalLexicalDecls(DC, [](Decl::Kind) { return true; }, Result);
    194   }
    195 
    196   /// \brief Get the decls that are contained in a file in the Offset/Length
    197   /// range. \p Length can be 0 to indicate a point at \p Offset instead of
    198   /// a range.
    199   virtual void FindFileRegionDecls(FileID File, unsigned Offset,
    200                                    unsigned Length,
    201                                    SmallVectorImpl<Decl *> &Decls);
    202 
    203   /// \brief Gives the external AST source an opportunity to complete
    204   /// the redeclaration chain for a declaration. Called each time we
    205   /// need the most recent declaration of a declaration after the
    206   /// generation count is incremented.
    207   virtual void CompleteRedeclChain(const Decl *D);
    208 
    209   /// \brief Gives the external AST source an opportunity to complete
    210   /// an incomplete type.
    211   virtual void CompleteType(TagDecl *Tag);
    212 
    213   /// \brief Gives the external AST source an opportunity to complete an
    214   /// incomplete Objective-C class.
    215   ///
    216   /// This routine will only be invoked if the "externally completed" bit is
    217   /// set on the ObjCInterfaceDecl via the function
    218   /// \c ObjCInterfaceDecl::setExternallyCompleted().
    219   virtual void CompleteType(ObjCInterfaceDecl *Class);
    220 
    221   /// \brief Loads comment ranges.
    222   virtual void ReadComments();
    223 
    224   /// \brief Notify ExternalASTSource that we started deserialization of
    225   /// a decl or type so until FinishedDeserializing is called there may be
    226   /// decls that are initializing. Must be paired with FinishedDeserializing.
    227   ///
    228   /// The default implementation of this method is a no-op.
    229   virtual void StartedDeserializing();
    230 
    231   /// \brief Notify ExternalASTSource that we finished the deserialization of
    232   /// a decl or type. Must be paired with StartedDeserializing.
    233   ///
    234   /// The default implementation of this method is a no-op.
    235   virtual void FinishedDeserializing();
    236 
    237   /// \brief Function that will be invoked when we begin parsing a new
    238   /// translation unit involving this external AST source.
    239   ///
    240   /// The default implementation of this method is a no-op.
    241   virtual void StartTranslationUnit(ASTConsumer *Consumer);
    242 
    243   /// \brief Print any statistics that have been gathered regarding
    244   /// the external AST source.
    245   ///
    246   /// The default implementation of this method is a no-op.
    247   virtual void PrintStats();
    248 
    249 
    250   /// \brief Perform layout on the given record.
    251   ///
    252   /// This routine allows the external AST source to provide an specific
    253   /// layout for a record, overriding the layout that would normally be
    254   /// constructed. It is intended for clients who receive specific layout
    255   /// details rather than source code (such as LLDB). The client is expected
    256   /// to fill in the field offsets, base offsets, virtual base offsets, and
    257   /// complete object size.
    258   ///
    259   /// \param Record The record whose layout is being requested.
    260   ///
    261   /// \param Size The final size of the record, in bits.
    262   ///
    263   /// \param Alignment The final alignment of the record, in bits.
    264   ///
    265   /// \param FieldOffsets The offset of each of the fields within the record,
    266   /// expressed in bits. All of the fields must be provided with offsets.
    267   ///
    268   /// \param BaseOffsets The offset of each of the direct, non-virtual base
    269   /// classes. If any bases are not given offsets, the bases will be laid
    270   /// out according to the ABI.
    271   ///
    272   /// \param VirtualBaseOffsets The offset of each of the virtual base classes
    273   /// (either direct or not). If any bases are not given offsets, the bases will be laid
    274   /// out according to the ABI.
    275   ///
    276   /// \returns true if the record layout was provided, false otherwise.
    277   virtual bool layoutRecordType(
    278       const RecordDecl *Record, uint64_t &Size, uint64_t &Alignment,
    279       llvm::DenseMap<const FieldDecl *, uint64_t> &FieldOffsets,
    280       llvm::DenseMap<const CXXRecordDecl *, CharUnits> &BaseOffsets,
    281       llvm::DenseMap<const CXXRecordDecl *, CharUnits> &VirtualBaseOffsets);
    282 
    283   //===--------------------------------------------------------------------===//
    284   // Queries for performance analysis.
    285   //===--------------------------------------------------------------------===//
    286 
    287   struct MemoryBufferSizes {
    288     size_t malloc_bytes;
    289     size_t mmap_bytes;
    290 
    291     MemoryBufferSizes(size_t malloc_bytes, size_t mmap_bytes)
    292     : malloc_bytes(malloc_bytes), mmap_bytes(mmap_bytes) {}
    293   };
    294 
    295   /// Return the amount of memory used by memory buffers, breaking down
    296   /// by heap-backed versus mmap'ed memory.
    297   MemoryBufferSizes getMemoryBufferSizes() const {
    298     MemoryBufferSizes sizes(0, 0);
    299     getMemoryBufferSizes(sizes);
    300     return sizes;
    301   }
    302 
    303   virtual void getMemoryBufferSizes(MemoryBufferSizes &sizes) const;
    304 
    305 protected:
    306   static DeclContextLookupResult
    307   SetExternalVisibleDeclsForName(const DeclContext *DC,
    308                                  DeclarationName Name,
    309                                  ArrayRef<NamedDecl*> Decls);
    310 
    311   static DeclContextLookupResult
    312   SetNoExternalVisibleDeclsForName(const DeclContext *DC,
    313                                    DeclarationName Name);
    314 
    315   /// \brief Increment the current generation.
    316   uint32_t incrementGeneration(ASTContext &C);
    317 };
    318 
    319 /// \brief A lazy pointer to an AST node (of base type T) that resides
    320 /// within an external AST source.
    321 ///
    322 /// The AST node is identified within the external AST source by a
    323 /// 63-bit offset, and can be retrieved via an operation on the
    324 /// external AST source itself.
    325 template<typename T, typename OffsT, T* (ExternalASTSource::*Get)(OffsT Offset)>
    326 struct LazyOffsetPtr {
    327   /// \brief Either a pointer to an AST node or the offset within the
    328   /// external AST source where the AST node can be found.
    329   ///
    330   /// If the low bit is clear, a pointer to the AST node. If the low
    331   /// bit is set, the upper 63 bits are the offset.
    332   mutable uint64_t Ptr;
    333 
    334 public:
    335   LazyOffsetPtr() : Ptr(0) { }
    336 
    337   explicit LazyOffsetPtr(T *Ptr) : Ptr(reinterpret_cast<uint64_t>(Ptr)) { }
    338   explicit LazyOffsetPtr(uint64_t Offset) : Ptr((Offset << 1) | 0x01) {
    339     assert((Offset << 1 >> 1) == Offset && "Offsets must require < 63 bits");
    340     if (Offset == 0)
    341       Ptr = 0;
    342   }
    343 
    344   LazyOffsetPtr &operator=(T *Ptr) {
    345     this->Ptr = reinterpret_cast<uint64_t>(Ptr);
    346     return *this;
    347   }
    348 
    349   LazyOffsetPtr &operator=(uint64_t Offset) {
    350     assert((Offset << 1 >> 1) == Offset && "Offsets must require < 63 bits");
    351     if (Offset == 0)
    352       Ptr = 0;
    353     else
    354       Ptr = (Offset << 1) | 0x01;
    355 
    356     return *this;
    357   }
    358 
    359   /// \brief Whether this pointer is non-NULL.
    360   ///
    361   /// This operation does not require the AST node to be deserialized.
    362   explicit operator bool() const { return Ptr != 0; }
    363 
    364   /// \brief Whether this pointer is non-NULL.
    365   ///
    366   /// This operation does not require the AST node to be deserialized.
    367   bool isValid() const { return Ptr != 0; }
    368 
    369   /// \brief Whether this pointer is currently stored as an offset.
    370   bool isOffset() const { return Ptr & 0x01; }
    371 
    372   /// \brief Retrieve the pointer to the AST node that this lazy pointer
    373   ///
    374   /// \param Source the external AST source.
    375   ///
    376   /// \returns a pointer to the AST node.
    377   T* get(ExternalASTSource *Source) const {
    378     if (isOffset()) {
    379       assert(Source &&
    380              "Cannot deserialize a lazy pointer without an AST source");
    381       Ptr = reinterpret_cast<uint64_t>((Source->*Get)(Ptr >> 1));
    382     }
    383     return reinterpret_cast<T*>(Ptr);
    384   }
    385 };
    386 
    387 /// \brief A lazy value (of type T) that is within an AST node of type Owner,
    388 /// where the value might change in later generations of the external AST
    389 /// source.
    390 template<typename Owner, typename T, void (ExternalASTSource::*Update)(Owner)>
    391 struct LazyGenerationalUpdatePtr {
    392   /// A cache of the value of this pointer, in the most recent generation in
    393   /// which we queried it.
    394   struct LazyData {
    395     LazyData(ExternalASTSource *Source, T Value)
    396         : ExternalSource(Source), LastGeneration(0), LastValue(Value) {}
    397     ExternalASTSource *ExternalSource;
    398     uint32_t LastGeneration;
    399     T LastValue;
    400   };
    401 
    402   // Our value is represented as simply T if there is no external AST source.
    403   typedef llvm::PointerUnion<T, LazyData*> ValueType;
    404   ValueType Value;
    405 
    406   LazyGenerationalUpdatePtr(ValueType V) : Value(V) {}
    407 
    408   // Defined in ASTContext.h
    409   static ValueType makeValue(const ASTContext &Ctx, T Value);
    410 
    411 public:
    412   explicit LazyGenerationalUpdatePtr(const ASTContext &Ctx, T Value = T())
    413       : Value(makeValue(Ctx, Value)) {}
    414 
    415   /// Create a pointer that is not potentially updated by later generations of
    416   /// the external AST source.
    417   enum NotUpdatedTag { NotUpdated };
    418   LazyGenerationalUpdatePtr(NotUpdatedTag, T Value = T())
    419       : Value(Value) {}
    420 
    421   /// Forcibly set this pointer (which must be lazy) as needing updates.
    422   void markIncomplete() {
    423     Value.template get<LazyData *>()->LastGeneration = 0;
    424   }
    425 
    426   /// Set the value of this pointer, in the current generation.
    427   void set(T NewValue) {
    428     if (LazyData *LazyVal = Value.template dyn_cast<LazyData*>()) {
    429       LazyVal->LastValue = NewValue;
    430       return;
    431     }
    432     Value = NewValue;
    433   }
    434 
    435   /// Set the value of this pointer, for this and all future generations.
    436   void setNotUpdated(T NewValue) { Value = NewValue; }
    437 
    438   /// Get the value of this pointer, updating its owner if necessary.
    439   T get(Owner O) {
    440     if (LazyData *LazyVal = Value.template dyn_cast<LazyData*>()) {
    441       if (LazyVal->LastGeneration != LazyVal->ExternalSource->getGeneration()) {
    442         LazyVal->LastGeneration = LazyVal->ExternalSource->getGeneration();
    443         (LazyVal->ExternalSource->*Update)(O);
    444       }
    445       return LazyVal->LastValue;
    446     }
    447     return Value.template get<T>();
    448   }
    449 
    450   /// Get the most recently computed value of this pointer without updating it.
    451   T getNotUpdated() const {
    452     if (LazyData *LazyVal = Value.template dyn_cast<LazyData*>())
    453       return LazyVal->LastValue;
    454     return Value.template get<T>();
    455   }
    456 
    457   void *getOpaqueValue() { return Value.getOpaqueValue(); }
    458   static LazyGenerationalUpdatePtr getFromOpaqueValue(void *Ptr) {
    459     return LazyGenerationalUpdatePtr(ValueType::getFromOpaqueValue(Ptr));
    460   }
    461 };
    462 } // end namespace clang
    463 
    464 /// Specialize PointerLikeTypeTraits to allow LazyGenerationalUpdatePtr to be
    465 /// placed into a PointerUnion.
    466 namespace llvm {
    467 template<typename Owner, typename T,
    468          void (clang::ExternalASTSource::*Update)(Owner)>
    469 struct PointerLikeTypeTraits<
    470     clang::LazyGenerationalUpdatePtr<Owner, T, Update>> {
    471   typedef clang::LazyGenerationalUpdatePtr<Owner, T, Update> Ptr;
    472   static void *getAsVoidPointer(Ptr P) { return P.getOpaqueValue(); }
    473   static Ptr getFromVoidPointer(void *P) { return Ptr::getFromOpaqueValue(P); }
    474   enum {
    475     NumLowBitsAvailable = PointerLikeTypeTraits<T>::NumLowBitsAvailable - 1
    476   };
    477 };
    478 }
    479 
    480 namespace clang {
    481 /// \brief Represents a lazily-loaded vector of data.
    482 ///
    483 /// The lazily-loaded vector of data contains data that is partially loaded
    484 /// from an external source and partially added by local translation. The
    485 /// items loaded from the external source are loaded lazily, when needed for
    486 /// iteration over the complete vector.
    487 template<typename T, typename Source,
    488          void (Source::*Loader)(SmallVectorImpl<T>&),
    489          unsigned LoadedStorage = 2, unsigned LocalStorage = 4>
    490 class LazyVector {
    491   SmallVector<T, LoadedStorage> Loaded;
    492   SmallVector<T, LocalStorage> Local;
    493 
    494 public:
    495   /// Iteration over the elements in the vector.
    496   ///
    497   /// In a complete iteration, the iterator walks the range [-M, N),
    498   /// where negative values are used to indicate elements
    499   /// loaded from the external source while non-negative values are used to
    500   /// indicate elements added via \c push_back().
    501   /// However, to provide iteration in source order (for, e.g., chained
    502   /// precompiled headers), dereferencing the iterator flips the negative
    503   /// values (corresponding to loaded entities), so that position -M
    504   /// corresponds to element 0 in the loaded entities vector, position -M+1
    505   /// corresponds to element 1 in the loaded entities vector, etc. This
    506   /// gives us a reasonably efficient, source-order walk.
    507   ///
    508   /// We define this as a wrapping iterator around an int. The
    509   /// iterator_adaptor_base class forwards the iterator methods to basic integer
    510   /// arithmetic.
    511   class iterator
    512       : public llvm::iterator_adaptor_base<
    513             iterator, int, std::random_access_iterator_tag, T, int, T *, T &> {
    514     LazyVector *Self;
    515 
    516     iterator(LazyVector *Self, int Position)
    517         : iterator::iterator_adaptor_base(Position), Self(Self) {}
    518 
    519     bool isLoaded() const { return this->I < 0; }
    520     friend class LazyVector;
    521 
    522   public:
    523     iterator() : iterator(nullptr, 0) {}
    524 
    525     typename iterator::reference operator*() const {
    526       if (isLoaded())
    527         return Self->Loaded.end()[this->I];
    528       return Self->Local.begin()[this->I];
    529     }
    530   };
    531 
    532   iterator begin(Source *source, bool LocalOnly = false) {
    533     if (LocalOnly)
    534       return iterator(this, 0);
    535 
    536     if (source)
    537       (source->*Loader)(Loaded);
    538     return iterator(this, -(int)Loaded.size());
    539   }
    540 
    541   iterator end() {
    542     return iterator(this, Local.size());
    543   }
    544 
    545   void push_back(const T& LocalValue) {
    546     Local.push_back(LocalValue);
    547   }
    548 
    549   void erase(iterator From, iterator To) {
    550     if (From.isLoaded() && To.isLoaded()) {
    551       Loaded.erase(&*From, &*To);
    552       return;
    553     }
    554 
    555     if (From.isLoaded()) {
    556       Loaded.erase(&*From, Loaded.end());
    557       From = begin(nullptr, true);
    558     }
    559 
    560     Local.erase(&*From, &*To);
    561   }
    562 };
    563 
    564 /// \brief A lazy pointer to a statement.
    565 typedef LazyOffsetPtr<Stmt, uint64_t, &ExternalASTSource::GetExternalDeclStmt>
    566   LazyDeclStmtPtr;
    567 
    568 /// \brief A lazy pointer to a declaration.
    569 typedef LazyOffsetPtr<Decl, uint32_t, &ExternalASTSource::GetExternalDecl>
    570   LazyDeclPtr;
    571 
    572 /// \brief A lazy pointer to a set of CXXCtorInitializers.
    573 typedef LazyOffsetPtr<CXXCtorInitializer *, uint64_t,
    574                       &ExternalASTSource::GetExternalCXXCtorInitializers>
    575   LazyCXXCtorInitializersPtr;
    576 
    577 /// \brief A lazy pointer to a set of CXXBaseSpecifiers.
    578 typedef LazyOffsetPtr<CXXBaseSpecifier, uint64_t,
    579                       &ExternalASTSource::GetExternalCXXBaseSpecifiers>
    580   LazyCXXBaseSpecifiersPtr;
    581 
    582 } // end namespace clang
    583 
    584 #endif
    585