changeset 5574:d4f334808463

Support inlining labels, bytecomp.el. lisp/ChangeLog addition: 2011-10-02 Aidan Kehoe <kehoea@parhasard.net> * bytecomp.el (byte-compile-initial-macro-environment): Add #'declare to this, so it doesn't need to rely on #'cl-compiling file to determine when we're byte-compiling. Update #'labels to support declaring labels inline, as Common Lisp requires. * bytecomp.el (byte-compile-function-form): Don't error if FUNCTION is quoting a non-lambda, non-symbol, just return it. * cl-extra.el (cl-macroexpand-all): If a label name has been quoted, expand to the label placeholder quoted with 'function. This allows the byte compiler to distinguish between uses of the placeholder as data and uses in contexts where it should be inlined. * cl-macs.el: * cl-macs.el (cl-do-proclaim): When proclaming something as inline, if it is bound as a label, don't modify the symbol's plist; instead, treat the first element of its placeholder constant vector as a place to store compile information. * cl-macs.el (declare): Leave processing declarations while compiling to the implementation of #'declare in byte-compile-initial-macro-environment. tests/ChangeLog addition: 2011-10-02 Aidan Kehoe <kehoea@parhasard.net> * automated/lisp-tests.el: * automated/lisp-tests.el (+): Test #'labels and inlining.
author Aidan Kehoe <kehoea@parhasard.net>
date Sun, 02 Oct 2011 15:32:16 +0100
parents f0f1fd0d8486
children 89cb6a66a61f
files lisp/ChangeLog lisp/bytecomp.el lisp/cl-extra.el lisp/cl-macs.el tests/ChangeLog tests/automated/lisp-tests.el
diffstat 6 files changed, 221 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/ChangeLog	Sun Sep 25 16:12:07 2011 +0100
+++ b/lisp/ChangeLog	Sun Oct 02 15:32:16 2011 +0100
@@ -1,3 +1,29 @@
+2011-10-02  Aidan Kehoe  <kehoea@parhasard.net>
+
+	* bytecomp.el (byte-compile-initial-macro-environment):
+	Add #'declare to this, so it doesn't need to rely on
+	#'cl-compiling file to determine when we're byte-compiling.
+	Update #'labels to support declaring labels inline, as Common Lisp
+	requires.
+	* bytecomp.el (byte-compile-function-form):
+	Don't error if FUNCTION is quoting a non-lambda, non-symbol, just
+	return it.
+	* cl-extra.el (cl-macroexpand-all):
+	If a label name has been quoted, expand to the label placeholder
+	quoted with 'function. This allows the byte compiler to
+	distinguish between uses of the placeholder as data and uses in
+	contexts where it should be inlined.
+	* cl-macs.el:
+	* cl-macs.el (cl-do-proclaim):
+	When proclaming something as inline, if it is bound as a label,
+	don't modify the symbol's plist; instead, treat the first element
+	of its placeholder constant vector as a place to store compile
+	information.
+	* cl-macs.el (declare):
+	Leave processing declarations while compiling to the
+	implementation of #'declare in
+	byte-compile-initial-macro-environment.
+
 2011-09-25  Aidan Kehoe  <kehoea@parhasard.net>
 
 	* files.el (binary-file-regexps):
