doc/concepts.tex: Reorganize the instance lifecycle material.
authorMark Wooding <mdw@distorted.org.uk>
Tue, 15 Dec 2015 19:15:23 +0000 (19:15 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Sun, 29 May 2016 14:09:03 +0000 (15:09 +0100)
Promote it to its own section and overhaul the text ready for further
improvements.

doc/concepts.tex

index 1a84b88..74e5902 100644 (file)
@@ -246,8 +246,8 @@ qualified by the defining class's nickname.
 As well as defining slot names and types, a class can also associate an
 \emph{initial value} with each slot defined by itself or one of its
 subclasses.  A class $C$ provides an \emph{initialization function} (see
-\xref{sec:concepts.classes.c}, and \xref{sec:structures.root.sodclass}) which
-sets the slots of a \emph{direct} instance of the class to the correct
+\xref{sec:concepts.lifecycle.birth}, and \xref{sec:structures.root.sodclass})
+which sets the slots of a \emph{direct} instance of the class to the correct
 initial values.  If several of $C$'s superclasses define initializers for the
 same slot then the initializer from the most specific such class is used.  If
 none of $C$'s superclasses define an initializer for some slot then that slot
@@ -307,89 +307,6 @@ functions for working with that class's instances.  (The @|SodClass| class
 doesn't define any messages, so it doesn't have any methods.  In Sod, a class
 slot containing a function pointer is not at all the same thing as a method.)
 
