Very ragged work-in-progress.
[sod] / codegen.lisp
diff --git a/codegen.lisp b/codegen.lisp
new file mode 100644 (file)
index 0000000..6419c0f
--- /dev/null
@@ -0,0 +1,470 @@
+;;; -*-lisp-*-
+;;;
+;;; Code generator for effective methods
+;;;
+;;; (c) 2009 Straylight/Edgeware
+;;;
+
+;;;----- Licensing notice ---------------------------------------------------
+;;;
+;;; This file is part of the Simple Object Definition system.
+;;;
+;;; SOD is free software; you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 2 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; SOD is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with SOD; if not, write to the Free Software Foundation,
+;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+(cl:in-package #:sod)
+
+;;;--------------------------------------------------------------------------
+;;; Temporary names.
+
+(defclass temporary-name ()
+  ((tag :initarg :tag :reader temp-tag))
+  (:documentation
+   "Base class for temporary variable and argument names."))
+
+(defclass temporary-argument (temporary-name) ())
+(defclass temporary-function (temporary-name) ())
+
+(defclass temporary-variable (temporary-name)
+  ((in-use-p :initarg :in-use-p
+            :initform nil
+            :type boolean
+            :accessor var-in-use-p)))
+
+(defmethod var-in-use-p ((var t))
+  "Non-temporary variables are always in use."
+  t)
+
+(defmethod commentify-argument-name ((name temporary-name))
+  nil)
+
+(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.")
+
+(defun temporary-function ()
+  "Return a temporary function name."
+  (make-instance 'temporary-function
+                :tag (prog1 *temporary-index* (incf *temporary-index*))))
+
+(defgeneric format-temporary-name (var stream)
+  (:method ((var temporary-name) stream)
+    (format stream "~A" (temp-tag var)))
+  (:method ((var temporary-argument) stream)
+    (format stream "sod__a~A" (temp-tag var)))
+  (:method ((var temporary-variable) stream)
+    (format stream "sod__v~A" (temp-tag var)))
+  (:method ((var temporary-function) stream)
+    (format stream "sod__f~A" (temp-tag var))))
+
+(defmethod print-object ((var temporary-name) stream)
+  (if *print-escape*
+      (print-unreadable-object (var stream :type t)
+       (prin1 (temp-tag var) stream))
+      (format-temporary-name var stream)))
+
+(defparameter *sod-ap*
+  (make-instance 'temporary-name :tag "sod__ap"))
+(defparameter *sod-master-ap*
+  (make-instance 'temporary-name :tag "sod__master_ap"))
+
+;;;--------------------------------------------------------------------------
+;;; Instructions.
+
+(defclass inst () ()
+  (:documentation
+   "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.
+
+   The only important protocol for instructions is output, which is achieved
+   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."))
+
+(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.
+
+   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))
+
+(defmacro definst (code (streamvar) 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:
+
+     * A class CODE-INST to represent the instruction.
+
+     * Instance slots named after the ARGS, with matching keyword initargs,
+       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 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)))
+    `(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)))
+       (defmethod inst-metric ((,inst-var ,class-name))
+        (with-slots (,@args) ,inst-var
+          (+ 1 ,@(mapcar (lambda (arg) `(inst-metric ,arg)) args))))
+       (defmethod print-object ((,inst-var ,class-name) ,streamvar)
+        (with-slots (,@args) ,inst-var
+          (if *print-escape*
+              (print-unreadable-object (,inst-var ,streamvar :type t)
+                (format stream "~@<~@{~S ~@_~S~^ ~_~}~:>"
+                        ,@(mappend #'list keys args)))
+              (progn ,@body)))))))
+
+(defun format-compound-statement* (stream child morep thunk)
+  "Underlying function for FORMAT-COMPOUND-STATEMENT."
+  (cond ((typep child 'block-inst)
+        (funcall thunk stream)
+        (write-char #\space stream)
+        (princ child stream)
+        (when morep (write-char #\space stream)))
+       (t
+        (pprint-logical-block (stream nil)
+          (funcall thunk stream)
+          (write-char #\space stream)
+          (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)))))))
+
+(defmacro format-compound-statement
+    ((stream child &optional morep) &body body)
+  "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
+   allow for more stuff following the child."
+  `(format-compound-statement* ,stream ,child ,morep
+                              (lambda (,stream) ,@body)))
+
+;;;--------------------------------------------------------------------------
+;;; Instruction types.
+
+;; Compound statements.
+
+(definst block (stream) (decls body)
+  (format stream "{~:@_~@<  ~2I~@[~{~A;~:@_~}~:@_~]~{~A~^~:@_~}~:>~:@_}"
+         decls body))
+
+(definst if (stream) (condition consequent alternative)
+  (format-compound-statement (stream consequent alternative)
+    (format stream "if (~A)" condition))
+  (when alternative
+    (format-compound-statement (stream alternative)
+      (write-string "else" stream))))
+
+(definst while (stream) (condition body)
+  (format-compound-statement (stream body)
+    (format stream "while (~A)" condition)))
+
+(definst do-while (stream) (body condition)
+  (format-compound-statement (stream body :space)
+    (write-string "do" stream))
+  (format stream "while (~A);" condition))
+
+;; Simple statements.
+
+(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))
+
+;; Special varargs hacks.
+
+(definst va-start (stream) (ap arg)
+  (format stream "va_start(~@<~A, ~_~A~:>);" ap arg))
+
+(definst va-copy (stream) (to from)
+  (format stream "va_copy(~@<~A, ~_~A~:>);" to from))
+
+(definst va-end (stream) (ap)
+  (format stream "va_end(~A);" ap))
+
+;; Declarations.  These should appear at the heads of BLOCK-INSTs.
+
+(definst var (stream) (name type init)
+  (pprint-c-type type stream name)
+  (when init
+    (format stream " = ~A" init)))
+
+;; Expressions.
+
+(definst call (stream) (func args)
+  (format stream "~A(~@<~{~A~^, ~_~}~:>)" func args))
+
+;; Top level things.
+
+(definst function (stream) (name type body)
+  (pprint-logical-block (stream nil)
+    (pprint-c-type type stream name)
+    (format stream "~:@_~A~:@_~:@_" body)))
+
+;;;--------------------------------------------------------------------------
+;;; Code generator objects.
+
+(defclass basic-codegen ()
+  ((vars :initarg :vars :initform nil :type list :accessor codegen-vars)
+   (insts :initarg :insts :initform nil :type list :accessor codegen-insts)
+   (temp-index :initarg :temp-index
+              :initform 0
+              :type fixnum
+              :accessor codegen-temp-index))
+  (:documentation
+   "Base class for code generator state.
+
+   This contains the bare essentials for supporting the EMIT-INST and
+   ENSURE-VAR protocols; see the documentation for those generic functions
+   for more details.
+
+   This class isn't abstract.  A full CODEGEN object uses instances of this
+   to keep track of pending functions which haven't been completed yet.
+
+   Just in case that wasn't clear enough: this is nothing to do with the
+   BASIC language."))
+
+(defgeneric emit-inst (codegen inst)
+  (:documentation
+   "Add INST to the end of CODEGEN's list of instructions.")
+  (:method ((codegen basic-codegen) inst)
+    (push inst (codegen-insts codegen))))
+
+(defgeneric emit-insts (codegen insts)
+  (:documentation
+   "Add a list of INSTS to the end of CODEGEN's list of instructions.")
+  (:method ((codegen basic-codegen) insts)
+    (setf (codegen-insts codegen)
+         (revappend insts (codegen-insts codegen)))))
+
+(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
+   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
+   initial value.")
+  (:method ((codegen basic-codegen) name type &optional init)
+    (let* ((vars (codegen-vars codegen))
+          (var (find name vars :key #'inst-name :test #'equal)))
+      (cond ((not var)
+            (setf (codegen-vars codegen)
+                  (cons (make-var-inst name type init) vars)))
+           ((not (c-type-equal-p type (inst-type var)))
+            (error "(Internal) Redefining type for variable ~A." name)))
+      name)))
+
+(defclass codegen (basic-codegen)
+  ((functions :initform nil :type list :accessor codegen-functions)
+   (stack :initform nil :type list :accessor codegen-stack))
+  (:documentation
+   "A full-fat code generator which can generate and track functions.
+
+   This is the real deal.  Subclasses may which to attach additional state
+   for convenience's sake, but this class is self-contained.  It supports the
+   CODEGEN-PUSH, CODEGEN-POP and CODEGEN-POP-FUNCTION protocols."))
+
+(defgeneric codegen-push (codegen)
+  (:documentation
+   "Pushes the current code generation state onto a stack.
+
+   The state consists of the accumulated variables and instructions, i.e.,
+   what is representable by a BASIC-CODEGEN.")
+  (:method ((codegen codegen))
+    (with-slots (vars insts temp-index stack) codegen
+      (push (make-instance 'basic-codegen
+                          :vars vars
+                          :insts insts
+                          :temp-index temp-index)
+           stack)
+      (setf vars nil insts nil temp-index 0))))
+
+(defgeneric codegen-pop (codegen)
+  (:documentation
+   "Pops a saved state off of the CODEGEN's stack.
+
+   Returns the newly accumulated variables and instructions as lists, as
+   separate values.")
+  (:method ((codegen codegen))
+    (with-slots (vars insts temp-index stack) codegen
+      (multiple-value-prog1
+         (values (nreverse vars) (nreverse insts))
+       (let ((sub (pop stack)))
+         (setf vars (codegen-vars sub)
+               insts (codegen-insts sub)
+               temp-index (codegen-temp-index sub)))))))
+
+(defgeneric codegen-add-function (codegen function)
+  (:documentation
+   "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.")
+  (:method ((codegen codegen) function)
+    (with-slots (functions) codegen
+      (setf functions (nconc functions (list function))))))
+
+(defun codegen-build-function (codegen name type vars insts)
+  "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)))
+  name)
+
+(defgeneric codegen-pop-function (codegen name type)
+  (: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 codegen) name type)
+    (multiple-value-bind (vars insts) (codegen-pop codegen)
+      (codegen-build-function codegen name type vars insts))))
+
+(defgeneric temporary-var (codegen type)
+  (:documentation
+   "Return the name of a temporary variable.
+
+   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."))
+
+(defmethod temporary-var ((codegen basic-codegen) type)
+  (with-slots (vars temp-index) codegen
+    (or (find-if (lambda (var)
+                  (and (not (var-in-use-p (inst-name var)))
+                       (c-type-equal-p type (inst-type var))))
+                vars)
+       (let* ((name (make-instance 'temporary-variable
+                                   :tag (prog1 temp-index
+                                          (incf temp-index)))))
+         (push (make-var-inst name type nil) vars)
+         name))))
+
+(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))))
+
+;;;--------------------------------------------------------------------------
+;;; Code generation idioms.
+
+(defun deliver-expr (codegen target expr)
+  "Emit code to deliver the value of EXPR to the TARGET.
+
+   The TARGET may be one of the following.
+
+     * :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.
+
+     * :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."
+
+  (case target
+    (:return (emit-inst codegen (make-return-inst expr)))
+    (:void (when expr (emit-inst codegen (make-expr-inst expr))))
+    (:void-return (when expr (emit-inst codegen (make-expr-inst expr)))
+                 (emit-inst codegen (make-return-inst nil)))
+    (t (emit-inst codegen (make-set-inst target expr)))))
+
+(defun convert-stmts (codegen target type func)
+  "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
+
+     setup instructions...
+     (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."
+
+  (case target
+    (:return (with-temporary-var (codegen var type)
+              (funcall func var)
+              (deliver-expr codegen target var)))
+    (:void-return (funcall func :void)
+                 (emit-inst codegen (make-return-inst nil)))
+    (t (funcall func target))))
+
+;;;----- That's all, folks --------------------------------------------------