struct list { int X; list *Next; };
list *MakeList(int N) {
list *Result = 0;
for (int i = 0; i != N; ++i) {
list *Node =
malloc(sizeof(list));
Node->Next = Result;
Node->X = i+’A’;
Result = Node; }
return Result; }
int Length(list *L) {
if (L == 0) return 0;
return Length(L->Next)+1;
}
int Testlists() {
list *A = MakeList(100);
list *B = MakeList(20);
int Sum = Length(A) + Length(B);
((char*)B)[5] = ’c’; // not type safe!
return Sum; }
(a) Original
list: HM
Result Node returning
(b) BU DS gra ph for MakeList
(c) BU DS gra ph for Length
(d) BU DS gra ph for Testlists
struct list { int X; list *Next; };
list *MakeList(Pool *PD, int N) {
list *Result = 0;
for (int i = 0; i != N; ++i) {
list *Node =
poolalloc(PD, sizeof(list));
Node->Next = Result;
Node->X = i+’A’;
Result = Node; }
return Result; }
int Length(list *L) {
if (L == 0) return 0;
return Length(L->Next)+1;
}
int Testlists() {
Pool P1, P2;
poolinit(&P1, sizeof(list));
poolinit(&P2, 0 /*no size hint known*/);
list *A = MakeList(&P1, 100);
list *B = MakeList(&P2, 20);
int Sum = Length(A) + Length(B);
((char*)B)[5] = ’c’;
pooldestroy(&P1); pooldestroy(&P2);
return Sum; }
(e) After Pool Allocation
Figure 4: Simple linked list example
the objects in the lists A and B (and prove the lists are
disjoint), even though t hey are created and manipulated by
common functions. If heap objects were instead named by
allocation site, A and B would point to a single node in the
graph and objects of the two lists would not be distinguish-
able.
A second feature of DSA is that, like many other context-
sensitive p ointer analyses, e.g., [5, 4, 11, 13], it actually
computes two points-to graphs for each function in a pro-
gram - a bottom-up ( BU) graph representing a function and
its callees (but not any callers), and a final top-down (TD)
graph representing the effects of both callees and cal lers.
The TD graph is the final result of the pointer analysis. The
BU graph, however, provides a more precise basis for both
Pool Allocation and Pointer Compression because two dis-
tinct pointer arguments in a function may be aliased (point
to the same DS graph node) in one calling context but not
in another. Using the BU graph allows the transformatio ns
to distinguish (and therefore) segregate objects in the latter
case. Therefore, pointer compression operates largely using
the BU g ra ph, except where noted, and pool allocation only
uses the BU graph [10].
The example graphs a lso illustrate o ther basic but relevant
features of DSA. The algorithm detects that the structure
is recursive (the cycle in the graph), and that the point-
ers in the functions point to the list objects. In MakeList,
DSA also detects that heap objects are allocated (H) and
returned, whereas in Length, memory is not allocated or
freed (no H flag). (The M and R flags shown in the figures
can be ignored for this work.)
2.2 Automatic Pool Allocation
Given a program with calls to malloc and free, Auto-
matic Pool Allocation modifies the program to allocate and
free memory from p ools within the standard system heap. A
pool is represented in the program by a pool descriptor. Au-
tomatic Pool Allocation creates one dist inct pool descriptor
for each node marked H in a function’s DS graph, effectively
partitioning objects in t he heap as they were partitioned in
the DS graph by pointer analysis. Calls to malloc and free
struct list_pc32 { int X; int Next; };
static int MakeList_pc32(Pool *PD, int N) {
int Result = 0;
for (int i = 0; i != N; ++i) {
int Node = poolalloc_pc(PD, 1);
int *tmp1 = PD->poolbase+Node+offsetof(list_pc32, Next);
*tmp1 = Result;
int *tmp2 = PD->poolbase+Node+offsetof(list_pc32, X);
*tmp2 = i+’A’;
Result = Node; }
return Result; }
static int Length_pc32(Pool *PD, int L) {
if (L == 0) return 0;
int *tmp = PD->poolbase+L+offsetof(list_pc32, Next);
return Length_pc32(PD, *tmp)+1;
}
int Testlists() {
Pool P1, P2;
poolinit_pc(&P1, sizeof(list_pc32));
poolinit_pc(&P2, 1);
int A = MakeList_pc32(&P1, 100);
int B = MakeList_pc64(&P2, 20);
int Sum = Length_pc32(&P1, A) + Length_pc64(&P2, B);
((char*)B)[5] = ’c’;
pooldestroy_pc(&P1); pooldestroy_pc(&P2);
return Sum; }
Figure 5: Example after static compression
are simply replaced with calls to poolalloc and poolfree,
passing i n the pool descriptor corresponding t o the DS node
poi nted to by the relevant pointer. This implies that the
lifetime of individual objects allo cated from the pool stay
exactly the same as the original program (except for some
leaked objects as explained below).
The pool runtime library provides functions poolinit
and pooldestroy to initialize and destroy a po o l descrip-
tor. pooldestroy also releases any remaining pool memory
to the system heap. (Note that this can reclaim memory fo r
objects that were never freed in the original program, i.e.,
were previously leaked.) Each pool is a fully general heap,
providing equivalents for all standard heap functions, includ-
ing poolalloc, poolfree, poolrealloc and poolmemalign.
A pool internally obtains and frees memory off the system
heap in large s labs using malloc and free.