<!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>
      <journal-title-group>
        <journal-title>Journal of Functional Programming (1996).
[12] I. Thomas</journal-title>
      </journal-title-group>
    </journal-meta>
    <article-meta>
      <article-id pub-id-type="doi">10.1145/1064979.1064996</article-id>
      <title-group>
        <article-title>Static Escape Analysis in Pharo: Towards Minimizing Object Allocations</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Faouzi Mokhefi</string-name>
          <xref ref-type="aff" rid="aff1">1</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Stéphane Ducasse</string-name>
          <xref ref-type="aff" rid="aff1">1</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Pablo Tesone</string-name>
          <xref ref-type="aff" rid="aff1">1</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Luc Fabresse</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <aff id="aff0">
          <label>0</label>
          <institution>Institut Mines Telecom Nord Europe</institution>
          ,
          <addr-line>Douai, Nord</addr-line>
          ,
          <country country="FR">France</country>
        </aff>
        <aff id="aff1">
          <label>1</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>
      </contrib-group>
      <pub-date>
        <year>2025</year>
      </pub-date>
      <volume>952</volume>
      <fpage>111</fpage>
      <lpage>120</lpage>
      <abstract>
        <p>Object-oriented programming idioms, such as boxing numbers or using design patterns like builders and commands, often create numerous short-lived objects. These objects put pressure on garbage collectors and are ideal candidates for static optimizations such as object inlining [1]. Escape analysis identifies these short-lived objects, ofering insights for future optimizations-both manual and automatic. Traditionally, escape analysis has been applied to statically-typed languages. However, dynamically-typed languages introduce additional uncertainty, particularly due to highly polymorphic call sites. In this paper, we present escapha, the first implementation of a context-sensitive, flow-insensitive, interprocedural escape analysis for the dynamically-typed language Pharo. We applied escapha to various Pharo packages, and it successfully identified instances of short-lived object creation and potential locations for optimization. Our approach demonstrates that static analysis can identify a small but relevant number of optimizable allocation sites. We found 280 candidates after analyzing 24,000 methods for a static call graph depth of 10. The paper also discusses the limitations of our analysis, particularly regarding the complexity of the call graph, and explains how we heuristically manage the potential explosion of analysis paths.</p>
      </abstract>
      <kwd-group>
        <kwd>eol&gt;Static Escape Analysis</kwd>
        <kwd>static call graph</kwd>
        <kwd>interprocedural analysis</kwd>
      </kwd-group>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>1. Introduction</title>
      <p>
        Object-oriented programming idioms, such as integer boxing, encapsulation, reification, and design
patterns (e.g., Builder, Command, Visitor) [
        <xref ref-type="bibr" rid="ref2">2</xref>
        ], often result in the creation of many short-lived or transient
objects. Moreover, well-structured object-oriented design, as promoted by design patterns, encourages
frequent object creation and extensive use of late-bound calls. These objects typically have a short
lifespan, limited to the execution scope of a single method.
      </p>
      <p>
        Object-oriented programming languages typically rely on garbage collection (GC) algorithms to
automatically reclaim memory no longer used by the application. The abundance of short-lived objects
increases the workload of these algorithms, as more allocated objects must be traced. Generational
garbage collectors [
        <xref ref-type="bibr" rid="ref3 ref4">3, 4</xref>
        ] have been designed to handle such object lifecycles efectively. However,
avoiding object allocation altogether can further reduce the pressure on the GC.
      </p>
      <p>
        This situation has led to the development of optimizations, with varying levels of automation, based
on escape analysis [
        <xref ref-type="bibr" rid="ref5">5, 6, 7</xref>
        ]. Escape analysis is a static compiler optimization technique used to determine
whether the lifetime of data exceeds its static scope [8]. In the context of object-oriented programming
(OOP), it identifies objects that are allocated only within a local scope and whose contents can be
optimized without afecting the program’s correctness. Most existing escape analysis approaches are
designed for statically-typed languages [7], or, in the case of the PyPy Python implementation, focus
specifically on avoiding integer boxing [ 9]. Similarly, Google’s TurboFan JavaScript compiler uses
escape analysis to inline temporary objects.
      </p>
      <p>This motivates our research into discovering optimization opportunities through static analysis for
the dynamically-typed object-oriented programming language Pharo [10]. In Pharo, computation
occurs primarily through the creation of many small objects that communicate via message passing. The
challenge of analyzing Pharo stems from lexical closures existing as first-class entities. This means they
can be stored and passed as arguments, and capture their defining environment. From this perspective,
Pharo combines object-oriented programming with strong functional programming characteristics.
This has a significant impact on escape analysis, as analyzing closures locally is insuficient to determine
whether a variable escapes its scope.</p>
      <p>In this article, we address the following objectives:
1. Identify allocation sites that potentially produce multiple objects which do not outlive the
lifetime of their originating method’s receiver, using static analysis.</p>
      <p>Research questions:
