git » jacl.git » master » tree

[master] / papers / jacl-els-2020.tex

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
\documentclass[sigconf]{acmart}
\usepackage{booktabs}

\AtBeginDocument{%
  \providecommand\BibTeX{{%
    \normalfont B\kern-0.5em{\scshape i\kern-0.25em b}\kern-0.8em\TeX}}}

\setcopyright{acmcopyright}
\copyrightyear{2020}
\acmYear{2020}
\acmDOI{10.1145/1122445.1122456}

\acmConference[ELS '20]{ELS '20: European Lisp Symposium}{April 27--28, 2020}{Z\"{u}rich, Switzerland}
\acmBooktitle{ELS '20: European Lisp Symposium, April 27--28, 2020, Z\"{u}rich, Switzerland}
\acmPrice{15.00}
\acmISBN{978-1-4503-XXXX-X/18/06}

%%\acmSubmissionID{123-A56-BU3}

\begin{document}

\title{JACL: A Common Lisp for Developing Single-Page Web Applications}

\author{Alan Dipert}
\email{alan@dipert.org}

\begin{abstract}
  This paper introduces JavaScript-Assisted Common Lisp (JACL), a new
  Web-browser based implementation of an extended subset of Common
  Lisp. JACL --- which is under active development and approaching
  utility --- is an effort to facilitate the use of Common Lisp in
  overcoming the challenges of Single-page Web Application (SPA)
  development. JACL promotes interactive development in the Web
  browser environment with its \emph{asynchronous reader} and Chrome
  DevTools-based REPL client. JACL also includes an optimizing
  Lisp-to-JavaScript compiler capable of generating competitively
  small and efficient JavaScript.
\end{abstract}

\begin{CCSXML}
  <ccs2012>
  <concept>
  <concept_id>10011007.10011006.10011041.10011045</concept_id>
  <concept_desc>Software and its engineering~Dynamic compilers</concept_desc>
  <concept_significance>500</concept_significance>
  </concept>
  <concept>
  <concept_id>10011007.10011006.10011041.10011048</concept_id>
  <concept_desc>Software and its engineering~Runtime environments</concept_desc>
  <concept_significance>500</concept_significance>
  </concept>
  </ccs2012>
\end{CCSXML}

\ccsdesc[500]{Software and its engineering~Dynamic compilers}
\ccsdesc[500]{Software and its engineering~Runtime environments}

\keywords{Common Lisp, JavaScript, web applications}

\maketitle

\section{Introduction}

The demand for SPAs in the past decade has only grown, and users and
stakeholders continually expect larger and more sophisticated
applications. Unfortunately, large-scale development on the Web
browser platform presents a particular set of challenges that are not
easily overcome. Developers have responded to these challenges by
creating a widening variety of special-purpose programming languages
that compile to JavaScript
\cite{Somasegar12,Czaplicki12,wiki:ReasonML}. Each new language
promotes one or more paradigms, application architectures, or
development workflows, and claims some advantage relative to the
status quo.

This paper presents one new such language, JavaScript-Assisted Common
Lisp (JACL), an implementation of an extended subset of Common
Lisp. The primary goal of the JACL project is to ease SPA development
by applying Common Lisp --- a proven\cite{Cannon07,Garnet90,Action}
substrate for UI innovation --- to the difficult challenges now faced
by developers. The JACL language is envisioned as the means to
achieving that goal.

Most popular, contemporary compile-to-JavaScript languages are
oriented around the affordances of static type checking; JACL, as a
Lisp, is not. Compared to similar languages that \emph{are} Lisps,
JACL differentiates itself in two particular ways: with its approach
to the REPL, and with its compilation techniques.

\section{Related Work}

Many Lisps exist that either compile to JavaScript or are interpreted
by a JavaScript program. Here, only Lisps that have either
demonstrated industrial utility or that implement a significant subset
of Common Lisp are featured.

\subsection{Parenscript}

Released in 2005\cite{Baringer05}, Parenscript\cite{ParenScriptManual}
was the first Common Lisp compiler to target JavaScript. Parenscript
is not bootstrapped and its compiler is not written in JavaScript, and
so it relies on a hosting Common Lisp system for compilation. Only
JavaScript types are available to Parenscript programs at runtime, and
so Parenscript is more of a syntax frontend for JavaScript than it is
an interactive Lisp system. While Parenscript is not positioned to
facilitate large-scale SPA development, it remains a popular way to
add dynamic JavaScript-based behaviors to static Web sites.

\subsection{SLip}

SLip\cite{SLipHome,SLipImpl} is arguably the most ambitious Common
Lisp-on-JavaScript system created to date, even though it
intentionally diverges\cite{SLipVsCl} from Common Lisp in certain
ways. It offers a stunning array of powerful features including a
self-hosting compiler, a full set of control operators, JavaScript
Foreign-Function Interface (FFI), tail-call optimization, green
threads, and perhaps most impressively, a resident Emacs clone,
\emph{Ymacs}. SLip is based originally on the compiler and bytecode
interpreter presented in Chapter 23 of \emph{Paradigms of Artificial
  Intelligence Programming: Case studies in Common
  Lisp}\cite{Norvig92}.

Lisp files may be batch-compiled to FASLs. FASLs represent code as
JavaScript code instead of as Lisp data. The browser is able to load
FASLs faster than SLip code because the JavaScript parser in the
browser is much faster than the SLip reader. Despite the ability to
produce FASLs, the interpreted nature of SLip precludes the system
from producing competitively fast or small application
deliverables. Consequently, SLip does not satisfy the JACL project
goal of facilitating large-scale industrial SPA development.

\subsection{valtan}

valtan\cite{valtanGitHub}

%% Optimizes tagbody; HIR has loop/recur
%% https://github.com/cxxxr/valtan/blob/master/library/valtan-core/compiler/hir-optimize.lisp#L270

%% Includes full JavaScript FFI 
%% https://github.com/cxxxr/valtan/blob/master/docs/js-interop.lisp

%% Is it self-hosting? Yes, I think it is:
%% https://github.com/cxxxr/valtan/blob/8d8ba20ded78f53502a6004609b29bb50872f866/library/valtan-core/lisp/compilation.lisp

%% Includes support for working with React and example tic-tac-toe application
%% https://github.com/cxxxr/valtan/tree/master/example 

\subsection{JSCL}

Of existing Common Lisps, JSCL\cite{JSCLGitHub,JSCLTalk} is the one
aligned most closely with the JACL project goal. Unlike Parenscript,
JSCL is a genuine Lisp system. And unlike SLip, JSCL compiles directly
to JavaScript instead of to an interpreted bytecode. It is
self-hosting, includes the major control operators, and integrates
tightly with JavaScript. JSCL includes a reader, compiler, and
printer, and evaluation is performed by the JavaScript \texttt{eval()}
function. Between these, a Read Eval Print Loop (REPL) is possible,
and the JSCL distribution includes an implementation of one.

\subsubsection{Synchronous reader}

JSCL supports reading from string-backed pseudo-streams. Input streams
from which characters may be synchronously consumed are not an
abstraction supported by JavaScript in Web browsers. With a few
obscure exceptions\footnote{\texttt{window.prompt()} and
  \texttt{window.confirm()} are two JavaScript functions that may be
  used to synchronously query the user for input.}, JavaScript
programs may only receive input asynchronously. An error is signaled
if the end of a string-backed input stream is encountered before the
reader has finished reading a datum.

Because input strings may not contain partial data, the REPL
necessitates a ``pre''-reader process that accumulates characters in
response to asynchronous input events, and invokes the reader only
once a complete form --- as a string --- has been accumulated. Such a
pre-reader can be found in the JSCL REPL implementation. It handles
standard syntax, but has the potential to be stymied by extended
syntax, such as that added by reader macros. As such, the pre-reader
is a separate, degenerate reader that limits what's capable of being
read by the underlying, full-featured synchronous reader.

\subsubsection{Compiler organization}

JSCL compilation is performed in two stages:

\begin{itemize}
  \item Conversion from Lisp to a JavaScript Abstract Syntax Tree
    (AST) represented as S-expressions.
  \item Conversion from JavaScript AST to JavaScript strings. Some
    code-size optimization of arithmetic expressions is also performed
    in this stage.
\end{itemize}

The first stage, the conversion from Lisp to JavaScript Abstract
Syntax Trees (AST), is where the implementation of the Lisp special
forms in terms of JavaScript language constructs and runtime support
is performed. This conversion is done in a single pass in which macro
expansion, lexical analysis, and JavaScript AST generation all
occur. The lexical environment is maintained in a dynamically-scoped
variable as the compiler descends into Lisp code and produces
JavaScript AST.

Code for \texttt{TAGBODY} is generated in the first stage, and the
generated code is much slower than comparable JavaScript code. Every
control transfer initiated by \texttt{GO} results in a JavaScript
exception being thrown, which is an expensive operation. Since many
Common Lisp operators have \emph{implicit tagbodies}, and since most
other iteration operators are expressed in terms of \texttt{TAGBODY},
this performance problem pervades the JSCL system.

More efficient ways of implementing \texttt{TAGBODY} are not hard to
imagine, but the JSCL compiler does not lend itself to the
implementation of this or other high-level optimizations, as JSCL
lacks a sufficiently expressive intermediate representation
(IR). High-level optimizations and transformations are important for a
Lisp to JavaScript compiler to perform, because they are exactly the
kind that a JavaScript optimizer could not later perform, provided
only JavaScript code.

\subsection{ClojureScript}

A discussion of industrial Lisp technology in the SPA setting would be
incomplete without mention of ClojureScript \cite{Cljs}. ClojureScript
is probably the most successful Lisp dialect for building SPAs by
number of commercial users \cite{CljsUsers}.

ClojureScript targets JavaScript, and is a dialect of an earlier
language, Clojure\cite{Clojure}, which targets Java Virtual Machine
(JVM) bytecode. The ClojureScript reader and macro systems were both
originally hosted in Clojure, in a manner similar to Parenscript.

Since its introduction\cite{CljsRelease}, ClojureScript has heavily
promoted and prioritized the ability to produce high-performance
deliverables. It has always been capable of generating JavaScript
deliverables amenable to aggressive optimization by the Google Closure
Compiler. In this respect, ClojureScript aligns closely with the JACL
project goal of competitive application performance. In fact,
experience with, and admiration for, ClojureScript is the reason the
ability to produce high-performance deliverables is considered a
crucial capability of JACL.

Other than the fact that JACL is a Common Lisp and ClojureScript is
not, the biggest difference between the two is that JACL promotes a
browser-based development environment with minimal host-side
tooling. ClojureScript, in contrast, promotes\cite{CljsQuickStart} a
development experience oriented around compilation performed on the
host.

\section{Design and Implementation}

From a design perspective, JACL is an effort to balance the
requirements of an interactive and practical Lisp development
environment with the constraints imposed by the Web browser
platform. JACL proposes several innovations with respect to previous
work in pursuit of this balance.

\subsection{Asynchronous reader}

The basis for interactive development in Lisp is undeniably the REPL,
but as the JSCL ``pre-reader'' demonstrates, even the direct approach
to this simple mechanism is hampered by the asynchronous model of
input imposed by JavaScript.\cite{EventLoop}. Traditionally, Lisp
readers are implemented in environments with a blocking function for
obtaining input, like \texttt{getc(1)} on Unix. The blocking nature of
input consumption allows the reader to consume nested input
recursively, using the call stack to accumulate structures. In
JavaScript, input arrives asynchronously, and only when the call stack
is empty. To mitigate this difficulty, the JACL reader facility is
completely asynchronous. Conceptually, it is the JSCL REPL
``pre-reader'' taken to its inevitable conclusion.

The JACL reader is implemented as a JavaScript class,
\texttt{Reader}. \texttt{Reader} instances are parameterized by an
input source. One such input source is the \texttt{BufferedStream}
class. The input source asynchronously notifies the reader instance
when characters are available. The reader incrementally consumes these
characters. Once the reader has accumulated a Lisp datum, it notifies
its subscribers of the availability of the datum.

The JACL reader implementation makes extensive use of modern
JavaScript features to support asynchronous programming including
promises, iterators, async functions, async iterators, and the
\texttt{await} keyword. These features simplify the JACL
implementation and aid its performance \cite{V8async}. It is hoped
that JACL will eventually be written in itself, and that these
features will be accessible from Lisp, perhaps as a set of
implementation-dependent \emph{declaration specifiers} available in
\texttt{DECLARE} expressions.

The following example demonstrates, in JavaScript, the process by
which the JACL reader consumes characters and produces Lisp objects. A
\texttt{BufferedStream} and \texttt{Reader} are instantiated, sent
characters asynchronously, and then the resulting Lisp object is
printed to the JavaScript console.

\begin{verbatim}
(async () => {
  let bs = new BufferedStream(),
      rdr = new Reader(bs);
  
  window.setTimeout(() => bs.write("1"), 1000);
  window.setTimeout(() => bs.write("2"), 2000);
  window.setTimeout(() => bs.write("3"), 3000);
  window.setTimeout(() => bs.write(" "), 4000);
  
  console.log(await rdr.read());
})();
\end{verbatim}

\noindent In the preceding example, \texttt{window.setTimeout()} is
used to enqueue several JavaScript functions for execution after 1000,
2000, 3000, and 4000 milliseconds. Each enqueued function writes a
character of input to the \texttt{BufferedStream} \texttt{bs} when
invoked.

Before any enqueued function is invoked, execution proceeds to the
\texttt{console.log} call, but is suspended by the \texttt{await}
keyword.

The \texttt{await} keyword expects a JavaScript \texttt{Promise}
object on its right side, and JavaScript execution remains suspended
until the \texttt{Promise} has ``resolved'', or notified its
subscribers that the pending computation it represents has
completed. \texttt{rdr.read()} is an \texttt{async} function that
returns such a \texttt{Promise}.

Once \texttt{rdr} has completed a form --- in this case, the number
123, after about 4000 milliseconds have elapsed --- execution
continues, and \texttt{123} is printed to the JavaScript console.

The ``read'' portion of the JACL REPL is implemented by first
instantiating \texttt{BufferedStream} and \texttt{Reader}
objects. Then, in an asynchronous loop, objects are consumed from the
\texttt{Reader}, analyzed, compiled, and evaluated.

Concurrently, characters may be sent to the \texttt{BufferedStream}
instantiated by the REPL by calling the \texttt{write()} or
\texttt{writeEach()} methods of the \texttt{BufferedaStream}
object. Neither character input nor read object consumption impede
other JavaScript operations, so the JACL REPL is suitable for
embedding in applications.

Because of the platform and implementation-dependent nature of the
JACL reader, JACL does not support Common Lisp input streams, nor its
standard \texttt{READ} and \texttt{READ-FROM-STRING}
functions. Standard interfaces for extending the reader, such as the
\linebreak\texttt{SET-MACRO-CHARACTER} function, are not directly
supported. However, the JACL reader does provide an
implementation-specific way to define reader macros.

\subsection{Chrome DevTools REPL}

A browser-based REPL facilitates experimentation with the language by
interested people, from the comfort of their Web browsers. It's also a
useful debugging feature of a deployed application.

However, most developers already have a preferred text editor and a
REPL interaction workflow, and so it's not within the JACL project
scope to build a resident IDE in the style of SLip. Even if a resident
IDE was a goal, the file system access restrictions imposed by the
browser would present significant challenges.

Instead, JACL offers an alternative development REPL approach that
requires minimal host tooling: the DevTools-based REPL. Google Chrome
is capable of hosting a WebSocket-based debug server that implements
the DevTools Protocol \cite{GDevTools}. DevTools Protocol clients may
then connect to the server and interact with open tabs, such as by
evaluating arbitrary JavaScript within the context of the tab. JACL
leverages the DevTools Protocol to deliver a command-line REPL client
that may be run on development machines. The workflow is the
following:

\begin{enumerate}
  \item Run Google Chrome from the shell with the \linebreak
    \texttt{--remote-debugging-port} parameter.
  \item Navigate to the Web site hosting the JACL system you wish to
    interact with.
  \item Run \texttt{jacl-repl} from the shell.
  \item Be presented with a Lisp prompt.
\end{enumerate}

As a simple command-line application with a textual interface,
\texttt{jacl-repl} can be run in various contexts. For example, it
could be run within an Emacs ``inferior-lisp'' buffer, and then Lisp
forms could be sent from other Emacs buffers for evaluation in the
REPL. It could also be run as part of a build process that pipes Lisp
sources over the WebSocket for batch compilation. It is anticipated
that additional host-side tools that depend on \texttt{jacl-client}
will be necessary in the future to support loading source files in
dependency order.

Unlike the pre-readers of SLip and JSCL, \texttt{jacl-client} is
completely ignorant of Lisp syntax. \texttt{jacl-client} merely
transports characters between the host machine and the Lisp system and
so is \emph{not} a pre-reader.

There are a few obvious ways the JACL REPL experience could be
improved. For example, \texttt{jacl-repl} is currently an
R\cite{Rstats} script requiring an R installation and the
\texttt{chromote}\cite{Rchromote} package. A standalone binary
executable is imagined in the future in order to make it easier for
developers to start working on JACL projects. Additionally, JACL has
yet to define a printer for its native types, or an extensible print
protocol. Object string representations are obtained by calling the
generic JavaScript \texttt{toString()} method, which doesn't always
produce a representation that can be read back in.

\subsection{Analyzing compiler}

Unlike JSCL, the JACL compiler is organized to facilitate high-level
optimizations such as those that could support efficient compilation
of \texttt{TAGBODY} and other fundamental Common Lisp operators.

The first compiler pass expands macros and produces an AST. The second
compiler pass performs optimizations and produces a new AST. The final
pass produces JavaScript code. AST nodes are represented by generic
JavaScript objects with at least the following keys:

\begin{itemize}
  \item \texttt{op}: The name of the node, as a JavaScript string.
  \item \texttt{env}: An object of class \texttt{Env} that represents
    the lexical environment of the node.
  \item \texttt{parent}: The parent of the node; this is \texttt{null}
    for the root.
  \item \texttt{form}: The original source data of the node, a Lisp
    datum.
\end{itemize}

\noindent Nodes and \texttt{Env} objects are immutable by
convention. Functions are provided for modifying and merging these
objects so as only to produce new objects. This convention reduces the
possibility of optimization passes interfering with one another. It
also eases understanding the AST, since every AST node contains a copy
of all relevant context. As JavaScript objects, AST nodes are easily
introspected using the object inspector of the Web browser.

Currently, the \texttt{Env} object tracks evaluation context --- one
of \emph{statement}, \emph{expression}, or \emph{return} --- lexical
variables, and \texttt{TAGBODY} tags. In the future, it will track the
remaining aspects of the lexical environment, such as lexical
functions and macros.

\subsubsection{Embedding JavaScript with \texttt{JACL:\%JS}}

Unlike JSCL or SLip, the JACL compiler supports a special operator for
constructing fragments of JavaScript code, verbatim, from Lisp. The
semantics of this operator, \texttt{JACL:\%JS}, are inspired by a
similar feature of ClojureScript, \texttt{js*}. For example, the
following JACL code displays the number 3 in an alert box:

\begin{verbatim}
(JACL:%JS "window.alert(~{})" 3)
\end{verbatim}

The character sequence \texttt{\textasciitilde\{\}} is distinct from
any plausible \linebreak JavaScript syntax and so is used as placeholder
syntax. There must be as many placeholders as there are arguments to
\texttt{JACL:\%JS}.

\subsubsection{Other interoperation support}

In addition to \texttt{JACL:\%JS}, the JACL compiler currently
supports three more special operators for interacting with the host
platform: \texttt{JACL:\%NEW}, \texttt{JACL:\%DOT} and
\texttt{JACL:\%CALL}. These operators perform JavaScript object
instantiation, field access, and function calls, respectively. Since
JACL functions compile into JavaScript functions, \texttt{JACL:\%CALL}
is the basis for \texttt{FUNCALL} in JACL, and for function calls
generally.

JACL also supplies a convenience macro, \texttt{JACL:\textbackslash.}
or ``the dot macro'' for performing a series of field accesses and
method calls\footnote{Strictly speaking, JavaScript ``method calls''
  are normal function calls but with a particular value of
  \texttt{this}.} concisely. The dot macro takes direct inspiration
from the \texttt{..} macro of Clojure. \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 corresponding
expansion:

\begin{verbatim}
(\. 123 (|toString|) |length|)
(%DOT (%CALL 123 |toString|) |length|)
\end{verbatim}

\noindent Note that JavaScript identifiers are case sensitive, and so
case-preserving, pipe-delimited Lisp symbols must be used to refer to
JavaScript object field and method names. The \emph{readtable case} of
the JACL reader cannot currently be modified. The dot macro also
recognizes Lisp or JavaScript strings as JavaScript identifiers.

\subsubsection{\texttt{TAGBODY} compilation strategy}

Consider the following Common Lisp program that decrements the local
variable \texttt{X} 10 times:

\begin{verbatim}
(let ((x 10))
  (tagbody
    start
    (when (zerop x) (go end))
    (setq x (1- x))
    (go start)
    end))
\end{verbatim}

\noindent JSCL, the existing Lisp closest to JACL, would compile the
preceding code into approximately\footnote{Actual JSCL output is not
  used because it includes type checks, generated variable names, and
  other code that would obscure the relevant machinery.} the following
JavaScript:

\begin{verbatim}
function Jump(id, label) {
  this.id = id;
  this.label = label;
}

var X = 10;
var id = [];
var label = 0;
LOOP: while (true) {
  try {
    switch(label) {
      case 0:
        if (X === 0) throw new Jump(id, 1);
        X = X-1;
        throw new Jump(id, 0);
      case 1:
      default:
        break LOOP;
    }
  } catch (e) {
    if (e instanceof Jump && e.id === id) {
      label = e.label;
    } else {
      throw e;
    }
  }
}
\end{verbatim}

\noindent The mechanism is ingenious. \texttt{GO} tags became
\texttt{switch} labels, and jumps became \texttt{throw}
statements. The thrown objects are instances of \texttt{Jump}. Each
instance of \texttt{Jump} contains a destination label.

Unfortunately, in this scheme, every jump requires a JavaScript
exception to be thrown, severely penalizing \texttt{TAGBODY} as
previously discussed. Fortunately, a straightforward \emph{local jump
  optimization} can be applied that yields a tremendous performance
benefit. Local jump optimization is a known
technique\cite{SICLTagbody}, but JACL is the first Lisp targeting
JavaScript to apply it.

In order to perform this optimization, the JACL compiler first
identifies local \texttt{GO}s in its analysis pass. These are
\texttt{GO} nodes with no intervening \texttt{LAMBDA}
nodes\footnote{Note that \texttt{LAMBDA} doesn't necessarily preclude
  local jump optimization if the \texttt{LAMBDA} is inlined, but JACL
  currently does not inline functions.} between them and their
respective, lexically-enclosing \texttt{TAGBODY}s. Then,
\texttt{TAGBODY}s are identified that consist of only local
\texttt{GO}s.

JavaScript generated for local \texttt{GO}s does not throw an
exception, but instead leverages the labeled form of the JavaScript
\texttt{continue}\cite{MozLabel} statement to transfer control
appropriately. JavaScript generated for \texttt{TAGBODY}s that have
been determined to consist only of local jumps omits the
\texttt{try/catch} block, saving on generated code size.

The following code is similar\footnote{Once more, actual compiler
  output has been significantly modified and reformatted for brevity.}
to that generated by the JACL compiler. Cursory benchmarks
\ref{appendix:benchmarks} show JACL code runs several orders of
magnitude faster than JSCL, and that JACL code is almost as fast as
the JavaScript statement \texttt{while(X--)}:

\begin{verbatim}
var X = 10;
var label = 0;
LOOP: while (true) {
  switch(label) {
    case 0:
      if (X === 0) {
        label = 1;
        continue LOOP;
      }
      X = X - 1;
      label = 0;
      continue LOOP;
    case 1:
    default:
      break LOOP;
  }
}
\end{verbatim}

\section{Conclusion}

We introduced JACL, a new Common Lisp created to ease SPA development.
JACL is designed as an efficient, practical tool, with the needs of
industrial SPA developers in mind. JACL integrates tightly with the
Web browser platform and interoperates easily with
JavaScript. Compared to other browser-based Lisps, JACL places a
higher emphasis on the value of the REPL, and introduces new
techniques for integrating the REPL into the development workflow.

\section{Future Work}

In order to be practical for application development, JACL must
support the creation of standalone executables. In the case of JACL,
these would be single JavaScript files that may be included in an HTML
page and are executed on page load. Fortunately, since JACL
development is image-based, JACL should support the traditional
approach of specifying a Lisp function entrypoint and dumping the Lisp
image to native (JavaScript) code. The
\texttt{SAVE-LISP-AND-DIE}\cite{SBCLManual} function in SBCL and the
\texttt{DELIVER}\cite{LispWorksDeliver} function in LispWorks are two
examples of this functionality in other implementations.

JACL should be able to perform rudimentary optimizations such as
global function and variable tree shaking\cite{wiki:TreeShaking} in
order to reduce the size of generated executables. In addition, JACL
should make dynamic function and variable references in executables
static, so that third party tools like Google Closure
Compiler\cite{Bolin10} may optionally be used to perform additional
optimization.

Other than the ability to produce optimized standalone executables,
many other design and implementation tasks remain, such as support for
special variables in lambda lists, \texttt{EVAL-WHEN}, macro lambda
lists, \texttt{DECLARE} et al, CLOS, various other data types,
compiler macros, etc. The list of tasks is enormous, but it is
anticipated that these features can be implemented over time, in the
order demanded by application development.

\section{Acknowledgments}

The author wishes to thank Micha Niskin, Bart Botta, Kevin Lynagh,
Lionel Henry, and Andy Keep for invaluable feedback on early versions
of this paper.

The author wishes to express particular thanks to Robert Strandh not
only for his feedback, but also for his guidance on the writing
process.

Finally, the author wishes to express special gratitude to his
beautiful wife, Sandra Dipert, for her encouragement and support.

\bibliographystyle{ACM-Reference-Format}
\bibliography{jacl-els-2020}

\appendix

\section{Tagbody Performance Benchmarks}
\label{appendix:benchmarks}

The following benchmark code was run on Google Chrome 80.0.3987.116,
on Linux, using a computer with an Intel i7-3520M CPU. Times are in
milliseconds.

\begin{verbatim}
function Jump(id, label) {
  this.id = id;
  this.label = label;
}

function tagbody_unoptimized(X) {
  var id = [];
  var label = 0;
  LOOP: while (true) {
    try {
      switch(label) {
        case 0:
          if (X === 0) throw new Jump(id, 1);
          X = X - 1;
          throw new Jump(id, 0);
        case 1:
        default:
          break LOOP;
      }
    } catch (e) {
      if (e instanceof Jump && e.id === id) {
        label = e.label;
      } else {
        throw e;
      }
    }
  }
}

function tagbody_optimized(X) {
  var X = 10;
  var label = 0;
  LOOP: while (true) {
    switch(label) {
      case 0:
        if (X === 0) {
          label = 1;
          continue LOOP;
        }
        X = X - 1;
        label = 0;
        continue LOOP;
      case 1:
      default:
        break LOOP;
    }
  }
}

function baseline_js(X) {
  while(X--);
}

var start = performance.now();
for (var i = 0; i < 1e6; i++) tagbody_unoptimized(10);
console.log("tagbody_unoptimized", performance.now() - start);
// tagbody_unoptimized 58994.43500000052

var start = performance.now();
for (var i = 0; i < 1e6; i++) tagbody_optimized(10);
console.log("tagbody_optimized", performance.now() - start);
// tagbody_optimized 23.61499999812804

var start = performance.now();
for (var i = 0; i < 1e6; i++) baseline_js(10);
console.log("baseline_js", performance.now() - start);
// baseline_js 15.055000003427267
\end{verbatim}

\end{document}
\endinput