1 2 /* 3 Provides a general mechanism to allow one to register new routines in 4 dynamic libraries for many of the PETSc objects (including, e.g., KSP and PC). 5 */ 6 #include <petsc/private/petscimpl.h> /*I "petscsys.h" I*/ 7 #include <petscviewer.h> 8 9 /* 10 This is the default list used by PETSc with the PetscDLLibrary register routines 11 */ 12 PetscDLLibrary PetscDLLibrariesLoaded = NULL; 13 14 #if defined(PETSC_HAVE_DYNAMIC_LIBRARIES) && defined(PETSC_USE_SHARED_LIBRARIES) 15 16 static PetscErrorCode PetscLoadDynamicLibrary(const char *name, PetscBool *found) { 17 char libs[PETSC_MAX_PATH_LEN], dlib[PETSC_MAX_PATH_LEN]; 18 19 PetscFunctionBegin; 20 PetscCall(PetscStrncpy(libs, "${PETSC_LIB_DIR}/libpetsc", sizeof(libs))); 21 PetscCall(PetscStrlcat(libs, name, sizeof(libs))); 22 PetscCall(PetscDLLibraryRetrieve(PETSC_COMM_WORLD, libs, dlib, 1024, found)); 23 if (*found) { 24 PetscCall(PetscDLLibraryAppend(PETSC_COMM_WORLD, &PetscDLLibrariesLoaded, dlib)); 25 } else { 26 PetscCall(PetscStrncpy(libs, "${PETSC_DIR}/${PETSC_ARCH}/lib/libpetsc", sizeof(libs))); 27 PetscCall(PetscStrlcat(libs, name, sizeof(libs))); 28 PetscCall(PetscDLLibraryRetrieve(PETSC_COMM_WORLD, libs, dlib, 1024, found)); 29 if (*found) PetscCall(PetscDLLibraryAppend(PETSC_COMM_WORLD, &PetscDLLibrariesLoaded, dlib)); 30 } 31 PetscFunctionReturn(0); 32 } 33 #endif 34 35 #if defined(PETSC_USE_SINGLE_LIBRARY) && !(defined(PETSC_HAVE_DYNAMIC_LIBRARIES) && defined(PETSC_USE_SHARED_LIBRARIES)) 36 PETSC_EXTERN PetscErrorCode AOInitializePackage(void); 37 PETSC_EXTERN PetscErrorCode PetscSFInitializePackage(void); 38 #if !defined(PETSC_USE_COMPLEX) 39 PETSC_EXTERN PetscErrorCode CharacteristicInitializePackage(void); 40 #endif 41 PETSC_EXTERN PetscErrorCode ISInitializePackage(void); 42 PETSC_EXTERN PetscErrorCode VecInitializePackage(void); 43 PETSC_EXTERN PetscErrorCode MatInitializePackage(void); 44 PETSC_EXTERN PetscErrorCode DMInitializePackage(void); 45 PETSC_EXTERN PetscErrorCode PCInitializePackage(void); 46 PETSC_EXTERN PetscErrorCode KSPInitializePackage(void); 47 PETSC_EXTERN PetscErrorCode SNESInitializePackage(void); 48 PETSC_EXTERN PetscErrorCode TSInitializePackage(void); 49 PETSC_EXTERN PetscErrorCode TaoInitializePackage(void); 50 #endif 51 #if defined(PETSC_HAVE_THREADSAFETY) 52 static MPI_Comm PETSC_COMM_WORLD_INNER = 0, PETSC_COMM_SELF_INNER = 0; 53 #endif 54 55 /* 56 PetscInitialize_DynamicLibraries - Adds the default dynamic link libraries to the 57 search path. 58 */ 59 PETSC_INTERN PetscErrorCode PetscInitialize_DynamicLibraries(void) { 60 char *libname[32]; 61 PetscInt nmax, i; 62 PetscBool preload = PETSC_FALSE; 63 #if defined(PETSC_HAVE_ELEMENTAL) 64 PetscBool PetscInitialized = PetscInitializeCalled; 65 #endif 66 67 PetscFunctionBegin; 68 #if defined(PETSC_HAVE_THREADSAFETY) 69 /* These must be all initialized here because it is not safe for individual threads to call these initialize routines */ 70 preload = PETSC_TRUE; 71 #endif 72 73 nmax = 32; 74 PetscCall(PetscOptionsGetStringArray(NULL, NULL, "-dll_prepend", libname, &nmax, NULL)); 75 for (i = 0; i < nmax; i++) { 76 PetscCall(PetscDLLibraryPrepend(PETSC_COMM_WORLD, &PetscDLLibrariesLoaded, libname[i])); 77 PetscCall(PetscFree(libname[i])); 78 } 79 80 PetscCall(PetscOptionsGetBool(NULL, NULL, "-library_preload", &preload, NULL)); 81 if (!preload) { 82 PetscCall(PetscSysInitializePackage()); 83 } else { 84 #if defined(PETSC_HAVE_DYNAMIC_LIBRARIES) && defined(PETSC_USE_SHARED_LIBRARIES) 85 PetscBool found; 86 #if defined(PETSC_USE_SINGLE_LIBRARY) 87 PetscCall(PetscLoadDynamicLibrary("", &found)); 88 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate PETSc dynamic library \n You cannot move the dynamic libraries!"); 89 #else 90 PetscCall(PetscLoadDynamicLibrary("sys", &found)); 91 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate PETSc dynamic library \n You cannot move the dynamic libraries!"); 92 PetscCall(PetscLoadDynamicLibrary("vec", &found)); 93 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate PETSc Vec dynamic library \n You cannot move the dynamic libraries!"); 94 PetscCall(PetscLoadDynamicLibrary("mat", &found)); 95 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate PETSc Mat dynamic library \n You cannot move the dynamic libraries!"); 96 PetscCall(PetscLoadDynamicLibrary("dm", &found)); 97 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate PETSc DM dynamic library \n You cannot move the dynamic libraries!"); 98 PetscCall(PetscLoadDynamicLibrary("ksp", &found)); 99 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate PETSc KSP dynamic library \n You cannot move the dynamic libraries!"); 100 PetscCall(PetscLoadDynamicLibrary("snes", &found)); 101 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate PETSc SNES dynamic library \n You cannot move the dynamic libraries!"); 102 PetscCall(PetscLoadDynamicLibrary("ts", &found)); 103 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate PETSc TS dynamic library \n You cannot move the dynamic libraries!"); 104 PetscCall(PetscLoadDynamicLibrary("tao", &found)); 105 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate Tao dynamic library \n You cannot move the dynamic libraries!"); 106 #endif 107 #else /* defined(PETSC_HAVE_DYNAMIC_LIBRARIES) && defined(PETSC_USE_SHARED_LIBRARIES) */ 108 #if defined(PETSC_USE_SINGLE_LIBRARY) 109 PetscCall(AOInitializePackage()); 110 PetscCall(PetscSFInitializePackage()); 111 #if !defined(PETSC_USE_COMPLEX) 112 PetscCall(CharacteristicInitializePackage()); 113 #endif 114 PetscCall(ISInitializePackage()); 115 PetscCall(VecInitializePackage()); 116 PetscCall(MatInitializePackage()); 117 PetscCall(DMInitializePackage()); 118 PetscCall(PCInitializePackage()); 119 PetscCall(KSPInitializePackage()); 120 PetscCall(SNESInitializePackage()); 121 PetscCall(TSInitializePackage()); 122 PetscCall(TaoInitializePackage()); 123 #else 124 SETERRQ(PETSC_COMM_WORLD, PETSC_ERR_SUP, "Cannot use -library_preload with multiple static PETSc libraries"); 125 #endif 126 #endif /* defined(PETSC_HAVE_DYNAMIC_LIBRARIES) && defined(PETSC_USE_SHARED_LIBRARIES) */ 127 } 128 129 #if defined(PETSC_HAVE_DYNAMIC_LIBRARIES) && defined(PETSC_USE_SHARED_LIBRARIES) && defined(PETSC_HAVE_BAMG) 130 { 131 PetscBool found; 132 PetscCall(PetscLoadDynamicLibrary("bamg", &found)); 133 PetscCheck(found, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Unable to locate PETSc BAMG dynamic library \n You cannot move the dynamic libraries!"); 134 } 135 #endif 136 137 nmax = 32; 138 PetscCall(PetscOptionsGetStringArray(NULL, NULL, "-dll_append", libname, &nmax, NULL)); 139 for (i = 0; i < nmax; i++) { 140 PetscCall(PetscDLLibraryAppend(PETSC_COMM_WORLD, &PetscDLLibrariesLoaded, libname[i])); 141 PetscCall(PetscFree(libname[i])); 142 } 143 144 #if defined(PETSC_HAVE_THREADSAFETY) 145 PetscCall(PetscCommDuplicate(PETSC_COMM_SELF, &PETSC_COMM_SELF_INNER, NULL)); 146 PetscCall(PetscCommDuplicate(PETSC_COMM_WORLD, &PETSC_COMM_WORLD_INNER, NULL)); 147 #endif 148 #if defined(PETSC_HAVE_ELEMENTAL) 149 /* in Fortran, PetscInitializeCalled is set to PETSC_TRUE before PetscInitialize_DynamicLibraries() */ 150 /* in C, it is not the case, but the value is forced to PETSC_TRUE so that PetscRegisterFinalize() is called */ 151 PetscInitializeCalled = PETSC_TRUE; 152 PetscCall(PetscElementalInitializePackage()); 153 PetscInitializeCalled = PetscInitialized; 154 #endif 155 PetscFunctionReturn(0); 156 } 157 158 /* 159 PetscFinalize_DynamicLibraries - Closes the opened dynamic libraries. 160 */ 161 PETSC_INTERN PetscErrorCode PetscFinalize_DynamicLibraries(void) { 162 PetscBool flg = PETSC_FALSE; 163 164 PetscFunctionBegin; 165 PetscCall(PetscOptionsGetBool(NULL, NULL, "-dll_view", &flg, NULL)); 166 if (flg) PetscCall(PetscDLLibraryPrintPath(PetscDLLibrariesLoaded)); 167 PetscCall(PetscDLLibraryClose(PetscDLLibrariesLoaded)); 168 169 #if defined(PETSC_HAVE_THREADSAFETY) 170 PetscCall(PetscCommDestroy(&PETSC_COMM_SELF_INNER)); 171 PetscCall(PetscCommDestroy(&PETSC_COMM_WORLD_INNER)); 172 #endif 173 174 PetscDLLibrariesLoaded = NULL; 175 PetscFunctionReturn(0); 176 } 177 178 /* ------------------------------------------------------------------------------*/ 179 struct _n_PetscFunctionList { 180 void (*routine)(void); /* the routine */ 181 char *name; /* string to identify routine */ 182 PetscFunctionList next; /* next pointer */ 183 PetscFunctionList next_list; /* used to maintain list of all lists for freeing */ 184 }; 185 186 /* 187 Keep a linked list of PetscFunctionLists so that we can destroy all the left-over ones. 188 */ 189 static PetscFunctionList dlallhead = NULL; 190 191 static PetscErrorCode PetscFunctionListCreateNode_Private(PetscFunctionList *entry, const char name[], void (*func)(void)) { 192 PetscFunctionBegin; 193 PetscCall(PetscNew(entry)); 194 PetscCall(PetscStrallocpy(name, &(*entry)->name)); 195 (*entry)->routine = func; 196 (*entry)->next = NULL; 197 PetscFunctionReturn(0); 198 } 199 200 /*MC 201 PetscFunctionListAdd - Given a routine and a string id, saves that routine in the 202 specified registry. 203 204 Synopsis: 205 #include <petscsys.h> 206 PetscErrorCode PetscFunctionListAdd(PetscFunctionList *flist,const char name[],void (*fptr)(void)) 207 208 Not Collective 209 210 Input Parameters: 211 + flist - pointer to function list object 212 . name - string to identify routine 213 - fptr - function pointer 214 215 Notes: 216 To remove a registered routine, pass in a NULL fptr. 217 218 Users who wish to register new classes for use by a particular PETSc 219 component (e.g., `SNES`) should generally call the registration routine 220 for that particular component (e.g., `SNESRegister()`) instead of 221 calling `PetscFunctionListAdd()` directly. 222 223 Level: developer 224 225 .seealso: `PetscFunctionListDestroy()`, `SNESRegister()`, `KSPRegister()`, 226 `PCRegister()`, `TSRegister()`, `PetscFunctionList`, `PetscObjectComposeFunction()` 227 M*/ 228 PETSC_EXTERN PetscErrorCode PetscFunctionListAdd_Private(PetscFunctionList *fl, const char name[], void (*fnc)(void)) { 229 PetscFunctionBegin; 230 PetscValidPointer(fl, 1); 231 if (name) PetscValidCharPointer(name, 2); 232 if (fnc) PetscValidFunction(fnc, 3); 233 if (*fl) { 234 /* search list to see if it is already there */ 235 PetscFunctionList empty_node = NULL; 236 PetscFunctionList ne = *fl; 237 238 while (1) { 239 PetscBool founddup; 240 241 PetscCall(PetscStrcmp(ne->name, name, &founddup)); 242 if (founddup) { 243 /* found duplicate, clear it */ 244 ne->routine = fnc; 245 if (!fnc) PetscCall(PetscFree(ne->name)); 246 PetscFunctionReturn(0); 247 } 248 249 if (!empty_node && !ne->routine && !ne->name) { 250 /* save the empty node for later */ 251 empty_node = ne; 252 } 253 254 if (!ne->next) break; /* end of list */ 255 ne = ne->next; 256 } 257 258 /* there was an empty entry we could grab, fill it and bail */ 259 if (empty_node) { 260 empty_node->routine = fnc; 261 PetscCall(PetscStrallocpy(name, &empty_node->name)); 262 } else { 263 /* create new entry at the end of list */ 264 PetscCall(PetscFunctionListCreateNode_Private(&ne->next, name, fnc)); 265 } 266 PetscFunctionReturn(0); 267 } 268 269 /* we didn't have a list */ 270 PetscCall(PetscFunctionListCreateNode_Private(fl, name, fnc)); 271 if (PetscDefined(USE_DEBUG)) { 272 const PetscFunctionList head = dlallhead; 273 274 /* add this new list to list of all lists */ 275 dlallhead = *fl; 276 (*fl)->next_list = head; 277 } 278 PetscFunctionReturn(0); 279 } 280 281 /*@ 282 PetscFunctionListDestroy - Destroys a list of registered routines. 283 284 Input Parameter: 285 . fl - pointer to list 286 287 Level: developer 288 289 .seealso: `PetscFunctionListAdd()`, `PetscFunctionList`, `PetscFunctionListClear()` 290 @*/ 291 PetscErrorCode PetscFunctionListDestroy(PetscFunctionList *fl) { 292 PetscFunctionList next, entry, tmp = dlallhead; 293 294 PetscFunctionBegin; 295 if (!*fl) PetscFunctionReturn(0); 296 297 /* 298 Remove this entry from the main DL list (if it is in it) 299 */ 300 if (dlallhead == *fl) { 301 if (dlallhead->next_list) dlallhead = dlallhead->next_list; 302 else dlallhead = NULL; 303 } else if (tmp) { 304 while (tmp->next_list != *fl) { 305 tmp = tmp->next_list; 306 if (!tmp->next_list) break; 307 } 308 if (tmp->next_list) tmp->next_list = tmp->next_list->next_list; 309 } 310 311 /* free this list */ 312 entry = *fl; 313 while (entry) { 314 next = entry->next; 315 PetscCall(PetscFree(entry->name)); 316 PetscCall(PetscFree(entry)); 317 entry = next; 318 } 319 *fl = NULL; 320 PetscFunctionReturn(0); 321 } 322 323 /*@ 324 PetscFunctionListClear - Clear a `PetscFunctionList` 325 326 Not Collective 327 328 Input Parameter: 329 . fl - The `PetscFunctionList` to clear 330 331 Notes: 332 This clears the contents of `fl` but does not deallocate the entries themselves. 333 334 Level: developer 335 336 .seealso: `PetscFunctionList`, `PetscFunctionListDestroy()`, `PetscFunctionListAdd()` 337 @*/ 338 PetscErrorCode PetscFunctionListClear(PetscFunctionList fl) { 339 PetscFunctionBegin; 340 /* free the names and clear the routine but don't deallocate the node */ 341 while (fl) { 342 PetscCall(PetscFree(fl->name)); 343 fl->routine = NULL; 344 fl = fl->next; 345 } 346 PetscFunctionReturn(0); 347 } 348 349 /* 350 Print registered PetscFunctionLists 351 */ 352 PetscErrorCode PetscFunctionListPrintAll(void) { 353 PetscFunctionList tmp = dlallhead; 354 355 PetscFunctionBegin; 356 if (tmp) PetscCall(PetscPrintf(PETSC_COMM_SELF, "[%d] Registered PetscFunctionLists\n", PetscGlobalRank)); 357 while (tmp) { 358 PetscCall(PetscPrintf(PETSC_COMM_SELF, "[%d] %s\n", PetscGlobalRank, tmp->name)); 359 tmp = tmp->next_list; 360 } 361 PetscFunctionReturn(0); 362 } 363 364 /*MC 365 PetscFunctionListNonEmpty - Print composed names for non null function pointers 366 367 Input Parameter: 368 . flist - pointer to list 369 370 Level: developer 371 372 .seealso: `PetscFunctionListAdd()`, `PetscFunctionList`, `PetscObjectQueryFunction()` 373 M*/ 374 PetscErrorCode PetscFunctionListPrintNonEmpty(PetscFunctionList fl) { 375 PetscFunctionBegin; 376 while (fl) { 377 PetscFunctionList next = fl->next; 378 if (fl->routine) PetscCall(PetscPrintf(PETSC_COMM_SELF, "[%d] function name: %s\n", PetscGlobalRank, fl->name)); 379 fl = next; 380 } 381 PetscFunctionReturn(0); 382 } 383 384 /*MC 385 PetscFunctionListFind - Find function registered under given name 386 387 Synopsis: 388 #include <petscsys.h> 389 PetscErrorCode PetscFunctionListFind(PetscFunctionList flist,const char name[],void (**fptr)(void)) 390 391 Input Parameters: 392 + flist - pointer to list 393 - name - name registered for the function 394 395 Output Parameters: 396 . fptr - the function pointer if name was found, else NULL 397 398 Level: developer 399 400 .seealso: `PetscFunctionListAdd()`, `PetscFunctionList`, `PetscObjectQueryFunction()` 401 M*/ 402 PETSC_EXTERN PetscErrorCode PetscFunctionListFind_Private(PetscFunctionList fl, const char name[], void (**r)(void)) { 403 PetscFunctionList entry = fl; 404 405 PetscFunctionBegin; 406 PetscValidCharPointer(name, 2); 407 PetscValidPointer(r, 3); 408 while (entry) { 409 PetscBool flg; 410 411 PetscCall(PetscStrcmp(name, entry->name, &flg)); 412 if (flg) { 413 *r = entry->routine; 414 PetscFunctionReturn(0); 415 } 416 entry = entry->next; 417 } 418 *r = NULL; 419 PetscFunctionReturn(0); 420 } 421 422 /*@ 423 PetscFunctionListView - prints out contents of an PetscFunctionList 424 425 Collective over viewer 426 427 Input Parameters: 428 + list - the list of functions 429 - viewer - currently ignored 430 431 Level: developer 432 433 .seealso: `PetscFunctionListAdd()`, `PetscFunctionListPrintTypes()`, `PetscFunctionList` 434 @*/ 435 PetscErrorCode PetscFunctionListView(PetscFunctionList list, PetscViewer viewer) { 436 PetscBool iascii; 437 438 PetscFunctionBegin; 439 if (!viewer) viewer = PETSC_VIEWER_STDOUT_SELF; 440 PetscValidPointer(list, 1); 441 PetscValidHeaderSpecific(viewer, PETSC_VIEWER_CLASSID, 2); 442 443 PetscCall(PetscObjectTypeCompare((PetscObject)viewer, PETSCVIEWERASCII, &iascii)); 444 PetscCheck(iascii, PETSC_COMM_SELF, PETSC_ERR_SUP, "Only ASCII viewer supported"); 445 446 while (list) { 447 PetscCall(PetscViewerASCIIPrintf(viewer, " %s\n", list->name)); 448 list = list->next; 449 } 450 PetscCall(PetscViewerASCIIPrintf(viewer, "\n")); 451 PetscFunctionReturn(0); 452 } 453 454 /*@C 455 PetscFunctionListGet - Gets an array the contains the entries in `PetscFunctionList`, this is used 456 by help etc. 457 458 Not Collective 459 460 Input Parameter: 461 . list - list of types 462 463 Output Parameters: 464 + array - array of names 465 - n - length of array 466 467 Note: 468 This allocates the array so that must be freed. BUT the individual entries are 469 not copied so should not be freed. 470 471 Level: developer 472 473 .seealso: `PetscFunctionListAdd()`, `PetscFunctionList` 474 @*/ 475 PetscErrorCode PetscFunctionListGet(PetscFunctionList list, const char ***array, int *n) { 476 PetscInt count = 0; 477 PetscFunctionList klist = list; 478 479 PetscFunctionBegin; 480 while (list) { 481 list = list->next; 482 count++; 483 } 484 PetscCall(PetscMalloc1(count + 1, (char ***)array)); 485 count = 0; 486 while (klist) { 487 (*array)[count] = klist->name; 488 klist = klist->next; 489 count++; 490 } 491 (*array)[count] = NULL; 492 *n = count + 1; 493 PetscFunctionReturn(0); 494 } 495 496 /*@C 497 PetscFunctionListPrintTypes - Prints the methods available in a list of functions 498 499 Collective over MPI_Comm 500 501 Input Parameters: 502 + comm - the communicator (usually `MPI_COMM_WORLD`) 503 . fd - file to print to, usually stdout 504 . prefix - prefix to prepend to name (optional) 505 . name - option string (for example, "-ksp_type") 506 . text - short description of the object (for example, "Krylov solvers") 507 . man - name of manual page that discusses the object (for example, "KSPCreate") 508 . list - list of types 509 . def - default (current) value 510 - newv - new value 511 512 Level: developer 513 514 .seealso: `PetscFunctionListAdd()`, `PetscFunctionList` 515 @*/ 516 PetscErrorCode PetscFunctionListPrintTypes(MPI_Comm comm, FILE *fd, const char prefix[], const char name[], const char text[], const char man[], PetscFunctionList list, const char def[], const char newv[]) { 517 char p[64]; 518 519 PetscFunctionBegin; 520 if (!fd) fd = PETSC_STDOUT; 521 522 PetscCall(PetscStrncpy(p, "-", sizeof(p))); 523 if (prefix) PetscCall(PetscStrlcat(p, prefix, sizeof(p))); 524 PetscCall(PetscFPrintf(comm, fd, " %s%s <now %s : formerly %s>: %s (one of)", p, name + 1, newv, def, text)); 525 526 while (list) { 527 PetscCall(PetscFPrintf(comm, fd, " %s", list->name)); 528 list = list->next; 529 } 530 PetscCall(PetscFPrintf(comm, fd, " (%s)\n", man)); 531 PetscFunctionReturn(0); 532 } 533 534 /*@ 535 PetscFunctionListDuplicate - Creates a new list from a given object list. 536 537 Input Parameters: 538 . fl - pointer to list 539 540 Output Parameters: 541 . nl - the new list (should point to 0 to start, otherwise appends) 542 543 Level: developer 544 545 .seealso: `PetscFunctionList`, `PetscFunctionListAdd()`, `PetscFlistDestroy()` 546 @*/ 547 PetscErrorCode PetscFunctionListDuplicate(PetscFunctionList fl, PetscFunctionList *nl) { 548 PetscFunctionBegin; 549 while (fl) { 550 PetscCall(PetscFunctionListAdd(nl, fl->name, fl->routine)); 551 fl = fl->next; 552 } 553 PetscFunctionReturn(0); 554 } 555