;;;; -*- 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.
;;;;
;;;; CMPC-MACHINE -- Abstract target machine details
;;;;

(in-package "COMPILER")

(defconstant +representation-types+
  '(;; These types can be used by ECL to unbox data
    ;; They are sorted from the most specific, to the least specific one.
    (:byte .
    #1=((signed-byte 8) "int8_t" "ecl_make_int8_t" "ecl_to_int8_t" "ecl_fixnum"))
    (:unsigned-byte .
    #2=((unsigned-byte 8) "uint8_t" "ecl_make_uint8_t" "ecl_to_uint8_t" "ecl_fixnum"))
    (:fixnum integer "cl_fixnum" "ecl_make_fixnum" "ecl_to_fixnum" "ecl_fixnum")
    (:int integer "int" "ecl_make_int" "ecl_to_int" "ecl_to_int")
    (:unsigned-int integer "unsigned int" "ecl_make_uint" "ecl_to_uint" "ecl_to_uint")
    (:long integer "long" "ecl_make_long" "ecl_to_long" "ecl_to_long")
    (:unsigned-long integer "unsigned long" "ecl_make_ulong" "ecl_to_ulong" "ecl_to_ulong")
    (:cl-index integer "cl_index" "ecl_make_unsigned_integer" "ecl_to_cl_index" "ecl_fixnum")
    (:long-long integer "ecl_long_long_t" "ecl_make_long_long" "ecl_to_long_long" "ecl_to_long_long")
    (:unsigned-long-long integer "ecl_ulong_long_t" "ecl_make_ulong_long" "ecl_to_ulong_long" "ecl_to_ulong_long")
    (:float single-float "float" "ecl_make_single_float" "ecl_to_float" "ecl_single_float")
    (:double double-float "double" "ecl_make_double_float" "ecl_to_double" "ecl_double_float")
    (:long-double long-float "long double" "ecl_make_long_float" "ecl_to_long_double" "ecl_long_float")
    (:unsigned-char base-char "unsigned char" "CODE_CHAR" "ecl_base_char_code" "CHAR_CODE")
    (:char base-char "char" "CODE_CHAR" "ecl_base_char_code" "CHAR_CODE")
    (:wchar character "ecl_character" "CODE_CHAR" "ecl_char_code" "CHAR_CODE")
    (:float-sse-pack ext::float-sse-pack "__m128" "ecl_make_float_sse_pack"
     "ecl_unbox_float_sse_pack" "ecl_unbox_float_sse_pack_unsafe")
    (:double-sse-pack ext::double-sse-pack "__m128d" "ecl_make_double_sse_pack"
     "ecl_unbox_double_sse_pack" "ecl_unbox_double_sse_pack_unsafe")
    (:int-sse-pack ext::sse-pack #|<-intentional|# "__m128i" "ecl_make_int_sse_pack"
     "ecl_unbox_int_sse_pack" "ecl_unbox_int_sse_pack_unsafe")
    (:object t "cl_object")
    (:bool t "bool" "ecl_make_bool" "ecl_to_bool" "ecl_to_bool")
    ;; These types are never selected to unbox data.
    ;; They are here, because we need to know how to print them.
    (:void nil "void")
    (:pointer-void si::foreign-data "void*" "ecl_make_pointer" "ecl_to_pointer" "ecl_to_pointer")
    (:cstring string "char*" "ecl_cstring_to_base_string_or_nil")
    (:char* string "char*")
    (:int8-t . #1#)
    (:uint8-t . #2#)
    (:int16-t integer "ecl_int16_t" "ecl_make_int16_t" "ecl_to_int16_t" "ecl_to_int16_t")
    (:uint16-t integer "ecl_uint16_t" "ecl_make_uint16_t" "ecl_to_uint16_t" "ecl_to_unt16_t")
    (:int32-t integer "ecl_int32_t" "ecl_make_int32_t" "ecl_to_int32_t" "ecl_to_int32_t")
    (:uint32-t integer "ecl_uint32_t" "ecl_make_uint32_t" "ecl_to_uint32_t" "ecl_to_uint32_t")
    (:int64-t integer "ecl_int64_t" "ecl_make_int64_t" "ecl_to_int64_t" "ecl_to_int64_t")
    (:uint64-t integer "ecl_uint64_t" "ecl_make_uint64_t" "ecl_to_uint64_t" "ecl_to_uint64_t")
    (:short integer "short" "ecl_make_short" "ecl_to_short" "ecl_fixnum")
    (:unsigned-short integer "unsigned short" "ecl_make_ushort" "ecl_to_ushort" "ecl_fixnum")
    ))

(defconstant +this-machine-c-types+
  '((:byte . -8)
    (:unsigned-byte . 8)
    (:unsigned-short . #.(- (logcount ffi:c-ushort-max)))
    (:short . #.(- (logcount ffi:c-ushort-max)))
    (:unsigned-int . #.(logcount ffi:c-uint-max))
    (:int . #.(- (logcount ffi:c-uint-max)))
    (:unsigned-long . #.(logcount ffi:c-ulong-max))
    (:long . #.(- (logcount ffi:c-ulong-max)))
    #+long-long
    (:unsigned-long-long . #.(logcount ffi:c-ulong-long-max))
    #+long-long
    (:long-long . #.(- (logcount ffi:c-ulong-long-max)))
    (:cl-index . #.(logcount most-positive-fixnum))
    (:fixnum . #.(- -1 (logcount most-positive-fixnum)))
    (:uint8-t . 8)
    (:int8-t . -8)
    #+:uint16-t
    (:uint16-t . 16)
    #+:uint16-t
    (:int16-t . -16)
    #+:uint32-t
    (:uint32-t . 32)
    #+:uint32-t
    (:int32-t . -32)
    #+:uint64-t
    (:uint64-t . 64)
    #+:uint64-t
    (:int64-t . -64)
    #+:sse2 (:float-sse-pack . nil)
    #+:sse2 (:double-sse-pack . nil)
    #+:sse2 (:int-sse-pack . nil)
    #+:long-float (:long-double . nil)
    ))

(defconstant +all-machines-c-types+
  '((:object)
    (:float)
    (:double)
    (:char)
    (:unsigned-char)
    (:wchar)
    (:char*)
    (:cstring)
    (:bool)
    (:void)
    (:pointer-void)))

(defun make-rep-type (all-c-types name lisp-type c-name &optional to-lisp from-lisp from-lisp-unsafe)
  (let* ((record (assoc name all-c-types))
         (bits (cdr record)))
    (when record
      ;; For integer bits we get extra information from ALL-C-TYPES
      (when bits
        (if (plusp bits)
            (setf lisp-type `(unsigned-byte ,bits))
            (setf bits (- bits)
                  lisp-type `(signed-byte ,bits))))
      (%make-rep-type
       :name name
       :lisp-type lisp-type
       :bits bits
       :numberp (subtypep lisp-type 'number)
       :integerp (subtypep lisp-type 'integer)
       :c-name c-name
       :to-lisp to-lisp
       :from-lisp from-lisp
       :from-lisp-unsafe from-lisp-unsafe))))

(defun make-rep-type-hash (all-c-types)
  (let ((table (make-hash-table :size 128 :test 'eq)))
    table))

(defun default-machine ()
  (let* ((all-c-types (append +this-machine-c-types+ +all-machines-c-types+))
         (table (make-hash-table :size 128 :test 'eq))
         (sorted-rep-types
          ;; Create the rep-type objects
          (loop for i from 0
             for record in +representation-types+
             for rep-type = (apply #'make-rep-type all-c-types record)
             when rep-type
             do (setf (rep-type-index rep-type) i)
             and collect (setf (gethash (rep-type-name rep-type) table) rep-type))))
    ;; hack: sse-pack -> int, but int -> int-sse-pack
    (let ((r (gethash :int-sse-pack table)))
      (when r
        (setf (rep-type-index r) 'ext:int-sse-pack)))
    ;; On a second pass, we replace types with more general ones
    (loop with fixnum-rep-type = (gethash ':fixnum table)
       with fixnum-lisp-type = (rep-type-lisp-type fixnum-rep-type)
       for (name . rest) in +representation-types+
       for r = (gethash name table)
       when (and r (subtypep (rep-type-lisp-type r) fixnum-lisp-type))
       do (setf (rep-type-from-lisp-unsafe r) "ecl_fixnum"))
    ;; Create machine object
    (make-machine :c-types all-c-types
                  :rep-type-hash table
                  :sorted-types sorted-rep-types)))

(defun machine-c-type-p (name)
  (gethash name (machine-rep-type-hash *machine*)))

(defun machine-fixnump (number)
  (typep number (rep-type-lisp-type (gethash :fixnum number))))

(defconstant +default-machine+ (setf *machine* (default-machine)))
