1 (in-package #:sb-grovel)
3 (defvar *default-c-stream* nil)
5 (defun escape-for-string (string)
8 (defun split-cflags (string)
9 (remove-if (lambda (flag)
10 (zerop (length flag)))
12 for start = 0 then (if end (1+ end) nil)
13 for end = (and start (position #\Space string :start start))
15 collect (subseq string start end))))
17 (defun c-escape (string &optional (dangerous-chars '(#\")) (escape-char #\\))
18 "Escape DANGEROUS-CHARS in STRING, with ESCAPE-CHAR."
19 (coerce (loop for c across string
20 if (member c dangerous-chars) collect escape-char
24 (defun as-c (&rest args)
25 "Pretty-print ARGS into the C source file, separated by #\Space"
26 (format *default-c-stream* "~A~{ ~A~}~%" (first args) (rest args)))
28 (defun printf (formatter &rest args)
29 "Emit C code to fprintf the quoted code, via FORMAT.
30 The first argument is the C string that should be passed to
33 The rest of the arguments are consumed by FORMAT clauses, until
34 there are no more FORMAT clauses to fill. If there are more
35 arguments, they are emitted as printf arguments.
37 There is no error checking done, unless you pass too few FORMAT
38 clause args. I recommend using this formatting convention in
41 (printf \"string ~A ~S %d %d\" format-arg-1 format-arg-2
42 printf-arg-1 printf-arg-2)"
43 (let ((*print-pretty* nil))
44 (apply #'format *default-c-stream*
45 " fprintf (out, \"~@?\\n\"~@{, ~A~});~%"
49 (defun c-for-enum (lispname elements export)
50 (printf "(cl:eval-when (:compile-toplevel :load-toplevel :execute) (sb-alien:define-alien-type ~A (sb-alien:enum nil" lispname)
51 (dolist (element elements)
52 (destructuring-bind (lisp-element-name c-element-name) element
53 (printf " (~S %d)" lisp-element-name c-element-name)))
56 (dolist (element elements)
57 (destructuring-bind (lisp-element-name c-element-name) element
58 (declare (ignore c-element-name))
59 (unless (keywordp lisp-element-name)
60 (printf "(export '~S)" lisp-element-name))))))
62 (defun c-for-structure (lispname cstruct)
63 (destructuring-bind (cname &rest elements) cstruct
64 (printf "(cl:eval-when (:compile-toplevel :load-toplevel :execute) (sb-grovel::define-c-struct ~A %d" lispname
65 (format nil "sizeof(~A)" cname))
67 (destructuring-bind (lisp-type lisp-el-name c-type c-el-name &key distrust-length) e
68 (printf " (~A ~A \"~A\"" lisp-el-name lisp-type c-type)
72 (format nil "((unsigned long)&(t.~A)) - ((unsigned long)&(t))" c-el-name))
80 (format nil "sizeof(t.~A)" c-el-name))
84 (defun print-c-source (stream headers definitions package-name)
85 (declare (ignorable definitions package-name))
86 (let ((*default-c-stream* stream)
87 (*print-right-margin* nil))
88 (loop for i in (cons "stdio.h" headers)
89 do (format stream "#include <~A>~%" i))
90 (as-c "#define SIGNEDP(x) (((x)-1)<0)")
91 (as-c "#define SIGNED_(x) (SIGNEDP(x)?\"\":\"un\")")
92 (as-c "int main(int argc, char *argv[]) {")
94 (as-c " if (argc != 2) {")
95 (as-c " printf(\"Invalid argcount!\");")
98 (as-c " out = fopen(argv[1], \"w\");")
100 (as-c " printf(\"Error opening output file!\");")
103 (printf "(cl:in-package #:~A)" package-name)
104 (printf "(cl:eval-when (:compile-toplevel)")
105 (printf " (cl:defparameter *integer-sizes* (cl:make-hash-table))")
106 (dolist (type '("char" "short" "long" "int"
107 #+nil"long long" ; TODO: doesn't exist in sb-alien yet
109 (printf " (cl:setf (cl:gethash %d *integer-sizes*) 'sb-alien:~A)" (substitute #\- #\Space type)
110 (format nil "sizeof(~A)" type)))
112 (dolist (def definitions)
113 (destructuring-bind (type lispname cname &optional doc export) def
116 (as-c "#ifdef" cname)
117 (printf "(cl:defconstant ~A %d \"~A\")" lispname doc
119 (when (eql type :errno)
120 (printf "(cl:setf (get '~A 'errno) t)" lispname))
122 (printf "(sb-int:style-warn \"Couldn't grovel for ~A (unknown to the C compiler).\")" cname)
125 (c-for-enum lispname cname export))
127 (printf "(cl:eval-when (:compile-toplevel :load-toplevel :execute) (sb-alien:define-alien-type ~A (sb-alien:%ssigned %d)))" lispname
128 (format nil "SIGNED_(~A)" cname)
129 (format nil "(8*sizeof(~A))" cname)))
131 (printf "(cl:defparameter ~A %s \"~A\"" lispname doc
134 (printf "(cl:declaim (cl:inline ~A))" lispname)
135 (destructuring-bind (f-cname &rest definition) cname
136 (printf "(sb-grovel::define-foreign-routine (\"~A\" ~A)" f-cname lispname)
137 (printf "~{ ~W~^\\n~})" definition)))
139 (c-for-structure lispname cname))
141 ;; should we really not sprechen espagnol, monsieurs?
142 (error "Unknown grovel keyword encountered: ~A" type)))
144 (printf "(cl:export '~A)" lispname))))
148 (defun c-constants-extract (filename output-file package)
149 (with-open-file (f output-file :direction :output :if-exists :supersede)
150 (with-open-file (i filename :direction :input)
151 (let* ((headers (read i))
152 (definitions (read i)))
153 (print-c-source f headers definitions package)))))
155 (defclass grovel-constants-file (asdf:cl-source-file)
156 ((package :accessor constants-package :initarg :package)
157 (do-not-grovel :accessor do-not-grovel
159 :initarg :do-not-grovel)))
161 (define-condition c-compile-failed (compile-failed) ()
162 (:report (lambda (c s)
163 (format s "~@<C compiler failed when performing ~A on ~A.~@:>"
164 (error-operation c) (error-component c)))))
165 (define-condition a-dot-out-failed (compile-failed) ()
166 (:report (lambda (c s)
167 (format s "~@<a.out failed when performing ~A on ~A.~@:>"
168 (error-operation c) (error-component c)))))
170 (defmethod asdf:perform ((op asdf:compile-op)
171 (component grovel-constants-file))
172 ;; we want to generate all our temporary files in the fasl directory
173 ;; because that's where we have write permission. Can't use /tmp;
174 ;; it's insecure (these files will later be owned by root)
175 (let* ((output-file (car (output-files op component)))
176 (filename (component-pathname component))
178 (if (typep output-file 'logical-pathname)
179 (translate-logical-pathname output-file)
180 (pathname output-file)))
181 (tmp-c-source (merge-pathnames #p"foo.c" real-output-file))
182 (tmp-a-dot-out (merge-pathnames #-win32 #p"a.out" #+win32 #p"a.exe"
184 (tmp-constants (merge-pathnames #p"constants.lisp-temp"
186 (princ (list filename output-file real-output-file
187 tmp-c-source tmp-a-dot-out tmp-constants))
189 (funcall (intern "C-CONSTANTS-EXTRACT" (find-package "SB-GROVEL"))
190 filename tmp-c-source (constants-package component))
191 (unless (do-not-grovel component)
192 (let* ((cc (or (and (string/= (sb-ext:posix-getenv "CC") "")
193 (sb-ext:posix-getenv "CC"))
194 ;; It might be nice to include a CONTINUE or
195 ;; USE-VALUE restart here, but ASDF seems to insist
196 ;; on handling the errors itself.
197 (error "The CC environment variable has not been set in SB-GROVEL. Since this variable should always be set during the SBCL build process, this might indicate an SBCL with a broken contrib installation.")))
198 (code (sb-ext:process-exit-code
202 (split-cflags (sb-ext:posix-getenv "EXTRA_CFLAGS"))
203 #+(and linux largefile)
204 '("-D_LARGEFILE_SOURCE"
205 "-D_LARGEFILE64_SOURCE"
206 "-D_FILE_OFFSET_BITS=64")
207 #+(and x86-64 darwin)
210 (namestring tmp-a-dot-out)
211 (namestring tmp-c-source)))
214 :output *trace-output*))))
216 (case (operation-on-failure op)
217 (:warn (warn "~@<C compiler failure when performing ~A on ~A.~@:>"
220 (error 'c-compile-failed :operation op :component component)))))
221 (let ((code (sb-ext:process-exit-code
222 (sb-ext:run-program (namestring tmp-a-dot-out)
223 (list (namestring tmp-constants))
226 :output *trace-output*))))
228 (case (operation-on-failure op)
229 (:warn (warn "~@<a.out failure when performing ~A on ~A.~@:>"
232 (error 'a-dot-out-failed :operation op :component component))))))
233 (multiple-value-bind (output warnings-p failure-p)
234 (compile-file tmp-constants :output-file output-file)
236 (case (operation-on-warnings op)
238 (formatter "~@<COMPILE-FILE warned while ~
239 performing ~A on ~A.~@:>")
241 (:error (error 'compile-warned :component component :operation op))
244 (case (operation-on-failure op)
246 (formatter "~@<COMPILE-FILE failed while ~
247 performing ~A on ~A.~@:>")
249 (:error (error 'compile-failed :component component :operation op))
252 (error 'compile-error :component component :operation op)))))