Workshop VMIL 2026 – Author Index |
Contents -
Abstracts -
Authors
|
| Anand, Aditya |
Gauravsingh Sisodia, Poorna Teja Pasala, Aditya Anand, and Manas Thakur (Sardar Patel Institute of Technology, India; Indian Institute of Technology Bombay, India) Static program analysis for Java is significantly hindered by the use of reflection and custom class loaders. To address this, the popular TamiFlex framework traces concrete targets of reflective calls and dumps loaded classes via a Play-out agent, enabling static analysis followed by re-insertion of transformed classes through a Play-in agent. While effective for Java 6, the evolution of the Java Virtual Machine (JVM) has rendered TamiFlex incompatible with Java versions released over the last decade. Modern runtime behaviors such as hidden-class introduction, randomized runtime-class generation, and class name collisions within frameworks cause the original agents to either crash or fail to converge. In this paper, we present an updated TamiFlex for modern JVMs, addressing three key limitations of the original. We introduce a hidden-class dumping and loading mechanism that captures classes unavailable to the agents, a normalization strategy that reconciles bytecode variations across runs, and a classloader-aware isolation strategy that resolves class-name collisions across different classloaders. We evaluate our toolchain on the latest DaCapo benchmark suite running on Java 21 across two JVMs, and find that for all 22 benchmarks, our Play-out agent produces a reflection log, while our Play-in agent successfully re-inserts transformed classes. To demonstrate the practical value of the updated framework, we also extend a recent static escape analysis and find that both its coverage and precision improve noticeably. |
|
| Bergström, C. |
C. Bergström (24x AI Research, Singapore) Fine-grained reactive systems built for the browser rely on implicit host facilities and guarantees, including promises, an event loop, a microtask checkpoint, and a garbage collector. We report on porting the reactive core of SolidJS to Rust, with those assumptions removed, and on what the removal revealed about which of them the system actually needs. Our headline finding is that SolidJS's async layer requires no asynchronous runtime. Upstream never polls a future; it registers a continuation and lets the platform make the call. Since the synchronous port already contained the queue that makes calls, the async layer landed with no executor, no futures, and no threads. A fine-grained reactive system needs the platform to queue continuations, drain them at a checkpoint, and let producers signal when asynchronous work settles. Compiling the port to WebAssembly tests the claim in its strongest form: the core, including asynchronous computations, runs as a 113.5 KiB module with no host imports. A binding to a browser engine whose DOM is a native node arena rather than a JavaScript API then measures the boundary using counts asserted for the native path and derived from emitted signatures for generated glue: one crossing per DOM operation versus two, while copying the same number of bytes as the glue toolchain's best configuration. The evaluation also reports where the arrangement loses, specifically in sixteen of twenty-five timed configurations under an interpreting runtime. It presents the porting method, the representation changes required by Rust, and four implementation questions plus one discrepancy between the specification and implementation uncovered during the port. The port reproduced the required scheduling without an event loop but could not reproduce the garbage collector's reachability guarantees. |
|
| Bernstein, Maxwell |
Maxwell Bernstein, Takashi Kokubun, Aaron Patterson, Si Xing (Alan) Wu, and Kevin Menard (Shopify, USA) Ruby is a dynamically typed and object-oriented programming language. Its primary implementation, CRuby, contains a bytecode virtual machine and a mature lazy basic block versioning (LBBV) just-in-time (JIT) compiler called YJIT. In order to both implement more advanced optimizations than YJIT supports and also encourage more outside contributions, we present a new method-based JIT called ZJIT. Like YJIT, ZJIT compiles from bytecode to machine code. Unlike YJIT, ZJIT has multiple global and local optimization passes. ZJIT's high-level intermediate representation is in static single assignment (SSA) form. In order to optimize Ruby's local variables, ZJIT lifts local variables into SSA values. This is a departure from how other Ruby compilers handle locals: other JIT compilers either leave local variables as memory loads and stores or do advanced partial evaluation to recover SSA values from memory. While implementing locals, we (re-)discovered what features make local variables in Ruby especially challenging to compile correctly and efficiently. We demonstrate these features and illustrate how we solved these problems in ZJIT. |
|
| Donat-Bouillud, Pierre |
Matěj Kocourek, Filip Říha, Oliver Tušla, Pierre Donat-Bouillud, Filip Křikava, and Jan Vitek (Charles University, Czech Republic; Czech Technical University, Czech Republic) Many programming language implementations have a benchmark suite that consists of a fixed set of programs with a harness and clear maintenance. R has none. Instead, R implementations are evaluated on ad hoc sets of programs and harnesses with different timing conventions. This paper is a step toward a benchmarking suite for R. We collect 118 R programs from 8 repositories and put them under a single harness. Next, we characterize their performance profiles, which show the share of the run spent in different regions of the R virtual machine (e.g., bytecode interpreter, memory management, builtin functions). This allows us to classify benchmarks by what they exercise so we can create suites that target a specific use such as compiler optimizations. On three R compilers, the speedups follow the profiles. They concentrate in the interpreter-bound programs and vanish in the ones dominated by native code. The profiles also cut the cost of running a suite. Dropping the 53 programs dominated by native code or builtins leaves 65 programs and saves 51.7% of the running time of the corpus. |
|
| Kamiyama, Takeshi |
Hiroki Yamada, Takeshi Kamiyama, Masato Oguchi, and Saneyasu Yamaguchi (Kogakuin University, Japan; Nagasaki University, Japan; Ochanomizu University, Japan) Concurrent Copying Garbage Collection (CCGC) in the Android Runtime (ART) manages objects at Region granularity. When objects with different lifetime characteristics coexist in the same Region, the number and total size of live objects processed during garbage collection may increase, reducing reclamation efficiency. This paper proposes an object-lifetime-aware allocation-control method that uses application-specific lifetime profiles collected for different object sizes and allocates objects belonging to size bins with high short-lived-object ratios to dedicated Regions. For each object-size bin, the proposed method calculates the fraction of objects whose lifetimes do not exceed a predefined threshold and ranks the bins in descending order of this fraction. It then selects the highest-ranked bins whose total allocated bytes are closest to 50% of the total allocated bytes. We implemented the proposed method in the ART of Android 13 and evaluated it using ten Android applications, with 50 benchmark executions for each configuration. Across the ten applications, the geometric mean of normalized application throughput increased by 0.59%, and GC throughput increased by 12.42%. The GC activity ratio, STW ratio, maximum STW time, and GC count per allocated GiB decreased by 25.80%, 22.67%, 2.69%, and 1.22%, respectively. However, the effects varied among applications. These results demonstrate that object-lifetime-aware allocation control can improve GC processing efficiency and pause-related performance, while its effectiveness depends on application-specific allocation characteristics and the processing cost of objects that survive beyond the selected lifetime threshold. Sota Shinagawa, Takeshi Kamiyama, Masato Oguchi, and Saneyasu Yamaguchi (Kogakuin University, Japan; Nagasaki University, Japan; Ochanomizu University, Japan) Programs written in Java and Kotlin with semantically iden- tical loops often have significantly different execution per- formance. Previous studies have shown that this is because the JVM JIT compiler only applies loop unrolling to spe- cific bytecode patterns. While these studies identified this phenomenon, they did not explain its cause based on the JVM implementation or provide a practical solution. In this paper, we explain that this behavior is caused by the JIT compiler’s inability to ensure that the loop variable does not overflow. We then propose a modification to the JIT compiler that enables loop unrolling even for previously unoptimized (slow-group) loop styles by simply converting eligible loops into long-typed counted loops. However, this transformation may change program behavior when the loop variable is cast. To preserve correctness, we introduce a method for detect- ing loop-variable casts and skip the long-typed counted-loop transformation for such loops. To evaluate the method, we implemented it in OpenJDK and tested it with representative Java and Kotlin loop styles. Our experimental results demon- strate that the proposed modification effectively bridges the performance gap between the fast and slow groups, showing performance im |
|
| Kocourek, Matěj |
Matěj Kocourek, Filip Říha, Oliver Tušla, Pierre Donat-Bouillud, Filip Křikava, and Jan Vitek (Charles University, Czech Republic; Czech Technical University, Czech Republic) Many programming language implementations have a benchmark suite that consists of a fixed set of programs with a harness and clear maintenance. R has none. Instead, R implementations are evaluated on ad hoc sets of programs and harnesses with different timing conventions. This paper is a step toward a benchmarking suite for R. We collect 118 R programs from 8 repositories and put them under a single harness. Next, we characterize their performance profiles, which show the share of the run spent in different regions of the R virtual machine (e.g., bytecode interpreter, memory management, builtin functions). This allows us to classify benchmarks by what they exercise so we can create suites that target a specific use such as compiler optimizations. On three R compilers, the speedups follow the profiles. They concentrate in the interpreter-bound programs and vanish in the ones dominated by native code. The profiles also cut the cost of running a suite. Dropping the 53 programs dominated by native code or builtins leaves 65 programs and saves 51.7% of the running time of the corpus. |
|
| Kokubun, Takashi |
Maxwell Bernstein, Takashi Kokubun, Aaron Patterson, Si Xing (Alan) Wu, and Kevin Menard (Shopify, USA) Ruby is a dynamically typed and object-oriented programming language. Its primary implementation, CRuby, contains a bytecode virtual machine and a mature lazy basic block versioning (LBBV) just-in-time (JIT) compiler called YJIT. In order to both implement more advanced optimizations than YJIT supports and also encourage more outside contributions, we present a new method-based JIT called ZJIT. Like YJIT, ZJIT compiles from bytecode to machine code. Unlike YJIT, ZJIT has multiple global and local optimization passes. ZJIT's high-level intermediate representation is in static single assignment (SSA) form. In order to optimize Ruby's local variables, ZJIT lifts local variables into SSA values. This is a departure from how other Ruby compilers handle locals: other JIT compilers either leave local variables as memory loads and stores or do advanced partial evaluation to recover SSA values from memory. While implementing locals, we (re-)discovered what features make local variables in Ruby especially challenging to compile correctly and efficiently. We demonstrate these features and illustrate how we solved these problems in ZJIT. |
|
| Křikava, Filip |
Matěj Kocourek, Filip Říha, Oliver Tušla, Pierre Donat-Bouillud, Filip Křikava, and Jan Vitek (Charles University, Czech Republic; Czech Technical University, Czech Republic) Many programming language implementations have a benchmark suite that consists of a fixed set of programs with a harness and clear maintenance. R has none. Instead, R implementations are evaluated on ad hoc sets of programs and harnesses with different timing conventions. This paper is a step toward a benchmarking suite for R. We collect 118 R programs from 8 repositories and put them under a single harness. Next, we characterize their performance profiles, which show the share of the run spent in different regions of the R virtual machine (e.g., bytecode interpreter, memory management, builtin functions). This allows us to classify benchmarks by what they exercise so we can create suites that target a specific use such as compiler optimizations. On three R compilers, the speedups follow the profiles. They concentrate in the interpreter-bound programs and vanish in the ones dominated by native code. The profiles also cut the cost of running a suite. Dropping the 53 programs dominated by native code or builtins leaves 65 programs and saves 51.7% of the running time of the corpus. |
|
| Menard, Kevin |
Maxwell Bernstein, Takashi Kokubun, Aaron Patterson, Si Xing (Alan) Wu, and Kevin Menard (Shopify, USA) Ruby is a dynamically typed and object-oriented programming language. Its primary implementation, CRuby, contains a bytecode virtual machine and a mature lazy basic block versioning (LBBV) just-in-time (JIT) compiler called YJIT. In order to both implement more advanced optimizations than YJIT supports and also encourage more outside contributions, we present a new method-based JIT called ZJIT. Like YJIT, ZJIT compiles from bytecode to machine code. Unlike YJIT, ZJIT has multiple global and local optimization passes. ZJIT's high-level intermediate representation is in static single assignment (SSA) form. In order to optimize Ruby's local variables, ZJIT lifts local variables into SSA values. This is a departure from how other Ruby compilers handle locals: other JIT compilers either leave local variables as memory loads and stores or do advanced partial evaluation to recover SSA values from memory. While implementing locals, we (re-)discovered what features make local variables in Ruby especially challenging to compile correctly and efficiently. We demonstrate these features and illustrate how we solved these problems in ZJIT. |
|
| Oguchi, Masato |
Hiroki Yamada, Takeshi Kamiyama, Masato Oguchi, and Saneyasu Yamaguchi (Kogakuin University, Japan; Nagasaki University, Japan; Ochanomizu University, Japan) Concurrent Copying Garbage Collection (CCGC) in the Android Runtime (ART) manages objects at Region granularity. When objects with different lifetime characteristics coexist in the same Region, the number and total size of live objects processed during garbage collection may increase, reducing reclamation efficiency. This paper proposes an object-lifetime-aware allocation-control method that uses application-specific lifetime profiles collected for different object sizes and allocates objects belonging to size bins with high short-lived-object ratios to dedicated Regions. For each object-size bin, the proposed method calculates the fraction of objects whose lifetimes do not exceed a predefined threshold and ranks the bins in descending order of this fraction. It then selects the highest-ranked bins whose total allocated bytes are closest to 50% of the total allocated bytes. We implemented the proposed method in the ART of Android 13 and evaluated it using ten Android applications, with 50 benchmark executions for each configuration. Across the ten applications, the geometric mean of normalized application throughput increased by 0.59%, and GC throughput increased by 12.42%. The GC activity ratio, STW ratio, maximum STW time, and GC count per allocated GiB decreased by 25.80%, 22.67%, 2.69%, and 1.22%, respectively. However, the effects varied among applications. These results demonstrate that object-lifetime-aware allocation control can improve GC processing efficiency and pause-related performance, while its effectiveness depends on application-specific allocation characteristics and the processing cost of objects that survive beyond the selected lifetime threshold. Sota Shinagawa, Takeshi Kamiyama, Masato Oguchi, and Saneyasu Yamaguchi (Kogakuin University, Japan; Nagasaki University, Japan; Ochanomizu University, Japan) Programs written in Java and Kotlin with semantically iden- tical loops often have significantly different execution per- formance. Previous studies have shown that this is because the JVM JIT compiler only applies loop unrolling to spe- cific bytecode patterns. While these studies identified this phenomenon, they did not explain its cause based on the JVM implementation or provide a practical solution. In this paper, we explain that this behavior is caused by the JIT compiler’s inability to ensure that the loop variable does not overflow. We then propose a modification to the JIT compiler that enables loop unrolling even for previously unoptimized (slow-group) loop styles by simply converting eligible loops into long-typed counted loops. However, this transformation may change program behavior when the loop variable is cast. To preserve correctness, we introduce a method for detect- ing loop-variable casts and skip the long-typed counted-loop transformation for such loops. To evaluate the method, we implemented it in OpenJDK and tested it with representative Java and Kotlin loop styles. Our experimental results demon- strate that the proposed modification effectively bridges the performance gap between the fast and slow groups, showing performance im |
|
| Pasala, Poorna Teja |
Gauravsingh Sisodia, Poorna Teja Pasala, Aditya Anand, and Manas Thakur (Sardar Patel Institute of Technology, India; Indian Institute of Technology Bombay, India) Static program analysis for Java is significantly hindered by the use of reflection and custom class loaders. To address this, the popular TamiFlex framework traces concrete targets of reflective calls and dumps loaded classes via a Play-out agent, enabling static analysis followed by re-insertion of transformed classes through a Play-in agent. While effective for Java 6, the evolution of the Java Virtual Machine (JVM) has rendered TamiFlex incompatible with Java versions released over the last decade. Modern runtime behaviors such as hidden-class introduction, randomized runtime-class generation, and class name collisions within frameworks cause the original agents to either crash or fail to converge. In this paper, we present an updated TamiFlex for modern JVMs, addressing three key limitations of the original. We introduce a hidden-class dumping and loading mechanism that captures classes unavailable to the agents, a normalization strategy that reconciles bytecode variations across runs, and a classloader-aware isolation strategy that resolves class-name collisions across different classloaders. We evaluate our toolchain on the latest DaCapo benchmark suite running on Java 21 across two JVMs, and find that for all 22 benchmarks, our Play-out agent produces a reflection log, while our Play-in agent successfully re-inserts transformed classes. To demonstrate the practical value of the updated framework, we also extend a recent static escape analysis and find that both its coverage and precision improve noticeably. |
|
| Patterson, Aaron |
Maxwell Bernstein, Takashi Kokubun, Aaron Patterson, Si Xing (Alan) Wu, and Kevin Menard (Shopify, USA) Ruby is a dynamically typed and object-oriented programming language. Its primary implementation, CRuby, contains a bytecode virtual machine and a mature lazy basic block versioning (LBBV) just-in-time (JIT) compiler called YJIT. In order to both implement more advanced optimizations than YJIT supports and also encourage more outside contributions, we present a new method-based JIT called ZJIT. Like YJIT, ZJIT compiles from bytecode to machine code. Unlike YJIT, ZJIT has multiple global and local optimization passes. ZJIT's high-level intermediate representation is in static single assignment (SSA) form. In order to optimize Ruby's local variables, ZJIT lifts local variables into SSA values. This is a departure from how other Ruby compilers handle locals: other JIT compilers either leave local variables as memory loads and stores or do advanced partial evaluation to recover SSA values from memory. While implementing locals, we (re-)discovered what features make local variables in Ruby especially challenging to compile correctly and efficiently. We demonstrate these features and illustrate how we solved these problems in ZJIT. |
|
| Říha, Filip |
Matěj Kocourek, Filip Říha, Oliver Tušla, Pierre Donat-Bouillud, Filip Křikava, and Jan Vitek (Charles University, Czech Republic; Czech Technical University, Czech Republic) Many programming language implementations have a benchmark suite that consists of a fixed set of programs with a harness and clear maintenance. R has none. Instead, R implementations are evaluated on ad hoc sets of programs and harnesses with different timing conventions. This paper is a step toward a benchmarking suite for R. We collect 118 R programs from 8 repositories and put them under a single harness. Next, we characterize their performance profiles, which show the share of the run spent in different regions of the R virtual machine (e.g., bytecode interpreter, memory management, builtin functions). This allows us to classify benchmarks by what they exercise so we can create suites that target a specific use such as compiler optimizations. On three R compilers, the speedups follow the profiles. They concentrate in the interpreter-bound programs and vanish in the ones dominated by native code. The profiles also cut the cost of running a suite. Dropping the 53 programs dominated by native code or builtins leaves 65 programs and saves 51.7% of the running time of the corpus. |
|
| Shinagawa, Sota |
Sota Shinagawa, Takeshi Kamiyama, Masato Oguchi, and Saneyasu Yamaguchi (Kogakuin University, Japan; Nagasaki University, Japan; Ochanomizu University, Japan) Programs written in Java and Kotlin with semantically iden- tical loops often have significantly different execution per- formance. Previous studies have shown that this is because the JVM JIT compiler only applies loop unrolling to spe- cific bytecode patterns. While these studies identified this phenomenon, they did not explain its cause based on the JVM implementation or provide a practical solution. In this paper, we explain that this behavior is caused by the JIT compiler’s inability to ensure that the loop variable does not overflow. We then propose a modification to the JIT compiler that enables loop unrolling even for previously unoptimized (slow-group) loop styles by simply converting eligible loops into long-typed counted loops. However, this transformation may change program behavior when the loop variable is cast. To preserve correctness, we introduce a method for detect- ing loop-variable casts and skip the long-typed counted-loop transformation for such loops. To evaluate the method, we implemented it in OpenJDK and tested it with representative Java and Kotlin loop styles. Our experimental results demon- strate that the proposed modification effectively bridges the performance gap between the fast and slow groups, showing performance im |
|
| Sisodia, Gauravsingh |
Gauravsingh Sisodia, Poorna Teja Pasala, Aditya Anand, and Manas Thakur (Sardar Patel Institute of Technology, India; Indian Institute of Technology Bombay, India) Static program analysis for Java is significantly hindered by the use of reflection and custom class loaders. To address this, the popular TamiFlex framework traces concrete targets of reflective calls and dumps loaded classes via a Play-out agent, enabling static analysis followed by re-insertion of transformed classes through a Play-in agent. While effective for Java 6, the evolution of the Java Virtual Machine (JVM) has rendered TamiFlex incompatible with Java versions released over the last decade. Modern runtime behaviors such as hidden-class introduction, randomized runtime-class generation, and class name collisions within frameworks cause the original agents to either crash or fail to converge. In this paper, we present an updated TamiFlex for modern JVMs, addressing three key limitations of the original. We introduce a hidden-class dumping and loading mechanism that captures classes unavailable to the agents, a normalization strategy that reconciles bytecode variations across runs, and a classloader-aware isolation strategy that resolves class-name collisions across different classloaders. We evaluate our toolchain on the latest DaCapo benchmark suite running on Java 21 across two JVMs, and find that for all 22 benchmarks, our Play-out agent produces a reflection log, while our Play-in agent successfully re-inserts transformed classes. To demonstrate the practical value of the updated framework, we also extend a recent static escape analysis and find that both its coverage and precision improve noticeably. |
|
| Thakur, Manas |
Gauravsingh Sisodia, Poorna Teja Pasala, Aditya Anand, and Manas Thakur (Sardar Patel Institute of Technology, India; Indian Institute of Technology Bombay, India) Static program analysis for Java is significantly hindered by the use of reflection and custom class loaders. To address this, the popular TamiFlex framework traces concrete targets of reflective calls and dumps loaded classes via a Play-out agent, enabling static analysis followed by re-insertion of transformed classes through a Play-in agent. While effective for Java 6, the evolution of the Java Virtual Machine (JVM) has rendered TamiFlex incompatible with Java versions released over the last decade. Modern runtime behaviors such as hidden-class introduction, randomized runtime-class generation, and class name collisions within frameworks cause the original agents to either crash or fail to converge. In this paper, we present an updated TamiFlex for modern JVMs, addressing three key limitations of the original. We introduce a hidden-class dumping and loading mechanism that captures classes unavailable to the agents, a normalization strategy that reconciles bytecode variations across runs, and a classloader-aware isolation strategy that resolves class-name collisions across different classloaders. We evaluate our toolchain on the latest DaCapo benchmark suite running on Java 21 across two JVMs, and find that for all 22 benchmarks, our Play-out agent produces a reflection log, while our Play-in agent successfully re-inserts transformed classes. To demonstrate the practical value of the updated framework, we also extend a recent static escape analysis and find that both its coverage and precision improve noticeably. |
|
| Tušla, Oliver |
Matěj Kocourek, Filip Říha, Oliver Tušla, Pierre Donat-Bouillud, Filip Křikava, and Jan Vitek (Charles University, Czech Republic; Czech Technical University, Czech Republic) Many programming language implementations have a benchmark suite that consists of a fixed set of programs with a harness and clear maintenance. R has none. Instead, R implementations are evaluated on ad hoc sets of programs and harnesses with different timing conventions. This paper is a step toward a benchmarking suite for R. We collect 118 R programs from 8 repositories and put them under a single harness. Next, we characterize their performance profiles, which show the share of the run spent in different regions of the R virtual machine (e.g., bytecode interpreter, memory management, builtin functions). This allows us to classify benchmarks by what they exercise so we can create suites that target a specific use such as compiler optimizations. On three R compilers, the speedups follow the profiles. They concentrate in the interpreter-bound programs and vanish in the ones dominated by native code. The profiles also cut the cost of running a suite. Dropping the 53 programs dominated by native code or builtins leaves 65 programs and saves 51.7% of the running time of the corpus. |
|
| Vitek, Jan |
Matěj Kocourek, Filip Říha, Oliver Tušla, Pierre Donat-Bouillud, Filip Křikava, and Jan Vitek (Charles University, Czech Republic; Czech Technical University, Czech Republic) Many programming language implementations have a benchmark suite that consists of a fixed set of programs with a harness and clear maintenance. R has none. Instead, R implementations are evaluated on ad hoc sets of programs and harnesses with different timing conventions. This paper is a step toward a benchmarking suite for R. We collect 118 R programs from 8 repositories and put them under a single harness. Next, we characterize their performance profiles, which show the share of the run spent in different regions of the R virtual machine (e.g., bytecode interpreter, memory management, builtin functions). This allows us to classify benchmarks by what they exercise so we can create suites that target a specific use such as compiler optimizations. On three R compilers, the speedups follow the profiles. They concentrate in the interpreter-bound programs and vanish in the ones dominated by native code. The profiles also cut the cost of running a suite. Dropping the 53 programs dominated by native code or builtins leaves 65 programs and saves 51.7% of the running time of the corpus. |
|
| Wu, Si Xing (Alan) |
Maxwell Bernstein, Takashi Kokubun, Aaron Patterson, Si Xing (Alan) Wu, and Kevin Menard (Shopify, USA) Ruby is a dynamically typed and object-oriented programming language. Its primary implementation, CRuby, contains a bytecode virtual machine and a mature lazy basic block versioning (LBBV) just-in-time (JIT) compiler called YJIT. In order to both implement more advanced optimizations than YJIT supports and also encourage more outside contributions, we present a new method-based JIT called ZJIT. Like YJIT, ZJIT compiles from bytecode to machine code. Unlike YJIT, ZJIT has multiple global and local optimization passes. ZJIT's high-level intermediate representation is in static single assignment (SSA) form. In order to optimize Ruby's local variables, ZJIT lifts local variables into SSA values. This is a departure from how other Ruby compilers handle locals: other JIT compilers either leave local variables as memory loads and stores or do advanced partial evaluation to recover SSA values from memory. While implementing locals, we (re-)discovered what features make local variables in Ruby especially challenging to compile correctly and efficiently. We demonstrate these features and illustrate how we solved these problems in ZJIT. |
|
| Yamada, Hiroki |
Hiroki Yamada, Takeshi Kamiyama, Masato Oguchi, and Saneyasu Yamaguchi (Kogakuin University, Japan; Nagasaki University, Japan; Ochanomizu University, Japan) Concurrent Copying Garbage Collection (CCGC) in the Android Runtime (ART) manages objects at Region granularity. When objects with different lifetime characteristics coexist in the same Region, the number and total size of live objects processed during garbage collection may increase, reducing reclamation efficiency. This paper proposes an object-lifetime-aware allocation-control method that uses application-specific lifetime profiles collected for different object sizes and allocates objects belonging to size bins with high short-lived-object ratios to dedicated Regions. For each object-size bin, the proposed method calculates the fraction of objects whose lifetimes do not exceed a predefined threshold and ranks the bins in descending order of this fraction. It then selects the highest-ranked bins whose total allocated bytes are closest to 50% of the total allocated bytes. We implemented the proposed method in the ART of Android 13 and evaluated it using ten Android applications, with 50 benchmark executions for each configuration. Across the ten applications, the geometric mean of normalized application throughput increased by 0.59%, and GC throughput increased by 12.42%. The GC activity ratio, STW ratio, maximum STW time, and GC count per allocated GiB decreased by 25.80%, 22.67%, 2.69%, and 1.22%, respectively. However, the effects varied among applications. These results demonstrate that object-lifetime-aware allocation control can improve GC processing efficiency and pause-related performance, while its effectiveness depends on application-specific allocation characteristics and the processing cost of objects that survive beyond the selected lifetime threshold. |
|
| Yamaguchi, Saneyasu |
Hiroki Yamada, Takeshi Kamiyama, Masato Oguchi, and Saneyasu Yamaguchi (Kogakuin University, Japan; Nagasaki University, Japan; Ochanomizu University, Japan) Concurrent Copying Garbage Collection (CCGC) in the Android Runtime (ART) manages objects at Region granularity. When objects with different lifetime characteristics coexist in the same Region, the number and total size of live objects processed during garbage collection may increase, reducing reclamation efficiency. This paper proposes an object-lifetime-aware allocation-control method that uses application-specific lifetime profiles collected for different object sizes and allocates objects belonging to size bins with high short-lived-object ratios to dedicated Regions. For each object-size bin, the proposed method calculates the fraction of objects whose lifetimes do not exceed a predefined threshold and ranks the bins in descending order of this fraction. It then selects the highest-ranked bins whose total allocated bytes are closest to 50% of the total allocated bytes. We implemented the proposed method in the ART of Android 13 and evaluated it using ten Android applications, with 50 benchmark executions for each configuration. Across the ten applications, the geometric mean of normalized application throughput increased by 0.59%, and GC throughput increased by 12.42%. The GC activity ratio, STW ratio, maximum STW time, and GC count per allocated GiB decreased by 25.80%, 22.67%, 2.69%, and 1.22%, respectively. However, the effects varied among applications. These results demonstrate that object-lifetime-aware allocation control can improve GC processing efficiency and pause-related performance, while its effectiveness depends on application-specific allocation characteristics and the processing cost of objects that survive beyond the selected lifetime threshold. Sota Shinagawa, Takeshi Kamiyama, Masato Oguchi, and Saneyasu Yamaguchi (Kogakuin University, Japan; Nagasaki University, Japan; Ochanomizu University, Japan) Programs written in Java and Kotlin with semantically iden- tical loops often have significantly different execution per- formance. Previous studies have shown that this is because the JVM JIT compiler only applies loop unrolling to spe- cific bytecode patterns. While these studies identified this phenomenon, they did not explain its cause based on the JVM implementation or provide a practical solution. In this paper, we explain that this behavior is caused by the JIT compiler’s inability to ensure that the loop variable does not overflow. We then propose a modification to the JIT compiler that enables loop unrolling even for previously unoptimized (slow-group) loop styles by simply converting eligible loops into long-typed counted loops. However, this transformation may change program behavior when the loop variable is cast. To preserve correctness, we introduce a method for detect- ing loop-variable casts and skip the long-typed counted-loop transformation for such loops. To evaluate the method, we implemented it in OpenJDK and tested it with representative Java and Kotlin loop styles. Our experimental results demon- strate that the proposed modification effectively bridges the performance gap between the fast and slow groups, showing performance im |
24 authors
proc time: 0.05