From: james.harris.1@nospicedham.gmail.com   
      
   On 07/01/2019 07:55, Rod Pemberton wrote:   
   > On Sun, 6 Jan 2019 16:59:38 +0000   
   > James Harris wrote:   
   >   
   >> That said, what I am writing is a hand translation of some HLL code   
   >> I've written, and all of this is sacrificial.   
   >   
   > If it's sacrificial code, why bother with finding the "best way"?   
      
   Partly because learnings from this experience can feed into other   
   projects. But also that pushing callee-saves before EBP rather than   
   after seemed so sensible I wondered if I was missing something.   
      
   >   
   > JH> My query, though, is over where and how it is best to add the   
   > JH> preservation of callee-save registers   
   >   
   > It would seem that the code only needs to work. It shouldn't have any   
   > need to be optimized. Should it? Why optimize something you're   
   > intending to throw away? Hence, I think your first working solution   
   > would've been sufficient.   
      
   Yes, this is not about optimisation for CPU time but about convenience -   
   optimisation for programmer time, if you like - mine!   
      
   >   
   > So, I'm clearly missing the point as to why this endeavor mattered at   
   > all now ... Was this just an intellectual exercise? Or, was this   
   > preparation for the future? etc.   
      
   OK. At the risk of boring you I can explain that. You may remember   
   suggesting to me a short while ago that I consider Nasm macros -   
   something I had generally avoided. Well, I took your advice and wrote   
   some macros to help with this project. You'll get the idea of what three   
   of the macros do from this example of how they might be used:   
      
    proc p, parm1, parm2, parm3   
    invoke q, arg1, arg2   
    endproc   
      
   Those three uses define a procedure, p, with three parameters, then   
   invoke another procedure, q, with two parameters, then end procedure p.   
      
   Since the performance of the generated code did not matter I wrote the   
   macros to be convenient rather than to generate fast code.   
      
   The proc macro set _._procname to the procedure name (p, in the example)   
   and began the generated code with the conventional   
      
    push ebp   
    mov ebp, esp   
      
   In endproc the procedure end code included   
      
    _._procname %+ ._finish   
    mov esp, ebp   
    pop ebp   
    ret   
      
   The finish label was intended for return statements, such that (and   
   remember that I am hand-translating HLL code) a statement like   
      
    return 1   
      
   could be effected with code which included   
      
    mov eax, 1   
    jmp _._procname %+ ._finish   
      
   I initially handled exceptions in a separate macro but eventually   
   decided it would be more convenient, less prone to error and easier to   
   work with to move exception handling into the invoke macro. To show how   
   that might work out in practice, here's code for procedure call with two   
   parameters.   
      
    push dword [parm1]   
    push dword [parm2]   
    call q   
    lea esp, [esp + 8]   
    cmp dword [___exception], 0   
    jne _._procname %+ ._finish   
    test eax, eax ;Set N and Z based on the return code   
      
   The caller pushes the args, calls the callee, removes the args it has   
   pushed (an approach which works whether the callee expects a fixed or a   
   variable number of arguments) then makes a sharp exit from the procedure   
   if an exception had occurred.   
      
   That's the background needed to answer your query and explain why I   
   asked the question in this thread - i.e. the problem with the above code   
   sequences is that, especially with exception handling, there's no point   
   at which callee-saves are restored. The issue, then, was how to ensure   
   they would be. The best answer seemed to be to push callee-saves before   
   EBP or, in fact, to treat EBP as just one of a number of callee saves.   
      
   Therefore procedures now begin with executable code such as   
      
   p:   
    push edi   
    push esi   
    push edx   
    push ecx   
    push ebx   
    push ebp   
    mov ebp, esp   
      
   and they end with   
      
   p._finish:   
    mov esp, ebp   
    pop ebp   
    pop ebx   
    pop ecx   
    pop edx   
    pop esi   
    pop edi   
    ret   
      
   The label p._finish can be jumped to from anywhere in the procedure,   
   including return statements and wherever exceptions are detected.   
      
   Usefully, a procedure can now include code which messes with ESP around   
   another procedure invocation such as   
      
    sub esp, 40   
    ...   
    mov eax, 1   
    jmp ._finish   
      
   or   
      
    push eax ;Save it across this call   
    invoke q, eax   
    pop eax   
      
   The epilogue code which restores ESP and then the callee-saves means   
   that arbitrary extra uses of the stack can be made in the procedure   
   without affecting its ability to return to its caller correctly.   
      
      
   --   
   James Harris   
      
   --- SoupGate-Win32 v1.05   
    * Origin: you cannot sedate... all the things you hate (1:229/2)   
|