Skip to content

Commit 93f22d0

Browse files
committed
[GR-68914] Review Truffle Deoptimization Cycle Patterns documentation.
PullRequest: graal/21941
2 parents 4dc6019 + 51bc735 commit 93f22d0

File tree

3 files changed

+76
-51
lines changed

3 files changed

+76
-51
lines changed

truffle/docs/CloseOnGc.md

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
## Automatic Close of Unreachable Context and Engine Objects in Polyglot API
1+
---
2+
layout: docs
3+
toc_group: truffle
4+
link_title: Automatic Close of Unreachable Context and Engine Objects in Polyglot API
5+
permalink: /graalvm-as-a-platform/language-implementation-framework/CloseOnGc/
6+
---
27

3-
Beginning with Polyglot version 24.2.0, `Context` and `Engine `objects are automatically closed when they are no longer strongly referenced. This feature ensures that unreachable `Context` and `Engine` instances are correcly cleaned up to minimize memory leaks. However, it remains highly recommended to manage the lifecycle of `Context` and `Engine` explicitly, ideally using a try-with-resources statement, rather than relying on garbage collection for this purpose.
8+
# Automatic Close of Unreachable Context and Engine Objects in Polyglot API
49

5-
### Context Lifecycle Management
10+
As of Polyglot version 24.2.0, `Context` and `Engine `objects are automatically closed when they are no longer strongly referenced.
11+
This feature ensures that unreachable `Context` and `Engine` instances are correctly cleaned up to minimize memory leaks.
12+
However, it remains highly recommended to manage the lifecycle of `Context` and `Engine` explicitly, ideally using a try-with-resources statement, rather than relying on garbage collection for this purpose.
13+
14+
## Context Lifecycle Management
615

716
A `Context` instance is eligible for automatic closure when no strong references to it remain. These references can include the following:
817

