|
@@ -2413,10 +2413,12 @@ short). \index{subject}{stack}\index{subject}{procedure call stack}
|
|
|
The stack consists of a separate \emph{frame}\index{subject}{frame}
|
|
|
for each procedure call. The memory layout for an individual frame is
|
|
|
shown in Figure~\ref{fig:frame}. The register \key{rsp} is called the
|
|
|
-\emph{stack pointer}\index{subject}{stack pointer} and points to the
|
|
|
-item at the top of the stack. The stack grows downward in memory, so
|
|
|
-we increase the size of the stack by subtracting from the stack
|
|
|
-pointer. In the context of a procedure call, the \emph{return
|
|
|
+\emph{stack pointer}\index{subject}{stack pointer} and it contains the
|
|
|
+address of the item at the top of the stack. In general, we use the
|
|
|
+term \emph{pointer}\index{subject}{pointer} for something that
|
|
|
+contains an address. The stack grows downward in memory, so we
|
|
|
+increase the size of the stack by subtracting from the stack pointer.
|
|
|
+In the context of a procedure call, the \emph{return
|
|
|
address}\index{subject}{return address} is the instruction after the
|
|
|
call instruction on the caller side. The function call instruction,
|
|
|
\code{callq}, pushes the return address onto the stack prior to
|
|
@@ -11166,57 +11168,62 @@ class TypeCheckLtup(TypeCheckLwhile):
|
|
|
\label{sec:GC}
|
|
|
|
|
|
Garbage collection is a runtime technique for reclaiming space on the
|
|
|
-heap that will not be used in the future by the executing
|
|
|
-program. Unfortunately, it is impossible to know precisely which heap
|
|
|
-allocated values, such as tuples, will be accessed in the
|
|
|
-future. Instead, garbage collectors overapproximate this set of values
|
|
|
-by identifying which of them are possible to access in the future. In
|
|
|
-particular, the \textbf{live}\index{subject}{live} values are those
|
|
|
-that are reachable from values in the registers or procedure call
|
|
|
-stack. Garbage collectors reclaim the space for those values that are
|
|
|
-no longer live.
|
|
|
-
|
|
|
-UNDER CONSTRUCTION
|
|
|
-
|
|
|
-These addresses are called the \emph{root
|
|
|
- set}\index{subject}{root set}. In addition, a program could access any tuple that is
|
|
|
-transitively reachable from the root set. Thus, it is safe for the
|
|
|
-garbage collector to reclaim the tuples that are not reachable in this
|
|
|
-way.
|
|
|
+heap that will not be used in the future of the running program. We
|
|
|
+use the term \emph{object}\index{subject}{object} to refer to any
|
|
|
+value that is stored in the heap, which for now only includes
|
|
|
+tuples.%
|
|
|
+%
|
|
|
+\footnote{The term ``object'' as used in the context of
|
|
|
+object-oriented programming has a more specific meaning than how we
|
|
|
+are using the term here.}
|
|
|
+%
|
|
|
+Unfortunately, it is impossible to know precisely which objects will
|
|
|
+be accessed in the future and which will not. Instead, garbage
|
|
|
+collectors overapproximate the set of objects that will be accessed by
|
|
|
+identifying which objects can possibly be accessed. The running
|
|
|
+program can directly access objects that are in registers and on the
|
|
|
+procedure call stack. It can also transitively access the elements of
|
|
|
+tuples, starting with a tuple whose address is in a register or on the
|
|
|
+procedure call stack. We define the \emph{root
|
|
|
+set}\index{subject}{root set} to be all the tuple addresses that are
|
|
|
+in registers or on the procedure call stack. We define the \emph{live
|
|
|
+objects}\index{subject}{live objects} to be the objects that are
|
|
|
+reachable from the root set. Garbage collectors reclaim the space that
|
|
|
+is allocated to objects that are no longer live. That means that some
|
|
|
+objects may not get reclaimed as soon as they could be, but at least
|
|
|
+garbage collectors do not reclaim the space dedicated to objects that
|
|
|
+will be accessed in the future! The programmer can influence which
|
|
|
+objects get reclaimed by causing them to become unreachable.
|
|
|
|
|
|
So the goal of the garbage collector is twofold:
|
|
|
\begin{enumerate}
|
|
|
-\item preserve all tuples that are reachable from the root set via a
|
|
|
- path of pointers, that is, the \emph{live} tuples, and
|
|
|
-\item reclaim the memory of everything else, that is, the
|
|
|
- \emph{garbage}.
|
|
|
+\item preserve all the live objects, and
|
|
|
+\item reclaim the memory of everything else, that is, the \emph{garbage}.
|
|
|
\end{enumerate}
|
|
|
|
|
|
+\subsection{Two-Space Copying Collector}
|
|
|
|
|
|
Here we study a relatively simple algorithm for garbage collection
|
|
|
-that is the basis of state-of-the-art garbage
|
|
|
+that is the basis of many state-of-the-art garbage
|
|
|
collectors~\citep{Lieberman:1983aa,Ungar:1984aa,Jones:1996aa,Detlefs:2004aa,Dybvig:2006aa,Tene:2011kx}. In
|
|
|
particular, we describe a two-space copying
|
|
|
collector~\citep{Wilson:1992fk} that uses Cheney's algorithm to
|
|
|
-perform the
|
|
|
-copy~\citep{Cheney:1970aa}.
|
|
|
-\index{subject}{copying collector}
|
|
|
-\index{subject}{two-space copying collector}
|
|
|
-Figure~\ref{fig:copying-collector} gives a
|
|
|
-coarse-grained depiction of what happens in a two-space collector,
|
|
|
-showing two time steps, prior to garbage collection (on the top) and
|
|
|
-after garbage collection (on the bottom). In a two-space collector,
|
|
|
-the heap is divided into two parts named the FromSpace and the
|
|
|
-ToSpace. Initially, all allocations go to the FromSpace until there is
|
|
|
-not enough room for the next allocation request. At that point, the
|
|
|
-garbage collector goes to work to make more room.
|
|
|
-\index{subject}{ToSpace}
|
|
|
-\index{subject}{FromSpace}
|
|
|
-
|
|
|
-A copying collector accomplishes this by copying all of the live
|
|
|
-objects from the FromSpace into the ToSpace and then performs a
|
|
|
-sleight of hand, treating the ToSpace as the new FromSpace and the old
|
|
|
-FromSpace as the new ToSpace. In the example of
|
|
|
+perform the copy~\citep{Cheney:1970aa}. \index{subject}{copying
|
|
|
+ collector} \index{subject}{two-space copying collector}
|
|
|
+Figure~\ref{fig:copying-collector} gives a coarse-grained depiction of
|
|
|
+what happens in a two-space collector, showing two time steps, prior
|
|
|
+to garbage collection (on the top) and after garbage collection (on
|
|
|
+the bottom). In a two-space collector, the heap is divided into two
|
|
|
+parts named the FromSpace\index{subject}{FromSpace} and the
|
|
|
+ToSpace\index{subject}{ToSpace}. Initially, all allocations go to the
|
|
|
+FromSpace until there is not enough room for the next allocation
|
|
|
+request. At that point, the garbage collector goes to work to room for
|
|
|
+the next allocation.
|
|
|
+
|
|
|
+A copying collector makes more room by copying all of the live objects
|
|
|
+from the FromSpace into the ToSpace and then performs a sleight of
|
|
|
+hand, treating the ToSpace as the new FromSpace and the old FromSpace
|
|
|
+as the new ToSpace. In the example of
|
|
|
Figure~\ref{fig:copying-collector}, there are three pointers in the
|
|
|
root set, one in a register and two on the stack. All of the live
|
|
|
objects have been copied to the ToSpace (the right-hand side of
|
|
@@ -11228,9 +11235,9 @@ not get copied into the ToSpace.
|
|
|
|
|
|
The exact situation in Figure~\ref{fig:copying-collector} cannot be
|
|
|
created by a well-typed program in \LangVec{} because it contains a
|
|
|
-cycle. However, creating cycles will be possible once we get to \LangAny{}.
|
|
|
-We design the garbage collector to deal with cycles to begin with so
|
|
|
-we will not need to revisit this issue.
|
|
|
+cycle. However, creating cycles will be possible once we get to
|
|
|
+\LangDyn{}. We design the garbage collector to deal with cycles to
|
|
|
+begin with so we will not need to revisit this issue.
|
|
|
|
|
|
\begin{figure}[tbp]
|
|
|
\centering
|
|
@@ -11240,27 +11247,6 @@ we will not need to revisit this issue.
|
|
|
\label{fig:copying-collector}
|
|
|
\end{figure}
|
|
|
|
|
|
-There are many alternatives to copying collectors (and their bigger
|
|
|
-siblings, the generational collectors) when its comes to garbage
|
|
|
-collection, such as mark-and-sweep~\citep{McCarthy:1960dz} and
|
|
|
-reference counting~\citep{Collins:1960aa}. The strengths of copying
|
|
|
-collectors are that allocation is fast (just a comparison and pointer
|
|
|
-increment), there is no fragmentation, cyclic garbage is collected,
|
|
|
-and the time complexity of collection only depends on the amount of
|
|
|
-live data, and not on the amount of garbage~\citep{Wilson:1992fk}. The
|
|
|
-main disadvantages of a two-space copying collector is that it uses a
|
|
|
-lot of space and takes a long time to perform the copy, though these
|
|
|
-problems are ameliorated in generational collectors. Racket and
|
|
|
-Scheme programs tend to allocate many small objects and generate a lot
|
|
|
-of garbage, so copying and generational collectors are a good fit.
|
|
|
-Garbage collection is an active research topic, especially concurrent
|
|
|
-garbage collection~\citep{Tene:2011kx}. Researchers are continuously
|
|
|
-developing new techniques and revisiting old
|
|
|
-trade-offs~\citep{Blackburn:2004aa,Jones:2011aa,Shahriyar:2013aa,Cutler:2015aa,Shidal:2015aa,Osterlund:2016aa,Jacek:2019aa,Gamari:2020aa}. Researchers
|
|
|
-meet every year at the International Symposium on Memory Management to
|
|
|
-present these findings.
|
|
|
-
|
|
|
-
|
|
|
\subsection{Graph Copying via Cheney's Algorithm}
|
|
|
\label{sec:cheney}
|
|
|
\index{subject}{Cheney's algorithm}
|
|
@@ -11281,19 +11267,20 @@ and copying tuples into the ToSpace.
|
|
|
Figure~\ref{fig:cheney} shows several snapshots of the ToSpace as the
|
|
|
copy progresses. The queue is represented by a chunk of contiguous
|
|
|
memory at the beginning of the ToSpace, using two pointers to track
|
|
|
-the front and the back of the queue. The algorithm starts by copying
|
|
|
-all tuples that are immediately reachable from the root set into the
|
|
|
-ToSpace to form the initial queue. When we copy a tuple, we mark the
|
|
|
-old tuple to indicate that it has been visited. We discuss how this
|
|
|
-marking is accomplish in Section~\ref{sec:data-rep-gc}. Note that any
|
|
|
-pointers inside the copied tuples in the queue still point back to the
|
|
|
-FromSpace. Once the initial queue has been created, the algorithm
|
|
|
-enters a loop in which it repeatedly processes the tuple at the front
|
|
|
-of the queue and pops it off the queue. To process a tuple, the
|
|
|
-algorithm copies all the tuple that are directly reachable from it to
|
|
|
-the ToSpace, placing them at the back of the queue. The algorithm then
|
|
|
-updates the pointers in the popped tuple so they point to the newly
|
|
|
-copied tuples.
|
|
|
+the front and the back of the queue, called the \emph{free pointer}
|
|
|
+and the \emph{scan pointer} respectively. The algorithm starts by
|
|
|
+copying all tuples that are immediately reachable from the root set
|
|
|
+into the ToSpace to form the initial queue. When we copy a tuple, we
|
|
|
+mark the old tuple to indicate that it has been visited. We discuss
|
|
|
+how this marking is accomplish in Section~\ref{sec:data-rep-gc}. Note
|
|
|
+that any pointers inside the copied tuples in the queue still point
|
|
|
+back to the FromSpace. Once the initial queue has been created, the
|
|
|
+algorithm enters a loop in which it repeatedly processes the tuple at
|
|
|
+the front of the queue and pops it off the queue. To process a tuple,
|
|
|
+the algorithm copies all the tuple that are directly reachable from it
|
|
|
+to the ToSpace, placing them at the back of the queue. The algorithm
|
|
|
+then updates the pointers in the popped tuple so they point to the
|
|
|
+newly copied tuples.
|
|
|
|
|
|
\begin{figure}[tbp]
|
|
|
\centering \includegraphics[width=0.9\textwidth]{figs/cheney}
|
|
@@ -11306,10 +11293,11 @@ tuple whose second element is $42$ to the back of the queue. The other
|
|
|
pointer goes to a tuple that has already been copied, so we do not
|
|
|
need to copy it again, but we do need to update the pointer to the new
|
|
|
location. This can be accomplished by storing a \emph{forwarding
|
|
|
-pointer} to the new location in the old tuple, back when we initially
|
|
|
-copied the tuple into the ToSpace. This completes one step of the
|
|
|
-algorithm. The algorithm continues in this way until the front of the
|
|
|
-queue is empty, that is, until the front catches up with the back.
|
|
|
+pointer}\index{subect}{forwarding pointer} to the new location in the
|
|
|
+old tuple, back when we initially copied the tuple into the
|
|
|
+ToSpace. This completes one step of the algorithm. The algorithm
|
|
|
+continues in this way until the queue is empty, that is, when the scan
|
|
|
+pointer catches up with the free pointer.
|
|
|
|
|
|
|
|
|
\subsection{Data Representation}
|
|
@@ -11317,8 +11305,8 @@ queue is empty, that is, until the front catches up with the back.
|
|
|
|
|
|
The garbage collector places some requirements on the data
|
|
|
representations used by our compiler. First, the garbage collector
|
|
|
-needs to distinguish between pointers and other kinds of data. There
|
|
|
-are several ways to accomplish this.
|
|
|
+needs to distinguish between pointers and other kinds of data such as
|
|
|
+integers. There are several ways to accomplish this.
|
|
|
\begin{enumerate}
|
|
|
\item Attached a tag to each object that identifies what type of
|
|
|
object it is~\citep{McCarthy:1960dz}.
|
|
@@ -11336,23 +11324,23 @@ would be unfortunate to require tags on every object, especially small
|
|
|
and pervasive objects like integers and Booleans. Option 3 is the
|
|
|
best-performing choice for statically typed languages, but comes with
|
|
|
a relatively high implementation complexity. To keep this chapter
|
|
|
-within a 2-week time budget, we recommend a combination of options 1
|
|
|
-and 2, using separate strategies for the stack and the heap.
|
|
|
+within a reasonable time budget, we recommend a combination of options
|
|
|
+1 and 2, using separate strategies for the stack and the heap.
|
|
|
|
|
|
Regarding the stack, we recommend using a separate stack for pointers,
|
|
|
-which we call a \emph{root stack}\index{subject}{root stack}
|
|
|
+which we call the \emph{root stack}\index{subject}{root stack}
|
|
|
(a.k.a. ``shadow
|
|
|
stack'')~\citep{Siebert:2001aa,Henderson:2002aa,Baker:2009aa}. That
|
|
|
is, when a local variable needs to be spilled and is of type
|
|
|
\racket{\code{Vector}}\python{\code{TupleType}}, then we put it on the
|
|
|
-root stack instead of the normal procedure call stack. Furthermore, we
|
|
|
-always spill tuple-typed variables if they are live during a call to
|
|
|
-the collector, thereby ensuring that no pointers are in registers
|
|
|
-during a collection. Figure~\ref{fig:shadow-stack} reproduces the
|
|
|
-example from Figure~\ref{fig:copying-collector} and contrasts it with
|
|
|
-the data layout using a root stack. The root stack contains the two
|
|
|
-pointers from the regular stack and also the pointer in the second
|
|
|
-register.
|
|
|
+root stack instead of putting it on the procedure call
|
|
|
+stack. Furthermore, we always spill tuple-typed variables if they are
|
|
|
+live during a call to the collector, thereby ensuring that no pointers
|
|
|
+are in registers during a collection. Figure~\ref{fig:shadow-stack}
|
|
|
+reproduces the example from Figure~\ref{fig:copying-collector} and
|
|
|
+contrasts it with the data layout using a root stack. The root stack
|
|
|
+contains the two pointers from the regular stack and also the pointer
|
|
|
+in the second register.
|
|
|
|
|
|
\begin{figure}[tbp]
|
|
|
\centering \includegraphics[width=0.60\textwidth]{figs/root-stack}
|
|
@@ -11372,16 +11360,20 @@ which corresponds to the direction of the x86 shifting instructions
|
|
|
is dedicated to specifying which elements of the tuple are pointers,
|
|
|
the part labeled ``pointer mask''. Within the pointer mask, a 1 bit
|
|
|
indicates there is a pointer and a 0 bit indicates some other kind of
|
|
|
-data. The pointer mask starts at bit location 7. We have limited
|
|
|
-tuples to a maximum size of 50 elements, so we just need 50 bits for
|
|
|
-the pointer mask. The tag also contains two other pieces of
|
|
|
-information. The length of the tuple (number of elements) is stored in
|
|
|
-bits location 1 through 6. Finally, the bit at location 0 indicates
|
|
|
-whether the tuple has yet to be copied to the ToSpace. If the bit has
|
|
|
-value 1, then this tuple has not yet been copied. If the bit has
|
|
|
-value 0 then the entire tag is a forwarding pointer. (The lower 3 bits
|
|
|
-of a pointer are always zero anyways because our tuples are 8-byte
|
|
|
-aligned.)
|
|
|
+data. The pointer mask starts at bit location 7. We limit tuples to a
|
|
|
+maximum size of 50 elements, so we just need 50 bits for the pointer
|
|
|
+mask.%
|
|
|
+%
|
|
|
+\footnote{A production-quality compiler would handle
|
|
|
+arbitrary-sized tuples and use a more complex approach.}
|
|
|
+%
|
|
|
+The tag also contains two other pieces of information. The length of
|
|
|
+the tuple (number of elements) is stored in bits location 1 through
|
|
|
+6. Finally, the bit at location 0 indicates whether the tuple has yet
|
|
|
+to be copied to the ToSpace. If the bit has value 1, then this tuple
|
|
|
+has not yet been copied. If the bit has value 0 then the entire tag
|
|
|
+is a forwarding pointer. (The lower 3 bits of a pointer are always
|
|
|
+zero anyways because our tuples are 8-byte aligned.)
|
|
|
|
|
|
\begin{figure}[tbp]
|
|
|
\centering \includegraphics[width=0.8\textwidth]{figs/tuple-rep}
|
|
@@ -11531,7 +11523,8 @@ of tuple creation.
|
|
|
&\MID& \key{collect}(\itm{int})
|
|
|
\MID \key{allocate}(\itm{int},\itm{type})
|
|
|
\MID \key{global\_value}(\itm{name}) \\
|
|
|
- &\MID& \key{begin:} ~ \Stmt^{*} ~ \Exp
|
|
|
+ &\MID& \key{begin:} ~ \Stmt^{*} ~ \Exp \\
|
|
|
+ \Stmt &::= & \CASSIGN{\CPUT{\Exp}{\itm{int}}}{\Exp}
|
|
|
\end{array}
|
|
|
\]
|
|
|
|
|
@@ -12855,7 +12848,29 @@ from the set.
|
|
|
|
|
|
\fi}
|
|
|
|
|
|
-% Further Reading
|
|
|
+\section{Further Reading}
|
|
|
+
|
|
|
+There are many alternatives to copying collectors (and their bigger
|
|
|
+siblings, the generational collectors) when its comes to garbage
|
|
|
+collection, such as mark-and-sweep~\citep{McCarthy:1960dz} and
|
|
|
+reference counting~\citep{Collins:1960aa}. The strengths of copying
|
|
|
+collectors are that allocation is fast (just a comparison and pointer
|
|
|
+increment), there is no fragmentation, cyclic garbage is collected,
|
|
|
+and the time complexity of collection only depends on the amount of
|
|
|
+live data, and not on the amount of garbage~\citep{Wilson:1992fk}. The
|
|
|
+main disadvantages of a two-space copying collector is that it uses a
|
|
|
+lot of extra space and takes a long time to perform the copy, though
|
|
|
+these problems are ameliorated in generational collectors.
|
|
|
+\racket{Racket}\python{Object-oriented} programs tend to allocate many
|
|
|
+small objects and generate a lot of garbage, so copying and
|
|
|
+generational collectors are a good fit\python{~\citep{Dieckmann99}}.
|
|
|
+Garbage collection is an active research topic, especially concurrent
|
|
|
+garbage collection~\citep{Tene:2011kx}. Researchers are continuously
|
|
|
+developing new techniques and revisiting old
|
|
|
+trade-offs~\citep{Blackburn:2004aa,Jones:2011aa,Shahriyar:2013aa,Cutler:2015aa,Shidal:2015aa,Osterlund:2016aa,Jacek:2019aa,Gamari:2020aa}. Researchers
|
|
|
+meet every year at the International Symposium on Memory Management to
|
|
|
+present these findings.
|
|
|
+
|
|
|
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
\chapter{Functions}
|