<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.0 20120330//EN" "JATS-archivearticle1.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink">
  <front>
    <journal-meta />
    <article-meta>
      <title-group>
        <article-title>PiNodes in the Druid Meta-Compiler</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Matias Demare</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Guillermo Polito</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Nahuel Palumbo</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Javier Pimás</string-name>
          <xref ref-type="aff" rid="aff1">1</xref>
        </contrib>
        <aff id="aff0">
          <label>0</label>
          <institution>Univ. Lille, Inria, CNRS, Centrale Lille, UMR 9189 CRIStAL</institution>
          ,
          <addr-line>F-59000 Lille</addr-line>
          ,
          <country country="FR">France</country>
        </aff>
        <aff id="aff1">
          <label>1</label>
          <institution>Univ. de Buenos Aires, Departamento de Computación, FCEyN</institution>
          ,
          <addr-line>Buenos Aires</addr-line>
          ,
          <country country="AR">Argentina</country>
        </aff>
      </contrib-group>
      <pub-date>
        <year>2025</year>
      </pub-date>
      <abstract>
        <p>Conditional branches incur high runtime overheads: they disrupt the processor's pipeline and add complexity to the control flow, preventing optimizations. Moreover, after inlining transformations and runtime-inserted type checks, it is often the case that some of these branches are redundant. Thus, there are opportunities to eliminate such branches without changing the semantics of the program. In this paper, we explore methods of eliminating such branches in the Druid meta-compiler, a project intended for source-to-source ahead-of-time generation of baseline JIT compilers from a language interpreter. Moreover, we compare our new solution against a pre-existing algorithm in Druid. We extend its intermediate representation with PiNodes, a sparse representation of constraints on SSA variables. Such constraint information is leveraged by the compiler to remove redundant branches. We propose two PiNode-based methods of flow-sensitive analysis: one based on modelling possible constant values for variables, and a re-implementation of the traditional ABCD algorithm.</p>
      </abstract>
      <kwd-group>
        <kwd>eol&gt;Meta-compiler</kwd>
        <kwd>Constraint propagation</kwd>
        <kwd>Dead-Branch elimination</kwd>
        <kwd>Optimization</kwd>
        <kwd>PiNodes</kwd>
        <kwd>Pharo</kwd>
      </kwd-group>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>1. Introduction</title>
      <p>Listing 1: Example of an unreachable branch that can be deleted without changing what the program
does.</p>
      <p>In this paper, we propose to study flow-sensitive constraint-based optimizations using PiNodes in the
Druid meta-compiler, a compiler generator program. We enhance the compiler’s intermediate language
with constraint information, encoded as nodes in the instruction graph, at each branch and merge point
in the control flow. Then, we leverage this information to enable more optimization opportunities.</p>
      <p>This representation exploits the sparseness of the SSA form: obtaining all constraints imposed on a
variable at a certain point in the program simply requires traversing its use-def chain, and collecting
the constraints found.</p>
      <p>
        In particular, we propose two diferent techniques for dead branch elimination, one based on modelling
possible constant values ranges for variables, and one based on the traditional ABCD algorithm [
        <xref ref-type="bibr" rid="ref1">1</xref>
        ].
We compare them both in terms of optimization speed and optimization quality (measuring number
of optimized branches and runtime performance). Additionally, we compare them with a preexisting
technique used in the Druid meta-compiler.
      </p>
      <p>
        Existing work proposes techniques that augment compiler intermediate representations to elide type
checks [
        <xref ref-type="bibr" rid="ref2 ref3">2, 3</xref>
        ] and array bound checks [
        <xref ref-type="bibr" rid="ref1">1</xref>
        ]. Jump threading [
        <xref ref-type="bibr" rid="ref4">4</xref>
        ] modifies the control flow to make it linear
when possible. Message splitting [
        <xref ref-type="bibr" rid="ref3">3</xref>
        ] duplicates basic blocks at merge points to specialize them for their
