;;;; the implementation-independent parts of the code generator. We use
;;;; functions and information provided by the VM definition to convert
;;;; IR2 into assembly code. After emitting code, we finish the
;;;; assembly and then do the post-assembly phase.

;;;; This software is part of the SBCL system. See the README file for
;;;; more information.
;;;;
;;;; This software is derived from the CMU CL system, which was
;;;; written at Carnegie Mellon University and released into the
;;;; public domain. The software is in the public domain and is
;;;; provided with absolutely no warranty. See the COPYING and CREDITS
;;;; files for more information.

(in-package "SB!C")

;;;; utilities used during code generation

;;; the number of bytes used by the code object header
(defun component-header-length (&optional
                                (component *component-being-compiled*))
  (let* ((2comp (component-info component))
         (constants (ir2-component-constants 2comp))
         (num-consts (length constants)))
    (ash (logandc2 (1+ num-consts) 1) sb!vm:word-shift)))

;;; the size of the NAME'd SB in the currently compiled component.
;;; This is useful mainly for finding the size for allocating stack
;;; frames.
(defun sb-allocated-size (name)
  (finite-sb-current-size (sb-or-lose name)))

;;; the TN that is used to hold the number stack frame-pointer in
;;; VOP's function, or NIL if no number stack frame was allocated
(defun current-nfp-tn (vop)
  (unless (zerop (sb-allocated-size 'non-descriptor-stack))
    (let ((block (ir2-block-block (vop-block vop))))
    (when (ir2-physenv-number-stack-p
           (physenv-info
            (block-physenv block)))
      (ir2-component-nfp (component-info (block-component block)))))))

;;; the TN that is used to hold the number stack frame-pointer in the
;;; function designated by 2ENV, or NIL if no number stack frame was
;;; allocated
(defun callee-nfp-tn (2env)
  (unless (zerop (sb-allocated-size 'non-descriptor-stack))
    (when (ir2-physenv-number-stack-p 2env)
      (ir2-component-nfp (component-info *component-being-compiled*)))))

;;; the TN used for passing the return PC in a local call to the function
;;; designated by 2ENV
(defun callee-return-pc-tn (2env)
  (ir2-physenv-return-pc-pass 2env))

;;;; specials used during code generation

(defvar *trace-table-info*)
(defvar *code-segment* nil)
(defvar *elsewhere* nil)
(defvar *elsewhere-label* nil)
#!+inline-constants
(progn
  (defvar *constant-segment* nil)
  (defvar *constant-table*   nil)
  (defvar *constant-vector*  nil))


;;;; noise to emit an instruction trace

(defvar *prev-segment*)
(defvar *prev-vop*)

(defun trace-instruction (segment vop inst args)
  (let ((*standard-output* *compiler-trace-output*))
    (unless (eq *prev-segment* segment)
      (format t "in the ~A segment:~%" (sb!assem:segment-type segment))
      (setf *prev-segment* segment))
    (unless (eq *prev-vop* vop)
      (when vop
        (format t "~%VOP ")
        (if (vop-p vop)
            (print-vop vop)
            (format *compiler-trace-output* "~S~%" vop)))
      (terpri)
      (setf *prev-vop* vop))
    (case inst
      (:label
       (format t "~A:~%" args))
      (:align
       (format t "~0,8T.align~0,8T~A~%" args))
      (t
       (format t "~0,8T~A~@[~0,8T~{~A~^, ~}~]~%" inst args))))
  (values))

;;;; GENERATE-CODE and support routines

;;; standard defaults for slots of SEGMENT objects
(defun default-segment-run-scheduler ()
  (and *assembly-optimize*
        (policy (lambda-bind
                 (block-home-lambda
                  (block-next (component-head *component-being-compiled*))))
                (or (> speed compilation-speed) (> space compilation-speed)))))
(defun default-segment-inst-hook ()
  (and *compiler-trace-output*
       #'trace-instruction))

(defun init-assembler ()
  (setf *code-segment*
        (sb!assem:make-segment :type :regular
                               :run-scheduler (default-segment-run-scheduler)
                               :inst-hook (default-segment-inst-hook)))
  #!+sb-dyncount
  (setf (sb!assem:segment-collect-dynamic-statistics *code-segment*)
        *collect-dynamic-statistics*)
  (setf *elsewhere*
        (sb!assem:make-segment :type :elsewhere
                               :run-scheduler (default-segment-run-scheduler)
                               :inst-hook (default-segment-inst-hook)
                               :alignment 0))
  #!+inline-constants
  (setf *constant-segment*
        (sb!assem:make-segment :type :elsewhere
                               :run-scheduler nil
                               :inst-hook (default-segment-inst-hook)
                               :alignment 0)
        *constant-table*  (make-hash-table :test #'equal)
        *constant-vector* (make-array 16 :adjustable t :fill-pointer 0))
  (values))

(defun generate-code (component)
  (when *compiler-trace-output*
    (format *compiler-trace-output*
            "~|~%assembly code for ~S~2%"
            component))
  (let ((prev-env nil)
        (*trace-table-info* nil)
        (*prev-segment* nil)
        (*prev-vop* nil)
        (*fixup-notes* nil))
    (let ((label (sb!assem:gen-label)))
      (setf *elsewhere-label* label)
      (sb!assem:assemble (*elsewhere*)
        (sb!assem:emit-label label)))
    (do-ir2-blocks (block component)
      (let ((1block (ir2-block-block block)))
        (when (and (eq (block-info 1block) block)
                   (block-start 1block))
          (sb!assem:assemble (*code-segment*)
            ;; Align first emitted block of each loop: x86 and x86-64 both
            ;; like 16 byte alignment, however, since x86 aligns code objects
            ;; on 8 byte boundaries we cannot guarantee proper loop alignment
            ;; there (yet.)  Only x86-64 does something with ALIGNP, but
            ;; it may be useful in the future.
            (let ((alignp (let ((cloop (block-loop 1block)))
                            (when (and cloop
                                       (loop-tail cloop)
                                       (not (loop-info cloop)))
                              ;; Mark the loop as aligned by saving the IR1 block aligned.
                              (setf (loop-info cloop) 1block)
                              t))))
              (emit-block-header (block-label 1block)
                                 (ir2-block-%trampoline-label block)
                                 (ir2-block-dropped-thru-to block)
                                 alignp)))
          (let ((env (block-physenv 1block)))
            (unless (eq env prev-env)
              (let ((lab (gen-label)))
                (setf (ir2-physenv-elsewhere-start (physenv-info env))
                      lab)
                (emit-label-elsewhere lab))
              (setq prev-env env)))))
      (do ((vop (ir2-block-start-vop block) (vop-next vop)))
          ((null vop))
        (let ((gen (vop-info-generator-function (vop-info vop))))
          (if gen
            (funcall gen vop)
            (format t
                    "missing generator for ~S~%"
                    (template-name (vop-info vop)))))))
    (sb!assem:append-segment *code-segment* *elsewhere*)
    (setf *elsewhere* nil)
    #!+inline-constants
    (progn
      (unless (zerop (length *constant-vector*))
        (let ((constants (sb!vm:sort-inline-constants *constant-vector*)))
          (assemble (*constant-segment*)
            (sb!vm:emit-constant-segment-header
             *constant-segment*
             constants
             (do-ir2-blocks (2block component nil)
               (when (policy (block-last (ir2-block-block 2block))
                             (> speed space))
                 (return t))))
            (map nil (lambda (constant)
                       (sb!vm:emit-inline-constant (car constant) (cdr constant)))
                 constants)))
        (sb!assem:append-segment *code-segment* *constant-segment*))
      (setf *constant-segment* nil
            *constant-vector*  nil
            *constant-table*   nil))
    (values (sb!assem:finalize-segment *code-segment*)
            (nreverse *trace-table-info*)
            *fixup-notes*)))

(defun emit-label-elsewhere (label)
  (sb!assem:assemble (*elsewhere*)
    (sb!assem:emit-label label)))

(defun label-elsewhere-p (label-or-posn)
  (<= (label-position *elsewhere-label*)
      (etypecase label-or-posn
        (label
         (label-position label-or-posn))
        (index
         label-or-posn))))

#!+inline-constants
(defun register-inline-constant (&rest constant-descriptor)
  (declare (dynamic-extent constant-descriptor))
  (let ((constant (sb!vm:canonicalize-inline-constant constant-descriptor)))
    (or (gethash constant *constant-table*)
        (multiple-value-bind (label value) (sb!vm:inline-constant-value constant)
          (vector-push-extend (cons constant label) *constant-vector*)
          (setf (gethash constant *constant-table*) value)))))
