Skip to content

Commit 40d9177

Browse files
authored
Merge pull request #150 from shwestrick/cc-work-list-opt
Better space performance for CC work list
2 parents a5f71ae + 058d57d commit 40d9177

File tree

3 files changed

+294
-64
lines changed

3 files changed

+294
-64
lines changed

runtime/gc/cc-work-list.c

+223-18
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ void CC_workList_init(
1010
HM_chunkList c = &(w->storage);
1111
HM_initChunkList(c);
1212
// arbitrary, just need an initial chunk
13-
w->currentChunk = HM_allocateChunk(c, sizeof(objptr));
13+
w->currentChunk = HM_allocateChunk(c, sizeof(struct CC_workList_elem));
1414
}
1515

1616

@@ -34,41 +34,240 @@ bool CC_workList_isEmpty(
3434
}
3535

3636

37+
pointer findStackNonEmptyFrameTop(GC_state s, pointer bottom, pointer top) {
38+
assert(bottom <= top);
39+
40+
while (top > bottom) {
41+
/* Invariant: top points just past a "return address". */
42+
GC_returnAddress returnAddress =
43+
*((GC_returnAddress*)(top - GC_RETURNADDRESS_SIZE));
44+
GC_frameInfo frameInfo = getFrameInfoFromReturnAddress(s, returnAddress);
45+
// index zero of this array is size
46+
GC_frameOffsets frameOffsets = frameInfo->offsets;
47+
48+
if (frameOffsets[0] > 0) {
49+
return top;
50+
}
51+
52+
top -= frameInfo->size;
53+
}
54+
55+
return NULL;
56+
}
57+
58+
59+
// returns FALSE if object has no objptrs and doesn't need to be traced
60+
bool makeInitialElem(GC_state s, objptr op, CC_workList_elem result) {
61+
GC_header header;
62+
uint16_t numObjptrs;
63+
GC_objectTypeTag tag;
64+
header = getHeader(objptrToPointer(op, NULL));
65+
splitHeader(s, header, &tag, NULL, NULL, &numObjptrs);
66+
67+
if (NORMAL_TAG == tag) {
68+
if (0 == numObjptrs) return FALSE;
69+
70+
result->op = op;
71+
result->data.normal.objptrIdx = 0;
72+
return TRUE;
73+
}
74+
75+
if (SEQUENCE_TAG == tag) {
76+
if (0 == numObjptrs) return FALSE;
77+
if (0 == getSequenceLength(objptrToPointer(op, NULL))) return FALSE;
78+
79+
result->op = op;
80+
result->data.sequence.cellIdx = 0;
81+
result->data.sequence.objptrIdx = 0;
82+
return TRUE;
83+
}
84+
85+
if (STACK_TAG == tag) {
86+
// printf("makeInitialElem stack\n");
87+
88+
GC_stack stack = (GC_stack)objptrToPointer(op, NULL);
89+
assert (stack->used <= stack->reserved);
90+
pointer bottom = getStackBottom(s, stack);
91+
pointer top = getStackTop(s, stack);
92+
pointer firstTop = findStackNonEmptyFrameTop(s, bottom, top);
93+
94+
if (NULL == firstTop) {
95+
// printf("makeInitialElem: skipping stack "FMTOBJPTR"\n", op);
96+
return FALSE;
97+
}
98+
99+
assert(firstTop > bottom);
100+
101+
result->op = op;
102+
result->data.stack.topCursor = firstTop;
103+
result->data.stack.frameOffsetsIdx = 0;
104+
105+
// printf("makeInitialElem: push stack "FMTOBJPTR"\n", op);
106+
return TRUE;
107+
}
108+
109+
DIE("makeInitialElem: cannot handle tag %u", tag);
110+
return FALSE;
111+
}
112+
113+
37114
void CC_workList_push(
38-
__attribute__((unused)) GC_state s,
115+
GC_state s,
39116
CC_workList w,
40117
objptr op)
41118
{
119+
struct CC_workList_elem elem;
120+
if (!makeInitialElem(s, op, &elem))
121+
return;
122+
42123
HM_chunkList list = &(w->storage);
43124
HM_chunk chunk = w->currentChunk;
44-
size_t opsz = sizeof(objptr);
125+
size_t elemSize = sizeof(struct CC_workList_elem);
45126

46-
if (HM_getChunkSizePastFrontier(chunk) < opsz) {
127+
if (HM_getChunkSizePastFrontier(chunk) < elemSize) {
47128
if (chunk->nextChunk != NULL) {
48129
chunk = chunk->nextChunk; // this will be an empty chunk
49130
} else {
50-
chunk = HM_allocateChunk(list, opsz);
131+
chunk = HM_allocateChunk(list, elemSize);
51132
}
52133
w->currentChunk = chunk;
53134
}
54135

55136
assert(NULL != chunk);
56-
assert(HM_getChunkSizePastFrontier(chunk) >= opsz);
137+
assert(HM_getChunkSizePastFrontier(chunk) >= elemSize);
57138
assert(chunk == w->currentChunk);
58139

59140
pointer frontier = HM_getChunkFrontier(chunk);
60141
HM_updateChunkFrontierInList(
61142
list,
62143
chunk,
63-
frontier + opsz);
144+
frontier + elemSize);
145+
146+
*(CC_workList_elem)frontier = elem;
147+
return;
148+
}
64149

