суббота, 27 октября 2007 г.

SEH Dynamic Unwinding with auto correction

To achieve this objective, I have made some modifications.

I have added next fields to the TYieldObject class

InnerSEHCount:DWORD;
InnerSEHOffsets:array[0..$F] of DWORD;

And of course the code has been changed a little.

function TYieldObject.MoveNext: boolean;
asm
push ebp;
push ebx;
push edi;
push esi;
push eax;
xor edx,edx;
mov eax.TYieldObject.IsYield,dl;
push offset @a1
{
Is it first call
}
mov ecx,eax.TYieldObject.BESP;
cmp ecx,edx;
jnz @NotFirstCall;
mov eax.TYieldObject.BESP,esp;
jmp @JustBeforeTheJump;
@NotFirstCall:
cmp eax.TYieldObject.StackFrameSize,edx;
jz @RestoreRegisters;
{
is need any correction
}
mov edx,esp;
sub edx,ecx;
jz @RestoreStackFrame;
{
Correction
}
{
Correct ebp
}
add [eax.TYieldObject.REBP],edx;
{
Is any SEH frames
}
mov ecx,eax.TYieldObject.InnerSEHCount;
jecxz @ChangeBESP;
{
correct SEH frames
}
mov ebx,eax.TYieldObject.REBP;
lea esi,eax.TYieldObject.StackFrame;
add esi,eax.TYieldObject.StackFrameSize;
dec ecx;
mov edi,esi;
sub edi,DWORD PTR eax.TYieldObject.InnerSEHOffsets+4*ecx;
mov [edi+$08],ebx;
@SEHCorrection:
dec ecx;
jl @ChangeBESP
mov edi,esi;
sub edi,DWORD PTR eax.TYieldObject.InnerSEHOffsets+4*ecx;
mov [edi+$08],ebx;
add [edi],edx;
jmp @SEHCorrection;
{
Change BESP
}
@ChangeBESP:
mov eax.TYieldObject.BESP,esp;
{
Restore stack frame
}
@RestoreStackFrame:
mov ecx,eax.TYieldObject.StackFrameSize;
sub esp,ecx;
mov edi,esp;
lea esi,eax.TYieldObject.StackFrame;
rep movsb;
{
Connect Inner SEH frame. Are any inner SEH?
}
mov ecx,eax.TYieldObject.InnerSEHCount;
jecxz @RestoreRegisters;
{
Connect Inner SEH frame
}
xor ecx,ecx;
mov edi,eax.TYieldObject.BESP;
sub edi,DWORD PTR eax.TYieldObject.InnerSEHOffsets+4*ecx;
mov fs:[ecx],edi;
{
Restore Registers
}
@RestoreRegisters:
mov ebx,eax.TYieldObject.REBX;
mov ecx,eax.TYieldObject.RECX;
mov edx,eax.TYieldObject.REDX;
mov esi,eax.TYieldObject.RESI;
mov edi,eax.TYieldObject.REDI;
mov ebp,eax.TYieldObject.REBP;

@JustBeforeTheJump:
push [eax.TYieldObject.NextItemEntryPoint];
mov eax,eax.TYieldObject.REAX;
ret;
@a1:;
pop eax;
pop esi;
pop edi;
pop ebx;
pop ebp;
mov al,eax.TYieldObject.IsYield;
end;

procedure TYieldObject.Yield(const Value);
asm
mov eax.TYieldObject.REBP,ebp;
mov eax.TYieldObject.REAX,eax;
mov eax.TYieldObject.REBX,ebx;
mov eax.TYieldObject.RECX,ecx;
mov eax.TYieldObject.REDX,edx; // This is the Ref to const param
mov eax.TYieldObject.RESI,ESI;
mov eax.TYieldObject.REDI,EDI;
pop ecx;
mov eax.TYieldObject.NextItemEntryPoint,ecx;
//We must do it first for valid const reference
push eax;
mov ecx,[eax];
CALL DWORD PTR [ecx+VMTOFFSET TYieldObject.SaveYieldedValue];
pop eax;
{
Unwind SEH
// There is no need ebx,esi,edi to retain
}
xor ebx,ebx;
mov ecx,fs:[ebx];
@SEHUnwind:
jecxz @JustAfterSEHUnwind;
cmp ecx,eax.TYieldObject.BESP;
jnl @JustAfterSEHUnwind
mov esi,eax.TYieldObject.BESP;
sub esi,ecx;
mov DWORD PTR eax.TYieldObject.InnerSEHOffsets+4*ebx,esi;
inc ebx;
mov ecx,[ecx];
jmp @SEHUnwind;
@JustAfterSEHUnwind:
mov eax.TYieldObject.InnerSEHCount,ebx;
{
Connect Outer SEH frame.
If no local SEH frames next two commands are redundant
}
xor ebx,ebx;
mov fs:[ebx],ecx;
{
Save local stack frame
}
mov ecx,eax.TYieldObject.BESP;
sub ecx,esp;
mov eax.TYieldObject.StackFrameSize,ecx;
jz @AfterSaveStack;
lea esi,[esp];
lea edi,[eax.TYieldObject.StackFrame];
rep movsb;
mov esp,eax.TYieldObject.BESP;
@AfterSaveStack:
mov eax.TYieldObject.IsYield,1;
end;

So for now you may use try/finally and try/except construction in procedure that uses yielding.
Next article will be about additional improvements.
Now I am thinking about separate stack.

1 комментарий:

Sébastien Doeraene комментирует...

Hi!

(Sorry for my English: I am French-speaking)

I was very impressed by this piece of code. It is really interresting. And I havec worked a bit on the subjet since then.

On Hallvard's blog, someone posted a link to a coroutine system using a separate stack.

Indeed, it is much easier to handle exceptions working this way.

However, Bart van der Werf's code was a bit redundant and difficult to read.

I have rewritten it the better I could, and added the TCoroutineEnumerator class the way you've defined it (but using TCoroutine).

You can download my work via FTP or via HTTP. The original unit is ScCoroutines.pas, but most comments are in French. You can find a English version in CoroutinesEn.pas - but my English is certainly not perfect. The TestCorout.pas unit provides some tests of the two classes.

What do you think of it? Maybe there are still ways of improving it. e.g. provide stacks smaller than 64 Kb.