predecessors.
      </p>
      <p>
        Extensions to the IR similar to the one we used have been applied in other implementations. Julia’s
PiNodes [
        <xref ref-type="bibr" rid="ref2">2</xref>
        ] use the same representation, whereas the ABCD paper [
        <xref ref-type="bibr" rid="ref1">1</xref>
        ] and the LLVM PredicateInfo
pass [
        <xref ref-type="bibr" rid="ref5">5</xref>
        ] also implement extensions to the SSA form, though the constraint is not represented within an
SSA instruction.
      </p>
      <p>
        Section 2 presents some preliminary definitions, along with the context of our research. In Section 3,
we explain how the general framework for PiNodes-based optimizations works, and how we perform
the insertion and deletion of PiNodes. In Section 4, we explain how we use this information to
perform dead branch elimination. In Section 5, we present a preliminary performance evaluation of our
implementation, and we compare it with an existing implementation of dead branch elimination in the
Druid compiler infrastructure [
        <xref ref-type="bibr" rid="ref6">6</xref>
        ]. Finally, in Section 7, we conclude and explain how we intend to use
PiNodes for future research.
      </p>
    </sec>
    <sec id="sec-2">
      <title>2. Context</title>
      <sec id="sec-2-1">
        <title>2.1. Preliminary Definitions</title>
        <p>This Section provides the basic definitions needed to understand the rest of the paper. If the reader is
well versed in the SSA representation, they may choose to skip to the next subsection.
Definition 1 (Control Flow Graph). A control flow graph (or CFG) is a graph-based representation
of a program, in which each node represents a basic block (a consecutive group of instructions with no
branching), and edges represent jumps between basic blocks.</p>
        <p>
          Definition 2 (Single Static Assignment form). A control flow graph is said to be in Single Static
Assignment form [
          <xref ref-type="bibr" rid="ref7">7</xref>
          ] (or SSA form) if each variable is assigned exactly once. This form uses  functions
to represent variables that are assigned a diferent value depending on the path took along the control flow
graph. 3 := (1 → 1, 2 → 2) defines 3 to be equal to 1 if the last executed block was 1, or
equal to 2 if it was 2. The SSA form simplifies dataflow analyses, like computing a use-def chain.
Definition 3 (Use-def chain). A use-def chain, is a data structure that links each variable usage with
all the definitions of that variable that can reach the usage. SSA simplifies finding use-def chains, because
for each variable, there is only one place where that variable may have received a value.
Definition 4 (Domination). Given a control flow graph, and two of its basic blocks 1 and 2, we say that
1 dominates 2 if every path from the entry node to 2 must go through 1. Variables are available
in every block dominated by the block the variable was defined in, and conversely, variable usages are
constrained by all the conditions imposed upon them in any block that dominates the usage.
Definition 5 (Critical Edge). Given a control flow graph, a critical edge is an edge whose successor has
multiple predecessors, and whose predecessor has multiple successors. Figure 2 shows on the left an example
of a critical edge.
        </p>
      </sec>
      <sec id="sec-2-2">
        <title>2.2. The Druid Compiler Infrastructure</title>
        <p>
          Druid1 is a compiler infrastructure originally born for meta-compilation. Its main usage is for
source-tosource ahead-of-time generation of baseline JIT compilers from a language interpreter [
          <xref ref-type="bibr" rid="ref6">6</xref>
          ]. Conceptually,
Druid works as a compiler generator program (Cogen) [
          <xref ref-type="bibr" rid="ref8">8</xref>
          ].
        </p>
        <p>
          In essence, Druid is an optimizing compiler which optionally targets the Cogit JIT compiler [
          <xref ref-type="bibr" rid="ref9">9</xref>
          ]. The
DruidIR is a control flow graph in three-address-code SSA form [
          <xref ref-type="bibr" rid="ref7">7</xref>
          ]. The unit of meta-compilation is a
bytecode handler i.e., given an interpreter handler, it generates the corresponding generator handler, as
illustrated in Figure 1.
        </p>
        <sec id="sec-2-2-1">
          <title>Interpreter</title>
          <p>Bytecode
