chiark / gitweb /
src/method-impl.lisp: Initialize `suppliedp' flags properly.
[sod] / src / codegen-proto.lisp
index 24b8c38ce53251fb078c1e5045df125f36f768ac..856e44e7f999ea188e061a2f48ec3fcaea54fad2 100644 (file)
@@ -7,7 +7,7 @@
 
 ;;;----- Licensing notice ---------------------------------------------------
 ;;;
-;;; This file is part of the Sensble Object Design, an object system for C.
+;;; This file is part of the Sensible Object Design, an object system for C.
 ;;;
 ;;; SOD is free software; you can redistribute it and/or modify
 ;;; it under the terms of the GNU General Public License as published by
@@ -38,34 +38,23 @@ (defgeneric format-temporary-name (var stream)
 (export 'var-in-use-p)
 (defgeneric var-in-use-p (var)
   (:documentation
-   "Answer whether VAR is currently being used.  See WITH-TEMPORARY-VAR.")
+   "Answer whether VAR is currently being used.  See `with-temporary-var'.")
   (:method (var)
     "Non-temporary variables are always in use."
+    (declare (ignore var))
     t))
 (defgeneric (setf var-in-use-p) (value var)
   (:documentation
-   "Record whether VAR is currently being used.  See WITH-TEMPORARY-VAR."))
+   "Record whether VAR is currently being used.  See `with-temporary-var'."))
 
 ;; Root class.
 