• Can static analysis identify non-escaping variables in Pharo?
• What are the key parameters that define the analysis search space (such as number of
implementors, call stack depth, etc.)?
• What variations in analysis strategies are possible?
2. Evaluate the relevance and impact of the proposed approach on performance. Research
questions:
• How should such an approach be evaluated?
• Are there heuristics that can make the analysis tractable?
• If the analysis is expensive but yields significant benefits, can it be performed in a batch
mode statically?</p>
      <p>In this paper, we present escapha (Section 3), the first implementation of a context-sensitive escape
analysis for the dynamically-typed language: Pharo. escapha builds an alias graph that tracks variables
referring to newly created objects. It performs interprocedural analysis to handle aliases involving
method arguments. A major challenge in such static analysis for dynamically-typed languages is the
high number of polymorphic call sites, which leads to large and complex call graphs. This complexity
can negate the performance benefits of subsequent optimizations. To address this, the proposed analysis
limits the depth of graph exploration and adopts an open-world assumption.</p>
      <p>We applied escapha to various Pharo packages, where it successfully identified instances of
shortlived object creation and potential optimization opportunities. We also discuss the limitations of the
analysis, particularly regarding the complexity of the call graph, and explain how we heuristically
manage the explosion of possible paths (Section 5). Our approach demonstrates that static analysis
can identify a small but relevant number of optimizable allocation sites. We identified 280 candidates
among 24,000 methods, for a call graph depth of 10.</p>
      <p>The outline of the paper is as follows. We begin by discussing the challenges of escape analysis in the
specific context of Pharo and present a motivating example (Section 2.1). We then describe the proposed
analysis and examine its potential to reduce allocation rates. In Section 4, we experimentally evaluate
the efectiveness of the analysis, focusing on its complexity and strategies to mitigate the explosion in
call graph size during static analysis.</p>
    </sec>
    <sec id="sec-2">
      <title>2. Challenges of Identifying Escaping Variables</title>
      <p>Heap allocation is significantly slower and more resource-intensive than stack allocation or register use
[11]. Moreover, adhering to good object-oriented design principles and best practices often results in a
higher rate of object allocation at runtime.</p>
      <p>
        From the perspective that compilers should address such ineficiencies through optimization, this
work focuses on identifying short-lived objects within methods that could be eliminated through stack
allocation or object inlining. Object inlining [
        <xref ref-type="bibr" rid="ref1">1</xref>
        ] is an optimization technique in which referenced
objects of class B are merged into their referencing objects of class A, efectively embedding the fields
and methods of B into A.
      </p>
      <p>In the remainder of this section, we detail a motivating example. Then, we discuss some specific
challenges that arise when applying static analysis to Pharo, which extensively uses polymorphic
methods.</p>
      <sec id="sec-2-1">
        <title>2.1. Motivating Example</title>
        <p>Listing 1 shows a code snippet taken from Pharo’s graph algorithms library. Each time the method
heuristicFrom:to: is called, it instantiates two objects used only within it.</p>
        <p>1. The local variable dijkstra (line 4) refers to an AIDijkstra object created during each iteration of
the AIstar algorithm.
2. The local variable parameters (line 10) refers to an OrderedCollection (a generic list) used only as
an intermediary to store model parameters. These parameters are then extracted and passed to
the Dijkstra object.</p>
        <p>In this example, allocating heap memory for these objects is unnecessary because their references
are stored in local variables and the objects do not outlive the method in which they are created. One
possible optimization is to allocate them on the stack (within their method’s activation context) instead
of on the heap.</p>
        <p>Escape analysis helps identify such unnecessary object allocations and highlights candidates for
optimization. This analysis determines whether object inlining is possible. For instance, it could replace
the use of OrderedCollection with a direct, method-local implementation inside heuristicFrom:to:.
Similarly, it can evaluate whether the functionality of the AIDijkstra object and its used methods can be
embedded directly into its caller. Such inlining requires verifying that none of the invoked methods
create aliases to the object’s internal state. For example, the methods from:to:width:, start:, end:, run,
and findNode: must be analyzed to ensure no references to inlined objects escape their scope.</p>
      </sec>
      <sec id="sec-2-2">
        <title>2.2. Static Analysis on Dynamically-typed Languages: The case of Pharo</title>
        <p>Pharo is a highly dynamically-typed, pure object-oriented, and reflective language where computation
is primarily expressed through extensive message passing. One of its defining characteristics is the
pervasive use of lexical closures. These closures are first-class entities: they can be stored, passed as
arguments, and executed later, all while capturing their lexical environment. This functional feature is
deeply integrated into the language. For example, Pharo does not provide built-in control structures such
as loops or conditionals. Instead, such constructs are expressed through message sends (for example,
do:, if True:), which developers can redefine or extend. It is common for developers to redefine these
messages when creating Domain Specific Languages (DSLs).</p>
        <p>Pharo also supports powerful reflective capabilities. Developers frequently use methods such as