@@ -11,22 +20,26 @@ A `Context` instance is eligible for automatic closure when no strong references
1120
* Active polyglot threads or any host thread that has explicitly entered the `Context`. A `Context` that is explicitly [entered](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/Context.html#enter()) will not be garbage-collected until it is explicitly [left](https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/Context.html#leave()).
1221
* Inner `Context` objects, as inner contexts prevent their parent contexts from being closed if they remain reachable.
1322

14-
When any of these conditions hold, the `Context` instance remains active and will not be garbage-collected and closed. The presence of active system threads does not prevent a `Context` from being closed.
15-
23+
When any of these conditions hold, the `Context` instance remains active and will not be garbage-collected and closed.
24+
The presence of active system threads does not prevent a `Context` from being closed.
1625

17-
### Engine Lifecycle Management
26+
## Engine Lifecycle Management
1827

19-
The `Engine` object follows a similar approach to lifecycle management. An `Engine` will only be closed when it no longer has any strong references to it, such as:
28+
The `Engine` object follows a similar approach to lifecycle management.
29+
An `Engine` will only be closed when it no longer has any strong references to it, such as:
2030

2131
* Direct references to the `Engine`.
2232
* Strong references to `Language`, `Instrument`, or `ExecutionListener` instances that were obtained from the `Engine`.
2333

24-
Furthermore, an `Engine` cannot be closed if there is an active `Context` within it or if any `Context` objects that uses this `Engine` remain reachable. Similar to `Context` objects, the presence of active system threads does not interfere with the automatic closure of an `Engine`.
34+
Furthermore, an `Engine` cannot be closed if there is an active `Context` within it or if any `Context` objects that uses this `Engine` remain reachable.
35+
Similar to `Context` objects, the presence of active system threads does not interfere with the automatic closure of an `Engine`.
2536

26-
### TruffleContext and Inner Contexts
37+
## TruffleContext and Inner Contexts
2738

28-
In scenarios where a language implementation creates inner contexts using [TruffleLanguage.Env.newInnerContextBuilder()](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/TruffleLanguage.Env.html#newInnerContextBuilder(java.lang.String...)), both the Polyglot API `Context` representing this inner context and the `TruffleContext` must be unreachable to enable automatic closure. This situation is illustrated in the diagram below, showing an engine with one top-level context and one inner context. Strong references are represented by solid lines, and weak references by dashed lines.
39+
In scenarios where a language implementation creates inner contexts using [TruffleLanguage.Env.newInnerContextBuilder()](https://www.graalvm.org/truffle/javadoc/com/oracle/truffle/api/TruffleLanguage.Env.html#newInnerContextBuilder(java.lang.String...)), both the Polyglot API `Context` representing this inner context and the `TruffleContext` must be unreachable to enable automatic closure.
40+
This situation is illustrated in the diagram below, showing an engine with one top-level context and one inner context. Strong references are represented by solid lines, and weak references by dashed lines.
2941

3042
![Context References](context_references.png "Context References")
3143

32-
When an inner `Context` is no longer referenced by the embedder, and the corresponding `TruffleContext` instance is no longer referenced by its language implementation, weak references from `PolyglotContext` to `Context` and `TruffleContext` are added to a reference queue. During the next lifecycle event (such as when a new context is created or closed), this queue is processed, and any unreachable context is closed automatically.
44+
When an inner `Context` is no longer referenced by the embedder, and the corresponding `TruffleContext` instance is no longer referenced by its language implementation, weak references from `PolyglotContext` to `Context` and `TruffleContext` are added to a reference queue.
45+
During the next lifecycle event (such as when a new context is created or closed), this queue is processed, and any unreachable context is closed automatically.

truffle/docs/DeoptCyclePatterns.md

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
## Deoptimization Cycle Patterns
1+
---
2+
layout: docs
3+
toc_group: truffle
4+
link_title: Deoptimization Cycle Patterns
5+
permalink: /graalvm-as-a-platform/language-implementation-framework/DeoptCyclePatterns/
6+
---
27

3-
Since version 25, Truffle has included an automatic deoptimization cycle detection feature, which is a powerful tool
4-
for identifying existing deoptimization cycles. The goal of this document is to help prevent deoptimization cycles.
5-
To that end, it describes a number of common patterns that can cause deoptimization cycles and should therefore be avoided.
8+
# Deoptimization Cycle Patterns
69

7-
### Always Deopt Node
10+
As of GraalVM for JDK 25, the Truffle framework includes an **automatic deoptimization cycle detection feature**, which is a powerful tool for identifying existing deoptimization cycles.
11+
The goal of this document is to help prevent deoptimization cycles by describing common patterns that can lead to such cycles, which should be avoided.
812

9-
The following is the simplest pattern that causes a deoptimization cycle:
13+
### Always Deoptimizing Node
14+
15+
One of the simplest patterns that causes a deoptimization cycle is as follows:
1016

1117
```java
1218
class AlwaysDeoptNode extends RootNode {
@@ -18,9 +24,9 @@ class AlwaysDeoptNode extends RootNode {
1824
}
1925
```
2026

21-
When the above code is compiled, it deoptimizes and invalidates on the first execution. The same occurs with each subsequent compilation and execution.
22-
This is an extreme example, and it's hard to imagine how the code could be useful for anything other than testing purposes,
23-
but it is a clear cause of unbounded repeated deoptimization—that is, a deoptimization cycle.
27+
When the above code is compiled, it deoptimizes and invalidates on the first execution.
28+
The same occurs with each subsequent compilation and execution.
29+
This is an extreme example, and it is hard to imagine how the code could be useful for anything other than testing purposes, but it is a clear cause of unbounded repeated deoptimization—that is, a deoptimization cycle.
2430

2531
### Invalid Argument Profiling Node
2632

@@ -43,14 +49,15 @@ class InvalidArgumentProfilingNode extends RootNode {
4349
}
4450
```
4551

46-
The number of deoptimizations and invalidations in the above code is unbounded because the profiled argument can keep changing
47-
with no limit on the number of these changes. Consequently, there is no bound on the number of deoptimizations.
52+
The number of deoptimizations and invalidations in the above code is unbounded because the profiled argument can keep changing with no limit on the number of these changes.
53+
Consequently, there is no bound on the number of deoptimizations.
4854

49-
A possible solution to this problem would be to use the `IntValueProfile`, which caches the value only once and switches to a generic state when the value changes.
55+
A possible solution to this problem would be to use `IntValueProfile`, which caches the value only once and switches to a generic state when the value changes.
5056

5157
### Stabilize Late Node
5258

53-
The following code does not cause a true deoptimization cycle. It eventually stabilizes, but it does so so late that the deoptimization cycle detection tool might report a deoptimization cycle:
59+
The following code does not cause a true deoptimization cycle.
60+
It eventually stabilizes, but stabilization occurs so late that the deoptimization cycle detection tool might still report a cycle:
5461

5562
```java
5663
class StabilizeLateNode extends RootNode {
@@ -88,27 +95,29 @@ class StabilizeLateNode extends RootNode {
8895
}
8996
```
9097

91-
The above code must be executed 20 times with an argument different from `cachedValue` in order for `seenCount` to reach 20 and
92-
prevent further deoptimization. In theory, the code could be compiled and deoptimized for each value of `seenCount` below 20, i.e., twenty times.
98+
After 20 consecutive executions where the argument `arg` is different from `cachedValue`, the variable `seenCount` reaches 20. At this point, deoptimizations stop.
99+
Until then, the code may be compiled and deoptimized once for each `seenCount` value below 20—up to twenty times.
93100
Since deoptimization cycle detection is enabled by default after 15 compilations, it could be triggered by this late-stabilizing code.
94101

95102
The solution to this problem is to ensure that the optimizations stabilize more quickly.
96103

97104
### Compilation Final Field of a Non-Constant Object
98105

99-
Truffle compilation final fields are a powerful optimization tool and are often used to control speculations. For example, they are used in conditions
100-
checking whether a certain specialized piece code is valid for the current input. When it is not, and the code is compiled, they cause deoptimization
101-
and invalidation which allows the code to re-specialize before the next compilation. However, this usage must follow one basic rule:
106+
Truffle compilation final fields are a powerful optimization tool, often used to control speculations.
107+
For example, they are used in conditions checking whether a certain specialized piece of code is valid for the current input.
108+
When it is not, the compiled code is deoptimized and invalidated. This allows the code to re-specialize and recompile with the updated assumption.
109+
However, this usage must follow one key rule:
102110

103111
* The number of possible deoptimizations (and invalidations) caused by the condition involving a compilation final field should be small. As seen in the previous examples, failing to follow this rule can lead to issues.
104112

105-
Extra caution must be taken when the object containing the compilation field is not a partial evaluation (PE) constant because, in that case, the field is not compilation-final, and reading it translates to a normal field read. This makes it more difficult to reason about the number of possible deoptimizations caused by a condition involving the field.
113+
Extra caution must be taken when the object containing the compilation field is not a partial evaluation (PE) constant because, in that case, the field is not compilation-final, and reading it translates to a normal field read.
114+
This makes it more difficult to reason about the number of possible deoptimizations caused by a condition involving the field.
106115

107116
The following examples demonstrate what can happen if this caution is not exercised.
108117

109118
#### Example 1: Non-Constant Language Context
110119

111-
The following code shows repeated deoptimization caused by the initialization of each fresh language context:
120+
The following code shows repeated deoptimization caused by the initialization of each new language context:
112121

113122
```java
114123
class LanguageContext {
@@ -119,14 +128,14 @@ class LanguageContext {
119128
LanguageContext(Env env) {
120129
this.env = env;
121130
}
122-
131+
123132
void ensureInitialized() {
124133
if (!initialized) {
125134
CompilerDirectives.transferToInterpreterAndInvalidate();
126135
initialize();
127136
}
128137
}
129-
138+
130139
private void initialize() {
131140
// perform initialization
132141
initialized = true;
@@ -146,9 +155,8 @@ class LanguageContextDrivenInitialization extends RootNode {
146155
}
147156
```
148157

149-
If the `TruffleLanguage.Registration.contextPolicy` of `MyLanguage` is `SHARED`, a new language context is created for each polyglot context used to run the language code. Therefore,
150-
`languageContext` is not a PE constant and so the number of deoptimizations (and invalidations) driven by the
151-
`initialized` boolean is unbounded.
158+
If `TruffleLanguage.Registration.contextPolicy` of `MyLanguage` is `SHARED`, a new language context is created for each polyglot context used to run the language code.
159+
Therefore, `languageContext` is not a PE constant and so the number of deoptimizations (and invalidations) driven by the `initialized` boolean is unbounded.
152160

153161
A possible solution for this problem would be to transfer to the interpreter only when `languageContext` is a PE constant, and to annotate the initialize method with `TruffleBoundary`:
154162

@@ -232,10 +240,12 @@ class RootNode {
232240

233241
The dispatch node has a single call site, and each uninitialized call target must be initialized before the call.
234242
Initialization is performed in the `getCallTarget` method if the compilation final field `callTarget` on the callee's `RootNode` object is `null`.
235-
The callee’s root node object is not a PE constant. In fact, the number of root nodes the compiled code can see is theoretically unbounded.
243+
The callee’s root node object is not a PE constant.
244+
In fact, the number of root nodes the compiled code can see is theoretically unbounded.
236245
Initializing a call target causes a deoptimization, so the entire pattern leads to a deoptimization cycle.
237246

238-
A possible solution to this problem would be to initialize the call target at parse time or when the function is looked up for the first time. `MyFunction` objects would then store the call targets directly:
247+
A possible solution to this problem would be to initialize the call target at parse time or when the function is looked up for the first time.
248+
`MyFunction` objects would then store the call targets directly:
239249

240250
```java
241251
class MyFunction {
@@ -275,7 +285,8 @@ class SkippedExceptionNode extends RootNode {
275285
}
276286
```
277287

278-
Skipped exceptions are exceptions that always cause a deoptimization. The complete list is as follows:
288+
Skipped exceptions are exceptions that always cause a deoptimization.
289+
The complete list is as follows:
279290
* `UnexpectedResultException`
280291
* `SlowPathException`
281292
* `ScopedMemoryAccess$ScopedAccessError`
@@ -290,9 +301,9 @@ Skipped exceptions are exceptions that always cause a deoptimization. The comple
290301
* `ReadOnlyBufferException`
291302
* `AssertionError`
292303

293-
As you can see from the example, catching the exception does not help. Whenever a skipped exception is thrown in Truffle-compiled code,
294-
the code is deoptimized and invalidated, and repeated execution of such code leads to a deoptimization cycle.
304+
As you can see from the example, catching the exception does not help.
305+
Whenever a skipped exception is thrown in Truffle-compiled code, the code is deoptimized and invalidated, and repeated execution of such code leads to a deoptimization cycle.
295306

296-
The solution to this problem is to ensure that a skipped exception is never thrown from compiled code.
297-
This can be achieved either by throwing the exception behind a `TruffleBoundary` or ensuring, with explicit guards,
298-
that the error cannot occur in compiled code.
307+
The solution to this problem is to ensure that skipped exceptions are never thrown from compiled code. You can achieve this by either:
308+
* Throwing the exception behind `TruffleBoundary`, or
309+
* Using explicit guards to guarantee that the exception cannot be thrown in compiled code.

0 commit comments

Comments
 (0)