Skip to content

Commit f8641a5

Browse files
committed
Multi-origin vOrigins with inline small-buffer optimization
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): - Small mode: 4 inline int slots (-1 = unused) - Large 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)
1 parent acaa07e commit f8641a5

7 files changed

Lines changed: 388 additions & 127 deletions

File tree

src/aig/gia/gia.h

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <stdlib.h>
3131
#include <string.h>
3232
#include <assert.h>
33+
#include <limits.h>
3334

3435
#include "misc/vec/vec.h"
3536
#include "misc/vec/vecWec.h"
@@ -45,6 +46,34 @@ ABC_NAMESPACE_HEADER_START
4546
#define GIA_NONE 0x1FFFFFFF
4647
#define GIA_VOID 0x0FFFFFFF
4748

49+
// Multi-origin tracking: small-buffer optimization with overflow
50+
#ifndef GIA_ORIGINS_INLINE
51+
#define GIA_ORIGINS_INLINE 4 // number of inline origin slots (configurable at compile time)
52+
#endif
53+
54+
typedef union {
55+
struct {
56+
int origins[GIA_ORIGINS_INLINE]; // -1 = unused slot
57+
} small;
58+
struct {
59+
int sentinel; // INT_MIN marks overflow mode
60+
int count; // number of origins in overflow array
61+
int * overflow; // heap-allocated origin array
62+
} large;
63+
} Gia_OriginsEntry_t;
64+
65+
#define GIA_ORIGINS_STRIDE ((int)(sizeof(Gia_OriginsEntry_t) / sizeof(int)))
66+
#define GIA_ORIGINS_SENTINEL INT_MIN
67+
68+
// Compile-time checks: inline slots must cover the overflow header,
69+
// and must not exceed the hardcoded initial overflow capacity of 8
70+
typedef char Gia_OriginsEntry_SizeCheck_t[
71+
(GIA_ORIGINS_INLINE * sizeof(int) >= sizeof(int) + sizeof(int) + sizeof(int *)) ? 1 : -1
72+
];
73+
typedef char Gia_OriginsEntry_CapCheck_t[
74+
(GIA_ORIGINS_INLINE <= 7) ? 1 : -1
75+
];
76+
4877
////////////////////////////////////////////////////////////////////////
4978
/// BASIC TYPES ///
5079
////////////////////////////////////////////////////////////////////////
@@ -463,8 +492,77 @@ static inline int Gia_ManIsConst0Lit( int iLit ) { return (iLit ==
463492
static inline int Gia_ManIsConst1Lit( int iLit ) { return (iLit == 1); }
464493
static inline int Gia_ManIsConstLit( int iLit ) { return (iLit <= 1); }
465494

466-
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; }
467-
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); }
495+
// Multi-origin accessors
496+
static inline Gia_OriginsEntry_t * Gia_ObjOriginsEntry( Gia_Man_t * p, int iObj )
497+
{
498+
return (Gia_OriginsEntry_t *)(Vec_IntArray(p->vOrigins) + iObj * GIA_ORIGINS_STRIDE);
499+
}
500+
static inline int Gia_ObjOriginsIsOverflow( Gia_OriginsEntry_t * e )
501+
{
502+
return e->small.origins[0] == GIA_ORIGINS_SENTINEL;
503+
}
504+
static inline int Gia_ObjOriginsNum( Gia_Man_t * p, int iObj )
505+
{
506+
Gia_OriginsEntry_t * e;
507+
int k;
508+
if ( !p->vOrigins || iObj * GIA_ORIGINS_STRIDE >= Vec_IntSize(p->vOrigins) )
509+
return 0;
510+
e = Gia_ObjOriginsEntry( p, iObj );
511+
if ( Gia_ObjOriginsIsOverflow(e) )
512+
return e->large.count;
513+
for ( k = 0; k < GIA_ORIGINS_INLINE; k++ )
514+
if ( e->small.origins[k] == -1 ) break;
515+
return k;
516+
}
517+
static inline int Gia_ObjOriginsGet( Gia_Man_t * p, int iObj, int idx )
518+
{
519+
Gia_OriginsEntry_t * e;
520+
if ( !p->vOrigins || iObj * GIA_ORIGINS_STRIDE >= Vec_IntSize(p->vOrigins) )
521+
return -1;
522+
e = Gia_ObjOriginsEntry( p, iObj );
523+
if ( Gia_ObjOriginsIsOverflow(e) )
524+
return (idx < e->large.count) ? e->large.overflow[idx] : -1;
525+
return (idx < GIA_ORIGINS_INLINE) ? e->small.origins[idx] : -1;
526+
}
527+
// Backward compatible: get first/primary origin
528+
static inline int Gia_ObjOrigin( Gia_Man_t * p, int iObj )
529+
{
530+
Gia_OriginsEntry_t * e;
531+
if ( !p->vOrigins || iObj * GIA_ORIGINS_STRIDE >= Vec_IntSize(p->vOrigins) )
532+
return -1;
533+
e = Gia_ObjOriginsEntry( p, iObj );
534+
if ( Gia_ObjOriginsIsOverflow(e) )
535+
return e->large.count > 0 ? e->large.overflow[0] : -1;
536+
return e->small.origins[0];
537+
}
538+
// Set single origin (clears any existing, for initialization)
539+
static inline void Gia_ObjSetOrigin( Gia_Man_t * p, int iObj, int iOrig )
540+
{
541+
Gia_OriginsEntry_t * e;
542+
int k;
543+
if ( !p->vOrigins || iObj * GIA_ORIGINS_STRIDE >= Vec_IntSize(p->vOrigins) )
544+
return;
545+
e = Gia_ObjOriginsEntry( p, iObj );
546+
if ( Gia_ObjOriginsIsOverflow(e) && e->large.overflow )
547+
ABC_FREE( e->large.overflow );
548+
e->small.origins[0] = iOrig;
549+
for ( k = 1; k < GIA_ORIGINS_INLINE; k++ )
550+
e->small.origins[k] = -1;
551+
}
552+
// Grow vOrigins to accommodate object iObj
553+
static inline void Gia_ManOriginsGrow( Gia_Man_t * p, int iObj )
554+
{
555+
Vec_IntFillExtra( p->vOrigins, (iObj + 1) * GIA_ORIGINS_STRIDE, -1 );
556+
}
557+
// Allocate vOrigins for nObjs objects (all entries initialized to -1)
558+
static inline Vec_Int_t * Gia_ManOriginsAlloc( int nObjs )
559+
{
560+
return Vec_IntStartFull( nObjs * GIA_ORIGINS_STRIDE );
561+
}
562+
// Iteration macro (caller must declare int _nOrig before use, or use nOrig variant)
563+
#define Gia_ObjForEachOrigin( p, iObj, orig, idx ) \
564+
for ( idx = 0, _nOrig = Gia_ObjOriginsNum(p, iObj); \
565+
(idx < _nOrig) && ((orig = Gia_ObjOriginsGet(p, iObj, idx)), 1); idx++ )
468566