--- a/lisp/bytecomp.el	Sun Sep 25 16:12:07 2011 +0100
+++ b/lisp/bytecomp.el	Sun Oct 02 15:32:16 2011 +0100
@@ -494,6 +494,11 @@
 	   (if byte-compile-delete-errors
 	       form
 	     (funcall (cdr (symbol-function 'the)) type form))))
+    (declare
+     . ,#'(lambda (&rest specs)
+	    (while specs
+	      (if (listp cl-declare-stack) (push (car specs) cl-declare-stack))
+	      (cl-do-proclaim (pop specs) nil))))
     (load-time-value
      . ,#'(lambda (form &optional read-only)
             (let* ((gensym (gensym))
@@ -517,37 +522,116 @@
                       (placeholders
                        (mapcar #'(lambda (lambda)
                                    (make-byte-code (second lambda) "\xc0\x87"
-                                                   [42] 1))
+                                                   ;; This list is used for
+                                                   ;; the byte-optimize
+                                                   ;; property, if the
+                                                   ;; function is to be
+                                                   ;; inlined. See
+                                                   ;; cl-do-proclaim.
+                                                   (vector nil) 1))
                                lambdas))
                       (byte-compile-macro-environment
                        (pairlis names (mapcar
                                        #'(lambda (placeholder)
                                            `(lambda (&rest cl-labels-args)
+                                              ;; Be careful not to quote
+                                              ;; PLACEHOLDER, otherwise
+                                              ;; byte-optimize-funcall inlines
+                                              ;; it.
                                               (list* 'funcall ,placeholder
                                                      cl-labels-args)))
                                        placeholders)
                                 byte-compile-macro-environment))
                       (gensym (gensym)))
-                 (put gensym 'byte-compile-label-alist
-                      (pairlis placeholders
-                               (mapcar 'second (mapcar 'cl-macroexpand-all
-                                                       lambdas))))
-                 (put gensym 'byte-compile
-                      #'(lambda (form)
-                          (let* ((byte-compile-label-alist
-                                  (get (car form) 'byte-compile-label-alist)))
-                            (dolist (acons byte-compile-label-alist)
-                              (setf (cdr acons)
-                                    (byte-compile-lambda (cdr acons))))
-                            (byte-compile-body-do-effect
-                             (sublis byte-compile-label-alist (cdr form)
-                                     :test #'eq))
-                            (dolist (acons byte-compile-label-alist)
-                              (nsubst (cdr acons) (car acons)
-                                      byte-compile-label-alist :test #'eq
-                                      :descend-structures t)))))
-                 (cl-macroexpand-all (cons gensym body)
-                                     byte-compile-macro-environment))))
+                 (labels
+                     ((byte-compile-transform-labels (form names lambdas
+                                                      placeholders)
+                        (let* ((inline
+                                 (mapcan
+                                  #'(lambda (name placeholder lambda)
+                                      (and
+                                       (eq
+                                        (getf (aref
+                                               (compiled-function-constants
+                                                placeholder) 0)
+                                              'byte-optimizer)
+                                        'byte-compile-inline-expand)
+                                       `(((function ,placeholder)
+                                          ,(byte-compile-lambda lambda)
+                                          (function ,lambda)))))
+                                  names placeholders lambdas))
+                               (compiled
+                                (mapcar #'byte-compile-lambda 
+                                        (if (not inline)
+                                            lambdas
+                                          ;; See further down for the
+                                          ;; rationale of the sublis calls.
+                                          (sublis (pairlis
+                                                   (mapcar #'cadar inline)
+                                                   (mapcar #'third inline))
+                                                  (sublis
+                                                   (pairlis
+                                                    (mapcar #'car inline)
+                                                    (mapcar #'second inline))
+                                                   lambdas :test #'equal)
+                                                  :test #'eq))))
+                               elt)
+                          (mapc #'(lambda (placeholder function)
+                                    (nsubst function placeholder compiled
+                                            :test #'eq
+                                            :descend-structures t))
+                                placeholders compiled)
+                          (when inline
+                            (dolist (triad inline)
+                              (nsubst (setq elt (elt compiled
+                                                     (position (cadar triad)
+                                                               placeholders)))
+                                      (second triad) compiled :test #'eq
+                                      :descend-structures t)
+                              (setf (second triad) elt))
+                            ;; For inlined labels: first, replace uses of
+                            ;; the placeholder in places where it's not an
+                            ;; evident, explicit funcall (that is, where
+                            ;; it is not to be inlined) with the compiled
+                            ;; function:
+                            (setq form (sublis
+                                        (pairlis (mapcar #'car inline)
+                                                 (mapcar #'second inline))
+                                        form :test #'equal)
+                                  ;; Now replace uses of the placeholder
+                                  ;; where it is an evident funcall with the
+                                  ;; lambda, quoted as a function, to allow
+                                  ;; byte-optimize-funcall to do its
+                                  ;; thing. Note that the lambdas still have
+                                  ;; the placeholders, so there's no risk
+                                  ;; of recursive inlining.
+                                  form (sublis (pairlis
+                                                (mapcar #'cadar inline)
+                                                (mapcar #'third inline))
+                                               form :test #'eq)))
+                          (sublis (pairlis placeholders compiled) form
+                                  :test #'eq))))
+                   (put gensym 'byte-compile
+                        #'(lambda (form)
+                            (let* ((names (cadr (cl-pop2 form)))
+                                   (lambdas (mapcar #'cadr (cdr (pop form))))
+                                   (placeholders (cadr (pop form))))
+                              (byte-compile-body-do-effect
+                               (byte-compile-transform-labels form names
+                                                              lambdas
+                                                              placeholders)))))
+                   (put gensym 'byte-hunk-handler
+                        #'(lambda (form)
+                            (let* ((names (cadr (cl-pop2 form)))
+                                   (lambdas (mapcar #'cadr (cdr (pop form))))
+                                   (placeholders (cadr (pop form))))
+                              (byte-compile-file-form
+                               (cons 'progn
+                                     (byte-compile-transform-labels
+                                      form names lambdas placeholders))))))
+                   (cl-macroexpand-all `(,gensym ',names (list ,@lambdas)
+                                         ',placeholders ,@body)
+                                       byte-compile-macro-environment)))))
     (flet .
       ,#'(lambda (bindings &rest body)
            (let* ((names (mapcar 'car bindings))
@@ -3699,10 +3783,9 @@
   (if (cddr form)
       (byte-compile-normal-call
        `(signal 'wrong-number-of-arguments '(function ,(length (cdr form)))))
-    (byte-compile-constant
-     (cond ((symbolp (nth 1 form))
-            (nth 1 form))
-           ((byte-compile-lambda (nth 1 form)))))))
+    (byte-compile-constant (if (eq 'lambda (car-safe (nth 1 form)))
+                               (byte-compile-lambda (nth 1 form))
+                             (nth 1 form)))))
 
 (defun byte-compile-insert (form)
   (cond ((null (cdr form))
--- a/lisp/cl-extra.el	Sun Sep 25 16:12:07 2011 +0100
+++ b/lisp/cl-extra.el	Sun Oct 02 15:32:16 2011 +0100
@@ -619,8 +619,11 @@
                      (cl-macroexpand-all (nth 1 (nth 2 (nth 2 found))) env)
                    ;; It's an atom, almost certainly a compiled function;
                    ;; we're using the implementation of labels in
-                   ;; bytecomp.el.
-                  (nth 2 (nth 2 found)))
+                   ;; bytecomp.el. Quote it with FUNCTION so that code can
+                   ;; tell uses as data apart from the uses with funcall,
+                   ;; where it's unquoted. #### We should warn if (car form)
+                   ;; above is quote, rather than function.
+                   (list 'function (nth 2 (nth 2 found))))
 	       form))))
 	((memq (car form) '(defun defmacro))
 	 (list* (car form) (nth 1 form) (cl-macroexpand-body (cddr form) env)))
--- a/lisp/cl-macs.el	Sun Sep 25 16:12:07 2011 +0100
+++ b/lisp/cl-macs.el	Sun Oct 02 15:32:16 2011 +0100
@@ -1969,18 +1969,38 @@
 
 	((eq (car-safe spec) 'inline)
 	 (while (setq spec (cdr spec))
-	   (or (memq (get (car spec) 'byte-optimizer)
-		     '(nil byte-compile-inline-expand))
-	       (error "%s already has a byte-optimizer, can't make it inline"
-		      (car spec)))
-	   (put (car spec) 'byte-optimizer 'byte-compile-inline-expand)))
-
+	   (let ((assq (cdr (assq (car spec) byte-compile-macro-environment))))
+	     (if (and (consp assq) (eq (nth 1 (nth 1 assq)) 'cl-labels-args)
+		      (atom (setq assq (nth 2 (nth 2 assq)))))
+		 ;; It's a label, and we're using the labels
+		 ;; implementation in bytecomp.el. Tell the compiler
+		 ;; to inline it, don't mark the symbol to be inlined
+		 ;; globally.
+		 (setf (getf (aref (compiled-function-constants assq) 0)
+                             'byte-optimizer)
+                       'byte-compile-inline-expand)
+	       (or (memq (get (car spec) 'byte-optimizer)
+			 '(nil byte-compile-inline-expand))
+		   (error
+		    "%s already has a byte-optimizer, can't make it inline"
+		    (car spec)))
+	       (put (car spec) 'byte-optimizer 'byte-compile-inline-expand)))))
 	((eq (car-safe spec) 'notinline)
 	 (while (setq spec (cdr spec))
-	   (if (eq (get (car spec) 'byte-optimizer)
-		   'byte-compile-inline-expand)
-	       (put (car spec) 'byte-optimizer nil))))
-
+	   (let ((assq (cdr (assq (car spec) byte-compile-macro-environment))))
+	     (if (and (consp assq) (eq (nth 1 (nth 1 assq)) 'cl-labels-args)
+		      (atom (setq assq (nth 2 (nth 2 assq)))))
+		 ;; It's a label, and we're using the labels
+		 ;; implementation in bytecomp.el. Tell the compiler
+		 ;; not to inline it.
+                 (if (eq 'byte-compile-inline-expand
+                         (getf (aref (compiled-function-constants assq) 0)
+                               'byte-optimizer))
+                     (remf (aref (compiled-function-constants assq) 0)
+                           'byte-optimizer))
+	       (if (eq (get (car spec) 'byte-optimizer)
+		       'byte-compile-inline-expand)
+		   (put (car spec) 'byte-optimizer nil))))))
 	((eq (car-safe spec) 'optimize)
 	 (let ((speed (assq (nth 1 (assq 'speed (cdr spec)))
 			    '((0 . nil) (1 . t) (2 . t) (3 . t))))
@@ -2014,14 +2034,8 @@
 
 ;;;###autoload
 (defmacro declare (&rest specs)
-  (if (cl-compiling-file)
-      (while specs
-	(if (listp cl-declare-stack) (push (car specs) cl-declare-stack))
-	(cl-do-proclaim (pop specs) nil)))
   nil)
 
-
-
 ;;; Generalized variables.
 
 ;;;###autoload
--- a/tests/ChangeLog	Sun Sep 25 16:12:07 2011 +0100
+++ b/tests/ChangeLog	Sun Oct 02 15:32:16 2011 +0100
@@ -1,3 +1,9 @@
+2011-10-02  Aidan Kehoe  <kehoea@parhasard.net>
+
+	* automated/lisp-tests.el:
+	* automated/lisp-tests.el (+):
+	Test #'labels and inlining.
+
 2011-09-04  Aidan Kehoe  <kehoea@parhasard.net>
 
 	* automated/lisp-reader-tests.el:
--- a/tests/automated/lisp-tests.el	Sun Sep 25 16:12:07 2011 +0100
+++ b/tests/automated/lisp-tests.el	Sun Oct 02 15:32:16 2011 +0100
@@ -2939,4 +2939,50 @@
   (Check-Error wrong-number-of-arguments (apply-partially))
   (Assert (equal (funcall construct-list) '(5 6 7))))
 
+;; Test labels and inlining.
+(labels
+    ((+ (&rest arguments)
+       ;; Shades of Java, hah.
+       (mapconcat #'prin1-to-string arguments ", "))
+     (print-with-commas (stream one two three four five)
+       (princ (+ one two three four five) stream))
+     (bookend (open close &rest arguments)
+       (refer-to-bookend (concat open (apply #'+ arguments) close)))
+     (refer-to-bookend (string)
+       (bookend "[" "]" string "hello" "there")))
+  (declare (inline + print-with-commas bookend refer-to-bookend))
+  (macrolet
+      ((with-first-arguments (&optional form)
+        (append form (list 1 [hi there] 40 "this is a string" pi)))
+       (with-second-arguments (&optional form)
+         (append form (list pi e ''hello ''there [40 50 60])))
+       (with-both-arguments (&optional form)
+         (append form
+                 (macroexpand '(with-first-arguments))
+                 (macroexpand '(with-second-arguments)))))
+
+    (with-temp-buffer
+      (Assert
+       (equal
+        (mapconcat #'prin1-to-string (with-first-arguments (list)) ", ")
+        (with-first-arguments (print-with-commas (current-buffer))))
+     "checking print-with-commas gives the expected result")
+      (Assert
+       (or
+        (not (compiled-function-p (indirect-function #'print-with-commas)))
+        (notany #'compiled-function-p
+                (compiled-function-constants
+                 (indirect-function #'print-with-commas))))
+       "checking the label + was inlined correctly")
+      (insert ", ")
+      ;; This call to + will be inline in compiled code, but there's
+      ;; no easy way for us to check that:
+      (Assert (null (insert (with-second-arguments (+)))))
+      (Assert (equal
+               (mapconcat #'prin1-to-string (with-both-arguments (list)) ", ")
+               (buffer-string))
+              "checking the buffer contents are as expected at the end.")
+      (Assert (not (funcall (intern "eq") #'bookend #'refer-to-bookend))
+	      "checking two mutually recursive functions compiled OK"))))
+
 ;;; end of lisp-tests.el