;;;; -*- Mode: Lisp; Syntax: Common-Lisp; indent-tabs-mode: nil; Package: C -*-
;;;; vim: set filetype=lisp tabstop=8 shiftwidth=2 expandtab:

;;;;
;;;;  Copyright (c) 2010, Juan Jose Garcia-Ripoll
;;;;
;;;;    This program is free software; you can redistribute it and/or
;;;;    modify it under the terms of the GNU Library General Public
;;;;    License as published by the Free Software Foundation; either
;;;;    version 2 of the License, or (at your option) any later version.
;;;;
;;;;    See file '../Copyright' for full details.
;;;;
;;;;  CMPOPT-BITS  -- Optimize operations acting on bits
;;;;

(in-package "COMPILER")

;;;
;;; LDB
;;; Look for inline expansion of LDB1 in sysfun.lsp
;;;

(defun inline-bytespec (bytespec)
  (declare (si::c-local))
  (and (consp bytespec)
       (eq 'BYTE (car bytespec))
       (= (length bytespec) 3)
       (policy-inline-bit-operations)))

(define-compiler-macro ldb (&whole whole bytespec integer)
  (if (inline-bytespec bytespec)
      (with-clean-symbols (%pos %size)
        `(with-let*-type-check ((%size ,(second bytespec) unsigned-byte)
                                (%pos ,(third bytespec) unsigned-byte))
           (logand (lognot (ash -1 %size)) (ash ,integer (- %pos)))))
      whole))

(define-compiler-macro ldb-test (&whole whole bytespec integer)
  (if (inline-bytespec bytespec)
      `(not (zerop (mask-field ,bytespec ,integer)))
      whole))

(define-compiler-macro mask-field (&whole whole bytespec integer)
  (if (inline-bytespec bytespec)
      (with-clean-symbols (%pos %size)
        `(with-let*-type-check ((%size ,(second bytespec) unsigned-byte)
                                (%pos ,(third bytespec) unsigned-byte))
           (logand (ash (lognot (ash -1 %size)) %pos)
                   ,integer)))
      whole))

(define-compiler-macro dpb (&whole whole newbyte bytespec integer)
  (if (inline-bytespec bytespec)
      (with-clean-symbols (%pos %size %mask)
        `(with-let*-type-check ((%size ,(second bytespec) unsigned-byte)
                                (%pos ,(third bytespec) unsigned-byte)
                                (%mask (ash (lognot (ash -1 %size)) %pos) t))
             (logior (logand (ash ,newbyte %pos) %mask)
                     (logandc2 ,integer %mask))))
      whole))

(define-compiler-macro deposit-field (&whole whole newbyte bytespec integer)
  (if (inline-bytespec bytespec)
      (with-clean-symbols (%pos %size %mask)
        `(with-let*-type-check ((%size ,(second bytespec) unsigned-byte)
                                (%pos ,(third bytespec) unsigned-byte)
                                (%mask (ash (lognot (ash -1 %size)) %pos) t))
             (logior (logand ,newbyte %mask)
                     (logandc2 ,integer %mask)
                     )))
      whole))

(define-setf-expander ldb (&environment env bytespec int)
  (multiple-value-bind (temps vals stores store-form access-form)
      (get-setf-expansion int env)
    (if (inline-bytespec bytespec)
        (let* ((bpos (gensym))
               (bsize (gensym))
               (store (gensym))
               (btemp `(byte ,bpos ,bsize))
               (stemp (first stores)))
          (values `(,bpos ,bsize ,@temps)
                  `(,(second bytespec) ,(third bytespec) ,@vals)
                  `(,store)
                  `(let ((,stemp (dpb ,store ,btemp ,access-form)))
                     ,store-form ,store)
                  `(ldb ,btemp ,access-form)))
        (let* ((btemp (gensym))
               (store (gensym))
               (stemp (first stores)))
          (values `(,btemp ,@temps)
                  `(,bytespec ,@vals)
                  `(,store)
                  `(let ((,stemp (dpb ,store ,btemp ,access-form)))
                     ,store-form ,store)
                  `(ldb ,btemp ,access-form))))))

(define-setf-expander mask-field (&environment env bytespec int)
  (multiple-value-bind (temps vals stores store-form access-form)
      (get-setf-expansion int env)
    (if (inline-bytespec bytespec)
        (let* ((bpos (gensym))
               (bsize (gensym))
               (store (gensym))
               (btemp `(byte ,bpos ,bsize))
               (stemp (first stores)))
          (values `(,bpos ,bsize ,@temps)
                  `(,(second bytespec) ,(third bytespec) ,@vals)
                  `(,store)
                  `(let ((,stemp (deposit-field ,store ,btemp ,access-form)))
                     ,store-form ,store)
                  `(mask-field ,btemp ,access-form)))
        (let* ((btemp (gensym))
               (store (gensym))
               (stemp (first stores)))
          (values `(,btemp ,@temps)
                  `(,bytespec ,@vals)
                  `(,store)
                  `(let ((,stemp (deposit-field ,store ,btemp ,access-form)))
                     ,store-form ,store)
                  `(mask-field ,btemp ,access-form))))))

;;;
;;; ASH
;;; Bit fiddling. It is a bit tricky because C does not allow
;;; shifts in << or >> which exceed the integer size. In those
;;; cases the compiler may do whatever it wants (and gcc does!)
;;;

(define-compiler-macro ash (&whole whole argument shift)
  (cond ((and (integerp argument)
              (integerp shift))
         (ash argument shift))
        ((and (policy-assume-right-type)
              (integerp shift))
         (if (zerop shift)
             argument
             `(shift ,argument ,shift)))
        (t
         whole)))

(define-c-inliner shift (return-type argument orig-shift)
  (let* ((arg-type (inlined-arg-type argument))
         (arg-c-type (lisp-type->rep-type arg-type))
         (return-c-type (lisp-type->rep-type return-type))
         (shift (loc-immediate-value (inlined-arg-loc orig-shift))))
    (if (or (not (c-integer-rep-type-p arg-c-type))
            (not (c-integer-rep-type-p return-c-type)))
        (produce-inline-loc (list argument orig-shift) '(:object :fixnum) '(:object)
                            "ecl_ash(#0,#1)" nil t)
        (let* ((arg-bits (c-integer-rep-type-bits arg-c-type))
               (return-bits (c-integer-rep-type-bits return-c-type))
               (max-type (if (and (plusp shift)
                                  (< arg-bits return-bits))
                           return-c-type
                           arg-c-type)))
          (produce-inline-loc (list argument) (list max-type) (list return-type)
                              (format nil
                                      (if (minusp shift)
                                          "((#0) >> (~D))"
                                          "((#0) << (~D))")
                                      (abs shift))
                              nil t)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; TYPE PROPAGATION
;;;

(def-type-propagator logand (fname &rest args)
  (values args
          (if args
              (dolist (int-type '((UNSIGNED-BYTE 8) FIXNUM) 'integer)
                (when (loop for value in args
                         always (subtypep value int-type))
                  (return int-type)))
              'fixnum)))