-(export 'temporary-name)
+(export '(temporary-name temp-tag))
 (defclass temporary-name ()
   ((tag :initarg :tag :reader temp-tag))
   (:documentation
    "Base class for temporary variable and argument names."))
 
-;; Important variables.
-
-(defparameter *temporary-index* 0
-  "Index for temporary name generation.
-
-   This is automatically reset to zero before the output functions are
-   invoked to write a file.  This way, we can ensure that the same output
-   file is always produced from the same input."
-  ;; FIXME: this is currently a lie.  Need some protocol to ensure that this
-  ;; happens.
-)
-
 ;; Important temporary names.
 
 (export '(*sod-ap* *sod-master-ap*))
@@ -73,6 +62,17 @@ (defparameter *sod-ap*
   (make-instance 'temporary-name :tag "sod__ap"))
 (defparameter *sod-master-ap*
   (make-instance 'temporary-name :tag "sod__master_ap"))
+(defparameter *sod-tmp-ap*
+  (make-instance 'temporary-name :tag "sod__tmp_ap"))
+(defparameter *sod-tmp-val*
+  (make-instance 'temporary-name :tag "sod__t"))
+(defparameter *sod-keywords*
+  (make-instance 'temporary-name :tag "sod__kw"))
+(defparameter *sod-key-pointer*
+  (make-instance 'temporary-name :tag "sod__keys"))
+
+(export '*null-pointer*)
+(defparameter *null-pointer* "NULL")
 
 ;;;--------------------------------------------------------------------------
 ;;; Instructions.
@@ -85,12 +85,12 @@ (defclass inst () ()
    "A base class for instructions.
 
    An `instruction' is anything which might be useful to string into a code
-   generator.  Both statements and expressions map can be represented by
-   trees of instructions.  The DEFINST macro is a convenient way of defining
-   new instructions.
+   generator.  Both statements and expressions can be represented by trees of
+   instructions.  The `definst' macro is a convenient way of defining new
+   instructions.
 
    The only important protocol for instructions is output, which is achieved
-   by calling PRINT-OBJECT with *PRINT-ESCAPE* nil.
+   by calling `print-object' with `*print-escape*' nil.
 
    This doesn't really do very much, but it acts as a handy marker for
    instruction subclasses."))
@@ -100,91 +100,89 @@ (defgeneric inst-metric (inst)
   (:documentation
    "Returns a `metric' describing how complicated INST is.
 
-   The default metric of an inst node is simply 1; INST subclasses generated
-   by DEFINST (q.v.) have an automatically generated method which returns one
-   plus the sum of the metrics of the node's children.
+   The default metric of an inst node is simply 1; `inst' subclasses
+   generated by `definst' (q.v.) have an automatically generated method which
+   returns one plus the sum of the metrics of the node's children.
 
    This isn't intended to be a particularly rigorous definition.  Its purpose
    is to allow code generators to make decisions about inlining or calling
    code fairly simply.")
-  (:method (inst) 1))
+  (:method ((inst t))
+    (declare (ignore inst))
+    1)
+  (:method ((inst null))
+    (declare (ignore inst))
+    1)
+  (:method ((inst list))
+    (reduce #'+ inst :key #'inst-metric)))
 
 ;; Instruction definition.
 
 (export 'definst)
-(defmacro definst (code (streamvar) args &body body)
+(defmacro definst (code (streamvar &key export) args &body body)
   "Define an instruction type and describe how to output it.
 
-   An INST can represent any structured piece of output syntax: a statement,
-   expression or declaration, for example.  This macro defines the following
-   things:
+   An `inst' can represent any structured piece of output syntax: a
+   statement, expression or declaration, for example.  This macro defines the
+   following things:
 
-     * A class CODE-INST to represent the instruction.
+     * A class `CODE-inst' to represent the instruction.
 
      * Instance slots named after the ARGS, with matching keyword initargs,
-       and INST-ARG readers.
+       and `inst-ARG' readers.
 
-     * A constructor MAKE-CODE-INST which accepts the ARGS (in order, not
-       with keywords) as arguments and returns a fresh instance.
+     * A constructor `make-CODE-inst' which accepts the ARGS (as an ordinary
+       BVL) as arguments and returns a fresh instance.
 
-     * A print method, which prints a diagnostic dump if *PRINT-ESCAPE* is
+     * A print method, which prints a diagnostic dump if `*print-escape*' is
        set, or invokes the BODY (with STREAMVAR bound to the output stream)
        otherwise.  The BODY is expected to produce target code at this
-       point."
-
-  (let ((inst-var (gensym "INST"))
-       (class-name (symbolicate code '-inst))
-       (keys (mapcar (lambda (arg) (intern (symbol-name arg) :keyword))
-                     args)))
+       point.
+
+   If EXPORT is non-nil, then export the `CODE-inst' and `make-CODE-inst'
+   symbols."
+
+  (let* ((inst-var (gensym "INST"))
+        (class-name (symbolicate code '-inst))
+        (constructor-name (symbolicate 'make- code '-inst))
+        (slots (mapcan (lambda (arg)
+                         (if (listp arg) (list (car arg))
+                             (let ((name (symbol-name arg)))
+                               (if (and (plusp (length name))
+                                        (char/= (char name 0) #\&))
+                                   (list arg)
+                                   nil))))
+                       args))
+        (keys (mapcar (lambda (arg) (intern (symbol-name arg) :keyword))
+                      slots)))
     `(progn
        (defclass ,class-name (inst)
-        ,(mapcar (lambda (arg key)
-                   `(,arg :initarg ,key :reader ,(symbolicate 'inst- arg)))
-                 args keys))
-       (defun ,(symbolicate 'make- code '-inst) (,@args)
-        (make-instance ',class-name ,@(mappend #'list keys args)))
+        ,(mapcar (lambda (slot key)
+                   `(,slot :initarg ,key
+                           :reader ,(symbolicate 'inst- slot)))
+                 slots keys))
+       (defun ,constructor-name (,@args)
+        (make-instance ',class-name ,@(mappend #'list keys slots)))
        (defmethod inst-metric ((,inst-var ,class-name))
-        (with-slots (,@args) ,inst-var
-          (+ 1 ,@(mapcar (lambda (arg) `(inst-metric ,arg)) args))))
+        (with-slots (,@slots) ,inst-var
+          (+ 1 ,@(mapcar (lambda (slot) `(inst-metric ,slot)) slots))))
        (defmethod print-object ((,inst-var ,class-name) ,streamvar)
-        (with-slots (,@args) ,inst-var
+        (with-slots (,@slots) ,inst-var
           (if *print-escape*
               (print-unreadable-object (,inst-var ,streamvar :type t)
                 (format stream "~@<~@{~S ~@_~S~^ ~_~}~:>"
-                        ,@(mappend #'list keys args)))
-              (progn ,@body)))))))
-
-;; Important instruction classes.
-
-(export '(block-inst make-block-inst var-inst make-var-inst
-         function-inst make-function-inst set-inst make-set-inst
-         return-inst make-return-inst expr-inst make-expr-inst
-         inst-decls inst-body inst-name inst-type inst-init inst-var
-         inst-expr))
-
-(definst var (stream) (name type init)
-  (pprint-c-type type stream name)
-  (when init
-    (format stream " = ~A" init)))
-(definst set (stream) (var expr)
-  (format stream "~@<~A = ~@_~2I~A;~:>" var expr))
-(definst return (stream) (expr)
-  (format stream "return~@[ (~A)~];" expr))
-(definst expr (stream) (expr)
-  (format stream "~A;" expr))
-(definst block (stream) (decls body)
-  (format stream "{~:@_~@<  ~2I~@[~{~A;~:@_~}~:@_~]~{~A~^~:@_~}~:>~:@_}"
-         decls body))
-(definst function (stream) (name type body)
-  (pprint-logical-block (stream nil)
-    (princ "static " stream)
-    (pprint-c-type type stream name)
-    (format stream "~:@_~A~:@_~:@_" body)))
+                        ,@(mappend #'list keys slots)))
+              (block ,code ,@body))))
+       ,@(and export `((export '(,class-name ,constructor-name
+                                ,@(mapcar (lambda (slot)
+                                            (symbolicate 'inst- slot))
+                                          slots)))))
+       ',code)))
 
 ;; Formatting utilities.
 
 (defun format-compound-statement* (stream child morep thunk)
-  "Underlying function for FORMAT-COMPOUND-STATEMENT."
+  "Underlying function for `format-compound-statement'."
   (cond ((typep child 'block-inst)
         (funcall thunk stream)
         (write-char #\space stream)
@@ -197,13 +195,13 @@ (defun format-compound-statement* (stream child morep thunk)
           (pprint-indent :block 2 stream)
           (pprint-newline :linear stream)
           (princ child stream)
-          (pprint-indent :block 0 stream)
-          (case morep
-            (:space
-             (write-char #\space stream)
-             (pprint-newline :linear stream))
-            ((t)
-             (pprint-newline :mandatory stream)))))))
+          (pprint-indent :block 0 stream))
+        (case morep
+          (:space
+           (write-char #\space stream)
+           (pprint-newline :linear stream))
+          ((t)
+           (pprint-newline :mandatory stream))))))
 
 (export 'format-compound-statement)
 (defmacro format-compound-statement
@@ -211,11 +209,129 @@ (defmacro format-compound-statement
   "Format a compound statement to STREAM.
 
    The introductory material is printed by BODY.  The CHILD is formatted
-   properly according to whether it's a BLOCK-INST.  If MOREP is true, then
+   properly according to whether it's a `block-inst'.  If MOREP is true, then
    allow for more stuff following the child."
   `(format-compound-statement* ,stream ,child ,morep
                               (lambda (,stream) ,@body)))
 
+(export 'format-banner-comment)
+(defun format-banner-comment (stream control &rest args)
+  (format stream "~@</~@<* ~@;~?~:>~_ */~:>" control args))
+
+;; Important instruction classes.
+
+;; HACK: Some of the slot names we'd like to use are external symbols in our
+;; package or the `common-lisp' package.  Use gensyms for these slot names to
+;; prevent them from leaking.
+
+(definst var (stream :export t) (name #1=#:type &optional init)
+  (pprint-logical-block (stream nil)
+    (pprint-c-type #1# stream name)
+    (when init
+      (format stream " = ~2I~_~A" init))
+    (write-char #\; stream)))
+
+(definst function (stream :export t)
+    (name #1=#:type body &optional #2=#:banner &rest banner-args)
+  (pprint-logical-block (stream nil)
+    (when #2#
+      (apply #'format-banner-comment stream #2# banner-args)
+      (pprint-newline :mandatory stream))
+    (princ "static " stream)
+    (pprint-c-type #1# stream name)
+    (format stream "~:@_~A~:@_~:@_" body)))
+
+;; Expression statements.
+(definst expr (stream :export t) (#1=#:expr)
+  (format stream "~A;" #1#))
+(definst set (stream :export t) (var #1=#:expr)
+  (format stream "~@<~A = ~2I~_~A;~:>" var #1#))
+(definst update (stream :export t) (var op #1=#:expr)
+  (format stream "~@<~A ~A= ~2I~_~A;~:>" var op #1#))
+
+;; Special kinds of expressions.
+(definst call (stream :export t) (#1=#:func &rest args)
+  (format stream "~@<~A~4I~_(~@<~{~A~^, ~_~}~:>)~:>" #1# args))
+(definst cond (stream :export t) (#1=#:cond conseq alt)
+  (format stream "~@<~A ~2I~@_~@<? ~A ~_: ~A~:>~:>" #1# conseq alt))
+
+;; Simple statements.
+(definst return (stream :export t) (#1=#:expr)
+  (format stream "return~@[ (~A)~];" #1#))
+(definst break (stream :export t) ()
+  (format stream "break;"))
+(definst continue (stream :export t) ()
+  (format stream "continue;"))
+
+;; Compound statements.
+
+(defvar *first-statement-p* t
+  "True if this is the first statement in a block.
+
+   This is used to communicate between `block-inst' and `banner-inst' so that
+   they get the formatting right between them.")
+
+(definst banner (stream :export t) (control &rest args)
+  (pprint-logical-block (stream nil)
+    (unless *first-statement-p* (pprint-newline :mandatory stream))
+    (apply #'format-banner-comment stream control args)))
+
+(export 'emit-banner)
+(defun emit-banner (codegen control &rest args)
+  (emit-inst codegen (apply #'make-banner-inst control args)))
+
+(definst block (stream :export t) (decls body)
+  (write-char #\{ stream)
+  (pprint-newline :mandatory stream)
+  (pprint-logical-block (stream nil)
+    (let ((newlinep nil))
+      (flet ((newline ()
+              (if newlinep
+                  (pprint-newline :mandatory stream)
+                  (setf newlinep t))))
+       (pprint-indent :block 2 stream)
+       (write-string "  " stream)
+       (when decls
+         (dolist (decl decls)
+           (newline)
+           (write decl :stream stream))
+         (when body (newline)))
+       (let ((*first-statement-p* t))
+         (dolist (inst body)
+           (newline)
+           (write inst :stream stream)
+           (setf *first-statement-p* nil))))))
+  (pprint-newline :mandatory stream)
+  (write-char #\} stream))
+
+(definst if (stream :export t) (#1=#:cond conseq &optional alt)
+  (let ((stmt "if"))
+    (loop (format-compound-statement (stream conseq (if alt t nil))
+           (format stream "~A (~A)" stmt #1#))
+         (typecase alt
+           (null (return))
+           (if-inst (setf stmt "else if"
+                          #1# (inst-cond alt)
+                          conseq (inst-conseq alt)
+                          alt (inst-alt alt)))
+           (t (format-compound-statement (stream alt)
+                (format stream "else"))
+              (return))))))
+
+(definst while (stream :export t) (#1=#:cond body)
+  (format-compound-statement (stream body)
+    (format stream "while (~A)" #1#)))
+
+(definst do-while (stream :export t) (body #1=#:cond)
+  (format-compound-statement (stream body :space)
+    (write-string "do" stream))
+  (format stream "while (~A);" #1#))
+
+(definst for (stream :export t) (init #1=#:cond update body)
+  (format-compound-statement (stream body)
+    (format stream "for (~@<~@[~A~];~@[ ~_~A~];~@[ ~_~A~]~:>)"
+           init #1# update)))
+
 ;;;--------------------------------------------------------------------------
 ;;; Code generation.
 
@@ -224,16 +340,16 @@ (defmacro format-compound-statement
 (export 'codegen-functions)
 (defgeneric codegen-functions (codegen)
   (:documentation
-   "Return the list of FUNCTION-INSTs of completed functions."))
+   "Return the list of `function-inst's of completed functions."))
 
 (export 'ensure-var)
 (defgeneric ensure-var (codegen name type &optional init)
   (:documentation
    "Add a variable to CODEGEN's list.
 
-   The variable is called NAME (which should be comparable using EQUAL and
+   The variable is called NAME (which should be comparable using `equal' and
    print to an identifier) and has the given TYPE.  If INIT is present and
-   non-nil it is an expression INST used to provide the variable with an
+   non-nil it is an expression `inst' used to provide the variable with an
    initial value."))
 
 (export '(emit-inst emit-insts))
@@ -246,6 +362,14 @@ (defgeneric emit-insts (codegen insts)
   (:method (codegen insts)
     (dolist (inst insts) (emit-inst codegen inst))))
 
+(export '(emit-decl emit-decls))
+(defgeneric emit-decl (codegen inst)
+  (:documentation
+   "Add INST to the end of CODEGEN's list of declarations."))
+(defgeneric emit-decls (codegen insts)
+  (:documentation
+   "Add a list of INSTS to the end of CODEGEN's list of declarations."))
+
 (export 'codegen-push)
 (defgeneric codegen-push (codegen)
   (:documentation
@@ -267,7 +391,7 @@ (defgeneric codegen-add-function (codegen function)
    "Adds a function to CODEGEN's list.
 
    Actually, we're not picky: FUNCTION can be any kind of object that you're
-   willing to find in the list returned by CODEGEN-FUNCTIONS."))
+   willing to find in the list returned by `codegen-functions'."))
 
 (export 'temporary-var)
 (defgeneric temporary-var (codegen type)
@@ -276,48 +400,55 @@ (defgeneric temporary-var (codegen type)
 
    The temporary variable will have the given TYPE, and will be marked
    in-use.  You should clear the in-use flag explicitly when you've finished
-   with the variable -- or, better, use WITH-TEMPORARY-VAR to do the cleanup
-   automatically."))
+   with the variable -- or, better, use `with-temporary-var' to do the
+   cleanup automatically."))
 
 (export 'codegen-build-function)
-(defun codegen-build-function (codegen name type vars insts)
+(defun codegen-build-function
+    (codegen name type vars insts &optional banner &rest banner-args)
   "Build a function and add it to CODEGEN's list.
 
    Returns the function's name."
   (codegen-add-function codegen
-                       (make-function-inst name type
-                                           (make-block-inst vars insts)))
+                       (apply #'make-function-inst name type
+                              (make-block-inst vars insts)
+                              banner banner-args))
   name)
 
 (export 'codegen-pop-block)
 (defgeneric codegen-pop-block (codegen)
   (:documentation
-   "Makes a block (BLOCK-INST) out of the completed code in CODEGEN.")
+   "Makes a block (`block-inst') out of the completed code in CODEGEN.")
   (:method (codegen)
     (multiple-value-bind (vars insts) (codegen-pop codegen)
       (make-block-inst vars insts))))
 
 (export 'codegen-pop-function)
-(defgeneric codegen-pop-function (codegen name type)
+(defgeneric codegen-pop-function
+    (codegen name type &optional banner &rest banner-args)
   (:documentation
    "Makes a function out of the completed code in CODEGEN.
 
    The NAME can be any object you like.  The TYPE should be a function type
    object which includes argument names.  The return value is the NAME.")
-  (:method (codegen name type)
+  (:method (codegen name type &optional banner &rest banner-args)
     (multiple-value-bind (vars insts) (codegen-pop codegen)
-      (codegen-build-function codegen name type vars insts))))
+      (apply #'codegen-build-function codegen name type vars insts
+            banner banner-args))))
 
 (export 'with-temporary-var)
 (defmacro with-temporary-var ((codegen var type) &body body)
   "Evaluate BODY with VAR bound to a temporary variable name.
 
    During BODY, VAR will be marked in-use; when BODY ends, VAR will be marked
-  available for re-use."
-  `(let ((,var (temporary-var ,codegen ,type)))
-     (unwind-protect
-         (progn ,@body)
-       (setf (var-in-use-p ,var) nil))))
+   available for re-use."
+  (multiple-value-bind (doc decls body) (parse-body body :docp nil)
+    (declare (ignore doc))
+    `(let ((,var (temporary-var ,codegen ,type)))
+       ,@decls
+       (unwind-protect
+           (progn ,@body)
+        (setf (var-in-use-p ,var) nil)))))
 
 ;;;--------------------------------------------------------------------------
 ;;; Code generation idioms.
@@ -328,22 +459,22 @@ (defun deliver-expr (codegen target expr)
 
    The TARGET may be one of the following.
 
-     * :VOID, indicating that the value is to be discarded.  The expression
+     * `:void', indicating that the value is to be discarded.  The expression
        will still be evaluated.
 
-     * :VOID-RETURN, indicating that the value is to be discarded (as for
-       :VOID) and furthermore a `return' from the current function should be
-       forced after computing the value.
+     * `:void-return', indicating that the value is to be discarded (as for
+       `:void') and furthermore a `return' from the current function should
+       be forced after computing the value.
 
-     * :RETURN, indicating that the value is to be returned from the current
-       function.
+     * `:return', indicating that the value is to be returned from the
+       current function.
 
      * A variable name, indicating that the value is to be stored in the
        variable.
 
-   In the cases of :RETURN, :VOID and :VOID-RETURN targets, it is valid for
-   EXPR to be nil; this signifies that no computation needs to be performed.
-   Variable-name targets require an expression."
+   In the cases of `:return', `:void' and `:void-return' targets, it is valid
+   for EXPR to be nil; this signifies that no computation needs to be
+   performed.  Variable-name targets require an expression."
 
   (case target
     (:return (emit-inst codegen (make-return-inst expr)))
@@ -354,24 +485,24 @@ (defun deliver-expr (codegen target expr)
 
 (export 'convert-stmts)
 (defun convert-stmts (codegen target type func)
-  "Invoke FUNC to deliver a value to a non-:RETURN target.
+  "Invoke FUNC to deliver a value to a non-`:return' target.
 
-   FUNC is a function which accepts a single argument, a non-:RETURN target,
-   and generates statements which deliver a value (see DELIVER-EXPR) of the
-   specified TYPE to this target.  In general, the generated code will have
-   the form
+   FUNC is a function which accepts a single argument, a non-`:return'
+   target, and generates statements which deliver a value (see
+   `deliver-expr') of the specified TYPE to this target.  In general, the
+   generated code will have the form
 
      setup instructions...
-     (DELIVER-EXPR CODEGEN TARGET (compute value...))
+     (deliver-expr CODEGEN TARGET (compute value...))
      cleanup instructions...
 
    where the cleanup instructions are essential to the proper working of the
    generated program.
 
-   CONVERT-STMTS will call FUNC to generate code, and arrange that its value
-   is correctly delivered to TARGET, regardless of what the TARGET is --
-   i.e., it lifts the restriction to non-:RETURN targets.  It does this by
-   inventing a new temporary variable."
+   The `convert-stmts' function will call FUNC to generate code, and arrange
+   that its value is correctly delivered to TARGET, regardless of what the
+   TARGET is -- i.e., it lifts the restriction to non-`:return' targets.  It
+   does this by inventing a new temporary variable."
 
   (case target
     (:return (with-temporary-var (codegen var type)
@@ -381,4 +512,9 @@ (defun convert-stmts (codegen target type func)
                  (emit-inst codegen (make-return-inst nil)))
     (t (funcall func target))))
 
+(export 'deliver-call)
+(defun deliver-call (codegen target func &rest args)
+  "Emit a statement to call FUNC with ARGS and deliver the result to TARGET."
+  (deliver-expr codegen target (apply #'make-call-inst func args)))
+
 ;;;----- That's all, folks --------------------------------------------------