Moved CcsReply to middle-ccs so it works both in bigsim and in normal charm
[charm.git] / src / conv-ccs / conv-ccs.c
1 /*****************************************************************************
2  * $Source$
3  * $Author$
4  * $Date$
5  * $Revision$
6  *****************************************************************************/
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <errno.h>
11 #include <string.h>
12
13 #include "converse.h"
14 #include "conv-ccs.h"
15 #include "ccs-server.h"
16 #include "sockRoutines.h"
17 #include "queueing.h"
18
19 #if CMK_CCS_AVAILABLE
20
21 /*****************************************************************************
22  *
23  * Converse Client-Server Functions
24  *
25  *****************************************************************************/
26  
27 static void initHandlerRec(CcsHandlerRec *c,const char *name) {
28   if (strlen(name)>=CCS_MAXHANDLER) 
29         CmiAbort("CCS handler names cannot exceed 32 characters");
30   c->name=strdup(name);
31   c->fn=NULL;
32   c->fnOld=NULL;
33   c->userPtr=NULL;
34   c->mergeFn=NULL;
35   c->nCalls=0;
36 }
37
38 static void callHandlerRec(CcsHandlerRec *c,int reqLen,const void *reqData) {
39         c->nCalls++;
40         if (c->fnOld) 
41         { /* Backward compatability version:
42             Pack user data into a converse message (cripes! why bother?);
43             user will delete the message. 
44           */
45                 char *cmsg = (char *) CmiAlloc(CmiReservedHeaderSize+reqLen);
46                 memcpy(cmsg+CmiReservedHeaderSize, reqData, reqLen);
47                 (c->fnOld)(cmsg);
48         }
49         else { /* Pass read-only copy of data straight to user */
50                 (c->fn)(c->userPtr, reqLen, reqData);
51         }
52 }
53
54 /*Table maps handler name to CcsHandler object*/
55 CpvDeclare(CcsHandlerTable, ccsTab);
56
57 CpvStaticDeclare(CcsImplHeader*,ccsReq);/*Identifies CCS requestor (client)*/
58
59 void CcsRegisterHandler(const char *name, CmiHandler fn) {
60   CcsHandlerRec cp;
61   initHandlerRec(&cp,name);
62   cp.fnOld=fn;
63   *(CcsHandlerRec *)CkHashtablePut(CpvAccess(ccsTab),(void *)&cp.name)=cp;
64 }
65 void CcsRegisterHandlerFn(const char *name, CcsHandlerFn fn, void *ptr) {
66   CcsHandlerRec cp;
67   initHandlerRec(&cp,name);
68   cp.fn=fn;
69   cp.userPtr=ptr;
70   *(CcsHandlerRec *)CkHashtablePut(CpvAccess(ccsTab),(void *)&cp.name)=cp;
71 }
72 CcsHandlerRec *CcsGetHandler(const char *name) {
73   return CkHashtableGet(CpvAccess(ccsTab),(void *)&name);
74 }
75 void CcsSetMergeFn(const char *name, CmiReduceMergeFn newMerge) {
76   CcsHandlerRec *rec=(CcsHandlerRec *)CkHashtableGet(CpvAccess(ccsTab),(void *)&name);
77   if (rec==NULL) {
78     CmiAbort("CCS: Unknown CCS handler name.\n");
79   }
80   rec->mergeFn=newMerge;
81   rec->redID=CmiGetGlobalReduction();
82 }
83
84 void * CcsMerge_concat(int *size,void *local,void **remote,int n) {
85   CcsImplHeader *hdr;
86   int total = *size;
87   void *reply;
88   char *ptr;
89   int i;
90   for (i=0; i<n; ++i) {
91     hdr = (CcsImplHeader*)(((char*)remote[i])+CmiReservedHeaderSize);
92     total += ChMessageInt(hdr->len);
93   }
94   reply = CmiAlloc(total);
95   memcpy(reply, local, *size);
96   ((CcsImplHeader*)(((char*)reply)+CmiReservedHeaderSize))->len = ChMessageInt_new(total-CmiReservedHeaderSize-sizeof(CcsImplHeader));
97   CmiFree(local);
98   ptr = ((char*)reply)+*size;
99   for (i=0; i<n; ++i) {
100     int len = ChMessageInt(((CcsImplHeader*)(((char*)remote[i])+CmiReservedHeaderSize))->len);
101     memcpy(ptr, ((char*)remote[i])+CmiReservedHeaderSize+sizeof(CcsImplHeader), len);
102     ptr += len;
103   }
104   *size = total;
105   return reply;
106 }
107
108 #define SIMPLE_REDUCTION(name, dataType, loop) \
109 void * CcsMerge_##name(int *size,void *local,void **remote,int n) { \
110   int i, m; \
111   CcsImplHeader *hdrLocal = (CcsImplHeader*)(((char*)local)+CmiReservedHeaderSize); \
112   int lenLocal = ChMessageInt(hdrLocal->len); \
113   int nElem = lenLocal / sizeof(dataType); \
114   dataType *ret = (dataType *) (hdrLocal+1); \
115   CcsImplHeader *hdr; \
116   for (m=0; m<n; ++m) { \
117     int len; \
118     dataType *value; \
119     hdr = (CcsImplHeader*)(((char*)remote[m])+CmiReservedHeaderSize); \
120     len = ChMessageInt(hdr->len); \
121     value = (dataType *)(hdr+1); \
122     CmiAssert(lenLocal == len); \
123     for (i=0; i<nElem; ++i) loop; \
124   } \
125   return local; \
126 }
127
128 SIMPLE_REDUCTION(logical_and, int, ret[i]=(ret[i]&&value[i])?1:0)
129 SIMPLE_REDUCTION(logical_or, int, ret[i]=(ret[i]||value[i])?1:0)
130 SIMPLE_REDUCTION(bitvec_and, int, ret[i]&=value[i])
131 SIMPLE_REDUCTION(bitvec_or, int, ret[i]|=value[i])
132
133 /*Use this macro for reductions that have the same type for all inputs */
134 #define SIMPLE_POLYMORPH_REDUCTION(nameBase,loop) \
135   SIMPLE_REDUCTION(nameBase##_int, int, loop) \
136   SIMPLE_REDUCTION(nameBase##_float, float, loop) \
137   SIMPLE_REDUCTION(nameBase##_double, double, loop)
138
139 SIMPLE_POLYMORPH_REDUCTION(sum, ret[i]+=value[i])
140 SIMPLE_POLYMORPH_REDUCTION(product, ret[i]*=value[i])
141 SIMPLE_POLYMORPH_REDUCTION(max, if (ret[i]<value[i]) ret[i]=value[i])
142 SIMPLE_POLYMORPH_REDUCTION(min, if (ret[i]>value[i]) ret[i]=value[i])
143
144 #undef SIMPLE_REDUCTION
145 #undef SIMPLE_POLYMORPH_REDUCTION
146
147 int CcsEnabled(void)
148 {
149   return 1;
150 }
151
152 int CcsIsRemoteRequest(void)
153 {
154   return CpvAccess(ccsReq)!=NULL;
155 }
156
157 void CcsCallerId(skt_ip_t *pip, unsigned int *pport)
158 {
159   *pip = CpvAccess(ccsReq)->attr.ip;
160   *pport = ChMessageInt(CpvAccess(ccsReq)->attr.port);
161 }
162
163 int rep_fw_handler_idx;
164
165 CcsDelayedReply CcsDelayReply(void)
166 {
167   CcsDelayedReply ret;
168   int len = sizeof(CcsImplHeader);
169   if (ChMessageInt(CpvAccess(ccsReq)->pe) < -1)
170     len += ChMessageInt(CpvAccess(ccsReq)->pe) * sizeof(int);
171   ret.hdr = (CcsImplHeader*)malloc(len);
172   memcpy(ret.hdr, CpvAccess(ccsReq), len);
173   CpvAccess(ccsReq)=NULL;
174   return ret;
175 }
176
177 void CcsSendReply(int replyLen, const void *replyData)
178 {
179   if (CpvAccess(ccsReq)==NULL)
180     CmiAbort("CcsSendReply: reply already sent!\n");
181   CpvAccess(ccsReq)->len = ChMessageInt_new(1);
182   CcsReply(CpvAccess(ccsReq),replyLen,replyData);
183   CpvAccess(ccsReq) = NULL;
184 }
185
186 void CcsSendDelayedReply(CcsDelayedReply d,int replyLen, const void *replyData)
187 {
188   CcsImplHeader *h = d.hdr;
189   h->len=ChMessageInt_new(1);
190   CcsReply(h,replyLen,replyData);
191   free(h);
192 }
193
194 void CcsNoReply()
195 {
196   if (CpvAccess(ccsReq)==NULL) return;
197   CpvAccess(ccsReq)->len = ChMessageInt_new(0);
198   CcsReply(CpvAccess(ccsReq),0,NULL);
199   CpvAccess(ccsReq) = NULL;
200 }
201
202 void CcsNoDelayedReply(CcsDelayedReply d)
203 {
204   CcsImplHeader *h = d.hdr;
205   h->len = ChMessageInt_new(0);
206   CcsReply(h,0,NULL);
207   free(h);
208 }
209
210
211 /**********************************
212 _CCS Implementation Routines:
213   These do the request forwarding and
214 delivery.
215 ***********************************/
216
217 /*CCS Bottleneck:
218   Deliver the given message data to the given
219 CCS handler.
220 */
221 void CcsHandleRequest(CcsImplHeader *hdr,const char *reqData)
222 {
223   char *cmsg;
224   int reqLen=ChMessageInt(hdr->len);
225 /*Look up handler's converse ID*/
226   char *handlerStr=hdr->handler;
227   CcsHandlerRec *fn=(CcsHandlerRec *)CkHashtableGet(CpvAccess(ccsTab),(void *)&handlerStr);
228   if (fn==NULL) {
229     CmiPrintf("CCS: Unknown CCS handler name '%s' requested. Ignoring...\n",
230               hdr->handler);
231     CpvAccess(ccsReq)=hdr;
232     CcsSendReply(0,NULL); /*Send an empty reply to the possibly waiting client*/
233     return;
234  /*   CmiAbort("CCS: Unknown CCS handler name.\n");*/
235   }
236
237 /* Call the handler */
238   CpvAccess(ccsReq)=hdr;
239   callHandlerRec(fn,reqLen,reqData);
240   
241 /*Check if a reply was sent*/
242   if (CpvAccess(ccsReq)!=NULL)
243     CcsSendReply(0,NULL);/*Send an empty reply if not*/
244 }
245
246 #if ! NODE_0_IS_CONVHOST || CMK_BLUEGENE_CHARM
247 /* The followings are necessary to prevent CCS requests to be processed before
248  * CCS has been initialized. Really it matters only when NODE_0_IS_CONVHOST=0, but
249  * it doesn't hurt having it in the other case as well */
250 static char **bufferedMessages = NULL;
251 static int CcsNumBufferedMsgs = 0;
252 #define CCS_MAX_NUM_BUFFERED_MSGS  100
253
254 void CcsBufferMessage(char *msg) {
255   //CmiPrintf("Buffering CCS message\n");
256   CmiAssert(CcsNumBufferedMsgs < CCS_MAX_NUM_BUFFERED_MSGS);
257   if (CcsNumBufferedMsgs < 0) CmiAbort("Why is a CCS message being buffered now???");
258   if (bufferedMessages == NULL) bufferedMessages = malloc(sizeof(char*)*CCS_MAX_NUM_BUFFERED_MSGS);
259   bufferedMessages[CcsNumBufferedMsgs] = msg;
260   CcsNumBufferedMsgs ++;
261 }
262 #endif
263   
264 /*Unpacks request message to call above routine*/
265 int _ccsHandlerIdx = 0;/*Converse handler index of routine req_fw_handler*/
266
267 #if CMK_BLUEGENE_CHARM
268 CpvDeclare(int, _bgCcsHandlerIdx);
269 CpvDeclare(int, _bgCcsAck);
270 /* This routine is needed when the application is built on top of the bigemulator
271  * layer of Charm. In this case, the real CCS handler must be called within a
272  * worker thread. The function of this function is to receive the CCS message in
273  * the bottom converse layer and forward it to the emulated layer. */
274 static void bg_req_fw_handler(char *msg) {
275   if (CpvAccess(_bgCcsAck) < BgNodeSize()) {
276     CcsBufferMessage(msg);
277     return;
278   }
279   /* Get out of the message who is the destination pe */
280   int offset = CmiReservedHeaderSize + sizeof(CcsImplHeader);
281   CcsImplHeader *hdr = (CcsImplHeader *)(msg+CmiReservedHeaderSize);
282   int destPE = (int)ChMessageInt(hdr->pe);
283   if (destPE == -1) destPE = 0;
284   if (destPE < -1) {
285     ChMessageInt_t *pes_nbo = (ChMessageInt_t *)(msg+CmiReservedHeaderSize+sizeof(CcsImplHeader));
286     destPE = ChMessageInt(pes_nbo[0]);
287   }
288   //CmiAssert(destPE >= 0); // FixME: should cover also broadcast and multicast -> create generic function to extract destpe
289   (((CmiBlueGeneMsgHeader*)msg)->tID) = 0;
290   (((CmiBlueGeneMsgHeader*)msg)->n) = 0;
291   (((CmiBlueGeneMsgHeader*)msg)->flag) = 0;
292   (((CmiBlueGeneMsgHeader*)msg)->t) = 0;
293   (((CmiBlueGeneMsgHeader*)msg)->hID) = CpvAccess(_bgCcsHandlerIdx);
294   /* Get the right thread to deliver to (for now assume it is using CyclicMapInfo) */
295   addBgNodeInbuffer_c(msg, destPE/CmiNumPes());
296   //CmiPrintf("message CCS added %d to %d\n",((CmiBlueGeneMsgHeader*)msg)->hID, ((CmiBlueGeneMsgHeader*)msg)->tID);
297 }
298 #define req_fw_handler bg_req_fw_handler
299 #endif
300 extern void req_fw_handler(char *msg);
301
302 void CcsReleaseMessages() {
303 #if ! NODE_0_IS_CONVHOST
304 #if CMK_BLUEGENE_CHARM
305   if (CpvAccess(_bgCcsAck) == 0 || CpvAccess(_bgCcsAck) < BgNodeSize()) return;
306 #endif
307   if (CcsNumBufferedMsgs > 0) {
308     int i;
309     for (i=0; i<CcsNumBufferedMsgs; ++i) {
310       CmiSetHandler(bufferedMessages[i], _ccsHandlerIdx);
311       CmiPushPE(0, bufferedMessages[i]);
312     }
313     free(bufferedMessages);
314     bufferedMessages = NULL;
315     CcsNumBufferedMsgs = -1;
316   }
317 #endif
318 }
319
320 /*Convert CCS header & message data into a converse message 
321  addressed to handler*/
322 char *CcsImpl_ccs2converse(const CcsImplHeader *hdr,const void *data,int *ret_len)
323 {
324   int reqLen=ChMessageInt(hdr->len);
325   int destPE = ChMessageInt(hdr->pe);
326   int len;
327   char *msg;
328   if (destPE < -1) reqLen -= destPE*sizeof(int);
329   len=CmiReservedHeaderSize+sizeof(CcsImplHeader)+reqLen;
330   msg=(char *)CmiAlloc(len);
331   memcpy(msg+CmiReservedHeaderSize,hdr,sizeof(CcsImplHeader));
332   memcpy(msg+CmiReservedHeaderSize+sizeof(CcsImplHeader),data,reqLen);
333   if (ret_len!=NULL) *ret_len=len;
334   if (_ccsHandlerIdx != 0) {
335     CmiSetHandler(msg, _ccsHandlerIdx);
336     return msg;
337   } else {
338 #if NODE_0_IS_CONVHOST
339     CmiAbort("Why do we need to buffer messages when node 0 is Convhost?");
340 #else
341     CcsBufferMessage(msg);
342     return NULL;
343 #endif
344   }
345 }
346
347 /*Receives reply messages passed up from
348 converse to node 0.*/
349 static void rep_fw_handler(char *msg)
350 {
351   int len;
352   char *r=msg+CmiReservedHeaderSize;
353   CcsImplHeader *hdr=(CcsImplHeader *)r; 
354   r+=sizeof(CcsImplHeader);
355   len=ChMessageInt(hdr->len);
356   CcsImpl_reply(hdr,len,r);
357   CmiFree(msg);
358 }
359
360 #if NODE_0_IS_CONVHOST
361 /************** NODE_0_IS_CONVHOST ***********
362 Non net- versions of charm++ are run without a 
363 (real) conv-host program.  This is fine, except 
364 CCS clients connect via conv-host; so for CCS
365 on non-net- versions of charm++, node 0 carries
366 out the CCS forwarding normally done in conv-host.
367
368 CCS works by listening to a TCP connection on a 
369 port-- the Ccs server socket.  A typical communcation
370 pattern is:
371
372 1.) Random program (CCS client) from the net
373 connects to the CCS server socket and sends
374 a CCS request.
375
376 2.) Node 0 forwards the request to the proper
377 PE as a regular converse message (built in CcsImpl_netReq)
378 for CcsHandleRequest.
379
380 3.) CcsHandleRequest looks up the user's pre-registered
381 CCS handler, and passes the user's handler the request data.
382
383 4.) The user's handler calls CcsSendReply with some
384 reply data; OR finishes without calling CcsSendReply,
385 in which case CcsHandleRequest does it.
386
387 5.) CcsSendReply forwards the reply back to node 0,
388 which sends the reply back to the original requestor,
389 on the (still-open) request socket.
390  */
391
392 /**
393 Send a Ccs reply back to the requestor, down the given socket.
394 Since there is no conv-host, node 0 does all the CCS 
395 communication-- this means all requests come to node 0
396 and are forwarded out; all replies are forwarded back to node 0.
397
398 Note: on Net- versions, CcsImpl_reply is implemented in machine.c
399 */
400 void CcsImpl_reply(CcsImplHeader *rep,int repLen,const void *repData)
401 {
402   const int repPE=0;
403   rep->len=ChMessageInt_new(repLen);
404   if (CmiMyPe()==repPE) {
405     /*Actually deliver reply data*/
406     CcsServer_sendReply(rep,repLen,repData);
407   } else {
408     /*Forward data & socket # to the replyPE*/
409     int len=CmiReservedHeaderSize+
410            sizeof(CcsImplHeader)+repLen;
411     char *msg=CmiAlloc(len);
412     char *r=msg+CmiReservedHeaderSize;
413     *(CcsImplHeader *)r=*rep; r+=sizeof(CcsImplHeader);
414     memcpy(r,repData,repLen);
415     CmiSetHandler(msg,rep_fw_handler_idx);
416     CmiSyncSendAndFree(repPE,len,msg);
417   }
418 }
419
420 /*No request will be sent through this socket.
421 Closes it.
422 */
423 /*void CcsImpl_noReply(CcsImplHeader *hdr)
424 {
425   int fd=ChMessageInt(hdr->replyFd);
426   skt_close(fd);
427 }*/
428
429 /**
430  * This is the entrance point of a CCS request into the server.
431  * It is executed only on proc 0, and it forwards the request to the appropriate PE.
432  */
433 void CcsImpl_netRequest(CcsImplHeader *hdr,const void *reqData)
434 {
435   char *msg;
436   int len,repPE=ChMessageInt(hdr->pe);
437   if (repPE<=-CmiNumPes() || repPE>=CmiNumPes()) {
438     /*Treat out of bound values as errors. Helps detecting bugs*/
439     if (repPE==-CmiNumPes()) CmiPrintf("Invalid processor index in CCS request: are you trying to do a broadcast instead?");
440     else CmiPrintf("Invalid processor index in CCS request.");
441     CpvAccess(ccsReq)=hdr;
442     CcsSendReply(0,NULL); /*Send an empty reply to the possibly waiting client*/
443     return;
444   }
445
446   msg=CcsImpl_ccs2converse(hdr,reqData,&len);
447   if (repPE >= 0) {
448     CmiSyncSendAndFree(repPE,len,msg);
449   } else if (repPE == -1) {
450     /* Broadcast to all processors */
451     CmiPushPE(0, msg);
452   } else {
453     /* Multicast to -repPE processors, specified right at the beginning of reqData (as a list of pes) */
454     int firstPE = ChMessageInt(*(ChMessageInt_t*)reqData);
455     CmiSyncSendAndFree(firstPE,len,msg);
456   }
457 }
458
459 /*
460 We have to run a CCS server socket here on
461 node 0.  To keep the speed impact minimal,
462 we only probe for new connections (with CcsServerCheck)
463 occasionally.  
464  */
465 #include <signal.h>
466 #include "ccs-server.c" /*Include implementation here in this case*/
467 #include "ccs-auth.c"
468
469 /*Check for ready Ccs messages:*/
470 void CcsServerCheck(void)
471 {
472   while (1==skt_select1(CcsServer_fd(),0)) {
473     CcsImplHeader hdr;
474     void *data;
475     /* printf("Got CCS connect...\n"); */
476     if (CcsServer_recvRequest(&hdr,&data))
477     {/*We got a network request*/
478       /* printf("Got CCS request...\n"); */
479       if (! check_stdio_header(&hdr)) {
480         CcsImpl_netRequest(&hdr,data);
481       }
482       free(data);
483     }
484   }
485 }
486
487 #endif /*NODE_0_IS_CONVHOST*/
488
489 int _isCcsHandlerIdx(int hIdx) {
490   if (hIdx==_ccsHandlerIdx) return 1;
491   if (hIdx==rep_fw_handler_idx) return 1;
492   return 0;
493 }
494
495 void CcsBuiltinsInit(char **argv);
496
497 CpvDeclare(int, cmiArgDebugFlag);
498 CpvDeclare(char *, displayArgument);
499 CpvDeclare(int, cpdSuspendStartup);
500
501 void CcsInit(char **argv)
502 {
503   CpvInitialize(CkHashtable_c, ccsTab);
504   CpvAccess(ccsTab) = CkCreateHashtable_string(sizeof(CcsHandlerRec),5);
505   CpvInitialize(CcsImplHeader *, ccsReq);
506   CpvAccess(ccsReq) = NULL;
507   _ccsHandlerIdx = CmiRegisterHandler((CmiHandler)req_fw_handler);
508 #if CMK_BLUEGENE_CHARM
509   CpvInitialize(int, _bgCcsHandlerIdx);
510   CpvAccess(_bgCcsHandlerIdx) = 0;
511   CpvInitialize(int, _bgCcsAck);
512   CpvAccess(_bgCcsAck) = 0;
513 #endif
514   CpvInitialize(int, cmiArgDebugFlag);
515   CpvInitialize(char *, displayArgument);
516   CpvInitialize(int, cpdSuspendStartup);
517   CpvAccess(cmiArgDebugFlag) = 0;
518   CpvAccess(displayArgument) = NULL;
519   CpvAccess(cpdSuspendStartup) = 0;
520   
521   CcsBuiltinsInit(argv);
522
523   rep_fw_handler_idx = CmiRegisterHandler((CmiHandler)rep_fw_handler);
524 #if NODE_0_IS_CONVHOST
525 #if ! CMK_CMIPRINTF_IS_A_BUILTIN
526   print_fw_handler_idx = CmiRegisterHandler((CmiHandler)print_fw_handler);
527 #endif
528   {
529    int ccs_serverPort=0;
530    char *ccs_serverAuth=NULL;
531    
532    if (CmiGetArgFlagDesc(argv,"++server", "Create a CCS server port") | 
533       CmiGetArgIntDesc(argv,"++server-port",&ccs_serverPort, "Listen on this TCP/IP port number") |
534       CmiGetArgStringDesc(argv,"++server-auth",&ccs_serverAuth, "Use this CCS authentication file")) 
535     if (CmiMyPe()==0)
536     {/*Create and occasionally poll on a CCS server port*/
537       CcsServer_new(NULL,&ccs_serverPort,ccs_serverAuth);
538       CcdCallOnConditionKeep(CcdPERIODIC,(CcdVoidFn)CcsServerCheck,NULL);
539     }
540   }
541 #endif
542   /* if in parallel debug mode i.e ++cpd, freeze */
543   if (CmiGetArgFlagDesc(argv, "+cpd", "Used *only* in conjunction with parallel debugger"))
544   {
545      CpvAccess(cmiArgDebugFlag) = 1;
546      if (CmiGetArgStringDesc(argv, "+DebugDisplay",&(CpvAccess(displayArgument)), "X display for gdb used only in cpd mode"))
547      {
548         if (CpvAccess(displayArgument) == NULL)
549             CmiPrintf("WARNING> NULL parameter for +DebugDisplay\n***");
550      }
551      else if (CmiMyPe() == 0)
552      {
553             /* only one processor prints the warning */
554             CmiPrintf("WARNING> x term for gdb needs to be specified as +DebugDisplay by debugger\n***\n");
555      }
556
557      if (CmiGetArgFlagDesc(argv, "+DebugSuspend", "Suspend execution at beginning of program")) {
558        CpvAccess(cpdSuspendStartup) = 1;
559      }
560   }
561
562   CcsReleaseMessages();
563 }
564
565 #endif /*CMK_CCS_AVAILABLE*/
566