From 376f487fde82cb12c4f92fde4246c0d8d51ca1c7 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Sat, 28 Feb 2026 01:37:11 +0000 Subject: [PATCH 1/8] Add per-object origin tracking (vOrigins) to Gia_Man_t MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add lightweight origin tracking that propagates source-location provenance through ABC's optimization passes. This enables the Yosys abc9 flow to preserve \src attributes on LUT cells after technology mapping, achieving ~100% coverage on tested designs with negligible overhead (~0.6% time, ~3% memory on picorv32). Changes: - Add Vec_Int_t *vOrigins field to Gia_Man_t with inline accessors - Read/write origins via AIGER "y" extension (sentinel -1 for unmapped) - Propagate through all Gia_ManDup* variants via Gia_ManOriginsDup() - Propagate through structural hashing (AND/XOR/MUX) in giaHash.c - Recover origins after GIA→AIG→GIA round-trips (&dc2, &dch) via Gia_ManOriginsAfterRoundTrip() using CO driver + top-down propagation - Propagate through IF mapper using iCopy correspondence - Instrument giaEquiv, giaMuxes, giaTim, giaBalAig, dauGia Co-developed-by: Claude Code v2.1.44 (claude-opus-4-6) --- src/aig/gia/gia.h | 6 +++ src/aig/gia/giaAig.c | 3 ++ src/aig/gia/giaAiger.c | 36 ++++++++++++-- src/aig/gia/giaBalAig.c | 2 + src/aig/gia/giaDup.c | 106 +++++++++++++++++++++++++++++++++++++++- src/aig/gia/giaEquiv.c | 1 + src/aig/gia/giaHash.c | 37 ++++++++++++++ src/aig/gia/giaIf.c | 18 +++++++ src/aig/gia/giaMan.c | 1 + src/aig/gia/giaMuxes.c | 2 + src/aig/gia/giaTim.c | 2 + src/opt/dau/dauGia.c | 2 + 12 files changed, 212 insertions(+), 4 deletions(-) diff --git a/src/aig/gia/gia.h b/src/aig/gia/gia.h index 6fddcac96b..c168d2acdb 100644 --- a/src/aig/gia/gia.h +++ b/src/aig/gia/gia.h @@ -190,6 +190,7 @@ struct Gia_Man_t_ Vec_Int_t * vIdsOrig; // original object IDs Vec_Int_t * vIdsEquiv; // original object IDs proved equivalent Vec_Int_t * vEquLitIds; // original object IDs proved equivalent + Vec_Int_t * vOrigins; // per-object origin mapping (from "y" extension) Vec_Int_t * vCofVars; // cofactoring variables Vec_Vec_t * vClockDoms; // clock domains Vec_Flt_t * vTiming; // arrival/required/slack @@ -462,6 +463,9 @@ static inline int Gia_ManIsConst0Lit( int iLit ) { return (iLit == static inline int Gia_ManIsConst1Lit( int iLit ) { return (iLit == 1); } static inline int Gia_ManIsConstLit( int iLit ) { return (iLit <= 1); } +static inline int Gia_ObjOrigin( Gia_Man_t * p, int iObj ) { return p->vOrigins ? Vec_IntEntry(p->vOrigins, iObj) : -1; } +static inline void Gia_ObjSetOrigin( Gia_Man_t * p, int iObj, int iOrig ) { if (p->vOrigins) Vec_IntWriteEntry(p->vOrigins, iObj, iOrig); } + static inline Gia_Obj_t * Gia_Regular( Gia_Obj_t * p ) { return (Gia_Obj_t *)((ABC_PTRUINT_T)(p) & ~01); } static inline Gia_Obj_t * Gia_Not( Gia_Obj_t * p ) { return (Gia_Obj_t *)((ABC_PTRUINT_T)(p) ^ 01); } static inline Gia_Obj_t * Gia_NotCond( Gia_Obj_t * p, int c ) { return (Gia_Obj_t *)((ABC_PTRUINT_T)(p) ^ (c)); } @@ -1348,6 +1352,8 @@ extern Gia_Man_t * Gia_ManDupOrderDfsReverse( Gia_Man_t * p, int fRevFan extern Gia_Man_t * Gia_ManDupOutputGroup( Gia_Man_t * p, int iOutStart, int iOutStop ); extern Gia_Man_t * Gia_ManDupOutputVec( Gia_Man_t * p, Vec_Int_t * vOutPres ); extern Gia_Man_t * Gia_ManDupSelectedOutputs( Gia_Man_t * p, Vec_Int_t * vOutsLeft ); +extern void Gia_ManOriginsDup( Gia_Man_t * pNew, Gia_Man_t * pOld ); +extern void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ); extern Gia_Man_t * Gia_ManDupOrderAiger( Gia_Man_t * p ); extern Gia_Man_t * Gia_ManDupLastPis( Gia_Man_t * p, int nLastPis ); extern Gia_Man_t * Gia_ManDupFlip( Gia_Man_t * p, int * pInitState ); diff --git a/src/aig/gia/giaAig.c b/src/aig/gia/giaAig.c index 07bab5fec2..70d19e351a 100644 --- a/src/aig/gia/giaAig.c +++ b/src/aig/gia/giaAig.c @@ -600,6 +600,7 @@ Gia_Man_t * Gia_ManCompress2( Gia_Man_t * p, int fUpdateLevel, int fVerbose ) Aig_ManStop( pTemp ); pGia = Gia_ManFromAig( pNew ); Aig_ManStop( pNew ); + Gia_ManOriginsAfterRoundTrip( pGia, p ); Gia_ManTransferTiming( pGia, p ); return pGia; } @@ -658,6 +659,8 @@ Gia_Man_t * Gia_ManPerformDch( Gia_Man_t * p, void * pPars ) Gia_ManStop( pGia ); pGia = Gia_ManDup( p ); } + else + Gia_ManOriginsAfterRoundTrip( pGia, p ); Gia_ManTransferTiming( pGia, p ); return pGia; } diff --git a/src/aig/gia/giaAiger.c b/src/aig/gia/giaAiger.c index c37c3ea9e4..6901f23dd8 100644 --- a/src/aig/gia/giaAiger.c +++ b/src/aig/gia/giaAiger.c @@ -947,7 +947,21 @@ Gia_Man_t * Gia_AigerReadFromMemory( char * pContents, int nFileSize, int fGiaSi else if ( fVerbose ) printf( "Finished reading extension \"y\".\n" ); } else { - if ( fVerbose ) printf( "Cannot read extension \"y\" because AIG is rehashed. Use \"&r -s \".\n" ); + if ( fVerbose ) printf( "Skipped extension \"y\" for vEquLitIds (AIG is rehashed).\n" ); + } + // populate vOrigins using vNodes to map AIG→GIA object indices + if ( nInts == Vec_IntSize(vNodes) ) { + int k; + int * pData = (int *)pCur; + pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + for ( k = 0; k < nInts; k++ ) + { + int giaLit = Vec_IntEntry( vNodes, k ); + int giaObj = Abc_Lit2Var( giaLit ); + int rawLit = pData[k]; + if ( rawLit >= 0 && giaObj < Gia_ManObjNum(pNew) ) + Vec_IntWriteEntry( pNew->vOrigins, giaObj, Abc_Lit2Var(rawLit) ); + } } pCur += 4*nInts; } @@ -1836,8 +1850,24 @@ void Gia_AigerWriteS( Gia_Man_t * pInit, char * pFileName, int fWriteSymbols, in assert( Vec_IntSize(p->vObjClasses) == Gia_ManObjNum(p) ); fwrite( Vec_IntArray(p->vObjClasses), 1, 4*Gia_ManObjNum(p), pFile ); } - // write object classes - if ( p->vEquLitIds ) + // write object origins (vOrigins takes priority over vEquLitIds) + if ( p->vOrigins ) + { + int k, nObjs = Gia_ManObjNum(p); + Vec_Int_t * vLits = Vec_IntStart( nObjs ); + assert( Vec_IntSize(p->vOrigins) == nObjs ); + for ( k = 0; k < nObjs; k++ ) + { + int orig = Vec_IntEntry(p->vOrigins, k); + Vec_IntWriteEntry( vLits, k, orig >= 0 ? 2 * orig : -1 ); + } + fprintf( pFile, "y" ); + Gia_FileWriteBufferSize( pFile, 4*nObjs ); + fwrite( Vec_IntArray(vLits), 1, (size_t)4*nObjs, pFile ); + Vec_IntFree( vLits ); + if ( fVerbose ) printf( "Finished writing extension \"y\" (from origins).\n" ); + } + else if ( p->vEquLitIds ) { fprintf( pFile, "y" ); Gia_FileWriteBufferSize( pFile, 4*Gia_ManObjNum(p) ); diff --git a/src/aig/gia/giaBalAig.c b/src/aig/gia/giaBalAig.c index 5603b4108b..9a132f8432 100644 --- a/src/aig/gia/giaBalAig.c +++ b/src/aig/gia/giaBalAig.c @@ -420,6 +420,7 @@ Gia_Man_t * Gia_ManBalanceInt( Gia_Man_t * p, int fStrict ) } assert( !fStrict || Gia_ManObjNum(pNew) <= Gia_ManObjNum(p) ); Gia_ManHashStop( pNew ); + Gia_ManOriginsDup( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); // perform cleanup pNew = Gia_ManCleanup( pTemp = pNew ); @@ -819,6 +820,7 @@ Gia_Man_t * Dam_ManMultiAig( Dam_Man_t * pMan ) } // assert( Gia_ManObjNum(pNew) <= Gia_ManObjNum(p) ); Gia_ManHashStop( pNew ); + Gia_ManOriginsDup( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); // perform cleanup pNew = Gia_ManCleanup( pTemp = pNew ); diff --git a/src/aig/gia/giaDup.c b/src/aig/gia/giaDup.c index d1187cb241..a5522148e1 100644 --- a/src/aig/gia/giaDup.c +++ b/src/aig/gia/giaDup.c @@ -193,12 +193,108 @@ int Gia_ManDupOrderDfs_rec( Gia_Man_t * pNew, Gia_Man_t * p, Gia_Obj_t * pObj ) return pObj->Value = Gia_ManAppendAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); } +/**Function************************************************************* + + Synopsis [Propagates origin mapping from old to new manager.] + + Description [Uses Value field of old objects to find corresponding new objects.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +void Gia_ManOriginsDup( Gia_Man_t * pNew, Gia_Man_t * pOld ) +{ + Gia_Obj_t * pObj; + int i; + if ( !pOld->vOrigins ) + return; + pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + Gia_ManForEachObj( pOld, pObj, i ) + { + if ( (int)Gia_ObjValue(pObj) != -1 ) + { + int iNew = Abc_Lit2Var( Gia_ObjValue(pObj) ); + if ( iNew < Gia_ManObjNum(pNew) ) + Vec_IntWriteEntry( pNew->vOrigins, iNew, + Vec_IntEntry(pOld->vOrigins, i) ); + } + } +} + +/**Function************************************************************* + + Synopsis [Restores origins after GIA->AIG->GIA round-trip.] + + Description [CIs map 1:1 in order. CO drivers map 1:1 (output + correspondence preserved through optimization). Remaining AND nodes + get origins via top-down propagation from CO drivers through fanin + cones. Note: shared nodes between multiple CO cones get the origin + of whichever CO driver is visited first (non-deterministic but + acceptable for best-effort source attribution).] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ) +{ + Gia_Obj_t * pObj; + int i; + if ( !pOld->vOrigins ) + return; + assert( Gia_ManCiNum(pNew) == Gia_ManCiNum(pOld) ); + assert( Gia_ManCoNum(pNew) == Gia_ManCoNum(pOld) ); + pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + // const0 + if ( Vec_IntSize(pOld->vOrigins) > 0 ) + Vec_IntWriteEntry( pNew->vOrigins, 0, Vec_IntEntry(pOld->vOrigins, 0) ); + // CIs map 1:1 in order + Gia_ManForEachCi( pNew, pObj, i ) + { + int iNewObj = Gia_ObjId( pNew, pObj ); + int iOldCi = Gia_ObjId( pOld, Gia_ManCi(pOld, i) ); + Vec_IntWriteEntry( pNew->vOrigins, iNewObj, + Vec_IntEntry(pOld->vOrigins, iOldCi) ); + } + // CO drivers map 1:1 (output correspondence preserved through optimization) + Gia_ManForEachCo( pNew, pObj, i ) + { + int iNewDriver = Gia_ObjFaninId0p( pNew, pObj ); + Gia_Obj_t * pOldCo = Gia_ManCo( pOld, i ); + int iOldDriver = Gia_ObjFaninId0p( pOld, pOldCo ); + if ( iNewDriver > 0 && Vec_IntEntry(pNew->vOrigins, iNewDriver) == -1 ) + Vec_IntWriteEntry( pNew->vOrigins, iNewDriver, + Vec_IntEntry(pOld->vOrigins, iOldDriver) ); + } + // Top-down propagation: spread CO driver origins backward through fanin cones + // Walk AND nodes in reverse topological order (high to low ID) + for ( i = Gia_ManObjNum(pNew) - 1; i > 0; i-- ) + { + int f0, f1, orig; + pObj = Gia_ManObj( pNew, i ); + if ( !Gia_ObjIsAnd(pObj) ) + continue; + orig = Vec_IntEntry( pNew->vOrigins, i ); + if ( orig < 0 ) + continue; + f0 = Gia_ObjFaninId0(pObj, i); + f1 = Gia_ObjFaninId1(pObj, i); + if ( f0 > 0 && Vec_IntEntry(pNew->vOrigins, f0) == -1 ) + Vec_IntWriteEntry( pNew->vOrigins, f0, orig ); + if ( f1 > 0 && Vec_IntEntry(pNew->vOrigins, f1) == -1 ) + Vec_IntWriteEntry( pNew->vOrigins, f1, orig ); + } +} + /**Function************************************************************* Synopsis [Duplicates AIG while putting objects in the DFS order.] Description [] - + SideEffects [] SeeAlso [] @@ -221,6 +317,7 @@ Gia_Man_t * Gia_ManDupOrderDfs( Gia_Man_t * p ) pObj->Value = Gia_ManAppendCi(pNew); assert( Gia_ManCiNum(pNew) == Gia_ManCiNum(p) ); Gia_ManDupRemapCis( pNew, p ); + Gia_ManOriginsDup( pNew, p ); Gia_ManDupRemapEquiv( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); return pNew; @@ -545,6 +642,7 @@ Gia_Man_t * Gia_ManDupOrderAiger( Gia_Man_t * p ) pObj->Value = Gia_ManAppendAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); Gia_ManForEachCo( p, pObj, i ) pObj->Value = Gia_ManAppendCo( pNew, Gia_ObjFanin0Copy(pObj) ); + Gia_ManOriginsDup( pNew, p ); Gia_ManDupRemapEquiv( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); assert( Gia_ManIsNormalized(pNew) ); @@ -778,6 +876,7 @@ Gia_Man_t * Gia_ManDup( Gia_Man_t * p ) else if ( Gia_ObjIsCo(pObj) ) pObj->Value = Gia_ManAppendCo( pNew, Gia_ObjFanin0Copy(pObj) ); } + Gia_ManOriginsDup( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); if ( p->pCexSeq ) pNew->pCexSeq = Abc_CexDup( p->pCexSeq, Gia_ManRegNum(p) ); @@ -1575,6 +1674,7 @@ Gia_Man_t * Gia_ManDupMarked( Gia_Man_t * p ) pNew->pSibls[Abc_Lit2Var(pObj->Value)] = Abc_Lit2Var(pSibl->Value); } } + Gia_ManOriginsDup( pNew, p ); return pNew; } @@ -1806,6 +1906,7 @@ Gia_Man_t * Gia_ManDupDfs( Gia_Man_t * p ) Gia_ManDupDfs_rec( pNew, p, Gia_ObjFanin0(pObj) ); Gia_ManForEachCo( p, pObj, i ) pObj->Value = Gia_ManAppendCo( pNew, Gia_ObjFanin0Copy(pObj) ); + Gia_ManOriginsDup( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); pNew->nConstrs = p->nConstrs; if ( p->pCexSeq ) @@ -1874,6 +1975,7 @@ Gia_Man_t * Gia_ManDupDfsRehash( Gia_Man_t * p ) Gia_ManDupDfsRehash_rec( pNew, p, Gia_ObjFanin0(pObj) ); Gia_ManForEachCo( p, pObj, i ) pObj->Value = Gia_ManAppendCo( pNew, Gia_ObjFanin0Copy(pObj) ); + Gia_ManOriginsDup( pNew, p ); pNew = Gia_ManCleanup( pTemp = pNew ); Gia_ManStop( pTemp ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); @@ -3890,6 +3992,8 @@ Gia_Man_t * Gia_ManChoiceMiter( Vec_Ptr_t * vGias ) Gia_ManChoiceMiter_rec( pNew, pGia, Gia_ManCo( pGia, k ) ); } Gia_ManHashStop( pNew ); + // propagate origins from the first (primary) AIG + Gia_ManOriginsDup( pNew, pGia0 ); // check the presence of dangling nodes nNodes = Gia_ManHasDangling( pNew ); //assert( nNodes == 0 ); diff --git a/src/aig/gia/giaEquiv.c b/src/aig/gia/giaEquiv.c index e8ddc99131..1081b50c72 100644 --- a/src/aig/gia/giaEquiv.c +++ b/src/aig/gia/giaEquiv.c @@ -744,6 +744,7 @@ Gia_Man_t * Gia_ManEquivReduce( Gia_Man_t * p, int fUseAll, int fDualOut, int fS Gia_ManForEachCo( p, pObj, i ) pObj->Value = Gia_ManAppendCo( pNew, Gia_ObjFanin0Copy(pObj) ); Gia_ManHashStop( pNew ); + Gia_ManOriginsDup( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); return pNew; } diff --git a/src/aig/gia/giaHash.c b/src/aig/gia/giaHash.c index 063cfd31ab..743e94754e 100644 --- a/src/aig/gia/giaHash.c +++ b/src/aig/gia/giaHash.c @@ -503,6 +503,17 @@ int Gia_ManHashXorReal( Gia_Man_t * p, int iLit0, int iLit1 ) assert( *pPlace == 0 ); *pPlace = Abc_Lit2Var( iNode ); } + // propagate origin from parent with lower valid origin ID + if ( p->vOrigins ) + { + int iNew = *pPlace; + int o0 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit0)); + int o1 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit1)); + int orig = (o0 >= 0 && (o1 < 0 || o0 <= o1)) ? o0 : o1; + while ( Vec_IntSize(p->vOrigins) <= iNew ) + Vec_IntPush( p->vOrigins, -1 ); + Vec_IntWriteEntry( p->vOrigins, iNew, orig ); + } return Abc_Var2Lit( *pPlace, fCompl ); } } @@ -558,6 +569,19 @@ int Gia_ManHashMuxReal( Gia_Man_t * p, int iLitC, int iLit1, int iLit0 ) assert( *pPlace == 0 ); *pPlace = Abc_Lit2Var( iNode ); } + // propagate origin from parent with lower valid origin ID + if ( p->vOrigins ) + { + int iNew = *pPlace; + int o0 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit0)); + int o1 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit1)); + int oC = Gia_ObjOrigin(p, Abc_Lit2Var(iLitC)); + int orig = (o0 >= 0 && (o1 < 0 || o0 <= o1)) ? o0 : o1; + if ( oC >= 0 && (orig < 0 || oC <= orig) ) orig = oC; + while ( Vec_IntSize(p->vOrigins) <= iNew ) + Vec_IntPush( p->vOrigins, -1 ); + Vec_IntWriteEntry( p->vOrigins, iNew, orig ); + } return Abc_Var2Lit( *pPlace, fCompl ); } } @@ -615,6 +639,18 @@ int Gia_ManHashAnd( Gia_Man_t * p, int iLit0, int iLit1 ) assert( *pPlace == 0 ); *pPlace = Abc_Lit2Var( iNode ); } + // propagate origin from parent with lower valid origin ID + if ( p->vOrigins ) + { + int iNew = *pPlace; + int o0 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit0)); + int o1 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit1)); + int orig = (o0 >= 0 && (o1 < 0 || o0 <= o1)) ? o0 : o1; + // grow vOrigins if needed + while ( Vec_IntSize(p->vOrigins) <= iNew ) + Vec_IntPush( p->vOrigins, -1 ); + Vec_IntWriteEntry( p->vOrigins, iNew, orig ); + } return Abc_Var2Lit( *pPlace, 0 ); } } @@ -761,6 +797,7 @@ Gia_Man_t * Gia_ManRehash( Gia_Man_t * p, int fAddStrash ) } Gia_ManHashStop( pNew ); pNew->fAddStrash = 0; + Gia_ManOriginsDup( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); // printf( "Top gate is %s\n", Gia_ObjFaninC0(Gia_ManCo(pNew, 0))? "OR" : "AND" ); pNew = Gia_ManCleanup( pTemp = pNew ); diff --git a/src/aig/gia/giaIf.c b/src/aig/gia/giaIf.c index c2d8886c0a..a341bc80af 100644 --- a/src/aig/gia/giaIf.c +++ b/src/aig/gia/giaIf.c @@ -3065,6 +3065,24 @@ Gia_Man_t * Gia_ManPerformMappingInt( Gia_Man_t * p, If_Par_t * pPars ) pNew = Gia_ManFromIfAig( pIfMan ); else pNew = Gia_ManFromIfLogic( pIfMan ); + // propagate origin mapping from input GIA through IF mapper to output GIA + // IF objects 0..Gia_ManObjNum(p)-1 correspond 1:1 to input GIA objects; + // iCopy gives the literal in the output GIA (set by Gia_ManFromIfLogic/Aig) + if ( p->vOrigins ) + { + If_Obj_t * pIfObj = NULL; + pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + If_ManForEachObj( pIfMan, pIfObj, i ) + { + if ( i < Gia_ManObjNum(p) && pIfObj->iCopy >= 0 ) + { + int iNewObj = Abc_Lit2Var( pIfObj->iCopy ); + if ( iNewObj < Gia_ManObjNum(pNew) ) + Vec_IntWriteEntry( pNew->vOrigins, iNewObj, + Vec_IntEntry(p->vOrigins, i) ); + } + } + } if ( p->vCiArrs || p->vCoReqs ) { If_Obj_t * pIfObj = NULL; diff --git a/src/aig/gia/giaMan.c b/src/aig/gia/giaMan.c index 889a49c0e2..35646e341c 100644 --- a/src/aig/gia/giaMan.c +++ b/src/aig/gia/giaMan.c @@ -107,6 +107,7 @@ void Gia_ManStop( Gia_Man_t * p ) Vec_IntFreeP( &p->vIdsOrig ); Vec_IntFreeP( &p->vIdsEquiv ); Vec_IntFreeP( &p->vEquLitIds ); + Vec_IntFreeP( &p->vOrigins ); Vec_IntFreeP( &p->vLutConfigs ); Vec_IntFreeP( &p->vEdgeDelay ); Vec_IntFreeP( &p->vEdgeDelayR ); diff --git a/src/aig/gia/giaMuxes.c b/src/aig/gia/giaMuxes.c index ff542c3053..8eb2e761b9 100644 --- a/src/aig/gia/giaMuxes.c +++ b/src/aig/gia/giaMuxes.c @@ -140,6 +140,7 @@ Gia_Man_t * Gia_ManDupMuxes( Gia_Man_t * p, int Limit ) pNew->pSibls[Gia_ObjId(pNew, pObjNew)] = Gia_ObjId(pNew, pSiblNew); } Gia_ManHashStop( pNew ); + Gia_ManOriginsDup( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); // perform cleanup pNew = Gia_ManCleanup( pTemp = pNew ); @@ -253,6 +254,7 @@ Gia_Man_t * Gia_ManDupNoMuxes( Gia_Man_t * p, int fSkipBufs ) pObj->Value = Gia_ManHashAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); } Gia_ManHashStop( pNew ); + Gia_ManOriginsDup( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); // perform cleanup pNew = Gia_ManCleanup( pTemp = pNew ); diff --git a/src/aig/gia/giaTim.c b/src/aig/gia/giaTim.c index 5f13dcfa70..9e91579598 100644 --- a/src/aig/gia/giaTim.c +++ b/src/aig/gia/giaTim.c @@ -186,6 +186,7 @@ Gia_Man_t * Gia_ManDupNormalize( Gia_Man_t * p, int fHashMapping ) Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); pNew->nConstrs = p->nConstrs; assert( Gia_ManIsNormalized(pNew) ); + Gia_ManOriginsDup( pNew, p ); Gia_ManDupRemapEquiv( pNew, p ); return pNew; } @@ -420,6 +421,7 @@ Gia_Man_t * Gia_ManDupUnnormalize( Gia_Man_t * p ) pObj->Value = 0; else assert( 0 ); } + Gia_ManOriginsDup( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); Vec_IntFree( vNodes ); return pNew; diff --git a/src/opt/dau/dauGia.c b/src/opt/dau/dauGia.c index a49a253cd9..5bf81bdd96 100644 --- a/src/opt/dau/dauGia.c +++ b/src/opt/dau/dauGia.c @@ -562,6 +562,8 @@ void * Dsm_ManDeriveGia( void * pGia, int fUseMuxes ) assert( iLev == 1 + Abc_MaxInt(iLev0, iLev1) ); } */ + // propagate origins + Gia_ManOriginsDup( pNew, p ); // perform cleanup pNew = Gia_ManCleanup( pTemp = pNew ); Gia_ManStop( pTemp ); From 58dfff9df5437c2458418e1c68a4fad48998b434 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Thu, 5 Mar 2026 23:07:15 +0000 Subject: [PATCH 2/8] Propagate vOrigins through all ABC9 engines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend origin tracking to cover the complete abc9 optimization pipeline. Add Gia_ManOriginsDupVec for functions that use Vec_Int_t copy vectors instead of the Value field. Engines instrumented: - &dc2, &dch: Round-trip recovery (giaAig.c) - &if: iCopy-based propagation in SopBalance/DsdBalance (giaIf.c), plus DupHashMapping/DupUnhashMapping for timing paths - &jf, &lf: OriginsDupVec in Jf_ManDeriveGia/Lf_ManDeriveMappingGia - &syn2, &synch2: Sub-operations (Jf/Lf mappers) + round-trip recovery in Gia_ManAigSynch2Choices, DupFromBarBufs/DupToBarBufs for box designs (giaScript.c) - &sweep: Gia_ManFraigReduceGia, Gia_ManDupWithBoxes (giaSweep.c) - &scorr: Gia_ManCorrReduce (cecCorr.c) - &mfs: Custom propagation via vMfs2Old/vMfs2Gia (giaMfs.c) - Supporting: Gia_ManDupCollapse/DupNormalize/DupUnnormalize (giaTim.c), DupUnshuffleInputs/DupMoveLast (giaTim.c), EquivToChoices (giaEquiv.c), DupMuxes (giaMuxes.c), BalanceInt (giaBalAig.c), DauMergePart (dauGia.c), DupWithAttributes (giaDup.c) Add bounds checks in Gia_ObjOrigin and Gia_ObjSetOrigin for GIAs that grow after vOrigins is allocated (e.g., AreaBalance adding nodes). Coverage verified by static analysis finding all Gia_Man_t* functions that call Gia_ManStart without Gia_ManOriginsDup — 23 functions covered, remaining 167 are outside the abc9 pipeline. Co-developed-by: Claude Code v2.1.58 (claude-opus-4-6) --- src/aig/gia/gia.h | 5 +++-- src/aig/gia/giaDup.c | 34 ++++++++++++++++++++++++++++++---- src/aig/gia/giaEquiv.c | 1 + src/aig/gia/giaIf.c | 38 +++++++++++++++++++++++++++++++++++++- src/aig/gia/giaJf.c | 4 +++- src/aig/gia/giaLf.c | 4 +++- src/aig/gia/giaMfs.c | 20 ++++++++++++++++++++ src/aig/gia/giaScript.c | 4 ++++ src/aig/gia/giaSweep.c | 2 ++ src/aig/gia/giaTim.c | 3 +++ src/proof/cec/cecCorr.c | 1 + 11 files changed, 107 insertions(+), 9 deletions(-) diff --git a/src/aig/gia/gia.h b/src/aig/gia/gia.h index c168d2acdb..7291348090 100644 --- a/src/aig/gia/gia.h +++ b/src/aig/gia/gia.h @@ -463,8 +463,8 @@ static inline int Gia_ManIsConst0Lit( int iLit ) { return (iLit == static inline int Gia_ManIsConst1Lit( int iLit ) { return (iLit == 1); } static inline int Gia_ManIsConstLit( int iLit ) { return (iLit <= 1); } -static inline int Gia_ObjOrigin( Gia_Man_t * p, int iObj ) { return p->vOrigins ? Vec_IntEntry(p->vOrigins, iObj) : -1; } -static inline void Gia_ObjSetOrigin( Gia_Man_t * p, int iObj, int iOrig ) { if (p->vOrigins) Vec_IntWriteEntry(p->vOrigins, iObj, iOrig); } +static inline int Gia_ObjOrigin( Gia_Man_t * p, int iObj ) { return (p->vOrigins && iObj < Vec_IntSize(p->vOrigins)) ? Vec_IntEntry(p->vOrigins, iObj) : -1; } +static inline void Gia_ObjSetOrigin( Gia_Man_t * p, int iObj, int iOrig ) { if (p->vOrigins && iObj < Vec_IntSize(p->vOrigins)) Vec_IntWriteEntry(p->vOrigins, iObj, iOrig); } static inline Gia_Obj_t * Gia_Regular( Gia_Obj_t * p ) { return (Gia_Obj_t *)((ABC_PTRUINT_T)(p) & ~01); } static inline Gia_Obj_t * Gia_Not( Gia_Obj_t * p ) { return (Gia_Obj_t *)((ABC_PTRUINT_T)(p) ^ 01); } @@ -1353,6 +1353,7 @@ extern Gia_Man_t * Gia_ManDupOutputGroup( Gia_Man_t * p, int iOutStart, extern Gia_Man_t * Gia_ManDupOutputVec( Gia_Man_t * p, Vec_Int_t * vOutPres ); extern Gia_Man_t * Gia_ManDupSelectedOutputs( Gia_Man_t * p, Vec_Int_t * vOutsLeft ); extern void Gia_ManOriginsDup( Gia_Man_t * pNew, Gia_Man_t * pOld ); +extern void Gia_ManOriginsDupVec( Gia_Man_t * pNew, Gia_Man_t * pOld, Vec_Int_t * vCopies ); extern void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ); extern Gia_Man_t * Gia_ManDupOrderAiger( Gia_Man_t * p ); extern Gia_Man_t * Gia_ManDupLastPis( Gia_Man_t * p, int nLastPis ); diff --git a/src/aig/gia/giaDup.c b/src/aig/gia/giaDup.c index a5522148e1..68455c35fe 100644 --- a/src/aig/gia/giaDup.c +++ b/src/aig/gia/giaDup.c @@ -213,6 +213,8 @@ void Gia_ManOriginsDup( Gia_Man_t * pNew, Gia_Man_t * pOld ) pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); Gia_ManForEachObj( pOld, pObj, i ) { + if ( i >= Vec_IntSize(pOld->vOrigins) ) + break; if ( (int)Gia_ObjValue(pObj) != -1 ) { int iNew = Abc_Lit2Var( Gia_ObjValue(pObj) ); @@ -239,6 +241,24 @@ void Gia_ManOriginsDup( Gia_Man_t * pNew, Gia_Man_t * pOld ) SeeAlso [] ***********************************************************************/ +void Gia_ManOriginsDupVec( Gia_Man_t * pNew, Gia_Man_t * pOld, Vec_Int_t * vCopies ) +{ + int i, iLit; + if ( !pOld->vOrigins ) + return; + pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + Vec_IntForEachEntry( vCopies, iLit, i ) + { + if ( iLit != -1 ) + { + int iNew = Abc_Lit2Var( iLit ); + if ( iNew < Gia_ManObjNum(pNew) && i < Vec_IntSize(pOld->vOrigins) ) + Vec_IntWriteEntry( pNew->vOrigins, iNew, + Vec_IntEntry(pOld->vOrigins, i) ); + } + } +} + void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ) { Gia_Obj_t * pObj; @@ -256,8 +276,9 @@ void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ) { int iNewObj = Gia_ObjId( pNew, pObj ); int iOldCi = Gia_ObjId( pOld, Gia_ManCi(pOld, i) ); - Vec_IntWriteEntry( pNew->vOrigins, iNewObj, - Vec_IntEntry(pOld->vOrigins, iOldCi) ); + if ( iOldCi < Vec_IntSize(pOld->vOrigins) ) + Vec_IntWriteEntry( pNew->vOrigins, iNewObj, + Vec_IntEntry(pOld->vOrigins, iOldCi) ); } // CO drivers map 1:1 (output correspondence preserved through optimization) Gia_ManForEachCo( pNew, pObj, i ) @@ -265,7 +286,8 @@ void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ) int iNewDriver = Gia_ObjFaninId0p( pNew, pObj ); Gia_Obj_t * pOldCo = Gia_ManCo( pOld, i ); int iOldDriver = Gia_ObjFaninId0p( pOld, pOldCo ); - if ( iNewDriver > 0 && Vec_IntEntry(pNew->vOrigins, iNewDriver) == -1 ) + if ( iNewDriver > 0 && iOldDriver < Vec_IntSize(pOld->vOrigins) && + Vec_IntEntry(pNew->vOrigins, iNewDriver) == -1 ) Vec_IntWriteEntry( pNew->vOrigins, iNewDriver, Vec_IntEntry(pOld->vOrigins, iOldDriver) ); } @@ -928,11 +950,14 @@ Gia_Man_t * Gia_ManDupWithAttributes( Gia_Man_t * p ) pNew->vConfigs2 = Vec_StrDup( p->vConfigs2 ); if ( p->pCellStr ) pNew->pCellStr = Abc_UtilStrsav( p->pCellStr ); + // copy origins if present + if ( p->vOrigins ) + pNew->vOrigins = Vec_IntDup( p->vOrigins ); // copy names if present if ( p->vNamesIn ) pNew->vNamesIn = Vec_PtrDupStr( p->vNamesIn ); if ( p->vNamesOut ) - pNew->vNamesOut = Vec_PtrDupStr( p->vNamesOut ); + pNew->vNamesOut = Vec_PtrDupStr( p->vNamesOut ); return pNew; } Gia_Man_t * Gia_ManDupRemovePis( Gia_Man_t * p, int nRemPis ) @@ -4035,6 +4060,7 @@ Gia_Man_t * Gia_ManDupWithConstraints( Gia_Man_t * p, Vec_Int_t * vPoTypes ) Gia_ManForEachRi( p, pObj, i ) pObj->Value = Gia_ManAppendCo( pNew, Gia_ObjFanin0Copy(pObj) ); // Gia_ManDupRemapEquiv( pNew, p ); + Gia_ManOriginsDup( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); pNew->nConstrs = nConstr; assert( Gia_ManIsNormalized(pNew) ); diff --git a/src/aig/gia/giaEquiv.c b/src/aig/gia/giaEquiv.c index 1081b50c72..96013df928 100644 --- a/src/aig/gia/giaEquiv.c +++ b/src/aig/gia/giaEquiv.c @@ -2072,6 +2072,7 @@ Gia_Man_t * Gia_ManEquivToChoices( Gia_Man_t * p, int nSnapshots ) Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); Gia_ManRemoveBadChoices( pNew ); //Gia_ManEquivPrintClasses( pNew, 0, 0 ); + Gia_ManOriginsDup( pNew, p ); pNew = Gia_ManCleanup( pTemp = pNew ); Gia_ManStop( pTemp ); //Gia_ManEquivPrintClasses( pNew, 0, 0 ); diff --git a/src/aig/gia/giaIf.c b/src/aig/gia/giaIf.c index a341bc80af..f2fdc55df1 100644 --- a/src/aig/gia/giaIf.c +++ b/src/aig/gia/giaIf.c @@ -3074,7 +3074,7 @@ Gia_Man_t * Gia_ManPerformMappingInt( Gia_Man_t * p, If_Par_t * pPars ) pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); If_ManForEachObj( pIfMan, pIfObj, i ) { - if ( i < Gia_ManObjNum(p) && pIfObj->iCopy >= 0 ) + if ( i < Vec_IntSize(p->vOrigins) && pIfObj->iCopy >= 0 ) { int iNewObj = Abc_Lit2Var( pIfObj->iCopy ); if ( iNewObj < Gia_ManObjNum(pNew) ) @@ -3178,6 +3178,23 @@ Gia_Man_t * Gia_ManPerformSopBalance( Gia_Man_t * p, int nCutNum, int nRelaxRati pIfMan = Gia_ManToIf( p, pPars ); If_ManPerformMapping( pIfMan ); pNew = Gia_ManFromIfAig( pIfMan ); + // propagate origins via IF mapper iCopy correspondence + if ( p->vOrigins ) + { + If_Obj_t * pIfObj = NULL; + int j; + pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + If_ManForEachObj( pIfMan, pIfObj, j ) + { + if ( j < Vec_IntSize(p->vOrigins) && pIfObj->iCopy >= 0 ) + { + int iNewObj = Abc_Lit2Var( pIfObj->iCopy ); + if ( iNewObj < Gia_ManObjNum(pNew) ) + Vec_IntWriteEntry( pNew->vOrigins, iNewObj, + Vec_IntEntry(p->vOrigins, j) ); + } + } + } If_ManStop( pIfMan ); Gia_ManTransferTiming( pNew, p ); // transfer name @@ -3211,6 +3228,23 @@ Gia_Man_t * Gia_ManPerformDsdBalance( Gia_Man_t * p, int nLutSize, int nCutNum, If_DsdManAllocIsops( pIfMan->pIfDsdMan, pPars->nLutSize ); If_ManPerformMapping( pIfMan ); pNew = Gia_ManFromIfAig( pIfMan ); + // propagate origins via IF mapper iCopy correspondence + if ( p->vOrigins ) + { + If_Obj_t * pIfObj = NULL; + int j; + pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + If_ManForEachObj( pIfMan, pIfObj, j ) + { + if ( j < Vec_IntSize(p->vOrigins) && pIfObj->iCopy >= 0 ) + { + int iNewObj = Abc_Lit2Var( pIfObj->iCopy ); + if ( iNewObj < Gia_ManObjNum(pNew) ) + Vec_IntWriteEntry( pNew->vOrigins, iNewObj, + Vec_IntEntry(p->vOrigins, j) ); + } + } + } If_ManStop( pIfMan ); Gia_ManTransferTiming( pNew, p ); // transfer name @@ -3316,6 +3350,7 @@ Gia_Man_t * Gia_ManDupHashMapping( Gia_Man_t * p ) Vec_IntPush( vMapping, Abc_Lit2Var(pObj->Value) ); } pNew->vMapping = vMapping; + Gia_ManOriginsDup( pNew, p ); return pNew; } @@ -3395,6 +3430,7 @@ Gia_Man_t * Gia_ManDupUnhashMapping( Gia_Man_t * p ) } Vec_IntFree( vMap ); pNew->vMapping = vMapping; + Gia_ManOriginsDup( pNew, p ); return pNew; } diff --git a/src/aig/gia/giaJf.c b/src/aig/gia/giaJf.c index 205ab40801..e0711a288a 100644 --- a/src/aig/gia/giaJf.c +++ b/src/aig/gia/giaJf.c @@ -1511,10 +1511,11 @@ Gia_Man_t * Jf_ManDeriveMappingGia( Jf_Man_t * p ) if ( p->pPars->fGenCnf ) Jf_ManGenCnf( ABC_CONST(0xAAAAAAAAAAAAAAAA), iLit, vLeaves, vLits, vClas, vCover ); } + Gia_ManOriginsDupVec( pNew, p->pGia, vCopies ); Vec_IntFree( vCopies ); Vec_IntFree( vCover ); Vec_IntFree( vLeaves ); - // finish mapping + // finish mapping if ( Vec_IntSize(vMapping) > Gia_ManObjNum(pNew) ) Vec_IntShrink( vMapping, Gia_ManObjNum(pNew) ); else @@ -1653,6 +1654,7 @@ Gia_Man_t * Jf_ManDeriveGia( Jf_Man_t * p ) } if ( !p->pPars->fCutMin ) Gia_ObjComputeTruthTableStop( p->pGia ); + Gia_ManOriginsDupVec( pNew, p->pGia, vCopies ); Vec_IntFree( vCopies ); Vec_IntFree( vLeaves ); Vec_IntFree( vCover ); diff --git a/src/aig/gia/giaLf.c b/src/aig/gia/giaLf.c index 4f3e49f424..7d15a45d3d 100644 --- a/src/aig/gia/giaLf.c +++ b/src/aig/gia/giaLf.c @@ -1771,6 +1771,7 @@ Gia_Man_t * Lf_ManDeriveMappingCoarse( Lf_Man_t * p ) Vec_IntPush( pNew->vMapping, pCut->fMux7 ? -Abc_Lit2Var(pObj->Value) : Abc_Lit2Var(pObj->Value) ); } Gia_ManSetRegNum( pNew, Gia_ManRegNum(pGia) ); + Gia_ManOriginsDup( pNew, pGia ); assert( Vec_IntCap(pNew->vMapping) == 16 || Vec_IntSize(pNew->vMapping) == Vec_IntCap(pNew->vMapping) ); return pNew; } @@ -1909,10 +1910,11 @@ Gia_Man_t * Lf_ManDeriveMappingGia( Lf_Man_t * p ) iLit = Lf_ManDerivePart( p, pNew, vMapping, vMapping2, vCopies, pCut, vLeaves, vCover, pObj ); Vec_IntWriteEntry( vCopies, i, Abc_LitNotCond(iLit, Abc_LitIsCompl(pCut->iFunc)) ); } + Gia_ManOriginsDupVec( pNew, p->pGia, vCopies ); Vec_IntFree( vCopies ); Vec_IntFree( vCover ); Vec_IntFree( vLeaves ); - // finish mapping + // finish mapping if ( Vec_IntSize(vMapping) > Gia_ManObjNum(pNew) ) Vec_IntShrink( vMapping, Gia_ManObjNum(pNew) ); else diff --git a/src/aig/gia/giaMfs.c b/src/aig/gia/giaMfs.c index 6ad6b0039c..eaee7ca63d 100644 --- a/src/aig/gia/giaMfs.c +++ b/src/aig/gia/giaMfs.c @@ -531,6 +531,26 @@ Gia_Man_t * Gia_ManInsertMfs( Gia_Man_t * p, Sfm_Ntk_t * pNtk, int fAllBoxes ) if ( p->vRegInits ) pNew->vRegInits = Vec_IntDup( p->vRegInits ); pNew->nAnd2Delay = p->nAnd2Delay; + // propagate origins via MFS ID correspondence + if ( p->vOrigins ) + { + int iOldObj; + pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + Vec_IntForEachEntry( vMfs2Old, iOldObj, i ) + { + if ( iOldObj >= 0 ) + { + int iNewLit = Vec_IntEntry( vMfs2Gia, i ); + if ( iNewLit >= 0 ) + { + int iNewObj = Abc_Lit2Var( iNewLit ); + if ( iNewObj < Gia_ManObjNum(pNew) && iOldObj < Vec_IntSize(p->vOrigins) ) + Vec_IntWriteEntry( pNew->vOrigins, iNewObj, + Vec_IntEntry(p->vOrigins, iOldObj) ); + } + } + } + } // cleanup Vec_WecFree( vGroups ); diff --git a/src/aig/gia/giaScript.c b/src/aig/gia/giaScript.c index b52288a946..59d8791517 100644 --- a/src/aig/gia/giaScript.c +++ b/src/aig/gia/giaScript.c @@ -315,6 +315,7 @@ Gia_Man_t * Gia_ManDupFromBarBufs( Gia_Man_t * p ) Gia_ManForEachCo( p, pObj, i ) Gia_ManAppendCo( pNew, Gia_ObjFanin0Copy(pObj) ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); + Gia_ManOriginsDup( pNew, p ); return pNew; } Gia_Man_t * Gia_ManDupToBarBufs( Gia_Man_t * p, int nBarBufs ) @@ -357,6 +358,7 @@ Gia_Man_t * Gia_ManDupToBarBufs( Gia_Man_t * p, int nBarBufs ) assert( Gia_ManBufNum(pNew) == nBarBufs ); assert( Gia_ManCiNum(pNew) == nPiReal ); assert( Gia_ManCoNum(pNew) == nPoReal ); + Gia_ManOriginsDup( pNew, p ); return pNew; } @@ -396,6 +398,8 @@ Gia_Man_t * Gia_ManAigSynch2Choices( Gia_Man_t * pGia1, Gia_Man_t * pGia2, Gia_M // convert to GIA pGia = Gia_ManFromAigChoices( pMan ); Aig_ManStop( pMan ); + // recover origins from base variant (pGia1) via CI/CO correspondence + Gia_ManOriginsAfterRoundTrip( pGia, pGia1 ); return pGia; } Gia_Man_t * Gia_ManAigSynch2( Gia_Man_t * pInit, void * pPars0, int nLutSize, int nRelaxRatio ) diff --git a/src/aig/gia/giaSweep.c b/src/aig/gia/giaSweep.c index d93ff2a605..d3e8a1407d 100644 --- a/src/aig/gia/giaSweep.c +++ b/src/aig/gia/giaSweep.c @@ -204,6 +204,7 @@ Gia_Man_t * Gia_ManDupWithBoxes( Gia_Man_t * p, int fSeq ) assert( Gia_ManCiNum(pNew) == Tim_ManPiNum((Tim_Man_t*)pNew->pManTime) + Gia_ManCoNum(pNew->pAigExtra) ); Vec_IntFree( vBoxesLeft ); pNew->nAnd2Delay = p->nAnd2Delay; + Gia_ManOriginsDup( pNew, p ); return pNew; } @@ -368,6 +369,7 @@ Gia_Man_t * Gia_ManFraigReduceGia( Gia_Man_t * p, int * pReprs ) else assert( 0 ); } Gia_ManHashStop( pNew ); + Gia_ManOriginsDup( pNew, p ); return pNew; } diff --git a/src/aig/gia/giaTim.c b/src/aig/gia/giaTim.c index 9e91579598..0ad60aac6a 100644 --- a/src/aig/gia/giaTim.c +++ b/src/aig/gia/giaTim.c @@ -241,6 +241,7 @@ Gia_Man_t * Gia_ManDupUnshuffleInputs( Gia_Man_t * p ) Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); pNew->nConstrs = p->nConstrs; assert( Gia_ManIsNormalized(pNew) ); + Gia_ManOriginsDup( pNew, p ); Gia_ManDupRemapEquiv( pNew, p ); return pNew; } @@ -779,6 +780,7 @@ Gia_Man_t * Gia_ManDupMoveLast( Gia_Man_t * p, int iInsert, int nItems ) else assert( 0 ); } Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); + Gia_ManOriginsDup( pNew, p ); return pNew; } @@ -905,6 +907,7 @@ Gia_Man_t * Gia_ManDupCollapse( Gia_Man_t * p, Gia_Man_t * pBoxes, Vec_Int_t * v assert( curCo == Gia_ManCoNum(p) ); Gia_ManSetRegNum( pNew, (fSeq && p->vRegClasses) ? Vec_IntSize(p->vRegClasses) : Gia_ManRegNum(p) ); Gia_ManHashStop( pNew ); + Gia_ManOriginsDup( pNew, p ); pNew = Gia_ManCleanup( pTemp = pNew ); Gia_ManCleanupRemap( p, pTemp ); Gia_ManStop( pTemp ); diff --git a/src/proof/cec/cecCorr.c b/src/proof/cec/cecCorr.c index 0496b2e49b..a8a8f563ae 100644 --- a/src/proof/cec/cecCorr.c +++ b/src/proof/cec/cecCorr.c @@ -714,6 +714,7 @@ Gia_Man_t * Gia_ManCorrReduce( Gia_Man_t * p ) Gia_ManAppendCo( pNew, Gia_ObjFanin0Copy(pObj) ); Gia_ManHashStop( pNew ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); + Gia_ManOriginsDup( pNew, p ); return pNew; } From f2633f295f97be1ba6477ae0bb99bf3c51f8c0f4 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Sat, 14 Mar 2026 19:47:13 +0000 Subject: [PATCH 3/8] Multi-origin vOrigins with inline buffer optimization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace single-origin-per-object vOrigins with a union-based scheme that stores up to GIA_ORIGINS_INLINE (default 4) origins inline, with overflow to a heap-allocated array for outliers. This captures merge events from structural hashing and optimization that previously lost origin information. Data structure (Gia_OriginsEntry_t union, 16 bytes on 64-bit): - Inline mode: 4 inline int slots (-1 = unused) - Overflow mode: sentinel (INT_MIN) + count + heap pointer - Stride = sizeof(entry)/sizeof(int) = 4 ints per object Key changes: - gia.h: Union type, stride constants, inline accessors (Gia_ObjOriginsNum, Gia_ObjOriginsGet, Gia_ObjForEachOrigin), updated Gia_ObjOrigin/Gia_ObjSetOrigin for backward compat - giaDup.c: Gia_ObjAddOrigin (dedup + promote), Gia_ObjUnionOrigins, Gia_ManOriginsFreeOverflows; updated OriginsDup/DupVec/AfterRoundTrip for strided multi-origin propagation - giaHash.c: Union all input origins on hash miss AND hash hit (captures structural sharing from different source paths) - giaAiger.c: Variable-length "y" extension format [count, lit0, lit1,...] with backward-compatible reader for old single-origin format - giaIf.c, giaMfs.c: Mapper propagation via Gia_ObjUnionOrigins - giaMan.c: Free overflow arrays before vOrigins cleanup Co-developed-by: Claude Code v2.1.58 (claude-opus-4-6) Harden multi-origin vOrigins: leak guards, geometric growth, format sentinel - Add free-before-alloc guards in all OriginsDup functions to prevent memory leaks when vOrigins is already populated (e.g. Gia_ManDup followed by Gia_ManDupWithAttributes) - Replace O(n²) realloc-by-1 with geometric doubling in overflow arrays (8-slot initial capacity, double at powers of 2) - Add sentinel int -2 to AIGER "y" extension writer/reader for unambiguous new-vs-old format detection (fixes edge case where nInts == nAigObjs with all-zero origins) - Extract Gia_ManOriginsDupIf() helper to deduplicate 3 identical 14-line IF mapper origin propagation blocks - Replace Gia_ManOriginsGrow push-loop with Vec_IntFillExtra - Add vNodes NULL guard in AIGER reader - Remove orphaned duplicate comment block Co-developed-by: Claude Code v2.1.58 (claude-opus-4-6) Simplify vOrigins: extract OriginsReset, fix free(), hoist iteration count Code review cleanup: - Extract Gia_ManOriginsReset() helper to replace 5 identical free-overflows + free-vec leak-guard blocks - Use ABC_FREE() instead of bare free() in Gia_ObjSetOrigin for allocator consistency - Hoist Gia_ObjOriginsNum() call out of Gia_ObjForEachOrigin macro loop condition (callers declare _nOrig) - Add compile-time assertion that GIA_ORIGINS_INLINE is large enough to cover the overflow header on the target platform Co-developed-by: Claude Code v2.1.83 (claude-opus-4-6) --- src/aig/gia/gia.h | 108 +++++++++++++++++- src/aig/gia/giaAiger.c | 51 +++++++-- src/aig/gia/giaDup.c | 244 +++++++++++++++++++++++++++++++++++------ src/aig/gia/giaHash.c | 56 ++++++---- src/aig/gia/giaIf.c | 54 +-------- src/aig/gia/giaMan.c | 1 + src/aig/gia/giaMfs.c | 7 +- 7 files changed, 394 insertions(+), 127 deletions(-) diff --git a/src/aig/gia/gia.h b/src/aig/gia/gia.h index 7291348090..1fc26c856d 100644 --- a/src/aig/gia/gia.h +++ b/src/aig/gia/gia.h @@ -30,6 +30,7 @@ #include #include #include +#include #include "misc/vec/vec.h" #include "misc/vec/vecWec.h" @@ -45,6 +46,35 @@ ABC_NAMESPACE_HEADER_START #define GIA_NONE 0x1FFFFFFF #define GIA_VOID 0x0FFFFFFF +// Multi-origin tracking: small-buffer optimization with overflow +#ifndef GIA_ORIGINS_INLINE +#define GIA_ORIGINS_INLINE 4 // number of inline origin slots (configurable at compile time) +#endif + +typedef union { + struct { + int origins[GIA_ORIGINS_INLINE]; // -1 = unused slot + } inl; + struct { + int sentinel; // INT_MIN marks overflow mode + int count; // number of origins in overflow array + int * overflow; // heap-allocated origin array + } ovf; +} Gia_OriginsEntry_t; + +#define GIA_ORIGINS_STRIDE ((int)(sizeof(Gia_OriginsEntry_t) / sizeof(int))) +#define GIA_ORIGINS_SENTINEL INT_MIN + +// Compile-time checks: inline slots must cover the overflow header +// (sentinel + count + pointer; minimum 4 ints on 64-bit), and must +// not exceed the hardcoded initial overflow capacity of 8. +#if GIA_ORIGINS_INLINE < 4 +# error "GIA_ORIGINS_INLINE must be >= 4 (overflow header needs sentinel+count+ptr, min 4 ints on 64-bit)" +#endif +#if GIA_ORIGINS_INLINE > 7 +# error "GIA_ORIGINS_INLINE must be <= 7 (initial overflow capacity is 8)" +#endif + //////////////////////////////////////////////////////////////////////// /// BASIC TYPES /// //////////////////////////////////////////////////////////////////////// @@ -463,8 +493,77 @@ static inline int Gia_ManIsConst0Lit( int iLit ) { return (iLit == static inline int Gia_ManIsConst1Lit( int iLit ) { return (iLit == 1); } static inline int Gia_ManIsConstLit( int iLit ) { return (iLit <= 1); } -static inline int Gia_ObjOrigin( Gia_Man_t * p, int iObj ) { return (p->vOrigins && iObj < Vec_IntSize(p->vOrigins)) ? Vec_IntEntry(p->vOrigins, iObj) : -1; } -static inline void Gia_ObjSetOrigin( Gia_Man_t * p, int iObj, int iOrig ) { if (p->vOrigins && iObj < Vec_IntSize(p->vOrigins)) Vec_IntWriteEntry(p->vOrigins, iObj, iOrig); } +// Multi-origin accessors +static inline Gia_OriginsEntry_t * Gia_ObjOriginsEntry( Gia_Man_t * p, int iObj ) +{ + return (Gia_OriginsEntry_t *)(Vec_IntArray(p->vOrigins) + iObj * GIA_ORIGINS_STRIDE); +} +static inline int Gia_ObjOriginsIsOverflow( Gia_OriginsEntry_t * e ) +{ + return e->inl.origins[0] == GIA_ORIGINS_SENTINEL; +} +static inline int Gia_ObjOriginsNum( Gia_Man_t * p, int iObj ) +{ + Gia_OriginsEntry_t * e; + int k; + if ( !p->vOrigins || iObj * GIA_ORIGINS_STRIDE >= Vec_IntSize(p->vOrigins) ) + return 0; + e = Gia_ObjOriginsEntry( p, iObj ); + if ( Gia_ObjOriginsIsOverflow(e) ) + return e->ovf.count; + for ( k = 0; k < GIA_ORIGINS_INLINE; k++ ) + if ( e->inl.origins[k] == -1 ) break; + return k; +} +static inline int Gia_ObjOriginsGet( Gia_Man_t * p, int iObj, int idx ) +{ + Gia_OriginsEntry_t * e; + if ( !p->vOrigins || iObj * GIA_ORIGINS_STRIDE >= Vec_IntSize(p->vOrigins) ) + return -1; + e = Gia_ObjOriginsEntry( p, iObj ); + if ( Gia_ObjOriginsIsOverflow(e) ) + return (idx < e->ovf.count) ? e->ovf.overflow[idx] : -1; + return (idx < GIA_ORIGINS_INLINE) ? e->inl.origins[idx] : -1; +} +// Backward compatible: get first/primary origin +static inline int Gia_ObjOrigin( Gia_Man_t * p, int iObj ) +{ + Gia_OriginsEntry_t * e; + if ( !p->vOrigins || iObj * GIA_ORIGINS_STRIDE >= Vec_IntSize(p->vOrigins) ) + return -1; + e = Gia_ObjOriginsEntry( p, iObj ); + if ( Gia_ObjOriginsIsOverflow(e) ) + return e->ovf.count > 0 ? e->ovf.overflow[0] : -1; + return e->inl.origins[0]; +} +// Set single origin (clears any existing, for initialization) +static inline void Gia_ObjSetOrigin( Gia_Man_t * p, int iObj, int iOrig ) +{ + Gia_OriginsEntry_t * e; + int k; + if ( !p->vOrigins || iObj * GIA_ORIGINS_STRIDE >= Vec_IntSize(p->vOrigins) ) + return; + e = Gia_ObjOriginsEntry( p, iObj ); + if ( Gia_ObjOriginsIsOverflow(e) && e->ovf.overflow ) + ABC_FREE( e->ovf.overflow ); + e->inl.origins[0] = iOrig; + for ( k = 1; k < GIA_ORIGINS_INLINE; k++ ) + e->inl.origins[k] = -1; +} +// Grow vOrigins to accommodate object iObj +static inline void Gia_ManOriginsGrow( Gia_Man_t * p, int iObj ) +{ + Vec_IntFillExtra( p->vOrigins, (iObj + 1) * GIA_ORIGINS_STRIDE, -1 ); +} +// Allocate vOrigins for nObjs objects (all entries initialized to -1) +static inline Vec_Int_t * Gia_ManOriginsAlloc( int nObjs ) +{ + return Vec_IntStartFull( nObjs * GIA_ORIGINS_STRIDE ); +} +// Iteration macro (caller must declare int _nOrig before use, or use nOrig variant) +#define Gia_ObjForEachOrigin( p, iObj, orig, idx ) \ + for ( idx = 0, _nOrig = Gia_ObjOriginsNum(p, iObj); \ + (idx < _nOrig) && ((orig = Gia_ObjOriginsGet(p, iObj, idx)), 1); idx++ ) static inline Gia_Obj_t * Gia_Regular( Gia_Obj_t * p ) { return (Gia_Obj_t *)((ABC_PTRUINT_T)(p) & ~01); } static inline Gia_Obj_t * Gia_Not( Gia_Obj_t * p ) { return (Gia_Obj_t *)((ABC_PTRUINT_T)(p) ^ 01); } @@ -1352,9 +1451,14 @@ extern Gia_Man_t * Gia_ManDupOrderDfsReverse( Gia_Man_t * p, int fRevFan extern Gia_Man_t * Gia_ManDupOutputGroup( Gia_Man_t * p, int iOutStart, int iOutStop ); extern Gia_Man_t * Gia_ManDupOutputVec( Gia_Man_t * p, Vec_Int_t * vOutPres ); extern Gia_Man_t * Gia_ManDupSelectedOutputs( Gia_Man_t * p, Vec_Int_t * vOutsLeft ); +extern void Gia_ObjAddOrigin( Gia_Man_t * p, int iObj, int iOrig ); +extern void Gia_ObjUnionOrigins( Gia_Man_t * p, int iDst, Gia_Man_t * pSrc, int iSrc ); +extern void Gia_ManOriginsFreeOverflows( Gia_Man_t * p ); +extern void Gia_ManOriginsReset( Gia_Man_t * p ); extern void Gia_ManOriginsDup( Gia_Man_t * pNew, Gia_Man_t * pOld ); extern void Gia_ManOriginsDupVec( Gia_Man_t * pNew, Gia_Man_t * pOld, Vec_Int_t * vCopies ); extern void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ); +extern void Gia_ManOriginsDupIf( Gia_Man_t * pNew, Gia_Man_t * p, void * pIfMan ); extern Gia_Man_t * Gia_ManDupOrderAiger( Gia_Man_t * p ); extern Gia_Man_t * Gia_ManDupLastPis( Gia_Man_t * p, int nLastPis ); extern Gia_Man_t * Gia_ManDupFlip( Gia_Man_t * p, int * pInitState ); diff --git a/src/aig/gia/giaAiger.c b/src/aig/gia/giaAiger.c index 6901f23dd8..d3050beeca 100644 --- a/src/aig/gia/giaAiger.c +++ b/src/aig/gia/giaAiger.c @@ -950,17 +950,40 @@ Gia_Man_t * Gia_AigerReadFromMemory( char * pContents, int nFileSize, int fGiaSi if ( fVerbose ) printf( "Skipped extension \"y\" for vEquLitIds (AIG is rehashed).\n" ); } // populate vOrigins using vNodes to map AIG→GIA object indices - if ( nInts == Vec_IntSize(vNodes) ) { + // sentinel -2 at pData[0] distinguishes new format from old + if ( vNodes && nInts >= 1 && ((int *)pCur)[0] == -2 ) { + // new multi-origin format: sentinel, then [count, lit0, lit1, ...] per AIG object + int k, nAigObjs = Vec_IntSize(vNodes); + int * pData = (int *)pCur; + int pos = 1; // skip sentinel + pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) ); + for ( k = 0; k < nAigObjs && pos < nInts; k++ ) + { + int giaLit = Vec_IntEntry( vNodes, k ); + int giaObj = Abc_Lit2Var( giaLit ); + int nOrig = pData[pos++]; + int j; + for ( j = 0; j < nOrig && pos < nInts; j++, pos++ ) + { + int rawLit = pData[pos]; + if ( rawLit >= 0 && giaObj < Gia_ManObjNum(pNew) ) + Gia_ObjAddOrigin( pNew, giaObj, Abc_Lit2Var(rawLit) ); + } + } + if ( fVerbose ) printf( "Finished reading extension \"y\" (multi-origin).\n" ); + } + else if ( vNodes && nInts == Vec_IntSize(vNodes) ) { + // old single-origin format: one literal per AIG object int k; int * pData = (int *)pCur; - pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) ); for ( k = 0; k < nInts; k++ ) { int giaLit = Vec_IntEntry( vNodes, k ); int giaObj = Abc_Lit2Var( giaLit ); int rawLit = pData[k]; if ( rawLit >= 0 && giaObj < Gia_ManObjNum(pNew) ) - Vec_IntWriteEntry( pNew->vOrigins, giaObj, Abc_Lit2Var(rawLit) ); + Gia_ObjSetOrigin( pNew, giaObj, Abc_Lit2Var(rawLit) ); } } pCur += 4*nInts; @@ -1851,21 +1874,27 @@ void Gia_AigerWriteS( Gia_Man_t * pInit, char * pFileName, int fWriteSymbols, in fwrite( Vec_IntArray(p->vObjClasses), 1, 4*Gia_ManObjNum(p), pFile ); } // write object origins (vOrigins takes priority over vEquLitIds) + // New variable-length format: sentinel -2, then for each object [count, lit0, lit1, ...] if ( p->vOrigins ) { int k, nObjs = Gia_ManObjNum(p); - Vec_Int_t * vLits = Vec_IntStart( nObjs ); - assert( Vec_IntSize(p->vOrigins) == nObjs ); + Vec_Int_t * vData = Vec_IntAlloc( 1 + nObjs * 2 ); + assert( Vec_IntSize(p->vOrigins) >= nObjs * GIA_ORIGINS_STRIDE ); + Vec_IntPush( vData, -2 ); // sentinel distinguishes new format from old for ( k = 0; k < nObjs; k++ ) { - int orig = Vec_IntEntry(p->vOrigins, k); - Vec_IntWriteEntry( vLits, k, orig >= 0 ? 2 * orig : -1 ); + int nOrig = Gia_ObjOriginsNum( p, k ); + int idx, orig, _nOrig; + Vec_IntPush( vData, nOrig ); + Gia_ObjForEachOrigin( p, k, orig, idx ) + Vec_IntPush( vData, orig >= 0 ? 2 * orig : -1 ); } fprintf( pFile, "y" ); - Gia_FileWriteBufferSize( pFile, 4*nObjs ); - fwrite( Vec_IntArray(vLits), 1, (size_t)4*nObjs, pFile ); - Vec_IntFree( vLits ); - if ( fVerbose ) printf( "Finished writing extension \"y\" (from origins).\n" ); + Gia_FileWriteBufferSize( pFile, 4*Vec_IntSize(vData) ); + fwrite( Vec_IntArray(vData), 1, (size_t)4*Vec_IntSize(vData), pFile ); + if ( fVerbose ) printf( "Finished writing extension \"y\" (multi-origin, %d ints for %d objs).\n", + Vec_IntSize(vData), nObjs ); + Vec_IntFree( vData ); } else if ( p->vEquLitIds ) { diff --git a/src/aig/gia/giaDup.c b/src/aig/gia/giaDup.c index 68455c35fe..f67e53307c 100644 --- a/src/aig/gia/giaDup.c +++ b/src/aig/gia/giaDup.c @@ -19,6 +19,7 @@ ***********************************************************************/ #include "gia.h" +#include "map/if/if.h" #include "misc/tim/tim.h" #include "misc/vec/vecWec.h" #include "proof/cec/cec.h" @@ -193,11 +194,131 @@ int Gia_ManDupOrderDfs_rec( Gia_Man_t * pNew, Gia_Man_t * p, Gia_Obj_t * pObj ) return pObj->Value = Gia_ManAppendAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); } +/**Function************************************************************* + + Synopsis [Adds an origin to an object with deduplication.] + + Description [May promote inline storage to heap-allocated overflow.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +void Gia_ObjAddOrigin( Gia_Man_t * p, int iObj, int iOrig ) +{ + Gia_OriginsEntry_t * e; + int k; + if ( iOrig < 0 || !p->vOrigins || iObj * GIA_ORIGINS_STRIDE >= Vec_IntSize(p->vOrigins) ) + return; + e = Gia_ObjOriginsEntry( p, iObj ); + if ( Gia_ObjOriginsIsOverflow(e) ) + { + // overflow mode: check for duplicate + for ( k = 0; k < e->ovf.count; k++ ) + if ( e->ovf.overflow[k] == iOrig ) return; + // geometric growth: double capacity when count is a power of 2 + if ( e->ovf.count >= 8 && (e->ovf.count & (e->ovf.count - 1)) == 0 ) + e->ovf.overflow = ABC_REALLOC( int, e->ovf.overflow, e->ovf.count * 2 ); + e->ovf.overflow[e->ovf.count++] = iOrig; + } + else + { + // inline mode: check for duplicate and find empty slot + for ( k = 0; k < GIA_ORIGINS_INLINE; k++ ) + { + if ( e->inl.origins[k] == iOrig ) return; + if ( e->inl.origins[k] == -1 ) + { + e->inl.origins[k] = iOrig; + return; + } + } + // inline buffer full: promote to overflow with 8-slot initial capacity + { + int * pOverflow = ABC_ALLOC( int, 8 ); + for ( k = 0; k < GIA_ORIGINS_INLINE; k++ ) + pOverflow[k] = e->inl.origins[k]; + pOverflow[GIA_ORIGINS_INLINE] = iOrig; + e->ovf.sentinel = GIA_ORIGINS_SENTINEL; + e->ovf.count = GIA_ORIGINS_INLINE + 1; + e->ovf.overflow = pOverflow; + } + } +} + +/**Function************************************************************* + + Synopsis [Unions all origins from src object into dst object.] + + Description [Iterates over all origins of iSrc in pSrc and adds each + to iDst in p, with deduplication. pSrc and p may be the same manager.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +void Gia_ObjUnionOrigins( Gia_Man_t * p, int iDst, Gia_Man_t * pSrc, int iSrc ) +{ + int idx, orig, _nOrig; + Gia_ObjForEachOrigin( pSrc, iSrc, orig, idx ) + Gia_ObjAddOrigin( p, iDst, orig ); +} + +/**Function************************************************************* + + Synopsis [Frees all overflow arrays in vOrigins.] + + Description [Walks every object's origins entry and frees the heap + array for entries in overflow mode. Must be called before freeing + the vOrigins Vec_Int_t itself.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +void Gia_ManOriginsFreeOverflows( Gia_Man_t * p ) +{ + int i, nObjs; + Gia_OriginsEntry_t * e; + if ( !p->vOrigins ) + return; + nObjs = Vec_IntSize(p->vOrigins) / GIA_ORIGINS_STRIDE; + for ( i = 0; i < nObjs; i++ ) + { + e = Gia_ObjOriginsEntry( p, i ); + if ( Gia_ObjOriginsIsOverflow(e) && e->ovf.overflow ) + ABC_FREE( e->ovf.overflow ); + } +} + +/**Function************************************************************* + + Synopsis [Frees existing vOrigins (overflows + vec).] + + Description [Used before reallocation to prevent leaks.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +void Gia_ManOriginsReset( Gia_Man_t * p ) +{ + if ( !p->vOrigins ) + return; + Gia_ManOriginsFreeOverflows( p ); + Vec_IntFreeP( &p->vOrigins ); +} + /**Function************************************************************* Synopsis [Propagates origin mapping from old to new manager.] - Description [Uses Value field of old objects to find corresponding new objects.] + Description [Uses Value field of old objects to find corresponding + new objects. Copies full multi-origin entries.] SideEffects [] @@ -210,31 +331,28 @@ void Gia_ManOriginsDup( Gia_Man_t * pNew, Gia_Man_t * pOld ) int i; if ( !pOld->vOrigins ) return; - pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + Gia_ManOriginsReset( pNew ); + pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) ); Gia_ManForEachObj( pOld, pObj, i ) { - if ( i >= Vec_IntSize(pOld->vOrigins) ) + if ( i * GIA_ORIGINS_STRIDE >= Vec_IntSize(pOld->vOrigins) ) break; if ( (int)Gia_ObjValue(pObj) != -1 ) { int iNew = Abc_Lit2Var( Gia_ObjValue(pObj) ); if ( iNew < Gia_ManObjNum(pNew) ) - Vec_IntWriteEntry( pNew->vOrigins, iNew, - Vec_IntEntry(pOld->vOrigins, i) ); + Gia_ObjUnionOrigins( pNew, iNew, pOld, i ); } } } /**Function************************************************************* - Synopsis [Restores origins after GIA->AIG->GIA round-trip.] + Synopsis [Propagates origins using a copies vector.] - Description [CIs map 1:1 in order. CO drivers map 1:1 (output - correspondence preserved through optimization). Remaining AND nodes - get origins via top-down propagation from CO drivers through fanin - cones. Note: shared nodes between multiple CO cones get the origin - of whichever CO driver is visited first (non-deterministic but - acceptable for best-effort source attribution).] + Description [Like Gia_ManOriginsDup but uses an explicit vCopies + vector instead of the Value field. Entry i of vCopies is the literal + in pNew corresponding to old object i.] SideEffects [] @@ -246,19 +364,33 @@ void Gia_ManOriginsDupVec( Gia_Man_t * pNew, Gia_Man_t * pOld, Vec_Int_t * vCopi int i, iLit; if ( !pOld->vOrigins ) return; - pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + Gia_ManOriginsReset( pNew ); + pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) ); Vec_IntForEachEntry( vCopies, iLit, i ) { if ( iLit != -1 ) { int iNew = Abc_Lit2Var( iLit ); - if ( iNew < Gia_ManObjNum(pNew) && i < Vec_IntSize(pOld->vOrigins) ) - Vec_IntWriteEntry( pNew->vOrigins, iNew, - Vec_IntEntry(pOld->vOrigins, i) ); + if ( iNew < Gia_ManObjNum(pNew) && i * GIA_ORIGINS_STRIDE < Vec_IntSize(pOld->vOrigins) ) + Gia_ObjUnionOrigins( pNew, iNew, pOld, i ); } } } +/**Function************************************************************* + + Synopsis [Restores origins after GIA->AIG->GIA round-trip.] + + Description [CIs map 1:1 in order. CO drivers map 1:1 (output + correspondence preserved through optimization). Remaining AND nodes + get origins via top-down propagation from CO drivers through fanin + cones.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ) { Gia_Obj_t * pObj; @@ -267,18 +399,16 @@ void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ) return; assert( Gia_ManCiNum(pNew) == Gia_ManCiNum(pOld) ); assert( Gia_ManCoNum(pNew) == Gia_ManCoNum(pOld) ); - pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + Gia_ManOriginsReset( pNew ); + pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) ); // const0 - if ( Vec_IntSize(pOld->vOrigins) > 0 ) - Vec_IntWriteEntry( pNew->vOrigins, 0, Vec_IntEntry(pOld->vOrigins, 0) ); + Gia_ObjUnionOrigins( pNew, 0, pOld, 0 ); // CIs map 1:1 in order Gia_ManForEachCi( pNew, pObj, i ) { int iNewObj = Gia_ObjId( pNew, pObj ); int iOldCi = Gia_ObjId( pOld, Gia_ManCi(pOld, i) ); - if ( iOldCi < Vec_IntSize(pOld->vOrigins) ) - Vec_IntWriteEntry( pNew->vOrigins, iNewObj, - Vec_IntEntry(pOld->vOrigins, iOldCi) ); + Gia_ObjUnionOrigins( pNew, iNewObj, pOld, iOldCi ); } // CO drivers map 1:1 (output correspondence preserved through optimization) Gia_ManForEachCo( pNew, pObj, i ) @@ -286,28 +416,56 @@ void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ) int iNewDriver = Gia_ObjFaninId0p( pNew, pObj ); Gia_Obj_t * pOldCo = Gia_ManCo( pOld, i ); int iOldDriver = Gia_ObjFaninId0p( pOld, pOldCo ); - if ( iNewDriver > 0 && iOldDriver < Vec_IntSize(pOld->vOrigins) && - Vec_IntEntry(pNew->vOrigins, iNewDriver) == -1 ) - Vec_IntWriteEntry( pNew->vOrigins, iNewDriver, - Vec_IntEntry(pOld->vOrigins, iOldDriver) ); + if ( iNewDriver > 0 ) + Gia_ObjUnionOrigins( pNew, iNewDriver, pOld, iOldDriver ); } // Top-down propagation: spread CO driver origins backward through fanin cones - // Walk AND nodes in reverse topological order (high to low ID) for ( i = Gia_ManObjNum(pNew) - 1; i > 0; i-- ) { - int f0, f1, orig; + int f0, f1; pObj = Gia_ManObj( pNew, i ); if ( !Gia_ObjIsAnd(pObj) ) continue; - orig = Vec_IntEntry( pNew->vOrigins, i ); - if ( orig < 0 ) + if ( Gia_ObjOriginsNum(pNew, i) == 0 ) continue; f0 = Gia_ObjFaninId0(pObj, i); f1 = Gia_ObjFaninId1(pObj, i); - if ( f0 > 0 && Vec_IntEntry(pNew->vOrigins, f0) == -1 ) - Vec_IntWriteEntry( pNew->vOrigins, f0, orig ); - if ( f1 > 0 && Vec_IntEntry(pNew->vOrigins, f1) == -1 ) - Vec_IntWriteEntry( pNew->vOrigins, f1, orig ); + if ( f0 > 0 ) + Gia_ObjUnionOrigins( pNew, f0, pNew, i ); + if ( f1 > 0 ) + Gia_ObjUnionOrigins( pNew, f1, pNew, i ); + } +} + +/**Function************************************************************* + + Synopsis [Propagates origins through IF mapper correspondence.] + + Description [IF objects 0..Gia_ManObjNum(p)-1 correspond 1:1 to + input GIA objects; iCopy gives the literal in the output GIA.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +void Gia_ManOriginsDupIf( Gia_Man_t * pNew, Gia_Man_t * p, void * pIfManVoid ) +{ + If_Man_t * pIfMan = (If_Man_t *)pIfManVoid; + If_Obj_t * pIfObj = NULL; + int i; + if ( !p->vOrigins ) + return; + Gia_ManOriginsReset( pNew ); + pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) ); + If_ManForEachObj( pIfMan, pIfObj, i ) + { + if ( i * GIA_ORIGINS_STRIDE < Vec_IntSize(p->vOrigins) && pIfObj->iCopy >= 0 ) + { + int iNewObj = Abc_Lit2Var( pIfObj->iCopy ); + if ( iNewObj < Gia_ManObjNum(pNew) ) + Gia_ObjUnionOrigins( pNew, iNewObj, p, i ); + } } } @@ -950,9 +1108,25 @@ Gia_Man_t * Gia_ManDupWithAttributes( Gia_Man_t * p ) pNew->vConfigs2 = Vec_StrDup( p->vConfigs2 ); if ( p->pCellStr ) pNew->pCellStr = Abc_UtilStrsav( p->pCellStr ); - // copy origins if present + // copy origins if present (deep-copy overflow arrays) if ( p->vOrigins ) + { + int iObj, nObjs; + Gia_OriginsEntry_t * eSrc, * eDst; + Gia_ManOriginsReset( pNew ); pNew->vOrigins = Vec_IntDup( p->vOrigins ); + nObjs = Vec_IntSize(p->vOrigins) / GIA_ORIGINS_STRIDE; + for ( iObj = 0; iObj < nObjs; iObj++ ) + { + eSrc = Gia_ObjOriginsEntry( p, iObj ); + if ( Gia_ObjOriginsIsOverflow(eSrc) && eSrc->ovf.overflow ) + { + eDst = Gia_ObjOriginsEntry( pNew, iObj ); + eDst->ovf.overflow = ABC_ALLOC( int, eSrc->ovf.count ); + memcpy( eDst->ovf.overflow, eSrc->ovf.overflow, sizeof(int) * eSrc->ovf.count ); + } + } + } // copy names if present if ( p->vNamesIn ) pNew->vNamesIn = Vec_PtrDupStr( p->vNamesIn ); diff --git a/src/aig/gia/giaHash.c b/src/aig/gia/giaHash.c index 743e94754e..fc581149a1 100644 --- a/src/aig/gia/giaHash.c +++ b/src/aig/gia/giaHash.c @@ -491,6 +491,12 @@ int Gia_ManHashXorReal( Gia_Man_t * p, int iLit0, int iLit1 ) if ( *pPlace ) { p->nHashHit++; + // on hash hit, union input origins into existing node + if ( p->vOrigins && *pPlace * GIA_ORIGINS_STRIDE < Vec_IntSize(p->vOrigins) ) + { + Gia_ObjUnionOrigins( p, *pPlace, p, Abc_Lit2Var(iLit0) ); + Gia_ObjUnionOrigins( p, *pPlace, p, Abc_Lit2Var(iLit1) ); + } return Abc_Var2Lit( *pPlace, fCompl ); } p->nHashMiss++; @@ -503,16 +509,13 @@ int Gia_ManHashXorReal( Gia_Man_t * p, int iLit0, int iLit1 ) assert( *pPlace == 0 ); *pPlace = Abc_Lit2Var( iNode ); } - // propagate origin from parent with lower valid origin ID + // propagate origins from both inputs into new node if ( p->vOrigins ) { int iNew = *pPlace; - int o0 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit0)); - int o1 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit1)); - int orig = (o0 >= 0 && (o1 < 0 || o0 <= o1)) ? o0 : o1; - while ( Vec_IntSize(p->vOrigins) <= iNew ) - Vec_IntPush( p->vOrigins, -1 ); - Vec_IntWriteEntry( p->vOrigins, iNew, orig ); + Gia_ManOriginsGrow( p, iNew ); + Gia_ObjUnionOrigins( p, iNew, p, Abc_Lit2Var(iLit0) ); + Gia_ObjUnionOrigins( p, iNew, p, Abc_Lit2Var(iLit1) ); } return Abc_Var2Lit( *pPlace, fCompl ); } @@ -557,6 +560,13 @@ int Gia_ManHashMuxReal( Gia_Man_t * p, int iLitC, int iLit1, int iLit0 ) if ( *pPlace ) { p->nHashHit++; + // on hash hit, union input origins into existing node + if ( p->vOrigins && *pPlace * GIA_ORIGINS_STRIDE < Vec_IntSize(p->vOrigins) ) + { + Gia_ObjUnionOrigins( p, *pPlace, p, Abc_Lit2Var(iLit0) ); + Gia_ObjUnionOrigins( p, *pPlace, p, Abc_Lit2Var(iLit1) ); + Gia_ObjUnionOrigins( p, *pPlace, p, Abc_Lit2Var(iLitC) ); + } return Abc_Var2Lit( *pPlace, fCompl ); } p->nHashMiss++; @@ -569,18 +579,14 @@ int Gia_ManHashMuxReal( Gia_Man_t * p, int iLitC, int iLit1, int iLit0 ) assert( *pPlace == 0 ); *pPlace = Abc_Lit2Var( iNode ); } - // propagate origin from parent with lower valid origin ID + // propagate origins from all three inputs into new node if ( p->vOrigins ) { int iNew = *pPlace; - int o0 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit0)); - int o1 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit1)); - int oC = Gia_ObjOrigin(p, Abc_Lit2Var(iLitC)); - int orig = (o0 >= 0 && (o1 < 0 || o0 <= o1)) ? o0 : o1; - if ( oC >= 0 && (orig < 0 || oC <= orig) ) orig = oC; - while ( Vec_IntSize(p->vOrigins) <= iNew ) - Vec_IntPush( p->vOrigins, -1 ); - Vec_IntWriteEntry( p->vOrigins, iNew, orig ); + Gia_ManOriginsGrow( p, iNew ); + Gia_ObjUnionOrigins( p, iNew, p, Abc_Lit2Var(iLit0) ); + Gia_ObjUnionOrigins( p, iNew, p, Abc_Lit2Var(iLit1) ); + Gia_ObjUnionOrigins( p, iNew, p, Abc_Lit2Var(iLitC) ); } return Abc_Var2Lit( *pPlace, fCompl ); } @@ -627,6 +633,12 @@ int Gia_ManHashAnd( Gia_Man_t * p, int iLit0, int iLit1 ) if ( *pPlace ) { p->nHashHit++; + // on hash hit, union input origins into existing node + if ( p->vOrigins && *pPlace * GIA_ORIGINS_STRIDE < Vec_IntSize(p->vOrigins) ) + { + Gia_ObjUnionOrigins( p, *pPlace, p, Abc_Lit2Var(iLit0) ); + Gia_ObjUnionOrigins( p, *pPlace, p, Abc_Lit2Var(iLit1) ); + } return Abc_Var2Lit( *pPlace, 0 ); } p->nHashMiss++; @@ -639,17 +651,13 @@ int Gia_ManHashAnd( Gia_Man_t * p, int iLit0, int iLit1 ) assert( *pPlace == 0 ); *pPlace = Abc_Lit2Var( iNode ); } - // propagate origin from parent with lower valid origin ID + // propagate origins from both inputs into new node if ( p->vOrigins ) { int iNew = *pPlace; - int o0 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit0)); - int o1 = Gia_ObjOrigin(p, Abc_Lit2Var(iLit1)); - int orig = (o0 >= 0 && (o1 < 0 || o0 <= o1)) ? o0 : o1; - // grow vOrigins if needed - while ( Vec_IntSize(p->vOrigins) <= iNew ) - Vec_IntPush( p->vOrigins, -1 ); - Vec_IntWriteEntry( p->vOrigins, iNew, orig ); + Gia_ManOriginsGrow( p, iNew ); + Gia_ObjUnionOrigins( p, iNew, p, Abc_Lit2Var(iLit0) ); + Gia_ObjUnionOrigins( p, iNew, p, Abc_Lit2Var(iLit1) ); } return Abc_Var2Lit( *pPlace, 0 ); } diff --git a/src/aig/gia/giaIf.c b/src/aig/gia/giaIf.c index f2fdc55df1..77ceeb25ee 100644 --- a/src/aig/gia/giaIf.c +++ b/src/aig/gia/giaIf.c @@ -3066,23 +3066,7 @@ Gia_Man_t * Gia_ManPerformMappingInt( Gia_Man_t * p, If_Par_t * pPars ) else pNew = Gia_ManFromIfLogic( pIfMan ); // propagate origin mapping from input GIA through IF mapper to output GIA - // IF objects 0..Gia_ManObjNum(p)-1 correspond 1:1 to input GIA objects; - // iCopy gives the literal in the output GIA (set by Gia_ManFromIfLogic/Aig) - if ( p->vOrigins ) - { - If_Obj_t * pIfObj = NULL; - pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); - If_ManForEachObj( pIfMan, pIfObj, i ) - { - if ( i < Vec_IntSize(p->vOrigins) && pIfObj->iCopy >= 0 ) - { - int iNewObj = Abc_Lit2Var( pIfObj->iCopy ); - if ( iNewObj < Gia_ManObjNum(pNew) ) - Vec_IntWriteEntry( pNew->vOrigins, iNewObj, - Vec_IntEntry(p->vOrigins, i) ); - } - } - } + Gia_ManOriginsDupIf( pNew, p, pIfMan ); if ( p->vCiArrs || p->vCoReqs ) { If_Obj_t * pIfObj = NULL; @@ -3178,23 +3162,7 @@ Gia_Man_t * Gia_ManPerformSopBalance( Gia_Man_t * p, int nCutNum, int nRelaxRati pIfMan = Gia_ManToIf( p, pPars ); If_ManPerformMapping( pIfMan ); pNew = Gia_ManFromIfAig( pIfMan ); - // propagate origins via IF mapper iCopy correspondence - if ( p->vOrigins ) - { - If_Obj_t * pIfObj = NULL; - int j; - pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); - If_ManForEachObj( pIfMan, pIfObj, j ) - { - if ( j < Vec_IntSize(p->vOrigins) && pIfObj->iCopy >= 0 ) - { - int iNewObj = Abc_Lit2Var( pIfObj->iCopy ); - if ( iNewObj < Gia_ManObjNum(pNew) ) - Vec_IntWriteEntry( pNew->vOrigins, iNewObj, - Vec_IntEntry(p->vOrigins, j) ); - } - } - } + Gia_ManOriginsDupIf( pNew, p, pIfMan ); If_ManStop( pIfMan ); Gia_ManTransferTiming( pNew, p ); // transfer name @@ -3228,23 +3196,7 @@ Gia_Man_t * Gia_ManPerformDsdBalance( Gia_Man_t * p, int nLutSize, int nCutNum, If_DsdManAllocIsops( pIfMan->pIfDsdMan, pPars->nLutSize ); If_ManPerformMapping( pIfMan ); pNew = Gia_ManFromIfAig( pIfMan ); - // propagate origins via IF mapper iCopy correspondence - if ( p->vOrigins ) - { - If_Obj_t * pIfObj = NULL; - int j; - pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); - If_ManForEachObj( pIfMan, pIfObj, j ) - { - if ( j < Vec_IntSize(p->vOrigins) && pIfObj->iCopy >= 0 ) - { - int iNewObj = Abc_Lit2Var( pIfObj->iCopy ); - if ( iNewObj < Gia_ManObjNum(pNew) ) - Vec_IntWriteEntry( pNew->vOrigins, iNewObj, - Vec_IntEntry(p->vOrigins, j) ); - } - } - } + Gia_ManOriginsDupIf( pNew, p, pIfMan ); If_ManStop( pIfMan ); Gia_ManTransferTiming( pNew, p ); // transfer name diff --git a/src/aig/gia/giaMan.c b/src/aig/gia/giaMan.c index 35646e341c..6ee993fa02 100644 --- a/src/aig/gia/giaMan.c +++ b/src/aig/gia/giaMan.c @@ -107,6 +107,7 @@ void Gia_ManStop( Gia_Man_t * p ) Vec_IntFreeP( &p->vIdsOrig ); Vec_IntFreeP( &p->vIdsEquiv ); Vec_IntFreeP( &p->vEquLitIds ); + Gia_ManOriginsFreeOverflows( p ); Vec_IntFreeP( &p->vOrigins ); Vec_IntFreeP( &p->vLutConfigs ); Vec_IntFreeP( &p->vEdgeDelay ); diff --git a/src/aig/gia/giaMfs.c b/src/aig/gia/giaMfs.c index eaee7ca63d..5330eac918 100644 --- a/src/aig/gia/giaMfs.c +++ b/src/aig/gia/giaMfs.c @@ -535,7 +535,7 @@ Gia_Man_t * Gia_ManInsertMfs( Gia_Man_t * p, Sfm_Ntk_t * pNtk, int fAllBoxes ) if ( p->vOrigins ) { int iOldObj; - pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) ); + pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) ); Vec_IntForEachEntry( vMfs2Old, iOldObj, i ) { if ( iOldObj >= 0 ) @@ -544,9 +544,8 @@ Gia_Man_t * Gia_ManInsertMfs( Gia_Man_t * p, Sfm_Ntk_t * pNtk, int fAllBoxes ) if ( iNewLit >= 0 ) { int iNewObj = Abc_Lit2Var( iNewLit ); - if ( iNewObj < Gia_ManObjNum(pNew) && iOldObj < Vec_IntSize(p->vOrigins) ) - Vec_IntWriteEntry( pNew->vOrigins, iNewObj, - Vec_IntEntry(p->vOrigins, iOldObj) ); + if ( iNewObj < Gia_ManObjNum(pNew) && iOldObj * GIA_ORIGINS_STRIDE < Vec_IntSize(p->vOrigins) ) + Gia_ObjUnionOrigins( pNew, iNewObj, p, iOldObj ); } } } From f2af506deeb1b5c9a86bdbc6a51d0d2a506b9481 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Tue, 17 Mar 2026 03:41:55 +0000 Subject: [PATCH 4/8] Add &origins and &origins_id commands for origin tracking - &origins: prints multi-origin statistics (entry count, total origins, average/max per node, overflow count, histogram). Origins are populated either from XAIGER "y" extension (normal abc9 flow) or by &origins_id (testing). - &origins_id: sets identity origins (each AND node -> itself) for testing origin propagation in standalone ABC sessions. Not needed in the normal abc9 flow where origins come from Yosys via XAIGER. Co-developed-by: Claude Code v2.1.58 (claude-opus-4-6) --- src/base/abci/abc.c | 143 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/src/base/abci/abc.c b/src/base/abci/abc.c index 6cc8df18fc..9be42cf00a 100644 --- a/src/base/abci/abc.c +++ b/src/base/abci/abc.c @@ -426,6 +426,8 @@ static int Abc_CommandAbc9WriteVer ( Abc_Frame_t * pAbc, int argc, cha static int Abc_CommandAbc9Write ( Abc_Frame_t * pAbc, int argc, char ** argv ); static int Abc_CommandAbc9WriteLut ( Abc_Frame_t * pAbc, int argc, char ** argv ); static int Abc_CommandAbc9Ps ( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Abc_CommandAbc9Origins ( Abc_Frame_t * pAbc, int argc, char ** argv ); +static int Abc_CommandAbc9OriginsId ( Abc_Frame_t * pAbc, int argc, char ** argv ); static int Abc_CommandAbc9PFan ( Abc_Frame_t * pAbc, int argc, char ** argv ); static int Abc_CommandAbc9Pms ( Abc_Frame_t * pAbc, int argc, char ** argv ); static int Abc_CommandAbc9PSig ( Abc_Frame_t * pAbc, int argc, char ** argv ); @@ -1269,6 +1271,8 @@ void Abc_Init( Abc_Frame_t * pAbc ) Cmd_CommandAdd( pAbc, "ABC9", "&write", Abc_CommandAbc9Write, 0 ); Cmd_CommandAdd( pAbc, "ABC9", "&wlut", Abc_CommandAbc9WriteLut, 0 ); Cmd_CommandAdd( pAbc, "ABC9", "&ps", Abc_CommandAbc9Ps, 0 ); + Cmd_CommandAdd( pAbc, "ABC9", "&origins", Abc_CommandAbc9Origins, 0 ); + Cmd_CommandAdd( pAbc, "ABC9", "&origins_id", Abc_CommandAbc9OriginsId, 0 ); Cmd_CommandAdd( pAbc, "ABC9", "&pfan", Abc_CommandAbc9PFan, 0 ); Cmd_CommandAdd( pAbc, "ABC9", "&pms", Abc_CommandAbc9Pms, 0 ); Cmd_CommandAdd( pAbc, "ABC9", "&psig", Abc_CommandAbc9PSig, 0 ); @@ -35540,6 +35544,145 @@ int Abc_CommandAbc9Ps( Abc_Frame_t * pAbc, int argc, char ** argv ) return 1; } +/**Function************************************************************* + + Synopsis [Prints multi-origin statistics.] + + Description [Shows how many objects have origins, total origin count, + average/max per node, overflow count, and a histogram. Origins are + populated either by reading an XAIGER file with a "y" extension + (normal abc9 flow) or by the &origins_id command (testing).] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +int Abc_CommandAbc9Origins( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + Gia_Man_t * pGia = pAbc->pGia; + int c; + Extra_UtilGetoptReset(); + while ( ( c = Extra_UtilGetopt( argc, argv, "h" ) ) != EOF ) + { + switch ( c ) + { + case 'h': + goto usage; + default: + goto usage; + } + } + if ( pGia == NULL ) + { + Abc_Print( -1, "Abc_CommandAbc9Origins(): There is no AIG.\n" ); + return 0; + } + if ( pGia->vOrigins == NULL ) + { + Abc_Print( 1, "No origin tracking data.\n" ); + return 0; + } + { + int i, nObjs = Gia_ManObjNum(pGia); + int nEntries = 0, nOrigins = 0, nMaxOrigins = 0; + int nOverflow = 0; + int histogram[16]; + memset( histogram, 0, sizeof(histogram) ); + for ( i = 0; i < nObjs; i++ ) + { + int nOrig = Gia_ObjOriginsNum( pGia, i ); + if ( nOrig > 0 ) + { + nEntries++; + nOrigins += nOrig; + if ( nOrig > nMaxOrigins ) + nMaxOrigins = nOrig; + if ( nOrig < 16 ) + histogram[nOrig]++; + else + histogram[15]++; + if ( nOrig > GIA_ORIGINS_INLINE ) + nOverflow++; + } + } + Abc_Print( 1, "Origins: %d entries, %d total origins (%.2fx avg), max %d, overflow %d\n", + nEntries, nOrigins, + nEntries > 0 ? (double)nOrigins / nEntries : 0.0, + nMaxOrigins, nOverflow ); + Abc_Print( 1, " Histogram: " ); + for ( i = 1; i < 16; i++ ) + if ( histogram[i] > 0 ) + Abc_Print( 1, "%s%d=%d", i > 1 ? " " : "", i, histogram[i] ); + Abc_Print( 1, "\n" ); + } + return 0; + +usage: + Abc_Print( -2, "usage: &origins [-h]\n" ); + Abc_Print( -2, "\t prints multi-origin tracking statistics\n" ); + Abc_Print( -2, "\t-h : print the command usage\n"); + return 1; +} + +/**Function************************************************************* + + Synopsis [Initialize identity origins for testing/debugging.] + + Description [Sets each AND node's origin to itself. This is a testing + convenience for exercising origin propagation in standalone ABC sessions. + In the normal abc9 flow, origins are supplied by Yosys via the XAIGER + "y" extension and this command is not needed.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +int Abc_CommandAbc9OriginsId( Abc_Frame_t * pAbc, int argc, char ** argv ) +{ + Gia_Man_t * pGia = pAbc->pGia; + int c; + Extra_UtilGetoptReset(); + while ( ( c = Extra_UtilGetopt( argc, argv, "h" ) ) != EOF ) + { + switch ( c ) + { + case 'h': + goto usage; + default: + goto usage; + } + } + if ( pGia == NULL ) + { + Abc_Print( -1, "Abc_CommandAbc9OriginsId(): There is no AIG.\n" ); + return 0; + } + if ( pGia->vOrigins != NULL ) + { + Abc_Print( 1, "Origins already present (%d entries). Use without existing origins.\n", + Vec_IntSize(pGia->vOrigins) / GIA_ORIGINS_STRIDE ); + return 0; + } + { + int i, nObjs = Gia_ManObjNum(pGia); + Gia_Obj_t * pObj; + pGia->vOrigins = Gia_ManOriginsAlloc( nObjs ); + Gia_ManForEachAnd( pGia, pObj, i ) + Gia_ObjSetOrigin( pGia, i, i ); + Abc_Print( 1, "Initialized identity origins for %d AND nodes.\n", Gia_ManAndNum(pGia) ); + } + return 0; + +usage: + Abc_Print( -2, "usage: &origins_id [-h]\n" ); + Abc_Print( -2, "\t sets identity origins for testing (each AND node -> itself)\n" ); + Abc_Print( -2, "\t in normal abc9 flow, origins come from XAIGER \"y\" extension\n" ); + Abc_Print( -2, "\t-h : print the command usage\n"); + return 1; +} + /**Function************************************************************* Synopsis [] From 4f035832f3b3c2bb1263d3dc321614cd24a09e10 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Wed, 1 Apr 2026 16:02:42 +0100 Subject: [PATCH 5/8] Add per-node origin cap (nOriginsMax) to bound accumulation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shared fanin cones can accumulate origins from all CO drivers, causing O(n²) dedup cost in Gia_ObjAddOrigin. Add nOriginsMax field to Gia_Man_t that short-circuits overflow-mode AddOrigin when the node has reached the limit. Cap is set via &origins_id -M. Pathological case (10K outputs, shared 50-deep cone): No cap: 5.70s, 1.01M total origins, max 10000/node -M 256: 0.79s, 45K total origins, max 256/node (7.2x faster) Normal designs (i10.aig, max 165/node): identical results. Co-developed-by: Claude Code v2.1.89 (claude-opus-4-6) --- src/aig/gia/gia.h | 1 + src/aig/gia/giaDup.c | 8 +++++++ src/base/abci/abc.c | 55 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/aig/gia/gia.h b/src/aig/gia/gia.h index 1fc26c856d..fe8ce9dea9 100644 --- a/src/aig/gia/gia.h +++ b/src/aig/gia/gia.h @@ -221,6 +221,7 @@ struct Gia_Man_t_ Vec_Int_t * vIdsEquiv; // original object IDs proved equivalent Vec_Int_t * vEquLitIds; // original object IDs proved equivalent Vec_Int_t * vOrigins; // per-object origin mapping (from "y" extension) + int nOriginsMax; // max origins per object (0 = unlimited) Vec_Int_t * vCofVars; // cofactoring variables Vec_Vec_t * vClockDoms; // clock domains Vec_Flt_t * vTiming; // arrival/required/slack diff --git a/src/aig/gia/giaDup.c b/src/aig/gia/giaDup.c index f67e53307c..d6fa79173a 100644 --- a/src/aig/gia/giaDup.c +++ b/src/aig/gia/giaDup.c @@ -214,6 +214,9 @@ void Gia_ObjAddOrigin( Gia_Man_t * p, int iObj, int iOrig ) e = Gia_ObjOriginsEntry( p, iObj ); if ( Gia_ObjOriginsIsOverflow(e) ) { + // cap check: skip if at user-specified limit + if ( p->nOriginsMax > 0 && e->ovf.count >= p->nOriginsMax ) + return; // overflow mode: check for duplicate for ( k = 0; k < e->ovf.count; k++ ) if ( e->ovf.overflow[k] == iOrig ) return; @@ -332,6 +335,7 @@ void Gia_ManOriginsDup( Gia_Man_t * pNew, Gia_Man_t * pOld ) if ( !pOld->vOrigins ) return; Gia_ManOriginsReset( pNew ); + pNew->nOriginsMax = pOld->nOriginsMax; pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) ); Gia_ManForEachObj( pOld, pObj, i ) { @@ -365,6 +369,7 @@ void Gia_ManOriginsDupVec( Gia_Man_t * pNew, Gia_Man_t * pOld, Vec_Int_t * vCopi if ( !pOld->vOrigins ) return; Gia_ManOriginsReset( pNew ); + pNew->nOriginsMax = pOld->nOriginsMax; pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) ); Vec_IntForEachEntry( vCopies, iLit, i ) { @@ -400,6 +405,7 @@ void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ) assert( Gia_ManCiNum(pNew) == Gia_ManCiNum(pOld) ); assert( Gia_ManCoNum(pNew) == Gia_ManCoNum(pOld) ); Gia_ManOriginsReset( pNew ); + pNew->nOriginsMax = pOld->nOriginsMax; pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) ); // const0 Gia_ObjUnionOrigins( pNew, 0, pOld, 0 ); @@ -457,6 +463,7 @@ void Gia_ManOriginsDupIf( Gia_Man_t * pNew, Gia_Man_t * p, void * pIfManVoid ) if ( !p->vOrigins ) return; Gia_ManOriginsReset( pNew ); + pNew->nOriginsMax = p->nOriginsMax; pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) ); If_ManForEachObj( pIfMan, pIfObj, i ) { @@ -1109,6 +1116,7 @@ Gia_Man_t * Gia_ManDupWithAttributes( Gia_Man_t * p ) if ( p->pCellStr ) pNew->pCellStr = Abc_UtilStrsav( p->pCellStr ); // copy origins if present (deep-copy overflow arrays) + pNew->nOriginsMax = p->nOriginsMax; if ( p->vOrigins ) { int iObj, nObjs; diff --git a/src/base/abci/abc.c b/src/base/abci/abc.c index 9be42cf00a..47b464ff98 100644 --- a/src/base/abci/abc.c +++ b/src/base/abci/abc.c @@ -35561,12 +35561,27 @@ int Abc_CommandAbc9Ps( Abc_Frame_t * pAbc, int argc, char ** argv ) int Abc_CommandAbc9Origins( Abc_Frame_t * pAbc, int argc, char ** argv ) { Gia_Man_t * pGia = pAbc->pGia; - int c; + int c, fSetCap = 0, nOriginsMax = 0; Extra_UtilGetoptReset(); - while ( ( c = Extra_UtilGetopt( argc, argv, "h" ) ) != EOF ) + while ( ( c = Extra_UtilGetopt( argc, argv, "Mh" ) ) != EOF ) { switch ( c ) { + case 'M': + if ( globalUtilOptind >= argc ) + { + Abc_Print( -1, "Command line switch \"-M\" should be followed by an integer.\n" ); + goto usage; + } + nOriginsMax = atoi(argv[globalUtilOptind]); + globalUtilOptind++; + if ( nOriginsMax < 0 ) + { + Abc_Print( -1, "The max origins value should be non-negative.\n" ); + goto usage; + } + fSetCap = 1; + break; case 'h': goto usage; default: @@ -35578,6 +35593,12 @@ int Abc_CommandAbc9Origins( Abc_Frame_t * pAbc, int argc, char ** argv ) Abc_Print( -1, "Abc_CommandAbc9Origins(): There is no AIG.\n" ); return 0; } + if ( fSetCap ) + { + pGia->nOriginsMax = nOriginsMax; + Abc_Print( 1, "Origins cap set to %d%s.\n", nOriginsMax, nOriginsMax ? "" : " (unlimited)" ); + return 0; + } if ( pGia->vOrigins == NULL ) { Abc_Print( 1, "No origin tracking data.\n" ); @@ -35619,8 +35640,9 @@ int Abc_CommandAbc9Origins( Abc_Frame_t * pAbc, int argc, char ** argv ) return 0; usage: - Abc_Print( -2, "usage: &origins [-h]\n" ); + Abc_Print( -2, "usage: &origins [-M num] [-h]\n" ); Abc_Print( -2, "\t prints multi-origin tracking statistics\n" ); + Abc_Print( -2, "\t-M num : set max origins per object (0 = unlimited) [default = %d]\n", pGia ? pGia->nOriginsMax : 0 ); Abc_Print( -2, "\t-h : print the command usage\n"); return 1; } @@ -35642,12 +35664,26 @@ int Abc_CommandAbc9Origins( Abc_Frame_t * pAbc, int argc, char ** argv ) int Abc_CommandAbc9OriginsId( Abc_Frame_t * pAbc, int argc, char ** argv ) { Gia_Man_t * pGia = pAbc->pGia; - int c; + int c, nOriginsMax = 0; Extra_UtilGetoptReset(); - while ( ( c = Extra_UtilGetopt( argc, argv, "h" ) ) != EOF ) + while ( ( c = Extra_UtilGetopt( argc, argv, "Mh" ) ) != EOF ) { switch ( c ) { + case 'M': + if ( globalUtilOptind >= argc ) + { + Abc_Print( -1, "Command line switch \"-M\" should be followed by an integer.\n" ); + goto usage; + } + nOriginsMax = atoi(argv[globalUtilOptind]); + globalUtilOptind++; + if ( nOriginsMax < 0 ) + { + Abc_Print( -1, "The max origins value should be non-negative.\n" ); + goto usage; + } + break; case 'h': goto usage; default: @@ -35668,17 +35704,22 @@ int Abc_CommandAbc9OriginsId( Abc_Frame_t * pAbc, int argc, char ** argv ) { int i, nObjs = Gia_ManObjNum(pGia); Gia_Obj_t * pObj; + pGia->nOriginsMax = nOriginsMax; pGia->vOrigins = Gia_ManOriginsAlloc( nObjs ); Gia_ManForEachAnd( pGia, pObj, i ) Gia_ObjSetOrigin( pGia, i, i ); - Abc_Print( 1, "Initialized identity origins for %d AND nodes.\n", Gia_ManAndNum(pGia) ); + Abc_Print( 1, "Initialized identity origins for %d AND nodes", Gia_ManAndNum(pGia) ); + if ( nOriginsMax > 0 ) + Abc_Print( 1, " (max %d per node)", nOriginsMax ); + Abc_Print( 1, ".\n" ); } return 0; usage: - Abc_Print( -2, "usage: &origins_id [-h]\n" ); + Abc_Print( -2, "usage: &origins_id [-M num] [-h]\n" ); Abc_Print( -2, "\t sets identity origins for testing (each AND node -> itself)\n" ); Abc_Print( -2, "\t in normal abc9 flow, origins come from XAIGER \"y\" extension\n" ); + Abc_Print( -2, "\t-M num : max origins per object, 0 = unlimited [default = %d]\n", nOriginsMax ); Abc_Print( -2, "\t-h : print the command usage\n"); return 1; } From 64165326d074ec9d37ef70523d9db854b20a62dd Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Sun, 28 Jun 2026 20:03:18 +0100 Subject: [PATCH 6/8] Add regression tests for origin tracking (vOrigins) PR #487 shipped no tests; this adds a gtest suite (test/gia/origins_test.cc, consistent with the existing gia_test.cc) covering: - core infra: set/get, add-with-dedup, inline->overflow promotion, cross-manager union, and the nOriginsMax cap; - propagation through Gia_ManDup; - the AIGER "y" extension write/read round-trip; - preservation through the &nf standard-cell mapper (driven via read_genlib + &read + &nf using a small bundled genlib fixture). &nf maps in place, so origins survive standard-cell mapping with no dedicated propagation code. All 8 cases pass under ctest. Co-developed-by: Claude Code v2.1.195 (claude-opus-4-8) --- test/gia/CMakeLists.txt | 11 ++ test/gia/origins_test.cc | 222 +++++++++++++++++++++++++++++++++++ test/gia/test_origins.genlib | 8 ++ 3 files changed, 241 insertions(+) create mode 100644 test/gia/origins_test.cc create mode 100644 test/gia/test_origins.genlib diff --git a/test/gia/CMakeLists.txt b/test/gia/CMakeLists.txt index e4dffd475e..9ae8a41ca1 100644 --- a/test/gia/CMakeLists.txt +++ b/test/gia/CMakeLists.txt @@ -7,4 +7,15 @@ target_link_libraries(gia_test gtest_discover_tests(gia_test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + +add_executable(origins_test origins_test.cc) + +target_link_libraries(origins_test + gtest_main + libabc +) + +gtest_discover_tests(origins_test + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) \ No newline at end of file diff --git a/test/gia/origins_test.cc b/test/gia/origins_test.cc new file mode 100644 index 0000000000..ad2f57863a --- /dev/null +++ b/test/gia/origins_test.cc @@ -0,0 +1,222 @@ +// Regression tests for per-object origin tracking (vOrigins). +// +// Covers the core infrastructure (set/get/add/dedup/inline->overflow +// promotion/union/cap), propagation through Gia_ManDup, the AIGER "y" +// extension write/read round-trip, and preservation through the &nf +// standard-cell mapper (which maps in place and is therefore expected to +// preserve origins without dedicated propagation code). + +#include "gtest/gtest.h" + +#include "aig/gia/gia.h" +#include "base/main/main.h" +#include "base/cmd/cmd.h" + +#include +#include + +ABC_NAMESPACE_IMPL_START + +// -------------------------------------------------------------------------- +// helpers +// -------------------------------------------------------------------------- + +// Build a 2-input single-AND combinational AIG: o = a & b. +static Gia_Man_t* BuildSmallAig() { + Gia_Man_t* p = Gia_ManStart(100); + int a = Gia_ManAppendCi(p); + int b = Gia_ManAppendCi(p); + int g = Gia_ManAppendAnd(p, a, b); + Gia_ManAppendCo(p, g); + return p; +} + +// Build o = (a & b) | (c & d), expressed with ANDs and inverters so the +// mapper has something non-trivial to do. +static Gia_Man_t* BuildOrOfAnds() { + Gia_Man_t* p = Gia_ManStart(100); + int a = Gia_ManAppendCi(p); + int b = Gia_ManAppendCi(p); + int c = Gia_ManAppendCi(p); + int d = Gia_ManAppendCi(p); + int ab = Gia_ManAppendAnd(p, a, b); + int cd = Gia_ManAppendAnd(p, c, d); + int nand = Gia_ManAppendAnd(p, Abc_LitNot(ab), Abc_LitNot(cd)); // ~ab & ~cd + Gia_ManAppendCo(p, Abc_LitNot(nand)); // ab | cd + return p; +} + +static int FirstAndId(Gia_Man_t* p) { + Gia_Obj_t* pObj; + int i; + Gia_ManForEachAnd(p, pObj, i) return i; + return -1; +} + +static int CountTotalOrigins(Gia_Man_t* p) { + Gia_Obj_t* pObj; + int i, total = 0; + if (!p->vOrigins) return 0; + Gia_ManForEachObj(p, pObj, i) total += Gia_ObjOriginsNum(p, i); + return total; +} + +// Allocate origins and seed every AND node with its own id (the "identity" +// mapping Yosys writes via the XAIGER "y" extension). +static void SeedIdentityOrigins(Gia_Man_t* p) { + Gia_Obj_t* pObj; + int i; + p->vOrigins = Gia_ManOriginsAlloc(Gia_ManObjNum(p)); + Gia_ManForEachAnd(p, pObj, i) Gia_ObjSetOrigin(p, i, i); +} + +// -------------------------------------------------------------------------- +// core infrastructure +// -------------------------------------------------------------------------- + +TEST(OriginsTest, SetAndGetSingleOrigin) { + Gia_Man_t* p = BuildSmallAig(); + p->vOrigins = Gia_ManOriginsAlloc(Gia_ManObjNum(p)); + int g = FirstAndId(p); + Gia_ObjSetOrigin(p, g, 42); + EXPECT_EQ(Gia_ObjOriginsNum(p, g), 1); + EXPECT_EQ(Gia_ObjOriginsGet(p, g, 0), 42); + Gia_ManStop(p); +} + +TEST(OriginsTest, AddDeduplicates) { + Gia_Man_t* p = BuildSmallAig(); + p->vOrigins = Gia_ManOriginsAlloc(Gia_ManObjNum(p)); + int g = FirstAndId(p); + Gia_ObjAddOrigin(p, g, 7); + Gia_ObjAddOrigin(p, g, 7); // duplicate: ignored + EXPECT_EQ(Gia_ObjOriginsNum(p, g), 1); + Gia_ObjAddOrigin(p, g, 9); + EXPECT_EQ(Gia_ObjOriginsNum(p, g), 2); + Gia_ManStop(p); +} + +TEST(OriginsTest, InlineToOverflowPromotion) { + Gia_Man_t* p = BuildSmallAig(); + p->vOrigins = Gia_ManOriginsAlloc(Gia_ManObjNum(p)); + int g = FirstAndId(p); + const int N = GIA_ORIGINS_INLINE + 3; // force promotion past the inline buffer + for (int k = 0; k < N; k++) Gia_ObjAddOrigin(p, g, 100 + k); + EXPECT_EQ(Gia_ObjOriginsNum(p, g), N); + std::set got; + for (int k = 0; k < N; k++) got.insert(Gia_ObjOriginsGet(p, g, k)); + for (int k = 0; k < N; k++) EXPECT_TRUE(got.count(100 + k)) << "missing origin " << (100 + k); + Gia_ManStop(p); +} + +TEST(OriginsTest, UnionAcrossManagers) { + Gia_Man_t* src = BuildSmallAig(); + src->vOrigins = Gia_ManOriginsAlloc(Gia_ManObjNum(src)); + int sg = FirstAndId(src); + Gia_ObjAddOrigin(src, sg, 11); + Gia_ObjAddOrigin(src, sg, 22); + + Gia_Man_t* dst = BuildSmallAig(); + dst->vOrigins = Gia_ManOriginsAlloc(Gia_ManObjNum(dst)); + int dg = FirstAndId(dst); + Gia_ObjAddOrigin(dst, dg, 11); // overlaps with src + + Gia_ObjUnionOrigins(dst, dg, src, sg); + EXPECT_EQ(Gia_ObjOriginsNum(dst, dg), 2); // {11, 22} deduplicated + + Gia_ManStop(src); + Gia_ManStop(dst); +} + +TEST(OriginsTest, NOriginsMaxCapsAccumulation) { + Gia_Man_t* p = BuildSmallAig(); + p->vOrigins = Gia_ManOriginsAlloc(Gia_ManObjNum(p)); + p->nOriginsMax = 10; + int g = FirstAndId(p); + for (int k = 0; k < 50; k++) Gia_ObjAddOrigin(p, g, 1000 + k); + EXPECT_EQ(Gia_ObjOriginsNum(p, g), 10); // capped once in overflow mode + Gia_ManStop(p); +} + +// -------------------------------------------------------------------------- +// propagation +// -------------------------------------------------------------------------- + +TEST(OriginsTest, GiaManDupPreservesOrigins) { + Gia_Man_t* p = BuildSmallAig(); + p->vOrigins = Gia_ManOriginsAlloc(Gia_ManObjNum(p)); + int g = FirstAndId(p); + Gia_ObjAddOrigin(p, g, 55); + + Gia_Man_t* dup = Gia_ManDup(p); + ASSERT_TRUE(dup->vOrigins != nullptr); + EXPECT_EQ(CountTotalOrigins(dup), 1); + int dg = FirstAndId(dup); + ASSERT_GE(dg, 0); + EXPECT_EQ(Gia_ObjOriginsNum(dup, dg), 1); + EXPECT_EQ(Gia_ObjOriginsGet(dup, dg, 0), 55); + + Gia_ManStop(p); + Gia_ManStop(dup); +} + +TEST(OriginsTest, AigerWriteReadRoundTripPreservesOrigins) { + Gia_Man_t* p = BuildSmallAig(); + p->vOrigins = Gia_ManOriginsAlloc(Gia_ManObjNum(p)); + int g = FirstAndId(p); + Gia_ObjAddOrigin(p, g, 77); + + const char* fname = "test_origins_roundtrip.aig"; + Gia_AigerWrite(p, (char*)fname, 0, 0, 0); // "y" extension emitted unconditionally + Gia_Man_t* q = Gia_AigerRead((char*)fname, 0, 0, 0); + remove(fname); + + ASSERT_TRUE(q != nullptr); + ASSERT_TRUE(q->vOrigins != nullptr); + EXPECT_EQ(CountTotalOrigins(q), 1); + + Gia_ManStop(p); + Gia_ManStop(q); +} + +// -------------------------------------------------------------------------- +// engine: &nf standard-cell mapping +// +// &nf maps in place (Nf_ManDeriveMapping returns p->pGia without renumbering), +// so origins on the AIG nodes must survive standard-cell mapping unchanged. +// -------------------------------------------------------------------------- + +TEST(OriginsTest, NfMappingPreservesOrigins) { + // Seed a design with origins and write it to AIGER (carries the "y" ext). + Gia_Man_t* p = BuildOrOfAnds(); + SeedIdentityOrigins(p); + const int seeded = CountTotalOrigins(p); + ASSERT_GT(seeded, 0); + const char* aig = "test_origins_nf_in.aig"; + Gia_AigerWrite(p, (char*)aig, 0, 0, 0); + Gia_ManStop(p); + + Abc_Start(); + Abc_Frame_t* pAbc = Abc_FrameGetGlobalFrame(); + + ASSERT_EQ(Cmd_CommandExecute(pAbc, "read_genlib test_origins.genlib"), 0); + char cmd[256]; + snprintf(cmd, sizeof(cmd), "&read %s", aig); + ASSERT_EQ(Cmd_CommandExecute(pAbc, cmd), 0); + + Gia_Man_t* before = Abc_FrameReadGia(pAbc); + ASSERT_TRUE(before != nullptr); + ASSERT_GT(CountTotalOrigins(before), 0); // origins read back from "y" + + ASSERT_EQ(Cmd_CommandExecute(pAbc, "&nf"), 0); + + Gia_Man_t* after = Abc_FrameReadGia(pAbc); + ASSERT_TRUE(after != nullptr); + ASSERT_TRUE(after->vOrigins != nullptr); + EXPECT_GT(CountTotalOrigins(after), 0); // preserved through &nf std-cell mapping + + remove(aig); + Abc_Stop(); +} + +ABC_NAMESPACE_IMPL_END diff --git a/test/gia/test_origins.genlib b/test/gia/test_origins.genlib new file mode 100644 index 0000000000..89815dbdbb --- /dev/null +++ b/test/gia/test_origins.genlib @@ -0,0 +1,8 @@ +GATE zero 0 O=CONST0; +GATE one 0 O=CONST1; +GATE buf 1 O=a; PIN * NONINV 1 999 1.0 0.0 1.0 0.0 +GATE inv 1 O=!a; PIN * INV 1 999 1.0 0.0 1.0 0.0 +GATE and2 3 O=a*b; PIN * NONINV 1 999 1.0 0.0 1.0 0.0 +GATE or2 3 O=a+b; PIN * NONINV 1 999 1.0 0.0 1.0 0.0 +GATE nand2 2 O=!(a*b); PIN * INV 1 999 1.0 0.0 1.0 0.0 +GATE nor2 2 O=!(a+b); PIN * INV 1 999 1.0 0.0 1.0 0.0 From 0225e847b6986c036850d09c64ded5e0d5196686 Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Sun, 28 Jun 2026 21:17:59 +0100 Subject: [PATCH 7/8] Fix: AIGER reader drops "y" origins after an "M" cell-mapping section Gia_AigerReadFromMemory's extension loop is a chain of `else if (*pCur == )` terminated by `else break`. The writer emits an uppercase "M" (cell-mapping doc) section, but the reader had no case for it, so reading a cell-mapped AIGER hit "M", fell through to `else break`, and abandoned every later extension -- including the "y" origin mapping, which is written after "M". Result: origins were silently lost when reading back any std-cell-mapped GIA (e.g. after &nf). Add an "M" branch that skips the section (its contents are consumed by external readers such as Yosys's read_xaiger2, not by ABC's GIA reader) so the loop continues to "y". "M" was the only writer-emitted section the reader did not handle. Adds a regression test (MappedAigerRoundTripPreservesOrigins) that maps with &nf, writes (emitting "M" then "y"), reads back, and asserts origins survive. Co-developed-by: Claude Code v2.1.195 (claude-opus-4-8) --- src/aig/gia/giaAiger.c | 13 +++++++++++++ test/gia/origins_test.cc | 31 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/aig/gia/giaAiger.c b/src/aig/gia/giaAiger.c index d3050beeca..c5472d606f 100644 --- a/src/aig/gia/giaAiger.c +++ b/src/aig/gia/giaAiger.c @@ -988,6 +988,19 @@ Gia_Man_t * Gia_AigerReadFromMemory( char * pContents, int nFileSize, int fGiaSi } pCur += 4*nInts; } + // skip the "M" cell-mapping doc (consumed by external readers such + // as Yosys's read_xaiger2). Without this, an unrecognized "M" + // section would fall through to "else break" below and abandon all + // subsequent extensions -- notably the "y" origin mapping, which is + // written after "M". + else if ( *pCur == 'M' ) + { + int nSize; + pCur++; + nSize = Gia_AigerReadInt(pCur); pCur += 4; + pCur += nSize; + if ( fVerbose ) printf( "Skipped extension \"M\".\n" ); + } else break; } } diff --git a/test/gia/origins_test.cc b/test/gia/origins_test.cc index ad2f57863a..dfdabf9e73 100644 --- a/test/gia/origins_test.cc +++ b/test/gia/origins_test.cc @@ -219,4 +219,35 @@ TEST(OriginsTest, NfMappingPreservesOrigins) { Abc_Stop(); } +// Regression: writing a cell-mapped GIA emits an "M" (cell-mapping) section +// followed by the "y" origin section. The AIGER reader must SKIP the unknown +// "M" section rather than bail out of the extension loop, otherwise the "y" +// origins (written after "M") are silently lost on read-back. +TEST(OriginsTest, MappedAigerRoundTripPreservesOrigins) { + Gia_Man_t* p = BuildOrOfAnds(); + SeedIdentityOrigins(p); + const char* in = "test_origins_mapped_in.aig"; + Gia_AigerWrite(p, (char*)in, 0, 0, 0); + Gia_ManStop(p); + + Abc_Start(); + Abc_Frame_t* pAbc = Abc_FrameGetGlobalFrame(); + ASSERT_EQ(Cmd_CommandExecute(pAbc, "read_genlib test_origins.genlib"), 0); + char cmd[256]; + snprintf(cmd, sizeof(cmd), "&read %s", in); + ASSERT_EQ(Cmd_CommandExecute(pAbc, cmd), 0); + ASSERT_EQ(Cmd_CommandExecute(pAbc, "&nf"), 0); // creates cell mapping + ASSERT_EQ(Cmd_CommandExecute(pAbc, "&write test_origins_mapped_out.aig"), 0); // emits "M" then "y" + ASSERT_EQ(Cmd_CommandExecute(pAbc, "&read test_origins_mapped_out.aig"), 0); // must skip "M" to reach "y" + + Gia_Man_t* after = Abc_FrameReadGia(pAbc); + ASSERT_TRUE(after != nullptr); + ASSERT_TRUE(after->vOrigins != nullptr); // would be NULL without the "M"-skip fix + EXPECT_GT(CountTotalOrigins(after), 0); + + remove(in); + remove("test_origins_mapped_out.aig"); + Abc_Stop(); +} + ABC_NAMESPACE_IMPL_END From 2daf32f2d1154547d98838bffb3d62bfe25150ef Mon Sep 17 00:00:00 2001 From: Rob Taylor Date: Mon, 29 Jun 2026 04:09:42 +0100 Subject: [PATCH 8/8] gia: propagate vOrigins through AIG-rebuilding passes (balance, mux conv) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Balancing (Gia_ManBalanceInt, Dam_ManMultiAig) and MUX<->AND conversion (Gia_ManDupMuxes/NoMuxes) rebuild the AIG via Gia_ManHashAnd/MuxReal, creating new nodes with no 1:1 correspondence to the source. The post-hoc Gia_ManOriginsDup (keyed on pObj->Value) cannot seed those new nodes, so their vOrigins — and the \src of any cell later mapped from them — were dropped. Add Gia_ManOriginsDupFill: Gia_ManOriginsDup for the survivors, then a single bottom-up pass giving each remaining AND node the union of its fanins' origins (incl. the MUX 3rd fanin). GIA's topological order makes one forward pass sufficient. Use it at the four rebuild sites. Effect: e.g. a sequential design with reset/enable through `&syn2` goes from 94% to 100% \src; `&b`/`&dc2`/`&dch` origin coverage is hardened. The change is additive (only ever adds origins), so engines already at full coverage are unaffected. Regression test: AreaBalancePreservesOriginCoverage. Note: heavy restructuring still dissolves the original \src-bearing AND nodes, so nodes that become pure functions of CIs get only CI-level provenance (CIs carry no map2 \src) — an inherent limit, not addressed here. The default std-cell flow uses `&dch;&nf`, which reaches 100% precise \src. Co-developed-by: Claude Code v2.1.195 (claude-opus-4-8) --- src/aig/gia/gia.h | 1 + src/aig/gia/giaBalAig.c | 10 ++++++++-- src/aig/gia/giaDup.c | 42 ++++++++++++++++++++++++++++++++++++++++ src/aig/gia/giaMuxes.c | 8 ++++++-- test/gia/origins_test.cc | 30 ++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/aig/gia/gia.h b/src/aig/gia/gia.h index fe8ce9dea9..1fda1603e2 100644 --- a/src/aig/gia/gia.h +++ b/src/aig/gia/gia.h @@ -1457,6 +1457,7 @@ extern void Gia_ObjUnionOrigins( Gia_Man_t * p, int iDst, Gia_Man extern void Gia_ManOriginsFreeOverflows( Gia_Man_t * p ); extern void Gia_ManOriginsReset( Gia_Man_t * p ); extern void Gia_ManOriginsDup( Gia_Man_t * pNew, Gia_Man_t * pOld ); +extern void Gia_ManOriginsDupFill( Gia_Man_t * pNew, Gia_Man_t * pOld ); extern void Gia_ManOriginsDupVec( Gia_Man_t * pNew, Gia_Man_t * pOld, Vec_Int_t * vCopies ); extern void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld ); extern void Gia_ManOriginsDupIf( Gia_Man_t * pNew, Gia_Man_t * p, void * pIfMan ); diff --git a/src/aig/gia/giaBalAig.c b/src/aig/gia/giaBalAig.c index 9a132f8432..04c082cc27 100644 --- a/src/aig/gia/giaBalAig.c +++ b/src/aig/gia/giaBalAig.c @@ -420,7 +420,11 @@ Gia_Man_t * Gia_ManBalanceInt( Gia_Man_t * p, int fStrict ) } assert( !fStrict || Gia_ManObjNum(pNew) <= Gia_ManObjNum(p) ); Gia_ManHashStop( pNew ); - Gia_ManOriginsDup( pNew, p ); + // Balancing rebuilds the AIG: the new balanced nodes have no 1:1 origin + // correspondence, so fill them bottom-up from their fanins (a plain + // Gia_ManOriginsDup would leave them, and the cells later mapped from them, + // without \src). + Gia_ManOriginsDupFill( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); // perform cleanup pNew = Gia_ManCleanup( pTemp = pNew ); @@ -820,7 +824,9 @@ Gia_Man_t * Dam_ManMultiAig( Dam_Man_t * pMan ) } // assert( Gia_ManObjNum(pNew) <= Gia_ManObjNum(p) ); Gia_ManHashStop( pNew ); - Gia_ManOriginsDup( pNew, p ); + // Area balancing rebuilds the AIG via the divisor manager: fill the new + // nodes' origins bottom-up (see Gia_ManBalanceInt for the rationale). + Gia_ManOriginsDupFill( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); // perform cleanup pNew = Gia_ManCleanup( pTemp = pNew ); diff --git a/src/aig/gia/giaDup.c b/src/aig/gia/giaDup.c index d6fa79173a..1661f76581 100644 --- a/src/aig/gia/giaDup.c +++ b/src/aig/gia/giaDup.c @@ -350,6 +350,48 @@ void Gia_ManOriginsDup( Gia_Man_t * pNew, Gia_Man_t * pOld ) } } +/**Function************************************************************* + + Synopsis [Like Gia_ManOriginsDup, then fills AND nodes left without origins.] + + Description [Use after a rebuild (e.g. balancing) that creates fresh AND nodes + with no 1:1 correspondence to pOld: Gia_ManOriginsDup seeds the survivors + (precise per-node origins), then a single bottom-up pass gives each remaining + AND node the union of its fanins' origins. Because GIA is topologically + ordered, one forward pass suffices: a node's fanins (lower ids) already carry + their origins. New nodes thus inherit the union of the surviving boundary + nodes in their cone — the most precise attribution available once the original + internal nodes are gone.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +void Gia_ManOriginsDupFill( Gia_Man_t * pNew, Gia_Man_t * pOld ) +{ + Gia_Obj_t * pObj; + int i; + Gia_ManOriginsDup( pNew, pOld ); + if ( !pNew->vOrigins ) + return; + Gia_ManForEachAnd( pNew, pObj, i ) + { + int f0, f1, f2; + if ( Gia_ObjOriginsNum( pNew, i ) > 0 ) + continue; + f0 = Gia_ObjFaninId0( pObj, i ); + f1 = Gia_ObjFaninId1( pObj, i ); + f2 = Gia_ObjFaninId2( pNew, i ); // 3rd fanin of MUX nodes, else -1 + if ( f0 > 0 ) + Gia_ObjUnionOrigins( pNew, i, pNew, f0 ); + if ( f1 > 0 ) + Gia_ObjUnionOrigins( pNew, i, pNew, f1 ); + if ( f2 > 0 ) + Gia_ObjUnionOrigins( pNew, i, pNew, f2 ); + } +} + /**Function************************************************************* Synopsis [Propagates origins using a copies vector.] diff --git a/src/aig/gia/giaMuxes.c b/src/aig/gia/giaMuxes.c index 8eb2e761b9..cade908158 100644 --- a/src/aig/gia/giaMuxes.c +++ b/src/aig/gia/giaMuxes.c @@ -140,7 +140,9 @@ Gia_Man_t * Gia_ManDupMuxes( Gia_Man_t * p, int Limit ) pNew->pSibls[Gia_ObjId(pNew, pObjNew)] = Gia_ObjId(pNew, pSiblNew); } Gia_ManHashStop( pNew ); - Gia_ManOriginsDup( pNew, p ); + // MUX <-> AND conversion rebuilds nodes (collapsing/expanding mux patterns), + // so fill new nodes' origins bottom-up; a plain Gia_ManOriginsDup misses them. + Gia_ManOriginsDupFill( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); // perform cleanup pNew = Gia_ManCleanup( pTemp = pNew ); @@ -254,7 +256,9 @@ Gia_Man_t * Gia_ManDupNoMuxes( Gia_Man_t * p, int fSkipBufs ) pObj->Value = Gia_ManHashAnd( pNew, Gia_ObjFanin0Copy(pObj), Gia_ObjFanin1Copy(pObj) ); } Gia_ManHashStop( pNew ); - Gia_ManOriginsDup( pNew, p ); + // MUX <-> AND conversion rebuilds nodes (collapsing/expanding mux patterns), + // so fill new nodes' origins bottom-up; a plain Gia_ManOriginsDup misses them. + Gia_ManOriginsDupFill( pNew, p ); Gia_ManSetRegNum( pNew, Gia_ManRegNum(p) ); // perform cleanup pNew = Gia_ManCleanup( pTemp = pNew ); diff --git a/test/gia/origins_test.cc b/test/gia/origins_test.cc index dfdabf9e73..92a49f0486 100644 --- a/test/gia/origins_test.cc +++ b/test/gia/origins_test.cc @@ -250,4 +250,34 @@ TEST(OriginsTest, MappedAigerRoundTripPreservesOrigins) { Abc_Stop(); } +// Balancing rebuilds the AIG, creating new internal nodes with no 1:1 origin +// correspondence. Gia_ManOriginsDupFill must cover them bottom-up so no node +// (and thus no cell later mapped from it) loses provenance. A left-deep AND +// chain is restructured into a shallow tree, forcing fresh nodes. +TEST(OriginsTest, AreaBalancePreservesOriginCoverage) { + Gia_Man_t* p = Gia_ManStart(100); + int ci[8], i; + for (i = 0; i < 8; i++) ci[i] = Gia_ManAppendCi(p); + int lit = ci[0]; + for (i = 1; i < 8; i++) lit = Gia_ManAppendAnd(p, lit, ci[i]); + Gia_ManAppendCo(p, lit); + // Seed identity origins on every object (CIs included) as the XAIGER "y" + // extension does — the fill propagates CI origins up into new leaf nodes. + Gia_Obj_t* pSeed; + p->vOrigins = Gia_ManOriginsAlloc(Gia_ManObjNum(p)); + Gia_ManForEachObj(p, pSeed, i) + if (i > 0) Gia_ObjSetOrigin(p, i, i); + + Gia_Man_t* pNew = Gia_ManAreaBalance(p, 1, 1000, 0, 0); + ASSERT_TRUE(pNew->vOrigins != nullptr); + Gia_Obj_t* pObj; + int uncovered = 0; + Gia_ManForEachAnd(pNew, pObj, i) + if (Gia_ObjOriginsNum(pNew, i) == 0) uncovered++; + EXPECT_EQ(uncovered, 0) << "balanced AIG has AND nodes with no origin"; + + Gia_ManStop(p); + Gia_ManStop(pNew); +} + ABC_NAMESPACE_IMPL_END