source: Microthreading_/Coroutine.pas@ 138

Last change on this file since 138 was 133, checked in by george, 13 years ago
  • Added: Package Microthreading.
File size: 7.0 KB
Line 
1unit Coroutine;
2
3{$mode Delphi}{$H+}
4
5{
6
7Author: Bart van der Werf
8Date: 19 Feb 2006
9Summary:
10
11Quick and dirty coroutine for delphi.
12
13Limitations:
14
15Does not support continuation invoke chains that include the same instance twice.
16Uses the assumption that pages are 4kb in size
17Uses the assumtion that delphi can build its own SEH chain from scratch.
18Limited to 32k instances because of the 64kb minimum stacksize. (or 48k if 3gb mode is used)
19}
20
21{$asmmode intel}
22
23interface
24
25uses
26 SysUtils, SyncObjs, Classes;
27
28type
29{
30Coroutine support class
31
32Note: recursive calling the same instance of the coroutine not supported
33Note: not threadsafe
34Note: no thread affinity
35}
36 TCoroutine = class
37 private
38 FStackBuffer: Pointer;
39 FStackSize: Cardinal;
40
41 FInCoroutine: Boolean;
42 FActive: Boolean;
43 FTerminating: Boolean;
44
45 FStackBase: Pointer;
46 FStackLimit: Pointer;
47 FSEH: Pointer;
48 FStack: Pointer;
49
50 FCurrentSEH: Pointer;
51 FCurrentStack: Pointer;
52 FCurrentInstruction: Pointer;
53
54 FCallerSEH: Pointer;
55 FCallerStackBase: Pointer;
56 FCallerStackLimit: Pointer;
57 FCallerStack: Pointer;
58 FCallerInstruction: Pointer;
59
60 FExceptionRaised: Exception;
61
62 procedure Setup;
63 procedure Reset;
64 procedure BackToCaller;
65 procedure Enter;
66 protected
67 procedure Yield; //call me from the Execute method to return the thread to the call of Invoke, unless IsTerminating is true then an exception is raised
68 function IsTerminating: Boolean; //signal to Execute that it should not call Yield and that it should cleanup its resources and return from the method
69
70 procedure Execute; virtual; abstract; // override me, either return from this method or call yield
71 public
72 constructor Create(const StackSize: Cardinal = $10000);
73 {
74 Destruction:
75 If the Coroutine is currently active then:
76 IsTerminating is set to true
77 Then Invoke is called and we want the Execute method to return
78 Yield throws an exception to enforce this.
79 }
80
81 destructor Destroy; override;
82
83 procedure Invoke; //call me to run/continue the Execute method
84end;
85
86
87implementation
88
89{ TCoroutine }
90
91function GetCurrentAddress: Pointer; assembler; nostackframe;
92asm
93 mov eax, [esp]
94 ret
95end;
96
97(*procedure FpuInit;
98const
99 Default8087CW: Word = $1332 { $133F};
100asm
101 FNINIT
102 FWAIT
103 FLDCW Default8087CW
104end;*)
105
106procedure TCoroutine.Enter;
107var
108 Me: TCoroutine;
109begin
110 Me := Self;
111 asm
112 pushad
113 cld
114 //call FpuInit
115 mov eax, Me
116 (*mov ecx, 0
117 mov edx, fs:[ecx]
118 mov [EAX].TCoroutine.FCallerSEH, edx
119 mov ecx, 4
120 mov edx, fs:[ecx]
121 mov [EAX].TCoroutine.FCallerStackBase, edx
122 mov ecx, 8
123 mov edx, fs:[ecx]
124 mov [EAX].TCoroutine.FCallerStackLimit, edx
125 *)
126 mov edx, esp
127 mov [EAX].TCoroutine.FCallerStack, edx
128 mov edx, offset @A
129 mov [EAX].TCoroutine.FCallerInstruction, edx
130
131
132 (*mov ecx, 0
133 mov edx, [EAX].TCoroutine.FCurrentSEH
134 mov fs:[ecx], edx
135 mov ecx, 4
136 mov edx, [EAX].TCoroutine.FStackBase
137 mov fs:[ecx], edx
138 mov ecx, 8
139 mov edx, [EAX].TCoroutine.FStackLimit
140 mov fs:[ecx], edx
141 *)
142 mov edx, [EAX].TCoroutine.FCurrentStack
143 mov esp, edx
144 mov edx, [EAX].TCoroutine.FCurrentInstruction
145 push edx
146 ret
147 @A:
148 popad
149 end;
150end;
151
152procedure TCoroutine.Setup;
153begin
154 try
155 Execute;
156 except
157 on e: Exception do
158 begin
159 FExceptionRaised := Exception(e.ClassType.NewInstance);
160 FExceptionRaised.Message := e.Message;
161 FExceptionRaised.HelpContext := e.HelpContext;
162 end;
163 end;
164 Reset;
165 BackToCaller;
166end;
167
168constructor TCoroutine.Create(const StackSize: Cardinal = $10000);
169begin
170 Assert(StackSize >= $10000, 'A stack is atleast 64kb large');
171 Assert((StackSize and $ffff) = 0, 'Use a multiple of 64kb');
172
173 FInCoroutine := False;
174
175 FStackSize := StackSize;
176 //FStackBuffer := VirtualAlloc(nil, FStackSize, MEM_RESERVE, PAGE_READWRITE);
177 FStackBuffer := GetMem(FStackSize);
178 if not Assigned(FStackBuffer) then
179 //RaiseLastWin32Error
180 ;
181
182 FStackLimit := FStackBuffer;
183 FStackBase := Pointer(Cardinal(FStackBuffer) + FStackSize);
184 FStack := FStackBase;
185
186 //if not Assigned(VirtualAlloc(Pointer(Cardinal(FStackBase) - 4096), 4096, MEM_COMMIT, PAGE_READWRITE)) then
187 // RaiseLastWin32Error;
188 //if not Assigned(VirtualAlloc(Pointer(Cardinal(FStackBase) - 2 * 4096), 4096, MEM_COMMIT, PAGE_READWRITE + PAGE_GUARD)) then
189 // RaiseLastWin32Error;
190
191 FSEH := nil;
192
193 Reset;
194end;
195
196destructor TCoroutine.Destroy;
197begin
198 Assert(not FInCoroutine);
199 if FActive then
200 begin
201 FTerminating := True;
202 FInCoroutine := True;
203 Enter;
204 FInCoroutine := False;
205 Assert(not FActive);
206 end;
207 if Assigned(FStackBuffer) then
208 FreeMem(FStackBuffer);
209 //if not VirtualFree(FStackBuffer, 0, MEM_RELEASE) then
210 //RaiseLastWin32Error;
211end;
212
213procedure TCoroutine.Invoke;
214var
215 E: Exception;
216begin
217 Assert(Assigned(Self));
218 Assert(not FInCoroutine);
219
220 FActive := True;
221 FInCoroutine := True;
222 Enter;
223 FInCoroutine := False;
224
225 if Assigned(FExceptionRaised) then
226 begin
227 if FExceptionRaised.ClassName = 'EStackOverflow' then
228 //if not Assigned(VirtualAlloc(FStackBuffer, 4096, MEM_COMMIT, PAGE_READWRITE + PAGE_GUARD)) then
229 //RaiseLastWin32Error
230 ;
231 E := FExceptionRaised;
232 FExceptionRaised := nil;
233 raise E;
234 end;
235end;
236
237procedure TCoroutine.Reset;
238type
239 TBlip = packed record
240 case Blap: Boolean of
241 True: (A: TThreadMethod;);
242 False: (B, C: Pointer;);
243 end;
244var
245 FProc: TBlip;
246begin
247 FProc.A := Setup;
248 FCurrentInstruction := FProc.B;
249 FCurrentSEH := FSEH;
250 FCurrentStack := FStack;
251 FActive := False;
252end;
253
254procedure TCoroutine.BackToCaller;
255var
256 Me: TCoroutine;
257begin
258 Me := Self;
259 asm
260 mov eax, Me
261 cld
262 //call FpuInit
263 (*mov ecx, 0
264 mov edx, [EAX].TCoroutine.FCallerSEH
265 mov fs:[ecx], edx
266 mov ecx, 4
267 mov edx, [EAX].TCoroutine.FCallerStackBase
268 mov fs:[ecx], edx
269 mov ecx, 8
270 mov edx, [EAX].TCoroutine.FCallerStackLimit
271 mov fs:[ecx], edx
272 *)
273 mov edx, [EAX].TCoroutine.FCallerStack
274 mov esp, edx
275 mov edx, [EAX].TCoroutine.FCallerInstruction
276 push edx
277 ret
278 end;
279end;
280
281procedure TCoroutine.Yield;
282var
283 Me: TCoroutine;
284begin
285 Assert(FInCoroutine);
286 if FTerminating then
287 raise Exception.Create('Cannot yield, terminating');
288
289 Me := Self;
290 asm
291 pushad
292 cld
293 //call FpuInit
294 mov eax, Me
295(* mov ecx, 0
296 mov edx, fs:[ecx]
297 mov [EAX].TCoroutine.FCurrentSEH, edx
298 *)
299 mov edx, esp
300 mov [EAX].TCoroutine.FCurrentStack, edx
301 mov edx, offset @A
302 mov [EAX].TCoroutine.FCurrentInstruction, edx
303
304 (*mov ecx, 0
305 mov edx, [EAX].TCoroutine.FCallerSEH
306 mov fs:[ecx], edx
307 mov ecx, 4
308 mov edx, [EAX].TCoroutine.FCallerStackBase
309 mov fs:[ecx], edx
310 mov ecx, 8
311 mov edx, [EAX].TCoroutine.FCallerStackLimit
312 mov fs:[ecx], edx
313 *)
314 mov edx, [EAX].TCoroutine.FCallerStack
315 mov esp, edx
316 mov edx, [EAX].TCoroutine.FCallerInstruction
317 push edx
318 ret
319 @A:
320 popad
321 end;
322end;
323
324function TCoroutine.IsTerminating: Boolean;
325begin
326 Result := FTerminating;
327end;
328
329end.
Note: See TracBrowser for help on using the repository browser.