pushVariable
sendMessage
...
execute</p>
        </sec>
        <sec id="sec-2-2-2">
          <title>Result</title>
          <p>AOT
meta compilation
execute</p>
        </sec>
        <sec id="sec-2-2-3">
          <title>JIT compiler front</title>
          <p>Bytecode
gen_pushVariable
gen_sendMessage
...
compile</p>
        </sec>
        <sec id="sec-2-2-4">
          <title>Target Machine code Method</title>
        </sec>
      </sec>
      <sec id="sec-2-3">
        <title>2.3. Druid’s Preexisting Constant Dead Branch Elimination Algorithm (old-CDBE)</title>
        <p>It is worth mentioning that Druid already implemented an expensive version of constant dead branch
elimination, which we will use as a comparison baseline in our experiments. That old implementation
works in a similar way to our new constant dead branch elimination method. In fact, it shares the
methods used for constraint solving, but represents constraints densely: it attaches constraints to CFG
edges, and computes them for all paths a variable is live in. As such, it does not really profit from
the sparseness of the SSA form, which makes this optimization expensive. In fact, it can timeout if
the number of paths it needs to generate constraints for grows too large. In this case, optimization
opportunities are missed. In the rest of this paper we refer to this old constant death branch elimination
implementation as old-CDBE.</p>
      </sec>
    </sec>
    <sec id="sec-3">
      <title>3. Tracking Constraint Information with PiNodes</title>
      <p>This Section presents the PiNode framework. PiNodes are nodes in the compiler’s intermediate language
that attach constraint information to SSA variables. Constraints are of diferent kinds, encoding ranges
of numeric variables, and type information, among others. PiNodes have the same semantics as Copy
instructions, with the only diference being that they track the new constraint on the variable. This
representation leverages the sparseness of the SSA form, thereby avoiding expensive dataflow analyses.
1https://github.com/Alamvic/druid visited on 2025-03-01.</p>
      <sec id="sec-3-1">
        <title>3.1. PiNodes by Example</title>
        <p>Let us consider the example code in 2 that we assume is already in SSA form for practical purposes.
