Skip to content

Commit 467a628

Browse files
committed
Add the single instruction required in activate glue to fix burning darwin tinderbox. And transplant 100 lines of comments from the ML code.
1 parent 2f25d9c commit 467a628

File tree

1 file changed

+99
-2
lines changed

1 file changed

+99
-2
lines changed

src/comp/back/x86.rs

+99-2
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,117 @@ fn store_esp_to_runtime_sp() -> vec[str] {
4141
ret vec("movl %esp, " + wstr(abi.task_field_runtime_sp) + "(%ecx)");
4242
}
4343

44+
/*
45+
* This is a bit of glue-code. It should be emitted once per
46+
* compilation unit.
47+
*
48+
* - save regs on C stack
49+
* - align sp on a 16-byte boundary
50+
* - save sp to task.runtime_sp (runtime_sp is thus always aligned)
51+
* - load saved task sp (switch stack)
52+
* - restore saved task regs
53+
* - return to saved task pc
54+
*
55+
* Our incoming stack looks like this:
56+
*
57+
* *esp+4 = [arg1 ] = task ptr
58+
* *esp = [retpc ]
59+
*/
60+
4461
fn rust_activate_glue() -> vec[str] {
4562
ret vec("movl 4(%esp), %ecx # ecx = rust_task")
4663
+ save_callee_saves()
4764
+ store_esp_to_runtime_sp()
4865
+ load_esp_from_rust_sp()
4966

50-
// This 'add' instruction is a bit surprising.
51-
// See lengthy comment in boot/be/x86.ml activate_glue.
67+
/*
68+
* There are two paths we can arrive at this code from:
69+
*
70+
*
71+
* 1. We are activating a task for the first time. When we switch
72+
* into the task stack and 'ret' to its first instruction, we'll
73+
* start doing whatever the first instruction says. Probably
74+
* saving registers and starting to establish a frame. Harmless
75+
* stuff, doesn't look at task->rust_sp again except when it
76+
* clobbers it during a later upcall.
77+
*
78+
*
79+
* 2. We are resuming a task that was descheduled by the yield glue
80+
* below. When we switch into the task stack and 'ret', we'll be
81+
* ret'ing to a very particular instruction:
82+
*
83+
* "esp <- task->rust_sp"
84+
*
85+
* this is the first instruction we 'ret' to after this glue,
86+
* because it is the first instruction following *any* upcall,
87+
* and the task we are activating was descheduled mid-upcall.
88+
*
89+
* Unfortunately for us, we have already restored esp from
90+
* task->rust_sp and are about to eat the 5 words off the top of
91+
* it.
92+
*
93+
*
94+
* | ... | <-- where esp will be once we restore + ret, below,
95+
* | retpc | and where we'd *like* task->rust_sp to wind up.
96+
* | ebp |
97+
* | edi |
98+
* | esi |
99+
* | ebx | <-- current task->rust_sp == current esp
100+
*
101+
*
102+
* This is a problem. If we return to "esp <- task->rust_sp" it
103+
* will push esp back down by 5 words. This manifests as a rust
104+
* stack that grows by 5 words on each yield/reactivate. Not
105+
* good.
106+
*
107+
* So what we do here is just adjust task->rust_sp up 5 words as
108+
* well, to mirror the movement in esp we're about to
109+
* perform. That way the "esp <- task->rust_sp" we 'ret' to below
110+
* will be a no-op. Esp won't move, and the task's stack won't
111+
* grow.
112+
*/
52113
+ vec("addl $20, " + wstr(abi.task_field_rust_sp) + "(%ecx)")
53114

115+
116+
/*
117+
* In most cases, the function we're returning to (activating)
118+
* will have saved any caller-saves before it yielded via upcalling,
119+
* so no work to do here. With one exception: when we're initially
120+
* activating, the task needs to be in the fastcall 2nd parameter
121+
* expected by the rust main function. That's edx.
122+
*/
123+
+ vec("mov %ecx, %edx")
124+
54125
+ restore_callee_saves()
55126
+ vec("ret");
56127
}
57128

129+
/* More glue code, this time the 'bottom half' of yielding.
130+
*
131+
* We arrived here because an upcall decided to deschedule the
132+
* running task. So the upcall's return address got patched to the
133+
* first instruction of this glue code.
134+
*
135+
* When the upcall does 'ret' it will come here, and its esp will be
136+
* pointing to the last argument pushed on the C stack before making
137+
* the upcall: the 0th argument to the upcall, which is always the
138+
* task ptr performing the upcall. That's where we take over.
139+
*
140+
* Our goal is to complete the descheduling
141+
*
142+
* - Switch over to the task stack temporarily.
143+
*
144+
* - Save the task's callee-saves onto the task stack.
145+
* (the task is now 'descheduled', safe to set aside)
146+
*
147+
* - Switch *back* to the C stack.
148+
*
149+
* - Restore the C-stack callee-saves.
150+
*
151+
* - Return to the caller on the C stack that activated the task.
152+
*
153+
*/
154+
58155
fn rust_yield_glue() -> vec[str] {
59156
ret vec("movl 0(%esp), %ecx # ecx = rust_task")
60157
+ load_esp_from_rust_sp()

0 commit comments

Comments
 (0)