perform:with: to dynamically construct and invoke messages, often based on metadata such as method
annotations [12].</p>
        <p>Number of implementors</p>
        <p>Listing 2 illustrates typical Pharo code that exhibits these dynamic features:
• Polymorphism: Use of the do: iterator. Pharo’s standard library includes 52 diferent
implementations of do:, and developers can define additional ones [10].
• Reflection: Use of perform:with: to dynamically invoke a method based on metadata retrieved
at runtime.</p>
        <p>• Closures: Use of multiple lexical closures to capture and manage control and data flow.
1 stSpotterProcessorsFor: aSpotterStep
2
3
4
5
6
7
(Pragma allNamed: #stSpotterOrder: from: self class to: Object)
do: [ :aPragma |
self perform: aPragma methodSelector with: aSpotterStep.
aSpotterStep processors
ifNotEmpty: [ :list | list last order: (aPragma argumentAt: 1) ] ]</p>
        <sec id="sec-2-2-1">
          <title>Listing 2: Example of reflective and closure uses</title>
          <p>These dynamic features, including closures, polymorphism, and reflection, present significant
challenges for static analysis. Closures may escape their lexical scope, which makes local reasoning
insuficient. A global analysis is often required to determine whether a variable escapes. To illustrate
the impact of polymorphism on static analysis, we present statistics on method name reuse in Pharo
13. As shown in Table 1, the system contains 140,463 compiled methods. Of these, 63,217 have unique
names, representing 45% of the total. Figure 1 presents the distribution of the method implementors.</p>
          <p>This indicates that a name in the code usually corresponds to two methods or more. Note that the
selector initialize is a major outlier with 1,879 methods, typically invoked via self initialize.</p>
          <p>These dynamic characteristics limit the assumptions that static analysis tools can make about specific
message sends or method names. As a result, conventional static analysis techniques cannot be directly
applied to Pharo without significant adaptation.</p>
        </sec>
      </sec>
      <sec id="sec-2-3">
        <title>2.3. Summary</title>
        <p>Some short-lived objects are repeatedly allocated on the heap but remain confined to the method in
which they are created, presenting clear opportunities for optimization through stack allocation or
object inlining. Escape analysis plays a crucial role in identifying these candidates for optimization.
However, performing static analysis in Pharo poses significant challenges due to its pervasive use of
polymorphism, lexical closures, and runtime reflection. These dynamic features introduce uncertainties
that hinder conventional static reasoning and often require more sophisticated or whole-program
analysis techniques. In the next section, we introduce our solution: escapha.</p>
      </sec>
    </sec>
    <sec id="sec-3">
      <title>3. escapha: a Simple Static Escape Analysis</title>
      <p>In this section, we provide a brief definition of the vocabulary, the meta-model, and the static analysis
we developed. It takes as input a single method. The goal, however, is to apply it to method batches.
The analysis yields false positives that necessitate manual inspection for a definitive conclusion. We
obtain a set of non-escaping variables, along with the analysis snapshots for each method to be reused.</p>
      <sec id="sec-3-1">
        <title>3.1. Illustration</title>
        <p>The following case scenario 3 exemplifies the awaited results of the analysis.</p>
        <p>| newObject outputStream outputStreamCopy |
newObject := Object new.
outputStream := Stream new.
self bar: newObject.
outputStreamCopy := outputStream returnSelf.</p>
        <p>newObject printOn: outputSteam.</p>
        <p>The Method foo: has one argument that is discarded from the followed variables set because it is the
root of the call graph. It creates two objects ,  ∈ , held in newObject, outputStream
respectively. We visit the ast node of the method and treat every statement that references one of
the followed variables p ∈  . In this example, the message sends AST nodes of interest are in lines
6, 7, 8, 11 ∈ . Line 8 message corresponds to two potential compiled methods, Object»printOn:
, A»printOn:, the rest are monomorphic call sites.</p>
        <p>The resulting call graph associated with Listing 3 is composed of 6 nodes: two nodes for the message
printOn: implementations, two for bar: and returnSelf each. The nodes related to bar: refer back to
their caller. The call graph could be recursively expanded further to cover the message copy at a greater
depth. The local variables and parameters form a points-to graph in Figure 3.</p>
        <p>outputStream is aliased by outputStreamCopy assumed to be a return of method returnSelf.
outputStreamCopy is aliased by variables aStream from Object»printOn: and stream from A»printOn:</p>
        <p>According to escape conditions 3.2.2, all variables outputStream, outputStreamCopy, newObject
escape.</p>
      </sec>
      <sec id="sec-3-2">
        <title>3.2. Meta Model</title>
        <p>Our analyzer takes as input a single method for individual analysis and identifies a set of variables that
are guaranteed to be of non-escaping.</p>
        <p>To achieve this, we combine escape analysis with points-to analysis, focusing on the data flow of
references while neglecting control flow. We denote the abstraction of runtime features of a given
program:
• O: the set of all heap objects potentially allocated during execution, also referred to as pointer
targets. Each is associated with the AST statement responsible for its allocation.
• P: the set of alias variables that reference heap objects in O, each corresponding to a local variable
or an argument. Given a reference , the set  () denotes the heap objects it may point to; there
must exist an  ∈  such that  is the first element in a points-to chain starting from . The
analysis primarily only follows these variables’ aliases and infers their types instead of deeply
inferring all arbitrary expressions.
• I: the set of all method invocations, also called analysis contexts. Each call site where at least one
argument is in P is associated with one or more contexts, particularly in the case of polymorphic
call sites. This is further discussed under virtual call resolution.</p>
        <p>An object  ∈ , may be instantiated using diferent selectors according to the class definition,
with new message or other known constructors. Our approach only focuses on the subset problem by
calls</p>
        <p>aliases
MethodContext
defines</p>
        <p>FollowedVariable
considering this specific message and other derivatives that begin with the prefix new such as newFrom:
because they are definite allocations. The impediment of this selectiveness is that it overlooks many
objects and data structures, such as Arrays ( e.g. Array from: aCollection) that are frequently used but
are created otherwise.</p>
        <p>Virtual call resolution is the process of identifying all possible target methods that a virtual call may
invoke at runtime. It involves linking the arguments at the call site with the parameters of the declared
method. In dynamically-typed languages, achieving precision in target methods is dificult due to the
loss of type information caused by message sends and variable reassignment. This feature introduces
context sensitivity into the points-to analysis.
3.2.1. Input
Our analyzer takes as input a single method within the context of a class hierarchy. For the analysis to
be meaningful, the method must include at least one explicit allocation. We only consider allocations
found at the root method analyzed.
3.2.2. Escape Conditions
The analysis considers a variable as escaping (i.e., outliving its method context) if it or any of its aliases
(other references to the same memory location) satisfies one of the following conditions:
1. Returned and propagated up to the top level of the call stack.
2. Assigned to an instance variable, global variable, or shared variable.
3. Passed to or referenced within a lexical closure that may be evaluated later in the call graph,
starting from its defining method.
4. Stored in a collection, making it dificult for the analyzer to track individual elements.
5. Used in reflective or primitive operations, such as instVarAt:put:.</p>
        <p>6. Involved in a serialization process.
3.2.3. Output
It is a set of non-escaping variables, along with the analysis history. This history is useful for deriving
statistical insights from the results.</p>
        <p>Internally, as illustrated in Figure 4, our analyzer takes a method as input and creates a corresponding
representative entity in the model. We call it an MethodContext. It then attaches additional objects to
this method context:
• (1) Other MethodContext instances for each message send within the input method. It represents
one of the possible implementations for a given message.
• (2) A list of temporaries, referred to as FollowedVariables, possibly referencing a heap-allocated
object. The analysis binds the caller’s followed variables to the arguments of the callee’s method
context [13]. This allows the analysis to determine whether a variable escapes the scope of the
root method.</p>
      </sec>
      <sec id="sec-3-3">
        <title>3.3. Analysis Steps</title>
        <p>In this section, we summarize the analysis in the following steps and pseudo-algorithm 1. As presented
before, the input is a compiled method that is then visited in its AST form.</p>
        <p>1. Create a root method context to statically represent runtime properties of the given method for
the analysis. The entry method is set as the root node of the static call graph. Normally, this
method contains the allocations to be studied.
2. Represent dynamically allocated objects as entities in the points to graph. Then, only select in
the root method definition, local variables that are assigned these objects to append as nodes to
the graph and to follow during the points-to phase.
3. Select call-sites whose arguments are followed and appear in the points-to graph. Since 67% of
the selectors in Pharo have multiple implementations, as shown in Figure 1. We need to consider
each of them in the static call graph. The
Virtual call resolution determines possible implementations using type propagation or retrieves
all implementations when type information is insuficient. This involves examining the receiver
class hierarchy.
4. Store the call graph nodes in the working list following the sequential order of execution, whilst
neglecting the control flow.
5. For each element of the working list we need to:
• Create a new method context.
• Create its followed variables.
• Link the new method context to the existing method context in the abstract static call graph.
• Unify the formal arguments of the new method declaration with the arguments at the
call-site of its caller.</p>
        <p>• Verify and propagate the escape condition of the variables.
6. Check escape constraints.</p>
        <p>7. Pop the next element in the working list and repeat steps from (4).</p>
      </sec>
      <sec id="sec-3-4">
        <title>3.4. Type Propagation</title>
        <p>Since type compatibility between the assigned and assignee is not required in dynamically-typed
languages, we cannot rely on this information to predict a variable’s type. We take the example of an
assignment. On the right-hand side, a message send return value is conservatively assumed to be of
unknown type. This increases the likelihood of inconclusive program analysis.</p>
        <p>To address this issue, we record the types of variables starting from their explicit allocation sites (e.g.,
via the new message or class-side messages) and propagate this information to all aliasing variables.
We assess the impact of this heuristic in the evaluation 4.</p>
        <p>We chose this approach for its simplistic aspect and low impact on analysis time, comprad to type
inference tools [14]. These traditional models provide complete type lattices for all variables. Our
recorded types are simply stored as the class of the object’s explicit declaration in the allocation
statement. It is then associated with the representation of the entity  ∈  referencing the object in
question  ∈  () in the points-to graph.</p>
        <p>Now that we have defined our approach to find unnecessary allocations. We evaluate it in the next
section.</p>
      </sec>
    </sec>
    <sec id="sec-4">
      <title>4. Evaluation</title>
      <p>To better understand escapha’s behavior on real-world programs and evaluate the impact of the
proposed precision-enhancing features, we conducted a series of experiments under diferent settings.</p>
      <sec id="sec-4-1">
        <title>4.1. Methodology</title>
        <p>First, we select a subset of Pharo system methods ( ℎ) that include explicit allocations using
the new message. This subset consists of 24,000 compiled methods.</p>
        <p>We then analyze various parameters of the approach, such as call graph depth, worklist size, and the
number of implementors per call site, by varying some parameters while keeping others fixed to assess
their individual efects.</p>
        <p>To evaluate the scalability of the algorithm with respect to the number of initial method parameters,
we use execution time as the performance metric.</p>
      </sec>
      <sec id="sec-4-2">
        <title>4.2. Approximating Precision and Recall</title>
        <p>The preselected  ℎ for the analysis are not annotated. Instead of doing so, we select fewer
packages to annotate and calculate the precision of the analysis in finding true positives at this scale.</p>
        <p>The dificult part of this approach is the validation of the found samples. An automatic validation
raises the question of how to ensure program execution will reach the optimization point? Crafting
such applications to tackle the specific running scenario is a challenge that we do not address here.</p>
        <p>Although unit test methods are most common among the results and are immediately available to
refactor into optimised code, the standard methods are found too.</p>
        <p>Instead, we do a manual validation of the escape conditions described in section 3.2.2. We write
specific unit tests to evaluate the proposed changes on candidate allocation sites found in test methods,
or visually navigate through the static call graph. The latter is the default approach we take for standard
methods.</p>
        <p>We apply stack allocation, object fusion, or object deletion at the end of program execution, and
confirm that the resulting code runs without errors. A clear direction for future work is to leverage
automatic code refactorings for validation.</p>
        <p>Threats to Validity: This evaluation involves manual checks, which are very time-consuming;
therefore, the results are incomplete and may include errors. Furthermore, we typically run the
experiment within a single Pharo image, and we maintain the detailed analysis context information,
which can lead to longer garbage collection times and influence the time complexity described in the
tables below 2 3 4.</p>
      </sec>
      <sec id="sec-4-3">
        <title>4.3. Intraprocedural Analysis</title>
        <p>We begin by running the intraprocedural analysis on  ℎ, limiting the call graph depth to
1 and restricting the maximum number of elements in the worklist to 5000 methods. This yields 54
candidate allocation sites from the global set , where  is defined as the union of   from each
local method in the initial subset of Pharo methods. Here,   denotes the set of assignment nodes
whose right-hand side is an allocation. For optimization purposes, 25 of these sites are located in test
methods (see the first line of table 2).</p>
        <p>Depth</p>
        <p>Analysis time</p>
        <p>Candidates</p>
      </sec>
      <sec id="sec-4-4">
        <title>4.4. Heuristics Analysis</title>
        <p>In this section, we aim to determine when an analysis run should be discarded. Among the possible
parameters for the open-world assumptions, we define three that act as limitations: call graph depth,
number of implementors, and worklist length.</p>
        <p>Call Graph Depth Setting the depth to 1 efectively disables interprocedural analysis. When the
depth exceeds 2, summary information becomes necessary, as the static call graph may revisit nodes.
Since the graph is constructed on the fly, such revisits can form closed cycles and eventually lead to
indirect recursion.</p>
        <p>Number of Implementors For messages with a large number of implementors, this parameter
controls the maximum number to consider. For instance, a simple analysis reveals that in Pharo 13, 1448
selectors have more than 10 implementations. This parameter controls the number of implementors
considered at a given call site.</p>
        <p>Worklist Length The worklist for a given root method is limited to a maximum size, which restricts
the expansion of the call graph. This is particularly useful for handling long methods. Let’s suppose
large methods are methods exceeding 10 lines of code; we have 14407 of them in a Pharo image alone.</p>
        <p>These help us assess whether a thorough analysis is worthwhile. We mainly explore variations across
call graph depth and input batch size.</p>
        <p>Total methods</p>
        <p>Table 2 presents the results obtained by keeping the input ( ℎ) fixed while varying the
depth of the call graph. We observe the following:
• As the call graph depth increases, the analysis takes more time. Beyond a depth of 3, the execution
time becomes excessively high.
• The activation of interprocedural property significantly enhances the outcome of the analysis,
even at one higher degree of depth.</p>
      </sec>
      <sec id="sec-4-5">
        <title>4.5. Varying The Input Method Sample With Fixed Depth</title>
        <p>In this section, we report the performance of the algorithm with type propagation enabled.</p>
        <p>Table 3 shows the results of the analysis with the call graph depth of 2, performed on an increasing
number of methods, ranging from 4800 to 24000.</p>
        <p>The relationship between the input batch size and analysis time is not linear. The analysis time does
not increase at a constant rate per additional method, and there are two very significant jumps (at 14,400
and 24,000 methods) that suggest specific methods causing extreme slowdowns. For it to be proportional,
doubling the methods would roughly double the time, which is not consistently happening here. The
results highlight the influence of methods with multiple implementors. It shows poor scalability due to
non-linear behavior with particular performance drops for higher workloads.</p>
      </sec>
      <sec id="sec-4-6">
        <title>4.6. Varying Depth for a Small Input Method Sample</title>
        <p>We intentionally select a small subset of 50 methods, which were previously identified as containing
optimization candidates. We then vary the call graph depth to observe at which point the results
stabilize.</p>
        <p>We observe from the table 4:
• Interprocedural analysis ofers clear benefits. The number of identified candidates increases from
12 to 43 (with 50 being the maximum possible).
• Beyond a call graph depth of 3, the results reach a plateau.
• It is important to note that the analysis is biased, as each method has unique characteristics. This
makes it dificult to generalize findings from a randomly selected subset.</p>
        <p>• The simple type propagation reduces time complexity and filters fewer true positive candidates.</p>
        <p>We can deduce from 4 and 2 that a call graph depth of 3 is enough to get escape information for a
reasonable execution time, so we follow the analysis with these optimal parameters. For validation, we
restrain the input batch of the analysis to include a few packages for annotation.</p>
      </sec>
    </sec>
    <sec id="sec-5">
      <title>5. Discussions and Pharo specific Analysis Concerns</title>
      <sec id="sec-5-1">
        <title>5.1. Field Sensitivity</title>
        <p>In this section, we discuss an efort to improve the precision of the escape analysis by incorporating
ifeld sensitivity. This is part of our future work and is not yet fully implemented.</p>
        <p>Many objects are embedded as fields within other objects. We are particularly interested in cases
where the outcome of the escape analysis for a field object depends on the escape status of its container.
For example, consider the case where a point object pt1 is stored in a line object line1.
In the current implementation, the point is assigned to an instance variable of the Line class and is
therefore conservatively marked as escaping.</p>
        <p>In future work, we aim to refine this behavior by analyzing the escape status of the container object. If
the container (in this case, the line) does not escape, it may be possible to conclude that the contained
object (the point) also does not escape. This requires a deeper analysis of how instance variables are
used and propagated.
pt1 := Point new.
pt1 setX: 1 setY: 1.
line1 := Line new.
line1 addPoint: pt1.</p>
        <p>Listing 4: Simplified example of non-escaping object assigned to instance variable</p>
        <p>We conducted an additional exploratory phase of the analysis to increase the efectiveness of candidate
detection. In this phase, we introduced field sensitivity by treating fields as tracked variables. The
motivation behind this approach is that, by analyzing field usage, we may identify true positives that
were previously missed due to conservative assumptions about instance variable assignments.</p>
        <p>A representative example is found in the HoneyGinger library (see Example 5), where each
HGSimulator instance holds a set of actions and particles in its instance variables. These objects are used
exclusively within the scope of the program execution and are never exposed externally. According to
the definition of escape, such objects should not be considered escaping. However, the current analysis
conservatively marks them as escaping.</p>
        <p>Depth
n</p>
        <p>Analysis time
w/ TP w/o TP</p>
        <p>Candidates
w/ TP w/o TP
simulator addParticleAt: 500 @ 500 temperature: 390 mass: 100.0.</p>
        <p>World activeHand showTemporaryCursor: HGSimulator.
. . .</p>
        <sec id="sec-5-1-1">
          <title>Listing 5: Program with inlineable embedded objects</title>
          <p>Our approach to addressing the misclassification of non-escaping variables as escaping is summarized
as follows:
1. The first step examines all references to the instance variable within its defining class. The
same escape conditions applied to temporary variables and blocks are reused here. The analysis
targets only the referenced instance variable within its referencing methods. This step determines
whether the analysis should proceed, which is the case when the instance variable escapes solely
through its accessor.
2. The second step constructs a static call graph in the reverse direction compared to the standard
analysis flow. Specifically, it collects all senders of the accessor identified in the previous step.</p>
          <p>These message sends are treated as variable references to the instance variable.
3. Finally, the analysis inspects all uses of the accessor in the collected sender methods and resumes
interprocedural analysis in the usual forward execution direction, applying the standard escape
constraint-solving logic.</p>
          <p>We could not measure the improvement brought by this approach due to Pharo-specific constraints.</p>
        </sec>
      </sec>
      <sec id="sec-5-2">
        <title>5.2. Cascade and Indirect Object Allocation</title>
        <p>Object creation can occur in several ways: through direct use of new, via factory methods, or as part of
a message cascade. Each of these cases requires dedicated static analysis strategies.
1. The standard form of allocation is explicit, using new or similar primitives, with the result assigned
to an aliasing variable. Tracking such objects involves adding an abstraction to the points-to
graph.
2. A second category includes indirect allocations, such as those performed by factory methods.</p>
        <p>These allocations are initiated by message sends—often not primitive—that eventually call a
primitive such as new or basicNew. While our focus is on identifying escaping objects, other
approaches use runtime execution data to locate such allocations [15]. In our analysis, we restrict
this category to class-side methods, which are most likely to act as indirect allocation sites.
3. In cascaded messages, an object created in the first message should be tracked as an alias of self
in the subsequent messages. The return value of the cascade depends on the final message. In
listing 6 for instance, if the last message is yourself, the result of the initial allocation is returned.
In such cases, the variable on the left-hand side of the assignment should be associated with the
created object.</p>
        <p>receiver := FixtureEscapeAnalysis new
anotherMessage: 5;</p>
        <sec id="sec-5-2-1">
          <title>Listing 6: Cascade message assignment</title>
          <p>Another challenge involves indirect aliasing. For example, in a message send where an argument
itself is the result of an allocating message, aliasing becomes interprocedural. To broaden the scope of
the analysis, we must recognize allocation sites beyond assignments of the form variable:= Object new.</p>
          <p>Proposition: Cascades and non-assigned allocating messages should be modeled as abstract entities.
They contain the object’s type and a unique identifier in the analysis.</p>
        </sec>
      </sec>
    </sec>
    <sec id="sec-6">
      <title>6. Related Work</title>
      <sec id="sec-6-1">
        <title>6.1. Points-to/Alias Analysis</title>
        <p>Escape analysis is heavily influenced by and built on top of points-to analysis. Milanova et al.,. [16]
explored diferent levels of context-sensitivity for points-to analysis, showing that object-sensitivity
provides better precision for object-oriented languages than call-site sensitivity. Our approach takes
into consideration these insights to apply to Pharo’s programming language model.</p>
        <p>Early work [17] highlighted naming schemes for alias variables. Their approach involved retaining
the original variable names or generating synthetic names during the analysis. In our implementation,
we chose the latter representation to compose a unique identifier for the memory location in diferent
execution contexts. Steensgaard presented an almost linear time points-to analysis algorithm based on
type inference. It is flow-insensitive, context-insensitive due to the monomorphic characteristic of the
subject type system, and interprocedural. The eficiency lies in the representation of multiple potential
runtime locations by a single graph component. Including all elements of composite objects, such as
struct objects in C.</p>
        <p>In [18], Rak-amnouykit et al., provided a hybrid evaluation of traditional static points-to analysis
with concrete evaluation using the Python interpreter. Static analysis tools typically require access to
the source code of external libraries to understand their behavior and propagate information through
the program. When this code is missing, PoTo attempts a concrete evaluation of such expressions in
their enclosing import environment and infers concrete types in the new 3-address code representation.
When processing call statements or field access statements, the analysis checks if the object being
operated on is concrete. If it is a concrete object, the analysis attempts concrete evaluation of the
operation, returning a new concrete object and adding it to the points-to set. This provides more
information during constraint resolution and improves program coverage.</p>
      </sec>
      <sec id="sec-6-2">
        <title>6.2. Escape Analysis</title>
        <p>[19] proposes a framework of escape analysis and demonstrates an application on Java programs,
introducing a context-sensitive, flow-sensitive algorithm to identify the non-escaping objects in methods
and threads eficiently. This enables stack allocations and synchronization removal and serves as the
basis for subsequent work on escape analysis for object-oriented programming languages.</p>
        <p>Wang et al., apply optimizations for the existing escape analysis for the Go programming language
(Golang) [20]. They transform the code with situations considered escape, mainly caused by passing
pointer arguments holding a reference to variables in the points-to set. It bypasses Golang’s escape
analysis mechanism by converting the pointer through intermediate types. This breaks the compiler’s
ability to trace the pointer back to the original object based solely on this function call. They evaluate
real-world projects before and after code optimisations using memory usage and time consumption
metrics.</p>
        <p>
          Kotzmann et al., present a new intraprocedural and interprocedural algorithm for escape analysis in
the context of dynamic compilation, where the compiler has to cope with dynamic class loading and
deoptimization [
          <xref ref-type="bibr" rid="ref5">5</xref>
          ]. It operates on an intermediate representation in SSA form. It introduces equi-escape
sets for the eficient propagation of escape information between related objects. The analysis is used
for scalar replacement of fields and synchronization removal, as well as for stack allocation of objects
and fixed-sized arrays.
        </p>
        <p>The all-or-nothing approach taken by most Escape Analysis algorithms prevents all these
optimizations as soon as there is one branch where the object escapes, no matter how unlikely this branch is
at runtime. [7] presents a new, practical algorithm that performs control flow sensitive partial escape
analysis in a dynamic Java compiler. It allows escape analysis, scalar replacement, and lock elision to be
performed on individual branches. The algorithm is implemented on top of the Just-in-time compiler.</p>
      </sec>
      <sec id="sec-6-3">
        <title>6.3. Tracing JITs</title>
      </sec>
    </sec>
    <sec id="sec-7">
      <title>7. Conclusion</title>
      <p>In dynamically-typed languages, Bolz et al.,. [21] demonstrated how trace-based JIT compilers can
achieve similar optimizations through specialization.</p>
      <p>After outlining the motivations and challenges of static analysis, we focused on the specific dificulties
of performing such analysis in a purely static setting within a dynamically-typed language like Pharo.</p>
      <p>We introduced escapha, an analysis designed to identify non-escaping variables. It represents a
ifrst implementation of an object- and context-sensitive, flow-insensitive escape analysis for the Pharo
language.</p>
      <p>We discussed the impact of type refinement, field nesting, and the various forms of object creation.
Additionally, we reported empirical results concerning worklist size and call graph depth efects on
static analysis on diferent batch sizes.</p>
      <p>For the future, we would like to experiment with combining type inference tools, for example,
RoelTyper [14]. It helps in finding types of instance variables statically, thereby we expect a higher
precision. We also want to include more forms of allocation into the points-to graph initial nodes. We
mentioned in 5.2 allocations found in cascades, factory methods, and builders.</p>
    </sec>
    <sec id="sec-8">
      <title>8. Acknowledgments</title>
      <p>We acknowledge the support of the Region Hauts de France for the PhD of the first author.</p>
    </sec>
    <sec id="sec-9">
      <title>Declaration on Generative AI</title>
      <sec id="sec-9-1">
        <title>The authors have not employed any Generative AI tools.</title>
      </sec>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [1]
          <string-name>
            <given-names>J.</given-names>
            <surname>Dolby</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Chien</surname>
          </string-name>
          ,
          <article-title>An automatic object inlining optimization and its evaluation</article-title>
          ,
          <source>in: Proceedings of the ACM SIGPLAN 2000 conference on Programming language design and implementation</source>
          ,
          <year>2000</year>
          , pp.
          <fpage>345</fpage>
          -
          <lpage>357</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [2]
          <string-name>
            <given-names>E.</given-names>
            <surname>Gamma</surname>
          </string-name>
          ,
          <string-name>
            <given-names>R.</given-names>
            <surname>Helm</surname>
          </string-name>
          ,
          <string-name>
            <given-names>R.</given-names>
            <surname>Johnson</surname>
          </string-name>
          , J. Vlissides, Design Patterns:
          <article-title>Elements of Reusable Object-Oriented Software</article-title>
          ,
          <string-name>
            <surname>Addison-Wesley</surname>
          </string-name>
          ,
          <year>1995</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref3">
        <mixed-citation>
          [3]
          <string-name>
            <given-names>D.</given-names>
            <surname>Ungar</surname>
          </string-name>
          , Generation Scavenging:
          <article-title>A Non-Disruptive High Performance Storage Reclamation Algorithm</article-title>
          ,
          <source>ACM SIGPLAN Notices</source>
          <volume>19</volume>
          (
          <year>1984</year>
          )
          <fpage>157</fpage>
          -
          <lpage>167</lpage>
          . doi:
          <volume>10</volume>
          .1145/390011.808261.
        </mixed-citation>
      </ref>
      <ref id="ref4">
        <mixed-citation>
          [4]
          <string-name>
            <given-names>R.</given-names>
            <surname>Jones</surname>
          </string-name>
          ,
          <string-name>
            <given-names>A.</given-names>
            <surname>Hosking</surname>
          </string-name>
          ,
          <string-name>
            <surname>E. Moss,</surname>
          </string-name>
          <article-title>The garbage collection handbook: the art of automatic memory management</article-title>
          , CRC Press,
          <year>2016</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref5">
        <mixed-citation>
          [5]
          <string-name>
            <given-names>T.</given-names>
            <surname>Kotzmann</surname>
          </string-name>
          ,
          <string-name>
            <given-names>H.</given-names>
            <surname>Mössenböck</surname>
          </string-name>
          ,
          <article-title>Escape analysis in the context of dynamic compilation and deoptimization</article-title>
          ,
          <source>in: Proceedings of the 1st ACM/USENIX International Conference on Virtual Execution</source>
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>