65-
*((objptr*)frontier) = op;
66150

151+
struct advanceOneFieldResult {
152+
objptr* field;
153+
bool objectDone;
154+
};
155+
156+
void advanceOneField(
157+
GC_state s,
158+
CC_workList_elem elem,
159+
struct advanceOneFieldResult * result)
160+
{
161+
pointer p = objptrToPointer(elem->op, NULL);
162+
163+
// inspect the object
164+
GC_header header;
165+
uint16_t bytesNonObjptrs;
166+
uint16_t numObjptrs;
167+
GC_objectTypeTag tag;
168+
header = getHeader(p);
169+
splitHeader(s, header, &tag, NULL, &bytesNonObjptrs, &numObjptrs);
170+
171+
// ======================== NORMAL OBJECTS ========================
172+
173+
if (NORMAL_TAG == tag) {
174+
uint16_t objptrIdx = elem->data.normal.objptrIdx;
175+
assert(objptrIdx < numObjptrs);
176+
result->field = (objptr*)(p + bytesNonObjptrs + (objptrIdx * OBJPTR_SIZE));
177+
result->objectDone = (objptrIdx+1 == numObjptrs);
178+
elem->data.normal.objptrIdx++;
179+
return;
180+
}
181+
182+
// ======================== SEQUENCE OBJECTS ========================
183+
184+
if (SEQUENCE_TAG == tag) {
185+
GC_sequenceLength numCells = getSequenceLength(p);
186+
size_t bytesPerCell = bytesNonObjptrs + (numObjptrs * OBJPTR_SIZE);
187+
188+
size_t cellIdx = elem->data.sequence.cellIdx;
189+
uint16_t objptrIdx = elem->data.sequence.objptrIdx;
190+
assert(cellIdx < numCells);
191+
assert(objptrIdx < numObjptrs);
192+
193+
result->field =
194+
(objptr*)(
195+
p // object start
196+
+ (cellIdx * bytesPerCell) // cell offset
197+
+ bytesNonObjptrs // objptrs offset
198+
+ (objptrIdx * OBJPTR_SIZE) // current objptr offset
199+
);
200+
201+
result->objectDone = FALSE;
202+
203+
elem->data.sequence.objptrIdx++;
204+
if (objptrIdx+1 == numObjptrs) {
205+
elem->data.sequence.cellIdx++;
206+
elem->data.sequence.objptrIdx = 0;
207+
if (cellIdx+1 == numCells)
208+
result->objectDone = TRUE;
209+
}
210+
return;
211+
}
212+
213+
// ======================== STACK OBJECTS ========================
214+
215+
if (STACK_TAG == tag) {
216+
GC_stack stack = (GC_stack)p;
217+
pointer bottom = getStackBottom(s, stack);
218+
pointer top = elem->data.stack.topCursor;
219+
unsigned int i = elem->data.stack.frameOffsetsIdx;
220+
221+
// printf(
222+
// "advanceOneField: stack "FMTOBJPTR" top="FMTPTR" bottom="FMTPTR" i=%u\n",
223+
// (uintptr_t)elem->op,
224+
// (uintptr_t)top,
225+
// (uintptr_t)bottom,
226+
// i
227+
// );
228+
229+
/* Invariant: top points just past a "return address". */
230+
GC_returnAddress returnAddress =
231+
*((GC_returnAddress*)(top - GC_RETURNADDRESS_SIZE));
232+
GC_frameInfo frameInfo = getFrameInfoFromReturnAddress(s, returnAddress);
233+
// index zero of this array is size
234+
GC_frameOffsets frameOffsets = frameInfo->offsets;
235+
pointer frameStart = top - frameInfo->size;
236+
237+
assert(frameOffsets[0] > 0);
238+
assert(i < frameOffsets[0]);
239+
assert(frameStart >= bottom);
240+
241+
result->field = (objptr*)(frameStart + frameOffsets[i+1]);
242+
elem->data.stack.frameOffsetsIdx++;
243+
244+
result->objectDone = FALSE;
245+
246+
if (i+1 == frameOffsets[0]) {
247+
/** walk backwards until we find a frame that has at least one objptr
248+
* or, until we find the end of the stack.
249+
*/
250+
pointer newTop = findStackNonEmptyFrameTop(s, bottom, frameStart);
251+
252+
if (NULL == newTop) {
253+
result->objectDone = TRUE;
254+
} else {
255+
elem->data.stack.topCursor = newTop;
256+
elem->data.stack.frameOffsetsIdx = 0;
257+
}
258+
}
259+
260+
return;
261+
}
262+
263+
// ======================== OTHERWISE... ========================
264+
265+
DIE("advanceOneField: cannot handle tag %u", tag);
67266
return;
68267
}
69268

