-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdtinygc.pas
246 lines (218 loc) · 5.83 KB
/
dtinygc.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
unit dtinygc;
// tiny garbage collector (<200 LOCs). Genius code tricks by @orangeduck (see: https://github.com/orangeduck/tgc README)
// - rlyeh, public domain.
// - ported to pascal by Doj
{$MODE FPC}
{$MODESWITCH DEFAULTPARAMETERS}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}
interface
procedure gc_init(argc: Pointer; InitialMBytesReserved: SizeUInt); // pointer to argc (from main), and initial MiB reserved
procedure gc_run; // mark & sweep
procedure gc_stop; // sweep
function gc_malloc(Sz: SizeUInt): Pointer; // allocator
function gc_strdup(S: PAnsiChar): PAnsiChar; // util
implementation
function GC_REALLOC(P: Pointer; S: SizeUInt): Pointer;
begin
Exit(ReAllocMem(P, S));
end;
var
gc_inuse: array of Pointer; // TODO use some set structure for better perf
gc_inuse_count: PtrUInt = 0;
gc_spawned: array of Pointer;
gc_top: Pointer = nil;
gc_min: Pointer = nil;
gc_max: Pointer = Pointer(High(PtrUInt));
function gc_is_inuse(P: Pointer): Boolean; inline;
var
i: PtrUInt;
begin
i := 0;
while i < gc_inuse_count do begin
if gc_inuse[i] = P then
Exit(True);
Inc(i);
end;
Exit(False);
end;
procedure gc_add_inuse(P: Pointer); inline;
begin
if Length(gc_inuse) = 0 then
SetLength(gc_inuse, 1024);
if gc_inuse_count >= Length(gc_inuse) then
SetLength(gc_inuse, 2 * Length(gc_inuse));
gc_inuse[gc_inuse_count - 1] := P;
Inc(gc_inuse_count);
end;
procedure gc_clear_inuse; inline;
begin
SetLength(gc_inuse, 0);
gc_inuse_count := 0;
end;
procedure gc_mark_stack;
label
LContinue;
var
bot, top, last, p, e, ptr: Pointer;
begin
bot := gc_top;
top := @bot;
last := nil;
if bot < top then begin
p := bot;
e := top;
end else begin
p := top;
e := bot;
end;
while p < e do begin
ptr := Pointer(p^);
if ptr = last then goto LContinue; // already checked
if ptr < gc_min then goto LContinue; // out of gc_spawned bounds. also, nullptr check included here
if ptr > gc_max then goto LContinue; // out of gc_spawned bounds.
{$IF Defined(CPU64)}
if PtrUInt(ptr) and $7 <> 0 then goto LContinue; // 64-bit unaligned (not a pointer).
{$ENDIF}
last := ptr;
gc_add_inuse(last);
LContinue:
Inc(p, SizeOf(Pointer));
end;
end;
procedure gc_mark; // mark reachable stack pointers
var
env: jmp_buf;
check: procedure;
begin
check := @gc_mark_stack;
SetJmp(env);
check;
end;
procedure gc_sweep(); // sweep unreachable stack pointers
var
back, i: SizeInt;
ptr, swap: Pointer;
used: Boolean;
collected: SizeUInt;
begin
gc_min := Pointer(High(PtrUInt));
gc_max := nil;
back := Length(gc_spawned);
i := 0;
while i < back do begin
ptr := gc_spawned[i];
if ptr > gc_max then gc_max := ptr;
if ptr < gc_min then gc_min := ptr;
used := gc_is_inuse(ptr);
if not used then begin
GC_REALLOC(gc_spawned[i], 0); //free
Dec(back);
swap := gc_spawned[back]; // vector erase
gc_spawned[back] := gc_spawned[i];
gc_spawned[i] := swap;
end else
Inc(i);
end;
collected := Length(gc_spawned) - back;
//if collected > 0 then
// Writeln('gc: ', collected, ' objects collected');
SetLength(gc_spawned, back);
gc_clear_inuse;
end;
procedure gc_init(argc: Pointer; InitialMBytesReserved: SizeUInt); // pointer to argc (from main), and initial MiB reserved
begin
gc_top := argc;
// gc_spawned.reserve((InitialMBytesReserved > 0) * InitialMBytesReserved * 1024 * 1024 / SizeOf(Pointer));
end;
procedure gc_run;
begin
gc_mark;
gc_sweep;
end;
procedure gc_stop;
begin
gc_sweep;
end;
function gc_malloc(Sz: SizeUInt): Pointer;
begin
Result := GC_REALLOC(nil, Sz); // malloc
if Result <> nil then begin
SetLength(gc_spawned, Length(gc_spawned) + 1);
gc_spawned[High(gc_spawned)] := Result;
end;
end;
function gc_strdup(S: PAnsiChar): PAnsiChar;
var
bytes: SizeInt;
begin
bytes := StrLen(S) + 1;
Result := gc_malloc(bytes);
Move(s^, Result^, bytes);
end;
// ----------------------------------------------------------------------------
//
// procedure demo;
// var
// memory: Pointer;
// s: PAnsiChar;
// x: PAnsiChar;
// begin
// memory := gc_malloc(1024); // will be collected
// s := gc_strdup('hello world'); // will be collected
// x := gc_strdup('Hi'); // will be collected.
// Byte(x[0]) := Ord(x[0]) or 32; // note: indexing is ok; pointer arithmetic is forbidden.
// gc_run();
//
// Writeln(HexStr(PtrUInt(memory), 2 * SizeOf(PtrUInt)));
// Writeln(s);
// Writeln(x);
// gc_run();
// end;
//
// procedure Main;
// var
// StackTop: Pointer;
// begin
// gc_init(@StackTop, 256);
//
// demo;
//
// gc_stop;
// end;
// ----------------------------------------------------------------------------
//
// procedure bench;
// const
// FRAMES = 30;
// COUNT = 1000000;
// var
// beg: TDateTimer;
// baseline, gctime: Double;
// Frame, n: LongInt;
// begin
// beg := SysUtils.Now;
// Write(Trunc(FRAMES * COUNT * 2 / 1000000.0), 'M allocs+frees (baseline; regular malloc)');
// for frame := 0 to FRAMES - 1 do begin
// for n := 0 to COUNT - 1 do begin
// FreeMem(GetMem(16));
// end;
// end;
// baseline := DateUtils.MilliSecondsBetween(Timer, SysUtils.Now) / 1000.0;
// Writeln(' ', baseline:5:2, 's');
//
// beg := SysUtils.Now;
// Write(Trunc(FRAMES * COUNT * 2 / 1000000.0), 'M allocs+frees (gc)');
// for frame := 0 to FRAMES - 1 do begin
// for n := 0 to COUNT - 1 do begin
// gc_malloc(16);
// end;
// end;
// gc_run();
// gctime := DateUtils.MilliSecondsBetween(Timer, SysUtils.Now) / 1000.0;
// Writeln(' ', gctime:5:2, 's');
//
// if baseline <= gctime then Writeln('gc is x', gctime/baseline:0:2, ' times slower')
// else if baseline > gctime then Writeln('gc is x', baseline/gctime:0:2, ' times faster!');
// end;
end.