/* * DRCcontinuous.c -- * * This file provides the facilities for continuously keeping * design-rule violation information up to date in Magic. It * records areas that need to be rechecked, and provides a * routine to perform those checks in background. * * ********************************************************************* * * Copyright (C) 1985, 1990 Regents of the University of California. * * * Permission to use, copy, modify, and distribute this * * * software and its documentation for any purpose and without * * * fee is hereby granted, provided that the above copyright * * * notice appear in all copies. The University of California * * * makes no representations about the suitability of this * * * software for any purpose. It is provided "as is" without * * * express or implied warranty. Export of this software outside * * * of the United States of America may require an export license. * * ********************************************************************* */ #ifndef lint static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/drc/DRCcontin.c,v 1.7 2010/03/12 17:19:45 tim Exp $"; #endif /* not lint */ #include #include #include "tcltk/tclmagic.h" #include "utils/magic.h" #include "textio/textio.h" #include "utils/geometry.h" #include "tiles/tile.h" #include "utils/hash.h" #include "database/database.h" #include "windows/windows.h" #include "dbwind/dbwind.h" #include "dbwind/dbwtech.h" #include "utils/main.h" #include "commands/commands.h" #include "drc/drc.h" #include "utils/signals.h" #include "graphics/graphics.h" #include "utils/undo.h" #include "utils/malloc.h" #ifdef MAGIC_WRAPPER /* Global variable which indicates that the background checker is * still registered as an idle process. */ global unsigned char DRCStatus = DRC_NOT_RUNNING; #endif /* Global variable, settable by outside world, that enables * and disables the background checker. If disabled, check * tiles pile up but nothing is checked. */ global unsigned char DRCBackGround = DRC_NOT_SET; /* Global variable, settable by outside world, that enables * and disables Euclidean DRC checking. If enabled, magic * computes corner-to-corner distances using a Euclidean metric. * If disabled, magic computes corner-to-corner distances using * a Manhattan metric. * * Note: should this feature be settable from the technology * file, rather than from the command line? */ global bool DRCEuclidean = FALSE; /* Global variable that defines the size of chunks into which * to decompose large DRC areas. Also used to guarantee a * canonical determination of interaction areas. May be * set either while reading the tech file, or automatically * when DRCInit() is called. */ /*------- Things used by other DRC modules but not outside world. -------*/ /* Base of linked list of CellDefs waiting for background DRC. * Can be read by outside world to see if there's any work * to do. */ global DRCPendingCookie * DRCPendingRoot = (DRCPendingCookie *) NULL; /* Pointers to yank buffer's use and def on the heap */ CellDef * DRCdef = (CellDef *) NULL; CellUse * DRCuse = (CellUse *) NULL; /* Boolean indicating whether DRC initialization has been done. */ bool DRCInitialized = FALSE; /* Boolean indicating whether or not check tiles are being displayed. */ bool DRCDisplayCheckTiles = FALSE; /* The following use is a dummy, provided because the DBCellCopyAll * routine requires a cellUse, and we don't generally have one. * As a kludge, we just keep around this dummy, with identity * transform and no arraying, and just change the def pointer * in it before calling DBCellCopyAll. */ CellUse *DRCDummyUse = (CellUse *) NULL; /*------- Things used only within this module. -------*/ /* Layer mask for the DRC layers. */ TileTypeBitMask DRCLayers; /* In order to reduce the amount of DRC redisplay, whenever an area * is rechecked we log all the previous violations in that area, * XOR them with all of the new violations in the area, and only * redisplay the bounding box of the changes. The plane and paint * tables below are used for storing the information and doing the * XOR. */ static Plane *drcDisplayPlane; #define DRC_SOLID 1 static PaintResultType drcXorTable[] = {DRC_SOLID, TT_SPACE}; /* When computing DRC violations, they don't get painted directly * into the database. Instead, they get painted first into a * temporary plane. That way, if DRC is interrupted we can exit * quickly without leaving error information in a weird state. * Once everything has been computed, then it's painted into the * cell in one fell swoop. */ static Plane *drcTempPlane; /* Forward declarations and imports from other drc modules: */ extern int drcCheckTile(); extern CellDef *DRCErrorDef; extern TileType DRCErrorType; /* * ---------------------------------------------------------------------------- * DRCCheckThis -- * * Mark an area of a CellDef so that it will be checked by continuous * DRC. Add the CellDef to the list of Defs to be checked. If the * pointer to the area rectangle is NULL, just add the CellDef to the * list. * * Two types of check tiles (CHECKPAINT and CHECKSUBCELL) are used * to distinguish areas of new paint in the CellDef from areas where * subcells of the CellDef have changed. Painted areas will be * checked for internal consistency and their interactions with * subcells. Areas where a subcell has changed will be checked * for interactions with paint, interactions with other subcells, and * correct array structure (if the subcell is used as an array). * * Called by paint, erase and cell operations in the commands module. * Also calls itself recursively in order to propagate check areas up * through a hierarchy of CellDefs and CellUses. * * Results: * None. * * Side effects: * Modifies the PL_DRC_CHECK plane of given CellDef. The area * recorded on the plane is one halo larger than the area modified, * i.e. the area where errors must be regenerated. Inserts the * CellDef into the list of Defs waiting to be checked. * ---------------------------------------------------------------------------- */ /* ARGSUSED */ void DRCCheckThis (celldef, operation, area) CellDef * celldef; /* Allows check areas to propagate * up from EditCell. */ TileType operation; /* TT_CHECKPAINT or TT_CHECKSUBCELL */ Rect * area; /* Area that changed. */ { CellUse * cu; /* Ptr to uses of the given CellDef */ Rect transRect; /* Area in coords of parent CellDefs, * expanded if the use is in an array */ Rect dummyRect, dummyRect2; DRCPendingCookie * p, ** pback; /* Used to insert new element in list * of CellDefs waiting for DRC */ /* Ignore internal cells. */ if (celldef->cd_flags & CDINTERNAL) return; /* Insert celldef into list of Defs waiting to be checked, unless */ /* it is already there. */ /* The switch to copying up DRC errors from non-interacting */ /* child cells means that the child cells must be processed */ /* first. So this routine changes from prepending the cell */ /* to the list to appending it. */ #if (0) pback = &DRCPendingRoot; p = DRCPendingRoot; while (p != (DRCPendingCookie *) NULL) { if (p->dpc_def == celldef) { *pback = p->dpc_next; break; } pback = &(p->dpc_next); p = p->dpc_next; } if (p == (DRCPendingCookie *) NULL) { p = (DRCPendingCookie *) mallocMagic(sizeof (DRCPendingCookie)); p->dpc_def = celldef; } p->dpc_next = DRCPendingRoot; DRCPendingRoot = p; #endif /* Append new cell to check to the pending list */ if (DRCPendingRoot == NULL) { p = (DRCPendingCookie *) mallocMagic(sizeof (DRCPendingCookie)); p->dpc_def = celldef; p->dpc_next = NULL; DRCPendingRoot = p; } else { DRCPendingCookie *plast; plast = DRCPendingRoot; while (plast->dpc_next != NULL) { if (plast->dpc_def == celldef) break; plast = plast->dpc_next; } if (plast->dpc_next == NULL) { p = (DRCPendingCookie *) mallocMagic(sizeof (DRCPendingCookie)); p->dpc_def = celldef; p->dpc_next = NULL; plast->dpc_next = p; } } /* Mark the area in this celldef (but don't worry about this stuff * for undo purposes). Also, it's important to disable interrupts * in here, or the paint operation could get aborted underneath us. */ if (area != (Rect *) NULL) { GEO_EXPAND(area, DRCTechHalo, &dummyRect); SigDisableInterrupts(); DBPaintPlane(celldef->cd_planes[PL_DRC_CHECK], &dummyRect, DBStdPaintTbl(TT_CHECKPAINT, PL_DRC_CHECK), (PaintUndoInfo *) NULL); SigEnableInterrupts(); } else return; /* Call recursively for each use of this * CellDef in a parent. */ for (cu = celldef->cd_parents; cu != (CellUse *) NULL; cu = cu->cu_nextuse) { if (cu->cu_parent == (CellDef *) NULL) /* use for a Window */ continue; /* area in coordinates of the parent Def */ GeoTransRect (&(cu->cu_transform), area, &transRect); if ((cu->cu_xlo != cu->cu_xhi) || (cu->cu_ylo != cu->cu_yhi)) { /* transRect needs to include all images * of "area" in the array, in coordinates * of the parent Def */ DBComputeArrayArea(area, cu, cu->cu_xhi, cu->cu_yhi, &dummyRect); GeoTransRect(&cu->cu_transform, &dummyRect, &dummyRect2); (void) GeoInclude (&dummyRect2, &transRect); } DRCCheckThis (cu->cu_parent, TT_CHECKSUBCELL, &transRect); } } /* * ---------------------------------------------------------------------------- * * DRCRemovePending -- * * Remove a cell def from the DRC pending list. * * Results: * None. * * Side effects: * DRCPendingRoot linked list modified * * ---------------------------------------------------------------------------- */ void DRCRemovePending(def) CellDef *def; { DRCPendingCookie *p, *plast; p = DRCPendingRoot; plast = NULL; while (p != NULL) { if (p->dpc_def == def) { if (plast == NULL) DRCPendingRoot = p->dpc_next; else plast->dpc_next = p->dpc_next; freeMagic(p); return; } plast = p; p = p->dpc_next; } } /* * ---------------------------------------------------------------------------- * * DRCInit -- * * This procedure initializes DRC data. It must be called after * the technology file has been read. * * Results: * None. * * Side effects: * Owns shared between the DRC modules are initialized. * * ---------------------------------------------------------------------------- */ void DRCInit() { int i; TileTypeBitMask displayedTypes; /* Only do initialization once. */ if (DRCInitialized) return; DRCInitialized = TRUE; /* Create a cell use to use for holding yank data in interaction checks. */ DRCdef = DBCellLookDef(DRCYANK); if (DRCdef == (CellDef *) NULL) { DRCdef = DBCellNewDef(DRCYANK); ASSERT(DRCdef != (CellDef *) NULL, "DRCInit"); DBCellSetAvail(DRCdef); DRCdef->cd_flags |= CDINTERNAL; } DRCuse = DBCellNewUse (DRCdef, (char *) NULL); DBSetTrans (DRCuse, &GeoIdentityTransform); DRCuse->cu_expandMask = CU_DESCEND_SPECIAL; /* This is always expanded. */ /* Create a dummy cell use to use for passing to procedures * that need a use when all we've got is a def. */ DRCDummyUse = DBCellNewUse(DRCdef, (char *) NULL); DBSetTrans (DRCDummyUse, &GeoIdentityTransform); /* See if check tiles are being displayed. */ TTMaskZero(&displayedTypes); for (i = 0; i < DBWNumStyles; i++) TTMaskSetMask(&displayedTypes, DBWStyleToTypes(i)); DRCDisplayCheckTiles = TTMaskHasType(&displayedTypes, TT_CHECKPAINT) || TTMaskHasType(&displayedTypes, TT_CHECKSUBCELL); /* Initialize mask of DRC layer types. */ TTMaskZero(&DRCLayers); TTMaskSetType(&DRCLayers, TT_ERROR_P); TTMaskSetType(&DRCLayers, TT_ERROR_S); TTMaskSetType(&DRCLayers, TT_ERROR_PS); /* Create planes to hold error areas to redisplay and to hold * temporary error information. */ drcDisplayPlane = DBNewPlane((ClientData) TT_SPACE); drcTempPlane = DBNewPlane((ClientData) TT_SPACE); } #ifdef MAGIC_WRAPPER /* Needs to be global so the DRCBreak() routine can use the value */ Rect drc_orig_bbox; /* * ---------------------------------------------------------------------------- * DRCBreak -- * * This is called by "window" or "file" event callbacks from magic * which would invalidate the current DRC process. This routine * is just a copy of the exit code for DRCContinuous. Because Tcl * doesn't have a "peek" routine for examining its event queue, we * can only execute pending events and break the DRC process from * the event callback code. * This is called prior to running commands which do their own * WindUpdate() call, so it is not necessary to call it here. * ---------------------------------------------------------------------------- */ void DRCBreak() { if (DRCHasWork && (DRCStatus == DRC_IN_PROGRESS)) { UndoEnable(); /* fprintf(stderr, "DRC breaking. . .\n"); fflush(stderr); */ /* As a convenience for debugging DRC stuff, we pretend the DRC * cell is a real one and recompute its bounding box, and redisplay * both its old area (currently in box) and its current area. */ DBReComputeBbox(DRCdef); (void) GeoInclude(&DRCdef->cd_bbox, &drc_orig_bbox); DBWAreaChanged(DRCdef, &drc_orig_bbox, DBW_ALLWINDOWS, &DBAllButSpaceBits); DRCStatus = DRC_BREAK_PENDING; } } #endif /* * ---------------------------------------------------------------------------- * DRCContinuous -- * * Called by WindDispatch() before it goes to sleep waiting for user input. * This routine checks to see if there are any areas of the layout that * need to be design-rule-checked. If so, it does the appropriate checks. * This procedure will abort itself at the earliest convenient moment * after the user types a new command. * * Results: * None. * * Side effects: * Modifies the DRC_CHECK and DRC_ERROR planes * of the CellDefs on the DRCPending list. * ---------------------------------------------------------------------------- */ void DRCContinuous() { #ifndef MAGIC_WRAPPER Rect drc_orig_bbox; /* Area of DRC def that changed. */ #endif if (DRCHasWork == FALSE) { #ifdef MAGIC_WRAPPER DRCStatus = DRC_NOT_RUNNING; #endif return; } #ifdef MAGIC_WRAPPER if (DRCStatus != DRC_NOT_RUNNING) return; /* Avoid infinitely recursive loop */ GrFlush(); DRCStatus = DRC_IN_PROGRESS; Tcl_EvalEx(magicinterp, "after idle magic::drcstate busy", -1, 0); if (TxInputRedirect != TX_INPUT_REDIRECTED) TxSetPrompt(']'); /* fprintf(stderr, "Starting DRC\n"); fflush(stderr); */ #endif UndoDisable(); /* Don't want to undo error info. */ drc_orig_bbox = DRCdef->cd_bbox; while (DRCPendingRoot != (DRCPendingCookie *) NULL) { /* DBSrPaintArea() returns 1 if drcCheckTile() * returns 1, meaning that a CHECK tile * was found and processed. */ while ((DRCPendingRoot != (DRCPendingCookie *)NULL) && DBSrPaintArea ((Tile *) NULL, DRCPendingRoot->dpc_def->cd_planes[PL_DRC_CHECK], &TiPlaneRect, &DBAllButSpaceBits, drcCheckTile, (ClientData) NULL)) { /* check for new user command (without blocking) */ #ifdef MAGIC_WRAPPER /* Execute pending Tcl events, so the DRC process doesn't block. */ /* NOTE: Exclude file events, or else "drc catchup" will not work */ /* in batch mode. */ UndoEnable(); while (Tcl_DoOneEvent(TCL_DONT_WAIT)) { if (DRCStatus == DRC_BREAK_PENDING) { /* fprintf(stderr, "DRC exiting loop. . .\n"); fflush(stderr); */ DRCStatus = DRC_NOT_RUNNING; return; } } UndoDisable(); /* fprintf(stderr, "DRC continuing internally. . .\n"); fflush(stderr); */ #else #ifndef USE_IO_PROBE if (SigInterruptPending) goto checkDone; #else if (TxGetInputEvent (FALSE, FALSE) == TRUE) goto checkDone; #endif #endif } /* No check tiles were found, so knock this cell off the list. */ if (DRCPendingRoot != (DRCPendingCookie *)NULL) { DBReComputeBbox(DRCPendingRoot->dpc_def); freeMagic((char *) DRCPendingRoot); DRCPendingRoot = DRCPendingRoot->dpc_next; } /* Give the timestamp manager a chance to update any mismatches. */ DBFixMismatch(); } #ifdef MAGIC_WRAPPER DRCStatus = DRC_NOT_RUNNING; Tcl_EvalEx(magicinterp, "after idle magic::drcstate idle", -1, 0); /* fprintf(stderr, "DRC is finished\n"); fflush(stderr); */ if (TxInputRedirect != TX_INPUT_REDIRECTED) TxSetPrompt('%'); #endif checkDone: UndoEnable(); /* As a convenience for debugging DRC stuff, we pretend the DRC * cell is a real one and recompute its bounding box, and redisplay * both its old area (currently in box) and its current area. */ DBReComputeBbox(DRCdef); (void) GeoInclude(&DRCdef->cd_bbox, &drc_orig_bbox); DBWAreaChanged(DRCdef, &drc_orig_bbox, DBW_ALLWINDOWS, &DBAllButSpaceBits); #ifdef MAGIC_WRAPPER WindUpdate(); GrFlush(); #endif } /* * ---------------------------------------------------------------------------- * drcCheckTile -- * * This procedure is called when a check tile is found in * the DRC_CHECK plane of the CellDef at the front of the * DRCPending list. This procedure is the heart of Magic's * continuous checker. * * For DRC purposes, each cell is divided up checkerboard-style * into areas DRCStepSize on each side. All checking is done * in terms of these squares. When a check tile is found, we * find the outer area of all check tiles in the square containing * the original check tile's lower-left corner. Errors within * this area are regenerated, then all check tiles in that area * are erased. The checkerboard approach serves three purposes. * First, it allows nearby small tiles to be combined for checking * purposes. Second, it limits the maximum amount of work that * is done at once, so if we're getting interrupted by new commands * there is still some hope of eventually getting the DRC caught up. * And third, it provides a canonical form for the checks, particularly * those involving subcells, so the same results are produced no * matter what the original check area is. * * The three DRC meta-rules are: * (1) paint in one CellDef must be consistent by itself, * that is, without regard to subcells * (2) subtrees must be consistent -- this includes both * paint interacting with subcells, and subcells * interacting with each other. * (3) an arrayed use of a CellDef must be consistent by itself, * that is, without regard to paint or other subcells * in the parent. This check happens automatically as * part of the subtree check. * * Two types of error tiles are kept independently in the * DRC_ERROR plane: (1) paint * (2) subtree * * This function is passed to DBSrPaintArea(). * * Results: * Always returns one. This function is only called on non-space * (CHECK) tiles, so if it is called a CHECK tile must have been * processed and we want DBSrPaintArea to abort the search. * * Side effects: * Modifies both DRC planes of the CellDef at the front of the * DRCPending list. * ---------------------------------------------------------------------------- */ /* ARGSUSED */ int drcCheckTile(tile, arg) Tile * tile; /* tile in DRC_CHECK plane */ ClientData arg; /* Not used. */ { Rect square; /* Square area of the checkerboard * being processed right now. */ Rect erasebox; /* erase old ERROR tiles in this * region and clip new ERRORs to it */ Rect checkbox; CellDef * celldef; /* First CellDef on DRCPending list. */ Rect redisplayArea; /* Area to be redisplayed. */ extern int drcXorFunc(); /* Forward declarations. */ extern int drcPutBackFunc(); celldef = DRCPendingRoot->dpc_def; DRCErrorDef = celldef; /* Find the checkerboard square containing the lower-left corner * of the check tile, then find all check tiles within that square. */ DRCstatSquares += 1; square.r_xbot = (LEFT(tile)/DRCStepSize) * DRCStepSize; if (square.r_xbot > LEFT(tile)) square.r_xbot -= DRCStepSize; square.r_ybot = (BOTTOM(tile)/DRCStepSize) * DRCStepSize; if (square.r_ybot > BOTTOM(tile)) square.r_ybot -= DRCStepSize; square.r_xtop = square.r_xbot + DRCStepSize; square.r_ytop = square.r_ybot + DRCStepSize; erasebox = GeoNullRect; (void) DBSrPaintArea((Tile *) NULL, celldef->cd_planes[PL_DRC_CHECK], &square, &DBAllButSpaceBits, drcIncludeArea, (ClientData) &erasebox); GeoClip(&erasebox, &square); /* TxPrintf("Check area = (%d, %d) (%d, %d)\n", erasebox.r_xbot, erasebox.r_ybot, erasebox.r_xtop, erasebox.r_ytop); */ /* checkbox is erasebox expanded by DRCTechHalo. Note that this is */ /* computed independently inside DRCInteractionCheck(). */ GEO_EXPAND(&erasebox, DRCTechHalo, &checkbox); GeoClip(&checkbox, &square); /* Use drcDisplayPlane to save all the current errors in the * area we're about to recheck. */ DBClearPaintPlane(drcDisplayPlane); (void) DBSrPaintArea((Tile *) NULL, celldef->cd_planes[PL_DRC_ERROR], &square, &DBAllButSpaceBits, drcXorFunc, (ClientData) NULL); DRCErrorType = TT_ERROR_P; DBClearPaintPlane(drcTempPlane); /* May 4, 2008: Moved DRCBasicCheck into DRCInteractionCheck * to avoid requiring DRC rules to be satisfied independently * of subcells (checkbox was [erasebox + DRCTechHalo], now * computed within DRCInteractionCheck()). */ /* Check interactions between paint and subcells, and * also between subcells and other subcells. If any part of a * square is rechecked for interactions, the whole thing has to * be rechecked. We use TT_ERROR_S tiles for this so that we * don't have to recheck paint and array errors over the whole * square. */ DRCErrorType = TT_ERROR_S; (void) DRCInteractionCheck(celldef, &square, &erasebox, drcPaintError, (ClientData)drcTempPlane); /* If there was an interrupt, return without modifying the cell * at all. */ if (SigInterruptPending) return 1; /* Erase the check tile from the check plane, erase the pre-existing * error tiles, and paint back in the new error tiles. Do this all * with interrupts disabled to be sure that it won't be aborted. */ SigDisableInterrupts(); DBPaintPlane(celldef->cd_planes[PL_DRC_CHECK], &erasebox, DBStdEraseTbl(TiGetType(tile), PL_DRC_CHECK), (PaintUndoInfo *) NULL); DBPaintPlane(celldef->cd_planes[PL_DRC_ERROR], &erasebox, DBStdEraseTbl(TT_ERROR_P, PL_DRC_ERROR), (PaintUndoInfo *) NULL); DBPaintPlane(celldef->cd_planes[PL_DRC_ERROR], &checkbox, DBStdEraseTbl(TT_ERROR_S, PL_DRC_ERROR), (PaintUndoInfo *) NULL); (void) DBSrPaintArea((Tile *) NULL, drcTempPlane, &TiPlaneRect, &DBAllButSpaceBits, drcPutBackFunc, (ClientData) celldef); /* XOR the new errors in the tile with the old errors we * saved in drcDisplayPlane. Where information has changed, * clip to square and redisplay. If check tiles are being * displayed, then always redisplay the entire area. */ (void) DBSrPaintArea((Tile *) NULL, celldef->cd_planes[PL_DRC_ERROR], &square, &DBAllButSpaceBits, drcXorFunc, (ClientData) NULL); if (DBBoundPlane(drcDisplayPlane, &redisplayArea)) { GeoClip(&redisplayArea, &square); if (!GEO_RECTNULL(&redisplayArea)) DBWAreaChanged (celldef, &redisplayArea, DBW_ALLWINDOWS, &DRCLayers); } if (DRCDisplayCheckTiles) DBWAreaChanged(celldef, &square, DBW_ALLWINDOWS, &DRCLayers); DBCellSetModified (celldef, TRUE); SigEnableInterrupts(); return (1); /* stop the area search: we modified the database! */ } /* The utility function below gets called for each error tile in a * region. It just XOR's the area of the tile into drcDisplayPlane. */ int drcXorFunc(tile) Tile *tile; { Rect area; TiToRect(tile, &area); DBPaintPlane(drcDisplayPlane, &area, drcXorTable, (PaintUndoInfo *) NULL); return 0; } /* This procedure is the one that actually paints error tiles into the * database cells. */ int drcPutBackFunc(tile, cellDef) Tile *tile; /* Error tile, from drcTempPlane. */ CellDef *cellDef; /* Celldef in which to paint error. */ { Rect area; TiToRect(tile, &area); DBPaintPlane(cellDef->cd_planes[PL_DRC_ERROR], &area, DBStdPaintTbl(TiGetType(tile), PL_DRC_ERROR), (PaintUndoInfo *) NULL); return 0; } /* * ---------------------------------------------------------------------------- * * drcIncludeArea -- * * This is a drc utility procedure called by DBSrPaintArea. It * merely computes the total area of non-space tiles in the * given area of the plane. It is only called for non-space * tiles. * * Results: * Always returns 0 so the search continues. * * Side effects: * The client data must be a pointer to a rectangle. The * rectangle is enlarged to include the area of this tile. * * ---------------------------------------------------------------------------- */ int drcIncludeArea(tile, rect) Tile *tile; Rect *rect; /* Rectangle in which to record total area. */ { Rect dum; TiToRect(tile, &dum); (void) GeoInclude(&dum, rect); return 0; }