about summary refs log tree commit diff
path: root/src/comp/back
diff options
context:
space:
mode:
authorGraydon Hoare <graydon@mozilla.com>2011-02-08 18:09:50 -0800
committerGraydon Hoare <graydon@mozilla.com>2011-02-08 18:09:50 -0800
commit467a628ffaf41844a90e6664c2ecd481eef1dc85 (patch)
tree1bd0dfdc6476de1b4ac6de2706c3e310ef5235bd /src/comp/back
parent2f25d9c983c5730d79cc14278e666b8eb6531b10 (diff)
downloadrust-467a628ffaf41844a90e6664c2ecd481eef1dc85.tar.gz
rust-467a628ffaf41844a90e6664c2ecd481eef1dc85.zip
Add the single instruction required in activate glue to fix burning darwin tinderbox. And transplant 100 lines of comments from the ML code.
Diffstat (limited to 'src/comp/back')
-rw-r--r--src/comp/back/x86.rs101
1 files changed, 99 insertions, 2 deletions
diff --git a/src/comp/back/x86.rs b/src/comp/back/x86.rs
index ac52fca5baf..907cb32e3fe 100644
--- a/src/comp/back/x86.rs
+++ b/src/comp/back/x86.rs
@@ -41,20 +41,117 @@ fn store_esp_to_runtime_sp() -> vec[str] {
     ret vec("movl  %esp, " + wstr(abi.task_field_runtime_sp) + "(%ecx)");
 }
 
+/*
+ * This is a bit of glue-code. It should be emitted once per
+ * compilation unit.
+ *
+ *   - save regs on C stack
+ *   - align sp on a 16-byte boundary
+ *   - save sp to task.runtime_sp (runtime_sp is thus always aligned)
+ *   - load saved task sp (switch stack)
+ *   - restore saved task regs
+ *   - return to saved task pc
+ *
+ * Our incoming stack looks like this:
+ *
+ *   *esp+4        = [arg1   ] = task ptr
+ *   *esp          = [retpc  ]
+ */
+
 fn rust_activate_glue() -> vec[str] {
     ret vec("movl  4(%esp), %ecx    # ecx = rust_task")
         + save_callee_saves()
         + store_esp_to_runtime_sp()
         + load_esp_from_rust_sp()
 
-        // This 'add' instruction is a bit surprising.
-        // See lengthy comment in boot/be/x86.ml activate_glue.
+        /*
+         * There are two paths we can arrive at this code from:
+         *
+         *
+         *   1. We are activating a task for the first time. When we switch
+         *      into the task stack and 'ret' to its first instruction, we'll
+         *      start doing whatever the first instruction says. Probably
+         *      saving registers and starting to establish a frame. Harmless
+         *      stuff, doesn't look at task->rust_sp again except when it
+         *      clobbers it during a later upcall.
+         *
+         *
+         *   2. We are resuming a task that was descheduled by the yield glue
+         *      below.  When we switch into the task stack and 'ret', we'll be
+         *      ret'ing to a very particular instruction:
+         *
+         *              "esp <- task->rust_sp"
+         *
+         *      this is the first instruction we 'ret' to after this glue,
+         *      because it is the first instruction following *any* upcall,
+         *      and the task we are activating was descheduled mid-upcall.
+         *
+         *      Unfortunately for us, we have already restored esp from
+         *      task->rust_sp and are about to eat the 5 words off the top of
+         *      it.
+         *
+         *
+         *      | ...    | <-- where esp will be once we restore + ret, below,
+         *      | retpc  |     and where we'd *like* task->rust_sp to wind up.
+         *      | ebp    |
+         *      | edi    |
+         *      | esi    |
+         *      | ebx    | <-- current task->rust_sp == current esp
+         *
+         * 
+         *      This is a problem. If we return to "esp <- task->rust_sp" it
+         *      will push esp back down by 5 words. This manifests as a rust
+         *      stack that grows by 5 words on each yield/reactivate. Not
+         *      good.
+         * 
+         *      So what we do here is just adjust task->rust_sp up 5 words as
+         *      well, to mirror the movement in esp we're about to
+         *      perform. That way the "esp <- task->rust_sp" we 'ret' to below
+         *      will be a no-op. Esp won't move, and the task's stack won't
+         *      grow.
+         */
         + vec("addl  $20, " + wstr(abi.task_field_rust_sp) + "(%ecx)")
 
+
+        /*
+         * In most cases, the function we're returning to (activating)
+         * will have saved any caller-saves before it yielded via upcalling,
+         * so no work to do here. With one exception: when we're initially
+         * activating, the task needs to be in the fastcall 2nd parameter
+         * expected by the rust main function. That's edx.
+         */
+        + vec("mov  %ecx, %edx")
+
         + restore_callee_saves()
         + vec("ret");
 }
 
+/* More glue code, this time the 'bottom half' of yielding.
+ *
+ * We arrived here because an upcall decided to deschedule the
+ * running task. So the upcall's return address got patched to the
+ * first instruction of this glue code.
+ *
+ * When the upcall does 'ret' it will come here, and its esp will be
+ * pointing to the last argument pushed on the C stack before making
+ * the upcall: the 0th argument to the upcall, which is always the
+ * task ptr performing the upcall. That's where we take over.
+ *
+ * Our goal is to complete the descheduling
+ *
+ *   - Switch over to the task stack temporarily.
+ *
+ *   - Save the task's callee-saves onto the task stack.
+ *     (the task is now 'descheduled', safe to set aside)
+ *
+ *   - Switch *back* to the C stack.
+ *
+ *   - Restore the C-stack callee-saves.
+ *
+ *   - Return to the caller on the C stack that activated the task.
+ *
+ */
+
 fn rust_yield_glue() -> vec[str] {
     ret vec("movl  0(%esp), %ecx    # ecx = rust_task")
         + load_esp_from_rust_sp()