author | Alan Dipert
<alan@dipert.org> 2020-02-20 21:21:01 UTC |
committer | Alan Dipert
<alan@dipert.org> 2020-02-20 21:21:01 UTC |
parent | c422152dacf0bc8b3d83f4a6515cc05799192103 |
paper/jacl-els-2020.tex | +78 | -44 |
diff --git a/paper/jacl-els-2020.tex b/paper/jacl-els-2020.tex index 041db8a..218b248 100644 --- a/paper/jacl-els-2020.tex +++ b/paper/jacl-els-2020.tex @@ -490,65 +490,71 @@ Clojure's \texttt{..} macro. \texttt{JACL:\textbackslash.} expands to zero or more nested \texttt{JACL:\%DOT} or \texttt{JACL:\%CALL} forms. Here is an example of a \texttt{JACL:\textbackslash.} form --- equivalent to the JavaScript expression -\texttt{(123).toString().length} --- and its expansion: +\texttt{(123).toString().length} --- and its corresponding expansion: \begin{verbatim} (\. 123 (|toString|) |length|) -(%DOT (%CALL 123 |toString|) |length|) ; 3 +(%DOT (%CALL 123 |toString|) |length|) \end{verbatim} Note that JavaScript identifiers are case sensitive, and so case-preserving, pipe-delimited Lisp symbols must be used to refer to -field and method names. The dot macro also can also recognize Lisp or +field and method names. The dot macro also recognizes Lisp or JavaScript strings as JavaScript identifiers. \subsubsection{\texttt{TAGBODY} compilation strategy} -JSCL implements \texttt{TAGBODY} as the combination of: +JSCL, the existing Lisp closest to JACL, implements \texttt{TAGBODY} +through the combination of: \begin{itemize} + \item Generating a unique identifier, the \texttt{tbidx}, for each + \texttt{TAGBODY} encountered during compilation. \item A local JavaScript \texttt{branch} variable containing the tag of the next branch to execute. - \item A labeled \texttt{while(true) { ... }} loop containing a + \item Labeled \texttt{while(true) { ... }} loops each containing a \texttt{try/catch} statement. The \texttt{catch} arm of the \texttt{try/catch} inspects the type of thrown exception, and if - it is of the marker type \texttt{TagNLX}, \texttt{branch} is set - to the tag carried by the \texttt{TagNLX} object. The \texttt{try} - arm of the \texttt{try/catch} contains a \texttt{switch} statement - that dispatches on \texttt{branch}. Each arm of the - \texttt{switch} corresponds to code associated with a \texttt{GO} + it is of the marker type \texttt{TagNLX} and corresponds to the + current \texttt{TAGBODY}, \texttt{branch} is set to the tag + carried by the \texttt{TagNLX} object. The \texttt{try} arm of the + \texttt{try/catch} contains a \texttt{switch} statement that + dispatches on \texttt{branch}. Each labeled block of the + \texttt{switch} corresponds to the code succeeding a \texttt{GO} tag. The \texttt{switch} falls through to a \texttt{break} to the enclosing labeled \texttt{while} loop, concluding execution of the \texttt{TAGBODY}. \item An implementation of \texttt{GO} that throws a JavaScript - exception of type \texttt{TagNLX} that contains the target tag. + exception of type \texttt{TagNLX} that contains \texttt{tbidx} and + target \texttt{branch} values. \end{itemize} -This mechanism is ingenious and general. Unfortunately, every jump +This mechanism is ingenious and general, and it's roughly how JACL +also currently implements \texttt{TAGBODY}. Unfortunately, every jump requires a JavaScript exception be thrown, severely penalizing nearly -every iteration construct in JSCL. Fortunately, a slight improvement -in the \texttt{GO} compilation strategy can yield a tremendous -performance benefit. The improvement follows from the observation that -any \emph{local jump} --- such as from a \texttt{GO} form to its -relevant \texttt{TAGBODY}, with no lexically-intervening +every iteration construct. Fortunately, a slight improvement in the +\texttt{GO} compilation strategy can yield a tremendous performance +benefit. The improvement follows from the observation that any +\emph{local jump} --- such as that from a \texttt{GO} to its relevant +enclosing \texttt{TAGBODY}, with no lexically-intervening \texttt{LAMBDA} forms --- may rely on JavaScript's -\texttt{continue}\cite{MozLabel} operator with a label argument -instead of on throwing an exception in order to transfer control. - -JACL's rich AST will facilitate the identification of usages of -\texttt{GO} that are amenable to improvement. The availability of a -high-level AST will also facilitate further optimizations, such as -\texttt{GO} tag escape\footnote{Such as by use within a - \texttt{LAMBDA} form that is assigned as the value of a global - variable or passed to a function} analysis. It should be possible to -identify \texttt{TAGBODY}s that consist only of local jumps, and to -avoid generating \texttt{try/catch} boilerplate for them, thereby -reducing generated code size. +\texttt{continue}\cite{MozLabel} operator with a label argument for +control transfer, instead of on throwing an exception. + +JACL's high-level AST will facilitate the identification of usages of +\texttt{GO} that are amenable to the aforementioned optimization. The +availability of a high-level AST will also facilitate other related +optimizations, such as \texttt{GO} tag escape\footnote{Such as by use + within a \texttt{LAMBDA} form that is assigned as the value of a + global variable or passed to a function} analysis. It should be +possible to identify \texttt{TAGBODY}s that consist only of local +jumps, and to avoid generating \texttt{try/catch} boilerplate for +them, thereby reducing generated code size. Code generated for \texttt{TAGBODY} in JACL will still generally require a \texttt{switch}, and so a slowdown relative to hand-coded JavaScript loops would not be unexpected. However, preliminary -benchmarking \ref{appendix:benchmarks} of a factorial function on a +benchmarking \ref{appendix:hypothetical} of a factorial function on a recent version of Google Chrome revealed only a very small discrepancy between JACL's proposed approach and a hand-coded JavaScript \texttt{while} loop-based approach. @@ -647,7 +653,13 @@ beautiful wife, Sandra Dipert, for her skillful editing. \appendix \section{Loop Performance Benchmarks} -\label{appendix:benchmarks} + +\subsection{JavaScript Baseline} + +This is a simple JavaScript function for computing the factorial of a +positive integer as efficiently as possible. Its performance is +considered baseline for the purposes of benchmarking JACL's +\texttt{TAGBODY}. \begin{verbatim} function fact1(n) { @@ -656,6 +668,39 @@ function fact1(n) { return prod; } +var start = performance.now(); +for (var i = 0; i < 1e8; i++) fact1(20); +console.log("fact1", performance.now() - start); +// fact1 1860.9749999595806 +\end{verbatim} + +\subsection{Equivalent Lisp} + +The following is an analagous Common Lisp factorial +function. \texttt{TAGBODY} is the means of iteration. This function +cannot currently be compiled by JACL because \texttt{SETF}, +\texttt{DECF}, and \texttt{RETURN-FROM} are not yet implemented. + +\begin{verbatim} +(defun fact2 (n &aux (prod 1)) + (tagbody + start + (when (zerop n) (go done)) + (setf prod (* prod n)) + (decf n) + (go start) + done + (return-from fact2 prod))) +\end{verbatim} + +\subsection{Hypothetical Generated Code} +\label{appendix:hypothetical} + +The following is the hypothetical output for a future version of the +JACL compiler that implements a \texttt{continue}-based strategy for +efficient control transfer. + +\begin{verbatim} function GoTag(tag) { this.tag = tag; return this; @@ -688,19 +733,8 @@ function fact2(n) { } } -var start; - -start = performance.now(); -for (var i = 0; i < 1e8; i++) { - fact1(20); -} -console.log("fact1", performance.now() - start); -// fact1 1860.9749999595806 - -start = performance.now(); -for (var i = 0; i < 1e8; i++) { - fact2(20); -} +var start = performance.now(); +for (var i = 0; i < 1e8; i++) fact2(20); console.log("fact2", performance.now() - start); // fact2 1981.7150000017136 \end{verbatim}