-
Notifications
You must be signed in to change notification settings - Fork 323
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Migrate some passes to Mini passes (#11191)
Gets ready for avoiding IR traversal by introducing _mini passes_ as proposed by #10981: - creates [MiniPassFactory](762045a) (that extends common `IRProcessingPass`) to transform an `IR` element to another `IR` element - modifies `PassManager` to recognize such _mini passes_ and treat them in a special way - by using `MiniIRPass.compile` - `MiniIRPass.compile` is using `IR.mapExpressions` to traverse the `IR` - alternative approach [withNewChildren](1abc70d) rejected for now, see _future work_ for details - unlike _mega passes_ `IRMiniPass.compile` **does not recursively** traverse, but with 0964711 it invokes each _mini pass_ at constant stack depth - way better for profiling - `MiniIRPass.prepare` _works on edges_ since ffd27df - there is `IRMiniPass prepare(parent, child)` to collect information while pre-order traversing from a particular `IR` parent to a particular `IR` child - `PassManager` rewritten to group _subsequent mini passes_ together by `MiniIRPass.combine` and really and traverse the `IR` just once - done in 2736a76 - converted to _mini pass_: `LambdaShorthandToLambda`, `OperatorToFunction`, `SectionsToBinOp` and `TailCall` - tested for 1:1 compatibility by [converting original code to test code](f54ba6d) and _comparing `IR` produced by old and new_ implementations
- Loading branch information
Showing
48 changed files
with
3,088 additions
and
1,106 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
engine/runtime-compiler/src/main/java/org/enso/compiler/pass/ChainedMiniPass.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package org.enso.compiler.pass; | ||
|
||
import java.util.Objects; | ||
import org.enso.compiler.core.IR; | ||
import org.enso.compiler.core.ir.Expression; | ||
|
||
/** Utility class for chaining mini passes together. */ | ||
final class ChainedMiniPass extends MiniIRPass { | ||
private final MiniIRPass firstPass; | ||
private final MiniIRPass secondPass; | ||
|
||
private ChainedMiniPass(MiniIRPass firstPass, MiniIRPass secondPass) { | ||
this.firstPass = firstPass; | ||
this.secondPass = secondPass; | ||
} | ||
|
||
static MiniIRPass chain(MiniIRPass firstPass, MiniIRPass secondPass) { | ||
if (firstPass == null) { | ||
return secondPass; | ||
} | ||
return new ChainedMiniPass(firstPass, secondPass); | ||
} | ||
|
||
@Override | ||
public MiniIRPass prepare(IR parent, Expression current) { | ||
var first = firstPass.prepare(parent, current); | ||
var second = secondPass.prepare(parent, current); | ||
if (first == firstPass && second == secondPass) { | ||
return this; | ||
} else { | ||
return new ChainedMiniPass(first, second); | ||
} | ||
} | ||
|
||
@Override | ||
public Expression transformExpression(Expression ir) { | ||
var fstIr = firstPass.transformExpression(ir); | ||
var sndIr = secondPass.transformExpression(fstIr); | ||
return sndIr; | ||
} | ||
|
||
@Override | ||
public boolean checkPostCondition(IR ir) { | ||
return firstPass.checkPostCondition(ir) && secondPass.checkPostCondition(ir); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return Objects.toString(firstPass) + ":" + Objects.toString(secondPass); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
engine/runtime-compiler/src/main/java/org/enso/compiler/pass/IRProcessingPass.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.enso.compiler.pass; | ||
|
||
import org.enso.compiler.core.ir.ProcessingPass; | ||
import scala.collection.immutable.Seq; | ||
|
||
/** | ||
* A generic {@link IR} processing pass. Currently with two subclasses: classical {@link IRPass mega | ||
* IR processing pass} and {@link MiniPassFactory}. | ||
*/ | ||
public interface IRProcessingPass extends ProcessingPass { | ||
/** The passes that this pass depends _directly_ on to run. */ | ||
public Seq<? extends IRProcessingPass> precursorPasses(); | ||
|
||
/** The passes that are invalidated by running this pass. */ | ||
public Seq<? extends IRProcessingPass> invalidatedPasses(); | ||
} |
136 changes: 136 additions & 0 deletions
136
engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniIRPass.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package org.enso.compiler.pass; | ||
|
||
import java.util.function.Function; | ||
import org.enso.compiler.core.IR; | ||
import org.enso.compiler.core.ir.Expression; | ||
import org.enso.compiler.core.ir.Module; | ||
|
||
/** | ||
* Mini IR pass operates on a single IR element at a time. The {@link org.enso.compiler.Compiler} | ||
* traverses the whole IR tree in DFS. It works in two phases. | ||
* | ||
* <p><b>Note</b> that the current implementation is limited to traverse only {@link | ||
* org.enso.compiler.core.ir.Expression} elements as provided by {@link | ||
* IR#mapExpressions(Function)}. Hence, the additional method {@link #transformModule(Module)}. | ||
* | ||
* <p>In the first, <b>prepare</b> phase, the compiler traverses from the root to the leaves and | ||
* calls the {@link #prepare(Expression)} method on the mini pass. During this phase, the mini pass | ||
* can gather information about the current IR element, but not modify it. | ||
* | ||
* <p>In the second, <b>transform</b> phase, the compiler returns from the leaves to the root and | ||
* calls the {@link #transformExpression(Expression)} method on the mini pass. During this phase, | ||
* the mini pass is free to transform the current IR element. The children of the current IR element | ||
* are already transformed. | ||
* | ||
* <p>For each IR element: | ||
* | ||
* <ol> | ||
* <li>The {@link #prepare(Expression)} method is called to prepare the pass for the current IR | ||
* element. This method is called when the {@link org.enso.compiler.Compiler} traverses the IR | ||
* tree from top to bottom. This is useful for mini passes that need to build some information | ||
* about the current IR element before transforming it. The mini pass must not modify the IR | ||
* element neither attach any metadata to it in this method. By returning {@code null} from | ||
* this method, the mini pass signals to the compiler that it wishes to not process the | ||
* subtree of the current IR element. | ||
* <li>The {@link #transformExpression(Expression)} method is called to transform the current IR | ||
* element. This method is called when the {@link org.enso.compiler.Compiler} traverses the | ||
* element from bottom to top. All the children of the current IR element are already | ||
* transformed when this method is called. | ||
* </ol> | ||
* | ||
* <p>Inspired by: <a href="https://dl.acm.org/doi/10.1145/3140587.3062346">Miniphases: compilation | ||
* using modular and efficient tree transformations</a>. PDF available at <a | ||
* href="https://infoscience.epfl.ch/server/api/core/bitstreams/8ab72c0a-8aa6-4dee-a704-3504938dc316/content">infoscience.epfl.ch</a> | ||
*/ | ||
public abstract class MiniIRPass { | ||
/** | ||
* Prepare the pass for the provided IR element. This method is called when the {@link | ||
* org.enso.compiler.Compiler} traverses the IR element from top to bottom. | ||
* | ||
* <p>The mini pass is free to gather any information about the elements it encounters (via this | ||
* method) and use it in the {@link #transformExpression(Expression)} method. Note however, that | ||
* it is not wise to store the references to the IR or their children for later comparison in the | ||
* {@link #transformExpression(Expression) transform phase}, as the IR tree will most likely be | ||
* transformed during the compilation process. | ||
* | ||
* <p>TL;DR; Do no store references to the IR elements or their children in this method. | ||
* | ||
* @param parent the the parent of the edge | ||
* @param child the child expression element to be be processed. | ||
* @return an instance of the pass to process the child's element subtree | ||
*/ | ||
public MiniIRPass prepare(IR parent, Expression child) { | ||
return this; | ||
} | ||
|
||
/** | ||
* Transform the provided IR element. Children of the IR element are already transformed when this | ||
* method is called. This method is called when the {@link org.enso.compiler.Compiler} traverses | ||
* the IR element from bottom to top. | ||
* | ||
* <p>The pass should not do any traversal in this method. | ||
* | ||
* @param expr Expression IR element to be transformed by this pass. | ||
* @return The transformed Expression IR, or the same IR if no transformation is needed. Must not | ||
* return null. | ||
*/ | ||
public abstract Expression transformExpression(Expression expr); | ||
|
||
/** | ||
* Transforms the module IR. This is the last method that is called. | ||
* | ||
* @see #transformExpression(Expression) | ||
*/ | ||
public Module transformModule(Module moduleIr) { | ||
return moduleIr; | ||
} | ||
|
||
public boolean checkPostCondition(IR ir) { | ||
return true; | ||
} | ||
|
||
/** | ||
* Name of the mini pass. | ||
* | ||
* @return by default it returns name of the implementing class | ||
*/ | ||
@Override | ||
public String toString() { | ||
return getClass().getName(); | ||
} | ||
|
||
/** | ||
* Combines two mini IR passes into one that delegates to both of them. | ||
* | ||
* @param first first mini pass (can be {@code null}) | ||
* @param second second mini pass | ||
* @return a combined pass that calls both non-{@code null} of the provided passes | ||
*/ | ||
public static MiniIRPass combine(MiniIRPass first, MiniIRPass second) { | ||
return ChainedMiniPass.chain(first, second); | ||
} | ||
|
||
/** | ||
* Takes an IR element of given type {@code irType} and transforms it by provided {@link | ||
* MiniIRPass}. When assertions are on, the resulting IR is checked with {@link | ||
* #checkPostCondition} method of provided {@code miniPass}. | ||
* | ||
* @param <T> the in and out type of IR | ||
* @param irType class of the requested IR type | ||
* @param ir the IR element (not {@code null}) | ||
* @param miniPass the pass to apply | ||
* @return the transformed IR | ||
*/ | ||
public static <T extends IR> T compile(Class<T> irType, T ir, MiniIRPass miniPass) { | ||
var newIr = MiniPassTraverser.compileDeep(ir, miniPass); | ||
assert irType.isInstance(newIr) | ||
: "Expected " | ||
+ irType.getName() | ||
+ " but got " | ||
+ newIr.getClass().getName() | ||
+ " by " | ||
+ miniPass; | ||
assert miniPass.checkPostCondition(newIr) : "Post condition failed for " + miniPass; | ||
return irType.cast(newIr); | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniPassFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package org.enso.compiler.pass; | ||
|
||
import org.enso.compiler.context.InlineContext; | ||
import org.enso.compiler.context.ModuleContext; | ||
|
||
/** | ||
* Mini IR pass operates on a single IR element at a time. The {@link org.enso.compiler.Compiler} | ||
* traverses the whole IR tree in DFS. The actual work is done by {@link MiniIRPass} implementation. | ||
* This factory only contains a collection of factory methods to create such {@link MiniIRPass IR | ||
* mini passes}. If a mini pass supports only inline compilation, its {@link | ||
* #createForModuleCompilation(ModuleContext)} method should return null. | ||
*/ | ||
public interface MiniPassFactory extends IRProcessingPass { | ||
/** | ||
* Creates an instance of mini pass that is capable of transforming IR elements in the context of | ||
* a module. | ||
* | ||
* @param moduleContext A mini pass can optionally save reference to this module context. | ||
* @return May return {@code null} if module compilation is not supported. | ||
*/ | ||
MiniIRPass createForModuleCompilation(ModuleContext moduleContext); | ||
|
||
/** | ||
* Creates an instance of mini pass that is capable of transforming IR elements in the context of | ||
* an inline compilation. | ||
* | ||
* @param inlineContext A mini pass can optionally save reference to this inline context. | ||
* @return Must not return {@code null}. Inline compilation should always be supported. | ||
*/ | ||
MiniIRPass createForInlineCompilation(InlineContext inlineContext); | ||
} |
89 changes: 89 additions & 0 deletions
89
engine/runtime-compiler/src/main/java/org/enso/compiler/pass/MiniPassTraverser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package org.enso.compiler.pass; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import org.enso.compiler.core.IR; | ||
import org.enso.compiler.core.ir.Expression; | ||
import org.enso.compiler.core.ir.Module; | ||
|
||
/** Implementation of {@link MiniIRPass#compile}. */ | ||
final class MiniPassTraverser { | ||
private final MiniIRPass miniPass; | ||
private List<IR> in; | ||
private final List<IR> out; | ||
private int outIndex; | ||
|
||
private MiniPassTraverser(MiniIRPass miniPass, List<IR> out, int outIndex) { | ||
this.miniPass = miniPass; | ||
this.out = out; | ||
this.outIndex = outIndex; | ||
} | ||
|
||
private boolean enqueue(Collection<MiniPassTraverser> queue) { | ||
if (in == null) { | ||
var ir = out.get(outIndex); | ||
in = enqueueSubExpressions(queue, ir, miniPass); | ||
return !in.isEmpty(); | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
private void convertExpression() { | ||
if (outIndex != -1) { | ||
var oldIr = out.get(outIndex); | ||
var index = new int[1]; | ||
var newIr = oldIr.mapExpressions((old) -> (Expression) in.get(index[0]++)); | ||
var transformedIr = | ||
switch (newIr) { | ||
case Module m -> miniPass.transformModule(m); | ||
case Expression e -> miniPass.transformExpression(e); | ||
default -> throw new IllegalArgumentException("" + oldIr); | ||
}; | ||
if (oldIr != transformedIr) { | ||
out.set(outIndex, transformedIr); | ||
} | ||
outIndex = -1; | ||
} | ||
} | ||
|
||
static IR compileDeep(IR root, MiniIRPass miniPass) { | ||
var result = new IR[] {root}; | ||
var rootTask = new MiniPassTraverser(miniPass, Arrays.asList(result), 0); | ||
var stackOfPendingIrs = new LinkedList<MiniPassTraverser>(); | ||
stackOfPendingIrs.add(rootTask); | ||
while (!stackOfPendingIrs.isEmpty()) { | ||
if (stackOfPendingIrs.peekLast().enqueue(stackOfPendingIrs)) { | ||
// continue descent | ||
continue; | ||
} | ||
var deepestIr = stackOfPendingIrs.removeLast(); | ||
deepestIr.convertExpression(); | ||
} | ||
assert result[0] != null; | ||
return result[0]; | ||
} | ||
|
||
/** | ||
* @param queue queue to put objects in | ||
* @param ir IR to process | ||
* @param miniPass process with this mini pass | ||
* @return {@code true} if the has been modified with new tries to process first | ||
*/ | ||
private static List<IR> enqueueSubExpressions( | ||
Collection<MiniPassTraverser> queue, IR ir, MiniIRPass miniPass) { | ||
var childExpressions = new ArrayList<IR>(); | ||
var i = new int[1]; | ||
ir.mapExpressions( | ||
(ch) -> { | ||
var preparedMiniPass = miniPass.prepare(ir, ch); | ||
childExpressions.add(ch); | ||
queue.add(new MiniPassTraverser(preparedMiniPass, childExpressions, i[0]++)); | ||
return ch; | ||
}); | ||
return childExpressions; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.