Legacy programming languages such as FORTRAN 77 still play a vital role in many industrial applications. Maintaining and modernizing these languages is challenging, especially when migrating to newer standards such as Fortran 2008. This is exacerbated in the presence of legacy proprietary extensions on such legacy languages, because their semantics are often based on old context (limits of legacy language, domain logic,...). This paper presents an approach for automatically migrating FORTRAN 77 with a proprietary extension, named Esope, to Fortran 2008. We introduce a tool that converts Esope source code to Fortran 2008. While supporting readability of the generated code, we want to maintain the level of abstraction provided by Esope. Our method uses model-driven engineering techniques, with transformations to generate a target model from which we export easy-to-read Fortran 2008 source code. We discuss the advantages, limitations, and maintainability considerations of our approach and provide insights into its scalability and adaptability to evolving requirements.
Refactorings are behavior-preserving source code transformations that have become integral to modern Integrated Development Environments (IDEs) and code editors, significantly enhancing software development practices. Our goal is to facilitate the creation of custom refactorings and support users in developing their own. To achieve this, we identify the essential building blocks required for creating composite, modular refactorings. We propose a set of abstractions that enable the development of a robust refactoring engine. These abstractions include an initial set of transformations, program primitives, and an API for precondition checking and static analysis necessary to create new refactorings. We demonstrate how standard refactorings can be effectively composed using these building blocks, providing concrete examples to illustrate their application.
Tools shape our mind. This is why it is important to have extensible and flexible tools for developers to adapt to their needs. Reasoning about programs in the abstract -- by imagining what objects should look like -- can make it harder to grasp the underlying model. In Smalltalk environments like Pharo, developers work closely with their objects, gaining immediate feedback -- not guessing how they will look like but directly interacting with them. This article presents some tools developers use in Pharo: Inspector custom views for defining specific views and navigation for objects, Microcommits for reverting changes without the need to commit and pull, Xtreme TDD that allows developers to code in the debugger, On the Fly Rewriting Deprecations that support API evolution through automated rewriting of deprecated calls, and Object-Centric Breakpoints -- when a problem cannot be efficiently solved with a dummy trace, developers can use break points that will only halt for a given instance. By showcasing these features that evolved alongside Smalltalk, we invite reflection on how other IDEs could rethink some of their features and improve developers' workflows.
Complishon is Pharo's context-aware code completion engine, built on AST analysis, lazy candidate generation, and filter-based candidate selection. Its existing design already provides strong semantic completion, but several practical limits remain. Strict prefix matching is sensitive to small typing errors, framework prefixes often force redundant input, large completion menus are difficult to scan, and prefix-only matching does not support common camel-case abbreviations. This paper presents four extensions that address these limits: typo tolerance, implicit prefix expansion, grouped completion entries, and camel-case matching. For each extension, we describe the implementation, discuss alternative designs, and explain the trade-offs involved. The main contribution of the paper is to show that these improvements can be integrated into Complishon while preserving its modular architecture and predictable behavior.
Baseline JIT compilers need to compile early and as fast as possible, while still performing optimizations. One powerful technique to write fast baseline JIT compilers is abstract interpretation. Several implementations of this technique exist in practice, implementing in a single pass optimizations such as register allocation, constant propagation, and instruction scheduling. However, although they share the same technique, all these implementations vary in the exact optimizations performed, their internal design and further implementation details (e.g., the implementation language and framework). Thus, it is challenging to understand and isolate the benefits of the technique by simply studing these existing implementations.Understanding the real impact of compile-time abstract interpreters requires isolating performance differences and experimenting with different variations of the same implementation, which demands extensive engineering work. In this paper, we propose to analyse the impact of abstract interpreters through metacompilation. We use metacompilation as a means to (a) reduce the experimentation effort and (b) to produce compiler variants that are comparable, reducing implementation noise.We implemented our solution to generate several JIT compiler variants for the Pharo VM. We describe the adaptations required in the metacompilation framework to target both abstract interpreters and direct translators, in combination with Static Type Prediction optimizations.Our benchmarks show that compile-time abstract interpreters, on average, reduce the emitted machine code size by 12% and increase execution speed by 10%, up to 30%, without increasing JIT compilation overhead, compared to direct translators.
Large Language Models (LLMs) unlocked new possibilities in automated code writing, becoming the backbone of most code completion tools. While LLMs excel in mainstream languages, they often lack support for the so-called low-resource languages where training data is scarce. As a result, these languages lag behind in the quality of code completion tooling available to their communities. A concrete example is Pharo, a Smalltalk-inspired language whose IDE currently offers only single-token completion. In this work, we report on our experience bringing LLM-based code completion to Pharo. First, we describe an end-to-end pipeline that combines Pharo-specific data curation, continued pre-training and fine-tuning of open code LLMs. Second, we introduce a set of Pharo code completion benchmarks designed to evaluate whether models (i) learn Pharo's syntax and (ii) accurately complete masked Pharo code from real-world GitHub repositories. Third, we show empirically that Pharo-specialized models substantially outperform their original base checkpoints and also exceed the accuracy of substantially larger code LLMs on Pharo completion. Overall, our case study demonstrates the feasibility of bringing strong LLM-based code completion to low-resource programming languages, with models small enough to provide “real-time” in-IDE support.
Source code does not exist in isolation. Code is structured in project repositories composed of packages, and large applications are often composed of multiple projects. This paper investigates whether token-level completion engines can take advantage of such information.In this context, Pharo offers a sophisticated completion engine based on language semantics using heuristics, i.e., ordered sequences of lazy fetchers that retrieve and rank completion candidates. These heuristics can be recomposed or extended to support various activities (e.g., live programming or history usage navigation). The default heuristics rely on the language’s semantics and scoping rules. While this system is powerful, it does not account for package concerns of any sort (neither repository structure nor package dependencies). As a result, it does not prioritize classes within the same package or project, nor the classes that they may use, treating all global names equally.In this paper, we introduce and systematically evaluate several new heuristics that address this limitation: Lateral Packages, Direct Package Dependencies, and Two-Level Package Dependencies heuristics. Our benchmark runs over the method call sites of 219 packages, comprising 4,535 classes and a total of 35,972 methods. This codebase covers major projects such as Iceberg, Moose, Roassal, Seaside, and Spec, ensuring a diverse and realistic benchmark for assessing the effectiveness of our proposed heuristics. We evaluate two key concerns: the completion of class and method names. Rather than proposing a new learning model, this work evaluates whether explicit package dependencies, already available in modular software systems, can serve as a lightweight structural signal for improving completion ranking.Results show that the Direct Package Dependencies heuristic improves Mean Reciprocal Rank (MRR): for 3-character prefixes, MRR increases from 0.20 to 0.44 for class-name completion and from 0.10 to 0.41 for method-name completion. These results indicate that package-aware completion strategies provide more accurate and relevant suggestions than the default Semantics-Based strategy in the evaluated projects. Because Direct Package Dependencies outperformed the default strategy for class-name completion, the Pharo Industrial Consortium decided to introduce it in Pharo 13.
Pharo offers a sophisticated completion engine based on semantic heuristics, which coordinates specific fetchers within a lazy architecture. These heuristics can be recomposed to support various activities (e.g., live programming or history usage navigation). While this system is powerful, it does not account for the repository structure when suggesting global names such as class names, class variables, or global variables. As a result, it does not prioritize classes within the same package or project, treating all global names equally. In this paper, we present a new heuristic that addresses this limitation. Our approach searches variable names in a structured manner: it begins with the package of the requesting class, then expands to other packages within the same repository, and finally considers the global namespace. We describe the logic behind this heuristic and evaluate it against the default semantic heuristic and one that directly queries the global namespace. Preliminary results indicate that the Mean Reciprocal Rank (MRR) improves, confirming that package-awareness completions deliver more accurate and relevant suggestions than the previous flat global approach.
Virtual Machines (VMs) combine interpreters and just-in-time (JIT) compiled code to achieve good performance. However, implementing different execution engines increases the cost of developing and maintaining such solutions. JIT compilers based on meta-compilation cope with these issues by automatically generating optimizing JIT compilers. This leaves open the question of how meta-compilation applies to baseline JIT compilers, which improve warmup times by trading off optimizations. In this paper, we present Druid, an ahead-of-time automatic approach to generate baseline JIT compiler frontends from interpreters. Language developers guide meta-compilation by annotating interpreter code and using Druid's intrinsics. Druid targets the meta-compilation to an existing JIT compiler infrastructure to achieve good warm-up performance. We applied Druid in the context of the Pharo programming language and evaluated it by comparing an autogenerated JIT compiler frontend against the one in production for more than 10 years. Our generated JIT compiler frontend is 2x faster on average than the interpreter and achieves on average 0.7x the performance of the handwritten JIT compiler. Our experiment only required changes in 60 call sites in the interpreter, showing that our solution makes language VMs **easier to maintain and evolve in the long run**.
Reflective operations are powerful APIs (Application Programming Interface) that let developers build advanced tools and architectures. Reflective operations are used for implementing tools and development environments (e.g., compilers, debuggers, inspectors) or language features (e.g., distributed systems, exceptions, proxies, aspect-oriented programming). In addition, languages are evolving, introducing better concepts, and revising practices and APIs. Since 2008 Pharo has evolved from Squeak and its reflective API has evolved accordingly, diverging consequently from the original Smalltalk reflective API. With more than 500 reflective methods identified, Pharo has one of the largest reflective feature sets ranging from structural reflection to on-demand stack reification. Those operations are often built on top of the other, creating different layers of reflective operations, from low-level to high-level ones. There is a need to understand the current reflective APIs to understand their underlying use, potential dependencies, and whether some reflective features can be scoped and optional. Such an analysis is challenged by new metaobjects organically introduced in the system, such as first-class instance variables, and their mixture with the base-level API of objects and classes. In this article, we analyze the reflective operations used in Pharo 12 and their interdependencies. We propose a classification based on their semantics and we identify a set of issues of the current implementation. Such an analysis of reflective operations in Pharo is important to support the revision of the reflective layer and its potential redesign.
Refactorings are behavior-preserving code transformations. They are a recommended software development practice and are now a standard feature in modern IDEs. There are however many situations where developers need to perform mere transformations (non-behavior-preserving) or to mix refactorings and transformations. Little work exists on the analysis of transformations implementation, how refactorings could be composed of smaller, reusable, parts (simple transformations or other refactorings), and, conversely, how transformations could be reused in isolation or to compose new refactorings. In a previous article, we started to analyze the seminal implementation of refactorings as proposed in the Ph.D. of D. Roberts, and whose evolution is available in the Pharo IDE. We identified a dichotomy between the class hierarchy of refactorings (56 classes) and that of transformations (70 classes). We also noted that there are different kinds of preconditions for different purposes (applicability preconditions or behavior-preserving preconditions). In this article, we go further by proposing a new architecture that: (i) supports two important scenarios (interactive use or scripting, i.e., batch use); (ii) defines a clear API unifying refactorings and transformations; (iii) expresses refactorings as decorators over transformations, and; (iv) formalizes the uses of the different kinds of preconditions, thus supporting better user feedback. We are in the process of migrating the existing Pharo refactorings to this new architecture. Current results show that elementary transformations such as the Add Method transformation is reused in 24 refactorings and 11 other transformations; and the Remove Method transformation is reused in 11 refactorings and 7 other transformations.
Using object lifetime information enables performance improvement through memory optimizations such as pretenuring and tuning garbage collector parameters. However, profiling object lifetimes is nontrivial and often requires a specialized virtual machine to instrument object allocations and dereferences. Alternative lifetime profiling could be done with less implementation effort using available finalization mechanisms such as weak references. In this paper, we study the impact of finalization on object lifetime profiling. We built an actionable lifetime profiler using the ephemeron finalization mechanism named FiLiP. FiLiP instruments object allocations to exactly record an object’s allocation time and it attaches an ephemeron to each allocated object to capture its finalization time. We show that FiLiP can be used in practice and achieves a significant overhead reduction by pretenuring the ephemeron objects. We further experiment with the impact of sampling object allocations, showing that sampling further reduces profiling overhead while still maintaining actionable lifetime measurements.
Debugging programs require program comprehension. To acquire this comprehension, developers explore the program execution, a task often performed using interactive debuggers. However, exploring a program execution through standard interactive debuggers is tedious and costly. In addition, standard debuggers are generic tools that are inflexible and difficult to use in domain-specific contexts. In this paper we propose Time-Traveling Queries (TTQs) to ease and customize program exploration. TTQs is an extensible mechanism that automatically explores program executions to collect execution data. This data is used to time-travel through execution states, facilitating the exploration of program executions. Queries can be created or extended for problem-specific or domain-specific debugging scenarios. TTQ have been successfully used on a real-world example of a bug, which shows that in practice the TTQ system is usable. To evaluate more in depth the impact of TTQs on program comprehension activities, we conducted a user study with 34 participants on program comprehension tasks. Results show that compared to traditional debugging tools, TTQs improve developers' precision (39% more correct answers) while reducing required time (27% faster to finish tasks) and effort (45% less debugging actions) when performing program comprehension tasks.
Reflection is a powerful tool that allows a program to manipulate itself during its execution. However, developers may use it to circumvent data encapsulation and method visibility modifiers. Thus, it is important to assess how much an application relies on reflection. Nonetheless, reflection is mostly incompatible with static analysis as it relies on runtime information (e.g., to determine the attribute to be accessed or the method to evaluate). These problems worsen with dynamically-typed languages, where reflective operations are polymorphic with non-reflective operations, e.g., in Pharo, array access is polymorphic with context variable modifications. In this paper, we present RAPIM, an approach to study the uses of reflective APIs: it uses mutation analysis with a new mutation operator for dealing with core reflective methods. We analyze a serialization library from a developer perspective, showing the information it reveals. We evaluate our approach on a selection of five projects by comparing its performance against static analysis. We show that out of five projects, RAPIM disambiguates more potentially reflective call-sites than the static analysis. When the code coverage is good, the percentage of disambiguation is three times higher. Finally, we question the relevance of polymorphism between non-reflective and reflective APIs. Out of five projects, only one uses it, for only 1.4% of potentially reflective call-sites. We argue that reflective APIs could be renamed to avoid ambiguities.
Marcus Denker合作论文数INRIA Lille - Nord Europe, team Rmod.47
Hani Abdeen合作论文数Montreal University12