quickthreads internals - modrpc/info GitHub Wiki

Table of Contents

QuickThreads primitives

QT_SP: Stack Top Finder

qt_t *QT_SP(void *sto,     // pointer to the user-allocated stack area
            int stacksize  // size of stack
);
  • Given a stack object (qt_t *), find the top of the stack.
  • Given that stack grows down (e.g. i386),
 +---------  <========  returns this stack top  (sto + stacksize)
 |
 |
 |  stk
 |
 |
 +---------  <===== sto (user allocated memory here)

QT_ARGS: Single-Arg Thread Creation

qt_t *QT-ARGS(qt_t *sp,           /* user-alloc stack top (i.e. QT_SP(sto, size)) */
              void *u,            /* user func arg */
              void *t,            /* ptr to thread-impl object */
              qt_userf_t *userf,  /* user func */
              qt_only_t *only     /* special func */
);
  • Arranges stack contents s.t. the stack data contains suspended state (i.e. saved regs)
  • Returns initialized stack pointer. Initial stack layout is given below
 +---------
 | arg[2]     === `userf' on startup
 | arg[1]     === `t' on startup
 | arg[0]     === `u' on startup
 +---------
 | ret pc     === qt_error
 +---------
 | ret pc     === `only' on startup
 +---------
 | %ebp
 | %esi
 | %edi
 | %ebx       <================================== qt_t.sp
 +---------

QT_VARGS: Variadic-Arg Thread Creation

qt_t *QT_VARGS(qt_t *sp, 
               int nbytes, 
               void *vargs, 
               void *pt, 
               qt_startup_t *startup, 
               qt_vuserf_t *vuserf,
               qt_cleanup_t *cleanup);
  • After initialization, initial stack layout is given below.
 +-----------
 | arg[n-1]
 | ..
 | arg[0]
 +-----------
 | ret pc     === `qt_vstart'
 +-----------
 | %ebp       === `startup'
 | %esi       === `cleanup'
 | %edi       === `pt'
 | %ebx       === `vuserf'  <================ qt_t.sp
 +-----------

QT_BLOCK, QT_ABORT

void *QT_BLOCK(qt_helper_t *helper, 
               void *arg0,         /* typically, ptr to old thread-impl (e.g. stp_t) */
               void *arg1,         /* some satellite data for yielding (e.g. runq) */
               qt_t *newthread     /* new thread stack */
);
  • Saves the state of the thread and call the helper function using the stack of the new thread. In detail,
    • (1) save the current (old) thread regs (push %ebp, %esi, %edi %ebx)
    • (2) remember current (old) stack pointer (movl %esp, %eax)
    • (3) move to the stack of the thread newthread (movl 32(%esp), %esp).
      • Note that in old thread stack, last frame is for call to qt_block(qt_helper_t *, void *a0, void *a1, qt_t *newthread)
      • That is 4th argument (i.e. 32(%esp)) in call frame is newthread
    • (4) Now we want to call the helper function; for this, push push 3 arguments to helper function
      • (a) qt_t *old_sp
      • (b) void *old (arg0)
      • (c) void *runq (arg1)
    • (3) call the helper function using the stack pointer of the old (blocked) thread AND the arguments arg0<code> and <code>arg1
    • (4) when helper returns, now the new thread is unblocked (with the new stack)
    • (5) restore previously-saved (for new) regs and then return (from QT_BLOCK!)
      • after all, QT_BLOCK caller is OLD THREAD but returns to the NEW THREAD

Before calling QT_BLOCK (by old thread)

+------------
| 
+-------------
| saved ebp    === caller save this before calling QT_BLOCK
|  arg3        === qt_t *newthread
|  arg2        === void *a1  (i.e. oldthread)
|  arg1        === void *a0  (i.e. runq)
|  arg0        === qt_helper_t*     <======== %esp before calling QT_BLOCK
+--------------

Right after CALL QT_BLOCK instruction is executed; before callee starts

+------------
| OLD STACK
+-------------+---------------------- <stack frame for QT_BLOCK call>
| saved ebp    === caller save this before calling QT_BLOCK
|  arg3        === qt_t *newthread
|  arg2        === void *a1  (i.e. oldthread)
|  arg1        === void *a0  (i.e. runq)
|  arg0        === qt_helper_t*     
| return eip                        <======== %esp right after CALL instr
+-------------+-------------------------------------

QT_BLOCK now begins execution (see i386.s qt_block code)

+------------
| OLD STACK
+-------------
| saved ebp    === caller save this before calling QT_BLOCK
|  arg3        === qt_t *newthread
|  arg2        === void *a1  (i.e. oldthread)
|  arg1        === void *a0  (i.e. runq)
|  arg0        === qt_helper_t*     
| return eip                        <======== %esp right after CALL instr
+-------------+-------------------------------------
|  <NOW, i386 qt_block execution begins> which doesn't necesaarily
|  follow C calling convention

Save registers (for OLD thread in OLD stack)

+------------
|  OLD STACK
+-------------
| saved ebp      === caller save this before calling QT_BLOCK
|  arg3      32  === qt_t *newthread
|  arg2      28  === void *a1  (i.e. oldthread)
|  arg1      24  === void *a0  (i.e. runq)
|  arg0      20  === qt_helper_t*     
| return eip 16                        
+-------------+-------------------------------------
|  ebp       12
|  esi        8
|  edi     sp+4
|  ebx          <============= %esp after pushing callee-save regs

Now switch to the new stack

NEW STACK should have similar stack layout since it would also have called QT_BLOCK previously

+------------
|  NEW STACK
+-------------o
| saved ebp      === caller save this before calling QT_BLOCK
|  arg3      32  === qt_t *newthread
|  arg2      28  === void *a1  (i.e. oldthread)
|  arg1      24  === void *a0  (i.e. runq)
|  arg0      20  === qt_helper_t*     
| return eip 16                        
+-------------+-------------------------------------
|  ebp       12
|  esi        8
|  edi     sp+4
|  ebx          <============= %esp after pushing callee-save regs 
                                (PREVIOUSLY during 'new thread blocking')

Now call helper function in the new stack

+------------
|  NEW STACK
+-------------o
| saved ebp      === caller save this before calling QT_BLOCK
|  arg3      32  === qt_t *newthread
|  arg2      28  === void *a1  (i.e. oldthread)
|  arg1      24  === void *a0  (i.e. runq)
|  arg0      20  === qt_helper_t*     
| return eip 16                        
+-------------+-------------------------------------
|  ebp       12
|  esi        8
|  edi     sp+4
|  ebx          <============= %esp after pushing callee-save regs 
|                              (PREVIOUSLY during 'new thread blocking')
+----------------------------------------------------
|  helper_arg2    === oldthread (a1)
|  helper_arg1    === runq (a0)
|  hepler_arg2    === qt_t *sp (esp of old thread saved in %eax)
|   ret eip
+---------------------------------<stack frame for worker>
|   

After return from helper (book keeping) function

Return to original QT_BLOCK call (called by NEW thread previously) and continue/resume from that point on

+------------
|  NEW STACK
+-------------
| saved ebp      === caller save this before calling QT_BLOCK
|  arg3      32  === qt_t *newthread
|  arg2      28  === void *a1  (i.e. oldthread)
|  arg1      24  === void *a0  (i.e. runq)
|  arg0      20  === qt_helper_t*     
| return eip 16                        
+-------------+-------------------------------------
⚠️ **GitHub.com Fallback** ⚠️