NDMeshStreamer: fixing a bug in the array interface due to improper casting
[charm.git] / src / libs / ck-libs / NDMeshStreamer / NDMeshStreamer.h
1 #ifndef NDMESH_STREAMER_H_
2 #define NDMESH_STREAMER_H_
3
4 #include <algorithm>
5 #include "NDMeshStreamer.decl.h"
6 #include "DataItemTypes.h"
7
8 // allocate more total buffer space than the maximum buffering limit but flush 
9 //   upon reaching totalBufferCapacity_
10 #define BUFFER_SIZE_FACTOR 4
11
12 // #define DEBUG_STREAMER
13 // #define CACHE_LOCATIONS
14 // #define SUPPORT_INCOMPLETE_MESH
15
16 struct MeshLocation {
17   int dimension; 
18   int bufferIndex; 
19 }; 
20
21 template<class dtype>
22 class MeshStreamerMessage : public CMessage_MeshStreamerMessage<dtype> {
23 public:
24     int numDataItems;
25     int *destinationPes;
26     dtype *data;
27
28     MeshStreamerMessage(): numDataItems(0) {}   
29
30     int addDataItem(const dtype &dataItem) {
31         data[numDataItems] = dataItem;
32         return ++numDataItems; 
33     }
34
35     void markDestination(const int index, const int destinationPe) {
36         destinationPes[index] = destinationPe;
37     }
38
39     dtype &getDataItem(const int index) {
40         return data[index];
41     }
42 };
43
44 template <class dtype>
45 class MeshStreamerGroupClient : public CBase_MeshStreamerGroupClient<dtype> {
46  public:
47      virtual void receiveCombinedData(MeshStreamerMessage<dtype> *msg);
48      virtual void process(dtype &data)=0; 
49 };
50
51 template <class dtype>
52 class MeshStreamerArrayClient : public CBase_MeshStreamerArrayClient<dtype> {
53  public:
54      // virtual void receiveCombinedData(MeshStreamerMessage<dtype> *msg);
55   // would like to make it pure virtual but charm will try to
56   // instantiate the abstract class, leading to errors
57   virtual void process(dtype &data) {} //=0; 
58   MeshStreamerArrayClient() {}
59   MeshStreamerArrayClient(CkMigrateMessage *msg) {}
60 };
61
62 template <class dtype>
63 class MeshStreamer : public CBase_MeshStreamer<dtype> {
64
65 private:
66     int bufferSize_; 
67     int totalBufferCapacity_;
68     int numDataItemsBuffered_;
69
70     int numMembers_; 
71     int numDimensions_;
72     int *individualDimensionSizes_;
73     int *combinedDimensionSizes_;
74
75     int myIndex_;
76     int *myLocationIndex_;
77
78     CkCallback   userCallback_;
79     int yieldFlag_;
80
81     double progressPeriodInMs_; 
82     bool isPeriodicFlushEnabled_; 
83     double timeOfLastSend_; 
84
85
86     MeshStreamerMessage<dtype> ***dataBuffers_;
87
88 #ifdef CACHE_LOCATIONS
89     MeshLocation *cachedLocations;
90     bool *isCached; 
91 #endif
92
93     MeshLocation determineLocation(int destinationPe);
94
95     void storeMessage(int destinationPe, 
96                       const MeshLocation &destinationCoordinates, 
97                       void *dataItem);
98
99     virtual int copyDataItemIntoMessage(
100                 MeshStreamerMessage<dtype> *destinationBuffer, 
101                 void *dataItemHandle);
102
103     virtual void deliverToDestination(
104                  int destinationPe, 
105                  MeshStreamerMessage<dtype> *destinationBuffer) = 0;
106
107     virtual void localDeliver(dtype &dataItem) = 0; 
108
109     void flushLargestBuffer();
110
111 public:
112
113     MeshStreamer(int totalBufferCapacity, int numDimensions, 
114                  int *dimensionSizes,
115                  int yieldFlag = 0, double progressPeriodInMs = -1.0);
116     ~MeshStreamer();
117
118       // entry
119     void receiveAlongRoute(MeshStreamerMessage<dtype> *msg);
120     void flushDirect();
121     void finish(CkReductionMsg *msg);
122
123       // non entry
124     bool isPeriodicFlushEnabled() {
125       return isPeriodicFlushEnabled_;
126     }
127     virtual void insertData(dtype &dataItem, int destinationPe); 
128     void insertData(void *dataItem, int destinationPe);
129     void doneInserting();
130     void associateCallback(CkCallback &cb, bool automaticFinish = true) { 
131       userCallback_ = cb;
132       if (automaticFinish) {
133         CkStartQD(CkCallback(CkIndex_MeshStreamer<dtype>::finish(NULL), 
134                              this->thisProxy));
135       }
136     }
137     void flushAllBuffers();
138     void registerPeriodicProgressFunction();
139
140     // flushing begins only after enablePeriodicFlushing has been invoked
141
142     void enablePeriodicFlushing(){
143       isPeriodicFlushEnabled_ = true; 
144       registerPeriodicProgressFunction();
145     }
146 };
147
148 template <class dtype>
149 void MeshStreamerGroupClient<dtype>::receiveCombinedData(
150                                 MeshStreamerMessage<dtype> *msg) {
151   for (int i = 0; i < msg->numDataItems; i++) {
152     dtype &data = msg->getDataItem(i);
153     process(data);
154   }
155   delete msg;
156 }
157
158 template <class dtype>
159 MeshStreamer<dtype>::MeshStreamer(
160                      int totalBufferCapacity, int numDimensions, 
161                      int *dimensionSizes, 
162                      int yieldFlag, 
163                      double progressPeriodInMs)
164  :numDimensions_(numDimensions), 
165   totalBufferCapacity_(totalBufferCapacity), 
166   yieldFlag_(yieldFlag), 
167   progressPeriodInMs_(progressPeriodInMs)
168 {
169   // limit total number of messages in system to totalBufferCapacity
170   //   but allocate a factor BUFFER_SIZE_FACTOR more space to take
171   //   advantage of nonuniform filling of buffers
172
173   int sumAlongAllDimensions = 0;   
174   individualDimensionSizes_ = new int[numDimensions_];
175   combinedDimensionSizes_ = new int[numDimensions_ + 1];
176   myLocationIndex_ = new int[numDimensions_];
177   memcpy(individualDimensionSizes_, dimensionSizes, 
178          numDimensions * sizeof(int)); 
179   combinedDimensionSizes_[0] = 1; 
180   for (int i = 0; i < numDimensions; i++) {
181     sumAlongAllDimensions += individualDimensionSizes_[i];
182     combinedDimensionSizes_[i + 1] = 
183       combinedDimensionSizes_[i] * individualDimensionSizes_[i];
184   }
185
186   // except for personalized messages, the buffers for dimensions with the 
187   //   same index as the sender's are not used
188   bufferSize_ = BUFFER_SIZE_FACTOR * totalBufferCapacity 
189     / (sumAlongAllDimensions - numDimensions_ + 1); 
190   if (bufferSize_ <= 0) {
191     bufferSize_ = 1; 
192     CkPrintf("Argument totalBufferCapacity to MeshStreamer constructor "
193              "is invalid. Defaulting to a single buffer per destination.\n");
194   }
195   totalBufferCapacity_ = totalBufferCapacity;
196   numDataItemsBuffered_ = 0; 
197   numMembers_ = CkNumPes(); 
198
199   dataBuffers_ = new MeshStreamerMessage<dtype> **[numDimensions_]; 
200   for (int i = 0; i < numDimensions; i++) {
201     int numMembersAlongDimension = individualDimensionSizes_[i]; 
202     dataBuffers_[i] = 
203       new MeshStreamerMessage<dtype> *[numMembersAlongDimension];
204     for (int j = 0; j < numMembersAlongDimension; j++) {
205       dataBuffers_[i][j] = NULL;
206     }
207   }
208
209   myIndex_ = CkMyPe();
210   int remainder = myIndex_;
211   for (int i = numDimensions_ - 1; i >= 0; i--) {    
212     myLocationIndex_[i] = remainder / combinedDimensionSizes_[i];
213     remainder -= combinedDimensionSizes_[i] * myLocationIndex_[i];
214   }
215
216   isPeriodicFlushEnabled_ = false; 
217
218 #ifdef CACHE_LOCATIONS
219   cachedLocations = new MeshLocation[numMembers_];
220   isCached = new bool[numMembers_];
221   std::fill(isCached, isCached + numMembers_, false);
222 #endif
223
224 }
225
226 template <class dtype>
227 MeshStreamer<dtype>::~MeshStreamer() {
228
229   for (int i = 0; i < numDimensions_; i++) {
230     for (int j=0; j < individualDimensionSizes_[i]; j++) {
231       delete[] dataBuffers_[i][j]; 
232     }
233     delete[] dataBuffers_[i]; 
234   }
235
236   delete[] individualDimensionSizes_;
237   delete[] combinedDimensionSizes_; 
238   delete[] myLocationIndex_;
239
240 #ifdef CACHE_LOCATIONS
241   delete[] cachedLocations;
242   delete[] isCached; 
243 #endif
244
245 }
246
247
248 template <class dtype>
249 inline
250 MeshLocation MeshStreamer<dtype>::determineLocation(int destinationPe) { 
251
252 #ifdef CACHE_LOCATIONS
253   if (isCached[destinationPe]) {    
254     return cachedLocations[destinationPe]; 
255   }
256 #endif
257
258   MeshLocation destinationLocation;
259   int remainder = destinationPe;
260   int dimensionIndex; 
261   for (int i = numDimensions_ - 1; i >= 0; i--) {        
262     dimensionIndex = remainder / combinedDimensionSizes_[i];
263     
264     if (dimensionIndex != myLocationIndex_[i]) {
265       destinationLocation.dimension = i; 
266       destinationLocation.bufferIndex = dimensionIndex; 
267 #ifdef CACHE_LOCATIONS
268       cachedLocations[destinationPe] = destinationLocation;
269       isCached[destinationPe] = true; 
270 #endif
271       return destinationLocation;
272     }
273
274     remainder -= combinedDimensionSizes_[i] * dimensionIndex;
275   }
276
277   // all indices agree - message to oneself
278   destinationLocation.dimension = 0; 
279   destinationLocation.bufferIndex = myLocationIndex_[0];
280   return destinationLocation; 
281 }
282
283 template <class dtype>
284 inline 
285 int MeshStreamer<dtype>::copyDataItemIntoMessage(
286                          MeshStreamerMessage<dtype> *destinationBuffer,
287                          void *dataItemHandle) {
288   return destinationBuffer->addDataItem(*((dtype *)dataItemHandle)); 
289 }
290
291 template <class dtype>
292 inline
293 void MeshStreamer<dtype>::storeMessage(
294                           int destinationPe, 
295                           const MeshLocation& destinationLocation,
296                           void *dataItem) {
297
298   int dimension = destinationLocation.dimension;
299   int bufferIndex = destinationLocation.bufferIndex; 
300   MeshStreamerMessage<dtype> ** messageBuffers = dataBuffers_[dimension];   
301
302
303
304   // allocate new message if necessary
305   if (messageBuffers[bufferIndex] == NULL) {
306     if (dimension == 0) {
307       // personalized messages do not require destination indices
308       messageBuffers[bufferIndex] = 
309         new (0, bufferSize_) MeshStreamerMessage<dtype>();
310     }
311     else {
312       messageBuffers[bufferIndex] = 
313         new (bufferSize_, bufferSize_) MeshStreamerMessage<dtype>();
314     }
315 #ifdef DEBUG_STREAMER
316     CkAssert(messageBuffers[bufferIndex] != NULL);
317 #endif
318   }
319   
320   MeshStreamerMessage<dtype> *destinationBuffer = messageBuffers[bufferIndex];
321   int numBuffered = 
322     copyDataItemIntoMessage(destinationBuffer, dataItem);
323   if (dimension != 0) {
324     destinationBuffer->markDestination(numBuffered-1, destinationPe);
325   }  
326   numDataItemsBuffered_++;
327
328   // send if buffer is full
329   if (numBuffered == bufferSize_) {
330
331     int destinationIndex;
332
333     destinationIndex = myIndex_ + 
334       (bufferIndex - myLocationIndex_[dimension]) * 
335       combinedDimensionSizes_[dimension];
336
337     if (dimension == 0) {
338       deliverToDestination(destinationIndex, destinationBuffer);
339     }
340     else {
341       this->thisProxy[destinationIndex].receiveAlongRoute(destinationBuffer);
342     }
343
344     messageBuffers[bufferIndex] = NULL;
345     numDataItemsBuffered_ -= numBuffered; 
346
347     if (isPeriodicFlushEnabled_) {
348       timeOfLastSend_ = CkWallTimer();
349     }
350
351   }
352
353   // send if total buffering capacity has been reached
354   if (numDataItemsBuffered_ == totalBufferCapacity_) {
355     flushLargestBuffer();
356     if (isPeriodicFlushEnabled_) {
357       timeOfLastSend_ = CkWallTimer();
358     }
359   }
360
361 }
362
363 template <class dtype>
364 void MeshStreamer<dtype>::insertData(void *dataItem, int destinationPe) {
365   static int count = 0;
366
367   MeshLocation destinationLocation = determineLocation(destinationPe);
368   storeMessage(destinationPe, destinationLocation, dataItem); 
369
370   // release control to scheduler if requested by the user, 
371   //   assume caller is threaded entry
372   if (yieldFlag_ && ++count == 1024) {
373     count = 0; 
374     CthYield();
375   }
376
377 }
378
379 template <class dtype>
380 inline
381 void MeshStreamer<dtype>::insertData(dtype &dataItem, int destinationPe) {
382
383   if (destinationPe == CkMyPe()) {
384     localDeliver(dataItem);
385     return;
386   }
387
388   insertData((void *) &dataItem, destinationPe);
389 }
390
391 template <class dtype>
392 void MeshStreamer<dtype>::doneInserting() {
393   this->contribute(CkCallback(CkIndex_MeshStreamer<dtype>::finish(NULL), 
394                               this->thisProxy));
395 }
396
397 template <class dtype>
398 void MeshStreamer<dtype>::finish(CkReductionMsg *msg) {
399
400   isPeriodicFlushEnabled_ = false; 
401   flushDirect();
402
403   if (!userCallback_.isInvalid()) {
404     CkStartQD(userCallback_);
405     userCallback_ = CkCallback();      // nullify the current callback
406   }
407
408   // TODO: TEST IF THIS DELETE STILL CAUSES UNEXPLAINED CRASHES
409   //  delete msg; 
410 }
411
412 template <class dtype>
413 void MeshStreamer<dtype>::receiveAlongRoute(MeshStreamerMessage<dtype> *msg) {
414
415   int destinationPe; 
416   MeshLocation destinationLocation;
417
418   for (int i = 0; i < msg->numDataItems; i++) {
419     destinationPe = msg->destinationPes[i];
420     dtype &dataItem = msg->getDataItem(i);
421     destinationLocation = determineLocation(destinationPe);
422     if (destinationPe == CkMyPe()) {
423       localDeliver(dataItem);
424     }
425     else {
426       storeMessage(destinationPe, destinationLocation, &dataItem);   
427     }
428   }
429
430   delete msg;
431
432 }
433
434 template <class dtype>
435 void MeshStreamer<dtype>::flushLargestBuffer() {
436
437   int flushDimension, flushIndex, maxSize, destinationIndex, numBuffers;
438   MeshStreamerMessage<dtype> ** messageBuffers; 
439   MeshStreamerMessage<dtype> *destinationBuffer; 
440
441   for (int i = 0; i < numDimensions_; i++) {
442
443     messageBuffers = dataBuffers_[i]; 
444     numBuffers = individualDimensionSizes_[i]; 
445
446     flushDimension = i; 
447     maxSize = 0;    
448     for (int j = 0; j < numBuffers; j++) {
449       if (messageBuffers[j] != NULL && 
450           messageBuffers[j]->numDataItems > maxSize) {
451         maxSize = messageBuffers[j]->numDataItems;
452         flushIndex = j; 
453       }
454     }
455
456     if (maxSize > 0) {
457
458       messageBuffers = dataBuffers_[flushDimension]; 
459       destinationBuffer = messageBuffers[flushIndex];
460       destinationIndex = myIndex_ + 
461         (flushIndex - myLocationIndex_[flushDimension]) * 
462         combinedDimensionSizes_[flushDimension] ;
463
464       if (destinationBuffer->numDataItems < bufferSize_) {
465         // not sending the full buffer, shrink the message size
466         envelope *env = UsrToEnv(destinationBuffer);
467         env->setTotalsize(env->getTotalsize() - sizeof(dtype) *
468                           (bufferSize_ - destinationBuffer->numDataItems));
469       }
470       numDataItemsBuffered_ -= destinationBuffer->numDataItems;
471
472       if (flushDimension == 0) {
473         deliverToDestination(destinationIndex, destinationBuffer);
474       }
475       else {
476         this->thisProxy[destinationIndex].receiveAlongRoute(destinationBuffer);
477       }
478       messageBuffers[flushIndex] = NULL;
479
480     }
481
482   }
483 }
484
485 template <class dtype>
486 void MeshStreamer<dtype>::flushAllBuffers() {
487
488   MeshStreamerMessage<dtype> **messageBuffers; 
489   int numBuffers; 
490
491   for (int i = 0; i < numDimensions_; i++) {
492
493     messageBuffers = dataBuffers_[i]; 
494     numBuffers = individualDimensionSizes_[i]; 
495
496     for (int j = 0; j < numBuffers; j++) {
497
498       if(messageBuffers[j] == NULL) {
499         continue;
500       }
501
502       numDataItemsBuffered_ -= messageBuffers[j]->numDataItems;
503
504       if (i == 0) {
505         int destinationPe = myIndex_ + j - myLocationIndex_[i];
506         deliverToDestination(destinationPe, messageBuffers[j]);
507       }  
508       else {
509
510         for (int k = 0; k < messageBuffers[j]->numDataItems; k++) {
511
512           MeshStreamerMessage<dtype> *directMsg = 
513             new (0, 1) MeshStreamerMessage<dtype>();
514 #ifdef DEBUG_STREAMER
515           CkAssert(directMsg != NULL);
516 #endif
517           int destinationPe = messageBuffers[j]->destinationPes[k]; 
518           dtype &dataItem = messageBuffers[j]->getDataItem(k);   
519           directMsg->addDataItem(dataItem);
520           deliverToDestination(destinationPe,directMsg);
521         }
522         delete messageBuffers[j];
523       }
524       messageBuffers[j] = NULL;
525     }
526   }
527 }
528
529 template <class dtype>
530 void MeshStreamer<dtype>::flushDirect(){
531
532     if (!isPeriodicFlushEnabled_ || 
533         1000 * (CkWallTimer() - timeOfLastSend_) >= progressPeriodInMs_) {
534       flushAllBuffers();
535     }
536
537     if (isPeriodicFlushEnabled_) {
538       timeOfLastSend_ = CkWallTimer();
539     }
540
541 #ifdef DEBUG_STREAMER
542     //CkPrintf("[%d] numDataItemsBuffered_: %d\n", CkMyPe(), numDataItemsBuffered_);
543     CkAssert(numDataItemsBuffered_ == 0); 
544 #endif
545
546 }
547
548 template <class dtype>
549 void periodicProgressFunction(void *MeshStreamerObj, double time) {
550
551   MeshStreamer<dtype> *properObj = 
552     static_cast<MeshStreamer<dtype>*>(MeshStreamerObj); 
553
554   if (properObj->isPeriodicFlushEnabled()) {
555     properObj->flushDirect();
556     properObj->registerPeriodicProgressFunction();
557   }
558 }
559
560 template <class dtype>
561 void MeshStreamer<dtype>::registerPeriodicProgressFunction() {
562   CcdCallFnAfter(periodicProgressFunction<dtype>, (void *) this, 
563                  progressPeriodInMs_); 
564 }
565
566
567 template <class dtype>
568 class GroupMeshStreamer : public MeshStreamer<dtype> {
569 private:
570
571   CProxy_MeshStreamerGroupClient<dtype> clientProxy_;
572   MeshStreamerGroupClient<dtype> *clientObj_;
573
574   void deliverToDestination(int destinationPe, 
575                             MeshStreamerMessage<dtype> *destinationBuffer) {
576     clientProxy_[destinationPe].receiveCombinedData(destinationBuffer);
577   }
578
579   void localDeliver(dtype &dataItem) {
580     clientObj_->process(dataItem);
581   }
582
583 public:
584
585   GroupMeshStreamer(int totalBufferCapacity, int numDimensions,
586                     int *dimensionSizes, 
587                     const CProxy_MeshStreamerGroupClient<dtype> &clientProxy,
588                     int yieldFlag = 0, double progressPeriodInMs = -1.0)
589    :MeshStreamer<dtype>(totalBufferCapacity, numDimensions, dimensionSizes, 
590                          yieldFlag, progressPeriodInMs) 
591   {
592     clientProxy_ = clientProxy; 
593     clientObj_ = 
594       ((MeshStreamerGroupClient<dtype> *)CkLocalBranch(clientProxy_));
595   }
596
597
598 };
599
600 template <class dtype>
601 class ArrayMeshStreamer : public MeshStreamer<ArrayDataItem<dtype> > {
602 private:
603
604   CProxy_MeshStreamerArrayClient<dtype> clientProxy_;
605   CkArray *clientArrayMgr_;
606   MeshStreamerArrayClient<dtype> *clientObj_;
607
608
609   void deliverToDestination(
610        int destinationPe, 
611        MeshStreamerMessage<ArrayDataItem<dtype> > *destinationBuffer) { 
612     ( (CProxy_ArrayMeshStreamer<dtype>) 
613       this->thisProxy )[destinationPe].receiveArrayData(destinationBuffer);
614   }
615
616   void localDeliver(ArrayDataItem<dtype> &packedDataItem) {
617     int arrayId = packedDataItem.arrayIndex; 
618     MeshStreamerArrayClient<dtype> *clientObj = 
619       clientProxy_[arrayId].ckLocal();
620
621     if (clientObj != NULL) {
622       clientObj->process(packedDataItem.dataItem);
623     }
624     else {
625       // array element is no longer present locally - redeliver using proxy
626       clientProxy_[arrayId].process(packedDataItem.dataItem);
627     }
628   }
629
630 public:
631
632   struct DataItemHandle {
633     int arrayIndex; 
634     dtype *dataItem;
635   };
636
637   ArrayMeshStreamer(int totalBufferCapacity, int numDimensions,
638                     int *dimensionSizes, 
639                     const CProxy_MeshStreamerArrayClient<dtype> &clientProxy,
640                     int yieldFlag = 0, double progressPeriodInMs = -1.0)
641     :MeshStreamer<ArrayDataItem<dtype> >(totalBufferCapacity, numDimensions, 
642                                         dimensionSizes, yieldFlag, 
643                                         progressPeriodInMs) 
644   {
645     clientProxy_ = clientProxy; 
646     clientArrayMgr_ = clientProxy_.ckLocalBranch();
647   }
648
649   void receiveArrayData(MeshStreamerMessage<ArrayDataItem<dtype> > *msg) {
650     for (int i = 0; i < msg->numDataItems; i++) {
651       ArrayDataItem<dtype> &packedData = msg->getDataItem(i);
652       localDeliver(packedData);
653     }
654     delete msg;
655   }
656
657   void insertData(dtype &dataItem, int arrayIndex) {
658
659     int destinationPe = 
660       clientArrayMgr_->lastKnown(clientProxy_[arrayIndex].ckGetIndex());
661     static ArrayDataItem<dtype> packedDataItem;
662     if (destinationPe == CkMyPe()) {
663       // copying here is necessary - user code should not be 
664       // passed back a reference to the original item
665       packedDataItem.arrayIndex = arrayIndex; 
666       packedDataItem.dataItem = dataItem;
667       localDeliver(packedDataItem);
668       return;
669     }
670
671     // this implementation avoids copying an item before transfer into message
672
673     static DataItemHandle tempHandle; 
674     tempHandle.arrayIndex = arrayIndex; 
675     tempHandle.dataItem = &dataItem;
676
677     MeshStreamer<ArrayDataItem<dtype> >::insertData(&tempHandle, destinationPe);
678
679   }
680
681   int copyDataItemIntoMessage(
682       MeshStreamerMessage<ArrayDataItem <dtype> > *destinationBuffer, 
683       void *dataItemHandle) {
684
685     int numDataItems = destinationBuffer->numDataItems;
686     DataItemHandle *tempHandle = (DataItemHandle *) dataItemHandle;
687     (destinationBuffer->data)[numDataItems].dataItem = 
688       *(tempHandle->dataItem);
689     (destinationBuffer->data)[numDataItems].arrayIndex = 
690       tempHandle->arrayIndex;
691     return ++destinationBuffer->numDataItems;
692   }
693
694 };
695
696 #define CK_TEMPLATES_ONLY
697 #include "NDMeshStreamer.def.h"
698 #undef CK_TEMPLATES_ONLY
699
700 #endif