SLE ’22, December 06–07, 2022, Auckland, New Zealand Aron Zwaan
using 8 cores and even 73.4 seconds on a single core [28].
On the same machine, a full compilation using
javac
takes
roughly 3 seconds on 8 cores, and 5 seconds on a single core.
In this paper, we apply partial evaluation to resolve a
newly identied performance bottleneck in Statix’ scope
graph query resolution algorithm. Our evaluation shows
that the approach ensures query resolution is up to 7.7x
faster than traditional query resolution on average. This
improves the performance of Statix-based type checkers by
38 – 48% on Java projects, which is a signicant step forward
to applying generated type checkers on larger codebases.
In summary, our contributions are as follows:
•
We explain the scope graph query resolution algo-
rithm, and identify a performance bottleneck in the
algorithm (section 3).
•
We introduce an intermediate language that makes
scope graph traversal order and partial query result
combination explicit (section 4).
•
We present a specializer from traditional scope graph
queries to our new intermediate language (section 5).
•
We evaluate the correctness and performance of our
approach (section 6). We show that specializing queries
makes scope graph query resolution up to 7.7x faster.
2 Partial Evaluation for DSL Interpreters
In this section, we provide a brief introduction to partial
evaluation (popularized by Futamura [4]), and explain why
we think it is especially benecial for declarative languages
such as Statix. From the perspective of partial evaluation, a
program can be seen as a function from inputs to an output
𝑂
.
Some of these inputs may be known statically (
𝑆
), while some
of them may vary per invocation (
𝐷
). Then, the signature of
a program looks as follows:
prog :𝑆×𝐷→𝑂
Aspecializer takes such a program and its static input, and
returns a residual program
prog𝑆
:
𝐷→𝑂
. When generating
prog𝑆
, it performs the part of the computation that does not
depend on 𝐷, making prog𝑆generally faster than prog.
2.1 Partial Evaluation for Interpreters
This pattern can easily be applied to programming languages.
In that case,
prog
is an interpreter that evaluates the seman-
tics of a program (its static input
𝑆
) with respect to some
arguments
𝐷
. The residual program is essentially a compiled
version of 𝑆. This is called the rst Futamura projection.
Specialization is generally only benecial when a program
is executed multiple times. However, Futamura argues that
specializing an interpreter to a program may already be ben-
ecial when executing the program once, as programs may
repeatedly evaluate a particular piece of code. Specializing
repeatedly executed program fragments removes the inter-
pretation overhead, which might outweigh the run time costs
of compilation. This eect becomes stronger when the com-
putational complexity of interpreting particular language
constructs is high. That is, the more overhead an interpreter
introduces, the more benecial specialization will be.
Declarative languages are languages in which a user spec-
ies intent rather than procedure. The logic to compute a
result that corresponds to the intent is then implemented in
the interpreter (or compiler) of the language. Thus, a declar-
ative language moves part of the complexity of a problem
from the program or programmer to its interpreter.
Having an interpreter with intricate logic means that
declarative languages are susceptible to introducing rela-
tively more run time overhead compared to non-declarative
languages. Interpreters of declarative languages might have
to execute non-trivial algorithms in order to evaluate a pro-
gram. For that reason, partial evaluation might be relatively
benecial for declarative languages.
2.2 Application to Statix
Applying partial evaluation to Statix introduces a few prob-
lems. In fact, it is as complex as nding a compiler from a
constraint language with an embedded scope graph logic
to an imperative language, such as Java. Such a compiler
should ensure that all internal scheduling of rule selection
and query resolution is handled correctly. We regard this as
an open research challenge that is too complicated to solve in
one step. Instead, in this paper, we specialize only a compu-
tationally complex part of the interpreter, namely the query
resolution algorithm, to a specication. This yields a speci-
cation in which the query resolution constraints are partially
evaluated, but other constraints are not. This specication
can then be interpreted by a constraint solver without using
the query resolution algorithm, ensuring faster execution.
To characterize this approach, regard a Statix specication
𝐶𝑄⊎𝐶𝑂
as a collection of query constraints
𝐶𝑄
and other
constraints
𝐶𝑂
. The
⊎
symbol indicates that these groups are
mutually embedded in actual specications. Our approach
is then summarized in the following functions:
specialize :𝐶𝑄⊎𝐶𝑂→𝐶∗
𝑄⊎𝐶𝑂
solve :𝐶∗
𝑄⊎𝐶𝑂×𝑃→𝑇
Here
specialize
specializes the query resolution algorithm
with respect to particular query constraints, yielding spe-
cialized queries (
𝐶∗
𝑄
). These constraints are embedded in the
original AST, yielding a partially specialized specication
𝐶∗
𝑄⊎𝐶𝑂
. When type-checking a concrete object program
𝑃
, this specialized specication is interpreted by an adapted
solver solve, returning a typing 𝑇.
These specialized queries
𝐶∗
𝑄
cannot be represented in
either Statix itself or Java (the language in which the solver is
written). Thus, we introduce a tailored intermediate language
to express those. For this language, we provide an interpreter,
2