Optimize RETURN-MULTIPLE on x86-64.
[sbcl.git] / src / assembly / x86-64 / assem-rtns.lisp
1 ;;;; the machine specific support routines needed by the file assembler
2
3 ;;;; This software is part of the SBCL system. See the README file for
4 ;;;; more information.
5 ;;;;
6 ;;;; This software is derived from the CMU CL system, which was
7 ;;;; written at Carnegie Mellon University and released into the
8 ;;;; public domain. The software is in the public domain and is
9 ;;;; provided with absolutely no warranty. See the COPYING and CREDITS
10 ;;;; files for more information.
11
12 (in-package "SB!VM")
13 \f
14 ;;;; RETURN-MULTIPLE
15
16 ;;; For RETURN-MULTIPLE, we have to move the results from the end of
17 ;;; the frame for the function that is returning to the end of the
18 ;;; frame for the function being returned to.
19
20 #+sb-assembling ;; We don't want a vop for this one.
21 (define-assembly-routine
22     (return-multiple (:return-style :none))
23     (;; These are really arguments.
24      (:temp ecx unsigned-reg rcx-offset)
25      (:temp esi unsigned-reg rsi-offset)
26
27      ;; These we need as temporaries.
28      (:temp eax unsigned-reg rax-offset)
29      (:temp ebx unsigned-reg rbx-offset)
30      (:temp edx unsigned-reg rdx-offset)
31      (:temp edi unsigned-reg rdi-offset)
32      (:temp temp unsigned-reg r8-offset)
33      (:temp loop-index unsigned-reg r9-offset))
34
35   ;; Pick off the cases where everything fits in register args.
36   (inst jrcxz ZERO-VALUES)
37   (inst cmp ecx (fixnumize 1))
38   (inst jmp :e ONE-VALUE)
39   (inst cmp ecx (fixnumize 2))
40   (inst jmp :e TWO-VALUES)
41   (inst cmp ecx (fixnumize 3))
42   (inst jmp :e THREE-VALUES)
43
44   ;; As per the calling convention EBX is expected to point at the SP
45   ;; before the stack frame.
46   (inst lea ebx (make-ea :qword :base rbp-tn
47                          :disp (* sp->fp-offset n-word-bytes)))
48
49   ;; Save the count, the return address and restore the frame pointer,
50   ;; because the loop is going to destroy them.
51   (inst mov edx ecx)
52   (inst mov eax (make-ea :qword :base rbp-tn
53                          :disp (frame-byte-offset return-pc-save-offset)))
54   (inst mov rbp-tn (make-ea :qword :base rbp-tn
55                             :disp (frame-byte-offset ocfp-save-offset)))
56   ;; Blit the values down the stack. Note: there might be overlap, so
57   ;; we have to be careful not to clobber values before we've read
58   ;; them. Because the stack builds down, we are copying to a larger
59   ;; address. Therefore, we need to iterate from larger addresses to
60   ;; smaller addresses.
61   (zeroize loop-index)
62   LOOP
63   (inst sub loop-index n-word-bytes)
64   (inst mov temp
65         (make-ea :qword :base esi
66                         :index loop-index))
67   (inst mov
68         (make-ea :qword :base ebx
69                         :index loop-index)
70         temp)
71
72   (inst sub edx (fixnumize 1))
73   (inst jmp :nz LOOP)
74
75   ;; Set the stack top to the last result.
76   (inst lea rsp-tn (make-ea :qword :base ebx :index loop-index))
77
78   ;; Load the register args.
79   (loadw edx ebx -1)
80   (loadw edi ebx -2)
81   (loadw esi ebx -3)
82
83   ;; And back we go.
84   (inst stc)
85   (inst push eax)
86   (inst ret)
87
88   ;; Handle the register arg cases.
89   ZERO-VALUES
90   (inst lea ebx (make-ea :qword :base rbp-tn
91                          :disp (* sp->fp-offset n-word-bytes)))
92   (inst mov edx nil-value)
93   (inst mov edi edx)
94   (inst mov esi edx)
95   (inst mov rsp-tn rbp-tn)
96   (inst stc)
97   (inst pop rbp-tn)
98   (inst ret)
99
100   ;; Note: we can get this, because the return-multiple vop doesn't
101   ;; check for this case when size > speed.
102   ONE-VALUE
103   (loadw edx esi -1)
104   (inst mov rsp-tn rbp-tn)
105   (inst clc)
106   (inst pop rbp-tn)
107   (inst ret)
108
109   TWO-VALUES
110   (inst lea ebx (make-ea :qword :base rbp-tn
111                          :disp (* sp->fp-offset n-word-bytes)))
112   (loadw edx esi -1)
113   (loadw edi esi -2)
114   (inst mov esi nil-value)
115   (inst mov rsp-tn rbp-tn)
116   (inst stc)
117   (inst pop rbp-tn)
118   (inst ret)
119
120   THREE-VALUES
121   (inst lea ebx (make-ea :qword :base rbp-tn
122                          :disp (* sp->fp-offset n-word-bytes)))
123   (loadw edx esi -1)
124   (loadw edi esi -2)
125   (loadw esi esi -3)
126   (inst mov rsp-tn rbp-tn)
127   (inst stc)
128   (inst pop rbp-tn)
129   (inst ret))
130 \f
131 ;;;; TAIL-CALL-VARIABLE
132
133 ;;; For tail-call-variable, we have to copy the arguments from the end
134 ;;; of our stack frame (were args are produced) to the start of our
135 ;;; stack frame (were args are expected).
136 ;;;
137 ;;; We take the function to call in EAX and a pointer to the arguments in
138 ;;; ESI. EBP says the same over the jump, and the old frame pointer is
139 ;;; still saved in the first stack slot. The return-pc is saved in
140 ;;; the second stack slot, so we have to push it to make it look like
141 ;;; we actually called. We also have to compute ECX from the difference
142 ;;; between ESI and the stack top.
143 #+sb-assembling ;; No vop for this one either.
144 (define-assembly-routine
145     (tail-call-variable
146      (:return-style :none))
147
148     ((:temp eax unsigned-reg rax-offset)
149      (:temp ebx unsigned-reg rbx-offset)
150      (:temp ecx unsigned-reg rcx-offset)
151      (:temp edx unsigned-reg rdx-offset)
152      (:temp edi unsigned-reg rdi-offset)
153      (:temp esi unsigned-reg rsi-offset))
154
155   ;; Calculate NARGS (as a fixnum)
156   (move ecx esi)
157   (inst sub ecx rsp-tn)
158   #!-#.(cl:if (cl:= sb!vm:word-shift sb!vm:n-fixnum-tag-bits) '(and) '(or))
159   (inst shr ecx (- word-shift n-fixnum-tag-bits))
160
161   ;; Check for all the args fitting the registers.
162   (inst cmp ecx (fixnumize register-arg-count))
163   (inst jmp :le REGISTER-ARGS)
164
165   ;; Save the OLD-FP and RETURN-PC because the blit is going to trash
166   ;; those stack locations. Save the ECX, because the loop is going to
167   ;; trash it.
168   (pushw rbp-tn (frame-word-offset ocfp-save-offset))
169   (loadw ebx rbp-tn (frame-word-offset return-pc-save-offset))
170   (inst push ecx)
171
172   ;; Do the blit. Because we are coping from smaller addresses to
173   ;; larger addresses, we have to start at the largest pair and work
174   ;; our way down.
175   (inst shr ecx n-fixnum-tag-bits)
176   (inst std)                            ; count down
177   (inst lea edi (make-ea :qword :base rbp-tn :disp (frame-byte-offset 0)))
178   (inst sub esi n-word-bytes)
179   (inst rep)
180   (inst movs :qword)
181   (inst cld)
182
183   ;; Load the register arguments carefully.
184   (loadw edx rbp-tn (frame-word-offset ocfp-save-offset))
185
186   ;; Restore OLD-FP and ECX.
187   (inst pop ecx)
188   ;; Overwrites a1
189   (popw rbp-tn (frame-word-offset ocfp-save-offset))
190
191   ;; Blow off the stack above the arguments.
192   (inst lea rsp-tn (make-ea :qword :base edi :disp n-word-bytes))
193
194   ;; remaining register args
195   (inst mov edi edx)
196   (loadw edx rbp-tn (frame-word-offset 0))
197   (loadw esi rbp-tn (frame-word-offset 2))
198
199   ;; Push the (saved) return-pc so it looks like we just called.
200   (inst push ebx)
201
202   ;; And jump into the function.
203   (inst jmp
204         (make-ea :byte :base eax
205                  :disp (- (* closure-fun-slot n-word-bytes)
206                           fun-pointer-lowtag)))
207
208   ;; All the arguments fit in registers, so load them.
209   REGISTER-ARGS
210   (loadw edx esi -1)
211   (loadw edi esi -2)
212   (loadw esi esi -3)
213
214   ;; Clear most of the stack.
215   (inst lea rsp-tn
216         (make-ea :qword :base rbp-tn :disp (* (- sp->fp-offset 3) n-word-bytes)))
217
218   ;; Push the return-pc so it looks like we just called.
219   (pushw rbp-tn (frame-word-offset return-pc-save-offset))
220
221   ;; And away we go.
222   (inst jmp (make-ea :byte :base eax
223                      :disp (- (* closure-fun-slot n-word-bytes)
224                               fun-pointer-lowtag))))
225 \f
226 (define-assembly-routine (throw
227                           (:return-style :raw))
228                          ((:arg target (descriptor-reg any-reg) rdx-offset)
229                           (:arg start any-reg rbx-offset)
230                           (:arg count any-reg rcx-offset)
231                           (:temp catch any-reg rax-offset))
232
233   (declare (ignore start count))
234
235   (load-tl-symbol-value catch *current-catch-block*)
236
237   LOOP
238
239   (let ((error (gen-label)))
240     (assemble (*elsewhere*)
241       (emit-label error)
242
243       ;; Fake up a stack frame so that backtraces come out right.
244       (inst push rbp-tn)
245       (inst mov rbp-tn rsp-tn)
246
247       (emit-error-break nil error-trap
248                         (error-number-or-lose 'unseen-throw-tag-error)
249                         (list target)))
250     (inst test catch catch)             ; check for NULL pointer
251     (inst jmp :z error))
252
253   (inst cmp target (make-ea-for-object-slot catch catch-block-tag-slot 0))
254   (inst jmp :e EXIT)
255
256   (loadw catch catch catch-block-previous-catch-slot)
257   (inst jmp LOOP)
258
259   EXIT
260
261   ;; Here EAX points to catch block containing symbol pointed to by EDX.
262   (inst jmp (make-fixup 'unwind :assembly-routine)))
263
264 ;;;; non-local exit noise
265
266 (define-assembly-routine (unwind
267                           (:return-style :none)
268                           (:translate %continue-unwind)
269                           (:policy :fast-safe))
270                          ((:arg block (any-reg descriptor-reg) rax-offset)
271                           (:arg start (any-reg descriptor-reg) rbx-offset)
272                           (:arg count (any-reg descriptor-reg) rcx-offset)
273                           (:temp uwp unsigned-reg rsi-offset))
274   (declare (ignore start count))
275
276   (let ((error (generate-error-code nil 'invalid-unwind-error)))
277     (inst test block block)             ; check for NULL pointer
278     (inst jmp :z error))
279
280   (load-tl-symbol-value uwp *current-unwind-protect-block*)
281
282   ;; Does *CURRENT-UNWIND-PROTECT-BLOCK* match the value stored in
283   ;; argument's CURRENT-UWP-SLOT?
284   (inst cmp uwp
285         (make-ea-for-object-slot block unwind-block-current-uwp-slot 0))
286   ;; If a match, return to context in arg block.
287   (inst jmp :e DO-EXIT)
288
289   ;; Not a match - return to *CURRENT-UNWIND-PROTECT-BLOCK* context.
290   ;; Important! Must save (and return) the arg 'block' for later use!!
291   (move rdx-tn block)
292   (move block uwp)
293   ;; Set next unwind protect context.
294   (loadw uwp uwp unwind-block-current-uwp-slot)
295   ;; we're about to reload ebp anyway, so let's borrow it here as a
296   ;; temporary.  Hope this works
297   (store-tl-symbol-value uwp *current-unwind-protect-block* rbp-tn)
298
299   DO-EXIT
300
301   (loadw rbp-tn block unwind-block-current-cont-slot)
302
303   ;; Uwp-entry expects some things in known locations so that they can
304   ;; be saved on the stack: the block in edx-tn, start in ebx-tn, and
305   ;; count in ecx-tn.
306
307   (inst jmp (make-ea :byte :base block
308                      :disp (* unwind-block-entry-pc-slot n-word-bytes))))