This code shows a conditional statement with and without PiNodes to the left and right, respectively.
"Before"
x1 &lt; y1 ifTrue: [</p>
        <p>x1 doSomething.
] ifFalse: [</p>
        <p>y1 doSomethingElse.</p>
        <p>In the example, PiNodes are inserted on each branch. Each PiNode includes the constraints learned
from the branch itself, and defines a new version of the SSA variable ( e.g., x2 or x3 are defined for x1).
Users of the old variable need to be rewritten to use the newly inserted PiNode.</p>
        <p>Note that if a PiNode adds a constraint that contradicts the existing restrictions imposed in a variable,
it must mean that the branch that leads to it contradicts some parent branch. Therefore, that means
that the PiNode’s basic block must be unreachable.</p>
      </sec>
      <sec id="sec-3-2">
        <title>3.2. The PiNode Framework</title>
        <p>Within the PiNode framework, code transformations and optimizations are organized in three phases.
In the first phase, control flow is evaluated, and PiNodes are inserted. In the second phase, optimizations
are applied, taking benefit from the constraint information available at PiNodes. Finally, PiNodes are
removed when lowering to a lower-level code representation (e.g., machine code).
PiNode insertion step. For each conditional branch, we insert two PiNode for each variable appearing
in the condition: one in each successor of the condition’s basic block. The PiNode in the true branch
stores the condition of the branch over the variable, and the one in the false branch stores the negated
condition.</p>
        <p>Variable rename step. Once PiNodes have been inserted, variable uses are renamed with the objective
of making explicit in the use-def chain the dependencies on constrained nodes. Our algorithm follows
the CFG domination tree and replaces any usages of the constrained variable that:
• Are dominated by the new variable.
• Do not correspond to PiNodes within the same block, since having dependencies between them
would negate the benefit of being able to collect all relevant constraints by traversing the use-def
chain.</p>
        <p>Critical Edges. Special consideration must be taken regarding critical edges, since in that case, the
added constraint may not hold true for every possible path. One option is to keep critical edges, but not
insert any PiNodes in any block that succeeds a critical edge. Another option is to break critical edges:
remove them, and insert a new basic block with an unconditional jump to the critical edge’s target in
their place. This is the path we took, since the information in that branch may be valuable for future
work, and in particular for message splitting. An example of this procedure is shown in Figure 2.
PiNode deletion. PiNode removal happens by propagating the constrained variable to the users of
the PiNode. The main idea is to revert the variable rename step. Internally, this algorithm is similar to a
copy-propagation algorithm.</p>
        <p>Constraints. PiNodes are represented as an instruction, containing as operands the constrained
variable, and the constraint imposed on it. In principle, this constraints can represent anything that
can be tested with a condition in Druid. However, not all types of constraints are usable by all of
our optimization algorithms. Table 1 shows which kinds of constraints are supported by each of our
algorithms.</p>
      </sec>
    </sec>
    <sec id="sec-4">
      <title>4. PiNode-based Dead Branch Elimination</title>
      <p>In this Section, we present two algorithms for dead branch elimination using PiNodes. The first
one considers constraints against constant values, the second one extends this work by considering
comparisons between variable values.</p>
      <p>The general structure of the algorithms is the same, and we show its pseudocode in Listing 3: for
each PiNode, it checks if its constraint is satisfiable. If it is not, it removes the jump to that block ( i.e.,
it converts the conditional branch instruction in the previous block, into an unconditional jump that
points to the other sibling block). Note that, since we removed critical edges during PiNode insertion,
blocks with PiNodes have only one predecessor, which ends in a conditional branch instruction, and no
optimizations currently break that invariant. Our two new algorithms difer on how the satisfiability
check is performed, which impacts the kinds of dead branches they detect, and also determines their
performance characteristics.
cfg piNodesDo: [ :piNode |
solver ifNotSatisfiable: piNode do: [</p>
      <p>unreachableBlocks add: piNode basicBlock.</p>
      <sec id="sec-4-1">
        <title>4.1. New Constant Dead Branch Elimination (new-CDBE)</title>
        <p>The algorithm. This algorithm considers only constraints with constant values. Constraints are
represented in a class hierarchy, where each instance can answer whether it includes a specific value,
and all values above and below a specific value. Using these methods, they can also answer if a constraint
is included in another one. On top of the basic constraints shown in Table 1, there are also classes that
represent the intersection and union of constraints.</p>
        <p>The algorithm proceeds as follows:
• For a given PiNode, it collects all constraints on its argument variable (i.e., without including the
constraint added by the PiNode itself), by iterating over the SSA use-def chain. The constraints
found as direct dependencies are interpreted as a conjunction, whereas dependencies from Phi
functions are interpreted as a disjunction of the operand’s constraints.
• It checks if the intersection between the collected constraint and the PiNode’s constraint is empty
or not. To do that, it negates the collected constraint, and checks if that negated constraint
includes the PiNode’s constraint.
• If it is empty, that means that the new constraint represented by the PiNode is not satisfiable, and
therefore its block must be unreachable.</p>
        <p>The pseudocode for the algorithm described can be found in Listing 4.
isSatisfiable: aDRPiNode
| collectedConstraint |
collectedConstraint := self collectConstraintsFrom: aDRPiNode operand.
^ (collectedConstraint negated includes: aDRPiNode constraint) not.</p>
        <p>Listing 4: Pseudocode for the new-CDBE algorithm.
x1 &lt; 3
ifTrue: [
x2 := Pi(x1, &lt;3).
x2 &lt; 5 ifTrue: [</p>
        <p>x3 := Pi(x2, &lt;5).
] ifFalse: [</p>
        <p>x4 := Pi(x2, &gt;=5). " unreachable "</p>
        <p>Listing 5: Unreachable block that is detected by new-CDBE.</p>
        <p>Example. Let us consider the example in Listing 5, which is the result of performing PiNode insertion
on the code from Listing 1:
• The algorithm starts analyzing x2 := Pi(x1, &lt;3). It collects all constraints on x1 (it has none,
so it is represented with a constraint that includes all values), and it checks if it is consistent with
the new constraint, &lt;3. Since it is, that means that the PiNode is reachable.
• It continues with the following PiNode, x3 := Pi(x2, &lt;5). It collects all constraints on x2,
which would be x2&lt;3, and checks if it is consistent with the new constraint, &lt;5. Again, that is
satisfiable.
• It continues with the next PiNode, x4 := Pi(x2, &gt;=5). It collects the constraints on its
argument x2, which would be again x2&lt;3, and checks if the new constraint &gt;=5 is satisfiable.</p>
        <p>Since the intersection between &lt;3 and &gt;=5 is empty, it marks that block as unreachable.
Implementation details and limitations. We extended this algorithm to handle copy propagation
and constant propagation. This means that we consider copy instructions of the form x := y to attach
the constraints from x to y. The main limitation of this implementation, is to not able to propagate
constraints across basic arithmetic operations (like addition and subtraction), nor is it able to handle
constraints between variables (for example, constraints of the form x &lt; y).</p>
      </sec>
      <sec id="sec-4-2">
        <title>4.2. Array Bounds Checks elimination on Demand (ABCD)</title>
        <p>
          To deal with the previous method’s limitations, we implemented another method of constraint solving
based on ABCD [
          <xref ref-type="bibr" rid="ref1">1</xref>
          ]. Their implementation is intended to be used for array bounds checks elimination,
but we extended it to detect dead branches of any kind, by leveraging the fact that array bounds checks
are just a particular case of conditional branches.
        </p>
        <p>Overview. This algorithm uses a weighted directed graph, called the inequality graph, to represent
the constraints over the variables, allowing it to represent constraints between variables, and to optimize
cases like the one shown in Listing 6. Figure 3 shows the corresponding graph. Nodes in the inequality
graph represent SSA values. An edge from 1 to 1 with weight  means that 1 − 1 ≤ . Using this
model, the algorithm considers that x&lt;y is a tautology if the shortest path from y to x has negative
weight. Here, the definition of shortest path is a generalization of the traditional definition with
extensions for phi nodes.  functions work as max nodes, meaning they consider the longest path
for each of its neighbours instead of the shortest one. Conceptually, this means that  functions are
bounded by the weakest constraint of its operands.</p>
        <p>Example. In the example in Figure 3, which shows the inequality graph corresponding to the code in
Listing 6, the shortest path from 2 to 3 has negative weight. Thus, 3 &lt; 2 is a tautology and the
ifFalse branch is unreachable. Note that the example cannot be optimized by constant dead branch
elimination, since it requires modelling the relation between variables.
x1 &lt;= y1
ifTrue: [
x2 := Pi(x1, &lt;=y1).
y2 := Pi(y1, &gt;=x1).
x3 := x2 - 10.
x3 &lt; y2 ifTrue: [
x4 := Pi(x3, &lt;y2).</p>
        <p>y3 := Pi(y2, &gt;x3).
] ifFalse: [
x5 := Pi(x3, &gt;=y2). " unreachable "
y4 := Pi(y2, &lt;=x3).</p>
      </sec>
    </sec>
    <sec id="sec-5">
      <title>5. Evaluation</title>
      <p>We wanted to perform an initial profitability analysis for the new algorithms, contextualized to the main
use case of Druid, which is compilation of VM primitives. Our objective was to answer the following
questions:
• Do the new methods detect dead branches?
• Do they detect more or less than the existing one?
• Is the ABCD algorithm more powerful than the others?
• Is there a reason to use more than one of these algorithms in combination?</p>
      <p>With that purpose, we compared the three algorithms (the one pre-existing in Druid, the new
implementation of constant dead branch elimination, and the implementation of ABCD) in three diferent
ways: meta-compilation speed, number of dead branches eliminated, and runtime speed.
Experimental setup All benchmarks were ran on a Macbook Pro with an Apple M3 Pro chip, and 18
GB of memory, runnning macOS 15.6. The laptop was plugged in, and no unnecessary programs were
running in the background.</p>
      <sec id="sec-5-1">
        <title>5.1. Meta-compilation Speed</title>
        <p>To measure meta-compilation speed, we designed a benchmark that consists of analyzing and optimizing
a selection of 50 VM primitives supported by Druid. We computed the time to run using the
highresolution clock of the machine, exposed by Smalltalk highResClock. We recorded both the
average and standard deviation, for three runs of compiling all the measured primitives.</p>
        <p>Do note that in the case of the new algorithms, PiNode insertion time is included, and in the case of
the old one, the time to generate and attach the constraints to the edges is also included. Moreover,
when we run both of the new algorithms, it is only necessary to perform PiNode insertion once. Results
are shown in Table 2.</p>
        <p>Conclusion. As we can see, our early results show that both the new-CDBE and ABCD methods
are substantially faster than the old-CDBE implementation, resulting in faster compile times. The
new-CDBE algorithm is roughly an order of magnitude faster, while the ABCD implementation is about
4x faster.</p>
        <p>We can see that running both the new-CDBE and ABCD methods in conjunction is faster than just
running the ABCD method. This is because the constant dead branch elimination method is able to
significantly simplify the CFG, reducing the size of the graph that the more expensive method, ABCD,
has to deal with. Additionally, PiNode insertion only needs to be performed once before running both
methods.</p>
        <p>Finally, it is also worth noting that in 3 out of the 50 primitives methods used for benchmarking, the
old dead branch elimination algorithm timed out due to their complexity. This has an impact on the
optimization opportunities it is able to find, as we will see in the next subsection.</p>
      </sec>
      <sec id="sec-5-2">
        <title>5.2. Number of Dead Branches Eliminated</title>
        <p>Using the same selection of primitives, we compared the total number of dead branches detected by
each algorithm. We also compared what happened if we ran both of our new algorithms, one after the
other, to see how they complemented each other, and determine if they were finding diferent sets of
dead branches. Results are shown in Table 3.</p>
      </sec>
      <sec id="sec-5-3">
        <title>5.3. Runtime Speed</title>
        <p>To assess runtime speed diferences between the diferent methods, we replaced four VM primitives
with versions optimized by each algorithm, and we ran microbenchmarks designed to stress test each
of them. We present the results in Figure 4.</p>
        <p>We should note that we were unable to run benchmarks optimizing only with the ABCD algorithm.
Since it did not eliminate enough branches, the size of IR graphs usually increased, which in turn caused
an increase in stack spills. Those spills in some cases made the stack grow too big, crashing the VM
and preventing us from running the desired benchmarks. Therefore, we only ran ABCD in conjunction
with the new-CDBE, to see if using it provided any advantage.</p>
        <p>Conclusion. As we can see, the runtime performance between all three methods is comparable, with
the diference between them being under 1%. Therefore, we can conclude that:
• The new and old CDBE methods have similar capabilities for detecting dead branches.
• Using them in conjunction with the ABCD method does not make a significant diference in
execution time.</p>
      </sec>
    </sec>
    <sec id="sec-6">
      <title>6. Prototype Implementation Details and Limitations</title>
      <p>
        In this Section we briefly explain some implementation details of our algorithms. We implemented
these algorithms in the Druid compiler infrastructure [
        <xref ref-type="bibr" rid="ref6">6</xref>
        ].
      </p>
      <p>Currently, none of the optimizations implemented on Druid invalidates the PiNodes. This means that
it is possible to apply multiple optimizations after PiNode insertion.</p>
      <p>During the insertion phase, we did not take any special considerations for the points where control
lfow merges. Merge points are not dominated by either branch, thus, variable usages will not be replaced
by the new variables. Moreover, this also means that we lose constraint information at merge points.
This loss of constraint information does not prevent the two optimizations presented in this article.
However, it prevents opportunities for message splitting. We intend to insert Phi functions at merge
points, making explicit the merge of constraint information.</p>
      <p>At the moment of writing this article, our ABCD prototype only supports upper bounds checks
elimination. Lower bounds checks elimination works similarly, but the building of the inequality graph
follows diferent rules.</p>
    </sec>
    <sec id="sec-7">
      <title>7. Conclusion and Future Work</title>
      <p>In this paper, explored augmenting the SSA IR from the Druid meta-compiler with constraint information,
to aid context-sensitive optimizations. We implemented two dead branch elimination techniques using
this representation, and we compared them with the existing technique in the Druid meta-compiler.</p>
      <p>Our results show that the new method of constant dead branch elimination is considerably faster
than the existing method in terms of meta-compilation speed, while still detecting approximately the
same dead branches. This makes VM build times faster, without any significant diference to runtime
performance.</p>
      <p>On the other hand, the ABCD algorithm does not seem fit for optimizing VM primitives. While it is
still faster than the old dead branch elimination method, its is designed to eliminate a kind of redundant
conditions that does not appear to be very common in the VM’s code, and therefore it cannot eliminate
enough branches for it to be useful.</p>
      <p>
        We are currently working on other methods of constraint solving for dead code elimination, that
would allow the compiler to solve more complex constraint systems. Our current goals include using
the Z3 constraint solver [
        <xref ref-type="bibr" rid="ref10">10</xref>
        ]. Additionally, we intend to explore the usage of PiNodes to inform
opportunities for message splitting.
      </p>
    </sec>
    <sec id="sec-8">
      <title>Acknowledgments</title>
      <p>This project is financed by the ANR JCJC project convention ANR-25-CE25-0002-01.</p>
    </sec>
    <sec id="sec-9">
      <title>Declaration on Generative AI</title>
      <p>The author(s) have not employed any Generative AI tools.</p>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [1]
          <string-name>
            <given-names>R.</given-names>
            <surname>Bodik</surname>
          </string-name>
          ,
          <string-name>
            <given-names>R.</given-names>
            <surname>Gupta</surname>
          </string-name>
          ,
          <string-name>
            <given-names>V.</given-names>
            <surname>Sarkar</surname>
          </string-name>
          ,
          <article-title>Abcd: eliminating array bounds checks on demand</article-title>
          ,
          <source>in: Proceedings of the ACM SIGPLAN 2000 conference on Programming language design and implementation</source>
          ,
          <year>2000</year>
          , pp.
          <fpage>321</fpage>
          -
          <lpage>333</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [2]
          <string-name>
            <surname>Julia</surname>
          </string-name>
          ssa
          <article-title>-form ir</article-title>
          , https://docs.julialang.org/en/v1/devdocs/ssair/#
          <article-title>Phi-nodes-and-</article-title>
          <string-name>
            <surname>Pi-nodes</surname>
          </string-name>
          ,
          <year>2025</year>
          . Accessed:
          <fpage>2025</fpage>
          -04-07.
        </mixed-citation>
      </ref>
      <ref id="ref3">
        <mixed-citation>
          [3]
          <string-name>
            <given-names>C.</given-names>
            <surname>Chambers</surname>
          </string-name>
          ,
          <string-name>
            <given-names>D.</given-names>
            <surname>Ungar</surname>
          </string-name>
          ,
          <article-title>Iterative type analysis and extended message splitting; optimizing dynamically-typed object-oriented programs</article-title>
          ,
          <source>in: Proceedings of the ACM SIGPLAN 1990 conference on Programming Language Design and Implementation</source>
          ,
          <year>1990</year>
          , pp.
          <fpage>150</fpage>
          -
          <lpage>164</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref4">
        <mixed-citation>
          <article-title>[4] Llvm jump threading</article-title>
          , https://llvm.org/docs/Passes.html#
          <article-title>jump-threading-jump-</article-title>
          <string-name>
            <surname>threading</surname>
          </string-name>
          ,
          <year>2025</year>
          . Accessed:
          <fpage>2025</fpage>
          -04-07.
        </mixed-citation>
      </ref>
      <ref id="ref5">
        <mixed-citation>
          <article-title>[5] Llvm predicate info</article-title>
          , https://llvm.org/doxygen/PredicateInfo_8h.html,
          <year>2025</year>
          . Accessed:
          <fpage>2025</fpage>
          -04-07.
        </mixed-citation>
      </ref>
      <ref id="ref6">
        <mixed-citation>
          [6]
          <string-name>
            <given-names>N.</given-names>
            <surname>Palumbo</surname>
          </string-name>
          , G. Polito,
          <string-name>
            <given-names>S.</given-names>
            <surname>Ducasse</surname>
          </string-name>
          , P. Tesone,
          <article-title>Meta-compilation of baseline jit compilers with druid</article-title>
          ,
          <source>The Art, Science, and Engineering of Programming</source>
          <volume>10</volume>
          (
          <year>2025</year>
          ). URL: http://dx.doi.org/10.22152/ programming-journal.org/
          <year>2025</year>
          /10/9. doi:
          <volume>10</volume>
          .22152/programming-journal.org/
          <year>2025</year>
          /10/9.
        </mixed-citation>
      </ref>
      <ref id="ref7">
        <mixed-citation>
          [7]
          <string-name>
            <given-names>R.</given-names>
            <surname>Cytron</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Ferrante</surname>
          </string-name>
          ,
          <string-name>
            <given-names>B. K.</given-names>
            <surname>Rosen</surname>
          </string-name>
          ,
          <string-name>
            <given-names>M. N.</given-names>
            <surname>Wegman</surname>
          </string-name>
          ,
          <string-name>
            <given-names>F. K.</given-names>
            <surname>Zadeck</surname>
          </string-name>
          ,
          <article-title>Eficiently computing static single assignment form and the control dependence graph</article-title>
          ,
          <source>ACM Transactions on Programming Languages and Systems (TOPLAS) 13</source>
          (
          <year>1991</year>
          )
          <fpage>451</fpage>
          -
          <lpage>490</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref8">
        <mixed-citation>
          [8]
          <string-name>
            <given-names>L.</given-names>
            <surname>Birkedal</surname>
          </string-name>
          ,
          <string-name>
            <given-names>M.</given-names>
            <surname>Welinder</surname>
          </string-name>
          ,
          <article-title>Hand-writing program generator generators</article-title>
          ,
          <source>in: Proceedings of the 6th International Symposium on Programming Language Implementation and Logic Programming</source>
          ,
          <source>PLILP '94</source>
          , Springer-Verlag, Berlin, Heidelberg,
          <year>1994</year>
          , p.
          <fpage>198</fpage>
          -
          <lpage>214</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref9">
        <mixed-citation>
          [9]
          <string-name>
            <surname>E. Miranda,</surname>
          </string-name>
          <article-title>The cog smalltalk virtual machine</article-title>
          ,
          <source>in: Proceedings of VMIL</source>
          <year>2011</year>
          ,
          <year>2011</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref10">
        <mixed-citation>
          [10]
          <string-name>
            <surname>L. De Moura</surname>
            ,
            <given-names>N. Bjørner,</given-names>
          </string-name>
          <article-title>Z3: an eficient smt solver</article-title>
          ,
          <source>in: Proceedings of the Theory and Practice of Software, 14th International Conference on Tools and Algorithms for the Construction and Analysis of Systems, TACAS'08/ETAPS'08</source>
          , Springer-Verlag, Berlin, Heidelberg,
          <year>2008</year>
          , p.
          <fpage>337</fpage>
          -
          <lpage>340</lpage>
          .
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>