Tech Off Post

Single Post Permalink

View Thread: IL to C++ compiler - Need advice on implementing context switching
  • User profile image

    Interestingly, the compiler gives me this warning:

    I'll answer this first because the rest can't be done properly with this in the way. The compiler can generate prolog/epilog code for a function:

                   push ebp         ; save the previous frame pointer
                   mov  ebp, esp    ; setup the frame pointer
                   sub  esp, 8      ; space for local variables
                   ...              ; function code
                   mov  esp, ebp    ; restore the stack pointer
                   pop  ebp         ; restore the frame pointer

    Now it should be obvious what's the warning about. The compiler uses ebp for its own purposes and warns you if you change it in inline asm. This compiler generated code can be a problem when you want to implement context switching because it affects the stack layout. You can implement SwitchContext even if such code is present but creating the stack for a new thread will be a problem because you don't know what stack layout SwitchContext expects.

    A possible solution is to use __declspec(naked) (which prevents such code from being generated) and __fastcall (which causes the first 2 arguments of the function to be passed in registers ecx and edx).

    I'm a bit rusty on my x86 assembly, but what I don't understand is that the code is moving oldStack into eax, then a few lines later, esp is moved into eax. Is my translation of the original code even correct?

    Nope. In the original version there were some parathesis which you ignored. Something like mov %esp, (%eax) converts to mov [eax], esp.

    Here's an example that uses __declspec(naked) and __fastcall):

    __declspec(naked) void __fastcall SwitchContext(VirtualThread* pOld, VirtualThread* pNew)
        // N.B. the following code assumes that NativeStack is at offset 4 in VirtualThread. If that's not true
        // then the appropiate offset needs to be used when storing/loading the stack.
            // Save registers
            push ebp
            push ebx
            push esi
            push edi
            // Save old stack
            mov [ecx+4], esp    // store to pOld->NativeStack
            // Load new stack
            mov esp, [edx+4]    // load from pNew->NativeStack
            // Restore registers
            pop edi
            pop esi
            pop ebx
            pop ebp

    So what I'm missing right now is a way to set up these stacks for the initial run

    To create a new thread you have to allocate memory for its native stack and setup the stack exactly the same as in SwitchContext does. Once the thread stack is properly initialized you can simply call SwitchContext to start the new thread.

    In addition, my assumption here is that my VirtualThread class will not only hold the "virtual stack", but also this native stack that will be used to do context switching for each thread.

    Yes, if you create thread you'll need to also allocate the native stacks yourself.

    void InitializeContext(VirtualThread* pNew, void *startFunction)
        int stackSize = 4096;
        char *stack = reinterpret_cast<char *>(malloc(stackSize)); // allocate some space, malloc is just an example
        stack += stackSize; // go to the top of the stack because the stack grows down
            mov ecx, esp // save current stack pointer
            mov esp, stack

            mov eax, startFunction
            push eax    // push the start function address as the return address (of SwitchContext)

            // Save registers
            xor eax, eax // let's always start a thread with zeroed registers
            push eax
            push eax
            push eax
            push eax

            mov stack, esp
            mov esp, ecx // restore the stack pointer
        pNew->NativeStack = stack;