-\subsubsection{Instance allocation, imprinting, and initialization}
-It is in general not sufficient to declare (or @|malloc|) an object of the
-appropriate class type and fill it in, since the class type only describes an
-instance's layout from the point of view of a single superclass chain.  The
-correct type to allocate, to store a direct instance of some class is a
-structure whose tag is the class name suffixed with `@|__ilayout|'; e.g., the
-correct layout structure for a direct instance of @|MyClass| would be
-@|struct MyClass__ilayout|.
-
-Instance layouts may be declared as objects with automatic storage duration
-(colloquially, `allocated on the stack') or allocated dynamically, e.g.,
-using @|malloc|.  Sod's runtime system doesn't retain addresses of instances,
-so, for example, Sod doesn't make using a fancy allocator which sometimes
-moves objects around in memory any more difficult than it needs to be.
-
-Once storage for an instance has been allocated, it must be \emph{imprinted}
-before it can be used.  Imprinting an instance stores some metadata about its
-direct class in the instance structure, so that the rest of the program (and
-Sod's runtime library) can tell what sort of object it is, and how to use
-it.\footnote{%
-  Specifically, imprinting an instance's storage involves storing the
-  appropriate vtable pointers in the right places in it.} %
-A class object's @|imprint| slot points to a function which will correctly
-imprint storage for one of that class's instances.
-
-Once an instance's storage has been imprinted, it is possible to send the
-instance messages; however, the instance's slots are uninitialized at this
-point, so most methods are unlikely to do much of any use.  So, usually, you
-don't just want to imprint instance storage, but to \emph{initialize} an
-instance.  Initialization includes imprinting, but also sets the new
-instance's slots to their initial values, as defined by the class.  If
-neither the class nor any of its superclasses defines an initializer for a
-slot then it will not be initialized.
-
-There is currently no facility for providing parameters to the instance
-initialization process (e.g., for use by slot initializer expressions).
-Instance initialization is a complicated matter and for now I want to
-experiment with various approaches before committing to one.  My current
-interim approach is to specify slot initializers where appropriate and send
-class-specific messages for more complicated parametrized initialization.
-
-Automatic-duration instances can be conveniently constructed and initialized
-using the \descref{SOD_DECL}[macro]{mac}.  No special support is currently
-provided for dynamically allocated instances.  A simple function using
-@|malloc| might work as follows.
-\begin{prog}
-  void *new_instance(const SodClass *c) \\
-  \{ \\ \ind
-    void *p = malloc(c@->cls.initsz); \\
-    if (!p) return (0); \\
-    c@->cls.init(p); \\
-    return (p); \- \\
-  \}
-\end{prog}
-
-\subsubsection{Instance finalization and deallocation}
-There is currently no provided assistance for finalization or deallocation.
-It is the programmer's responsibility to decide and implement an appropriate
-protocol.  Note that to free an instance allocated from the heap, one must
-correctly find its base address: the \descref{SOD_INSTBASE}[macro]{mac} will
-do this for you.
-
-The following simple mixin class is suggested.
-\begin{prog}
-  [nick = disposable] \\
-  class DisposableObject : SodObject \{ \\- \ind
-    void release() \{ ; \} \\
-    \quad /* Release resources held by the receiver. */ \- \\-
-  \}
-  \\+
-  code c : user \{ \\- \ind
-    /\=\+* Free object p's instance storage.  If p is a DisposableObject \\
-       {}* then release its resources beforehand. \\
-       {}*/ \- \\
-    void free_instance(void *p) \\
-    \{ \\ \ind
-      DisposableObject *d = SOD_CONVERT(DisposableObject, p); \\
-      if (d) DisposableObject_release(d); \\
-      free(d); \- \\
-    \} \- \\
-  \}
-\end{prog}
-
 \subsubsection{Conversions}
 Suppose one has a value of type pointer to class type of some class~$C$, and
 wants to convert it to a pointer to class type of some other class~$B$.
@@ -722,6 +639,179 @@ from the direct method definition if there was one, or an unspecified value
 otherwise.
 
 %%%--------------------------------------------------------------------------
+\section{The object lifecycle} \label{sec:concepts.lifecycle}
+
+\subsection{Creation} \label{sec:concepts.lifecycle.birth}
+
+Construction of a new instance of a class involves three steps.
+\begin{enumerate}
+\item \emph{Allocation} arranges for there to be storage space for the
+  instance's slots and associated metadata.
+\item \emph{Imprinting} fills in the instance's metadata, associating the
+  instance with its class.
+\item \emph{Initialization} stores appropriate initial values in the
+  instance's slots, and maybe links it into any external data structures as
+  necessary.
+\end{enumerate}
+The \descref{SOD_DECL}[macro]{mac} handles constructing instances with
+automatic storage duration (`on the stack').  Currently, there is no built-in
+support for constructing dynamically-allocated instances.
+
+\subsubsection{Allocation}
+Instances of most classes (specifically including those classes defined by
+Sod itself) can be held in any storage of sufficient size.  The in-memory
+layout of an instance of some class~$C$ is described by the type @|struct
+$C$__ilayout|, and if the relevant class is known at compile time then the
+best way to discover the layout size is with the @|sizeof| operator.  Failing
+that, the size required to hold an instance of $C$ is available in a slot in
+$C$'s class object, as @|$C$__class@->cls.initsz|.
+
+It is not in general sufficient to declare, or otherwise allocate, an object
+of the class type $C$.  The class type only describes a single chain of the
+object's layout.  It is nearly always an error to use the class type as if it
+is a \emph{complete type}, e.g., to declare objects or arrays of the class
+type, or to enquire about its size or alignment requirements.
+
+Instance layouts may be declared as objects with automatic storage duration
+(colloquially, `allocated on the stack') or allocated dynamically, e.g.,
+using @|malloc|.  They may be included as members of structures or unions, or
+elements of arrays.  Sod's runtime system doesn't retain addresses of
+instances, so, for example, Sod doesn't make using fancy allocators which
+sometimes move objects around in memory any more difficult than it needs to
+be.
+
+There isn't any way to discover the alignment required for a particular
+class's instances at runtime; it's best to be conservative and assume that
+the platform's strictest alignment requirement applies.
+
+The following simple function correctly allocates and returns space for an
+instance of a class given a pointer to its class object @<cls>.
+\begin{prog}
+  void *allocate_instance(const SodClass *cls) \\ \ind
+    \{ return malloc(cls@->cls.initsz); \}
+\end{prog}
+
+\subsubsection{Imprinting}
+Once storage has been allocated, it must be \emph{imprinted} before it can be
+used as an instance of a class, e.g., before any messages can be sent to it.
+
+Imprinting an instance stores some metadata about its direct class in the
+instance structure, so that the rest of the program (and Sod's runtime
+library) can tell what sort of object it is, and how to use it.\footnote{%
+  Specifically, imprinting an instance's storage involves storing the
+  appropriate vtable pointers in the right places in it.} %
+A class object's @|imprint| slot points to a function which will correctly
+imprint storage for one of that class's instances.
+
+Once an instance's storage has been imprinted, it is technically possible to
+send messages to the instance; however the instance's slots are still
+uninitialized at this point, the applicable methods are unlikely to do much
+of any use unless they've been written specifically for the purpose.
+
+The following simple function imprints storage at address @<p> as an instance
+of a class, given a pointer to its class object @<cls>.
+\begin{prog}
+  void imprint_instance(const SodClass *cls, void *p) \\ \ind
+    \{ cls@->cls.imprint(p); \}
+\end{prog}
+
+\subsubsection{Initialization}
+The final step for constructing a new instance is to \emph{initialize} it, to
+establish the necessary invariants for the instance itself and the
+environment in which it operates.
+
+Details of initialization are necessarily class-specific, but typically it
+involves setting the instance's slots to appropriate values, and possibly
+linking it into some larger data structure to keep track of it.
+
+Classes can declare initial values for their slots.  A class object's @|init|
+slot points to a function which will establish the appropriate initial values
+for a new instance's slots.  Slots are not initialized in any particularly
+useful order.  The @|init| function also imprints the instance storage.
+
+The provided initialization protocol is extremely simplistic; most notably,
+it's not possible to pass parameters into the initialization process.
+Classes which have more complex requirements will need to define and
+implement their own additional (or alternative) protocols.
+
+\subsubsection{Example}
+The following is a simple function, with syntactic-sugar macro, which
+allocate storage for an instance of a class, imprints and initializes it, and
+returns a pointer to the new instance.
+\begin{prog}
+  void *make_instance(const SodClass *c) \\
+  \{ \\ \ind
+    void *p = malloc(c@->cls.initsz); \\
+    if (!p) return (0); \\
+    c@->cls.init(p); \\
+    return (p); \- \\
+  \}
+  \\+
+  \#define MAKE(cls) (cls *)make_instance(cls\#\#__class)
+\end{prog}
+
+
+\subsection{Destruction}
+\label{sec:concepts.lifecycle.death}
+
+Destruction of an instance, when it is no longer required, consists of two
+steps.
+\begin{enumerate}
+\item \emph{Teardown} releases any resources held by the instance and
+  disentangles it from any external data structures.
+\item \emph{Deallocation} releases the memory used to store the instance so
+  that it can be reused.
+\end{enumerate}
+
+\subsubsection{Teardown}
+Details of teardown are class-specific, but typically it involves releasing
+resources held by the instance, and possibly unlinking it from some larger
+data structure which used to keep track of it.
+
+There is no provided protocol for teardown: classes whose instances require
+teardown behaviour must define and implement an appropriate protocol of their
+own.  The following class may serve for simple cases.
+\begin{prog}
+  [nick = disposable] \\
+  class DisposableObject : SodObject \{ \\- \ind
+    void release() \{ ; \} \\
+    \quad /* Release resources held by the receiver. */ \- \\-
+  \}
+  \\+
+  code c : user \{ \\- \ind
+    /* If p is a a DisposableObject then release its resources. */ \\
+    void maybe_dispose(void *p) \\
+    \{ \\ \ind
+      DisposableObject *d = SOD_CONVERT(DisposableObject, p); \\
+      if (d) DisposableObject_release(d); \- \\
+    \} \- \\
+  \}
+\end{prog}
+
+\subsubsection{Deallocation}
+The details of instance deallocation are obviously specific to the allocation
+strategy used by the instance, and this is often orthogonal from the object's
+class.
+
+The code which makes the decision to destroy an object may often not be aware
+of the object's direct class.  Low-level details of deallocation often
+require the proper base address of the instance's storage, which can be
+determined using the \descref{SOD_INSTBASE}[macro]{mac}.
+
+\subsubsection{Example}
+The following is a counterpart to the @|new_instance| function
+(\xref{sec:concepts.lifecycle.birth}), which tears down and deallocates an
+instance allocated using @|malloc|.
+\begin{prog}
+  void free_instance(void *p) \\
+  \{ \\ \ind
+    SodObject *obj = p; \\
+    maybe_dispose(p); \\
+    free(SOD_INSTBASE(obj)); \- \\
+  \}
+\end{prog}
+
+%%%--------------------------------------------------------------------------
 \section{Metaclasses} \label{sec:concepts.metaclasses}
 
 %%%----- That's all, folks --------------------------------------------------