469567
static inline Gia_Obj_t * Gia_Regular( Gia_Obj_t * p ) { return (Gia_Obj_t *)((ABC_PTRUINT_T)(p) & ~01); }
470568
static inline Gia_Obj_t * Gia_Not( Gia_Obj_t * p ) { return (Gia_Obj_t *)((ABC_PTRUINT_T)(p) ^ 01); }
@@ -1352,9 +1450,14 @@ extern Gia_Man_t * Gia_ManDupOrderDfsReverse( Gia_Man_t * p, int fRevFan
13521450
extern Gia_Man_t * Gia_ManDupOutputGroup( Gia_Man_t * p, int iOutStart, int iOutStop );
13531451
extern Gia_Man_t * Gia_ManDupOutputVec( Gia_Man_t * p, Vec_Int_t * vOutPres );
13541452
extern Gia_Man_t * Gia_ManDupSelectedOutputs( Gia_Man_t * p, Vec_Int_t * vOutsLeft );
1453+
extern void Gia_ObjAddOrigin( Gia_Man_t * p, int iObj, int iOrig );
1454+
extern void Gia_ObjUnionOrigins( Gia_Man_t * p, int iDst, Gia_Man_t * pSrc, int iSrc );
1455+
extern void Gia_ManOriginsFreeOverflows( Gia_Man_t * p );
1456+
extern void Gia_ManOriginsReset( Gia_Man_t * p );
13551457
extern void Gia_ManOriginsDup( Gia_Man_t * pNew, Gia_Man_t * pOld );
13561458
extern void Gia_ManOriginsDupVec( Gia_Man_t * pNew, Gia_Man_t * pOld, Vec_Int_t * vCopies );
13571459
extern void Gia_ManOriginsAfterRoundTrip( Gia_Man_t * pNew, Gia_Man_t * pOld );
1460+
extern void Gia_ManOriginsDupIf( Gia_Man_t * pNew, Gia_Man_t * p, void * pIfMan );
13581461
extern Gia_Man_t * Gia_ManDupOrderAiger( Gia_Man_t * p );
13591462
extern Gia_Man_t * Gia_ManDupLastPis( Gia_Man_t * p, int nLastPis );
13601463
extern Gia_Man_t * Gia_ManDupFlip( Gia_Man_t * p, int * pInitState );

src/aig/gia/giaAiger.c

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -942,17 +942,40 @@ Gia_Man_t * Gia_AigerReadFromMemory( char * pContents, int nFileSize, int fGiaSi
942942
if ( fVerbose ) printf( "Skipped extension \"y\" for vEquLitIds (AIG is rehashed).\n" );
943943
}
944944
// populate vOrigins using vNodes to map AIG→GIA object indices
945-
if ( nInts == Vec_IntSize(vNodes) ) {
945+
// sentinel -2 at pData[0] distinguishes new format from old
946+
if ( vNodes && nInts >= 1 && ((int *)pCur)[0] == -2 ) {
947+
// new multi-origin format: sentinel, then [count, lit0, lit1, ...] per AIG object
948+
int k, nAigObjs = Vec_IntSize(vNodes);
949+
int * pData = (int *)pCur;
950+
int pos = 1; // skip sentinel
951+
pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) );
952+
for ( k = 0; k < nAigObjs && pos < nInts; k++ )
953+
{
954+
int giaLit = Vec_IntEntry( vNodes, k );
955+
int giaObj = Abc_Lit2Var( giaLit );
956+
int nOrig = pData[pos++];
957+
int j;
958+
for ( j = 0; j < nOrig && pos < nInts; j++, pos++ )
959+
{
960+
int rawLit = pData[pos];
961+
if ( rawLit >= 0 && giaObj < Gia_ManObjNum(pNew) )
962+
Gia_ObjAddOrigin( pNew, giaObj, Abc_Lit2Var(rawLit) );
963+
}
964+
}
965+
if ( fVerbose ) printf( "Finished reading extension \"y\" (multi-origin).\n" );
966+
}
967+
else if ( vNodes && nInts == Vec_IntSize(vNodes) ) {
968+
// old single-origin format: one literal per AIG object
946969
int k;
947970
int * pData = (int *)pCur;
948-
pNew->vOrigins = Vec_IntStartFull( Gia_ManObjNum(pNew) );
971+
pNew->vOrigins = Gia_ManOriginsAlloc( Gia_ManObjNum(pNew) );
949972
for ( k = 0; k < nInts; k++ )
950973
{
951974
int giaLit = Vec_IntEntry( vNodes, k );
952975
int giaObj = Abc_Lit2Var( giaLit );
953976
int rawLit = pData[k];
954977
if ( rawLit >= 0 && giaObj < Gia_ManObjNum(pNew) )
955-
Vec_IntWriteEntry( pNew->vOrigins, giaObj, Abc_Lit2Var(rawLit) );
978+
Gia_ObjSetOrigin( pNew, giaObj, Abc_Lit2Var(rawLit) );
956979
}
957980
}
958981
pCur += 4*nInts;
@@ -1836,21 +1859,27 @@ void Gia_AigerWriteS( Gia_Man_t * pInit, char * pFileName, int fWriteSymbols, in
18361859
fwrite( Vec_IntArray(p->vObjClasses), 1, 4*Gia_ManObjNum(p), pFile );
18371860
}
18381861
// write object origins (vOrigins takes priority over vEquLitIds)
1862+
// New variable-length format: sentinel -2, then for each object [count, lit0, lit1, ...]
18391863
if ( p->vOrigins )
18401864
{
18411865
int k, nObjs = Gia_ManObjNum(p);
1842-
Vec_Int_t * vLits = Vec_IntStart( nObjs );
1843-
assert( Vec_IntSize(p->vOrigins) == nObjs );
1866+
Vec_Int_t * vData = Vec_IntAlloc( 1 + nObjs * 2 );
1867+
assert( Vec_IntSize(p->vOrigins) >= nObjs * GIA_ORIGINS_STRIDE );
1868+
Vec_IntPush( vData, -2 ); // sentinel distinguishes new format from old
18441869
for ( k = 0; k < nObjs; k++ )
18451870
{
1846-
int orig = Vec_IntEntry(p->vOrigins, k);
1847-
Vec_IntWriteEntry( vLits, k, orig >= 0 ? 2 * orig : -1 );
1871+
int nOrig = Gia_ObjOriginsNum( p, k );
1872+
int idx, orig, _nOrig;
1873+
Vec_IntPush( vData, nOrig );
1874+
Gia_ObjForEachOrigin( p, k, orig, idx )
1875+
Vec_IntPush( vData, orig >= 0 ? 2 * orig : -1 );
18481876
}
18491877
fprintf( pFile, "y" );
1850-
Gia_FileWriteBufferSize( pFile, 4*nObjs );
1851-
fwrite( Vec_IntArray(vLits), 1, (size_t)4*nObjs, pFile );
1852-
Vec_IntFree( vLits );
1853-
if ( fVerbose ) printf( "Finished writing extension \"y\" (from origins).\n" );
1878+
Gia_FileWriteBufferSize( pFile, 4*Vec_IntSize(vData) );
1879+
fwrite( Vec_IntArray(vData), 1, (size_t)4*Vec_IntSize(vData), pFile );
1880+
if ( fVerbose ) printf( "Finished writing extension \"y\" (multi-origin, %d ints for %d objs).\n",
1881+
Vec_IntSize(vData), nObjs );
1882+
Vec_IntFree( vData );
18541883
}
18551884
else if ( p->vEquLitIds )
18561885
{

0 commit comments

Comments
 (0)