chiark / gitweb /
df068ed3eeee9ab5b2f472630f747c20a5782b4f
[sod] / class-layout.lisp
1 ;;; -*-lisp-*-
2 ;;;
3 ;;; Layout for instances and vtables
4 ;;;
5 ;;; (c) 2009 Straylight/Edgeware
6 ;;;
7
8 ;;;----- Licensing notice ---------------------------------------------------
9 ;;;
10 ;;; This file is part of the Simple Object Definition system.
11 ;;;
12 ;;; SOD is free software; you can redistribute it and/or modify
13 ;;; it under the terms of the GNU General Public License as published by
14 ;;; the Free Software Foundation; either version 2 of the License, or
15 ;;; (at your option) any later version.
16 ;;;
17 ;;; SOD is distributed in the hope that it will be useful,
18 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;;; GNU General Public License for more details.
21 ;;;
22 ;;; You should have received a copy of the GNU General Public License
23 ;;; along with SOD; if not, write to the Free Software Foundation,
24 ;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 (cl:in-package #:sod)
27
28 ;;;--------------------------------------------------------------------------
29 ;;; Effective slot objects.
30
31 (defclass effective-slot ()
32   ((class :initarg :class :type sod-slot :reader effective-slot-class)
33    (slot :initarg :slot :type sod-slot :reader effective-slot-direct-slot)
34    (initializer :initarg :initializer :type (or sod-initializer null)
35                 :reader effective-slot-initializer))
36   (:documentation
37    "Describes a slot and how it's meant to be initialized.
38
39    Effective slot objects are usually attached to layouts."))
40
41 (defgeneric find-slot-initializer (class slot)
42   (:documentation
43    "Return the most specific initializer for SLOT, starting from CLASS."))
44
45 (defgeneric compute-effective-slot (class slot)
46   (:documentation
47    "Construct an effective slot from the supplied direct slot.
48
49    SLOT is a direct slot defined on CLASS or one of its superclasses.
50    (Metaclass initializers are handled using a different mechanism.)"))
51
52 (defmethod print-object ((slot effective-slot) stream)
53   (maybe-print-unreadable-object (slot stream :type t)
54     (format stream "~A~@[ = ~@_~A~]"
55             (effective-slot-direct-slot slot)
56             (effective-slot-initializer slot))))
57
58 (defmethod find-slot-initializer ((class sod-class) (slot sod-slot))
59   (some (lambda (super)
60           (find slot
61                 (sod-class-instance-initializers super)
62                 :key #'sod-initializer-slot))
63         (sod-class-precedence-list class)))
64
65 (defmethod compute-effective-slot ((class sod-class) (slot sod-slot))
66   (make-instance 'effective-slot
67                  :slot slot
68                  :class class
69                  :initializer (find-slot-initializer class slot)))
70
71 ;;;--------------------------------------------------------------------------
72 ;;; Instance layout objects.
73
74 ;;; islots
75
76 (defclass islots ()
77   ((class :initarg :class :type sod-class :reader islots-class)
78    (subclass :initarg :subclass :type sod-class :reader islots-subclass)
79    (slots :initarg :slots :type list :reader islots-slots))
80   (:documentation
81    "The collection of effective SLOTS defined by an instance of CLASS."))
82
83 (defmethod print-object ((islots islots) stream)
84   (print-unreadable-object (islots stream :type t)
85     (format stream "~A <= ~A ~_~:<~@{~S~^ ~_~}~:>"
86             (islots-subclass islots)
87             (islots-class islots)
88             (islots-slots islots))))
89
90 (defgeneric compute-islots (class subclass)
91   (:documentation
92    "Return ISLOTS containing EFFECTIVE-SLOTs for a particular CLASS.
93
94    Initializers for the slots should be taken from the most specific
95    superclass of SUBCLASS."))
96
97 ;;; vtable-pointer
98
99 (defclass vtable-pointer ()
100   ((class :initarg :class :type sod-class :reader vtable-pointer-class)
101    (chain-head :initarg :chain-head :type sod-class
102                :reader vtable-pointer-chain-head))
103   (:documentation
104    "A pointer to the vtable for CLASS corresponding to a particular CHAIN."))
105
106 (defmethod print-object ((vtp vtable-pointer) stream)
107   (print-unreadable-object (vtp stream :type t)
108     (format stream "~A:~A"
109             (vtable-pointer-class vtp)
110             (sod-class-nickname (vtable-pointer-chain-head vtp)))))
111
112 ;;; ichain
113
114 (defclass ichain ()
115   ((class :initarg :class :type sod-class :reader ichain-class)
116    (chain-head :initarg :chain-head :type sod-class :reader ichain-head)
117    (body :initarg :body :type list :reader ichain-body))
118   (:documentation
119    "All of the instance layout for CLASS corresponding to a particular CHAIN.
120
121    The BODY is a list of things to include in the finished structure.  By
122    default, it contains a VTABLE-POINTER and ISLOTS for each class in the
123    chain."))
124
125 (defmethod print-object ((ichain ichain) stream)
126   (print-unreadable-object (ichain stream :type t)
127     (format stream "~A:~A ~_~:<~@{~S~^ ~_~}~:>"
128             (ichain-class ichain)
129             (sod-class-nickname (ichain-head ichain))
130             (ichain-body ichain))))
131
132 (defgeneric compute-ichain (class chain)
133   (:documentation
134    "Return an ICHAIN for a particular CHAIN of CLASS's superclasses.
135
136    The CHAIN is a list of classes, with the least specific first -- so the
137    chain head is the first element."))
138
139 ;;; ilayout
140
141 (defclass ilayout ()
142   ((class :initarg :class :type sod-class :reader ilayout-class)
143    (ichains :initarg :ichains :type list :reader ilayout-ichains))
144   (:documentation
145    "All of the instance layout for a CLASS.
146
147    Consists of an ICHAIN for each distinct chain."))
148
149 (defmethod print-object ((ilayout ilayout) stream)
150   (print-unreadable-object (ilayout stream :type t)
151     (format stream "~A ~_~:<~@{~S~^ ~_~}~:>"
152             (ilayout-class ilayout)
153             (ilayout-ichains ilayout))))
154
155 (defgeneric compute-ilayout (class)
156   (:documentation
157    "Compute and return an instance layout for CLASS."))
158
159 ;;; Standard implementation.
160
161 (defmethod compute-islots ((class sod-class) (subclass sod-class))
162   (make-instance 'islots
163                  :class class
164                  :subclass subclass
165                  :slots (mapcar (lambda (slot)
166                                   (compute-effective-slot subclass slot))
167                                 (sod-class-slots class))))
168
169 (defmethod compute-ichain ((class sod-class) chain)
170   (let* ((head (car chain))
171          (vtable-pointer (make-instance 'vtable-pointer
172                                         :class class
173                                         :chain-head head))
174          (islots (remove-if-not #'islots-slots
175                                 (mapcar (lambda (super)
176                                           (compute-islots super class))
177                                         chain))))
178     (make-instance 'ichain
179                    :class class
180                    :chain-head head
181                    :body (cons vtable-pointer islots))))
182
183 (defmethod compute-ilayout ((class sod-class))
184   (make-instance 'ilayout
185                  :class class
186                  :ichains (mapcar (lambda (chain)
187                                     (compute-ichain class
188                                                     (reverse chain)))
189                                   (sod-class-chains class))))
190
191 ;;;--------------------------------------------------------------------------
192 ;;; Effective methods.
193
194 (defclass effective-method ()
195   ((message :initarg :message :type sod-message
196             :reader effective-method-message)
197    (class :initarg :class :type sod-class :reader effective-method-class))
198   (:documentation
199    "The effective method invoked by sending MESSAGE to an instance of CLASS.
200
201    This is not a useful class by itself.  Message classes are expected to
202    define their own effective-method classes.
203
204    An effective method class must accept a :DIRECT-METHODS initarg, which
205    will be a list of applicable methods sorted in most-to-least specific
206    order."))
207
208 (defmethod print-object ((method effective-method) stream)
209   (maybe-print-unreadable-object (method stream :type t)
210     (format stream "~A ~A"
211             (effective-method-message method)
212             (effective-method-class method))))
213
214 (defgeneric message-effective-method-class (message)
215   (:documentation
216    "Return the effective method class for the given MESSAGE."))
217
218 (defgeneric compute-sod-effective-method (message class)
219   (:documentation
220    "Return the effective method when a CLASS instance receives MESSAGE.
221
222    The default method constructs an instance of the message's chosen
223    MESSAGE-EFFECTIVE-METHOD-CLASS, passing the MESSAGE, the CLASS and the
224    list of applicable methods as initargs to MAKE-INSTANCE."))
225
226 (defmethod compute-sod-effective-method
227     ((message sod-message) (class sod-class))
228   (let ((direct-methods (mapcan (lambda (super)
229                                   (let ((method
230                                          (find message
231                                                (sod-class-methods super)
232                                                :key #'sod-method-message)))
233                                     (and method (list method))))
234                                 (sod-class-precedence-list class))))
235     (make-instance (message-effective-method-class message)
236                    :message message
237                    :class class
238                    :direct-methods direct-methods)))
239
240 ;;;--------------------------------------------------------------------------
241 ;;; Vtable layout.
242
243 ;;; method-entry
244
245 (defclass method-entry ()
246   ((method :initarg :method :type effective-method
247            :reader method-entry-effective-method)
248    (chain-head :initarg :chain-head
249                :type sod-class
250                :reader method-entry-chain-head))
251   (:documentation
252    "An entry point into an effective method.
253
254    Calls to an effective method via different vtable chains will have their
255    `me' pointers pointing to different ichains within the instance layout.
256    Rather than (necessarily) duplicating the entire effective method for each
257    chain, we insert an entry veneer (the method entry) to fix up the pointer.
258    Exactly how it does this is up to the effective method -- and duplication
259    under some circumstances is probably a reasonable approach -- e.g., if the
260    effective method is just going to call a direct method immediately."))
261
262 (defmethod print-object ((entry method-entry) stream)
263   (maybe-print-unreadable-object (entry stream :type t)
264     (format stream "~A:~A"
265             (method-entry-effective-method entry)
266             (sod-class-nickname (method-entry-chain-head entry)))))
267
268 (defgeneric make-method-entry (effective-method chain-head)
269   (:documentation
270    "Return a METHOD-ENTRY for an EFFECTIVE-METHOD called via CHAIN-HEAD.
271
272    There is no default method for this function.  (Maybe when the
273    effective-method/method-entry output protocol has settled down I'll know
274    what a sensible default action would be.)"))
275
276 ;;; vtmsgs
277
278 (defclass vtmsgs ()
279   ((class :initarg :class :type sod-class :reader vtmsgs-class)
280    (subclass :initarg :subclass :type sod-class :reader vtmsgs-subclass)
281    (chain-head :initarg :chain-head :type sod-class
282                :reader vtmsgs-chain-head)
283    (entries :initarg :entries :type list :reader vtmsgs-entries))
284   (:documentation
285    "The message dispatch table for a particular CLASS.
286
287    The BODY contains a list of effective method objects for the messages
288    defined on CLASS, customized for calling from the chain headed by
289    CHAIN-HEAD."))
290
291 (defmethod print-object ((vtmsgs vtmsgs) stream)
292   (print-unreadable-object (vtmsgs stream :type t)
293     (format stream "~A <= ~A ~_~:<~@{~S~^ ~_~}~:>"
294             (vtmsgs-subclass vtmsgs)
295             (vtmsgs-class vtmsgs)
296             (vtmsgs-entries vtmsgs))))
297
298 (defgeneric compute-vtmsgs (class subclass chain-head)
299   (:documentation
300    "Return a VTMSGS object containing method entries for CLASS.
301
302    The CHAIN-HEAD describes which chain the method entries should be
303    constructed for.
304
305    The default method simply calls MAKE-METHOD-ENTRY for each of the methods
306    and wraps a VTMSGS object around them.  This ought to be enough for almost
307    all purposes."))
308
309 ;;; class-pointer
310
311 (defclass class-pointer ()
312   ((class :initarg :class :type sod-class :reader class-pointer-class)
313    (chain-head :initarg :chain-head :type sod-class
314                :reader class-pointer-chain-head)
315    (metaclass :initarg :metaclass :type sod-class
316               :reader class-pointer-metaclass)
317    (meta-chain-head :initarg :meta-chain-head :type sod-class
318                     :reader class-pointer-meta-chain-head))
319   (:documentation
320    "Represents a pointer to a class object for the instance's class.
321
322    A class instance can have multiple chains.  It may be useful to find any
323    of those chains from an instance of the class.  Therefore the vtable
324    stores a pointer to each separate chain of the class instance."))
325
326 (defmethod print-object ((cptr class-pointer) stream)
327   (print-unreadable-object (cptr stream :type t)
328     (format stream "~A:~A"
329             (class-pointer-metaclass cptr)
330             (sod-class-nickname (class-pointer-meta-chain-head cptr)))))
331
332 (defgeneric make-class-pointer (class chain-head metaclass meta-chain-head)
333   (:documentation
334    "Return a class pointer to a metaclass chain."))
335
336 ;;; base-offset
337
338 (defclass base-offset ()
339   ((class :initarg :class :type sod-class :reader base-offset-class)
340    (chain-head :initarg :chain-head :type sod-class
341                :reader base-offset-chain-head))
342   (:documentation
343    "The offset of this chain to the ilayout base.
344
345    There's only one of these per vtable."))
346
347 (defmethod print-object ((boff base-offset) stream)
348   (print-unreadable-object (boff stream :type t)
349     (format stream "~A:~A"
350             (base-offset-class boff)
351             (sod-class-nickname (base-offset-chain-head boff)))))
352
353 (defgeneric make-base-offset (class chain-head)
354   (:documentation
355    "Return the base offset object for CHAIN-HEAD ichain."))
356
357 ;;; chain-offset
358
359 (defclass chain-offset ()
360   ((class :initarg :class :type sod-class :reader chain-offset-class)
361    (chain-head :initarg :chain-head :type sod-class
362                :reader chain-offset-chain-head)
363    (target-head :initarg :target-head :type sod-class
364                 :reader chain-offset-target-head))
365   (:documentation
366    "The offset from the CHAIN-HEAD ichain to the TARGET-HEAD ichain."))
367
368 (defmethod print-object ((choff chain-offset) stream)
369   (print-unreadable-object (choff stream :type t)
370     (format stream "~A:~A->~A"
371             (chain-offset-class choff)
372             (sod-class-nickname (chain-offset-chain-head choff))
373             (sod-class-nickname (chain-offset-target-head choff)))))
374
375 (defgeneric make-chain-offset (class chain-head target-head)
376   (:documentation
377    "Return the offset from CHAIN-HEAD to TARGET-HEAD."))
378
379 ;;; vtable
380
381 (defclass vtable ()
382   ((class :initarg :class :type sod-class :reader vtable-class)
383    (chain-head :initarg :chain-head :type sod-class
384                :reader vtable-chain-head)
385    (body :initarg :body :type list :reader vtable-body))
386   (:documentation
387    "VTABLEs hold all of the per-chain static information for a class.
388
389    There is one vtable for each chain of each class.  The vtables for a class
390    are prefixes of the corresponding chains of its subclasses.
391
392    Vtables contain method entry pointers, pointers to class objects, and
393    the offset information used for cross-chain slot access."))
394
395 (defmethod print-object ((vtable vtable) stream)
396   (print-unreadable-object (vtable stream :type t)
397     (format stream "~A:~A ~_~:<~@{~S~^ ~_~}~:>"
398             (vtable-class vtable)
399             (sod-class-nickname (vtable-chain-head vtable))
400             (vtable-body vtable))))
401
402 (defgeneric compute-vtable (class chain)
403   (:documentation
404    "Compute the vtable layout for a chain of CLASS.
405
406    The CHAIN is a list of classes, with the least specific first."))
407
408 (defgeneric compute-vtables (class)
409   (:documentation
410    "Compute the vtable layouts for CLASS.
411
412    Returns a list of VTABLE objects in the order of CLASS's chains."))
413
414 ;;; Implementation.
415
416 (defmethod compute-vtmsgs
417     ((class sod-class)
418      (subclass sod-class)
419      (chain-head sod-class))
420   (flet ((make-entry (message)
421            (let ((method (find message
422                                (sod-class-effective-methods subclass)
423                                :key #'effective-method-message)))
424              (make-method-entry method chain-head))))
425     (make-instance 'vtmsgs
426                    :class class
427                    :subclass subclass
428                    :chain-head chain-head
429                    :entries (mapcar #'make-entry
430                                     (sod-class-messages class)))))
431
432 (defmethod make-class-pointer
433     ((class sod-class) (chain-head sod-class)
434      (metaclass sod-class) (meta-chain-head sod-class))
435
436   ;; Slightly tricky.  We don't necessarily want a pointer to the metaclass,
437   ;; but to its most specific subclass on the given chain.  Fortunately, CL
438   ;; is good at this game.
439   (let* ((meta-chains (sod-class-chains metaclass))
440          (meta-chain-tails (mapcar #'car meta-chains))
441          (meta-chain-tail (find meta-chain-head meta-chain-tails
442                                 :key #'sod-class-chain-head)))
443     (make-instance 'class-pointer
444                    :class class
445                    :chain-head chain-head
446                    :metaclass meta-chain-tail
447                    :meta-chain-head meta-chain-head)))
448
449 (defmethod make-base-offset ((class sod-class) (chain-head sod-class))
450   (make-instance 'base-offset
451                  :class class
452                  :chain-head chain-head))
453
454 (defmethod make-chain-offset
455     ((class sod-class) (chain-head sod-class) (target-head sod-class))
456   (make-instance 'chain-offset
457                  :class class
458                  :chain-head chain-head
459                  :target-head target-head))
460
461 ;; Special variables used by COMPUTE-VTABLE.
462 (defvar *done-metaclass-chains*)
463 (defvar *done-instance-chains*)
464
465 (defgeneric compute-vtable-items (class super chain-head emit)
466   (:documentation
467    "Emit vtable items for a superclass of CLASS.
468
469    This function is called for each superclass SUPER of CLASS reached on the
470    chain headed by CHAIN-HEAD.  The function should call EMIT for each
471    vtable item it wants to write.
472
473    The right way to check to see whether items have already been emitted
474    (e.g., has an offset to some other chain been emitted?) is as follows:
475
476      * In a method on COMPUTE-VTABLE, bind a special variable to an empty
477        list or hash table.
478
479      * In a method on this function, check the variable or hash table.
480
481    This function is the real business end of COMPUTE-VTABLE."))
482
483 (defmethod compute-vtable-items
484     ((class sod-class) (super sod-class) (chain-head sod-class)
485      (emit function))
486
487   ;; If this class introduces new metaclass chains, then emit pointers to
488   ;; them.
489   (let* ((metasuper (sod-class-metaclass super))
490          (metasuper-chains (sod-class-chains metasuper))
491          (metasuper-chain-heads (mapcar (lambda (chain)
492                                           (sod-class-chain-head (car chain)))
493                                         metasuper-chains)))
494     (dolist (metasuper-chain-head metasuper-chain-heads)
495       (unless (member metasuper-chain-head *done-metaclass-chains*)
496         (funcall emit (make-class-pointer class
497                                           chain-head
498                                           metasuper
499                                           metasuper-chain-head))
500         (push metasuper-chain-head *done-metaclass-chains*))))
501
502   ;; If there are new instance chains, then emit offsets to them.
503   (let* ((chains (sod-class-chains super))
504          (chain-heads (mapcar (lambda (chain)
505                                 (sod-class-chain-head (car chain)))
506                               chains)))
507     (dolist (head chain-heads)
508       (unless (member head *done-instance-chains*)
509         (funcall emit (make-chain-offset class chain-head head))
510         (push head *done-instance-chains*))))
511
512   ;; Finally, if there are interesting methods, emit those too.
513   (when (sod-class-messages super)
514     (funcall emit (compute-vtmsgs super class chain-head))))
515
516 (defmethod compute-vtable ((class sod-class) (chain list))
517   (let* ((chain-head (car chain))
518          (*done-metaclass-chains* nil)
519          (*done-instance-chains* (list chain-head))
520          (done-superclasses nil)
521          (items nil))
522     (flet ((emit (item)
523              (push item items)))
524
525       ;; Find the root chain in the metaclass and write a pointer.
526       (let* ((metaclass (sod-class-metaclass class))
527              (metaclass-chains (sod-class-chains metaclass))
528              (metaclass-chain-heads (mapcar (lambda (chain)
529                                               (sod-class-chain-head
530                                                (car chain)))
531                                             metaclass-chains))
532              (metaclass-root-chain (find-if-not
533                                     #'sod-class-direct-superclasses
534                                     metaclass-chain-heads)))
535         (emit (make-class-pointer class chain-head
536                                   metaclass metaclass-root-chain))
537         (push metaclass-root-chain *done-metaclass-chains*))
538
539       ;; Write an offset to the instance base.
540       (emit (make-base-offset class chain-head))
541
542       ;; Now walk the chain.  As we ascend the chain, scan the class
543       ;; precedence list of each class in reverse to ensure that we have
544       ;; everything interesting.
545       (dolist (super chain)
546         (dolist (sub (reverse (sod-class-precedence-list super)))
547           (unless (member sub done-superclasses)
548             (compute-vtable-items class
549                                   sub
550                                   chain-head
551                                   #'emit)
552             (push sub done-superclasses))))
553
554       ;; We're through.
555       (make-instance 'vtable
556                      :class class
557                      :chain-head chain-head
558                      :body (nreverse items)))))
559
560 (defgeneric compute-effective-methods (class)
561   (:documentation
562    "Return a list of all of the effective methods needed for CLASS.
563
564    The list needn't be in any particular order."))
565
566 (defmethod compute-effective-methods ((class sod-class))
567   (mapcan (lambda (super)
568             (mapcar (lambda (message)
569                       (compute-sod-effective-method message class))
570                     (sod-class-messages super)))
571           (sod-class-precedence-list class)))
572
573 (defmethod compute-vtables ((class sod-class))
574   (mapcar (lambda (chain)
575             (compute-vtable class (reverse chain)))
576           (sod-class-chains class)))
577
578 ;;;--------------------------------------------------------------------------
579 ;;; Names of things.
580
581 (defun islots-struct-tag (class)
582   (format nil "~A__islots" class))
583
584 (defun ichain-struct-tag (class chain-head)
585   (format nil "~A__ichain_~A" class(sod-class-nickname chain-head)))
586
587 (defun ilayout-struct-tag (class)
588   (format nil "~A__ilayout" class))
589
590 (defun vtmsgs-struct-tag (class super)
591   (format nil "~A__vtmsgs_~A" class (sod-class-nickname super)))
592
593 (defun vtable-struct-tag (class chain-head)
594   (format nil "~A__vt_~A" class (sod-class-nickname chain-head)))
595
596 (defun vtable-name (class chain-head)
597   (format nil "~A__vtable_~A" class (sod-class-nickname chain-head)))
598
599 ;;;--------------------------------------------------------------------------
600 ;;; Hacks for now.
601
602 (defclass hacky-effective-method (effective-method)
603   ((direct-methods :initarg :direct-methods)))
604
605 (defmethod print-object ((method hacky-effective-method) stream)
606   (if *print-escape*
607       (print-unreadable-object (method stream :type t)
608         (format stream "~A ~_~A ~_~:<~@{~S~^ ~_~}~:>"
609                 (effective-method-message method)
610                 (effective-method-class method)
611                 (slot-value method 'direct-methods)))
612       (call-next-method)))
613
614 (defmethod message-effective-method-class ((message sod-message))
615   'hacky-effective-method)
616
617 (defmethod make-method-entry
618     ((method hacky-effective-method) (chain-head sod-class))
619   (make-instance 'method-entry
620                  :method method
621                  :chain-head chain-head))
622
623 ;;;----- That's all, folks --------------------------------------------------