70269

71-
objptr CC_workList_pop(
270+
objptr* CC_workList_pop(
72271
GC_state s,
73272
CC_workList w)
74273
{
@@ -81,7 +280,7 @@ objptr CC_workList_pop(
81280
HM_chunk prevChunk = chunk->prevChunk;
82281
if (prevChunk == NULL) {
83282
// whole worklist is empty
84-
return BOGUS_OBJPTR;
283+
return NULL;
85284
}
86285

87286
/** Otherwise, there is a chunk before us. It's now safe (for cost
@@ -101,19 +300,25 @@ objptr CC_workList_pop(
101300
}
102301

103302
assert(w->currentChunk == chunk);
104-
assert(HM_getChunkFrontier(chunk) >= HM_getChunkStart(chunk) + sizeof(objptr));
303+
assert(HM_getChunkFrontier(chunk) >= HM_getChunkStart(chunk) + sizeof(struct CC_workList_elem));
105304

106305
pointer frontier = HM_getChunkFrontier(chunk);
107-
pointer newFrontier = frontier - sizeof(objptr);
306+
pointer elemPtr = frontier - sizeof(struct CC_workList_elem);
307+
CC_workList_elem elem = (CC_workList_elem)elemPtr;
108308

109-
HM_updateChunkFrontierInList(
110-
list,
111-
chunk,
112-
newFrontier);
309+
struct advanceOneFieldResult r;
310+
advanceOneField(s, elem, &r);
113311

114-
objptr result = *((objptr*)newFrontier);
312+
if (r.objectDone) {
313+
pointer newFrontier = elemPtr;
314+
HM_updateChunkFrontierInList(
315+
list,
316+
chunk,
317+
newFrontier);
318+
}
115319

116-
return result;
320+
assert(NULL != r.field);
321+
return r.field;
117322
}
118323

119324
#endif /* MLTON_GC_INTERNAL_FUNCS */

runtime/gc/cc-work-list.h

+28-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,34 @@ typedef struct CC_workList {
88
HM_chunk currentChunk;
99
} * CC_workList;
1010

11+
typedef struct CC_workList_elem {
12+
objptr op; // object front
13+
union data {
14+
struct normal {
15+
uint16_t objptrIdx;
16+
} normal;
17+
struct sequence {
18+
size_t cellIdx;
19+
uint16_t objptrIdx;
20+
} sequence;
21+
struct stack {
22+
pointer topCursor;
23+
unsigned int frameOffsetsIdx;
24+
} stack;
25+
} data;
26+
} * CC_workList_elem;
27+
1128
#else
1229

1330
struct CC_workList;
1431
typedef struct CC_workList * CC_workList;
1532

33+
struct CC_workList_elem;
34+
typedef struct CC_workList_elem * CC_workList_elem;
35+
36+
struct CC_workList_range;
37+
typedef struct CC_workList_range * CC_workList_range;
38+
1639
#endif /* MLTON_GC_INTERNAL_TYPES */
1740

1841
#if (defined (MLTON_GC_INTERNAL_FUNCS))
@@ -21,8 +44,11 @@ bool CC_workList_isEmpty(GC_state s, CC_workList w);
2144
void CC_workList_init(GC_state s, CC_workList w);
2245
void CC_workList_push(GC_state s, CC_workList w, objptr op);
2346

24-
// returns BOGUS_OBJPTR if empty
25-
objptr CC_workList_pop(GC_state s, CC_workList w);
47+
/** Returns a single field of an object that still needs to be traced.
48+
* So, note that a single push can result in many pops.
49+
*
50+
* Returns NULL if work list is empty */
51+
objptr* CC_workList_pop(GC_state s, CC_workList w);
2652

2753
#endif /* MLTON_GC_INTERNAL_FUNCS */
2854

0 commit comments

Comments
 (0)