@@ -41,20 +41,117 @@ fn store_esp_to_runtime_sp() -> vec[str] {
41
41
ret vec ( "movl %esp, " + wstr ( abi. task_field_runtime_sp ) + "(%ecx)" ) ;
42
42
}
43
43
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
+
44
61
fn rust_activate_glue ( ) -> vec[ str ] {
45
62
ret vec ( "movl 4(%esp), %ecx # ecx = rust_task" )
46
63
+ save_callee_saves ( )
47
64
+ store_esp_to_runtime_sp ( )
48
65
+ load_esp_from_rust_sp ( )
49
66
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
+ */
52
113
+ vec ( "addl $20, " + wstr ( abi. task_field_rust_sp ) + "(%ecx)" )
53
114
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
+
54
125
+ restore_callee_saves ( )
55
126
+ vec ( "ret" ) ;
56
127
}
57
128
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
+
58
155
fn rust_yield_glue ( ) -> vec[ str ] {
59
156
ret vec ( "movl 0(%esp), %ecx # ecx = rust_task" )
60
157
+ load_esp_from_rust_sp ( )
0 commit comments