Migrate some passes to Mini passes (#11191)

Gets ready for avoiding IR traversal by introducing _mini passes_ as proposed by #10981:
- creates [MiniPassFactory](762045a357) (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](1abc70d33c) rejected for now, see _future work_ for details
- unlike _mega passes_ `IRMiniPass.compile` **does not  recursively** traverse, but with 0964711ba9 it invokes each _mini pass_ at constant stack depth - way better for profiling
- `MiniIRPass.prepare` _works on edges_ since ffd27dfe9b - 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](f54ba6d162) and _comparing `IR` produced by old and new_ implementations
This commit is contained in:
Pavel Marek 2024-10-15 21:26:08 +02:00 committed by GitHub
parent 07d0015f2f
commit b36fd1c01b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
48 changed files with 3065 additions and 1083 deletions

View File

@ -3,39 +3,28 @@ package org.enso.compiler.dump;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import org.enso.compiler.context.InlineContext;
import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.core.IR;
import org.enso.compiler.core.ir.Expression;
import org.enso.compiler.core.ir.Module;
import org.enso.compiler.pass.IRPass;
import org.enso.compiler.pass.IRProcessingPass;
import scala.collection.immutable.Seq;
/** A pass that just dumps IR to the local {@code ir-dumps} directory. See {@link IRDumper}. */
public class IRDumperPass implements IRPass {
public static final IRDumperPass INSTANCE = new IRDumperPass();
private UUID uuid;
private IRDumperPass() {}
@Override
public UUID key() {
return uuid;
}
@Override
public void org$enso$compiler$pass$IRPass$_setter_$key_$eq(UUID v) {
this.uuid = v;
}
@Override
public Seq<IRPass> precursorPasses() {
public Seq<IRProcessingPass> precursorPasses() {
return nil();
}
@Override
public Seq<IRPass> invalidatedPasses() {
public Seq<IRProcessingPass> invalidatedPasses() {
return nil();
}
@ -68,8 +57,8 @@ public class IRDumperPass implements IRPass {
}
@SuppressWarnings("unchecked")
private static scala.collection.immutable.List<IRPass> nil() {
private static scala.collection.immutable.List<IRProcessingPass> nil() {
Object obj = scala.collection.immutable.Nil$.MODULE$;
return (scala.collection.immutable.List<IRPass>) obj;
return (scala.collection.immutable.List<IRProcessingPass>) obj;
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -47,7 +47,7 @@ import scala.Tuple2$;
@Persistable(clazz = GlobalNames$.class, id = 1205)
@Persistable(clazz = IgnoredBindings$.class, id = 1206)
@Persistable(clazz = Patterns$.class, id = 1207)
@Persistable(clazz = TailCall$.class, id = 1208)
@Persistable(clazz = TailCall.class, id = 1208)
@Persistable(clazz = TypeNames$.class, id = 1209)
@Persistable(clazz = TypeSignatures$.class, id = 1210)
@Persistable(clazz = DocumentationComments$.class, id = 1211)
@ -66,7 +66,7 @@ import scala.Tuple2$;
@Persistable(clazz = Graph.Link.class, id = 1266, allowInlining = false)
@Persistable(clazz = TypeInference.class, id = 1280)
@Persistable(clazz = FramePointerAnalysis$.class, id = 1281)
@Persistable(clazz = TailCall$TailPosition$Tail$.class, id = 1282)
@Persistable(clazz = TailCall.TailPosition.class, id = 1282)
public final class PassPersistance {
private PassPersistance() {}

View File

@ -1,7 +1,6 @@
package org.enso.compiler.pass.analyse;
import java.util.List;
import java.util.UUID;
import org.enso.compiler.context.InlineContext;
import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.core.IR;
@ -11,6 +10,7 @@ import org.enso.compiler.core.ir.expression.errors.Syntax;
import org.enso.compiler.core.ir.expression.errors.Syntax.InconsistentConstructorVisibility$;
import org.enso.compiler.core.ir.module.scope.Definition;
import org.enso.compiler.pass.IRPass;
import org.enso.compiler.pass.IRProcessingPass;
import scala.collection.immutable.Seq;
import scala.jdk.javaapi.CollectionConverters;
@ -21,31 +21,19 @@ import scala.jdk.javaapi.CollectionConverters;
public final class PrivateConstructorAnalysis implements IRPass {
public static final PrivateConstructorAnalysis INSTANCE = new PrivateConstructorAnalysis();
private UUID uuid;
private PrivateConstructorAnalysis() {}
@Override
public void org$enso$compiler$pass$IRPass$_setter_$key_$eq(UUID v) {
this.uuid = v;
}
@Override
public UUID key() {
return uuid;
}
@Override
public Seq<IRPass> precursorPasses() {
List<IRPass> passes = List.of(PrivateModuleAnalysis.INSTANCE);
public Seq<IRProcessingPass> precursorPasses() {
List<IRProcessingPass> passes = List.of(PrivateModuleAnalysis.INSTANCE);
return CollectionConverters.asScala(passes).toList();
}
@Override
@SuppressWarnings("unchecked")
public Seq<IRPass> invalidatedPasses() {
public Seq<IRProcessingPass> invalidatedPasses() {
Object obj = scala.collection.immutable.Nil$.MODULE$;
return (scala.collection.immutable.List<IRPass>) obj;
return (scala.collection.immutable.List<IRProcessingPass>) obj;
}
@Override

View File

@ -2,7 +2,6 @@ package org.enso.compiler.pass.analyse;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.enso.compiler.context.InlineContext;
import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.core.IR;
@ -13,6 +12,7 @@ import org.enso.compiler.core.ir.module.scope.Export;
import org.enso.compiler.core.ir.module.scope.Import;
import org.enso.compiler.data.BindingsMap;
import org.enso.compiler.pass.IRPass;
import org.enso.compiler.pass.IRProcessingPass;
import org.enso.pkg.QualifiedName;
import scala.Option;
import scala.collection.immutable.Seq;
@ -31,31 +31,21 @@ import scala.jdk.javaapi.CollectionConverters;
*/
public final class PrivateModuleAnalysis implements IRPass {
public static final PrivateModuleAnalysis INSTANCE = new PrivateModuleAnalysis();
private UUID uuid;
private PrivateModuleAnalysis() {}
@Override
public void org$enso$compiler$pass$IRPass$_setter_$key_$eq(UUID v) {
this.uuid = v;
}
@Override
public UUID key() {
return uuid;
}
@Override
public Seq<IRPass> precursorPasses() {
List<IRPass> passes = List.of(BindingAnalysis$.MODULE$, ImportSymbolAnalysis$.MODULE$);
public Seq<IRProcessingPass> precursorPasses() {
List<IRProcessingPass> passes =
List.of(BindingAnalysis$.MODULE$, ImportSymbolAnalysis$.MODULE$);
return CollectionConverters.asScala(passes).toList();
}
@Override
@SuppressWarnings("unchecked")
public Seq<IRPass> invalidatedPasses() {
public Seq<IRProcessingPass> invalidatedPasses() {
Object obj = scala.collection.immutable.Nil$.MODULE$;
return (scala.collection.immutable.List<IRPass>) obj;
return (scala.collection.immutable.List<IRProcessingPass>) obj;
}
@Override

View File

@ -1,7 +1,6 @@
package org.enso.compiler.pass.analyse;
import java.util.List;
import java.util.UUID;
import org.enso.compiler.context.InlineContext;
import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.core.IR;
@ -17,6 +16,7 @@ import org.enso.compiler.data.BindingsMap.ResolvedConstructor;
import org.enso.compiler.data.BindingsMap.ResolvedModule;
import org.enso.compiler.data.BindingsMap.ResolvedName;
import org.enso.compiler.pass.IRPass;
import org.enso.compiler.pass.IRProcessingPass;
import org.enso.compiler.pass.resolve.Patterns$;
import org.enso.pkg.QualifiedName;
import scala.collection.immutable.Seq;
@ -30,30 +30,19 @@ import scala.jdk.javaapi.CollectionConverters;
*/
public class PrivateSymbolsAnalysis implements IRPass {
public static final PrivateSymbolsAnalysis INSTANCE = new PrivateSymbolsAnalysis();
private UUID uuid;
private PrivateSymbolsAnalysis() {}
@Override
public void org$enso$compiler$pass$IRPass$_setter_$key_$eq(UUID v) {
this.uuid = v;
}
@Override
public UUID key() {
return uuid;
}
@Override
public Seq<IRPass> precursorPasses() {
List<IRPass> passes =
public Seq<IRProcessingPass> precursorPasses() {
List<IRProcessingPass> passes =
List.of(
PrivateModuleAnalysis.INSTANCE, PrivateConstructorAnalysis.INSTANCE, Patterns$.MODULE$);
return CollectionConverters.asScala(passes).toList();
}
@Override
public Seq<IRPass> invalidatedPasses() {
public Seq<IRProcessingPass> invalidatedPasses() {
return nil();
}

View File

@ -0,0 +1,393 @@
package org.enso.compiler.pass.analyse;
import java.util.List;
import org.enso.compiler.context.InlineContext;
import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.core.CompilerError;
import org.enso.compiler.core.CompilerStub;
import org.enso.compiler.core.IR;
import org.enso.compiler.core.ir.CallArgument;
import org.enso.compiler.core.ir.Expression;
import org.enso.compiler.core.ir.Function;
import org.enso.compiler.core.ir.Literal;
import org.enso.compiler.core.ir.MetadataStorage.MetadataPair;
import org.enso.compiler.core.ir.Module;
import org.enso.compiler.core.ir.Name;
import org.enso.compiler.core.ir.Type;
import org.enso.compiler.core.ir.Warning;
import org.enso.compiler.core.ir.expression.Application;
import org.enso.compiler.core.ir.expression.Case;
import org.enso.compiler.core.ir.expression.Comment;
import org.enso.compiler.core.ir.module.scope.Definition;
import org.enso.compiler.core.ir.module.scope.definition.*;
import org.enso.compiler.pass.IRPass;
import org.enso.compiler.pass.IRProcessingPass;
import org.enso.compiler.pass.MiniIRPass;
import org.enso.compiler.pass.MiniPassFactory;
import org.enso.compiler.pass.desugar.*;
import org.enso.compiler.pass.resolve.ExpressionAnnotations;
import org.enso.compiler.pass.resolve.ExpressionAnnotations$;
import org.enso.compiler.pass.resolve.GlobalNames$;
import org.enso.compiler.pass.resolve.ModuleAnnotations.Annotations;
import scala.Option;
import scala.collection.immutable.Seq;
import scala.jdk.javaapi.CollectionConverters;
/**
* This pass performs tail call analysis on the Enso IR.
*
* <p>It is responsible for marking every single expression with whether it is in tail position.
* This allows the code generator to correctly create the Truffle nodes. If the expression is in
* tail position, [[TailPosition.Tail]] metadata is attached to it, otherwise, nothing is attached.
*
* <p>This pass requires the context to provide:
*
* <p>- The tail position of its expression, where relevant.
*/
public final class TailCall implements MiniPassFactory {
public static final TailCall INSTANCE = new TailCall();
private static final MetadataPair<TailCall> TAIL_META =
new MetadataPair<>(INSTANCE, TailPosition.Tail);
private TailCall() {}
@Override
public Seq<IRProcessingPass> precursorPasses() {
List<IRProcessingPass> passes =
List.of(
FunctionBinding$.MODULE$,
GenerateMethodBodies$.MODULE$,
SectionsToBinOp.INSTANCE,
OperatorToFunction$.MODULE$,
LambdaShorthandToLambda$.MODULE$,
GlobalNames$.MODULE$);
return CollectionConverters.asScala(passes).toList();
}
@Override
public Seq<IRProcessingPass> invalidatedPasses() {
return CollectionConverters.asScala(List.<IRProcessingPass>of()).toList();
}
@Override
public MiniIRPass createForInlineCompilation(InlineContext inlineContext) {
var opt = inlineContext.isInTailPosition();
if (opt.isEmpty()) {
throw new CompilerError(
"Information about the tail position for an inline expression "
+ "must be known by the point of tail call analysis.");
}
return mini(Boolean.TRUE.equals(opt.get()));
}
@Override
public MiniIRPass createForModuleCompilation(ModuleContext moduleContext) {
return mini(false);
}
private static final Mini IN_TAIL_POS = new Mini(true);
private static final Mini NOT_IN_TAIL_POS = new Mini(false);
static Mini mini(boolean isInTailPos) {
return isInTailPos ? IN_TAIL_POS : NOT_IN_TAIL_POS;
}
/** Expresses the tail call state of an IR Node. */
@SuppressWarnings("unchecked")
public static final class TailPosition implements IRPass.IRMetadata {
public static final TailPosition Tail = new TailPosition();
private TailPosition() {}
/** A boolean representation of the expression's tail state. */
public boolean isTail() {
return true;
}
@Override
public String metadataName() {
return "TailCall.TailPosition.Tail";
}
@Override
public Option duplicate() {
return Option.apply(this);
}
/**
* @inheritdoc
*/
@Override
public TailPosition prepareForSerialization(CompilerStub compiler) {
return this;
}
/**
* @inheritdoc
*/
@Override
public Option restoreFromSerialization(CompilerStub compiler) {
return Option.apply(this);
}
}
/**
* Checks if the provided `expression` is annotated with a tail call annotation.
*
* @param expression the expression to check
* @return `true` if `expression` is annotated with `@Tail_Call`, otherwise `false`
*/
public static final boolean isTailAnnotated(Expression expression) {
var meta = expression.passData().get(ExpressionAnnotations$.MODULE$);
if (meta.isEmpty()) {
return false;
}
var anns = (Annotations) meta.get();
return anns.annotations().exists(a -> ExpressionAnnotations.tailCallName().equals(a.name()));
}
private static final class Mini extends MiniIRPass {
private final boolean isInTailPos;
Mini(boolean in) {
isInTailPos = in;
}
@Override
public Module transformModule(Module m) {
m.bindings().map(this::updateModuleBinding);
return m;
}
/**
* Performs tail call analysis on a top-level definition in a module.
*
* @param moduleDefinition the top-level definition to analyse
* @return `definition`, annotated with tail call information
*/
private Void updateModuleBinding(Definition moduleDefinition) {
switch (moduleDefinition) {
case Method.Conversion method -> markAsTail(method);
case Method.Explicit method -> markAsTail(method);
case Method.Binding b -> throw new CompilerError(
"Sugared method definitions should not occur during tail call " + "analysis.");
case Definition.Type t -> markAsTail(t);
case Definition.SugaredType st -> throw new CompilerError(
"Complex type definitions should not be present during " + "tail call analysis.");
case Comment.Documentation cd -> throw new CompilerError(
"Documentation should not exist as an entity during tail call analysis.");
case Type.Ascription ta -> throw new CompilerError(
"Type signatures should not exist at the top level during " + "tail call analysis.");
case Name.BuiltinAnnotation ba -> throw new CompilerError(
"Annotations should already be associated by the point of " + "tail call analysis.");
case Name.GenericAnnotation ann -> markAsTail(ann);
default -> {}
}
return null;
}
private void markAsTailConditionally(IR ir) {
if (isInTailPos) {
markAsTail(ir);
}
}
private Void markAsTail(IR ir) {
ir.passData().update(TAIL_META);
return null;
}
@Override
public Expression transformExpression(Expression ir) {
switch (ir) {
case Literal l -> {}
case Application.Prefix p -> {
markAsTailConditionally(p);
// Note [Call Argument Tail Position]
p.arguments().foreach(a -> markAsTail(a));
}
case Case.Expr e -> {
if (isInTailPos) {
markAsTail(ir);
// Note [Analysing Branches in Case Expressions]
e.branches().foreach(b -> markAsTail(b));
}
}
default -> markAsTailConditionally(ir);
}
if (!isInTailPos && isTailAnnotated(ir)) {
ir.getDiagnostics().add(new Warning.WrongTco(ir.identifiedLocation()));
}
return ir;
}
@Override
public Mini prepare(IR parent, Expression child) {
var isChildTailCandidate =
switch (parent) {
case Module m -> true;
case Expression e -> {
var tailCandidates = new java.util.IdentityHashMap<IR, Boolean>();
collectTailCandidatesExpression(e, tailCandidates);
yield tailCandidates.containsKey(child);
}
default -> false;
};
return new Mini(isChildTailCandidate);
}
/**
* Performs tail call analysis on an arbitrary expression.
*
* @param expression the expression to check
* @return `expression`, annotated with tail position metadata
*/
private void collectTailCandidatesExpression(
Expression expression, java.util.Map<IR, Boolean> tailCandidates) {
switch (expression) {
case Function function -> collectTailCandicateFunction(function, tailCandidates);
case Case caseExpr -> collectTailCandidatesCase(caseExpr, tailCandidates);
case Application app -> collectTailCandidatesApplication(app, tailCandidates);
case Name name -> collectTailCandidatesName(name, tailCandidates);
case Comment c -> throw new CompilerError(
"Comments should not be present during tail call analysis.");
case Expression.Block b -> {
if (isInTailPos) {
tailCandidates.put(b.returnValue(), true);
}
}
default -> {}
}
}
/**
* Performs tail call analysis on an occurrence of a name.
*
* @param name the name to check
* @return `name`, annotated with tail position metadata
*/
private void collectTailCandidatesName(Name name, java.util.Map<IR, Boolean> tailCandidates) {
if (isInTailPos) {
tailCandidates.put(name, true);
}
}
/**
* Performs tail call analysis on an application.
*
* @param application the application to check
* @return `application`, annotated with tail position metadata
*/
private void collectTailCandidatesApplication(
Application application, java.util.Map<IR, Boolean> tailCandidates) {
switch (application) {
case Application.Prefix p -> p.arguments()
.foreach(a -> collectTailCandidatesCallArg(a, tailCandidates));
case Application.Force f -> {
if (isInTailPos) {
tailCandidates.put(f.target(), true);
}
}
case Application.Sequence s -> {}
case Application.Typeset ts -> {}
default -> throw new CompilerError("Unexpected binary operator.");
}
}
/**
* Performs tail call analysis on a call site argument.
*
* @param argument the argument to check
* @return `argument`, annotated with tail position metadata
*/
private Void collectTailCandidatesCallArg(
CallArgument argument, java.util.Map<IR, Boolean> tailCandidates) {
switch (argument) {
case CallArgument.Specified ca ->
// Note [Call Argument Tail Position]
tailCandidates.put(ca.value(), true);
default -> {}
}
return null;
}
/* Note [Call Argument Tail Position]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* In order to efficiently deal with Enso's ability to suspend function
* arguments, we behave as if all arguments to a function are passed as
* thunks. This means that the _function_ becomes responsible for deciding
* when to evaluate its arguments.
*
* Conceptually, this results in a desugaring as follows:
*
* ```
* foo a b c
* ```
*
* Becomes:
*
* ```
* foo ({} -> a) ({} -> b) ({} -> c)
* ```
*
* Quite obviously, the arguments `a`, `b` and `c` are in tail position in
* these closures, and hence should be marked as tail.
*/
/**
* Performs tail call analysis on a case expression.
*
* @param caseExpr the case expression to check
* @return `caseExpr`, annotated with tail position metadata
*/
private void collectTailCandidatesCase(
Case caseExpr, java.util.Map<IR, Boolean> tailCandidates) {
switch (caseExpr) {
case Case.Expr expr -> {
if (isInTailPos) {
// Note [Analysing Branches in Case Expressions]
expr.branches()
.foreach(
b -> {
tailCandidates.put(b.expression(), true);
return null;
});
}
}
default -> throw new CompilerError("Unexpected case branch.");
}
}
/* Note [Analysing Branches in Case Expressions]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* When performing tail call analysis on a case expression it is very
* important to recognise that the branches of a case expression should all
* have the same tail call state. The branches should only be marked as being
* in tail position when the case expression _itself_ is in tail position.
*
* As only one branch is ever executed, it is hence safe to mark _all_
* branches as being in tail position if the case expression is.
*/
/**
* Body of the function may be in tail position.
*
* @param function the function to check
* @return `function`, annotated with tail position metadata
*/
private void collectTailCandicateFunction(
Function function, java.util.Map<IR, Boolean> tailCandidates) {
var canBeTCO = function.canBeTCO();
var markAsTail = (!canBeTCO && isInTailPos) || canBeTCO;
switch (function) {
case Function.Lambda l -> {
if (markAsTail) {
tailCandidates.put(l.body(), true);
}
}
default -> throw new CompilerError(
"Function sugar should not be present during tail call analysis.");
}
}
}
}

View File

@ -2,7 +2,6 @@ package org.enso.compiler.pass.analyse.types;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import org.enso.compiler.context.InlineContext;
import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.core.IR;
@ -12,6 +11,7 @@ import org.enso.compiler.core.ir.Warning;
import org.enso.compiler.core.ir.module.scope.Definition;
import org.enso.compiler.core.ir.module.scope.definition.Method;
import org.enso.compiler.pass.IRPass;
import org.enso.compiler.pass.IRProcessingPass;
import org.enso.compiler.pass.analyse.BindingAnalysis$;
import org.enso.compiler.pass.resolve.FullyQualifiedNames$;
import org.enso.compiler.pass.resolve.GlobalNames$;
@ -92,21 +92,10 @@ public final class TypeInference implements IRPass {
.add(new Warning.NotInvokable(relatedIr.identifiedLocation(), type.toString()));
}
};
private UUID uuid;
@Override
public void org$enso$compiler$pass$IRPass$_setter_$key_$eq(UUID v) {
this.uuid = v;
}
@Override
public UUID key() {
return uuid;
}
@Override
public Seq<IRPass> precursorPasses() {
List<IRPass> passes =
public Seq<IRProcessingPass> precursorPasses() {
List<IRProcessingPass> passes =
List.of(
BindingAnalysis$.MODULE$,
GlobalNames$.MODULE$,
@ -119,8 +108,8 @@ public final class TypeInference implements IRPass {
@Override
@SuppressWarnings("unchecked")
public Seq<IRPass> invalidatedPasses() {
return (Seq<IRPass>) Seq$.MODULE$.empty();
public Seq<IRProcessingPass> invalidatedPasses() {
return (Seq<IRProcessingPass>) Seq$.MODULE$.empty();
}
@Override

View File

@ -0,0 +1,44 @@
package org.enso.compiler.pass.desugar;
import org.enso.compiler.core.IR;
import org.enso.compiler.core.ir.CallArgument;
import org.enso.compiler.core.ir.Expression;
import org.enso.compiler.core.ir.expression.Application;
import org.enso.compiler.core.ir.expression.Operator;
import org.enso.compiler.pass.MiniIRPass;
import scala.collection.mutable.ListBuffer;
public class OperatorToFunctionMini extends MiniIRPass {
OperatorToFunctionMini() {}
@Override
public Expression transformExpression(Expression ir) {
if (ir instanceof Operator.Binary binOp) {
ListBuffer<CallArgument> args = new ListBuffer<>();
args.addOne(binOp.left());
args.addOne(binOp.right());
return new Application.Prefix(
binOp.operator(),
args.toList(),
false,
binOp.location().isDefined() ? binOp.location().get() : null,
binOp.passData(),
binOp.diagnostics());
}
return ir;
}
@Override
public boolean checkPostCondition(IR ir) {
boolean[] isChildOperator = {false};
ir.children()
.foreach(
child -> {
if (child instanceof Operator.Binary) {
isChildOperator[0] = true;
}
return null;
});
return !isChildOperator[0];
}
}

View File

@ -0,0 +1,253 @@
package org.enso.compiler.pass.desugar;
import java.util.List;
import org.enso.compiler.context.InlineContext;
import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.core.ir.CallArgument;
import org.enso.compiler.core.ir.DefinitionArgument;
import org.enso.compiler.core.ir.Expression;
import org.enso.compiler.core.ir.Function;
import org.enso.compiler.core.ir.MetadataStorage;
import org.enso.compiler.core.ir.Name;
import org.enso.compiler.core.ir.expression.Application;
import org.enso.compiler.core.ir.expression.Section;
import org.enso.compiler.pass.IRProcessingPass;
import org.enso.compiler.pass.MiniIRPass;
import org.enso.compiler.pass.MiniPassFactory;
import org.enso.compiler.pass.analyse.AliasAnalysis$;
import org.enso.compiler.pass.analyse.CachePreferenceAnalysis$;
import org.enso.compiler.pass.analyse.DataflowAnalysis$;
import org.enso.compiler.pass.analyse.DemandAnalysis$;
import org.enso.compiler.pass.analyse.TailCall;
import org.enso.compiler.pass.lint.UnusedBindings$;
import scala.Option;
import scala.collection.immutable.Seq;
import scala.jdk.javaapi.CollectionConverters;
public final class SectionsToBinOp implements MiniPassFactory {
public static final SectionsToBinOp INSTANCE = new SectionsToBinOp();
private SectionsToBinOp() {}
@Override
public Seq<IRProcessingPass> precursorPasses() {
List<IRProcessingPass> passes = List.of(GenerateMethodBodies$.MODULE$);
return CollectionConverters.asScala(passes).toList();
}
@Override
public Seq<IRProcessingPass> invalidatedPasses() {
List<IRProcessingPass> passes =
List.of(
AliasAnalysis$.MODULE$,
CachePreferenceAnalysis$.MODULE$,
DataflowAnalysis$.MODULE$,
DemandAnalysis$.MODULE$,
TailCall.INSTANCE,
UnusedBindings$.MODULE$);
return CollectionConverters.asScala(passes).toList();
}
@Override
public MiniIRPass createForModuleCompilation(ModuleContext moduleContext) {
var ctx = InlineContext.fromModuleContext(moduleContext);
return new Mini(ctx);
}
@Override
public MiniIRPass createForInlineCompilation(InlineContext inlineContext) {
return new Mini(inlineContext);
}
private static final class Mini extends MiniIRPass {
private final InlineContext ctx;
private Mini(InlineContext ctx) {
this.ctx = ctx;
}
public Expression transformExpression(Expression ir) {
var freshNameSupply = ctx.freshNameSupply().get();
return switch (ir) {
case Section.Left sectionLeft -> {
var arg = sectionLeft.arg();
var op = sectionLeft.operator();
var loc = sectionLeft.location().isDefined() ? sectionLeft.location().get() : null;
var passData = sectionLeft.passData();
var rightArgName = freshNameSupply.newName(false, Option.empty());
var rightCallArg = new CallArgument.Specified(Option.empty(), rightArgName, null, meta());
var rightDefArg =
new DefinitionArgument.Specified(
rightArgName.duplicate(true, true, true, false),
Option.empty(),
Option.empty(),
false,
null,
meta());
if (arg.value() instanceof Name.Blank) {
var leftArgName = freshNameSupply.newName(false, Option.empty());
var leftCallArg = new CallArgument.Specified(Option.empty(), leftArgName, null, meta());
var leftDefArg =
new DefinitionArgument.Specified(
leftArgName.duplicate(true, true, true, false),
Option.empty(),
Option.empty(),
false,
null,
meta());
var opCall =
new Application.Prefix(
op,
cons(leftCallArg, cons(rightCallArg, nil())),
false,
null,
passData,
sectionLeft.diagnostics());
var rightLam =
new Function.Lambda(cons(rightDefArg, nil()), opCall, null, true, meta());
yield new Function.Lambda(cons(leftDefArg, nil()), rightLam, loc, true, meta());
} else {
yield new Application.Prefix(
op, cons(arg, nil()), false, loc, passData, sectionLeft.diagnostics());
}
}
case Section.Sides sectionSides -> {
var op = sectionSides.operator();
var loc = sectionSides.location().isDefined() ? sectionSides.location().get() : null;
var passData = sectionSides.passData();
var leftArgName = freshNameSupply.newName(false, Option.empty());
var leftCallArg = new CallArgument.Specified(Option.empty(), leftArgName, null, meta());
var leftDefArg =
new DefinitionArgument.Specified(
leftArgName.duplicate(true, true, true, false),
Option.empty(),
Option.empty(),
false,
null,
meta());
var rightArgName = freshNameSupply.newName(false, Option.empty());
var rightCallArg = new CallArgument.Specified(Option.empty(), rightArgName, null, meta());
var rightDefArg =
new DefinitionArgument.Specified(
rightArgName.duplicate(true, true, true, false),
Option.empty(),
Option.empty(),
false,
null,
meta());
var opCall =
new Application.Prefix(
op,
cons(leftCallArg, cons(rightCallArg, nil())),
false,
null,
passData,
sectionSides.diagnostics());
var rightLambda =
new Function.Lambda(cons(rightDefArg, nil()), opCall, null, true, meta());
yield new Function.Lambda(cons(leftDefArg, nil()), rightLambda, loc, true, meta());
}
/* Note [Blanks in Sections]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* While the naiive compositional translation of `(- _)` first translates
* the section into a function applying `-` to two arguments, one of which
* is a blank, the compositional nature of the blanks translation actually
* works against us here.
*
* As the `LambdaShorthandToLambda` pass can only operate on the
* application with the blanks, it can't know to push the blank outside
* that application chain. To that end, we have to handle this case
* specially here instead. What we want it to translate to is as follows:
*
* `(- _)` == `x -> (- x)` == `x -> y -> y - x`
*
* We implement this special case here.
*
* The same is true of left sections.
*/
case Section.Right sectionRight -> {
var arg = sectionRight.arg();
var op = sectionRight.operator();
var loc = sectionRight.location().isDefined() ? sectionRight.location().get() : null;
var passData = sectionRight.passData();
var leftArgName = freshNameSupply.newName(false, Option.empty());
var leftCallArg = new CallArgument.Specified(Option.empty(), leftArgName, null, meta());
var leftDefArg =
new DefinitionArgument.Specified(
leftArgName.duplicate(true, true, true, false),
Option.empty(),
Option.empty(),
false,
null,
meta());
if (arg.value() instanceof Name.Blank) {
// Note [Blanks in Sections]
var rightArgName = freshNameSupply.newName(false, Option.empty());
var rightCallArg =
new CallArgument.Specified(Option.empty(), rightArgName, null, meta());
var rightDefArg =
new DefinitionArgument.Specified(
rightArgName.duplicate(true, true, true, false),
Option.empty(),
Option.empty(),
false,
null,
meta());
var opCall =
new Application.Prefix(
op,
cons(leftCallArg, cons(rightCallArg, nil())),
false,
null,
passData,
sectionRight.diagnostics());
var leftLam = new Function.Lambda(cons(leftDefArg, nil()), opCall, null, true, meta());
yield new Function.Lambda(cons(rightDefArg, nil()), leftLam, loc, true, meta());
} else {
var opCall =
new Application.Prefix(
op,
cons(leftCallArg, cons(arg, nil())),
false,
null,
passData,
sectionRight.diagnostics());
yield new Function.Lambda(cons(leftDefArg, nil()), opCall, loc, true, meta());
}
}
default -> ir;
};
}
private static MetadataStorage meta() {
return new MetadataStorage();
}
@SuppressWarnings("unchecked")
private static <T> scala.collection.immutable.List<T> nil() {
return (scala.collection.immutable.List<T>) scala.collection.immutable.Nil$.MODULE$;
}
private static <T> scala.collection.immutable.List<T> cons(
T head, scala.collection.immutable.List<T> tail) {
return scala.collection.immutable.$colon$colon$.MODULE$.apply(head, tail);
}
}
}

View File

@ -18,7 +18,7 @@ import org.enso.compiler.pass.optimise.{
}
import org.enso.compiler.pass.resolve._
import org.enso.compiler.pass.{
IRPass,
IRProcessingPass,
PassConfiguration,
PassGroup,
PassManager
@ -41,7 +41,7 @@ class Passes(config: CompilerConfig) {
val globalTypingPasses = new PassGroup(
List(
MethodDefinitions,
SectionsToBinOp,
SectionsToBinOp.INSTANCE,
OperatorToFunction,
LambdaShorthandToLambda,
ImportSymbolAnalysis,
@ -86,7 +86,7 @@ class Passes(config: CompilerConfig) {
AliasAnalysis,
DemandAnalysis,
AliasAnalysis,
TailCall,
TailCall.INSTANCE,
Patterns
) ++ (if (config.privateCheckEnabled) {
List(PrivateSymbolsAnalysis.INSTANCE)
@ -123,7 +123,7 @@ class Passes(config: CompilerConfig) {
)
/** The ordered representation of all passes run by the compiler. */
val allPassOrdering: List[IRPass] = passOrdering.flatMap(_.passes)
val allPassOrdering: List[IRProcessingPass] = passOrdering.flatMap(_.passes)
/** Configuration for the passes. */
private val passConfig: PassConfiguration = PassConfiguration(
@ -142,7 +142,7 @@ class Passes(config: CompilerConfig) {
* @param pass the pass to get the precursors for
* @return the precursors to the first instance of `pass`
*/
def getPrecursors(pass: IRPass): Option[PassGroup] = {
def getPrecursors(pass: IRProcessingPass): Option[PassGroup] = {
val allPasses = passOrdering.flatMap(_.passes)
val result = allPasses.takeWhile(_ != pass)
if (result.length != allPasses.length) {

View File

@ -19,10 +19,7 @@ import scala.reflect.ClassTag
* its header the requirements it has for pass configuration and for passes
* that must run before it.
*/
trait IRPass extends ProcessingPass {
/** An identifier for the pass. Useful for keying it in maps. */
val key: UUID @Identifier = IRPass.genId
trait IRPass extends IRProcessingPass with ProcessingPass {
/** The type of the metadata object that the pass writes to the IR. */
type Metadata <: ProcessingPass.Metadata
@ -31,10 +28,10 @@ trait IRPass extends ProcessingPass {
type Config <: IRPass.Configuration
/** The passes that this pass depends _directly_ on to run. */
val precursorPasses: Seq[IRPass]
val precursorPasses: Seq[IRProcessingPass]
/** The passes that are invalidated by running this pass. */
val invalidatedPasses: Seq[IRPass]
val invalidatedPasses: Seq[IRProcessingPass]
/** Executes the pass on the provided `ir`, and returns a possibly transformed
* or annotated version of `ir`.

View File

@ -1,5 +1,6 @@
package org.enso.compiler.pass
import org.slf4j.LoggerFactory
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.ir.{Expression, Module}
import org.enso.compiler.core.CompilerError
@ -20,7 +21,8 @@ class PassManager(
passes: List[PassGroup],
passConfiguration: PassConfiguration
) {
val allPasses = verifyPassOrdering(passes.flatMap(_.passes))
private val logger = LoggerFactory.getLogger(classOf[PassManager])
val allPasses = verifyPassOrdering(passes.flatMap(_.passes))
/** Computes a valid pass ordering for the compiler.
*
@ -28,8 +30,10 @@ class PassManager(
* @throws CompilerError if a valid pass ordering cannot be computed
* @return a valid pass ordering for the compiler, based on `passes`
*/
private def verifyPassOrdering(passes: List[IRPass]): List[IRPass] = {
var validPasses: Set[IRPass] = Set()
private def verifyPassOrdering(
passes: List[IRProcessingPass]
): List[IRProcessingPass] = {
var validPasses: Set[IRProcessingPass] = Set()
passes.foreach(pass => {
val prereqsSatisfied =
@ -89,18 +93,65 @@ class PassManager(
val passesWithIndex = passGroup.passes.zipWithIndex
passesWithIndex.foldLeft(ir) {
case (intermediateIR, (pass, index)) => {
// TODO [AA, MK] This is a possible race condition.
passConfiguration
.get(pass)
.foreach(c =>
c.shouldWriteToContext = isLastRunOf(index, pass, passGroup)
)
pass.runModule(intermediateIR, newContext)
logger.debug(
"runPassesOnModule[{}@{}]",
moduleContext.getName(),
moduleContext.module.getCompilationStage()
)
var pendingMiniPasses: List[MiniPassFactory] = List()
def flushMiniPass(in: Module): Module = {
if (pendingMiniPasses.nonEmpty) {
val miniPasses = pendingMiniPasses.map(factory =>
factory.createForModuleCompilation(newContext)
)
val combinedPass = miniPasses.fold(null)(MiniIRPass.combine)
logger.trace(" flushing pending mini pass: {}", combinedPass)
pendingMiniPasses = List()
MiniIRPass.compile(classOf[Module], in, combinedPass)
} else {
in
}
}
val res = passesWithIndex.foldLeft(ir) {
case (intermediateIR, (pass, index)) => {
pass match {
case miniFactory: MiniPassFactory =>
logger.trace(
" mini collected: {}",
pass
)
val combiningPreventedBy = pendingMiniPasses.find { p =>
p.invalidatedPasses.contains(miniFactory)
}
val irForRemainingMiniPasses = if (combiningPreventedBy.isDefined) {
logger.trace(
" pass {} forces flush before {}",
combiningPreventedBy.orNull,
miniFactory
)
flushMiniPass(intermediateIR)
} else {
intermediateIR
}
pendingMiniPasses = pendingMiniPasses.appended(miniFactory)
irForRemainingMiniPasses
case megaPass: IRPass =>
// TODO [AA, MK] This is a possible race condition.
passConfiguration
.get(megaPass)
.foreach(c =>
c.shouldWriteToContext = isLastRunOf(index, megaPass, passGroup)
)
val flushedIR = flushMiniPass(intermediateIR)
logger.trace(
" mega running: {}",
megaPass
)
megaPass.runModule(flushedIR, newContext)
}
}
}
flushMiniPass(res)
}
/** Executes all passes on the [[Expression]].
@ -141,14 +192,21 @@ class PassManager(
passesWithIndex.foldLeft(ir) {
case (intermediateIR, (pass, index)) => {
// TODO [AA, MK] This is a possible race condition.
passConfiguration
.get(pass)
.foreach(c =>
c.shouldWriteToContext = isLastRunOf(index, pass, passGroup)
)
pass.runExpression(intermediateIR, newContext)
pass match {
case miniFactory: MiniPassFactory =>
val miniPass = miniFactory.createForInlineCompilation(newContext)
MiniIRPass.compile(classOf[Expression], intermediateIR, miniPass)
case megaPass: IRPass =>
// TODO [AA, MK] This is a possible race condition.
passConfiguration
.get(megaPass)
.foreach(c =>
c.shouldWriteToContext = isLastRunOf(index, megaPass, passGroup)
)
megaPass.runExpression(intermediateIR, newContext)
}
}
}
}
@ -189,8 +247,10 @@ class PassManager(
* information from `sourceIr`
*/
def runMetadataUpdate(sourceIr: Module, copyOfIr: Module): Module = {
allPasses.foldLeft(copyOfIr) { (module, pass) =>
pass.updateMetadataInDuplicate(sourceIr, module)
allPasses.foldLeft(copyOfIr) {
case (module, megaPass: IRPass) =>
megaPass.updateMetadataInDuplicate(sourceIr, module)
case (module, _) => module
}
}
}
@ -199,4 +259,4 @@ class PassManager(
*
* @param passes the passes in the group
*/
class PassGroup(val passes: List[IRPass])
class PassGroup(val passes: List[IRProcessingPass])

View File

@ -28,6 +28,7 @@ import org.enso.compiler.core.ir.{
}
import org.enso.compiler.core.{CompilerError, IR}
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.alias.graph.Graph
import org.enso.compiler.pass.analyse.alias.graph.GraphOccurrence
import org.enso.compiler.pass.analyse.alias.graph.Graph.Scope
@ -74,15 +75,15 @@ case object AliasAnalysis extends IRPass {
override type Metadata = alias.AliasMetadata
override type Config = Configuration
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
FunctionBinding,
GenerateMethodBodies,
SectionsToBinOp,
SectionsToBinOp.INSTANCE,
OperatorToFunction,
LambdaShorthandToLambda
)
override lazy val invalidatedPasses: Seq[IRPass] =
override lazy val invalidatedPasses: Seq[IRProcessingPass] =
List(DataflowAnalysis, UnusedBindings)
/** Performs alias analysis on a module.

View File

@ -17,6 +17,7 @@ import org.enso.compiler.core.ir.module.scope.definition
import org.enso.compiler.core.ir.expression.{Application, Comment, Error}
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.desugar._
import java.util
@ -38,16 +39,16 @@ case object CachePreferenceAnalysis extends IRPass {
override type Metadata = WeightInfo
/** Run desugaring passes first. */
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
ComplexType,
FunctionBinding,
GenerateMethodBodies,
LambdaShorthandToLambda,
OperatorToFunction,
SectionsToBinOp
SectionsToBinOp.INSTANCE
)
override lazy val invalidatedPasses: Seq[IRPass] = List()
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List()
/** Performs the cache preference analysis on a module.
*

View File

@ -29,6 +29,7 @@ import org.enso.compiler.core.ir.{
}
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.DataflowAnalysis.DependencyInfo.Type.asStatic
import org.enso.compiler.pass.analyse.alias.graph.GraphOccurrence
@ -53,13 +54,13 @@ case object DataflowAnalysis extends IRPass {
override type Metadata = DependencyInfo
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DemandAnalysis,
TailCall
TailCall.INSTANCE
)
override lazy val invalidatedPasses: Seq[IRPass] = List()
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List()
/** Executes the dataflow analysis process on an Enso module.
*

View File

@ -19,6 +19,7 @@ import org.enso.compiler.core.ir.module.scope.definition
import org.enso.compiler.core.ir.expression.Error
import org.enso.compiler.core.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
@ -51,8 +52,10 @@ case object ComplexType extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(ModuleAnnotations)
override lazy val invalidatedPasses: Seq[IRPass] =
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
ModuleAnnotations
)
override lazy val invalidatedPasses: Seq[IRProcessingPass] =
List(
AliasAnalysis,
DataflowAnalysis,
@ -64,8 +67,8 @@ case object ComplexType extends IRPass {
LambdaShorthandToLambda,
NestedPatternMatch,
OperatorToFunction,
SectionsToBinOp,
TailCall,
SectionsToBinOp.INSTANCE,
TailCall.INSTANCE,
UnusedBindings
)

View File

@ -17,6 +17,7 @@ import org.enso.compiler.core.ir.{
import org.enso.compiler.core.ir.MetadataStorage.MetadataPair
import org.enso.compiler.core.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
@ -42,8 +43,8 @@ case object FunctionBinding extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(ComplexType)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(ComplexType)
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
@ -53,8 +54,8 @@ case object FunctionBinding extends IRPass {
LambdaShorthandToLambda,
NestedPatternMatch,
OperatorToFunction,
SectionsToBinOp,
TailCall
SectionsToBinOp.INSTANCE,
TailCall.INSTANCE
)
/** The name of the conversion method, as a reserved name for methods. */

View File

@ -15,6 +15,7 @@ import org.enso.compiler.core.ir.expression.errors
import org.enso.compiler.core.CompilerError
import org.enso.compiler.core.ir.expression.Foreign
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
@ -48,14 +49,14 @@ case object GenerateMethodBodies extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] =
override lazy val precursorPasses: Seq[IRProcessingPass] =
List(ComplexType, FunctionBinding)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DataflowAnalysis,
LambdaConsolidate,
NestedPatternMatch,
TailCall,
TailCall.INSTANCE,
UnusedBindings
)

View File

@ -1,18 +1,7 @@
package org.enso.compiler.pass.desugar
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
import org.enso.compiler.core.ir.{
CallArgument,
DefinitionArgument,
Expression,
Function,
IdentifiedLocation,
Module,
Name
}
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.CompilerError
import org.enso.compiler.core.ir.expression.{Application, Case, Operator}
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
@ -26,412 +15,51 @@ import org.enso.compiler.pass.resolve.{
IgnoredBindings,
OverloadsResolution
}
import org.enso.compiler.pass.{IRProcessingPass, MiniPassFactory}
/** This pass translates `_` arguments at application sites to lambda functions.
*
* This pass has no configuration.
*
* This pass requires the context to provide:
*
* - A [[FreshNameSupply]]
/** Implementation moved to `LambdaShorthandToLambdaMegaPass` test.
*/
case object LambdaShorthandToLambda extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
case object LambdaShorthandToLambda extends MiniPassFactory {
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
ComplexType,
DocumentationComments,
FunctionBinding,
GenerateMethodBodies,
OperatorToFunction,
SectionsToBinOp
SectionsToBinOp.INSTANCE
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
IgnoredBindings,
LambdaConsolidate,
OverloadsResolution,
TailCall,
TailCall.INSTANCE,
UnusedBindings
)
/** Desugars underscore arguments to lambdas for a module.
*
* @param ir the Enso IR to process
* @param moduleContext a context object that contains the information needed
* to process a module
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runModule(
ir: Module,
override def createForModuleCompilation(
moduleContext: ModuleContext
): Module = {
val new_bindings = ir.bindings.map { case a =>
a.mapExpressions(
runExpression(
_,
InlineContext(
moduleContext,
freshNameSupply = moduleContext.freshNameSupply,
compilerConfig = moduleContext.compilerConfig
)
)
): LambdaShorthandToLambdaMini = {
val freshNameSupply = moduleContext.freshNameSupply.getOrElse(
throw new CompilerError(
"Desugaring underscore arguments to lambdas requires a fresh name " +
"supply."
)
}
ir.copy(bindings = new_bindings)
)
new LambdaShorthandToLambdaMini(freshNameSupply)
}
/** Desugars underscore arguments to lambdas for an arbitrary expression.
*
* @param ir the Enso IR to process
* @param inlineContext a context object that contains the information needed
* for inline evaluation
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runExpression(
ir: Expression,
override def createForInlineCompilation(
inlineContext: InlineContext
): Expression = {
): LambdaShorthandToLambdaMini = {
val freshNameSupply = inlineContext.freshNameSupply.getOrElse(
throw new CompilerError(
"Desugaring underscore arguments to lambdas requires a fresh name " +
"supply."
)
)
desugarExpression(ir, freshNameSupply)
}
// === Pass Internals =======================================================
/** Performs lambda shorthand desugaring on an arbitrary expression.
*
* @param ir the expression to desugar
* @param freshNameSupply the compiler's fresh name supply
* @return `ir`, with any lambda shorthand arguments desugared
*/
def desugarExpression(
ir: Expression,
freshNameSupply: FreshNameSupply
): Expression = {
ir.transformExpressions {
case app: Application => desugarApplication(app, freshNameSupply)
case caseExpr: Case.Expr => desugarCaseExpr(caseExpr, freshNameSupply)
case name: Name => desugarName(name, freshNameSupply)
}
}
/** Desugars an arbitrary name occurrence, turning isolated occurrences of
* `_` into the `id` function.
*
* @param name the name to desugar
* @param supply the compiler's fresh name supply
* @return `name`, desugared where necessary
*/
private def desugarName(name: Name, supply: FreshNameSupply): Expression = {
name match {
case blank: Name.Blank =>
val newName = supply.newName()
new Function.Lambda(
List(
DefinitionArgument.Specified(
name = Name.Literal(
newName.name,
isMethod = false,
identifiedLocation = null
),
ascribedType = None,
defaultValue = None,
suspended = false,
identifiedLocation = null
)
),
newName,
blank.identifiedLocation
)
case _ => name
}
}
/** Desugars lambda shorthand arguments to an arbitrary function application.
*
* @param application the function application to desugar
* @param freshNameSupply the compiler's supply of fresh names
* @return `application`, with any lambda shorthand arguments desugared
*/
private def desugarApplication(
application: Application,
freshNameSupply: FreshNameSupply
): Expression = {
application match {
case p @ Application.Prefix(fn, args, _, _, _) =>
// Determine which arguments are lambda shorthand
val argIsUnderscore = determineLambdaShorthand(args)
// Generate a new name for the arg value for each shorthand arg
val updatedArgs =
args
.zip(argIsUnderscore)
.map(updateShorthandArg(_, freshNameSupply))
.map { case s @ CallArgument.Specified(_, value, _, _) =>
s.copy(value = desugarExpression(value, freshNameSupply))
}
// Generate a definition arg instance for each shorthand arg
val defArgs = updatedArgs.zip(argIsUnderscore).map {
case (arg, isShorthand) => generateDefinitionArg(arg, isShorthand)
}
val actualDefArgs = defArgs.collect { case Some(defArg) =>
defArg
}
// Determine whether or not the function itself is shorthand
val functionIsShorthand = fn.isInstanceOf[Name.Blank]
val (updatedFn, updatedName) = if (functionIsShorthand) {
val newFn = freshNameSupply
.newName()
.copy(
location = fn.location,
passData = fn.passData,
diagnostics = fn.diagnostics
)
val newName = newFn.name
(newFn, Some(newName))
} else {
val newFn = desugarExpression(fn, freshNameSupply)
(newFn, None)
}
val processedApp = p.copy(
function = updatedFn,
arguments = updatedArgs
)
// Wrap the app in lambdas from right to left, 1 lambda per shorthand
// arg
val appResult =
actualDefArgs.foldRight(processedApp: Expression)((arg, body) =>
new Function.Lambda(List(arg), body, identifiedLocation = null)
)
// If the function is shorthand, do the same
val resultExpr = if (functionIsShorthand) {
new Function.Lambda(
List(
DefinitionArgument.Specified(
Name.Literal(
updatedName.get,
isMethod = false,
fn.identifiedLocation()
),
None,
None,
suspended = false,
null
)
),
appResult,
identifiedLocation = null
)
} else appResult
resultExpr match {
case lam: Function.Lambda => lam.copy(location = p.location)
case result => result
}
case f @ Application.Force(tgt, _, _) =>
f.copy(target = desugarExpression(tgt, freshNameSupply))
case vector @ Application.Sequence(items, _, _) =>
var bindings: List[Name] = List()
val newItems = items.map {
case blank: Name.Blank =>
val name = freshNameSupply
.newName()
.copy(
location = blank.location,
passData = blank.passData,
diagnostics = blank.diagnostics
)
bindings ::= name
name
case it => desugarExpression(it, freshNameSupply)
}
val newVec = vector.copy(newItems)
val locWithoutId =
if (newVec.identifiedLocation eq null) null
else new IdentifiedLocation(newVec.identifiedLocation.location())
bindings.foldLeft(newVec: Expression) { (body, bindingName) =>
val defArg = DefinitionArgument.Specified(
bindingName,
ascribedType = None,
defaultValue = None,
suspended = false,
identifiedLocation = null
)
new Function.Lambda(List(defArg), body, locWithoutId)
}
case tSet @ Application.Typeset(expr, _, _) =>
tSet.copy(expression = expr.map(desugarExpression(_, freshNameSupply)))
case _: Operator =>
throw new CompilerError(
"Operators should be desugared by the point of underscore " +
"to lambda conversion."
)
}
}
/** Determines, positionally, which of the application arguments are lambda
* shorthand arguments.
*
* @param args the application arguments
* @return a list containing `true` for a given position if the arg in that
* position is lambda shorthand, otherwise `false`
*/
private def determineLambdaShorthand(
args: List[CallArgument]
): List[Boolean] = {
args.map { case CallArgument.Specified(_, value, _, _) =>
value match {
case _: Name.Blank => true
case _ => false
}
}
}
/** Generates a new name to replace a shorthand argument, as well as the
* corresponding definition argument.
*
* @param argAndIsShorthand the arguments, and whether or not the argument in
* the corresponding position is shorthand
* @return the above described pair for a given position if the argument in
* a given position is shorthand, otherwise [[None]].
*/
private def updateShorthandArg(
argAndIsShorthand: (CallArgument, Boolean),
freshNameSupply: FreshNameSupply
): CallArgument = {
val arg = argAndIsShorthand._1
val isShorthand = argAndIsShorthand._2
arg match {
case s @ CallArgument.Specified(_, value, _, _) =>
if (isShorthand) {
val newName = freshNameSupply
.newName()
.copy(
location = value.location,
passData = value.passData,
diagnostics = value.diagnostics
)
s.copy(value = newName)
} else s
}
}
/** Generates a corresponding definition argument to a call argument that was
* previously lambda shorthand.
*
* @param arg the argument to generate a corresponding def argument to
* @param isShorthand whether or not `arg` was shorthand
* @return a corresponding definition argument if `arg` `isShorthand`,
* otherwise [[None]]
*/
private def generateDefinitionArg(
arg: CallArgument,
isShorthand: Boolean
): Option[DefinitionArgument] = {
if (isShorthand) {
arg match {
case specified @ CallArgument.Specified(_, value, _, passData) =>
// Note [Safe Casting to Name.Literal]
val defArgName =
Name.Literal(
value.asInstanceOf[Name.Literal].name,
isMethod = false,
identifiedLocation = null
)
Some(
new DefinitionArgument.Specified(
defArgName,
None,
None,
suspended = false,
null,
passData.duplicate,
specified.diagnosticsCopy
)
)
}
} else None
}
/* Note [Safe Casting to Name.Literal]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* This cast is entirely safe here as, by construction in
* `updateShorthandArg`, any arg for which `isShorthand` is true has its
* value as an `Name.Literal`.
*/
/** Performs desugaring of lambda shorthand arguments in a case expression.
*
* In the case where a user writes `case _ of`, this gets converted into a
* lambda (`x -> case x of`).
*
* @param caseExpr the case expression to desugar
* @param freshNameSupply the compiler's supply of fresh names
* @return `caseExpr`, with any lambda shorthand desugared
*/
private def desugarCaseExpr(
caseExpr: Case.Expr,
freshNameSupply: FreshNameSupply
): Expression = {
val newBranches = caseExpr.branches.map(
_.mapExpressions(expr => desugarExpression(expr, freshNameSupply))
)
caseExpr.scrutinee match {
case nameBlank: Name.Blank =>
val scrutineeName =
freshNameSupply
.newName()
.copy(
location = nameBlank.location,
passData = nameBlank.passData,
diagnostics = nameBlank.diagnostics
)
val lambdaArg = DefinitionArgument.Specified(
scrutineeName.copy(id = null),
None,
None,
suspended = false,
null
)
val newCaseExpr = caseExpr.copy(
scrutinee = scrutineeName,
branches = newBranches
)
new Function.Lambda(
caseExpr,
List(lambdaArg),
newCaseExpr,
caseExpr.identifiedLocation
)
case x =>
caseExpr.copy(
scrutinee = desugarExpression(x, freshNameSupply),
branches = newBranches
)
}
new LambdaShorthandToLambdaMini(freshNameSupply)
}
}

View File

@ -0,0 +1,352 @@
package org.enso.compiler.pass.desugar
import org.enso.compiler.context.FreshNameSupply
import org.enso.compiler.core.CompilerError
import org.enso.compiler.core.IR
import org.enso.compiler.core.ir.expression.{Application, Case, Operator}
import org.enso.compiler.core.ir.{
CallArgument,
DefinitionArgument,
Expression,
Function,
IdentifiedLocation,
Name
}
import org.enso.compiler.pass.MiniIRPass
class LambdaShorthandToLambdaMini(
protected val freshNameSupply: FreshNameSupply,
private val shouldSkipBlanks: Boolean = false
) extends MiniIRPass {
override def prepare(
parent: IR,
current: Expression
): LambdaShorthandToLambdaMini = {
if (shouldSkipBlanks(parent)) {
new LambdaShorthandToLambdaMini(freshNameSupply, true)
} else {
this
}
}
private def shouldSkipBlanks(parent: IR): Boolean = {
parent match {
case Application.Prefix(fn, args, _, _, _) =>
val hasBlankArg = args.exists {
case CallArgument.Specified(_, _: Name.Blank, _, _) => true
case _ => false
}
val hasBlankFn = fn.isInstanceOf[Name.Blank]
hasBlankArg || hasBlankFn
case Application.Sequence(items, _, _) =>
val hasBlankItem = items.exists {
case _: Name.Blank => true
case _ => false
}
hasBlankItem
case Case.Expr(_: Name.Blank, _, _, _, _) =>
true
case _ => false
}
}
override def transformExpression(ir: Expression): Expression = {
val newIr = ir match {
case name: Name => desugarName(name)
case app: Application => desugarApplication(app)
case caseExpr: Case.Expr => desugarCaseExpr(caseExpr)
case _ => ir
}
newIr
}
/** Desugars an arbitrary name occurrence, turning isolated occurrences of
* `_` into the `id` function.
*
* @param name the name to desugar
* @return `name`, desugared where necessary
*/
private def desugarName(name: Name): Expression = {
name match {
case blank: Name.Blank if !shouldSkipBlanks =>
val newName = freshNameSupply.newName()
new Function.Lambda(
List(
DefinitionArgument.Specified(
name = Name.Literal(
newName.name,
isMethod = false,
null
),
ascribedType = None,
defaultValue = None,
suspended = false,
identifiedLocation = null
)
),
newName,
blank.location.orNull
)
case _ => name
}
}
/** Desugars lambda shorthand arguments to an arbitrary function application.
*
* @param application the function application to desugar
* @return `application`, with any lambda shorthand arguments desugared
*/
private def desugarApplication(
application: Application
): Expression = {
application match {
case p @ Application.Prefix(fn, args, _, _, _) =>
// Determine which arguments are lambda shorthand
val argIsUnderscore = determineLambdaShorthand(args)
// Generate a new name for the arg value for each shorthand arg
val updatedArgs =
args
.zip(argIsUnderscore)
.map(updateShorthandArg)
// Generate a definition arg instance for each shorthand arg
val defArgs = updatedArgs.zip(argIsUnderscore).map {
case (arg, isShorthand) => generateDefinitionArg(arg, isShorthand)
}
val actualDefArgs = defArgs.collect { case Some(defArg) =>
defArg
}
// Determine whether or not the function itself is shorthand
val functionIsShorthand = fn.isInstanceOf[Name.Blank]
val (updatedFn, updatedName) = if (functionIsShorthand) {
val newFn = freshNameSupply
.newName()
.copy(
location = fn.location,
passData = fn.passData,
diagnostics = fn.diagnostics
)
val newName = newFn.name
(newFn, Some(newName))
} else {
(fn, None)
}
val processedApp = p.copy(
function = updatedFn,
arguments = updatedArgs
)
// Wrap the app in lambdas from right to left, 1 lambda per shorthand
// arg
val appResult =
actualDefArgs.foldRight(processedApp: Expression)((arg, body) =>
new Function.Lambda(List(arg), body, null)
)
// If the function is shorthand, do the same
val resultExpr = if (functionIsShorthand) {
new Function.Lambda(
List(
DefinitionArgument.Specified(
Name
.Literal(
updatedName.get,
isMethod = false,
fn.location.orNull
),
None,
None,
suspended = false,
null
)
),
appResult,
null
)
} else appResult
resultExpr match {
case lam: Function.Lambda => lam.copy(location = p.location)
case result => result
}
case vector @ Application.Sequence(items, _, _) =>
var bindings: List[Name] = List()
val newItems = items.map {
case blank: Name.Blank =>
val name = freshNameSupply
.newName()
.copy(
location = blank.location,
passData = blank.passData,
diagnostics = blank.diagnostics
)
bindings ::= name
name
case it => it
}
val newVec = vector.copy(newItems)
val locWithoutId =
newVec.location.map(l => new IdentifiedLocation(l.location()))
bindings.foldLeft(newVec: Expression) { (body, bindingName) =>
val defArg = DefinitionArgument.Specified(
bindingName,
ascribedType = None,
defaultValue = None,
suspended = false,
identifiedLocation = null
)
new Function.Lambda(List(defArg), body, locWithoutId.orNull)
}
case _: Operator =>
throw new CompilerError(
"Operators should be desugared by the point of underscore " +
"to lambda conversion."
)
}
}
/** Determines, positionally, which of the application arguments are lambda
* shorthand arguments.
*
* @param args the application arguments
* @return a list containing `true` for a given position if the arg in that
* position is lambda shorthand, otherwise `false`
*/
private def determineLambdaShorthand(
args: List[CallArgument]
): List[Boolean] = {
args.map { case CallArgument.Specified(_, value, _, _) =>
value match {
case _: Name.Blank => true
case _ => false
}
}
}
/** Generates a new name to replace a shorthand argument, as well as the
* corresponding definition argument.
*
* @param argAndIsShorthand the arguments, and whether or not the argument in
* the corresponding position is shorthand
* @return the above described pair for a given position if the argument in
* a given position is shorthand, otherwise [[None]].
*/
private def updateShorthandArg(
argAndIsShorthand: (CallArgument, Boolean)
): CallArgument = {
val arg = argAndIsShorthand._1
val isShorthand = argAndIsShorthand._2
arg match {
case s @ CallArgument.Specified(_, value, _, _) =>
if (isShorthand) {
val newName = freshNameSupply
.newName()
.copy(
location = value.location,
passData = value.passData,
diagnostics = value.diagnostics
)
s.copy(value = newName)
} else s
}
}
/** Generates a corresponding definition argument to a call argument that was
* previously lambda shorthand.
*
* @param arg the argument to generate a corresponding def argument to
* @param isShorthand whether or not `arg` was shorthand
* @return a corresponding definition argument if `arg` `isShorthand`,
* otherwise [[None]]
*/
private def generateDefinitionArg(
arg: CallArgument,
isShorthand: Boolean
): Option[DefinitionArgument] = {
if (isShorthand) {
arg match {
case specified @ CallArgument.Specified(_, value, _, passData) =>
// Note [Safe Casting to Name.Literal]
val defArgName =
Name.Literal(
value.asInstanceOf[Name.Literal].name,
isMethod = false,
null
)
Some(
new DefinitionArgument.Specified(
defArgName,
None,
None,
suspended = false,
null,
passData.duplicate,
specified.diagnosticsCopy
)
)
}
} else None
}
/* Note [Safe Casting to Name.Literal]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* This cast is entirely safe here as, by construction in
* `updateShorthandArg`, any arg for which `isShorthand` is true has its
* value as an `Name.Literal`.
*/
/** Performs desugaring of lambda shorthand arguments in a case expression.
*
* In the case where a user writes `case _ of`, this gets converted into a
* lambda (`x -> case x of`).
*
* @param caseExpr the case expression to desugar
* @return `caseExpr`, with any lambda shorthand desugared
*/
private def desugarCaseExpr(
caseExpr: Case.Expr
): Expression = {
caseExpr.scrutinee match {
case nameBlank: Name.Blank =>
val scrutineeName =
freshNameSupply
.newName()
.copy(
location = nameBlank.location,
passData = nameBlank.passData,
diagnostics = nameBlank.diagnostics
)
val lambdaArg = DefinitionArgument.Specified(
scrutineeName.copy(id = null),
None,
None,
suspended = false,
null
)
val newCaseExpr = caseExpr.copy(
scrutinee = scrutineeName
)
new Function.Lambda(
caseExpr,
List(lambdaArg),
newCaseExpr,
caseExpr.location.orNull
)
case _ => caseExpr
}
}
}

View File

@ -11,6 +11,7 @@ import org.enso.compiler.core.ir.{
import org.enso.compiler.core.ir.expression.{errors, Case}
import org.enso.compiler.core.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
@ -73,19 +74,19 @@ case object NestedPatternMatch extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
ComplexType,
DocumentationComments,
FunctionBinding,
GenerateMethodBodies,
LambdaShorthandToLambda
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
IgnoredBindings,
TailCall
TailCall.INSTANCE
)
/** Desugars nested pattern matches in a module.

View File

@ -1,86 +1,35 @@
package org.enso.compiler.pass.desugar
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.ir.expression.{Application, Operator}
import org.enso.compiler.core.ir.{Expression, Module}
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.{IRProcessingPass, MiniPassFactory}
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis
}
/** This pass converts usages of operators to calls to standard functions.
*
* This pass requires the context to provide:
*
* - Nothing
/** Implementation moved to `OperatorToFunctionTest`
*/
case object OperatorToFunction extends IRPass {
case object OperatorToFunction extends MiniPassFactory {
/** A purely desugaring pass has no analysis output. */
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
GenerateMethodBodies,
SectionsToBinOp
SectionsToBinOp.INSTANCE
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis
DemandAnalysis,
LambdaShorthandToLambda
)
/** Executes the conversion pass.
*
* @param ir the Enso IR to process
* @param moduleContext a context object that contains the information needed
* to process a module
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runModule(
ir: Module,
override def createForModuleCompilation(
moduleContext: ModuleContext
): Module = {
val new_bindings = ir.bindings.map { a =>
a.mapExpressions(
runExpression(
_,
new InlineContext(
moduleContext,
compilerConfig = moduleContext.compilerConfig
)
)
)
}
ir.copy(bindings = new_bindings)
}
): OperatorToFunctionMini =
new OperatorToFunctionMini()
/** Executes the conversion pass in an inline context.
*
* @param ir the Enso IR to process
* @param inlineContext a context object that contains the information needed
* for inline evaluation
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runExpression(
ir: Expression,
override def createForInlineCompilation(
inlineContext: InlineContext
): Expression =
ir.transformExpressions { case operatorBinary: Operator.Binary =>
new Application.Prefix(
operatorBinary.operator,
List(
operatorBinary.left.mapExpressions(runExpression(_, inlineContext)),
operatorBinary.right.mapExpressions(runExpression(_, inlineContext))
),
hasDefaultsSuspended = false,
operatorBinary.identifiedLocation,
operatorBinary.passData,
operatorBinary.diagnostics
)
}
): OperatorToFunctionMini =
new OperatorToFunctionMini()
}

View File

@ -7,6 +7,7 @@ import org.enso.compiler.core.ir.{Expression, Module, Name, Pattern}
import org.enso.compiler.core.ir.expression.{errors, warnings, Case}
import org.enso.compiler.core.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
@ -34,16 +35,16 @@ case object ShadowedPatternFields extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
GenerateMethodBodies
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
IgnoredBindings,
NestedPatternMatch,
TailCall
TailCall.INSTANCE
)
/** Lints for shadowed pattern fields.

View File

@ -15,6 +15,7 @@ import org.enso.compiler.core.ir.{
import org.enso.compiler.core.ir.expression.{errors, warnings, Case, Foreign}
import org.enso.compiler.core.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.AliasAnalysis
import org.enso.compiler.pass.analyse.alias.{AliasMetadata => AliasInfo}
import org.enso.compiler.pass.desugar._
@ -32,7 +33,7 @@ case object UnusedBindings extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
ComplexType,
GenerateMethodBodies,
IgnoredBindings,
@ -40,9 +41,9 @@ case object UnusedBindings extends IRPass {
LambdaShorthandToLambda,
NestedPatternMatch,
OperatorToFunction,
SectionsToBinOp
SectionsToBinOp.INSTANCE
)
override lazy val invalidatedPasses: Seq[IRPass] = List()
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List()
/** Lints a module.
*

View File

@ -15,6 +15,7 @@ import org.enso.compiler.core.ir.{
import org.enso.compiler.core.ir.expression.warnings
import org.enso.compiler.core.ir.expression.errors
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.alias.graph.{
GraphOccurrence,
Graph => AliasGraph
@ -63,7 +64,7 @@ case object LambdaConsolidate extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
ComplexType,
FunctionBinding,
@ -71,13 +72,13 @@ case object LambdaConsolidate extends IRPass {
IgnoredBindings,
LambdaShorthandToLambda,
OperatorToFunction,
SectionsToBinOp
SectionsToBinOp.INSTANCE
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall
TailCall.INSTANCE
)
/** Performs lambda consolidation on a module.

View File

@ -11,6 +11,7 @@ import org.enso.compiler.core.ir.{
import org.enso.compiler.core.ir.expression.{errors, warnings, Case}
import org.enso.compiler.core.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
@ -46,20 +47,20 @@ case object UnreachableMatchBranches extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
ComplexType,
DocumentationComments,
FunctionBinding,
GenerateMethodBodies,
LambdaShorthandToLambda
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
IgnoredBindings,
NestedPatternMatch,
TailCall
TailCall.INSTANCE
)
/** Runs unreachable branch optimisation on a module.

View File

@ -15,6 +15,7 @@ import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.core.ir.expression.{errors, Case}
import org.enso.compiler.core.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
@ -42,17 +43,17 @@ case object IgnoredBindings extends IRPass {
override type Metadata = State
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
ComplexType,
GenerateMethodBodies,
LambdaShorthandToLambda,
NestedPatternMatch
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall
TailCall.INSTANCE
)
/** Desugars ignored bindings for a module.

View File

@ -16,6 +16,7 @@ import org.enso.compiler.core.ir.module.scope.Definition
import org.enso.compiler.core.ir.module.scope.definition
import org.enso.compiler.core.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse._
import org.enso.compiler.pass.desugar.ComplexType
import org.enso.compiler.pass.lint.UnusedBindings
@ -51,18 +52,18 @@ case object SuspendedArguments extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
ComplexType,
TypeSignatures,
LambdaConsolidate
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
CachePreferenceAnalysis,
DataflowAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall,
TailCall.INSTANCE,
UnusedBindings
)

View File

@ -17,6 +17,7 @@ import org.enso.compiler.core.ir.expression.Error
import org.enso.compiler.core.CompilerError
import org.enso.compiler.core.ir.expression.{Application, Operator}
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse._
import org.enso.compiler.pass.desugar.{
LambdaShorthandToLambda,
@ -38,19 +39,19 @@ case object TypeFunctions extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
IgnoredBindings,
LambdaShorthandToLambda,
OperatorToFunction,
SectionsToBinOp
SectionsToBinOp.INSTANCE
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
CachePreferenceAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall,
org.enso.compiler.pass.analyse.TailCall.INSTANCE,
UnusedBindings
)

View File

@ -16,6 +16,7 @@ import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.core.ir.expression.{errors, Comment, Error}
import org.enso.compiler.core.{CompilerError, IR}
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse._
import org.enso.compiler.pass.lint.UnusedBindings
@ -36,16 +37,16 @@ case object TypeSignatures extends IRPass {
override type Metadata = Signature
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
TypeFunctions,
ModuleAnnotations
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
CachePreferenceAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall,
org.enso.compiler.pass.analyse.TailCall.INSTANCE,
UnusedBindings
)

View File

@ -1,7 +1,7 @@
package org.enso.compiler.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
@ -19,15 +19,20 @@ public abstract class CompilerTests {
return ir;
}
public static void assertIR(String msg, Module old, Module now) throws IOException {
public static void assertIR(String msg, IR old, IR now) throws IOException {
assertEqualsIR(msg, null, old, now);
}
public static void assertEqualsIR(String msg, String testName, IR old, IR now)
throws IOException {
Function<IR, String> filter = f -> simplifyIR(f, true, true, false);
String ir1 = filter.apply(old);
String ir2 = filter.apply(now);
if (!ir1.equals(ir2)) {
String name = findTestMethodName();
var home = new File(System.getProperty("java.io.tmpdir")).toPath();
var file1 = home.resolve(name + ".1");
var file2 = home.resolve(name + ".2");
var fname = testName == null ? findTestMethodName() : testName;
var file1 = home.resolve(fname + ".1");
var file2 = home.resolve(fname + ".2");
Files.writeString(
file1,
ir1,
@ -40,17 +45,19 @@ public abstract class CompilerTests {
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE);
assertEquals(msg, file1, file2);
fail("IRs contained in files " + file1 + " and " + file2 + " should equal: " + msg);
}
}
private static int counter = 'A';
private static String findTestMethodName() {
for (var e : new Exception().getStackTrace()) {
if (e.getMethodName().startsWith("test")) {
return e.getMethodName();
}
}
throw new IllegalStateException();
return "test" + (char) counter++;
}
/**

View File

@ -0,0 +1,63 @@
package org.enso.compiler.test.mini.passes;
import java.io.IOException;
import org.enso.compiler.PackageRepository;
import org.enso.compiler.context.FreshNameSupply;
import org.enso.compiler.context.ModuleContext;
import org.enso.compiler.core.EnsoParser;
import org.enso.compiler.core.ir.Module;
import org.enso.compiler.data.CompilerConfig;
import org.enso.compiler.pass.IRPass;
import org.enso.compiler.pass.MiniIRPass;
import org.enso.compiler.pass.MiniPassFactory;
import org.enso.compiler.pass.PassConfiguration;
import org.enso.compiler.test.CompilerTests;
import org.enso.pkg.QualifiedName;
import scala.Option;
/**
* A tester class that asserts that a {@link MiniIRPass} has the same result as its corresponding
* {@link IRPass} in a module compilation.
*/
public abstract class MiniPassTester {
protected void compareModuleCompilation(
Module ir, ModuleContext moduleContext, IRPass megaPass, MiniPassFactory miniPassFactory) {
var miniPass = miniPassFactory.createForModuleCompilation(moduleContext);
if (miniPass == null) {
throw new IllegalArgumentException("Mini pass does not support module compilation");
}
var megaPassResult = megaPass.runModule(ir, moduleContext);
var miniPassResult = MiniIRPass.compile(Module.class, ir, miniPass);
try {
CompilerTests.assertIR(
"Mini pass and mega pass results are equal", megaPassResult, miniPassResult);
} catch (IOException e) {
throw new AssertionError(e);
}
}
protected Module parse(String code) {
var modIr = EnsoParser.compile(code);
return modIr;
}
protected ModuleContext buildModuleContext(QualifiedName moduleName) {
var compilerConf = defaultCompilerConfig();
Option<FreshNameSupply> freshNameSupply = Option.empty();
Option<PassConfiguration> passConfig = Option.empty();
Option<PackageRepository> pkgRepo = Option.empty();
var isGeneratingDocs = false;
var runtimeMod = org.enso.interpreter.runtime.Module.empty(moduleName, null);
return ModuleContext.apply(
runtimeMod.asCompilerModule(),
compilerConf,
freshNameSupply,
passConfig,
isGeneratingDocs,
pkgRepo);
}
private static CompilerConfig defaultCompilerConfig() {
return CompilerConfig.apply(false, true, true, false, false, false, false, Option.empty());
}
}

View File

@ -0,0 +1,134 @@
package org.enso.compiler.test
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.EnsoParser
import org.enso.compiler.core.ir.{Expression, Module}
import org.enso.compiler.pass.{IRPass, MiniIRPass, MiniPassFactory, PassManager}
trait MiniPassTest extends CompilerTest {
def testName: String
/** Configuration for mini pass
*/
def miniPassFactory: MiniPassFactory
def megaPass: IRPass
/** Configuration for mega pass
*/
def megaPassManager: PassManager
/** Tests module compilation in both mega pass and mini pass.
* @param code Source code of the whole module to compile.
* @param createContext Function that creates module context. For both mega pass and minipass,
* there will be a new context created.
* @param testSpec Body of the test. Receives module compiled either by mega pass or by mini pass.
*/
def assertModuleCompilation(
code: String,
createContext: () => ModuleContext,
testSpec: Module => Unit,
compareIR: Boolean = false
): Unit = {
val megaIr = withClue("Mega pass module compilation") {
val ctx = createContext()
processModuleWithMegaPass(code, ctx)
}
val miniIr = withClue("Mini pass module compilation") {
val ctx = createContext()
processModuleWithMiniPass(code, ctx)
}
if (compareIR) {
CompilerTests.assertIR("Should be the same", megaIr, miniIr)
}
withClue("Mega pass module spec execution") {
testSpec(megaIr)
}
withClue("Mini pass module spec execution") {
testSpec(miniIr)
}
}
/** Tests inline compilation in both mega pass and mini pass.
* @param code Source code to compile.
* @param createContext Function that creates inline context. For both mega pass and minipass,
* there will be a new context created.
* @param testSpec Body of the test. Receives expression compiled either by mega pass or by mini pass.
*/
def assertInlineCompilation(
code: String,
createContext: () => InlineContext,
testSpec: Expression => Unit,
compareIR: Boolean = false
): Unit = {
val megaIr = withClue("Mega pass inline compilation: ") {
val ctx = createContext()
preprocessExpressionWithMegaPass(code, ctx)
}
val miniIr = withClue("Mini pass inline compilation: ") {
val ctx = createContext()
preprocessExpressionWithMiniPass(code, ctx)
}
if (compareIR) {
CompilerTests.assertIR("Should be the same", megaIr, miniIr)
}
withClue("Mega pass inline spec execution") {
testSpec(megaIr)
}
withClue("Mini pass inline spec execution") {
testSpec(miniIr)
}
}
private def processModuleWithMegaPass(
source: String,
moduleCtx: ModuleContext
): Module = {
val module = parseModule(source)
val preprocessedModule =
megaPassManager.runPassesOnModule(module, moduleCtx)
megaPass.runModule(preprocessedModule, moduleCtx)
}
private def processModuleWithMiniPass(
source: String,
moduleCtx: ModuleContext
): Module = {
val module = parseModule(source)
val miniPass = miniPassFactory.createForModuleCompilation(moduleCtx)
val preprocessedModule =
megaPassManager.runPassesOnModule(module, moduleCtx)
MiniIRPass.compile(classOf[Module], preprocessedModule, miniPass)
}
def preprocessExpressionWithMegaPass(
expression: String,
inlineCtx: InlineContext
): Expression = {
val expr = parseExpression(expression)
val preprocessedExpr =
megaPassManager.runPassesInline(expr, inlineCtx)
megaPass.runExpression(preprocessedExpr, inlineCtx)
}
def preprocessExpressionWithMiniPass(
expression: String,
inlineCtx: InlineContext
): Expression = {
val expr = parseExpression(expression)
val miniPass = miniPassFactory.createForInlineCompilation(inlineCtx)
val preprocessedExpr =
megaPassManager.runPassesInline(expr, inlineCtx)
MiniIRPass.compile(classOf[Expression], preprocessedExpr, miniPass)
}
private def parseModule(source: String): Module = {
EnsoParser.compile(source)
}
private def parseExpression(source: String): Expression = {
val exprIrOpt = EnsoParser.compileInline(source)
exprIrOpt shouldBe defined
exprIrOpt.get
}
}

View File

@ -61,7 +61,7 @@ class PassesTest extends CompilerTest {
BindingAnalysis,
ModuleNameConflicts,
MethodDefinitions,
SectionsToBinOp,
SectionsToBinOp.INSTANCE,
OperatorToFunction,
LambdaShorthandToLambda,
ImportSymbolAnalysis,

View File

@ -3,7 +3,7 @@ package org.enso.compiler.test.pass
import org.enso.compiler.Passes
import org.enso.compiler.core.CompilerError
import org.enso.compiler.pass.{
IRPass,
IRProcessingPass,
PassConfiguration,
PassGroup,
PassManager
@ -24,11 +24,11 @@ class PassManagerTest extends CompilerTest {
// === Test Setup ===========================================================
val invalidOrdering: List[IRPass] = List(
val invalidOrdering: List[IRProcessingPass] = List(
ComplexType,
FunctionBinding,
GenerateMethodBodies,
SectionsToBinOp,
SectionsToBinOp.INSTANCE,
OperatorToFunction,
LambdaShorthandToLambda,
IgnoredBindings,
@ -36,7 +36,7 @@ class PassManagerTest extends CompilerTest {
LambdaConsolidate,
OverloadsResolution,
DemandAnalysis,
TailCall,
TailCall.INSTANCE,
AliasAnalysis,
DataflowAnalysis,
UnusedBindings

View File

@ -1,4 +1,4 @@
package org.enso.compiler.pass.analyse
package org.enso.compiler.test.pass.analyse
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.Implicits.{AsDiagnostics, AsMetadata}
@ -28,40 +28,45 @@ import org.enso.compiler.core.ir.{
}
import org.enso.compiler.core.{CompilerError, IR}
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse.TailCall
import org.enso.compiler.pass.analyse.TailCall.TailPosition
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.resolve.{ExpressionAnnotations, GlobalNames}
/** This pass performs tail call analysis on the Enso IR.
/** Original implementation of [[org.enso.compiler.pass.analyse.TailCall]].
* Now server as the test verification of the new [[org.enso.compiler.pass.analyse.TailCallMini]].
*
* This pass performs tail call analysis on the Enso IR.
*
* It is responsible for marking every single expression with whether it is in
* tail position. This allows the code generator to correctly create the
* tail position or not. This allows the code generator to correctly create the
* Truffle nodes.
* If the expression is in tail position, [[TailPosition.Tail]] metadata is attached
* to it, otherwise, nothing is attached.
*
* This pass requires the context to provide:
*
* - The tail position of its expression, where relevant.
*/
case object TailCall extends IRPass {
case object TailCallMegaPass extends IRPass {
/** The annotation metadata type associated with IR nodes by this pass. */
override type Metadata = TailPosition
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
FunctionBinding,
GenerateMethodBodies,
SectionsToBinOp,
SectionsToBinOp.INSTANCE,
OperatorToFunction,
LambdaShorthandToLambda,
GlobalNames
)
override lazy val invalidatedPasses: Seq[IRPass] = List()
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List()
private lazy val TAIL_META = new MetadataPair(this, TailPosition.Tail)
private lazy val TAIL_META =
new MetadataPair(TailCall.INSTANCE, TailPosition.Tail)
/** Analyses tail call state for expressions in a module.
*
@ -481,42 +486,6 @@ case object TailCall extends IRPass {
}
}
/** Expresses the tail call state of an IR Node. */
sealed trait TailPosition extends IRPass.IRMetadata {
/** A boolean representation of the expression's tail state. */
def isTail: Boolean
}
object TailPosition {
/** The expression is in a tail position and can be tail call optimised.
* If the expression is not in tail-call position, it has no metadata attached.
*/
final case object Tail extends TailPosition {
override val metadataName: String = "TailCall.TailPosition.Tail"
override def isTail: Boolean = true
override def duplicate(): Option[IRPass.IRMetadata] = Some(Tail)
/** @inheritdoc */
override def prepareForSerialization(compiler: Compiler): Tail.type = this
/** @inheritdoc */
override def restoreFromSerialization(
compiler: Compiler
): Option[Tail.type] = Some(this)
}
/** Implicitly converts the tail position data into a boolean.
*
* @param tailPosition the tail position value
* @return the boolean value corresponding to `tailPosition`
*/
implicit def toBool(tailPosition: TailPosition): Boolean = {
tailPosition.isTail
}
}
/** Checks if the provided `expression` is annotated with a tail call
* annotation.
*

View File

@ -3,24 +3,34 @@ package org.enso.compiler.test.pass.analyse
import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
import org.enso.compiler.core.Implicits.AsMetadata
import org.enso.compiler.core.ir.{
Expression,
Function,
Module,
Pattern,
Warning
}
import org.enso.compiler.core.ir.{Expression, Function, Pattern, Warning}
import org.enso.compiler.core.ir.module.scope.definition
import org.enso.compiler.core.ir.expression.Application
import org.enso.compiler.core.ir.expression.Case
import org.enso.compiler.pass.PassConfiguration._
import org.enso.compiler.pass.analyse.TailCall.TailPosition
import org.enso.compiler.pass.analyse.{AliasAnalysis, TailCall}
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
import org.enso.compiler.pass.{
IRPass,
MiniPassFactory,
PassConfiguration,
PassGroup,
PassManager
}
import org.enso.compiler.test.MiniPassTest
import org.enso.compiler.context.LocalScope
class TailCallTest extends CompilerTest {
class TailCallTest extends MiniPassTest {
override def testName: String = "Tail call"
override def miniPassFactory: MiniPassFactory = {
TailCall.INSTANCE
}
override def megaPass: IRPass = TailCallMegaPass
override def megaPassManager: PassManager =
new PassManager(List(precursorPasses), passConfiguration)
// === Test Setup ===========================================================
@ -45,53 +55,16 @@ class TailCallTest extends CompilerTest {
val passes = new Passes(defaultConfig)
val precursorPasses: PassGroup = passes.getPrecursors(TailCall).get
val precursorPasses: PassGroup = passes.getPrecursors(TailCall.INSTANCE).get
val passConfiguration: PassConfiguration = PassConfiguration(
AliasAnalysis -->> AliasAnalysis.Configuration()
)
implicit val passManager: PassManager =
new PassManager(List(precursorPasses), passConfiguration)
/** Adds an extension method to analyse an Enso module.
*
* @param ir the ir to analyse
*/
implicit class AnalyseModule(ir: Module) {
/** Performs tail call analysis on [[ir]].
*
* @param context the module context in which analysis takes place
* @return [[ir]], with tail call analysis metadata attached
*/
def analyse(implicit context: ModuleContext) = {
TailCall.runModule(ir, context)
}
}
/** Adds an extension method to preprocess source code as an Enso expression.
*
* @param ir the ir to analyse
*/
implicit class AnalyseExpresion(ir: Expression) {
/** Performs tail call analysis on [[ir]].
*
* @param context the inline context in which analysis takes place
* @return [[ir]], with tail call analysis metadata attached
*/
def analyse(implicit context: InlineContext): Expression = {
TailCall.runExpression(ir, context)
}
}
// === The Tests ============================================================
"Tail call analysis on modules" should {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
val code =
"""
|Foo.bar = a -> b -> c ->
| d = a + b
@ -103,18 +76,42 @@ class TailCallTest extends CompilerTest {
|type MyAtom a b c
|
|Foo.from (that : Bar) = undefined
|""".stripMargin.preprocessModule.analyse
|""".stripMargin
"mark methods as tail" in {
ir.bindings.head.getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
assertModuleCompilation(
code,
() => mkModuleContext,
ir => {
ir.bindings.head.getMetadata(TailCall.INSTANCE) shouldEqual Some(
TailPosition.Tail
)
}
)
}
"mark atoms as tail" in {
ir.bindings(1).getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
assertModuleCompilation(
code,
() => mkModuleContext,
ir => {
ir.bindings(1).getMetadata(TailCall.INSTANCE) shouldEqual Some(
TailPosition.Tail
)
}
)
}
"mark conversions as tail" in {
ir.bindings(2).getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
assertModuleCompilation(
code,
() => mkModuleContext,
ir => {
ir.bindings(2).getMetadata(TailCall.INSTANCE) shouldEqual Some(
TailPosition.Tail
)
}
)
}
}
@ -125,305 +122,435 @@ class TailCallTest extends CompilerTest {
|""".stripMargin
"mark the expression as tail if the context requires it" in {
implicit val ctx: InlineContext = mkTailContext
val ir = code.preprocessExpression.get.analyse
ir.getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
assertInlineCompilation(
code,
() => mkTailContext,
ir => {
ir.getMetadata(TailCall.INSTANCE) shouldEqual Some(TailPosition.Tail)
},
true
)
}
"not mark the expression as tail if the context doesn't require it" in {
implicit val ctx: InlineContext = mkNoTailContext
val ir = code.preprocessExpression.get.analyse
ir.getMetadata(TailCall) shouldEqual None
assertInlineCompilation(
code,
() => mkNoTailContext,
ir => {
ir.getMetadata(TailCall.INSTANCE) shouldEqual None
}
)
}
"mark the value of a tail assignment as non-tail" in {
implicit val ctx: InlineContext = mkTailContext
val binding =
assertInlineCompilation(
"""
|foo = a b
|""".stripMargin.preprocessExpression.get.analyse
.asInstanceOf[Expression.Binding]
binding.getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
binding.expression.getMetadata(TailCall) shouldEqual None
|""".stripMargin,
() => mkTailContext,
ir => {
val binding = ir.asInstanceOf[Expression.Binding]
binding.getMetadata(TailCall.INSTANCE) shouldEqual Some(
TailPosition.Tail
)
binding.expression.getMetadata(TailCall.INSTANCE) shouldEqual None
},
true
)
}
}
"Tail call analysis on functions" should {
implicit val ctx: InlineContext = mkTailContext
val ir =
val code =
"""
|a -> b -> c ->
| d = @Tail_Call (a + b)
| e = a * c
| @Tail_Call (d + e)
|""".stripMargin.preprocessExpression.get.analyse
.asInstanceOf[Function.Lambda]
val fnBody = ir.body.asInstanceOf[Expression.Block]
|""".stripMargin
"mark the last expression of the function as tail" in {
fnBody.returnValue.getMetadata(TailCall) shouldEqual Some(
TailPosition.Tail
assertInlineCompilation(
code,
() => mkTailContext,
ir => {
val lambda = ir.asInstanceOf[Function.Lambda]
val fnBody = lambda.body.asInstanceOf[Expression.Block]
fnBody.returnValue.getMetadata(TailCall.INSTANCE) shouldEqual Some(
TailPosition.Tail
)
}
)
}
"mark the other expressions in the function as not tail" in {
fnBody.expressions.foreach(expr =>
expr.getMetadata(TailCall) shouldEqual None
assertInlineCompilation(
code,
() => mkTailContext,
ir => {
val lambda = ir.asInstanceOf[Function.Lambda]
val fnBody = lambda.body.asInstanceOf[Expression.Block]
fnBody.expressions.foreach { expr =>
expr.getMetadata(TailCall.INSTANCE) shouldEqual None
}
}
)
}
"warn about misplaced @TailCall annotations" in {
fnBody
.expressions(0)
.asInstanceOf[Expression.Binding]
.expression
.diagnosticsList
.count(_.isInstanceOf[Warning.WrongTco]) shouldEqual 1
fnBody.returnValue.diagnosticsList
.count(_.isInstanceOf[Warning.WrongTco]) shouldEqual 0
assertInlineCompilation(
code,
() => mkTailContext,
ir => {
val lambda = ir.asInstanceOf[Function.Lambda]
val fnBody = lambda.body.asInstanceOf[Expression.Block]
fnBody
.expressions(0)
.asInstanceOf[Expression.Binding]
.expression
.diagnosticsList
.count(_.isInstanceOf[Warning.WrongTco]) shouldEqual 1
fnBody.returnValue.diagnosticsList
.count(_.isInstanceOf[Warning.WrongTco]) shouldEqual 0
},
true
)
}
}
"Tail call analysis on local functions" should {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""
|adder_two =
| if 0 == 0 then 0 else
| @Tail_Call adder_two
|""".stripMargin.preprocessModule.analyse
val fnBody = ir.bindings.head
.asInstanceOf[definition.Method]
.body
.asInstanceOf[Function.Lambda]
.body
"handle application involving local functions" in {
fnBody
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Application.Prefix]
.arguments(2)
.value
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Application.Prefix]
.function
.diagnosticsList
.count(_.isInstanceOf[Warning.WrongTco]) shouldEqual 0
}
val code = """
|adder_two =
| if 0 == 0 then 0 else
| @Tail_Call adder_two
|""".stripMargin
assertModuleCompilation(
code,
() => mkModuleContext,
ir => {
val fnBody = ir.bindings.head
.asInstanceOf[definition.Method]
.body
.asInstanceOf[Function.Lambda]
.body
fnBody
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Application.Prefix]
.arguments(2)
.value
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Application.Prefix]
.function
.diagnosticsList
.count(_.isInstanceOf[Warning.WrongTco]) shouldEqual 0
}
)
}
}
"Tail call analysis on case expressions" should {
"not mark any portion of the branch functions as tail by default" in {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
val code =
"""
|Foo.bar = a ->
| x = case a of
| Lambda fn arg -> fn arg
|
| x
|""".stripMargin.preprocessModule.analyse
|""".stripMargin
val caseExpr = ir.bindings.head
.asInstanceOf[definition.Method]
.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
.expressions
.head
.asInstanceOf[Expression.Binding]
.expression
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Case.Expr]
assertModuleCompilation(
code,
() => mkModuleContext,
ir => {
val caseExpr = ir.bindings.head
.asInstanceOf[definition.Method]
.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
.expressions
.head
.asInstanceOf[Expression.Binding]
.expression
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Case.Expr]
caseExpr.getMetadata(TailCall) shouldEqual None
caseExpr.branches.foreach(branch => {
val branchExpression =
branch.expression.asInstanceOf[Application.Prefix]
caseExpr.getMetadata(TailCall.INSTANCE) shouldEqual None
caseExpr.branches.foreach(branch => {
val branchExpression =
branch.expression.asInstanceOf[Application.Prefix]
branchExpression.getMetadata(TailCall) shouldEqual None
})
branchExpression.getMetadata(TailCall.INSTANCE) shouldEqual None
})
}
)
}
"only mark the branches as tail if the expression is in tail position" in {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
val code =
"""
|Foo.bar = a ->
| case a of
| Lambda fn arg -> fn arg
|""".stripMargin.preprocessModule.analyse
|""".stripMargin
val caseExpr = ir.bindings.head
.asInstanceOf[definition.Method]
.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Case.Expr]
assertModuleCompilation(
code,
() => mkModuleContext,
ir => {
val caseExpr = ir.bindings.head
.asInstanceOf[definition.Method]
.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Case.Expr]
caseExpr.getMetadata(TailCall) shouldEqual Some(
TailPosition.Tail
caseExpr.getMetadata(TailCall.INSTANCE) shouldEqual Some(
TailPosition.Tail
)
caseExpr.branches.foreach(branch => {
val branchExpression =
branch.expression.asInstanceOf[Application.Prefix]
branchExpression.getMetadata(TailCall.INSTANCE) shouldEqual Some(
TailPosition.Tail
)
})
},
true
)
caseExpr.branches.foreach(branch => {
val branchExpression =
branch.expression.asInstanceOf[Application.Prefix]
branchExpression.getMetadata(TailCall) shouldEqual Some(
TailPosition.Tail
)
})
}
"mark patters and pattern elements as not tail" in {
implicit val ctx: InlineContext = mkTailContext
val ir =
val code =
"""
|case x of
| Cons a b -> a + b
|""".stripMargin.preprocessExpression.get.analyse
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Case.Expr]
|""".stripMargin
val caseBranch = ir.branches.head
val pattern = caseBranch.pattern.asInstanceOf[Pattern.Constructor]
val patternConstructor = pattern.constructor
assertInlineCompilation(
code,
() => mkTailContext,
ir => {
val caseExpr = ir
.asInstanceOf[Expression.Block]
.returnValue
.asInstanceOf[Case.Expr]
val caseBranch = caseExpr.branches.head
val pattern = caseBranch.pattern.asInstanceOf[Pattern.Constructor]
val patternConstructor = pattern.constructor
pattern.getMetadata(TailCall) shouldEqual None
patternConstructor.getMetadata(TailCall) shouldEqual None
pattern.fields.foreach(f => {
f.getMetadata(TailCall) shouldEqual None
pattern.getMetadata(TailCall.INSTANCE) shouldEqual None
patternConstructor.getMetadata(TailCall.INSTANCE) shouldEqual None
pattern.fields.foreach(f => {
f.getMetadata(TailCall.INSTANCE) shouldEqual None
f.asInstanceOf[Pattern.Name]
.name
.getMetadata(TailCall) shouldEqual None
})
f.asInstanceOf[Pattern.Name]
.name
.getMetadata(TailCall.INSTANCE) shouldEqual None
})
}
)
}
}
"Tail call analysis on function calls" should {
implicit val ctx: ModuleContext = mkModuleContext
"work on function that has tail call return value" in {
assertModuleCompilation(
"""
|Foo.bar =
| IO.println "AAAAA"
|""".stripMargin,
() => mkModuleContext,
ir => {
val tailCall = ir.bindings.head.asInstanceOf[definition.Method]
val tailCallBody = tailCall.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
val tailCall =
"""
|Foo.bar =
| IO.println "AAAAA"
|""".stripMargin.preprocessModule.analyse.bindings.head
.asInstanceOf[definition.Method]
val tailCallBody = tailCall.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
withClue("Mark the arguments as tail") {
tailCallBody.returnValue
.asInstanceOf[Application.Prefix]
.arguments
.foreach(arg =>
arg.getMetadata(TailCall.INSTANCE) shouldEqual Some(
TailPosition.Tail
)
)
}
val nonTailCall =
"""
|Foo.bar =
| a = b c d
| a
|""".stripMargin.preprocessModule.analyse.bindings.head
.asInstanceOf[definition.Method]
val nonTailCallBody = nonTailCall.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
"mark the arguments as tail" in {
nonTailCallBody.expressions.head
.asInstanceOf[Expression.Binding]
.expression
.asInstanceOf[Application.Prefix]
.arguments
.foreach(arg =>
arg.getMetadata(TailCall) shouldEqual Some(
TailPosition.Tail
)
)
tailCallBody.returnValue
.asInstanceOf[Application.Prefix]
.arguments
.foreach(arg =>
arg.getMetadata(TailCall) shouldEqual Some(
TailPosition.Tail
)
)
withClue(
"Mark the function call as tail if it is in a tail position"
) {
tailCallBody.returnValue.getMetadata(
TailCall.INSTANCE
) shouldEqual Some(
TailPosition.Tail
)
}
}
)
}
"mark the function call as tail if it is in a tail position" in {
tailCallBody.returnValue.getMetadata(TailCall) shouldEqual Some(
TailPosition.Tail
"work on function that has not-tail call return value" in {
assertModuleCompilation(
"""
|Foo.bar =
| a = b c d
| a
|""".stripMargin,
() => mkModuleContext,
ir => {
val nonTailCall = ir.bindings.head.asInstanceOf[definition.Method]
val nonTailCallBody = nonTailCall.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
withClue("Mark the arguments as tail") {
nonTailCallBody
.expressions(0)
.asInstanceOf[Expression.Binding]
.expression
.asInstanceOf[Application.Prefix]
.arguments
.foreach(arg =>
arg.getMetadata(TailCall.INSTANCE) shouldEqual Some(
TailPosition.Tail
)
)
}
withClue(
"Mark the function call as not tail if it is in a tail position"
) {
nonTailCallBody.expressions.head
.asInstanceOf[Expression.Binding]
.expression
.getMetadata(TailCall.INSTANCE) shouldEqual None
}
}
)
}
"mark the function call as not tail if it is in a tail position" in {
nonTailCallBody.expressions.head
.asInstanceOf[Expression.Binding]
.expression
.getMetadata(TailCall) shouldEqual None
assertModuleCompilation(
"""
|Foo.bar =
| a = b c d
| a
|""".stripMargin,
() => mkModuleContext,
ir => {
val nonTailCall = ir.bindings.head.asInstanceOf[definition.Method]
val nonTailCallBody = nonTailCall.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
nonTailCallBody.expressions.head
.asInstanceOf[Expression.Binding]
.expression
.getMetadata(TailCall.INSTANCE) shouldEqual None
}
)
}
}
"Tail call analysis on blocks" should {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
val code =
"""
|Foo.bar = a -> b -> c ->
| d = a + b
| mul = a -> b -> a * b
| mul c d
|""".stripMargin.preprocessModule.analyse.bindings.head
.asInstanceOf[definition.Method]
val block = ir.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
|""".stripMargin
"mark the bodies of bound functions as tail properly" in {
block
.expressions(1)
.asInstanceOf[Expression.Binding]
.expression
.asInstanceOf[Function.Lambda]
.body
.getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
assertModuleCompilation(
code,
() => mkModuleContext,
ir => {
val method = ir.bindings.head.asInstanceOf[definition.Method]
val block = method.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
block
.expressions(1)
.asInstanceOf[Expression.Binding]
.expression
.asInstanceOf[Function.Lambda]
.body
.getMetadata(TailCall.INSTANCE) shouldEqual Some(TailPosition.Tail)
}
)
}
"mark the block expressions as not tail" in {
block.expressions.foreach(expr =>
expr.getMetadata(TailCall) shouldEqual None
assertModuleCompilation(
code,
() => mkModuleContext,
ir => {
val method = ir.bindings.head.asInstanceOf[definition.Method]
val block = method.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
block.expressions.foreach(expr =>
expr.getMetadata(TailCall.INSTANCE) shouldEqual None
)
}
)
}
"mark the final expression of the block as tail" in {
block.returnValue.getMetadata(TailCall) shouldEqual Some(
TailPosition.Tail
assertModuleCompilation(
code,
() => mkModuleContext,
ir => {
val method = ir.bindings.head.asInstanceOf[definition.Method]
val block = method.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
block.returnValue.getMetadata(TailCall.INSTANCE) shouldEqual Some(
TailPosition.Tail
)
}
)
}
"mark the block as tail if it is in a tail position" in {
block.getMetadata(TailCall) shouldEqual Some(TailPosition.Tail)
assertModuleCompilation(
code,
() => mkModuleContext,
ir => {
val method = ir.bindings.head.asInstanceOf[definition.Method]
val block = method.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
block.getMetadata(TailCall.INSTANCE) shouldEqual Some(
TailPosition.Tail
)
},
true
)
}
}
}

View File

@ -0,0 +1,447 @@
package org.enso.compiler.test.pass.desugar
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
import org.enso.compiler.core.CompilerError
import org.enso.compiler.core.ir.{
CallArgument,
DefinitionArgument,
Expression,
Function,
IdentifiedLocation,
Module,
Name
}
import org.enso.compiler.core.ir.expression.{Application, Case, Operator}
import org.enso.compiler.pass.{IRPass, IRProcessingPass}
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall
}
import org.enso.compiler.pass.desugar.{
ComplexType,
FunctionBinding,
GenerateMethodBodies,
OperatorToFunction,
SectionsToBinOp
}
import org.enso.compiler.pass.lint.UnusedBindings
import org.enso.compiler.pass.optimise.LambdaConsolidate
import org.enso.compiler.pass.resolve.{
DocumentationComments,
IgnoredBindings,
OverloadsResolution
}
/** Original implementation of [[org.enso.compiler.pass.desugar.LambdaShorthandToLambda]].
* Now serves as the test verification of the new [[org.enso.compiler.pass.desugar.LambdaShorthandToLambdaMini]] mini pass version.
*
* This pass translates `_` arguments at application sites to lambda functions.
*
* This pass has no configuration.
*
* This pass requires the context to provide:
*
* - A [[FreshNameSupply]]
*/
case object LambdaShorthandToLambdaMegaPass extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
ComplexType,
DocumentationComments,
FunctionBinding,
GenerateMethodBodies,
OperatorToFunction,
SectionsToBinOp.INSTANCE
)
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis,
IgnoredBindings,
LambdaConsolidate,
OverloadsResolution,
TailCall.INSTANCE,
UnusedBindings
)
/** Desugars underscore arguments to lambdas for a module.
*
* @param ir the Enso IR to process
* @param moduleContext a context object that contains the information needed
* to process a module
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runModule(
ir: Module,
moduleContext: ModuleContext
): Module = {
val new_bindings = ir.bindings.map { case a =>
a.mapExpressions(
runExpression(
_,
InlineContext(
moduleContext,
freshNameSupply = moduleContext.freshNameSupply,
compilerConfig = moduleContext.compilerConfig
)
)
)
}
ir.copy(bindings = new_bindings)
}
/** Desugars underscore arguments to lambdas for an arbitrary expression.
*
* @param ir the Enso IR to process
* @param inlineContext a context object that contains the information needed
* for inline evaluation
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runExpression(
ir: Expression,
inlineContext: InlineContext
): Expression = {
val freshNameSupply = inlineContext.freshNameSupply.getOrElse(
throw new CompilerError(
"Desugaring underscore arguments to lambdas requires a fresh name " +
"supply."
)
)
desugarExpression(ir, freshNameSupply)
}
// === Pass Internals =======================================================
/** Performs lambda shorthand desugaring on an arbitrary expression.
*
* @param ir the expression to desugar
* @param freshNameSupply the compiler's fresh name supply
* @return `ir`, with any lambda shorthand arguments desugared
*/
def desugarExpression(
ir: Expression,
freshNameSupply: FreshNameSupply
): Expression = {
ir.transformExpressions {
case app: Application => desugarApplication(app, freshNameSupply)
case caseExpr: Case.Expr => desugarCaseExpr(caseExpr, freshNameSupply)
case name: Name => desugarName(name, freshNameSupply)
}
}
/** Desugars an arbitrary name occurrence, turning isolated occurrences of
* `_` into the `id` function.
*
* @param name the name to desugar
* @param supply the compiler's fresh name supply
* @return `name`, desugared where necessary
*/
private def desugarName(name: Name, supply: FreshNameSupply): Expression = {
name match {
case blank: Name.Blank =>
val newName = supply.newName()
new Function.Lambda(
List(
DefinitionArgument.Specified(
name = Name.Literal(
newName.name,
isMethod = false,
null
),
ascribedType = None,
defaultValue = None,
suspended = false,
identifiedLocation = null
)
),
newName,
blank.location.orNull
)
case _ => name
}
}
/** Desugars lambda shorthand arguments to an arbitrary function application.
*
* @param application the function application to desugar
* @param freshNameSupply the compiler's supply of fresh names
* @return `application`, with any lambda shorthand arguments desugared
*/
private def desugarApplication(
application: Application,
freshNameSupply: FreshNameSupply
): Expression = {
application match {
case p @ Application.Prefix(fn, args, _, _, _) =>
// Determine which arguments are lambda shorthand
val argIsUnderscore = determineLambdaShorthand(args)
// Generate a new name for the arg value for each shorthand arg
val updatedArgs =
args
.zip(argIsUnderscore)
.map(updateShorthandArg(_, freshNameSupply))
.map { case s @ CallArgument.Specified(_, value, _, _) =>
s.copy(value = desugarExpression(value, freshNameSupply))
}
// Generate a definition arg instance for each shorthand arg
val defArgs = updatedArgs.zip(argIsUnderscore).map {
case (arg, isShorthand) => generateDefinitionArg(arg, isShorthand)
}
val actualDefArgs = defArgs.collect { case Some(defArg) =>
defArg
}
// Determine whether or not the function itself is shorthand
val functionIsShorthand = fn.isInstanceOf[Name.Blank]
val (updatedFn, updatedName) = if (functionIsShorthand) {
val newFn = freshNameSupply
.newName()
.copy(
location = fn.location,
passData = fn.passData,
diagnostics = fn.diagnostics
)
val newName = newFn.name
(newFn, Some(newName))
} else {
val newFn = desugarExpression(fn, freshNameSupply)
(newFn, None)
}
val processedApp = p.copy(
function = updatedFn,
arguments = updatedArgs
)
// Wrap the app in lambdas from right to left, 1 lambda per shorthand
// arg
val appResult =
actualDefArgs.foldRight(processedApp: Expression)((arg, body) =>
new Function.Lambda(List(arg), body, null)
)
// If the function is shorthand, do the same
val resultExpr = if (functionIsShorthand) {
new Function.Lambda(
List(
DefinitionArgument.Specified(
Name
.Literal(
updatedName.get,
isMethod = false,
fn.location.orNull
),
None,
None,
suspended = false,
null
)
),
appResult,
null
)
} else appResult
resultExpr match {
case lam: Function.Lambda => lam.copy(location = p.location)
case result => result
}
case f @ Application.Force(tgt, _, _) =>
f.copy(target = desugarExpression(tgt, freshNameSupply))
case vector @ Application.Sequence(items, _, _) =>
var bindings: List[Name] = List()
val newItems = items.map {
case blank: Name.Blank =>
val name = freshNameSupply
.newName()
.copy(
location = blank.location,
passData = blank.passData,
diagnostics = blank.diagnostics
)
bindings ::= name
name
case it => desugarExpression(it, freshNameSupply)
}
val newVec = vector.copy(newItems)
val locWithoutId =
newVec.location.map(l => new IdentifiedLocation(l.location()))
bindings.foldLeft(newVec: Expression) { (body, bindingName) =>
val defArg = DefinitionArgument.Specified(
bindingName,
ascribedType = None,
defaultValue = None,
suspended = false,
identifiedLocation = null
)
new Function.Lambda(List(defArg), body, locWithoutId.orNull)
}
case tSet @ Application.Typeset(expr, _, _) =>
tSet.copy(expression = expr.map(desugarExpression(_, freshNameSupply)))
case _: Operator =>
throw new CompilerError(
"Operators should be desugared by the point of underscore " +
"to lambda conversion."
)
}
}
/** Determines, positionally, which of the application arguments are lambda
* shorthand arguments.
*
* @param args the application arguments
* @return a list containing `true` for a given position if the arg in that
* position is lambda shorthand, otherwise `false`
*/
private def determineLambdaShorthand(
args: List[CallArgument]
): List[Boolean] = {
args.map { case CallArgument.Specified(_, value, _, _) =>
value match {
case _: Name.Blank => true
case _ => false
}
}
}
/** Generates a new name to replace a shorthand argument, as well as the
* corresponding definition argument.
*
* @param argAndIsShorthand the arguments, and whether or not the argument in
* the corresponding position is shorthand
* @return the above described pair for a given position if the argument in
* a given position is shorthand, otherwise [[None]].
*/
private def updateShorthandArg(
argAndIsShorthand: (CallArgument, Boolean),
freshNameSupply: FreshNameSupply
): CallArgument = {
val arg = argAndIsShorthand._1
val isShorthand = argAndIsShorthand._2
arg match {
case s @ CallArgument.Specified(_, value, _, _) =>
if (isShorthand) {
val newName = freshNameSupply
.newName()
.copy(
location = value.location,
passData = value.passData,
diagnostics = value.diagnostics
)
s.copy(value = newName)
} else s
}
}
/** Generates a corresponding definition argument to a call argument that was
* previously lambda shorthand.
*
* @param arg the argument to generate a corresponding def argument to
* @param isShorthand whether or not `arg` was shorthand
* @return a corresponding definition argument if `arg` `isShorthand`,
* otherwise [[None]]
*/
private def generateDefinitionArg(
arg: CallArgument,
isShorthand: Boolean
): Option[DefinitionArgument] = {
if (isShorthand) {
arg match {
case specified @ CallArgument.Specified(_, value, _, passData) =>
// Note [Safe Casting to Name.Literal]
val defArgName =
Name.Literal(
value.asInstanceOf[Name.Literal].name,
isMethod = false,
null
)
Some(
new DefinitionArgument.Specified(
defArgName,
None,
None,
suspended = false,
null,
passData.duplicate,
specified.diagnosticsCopy
)
)
}
} else None
}
/* Note [Safe Casting to Name.Literal]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* This cast is entirely safe here as, by construction in
* `updateShorthandArg`, any arg for which `isShorthand` is true has its
* value as an `Name.Literal`.
*/
/** Performs desugaring of lambda shorthand arguments in a case expression.
*
* In the case where a user writes `case _ of`, this gets converted into a
* lambda (`x -> case x of`).
*
* @param caseExpr the case expression to desugar
* @param freshNameSupply the compiler's supply of fresh names
* @return `caseExpr`, with any lambda shorthand desugared
*/
private def desugarCaseExpr(
caseExpr: Case.Expr,
freshNameSupply: FreshNameSupply
): Expression = {
val newBranches = caseExpr.branches.map(
_.mapExpressions(expr => desugarExpression(expr, freshNameSupply))
)
caseExpr.scrutinee match {
case nameBlank: Name.Blank =>
val scrutineeName =
freshNameSupply
.newName()
.copy(
location = nameBlank.location,
passData = nameBlank.passData,
diagnostics = nameBlank.diagnostics
)
val lambdaArg = DefinitionArgument.Specified(
scrutineeName.copy(id = null),
None,
None,
suspended = false,
null
)
val newCaseExpr = caseExpr.copy(
scrutinee = scrutineeName,
branches = newBranches
)
new Function.Lambda(
caseExpr,
List(lambdaArg),
newCaseExpr,
caseExpr.location.orNull
)
case x =>
caseExpr.copy(
scrutinee = desugarExpression(x, freshNameSupply),
branches = newBranches
)
}
}
}

View File

@ -2,6 +2,7 @@ package org.enso.compiler.test.pass.desugar
import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, InlineContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.ir.{
CallArgument,
DefinitionArgument,
@ -12,8 +13,13 @@ import org.enso.compiler.core.ir.{
}
import org.enso.compiler.core.ir.expression.{Application, Case}
import org.enso.compiler.pass.desugar.LambdaShorthandToLambda
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
import org.enso.compiler.pass.{
MiniIRPass,
PassConfiguration,
PassGroup,
PassManager
}
import org.enso.compiler.test.{CompilerTest, CompilerTests}
class LambdaShorthandToLambdaTest extends CompilerTest {
@ -42,7 +48,13 @@ class LambdaShorthandToLambdaTest extends CompilerTest {
* @return [[ir]], with all lambda shorthand desugared
*/
def desugar(implicit inlineContext: InlineContext): Expression = {
LambdaShorthandToLambda.runExpression(ir, inlineContext)
LambdaShorthandToLambdaMegaPass.runExpression(ir, inlineContext)
}
def desugarMini(implicit inlineContext: InlineContext): Expression = {
val miniPass =
LambdaShorthandToLambda.createForInlineCompilation(inlineContext)
MiniIRPass.compile(classOf[Expression], ir, miniPass)
}
}
@ -54,6 +66,20 @@ class LambdaShorthandToLambdaTest extends CompilerTest {
buildInlineContext(freshNameSupply = Some(new FreshNameSupply))
}
private def desugarWithMegaPass(
code: String
): IR = {
implicit val ctx: InlineContext = mkInlineContext
code.preprocessExpression.get.desugar
}
private def desugarWithMiniPass(
code: String
): IR = {
implicit val ctx: InlineContext = mkInlineContext
code.preprocessExpression.get.desugarMini
}
// === The Tests ============================================================
"Desugaring of underscore arguments" should {
@ -335,107 +361,123 @@ class LambdaShorthandToLambdaTest extends CompilerTest {
"Nested underscore arguments" should {
"work for applications" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
val code =
"""
|a _ (fn _ c)
|""".stripMargin.preprocessExpression.get.desugar
|""".stripMargin
val megaIr = desugarWithMegaPass(code)
val miniIr = desugarWithMiniPass(code)
ir shouldBe an[Function.Lambda]
ir.asInstanceOf[Function.Lambda].body shouldBe an[Application.Prefix]
val irBody = ir
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Application.Prefix]
for ((ir, msg) <- List((miniIr, "Mini IR"), (megaIr, "Mega IR"))) {
withClue("Processed by " + msg) {
ir shouldBe an[Function.Lambda]
ir.asInstanceOf[Function.Lambda].body shouldBe an[Application.Prefix]
val irBody = ir
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Application.Prefix]
irBody
.arguments(1)
.asInstanceOf[CallArgument.Specified]
.value shouldBe an[Function.Lambda]
val lamArg = irBody
.arguments(1)
.asInstanceOf[CallArgument.Specified]
.value
.asInstanceOf[Function.Lambda]
val lamArgArgName =
lamArg.arguments.head.asInstanceOf[DefinitionArgument.Specified].name
irBody
.arguments(1)
.asInstanceOf[CallArgument.Specified]
.value shouldBe an[Function.Lambda]
val lamArg = irBody
.arguments(1)
.asInstanceOf[CallArgument.Specified]
.value
.asInstanceOf[Function.Lambda]
val lamArgArgName =
lamArg.arguments.head
.asInstanceOf[DefinitionArgument.Specified]
.name
lamArg.body shouldBe an[Application.Prefix]
val lamArgBody = lamArg.body.asInstanceOf[Application.Prefix]
val lamArgBodyArg1Name = lamArgBody.arguments.head
.asInstanceOf[CallArgument.Specified]
.value
.asInstanceOf[Name.Literal]
lamArg.body shouldBe an[Application.Prefix]
val lamArgBody = lamArg.body.asInstanceOf[Application.Prefix]
val lamArgBodyArg1Name = lamArgBody.arguments.head
.asInstanceOf[CallArgument.Specified]
.value
.asInstanceOf[Name.Literal]
lamArgArgName.name shouldEqual lamArgBodyArg1Name.name
lamArgArgName.name shouldEqual lamArgBodyArg1Name.name
}
}
}
"work in named applications" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
val code =
"""
|a _ (fn (t = _) c)
|""".stripMargin.preprocessExpression.get.desugar
|""".stripMargin
val megaIr = desugarWithMegaPass(code)
val miniIr = desugarWithMiniPass(code)
ir shouldBe an[Function.Lambda]
ir.asInstanceOf[Function.Lambda].body shouldBe an[Application.Prefix]
val irBody = ir
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Application.Prefix]
for ((ir, msg) <- List((miniIr, "Mini IR"), (megaIr, "Mega IR"))) {
withClue("Processed by " + msg) {
ir shouldBe an[Function.Lambda]
ir.asInstanceOf[Function.Lambda].body shouldBe an[Application.Prefix]
val irBody = ir
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Application.Prefix]
irBody
.arguments(1)
.asInstanceOf[CallArgument.Specified]
.value shouldBe an[Function.Lambda]
val lamArg = irBody
.arguments(1)
.asInstanceOf[CallArgument.Specified]
.value
.asInstanceOf[Function.Lambda]
val lamArgArgName =
lamArg.arguments.head.asInstanceOf[DefinitionArgument.Specified].name
irBody
.arguments(1)
.asInstanceOf[CallArgument.Specified]
.value shouldBe an[Function.Lambda]
val lamArg = irBody
.arguments(1)
.asInstanceOf[CallArgument.Specified]
.value
.asInstanceOf[Function.Lambda]
val lamArgArgName =
lamArg.arguments.head
.asInstanceOf[DefinitionArgument.Specified]
.name
lamArg.body shouldBe an[Application.Prefix]
val lamArgBody = lamArg.body.asInstanceOf[Application.Prefix]
val lamArgBodyArg1Name = lamArgBody.arguments.head
.asInstanceOf[CallArgument.Specified]
.value
.asInstanceOf[Name.Literal]
lamArg.body shouldBe an[Application.Prefix]
val lamArgBody = lamArg.body.asInstanceOf[Application.Prefix]
val lamArgBodyArg1Name = lamArgBody.arguments.head
.asInstanceOf[CallArgument.Specified]
.value
.asInstanceOf[Name.Literal]
lamArgArgName.name shouldEqual lamArgBodyArg1Name.name
lamArgArgName.name shouldEqual lamArgBodyArg1Name.name
}
}
}
"work in function argument defaults" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
val code =
"""
|\a (b = f _ 1) -> f a
|""".stripMargin.preprocessExpression.get.desugar
|""".stripMargin
val megaIr = desugarWithMegaPass(code)
val miniIr = desugarWithMiniPass(code)
ir shouldBe an[Function.Lambda]
val irFn = ir.asInstanceOf[Function.Lambda]
val bArg =
irFn.arguments.tail.head.asInstanceOf[DefinitionArgument.Specified]
for ((ir, msg) <- List((miniIr, "Mini IR"), (megaIr, "Mega IR"))) {
withClue("Processed by " + msg) {
ir shouldBe an[Function.Lambda]
val irFn = ir.asInstanceOf[Function.Lambda]
val bArg =
irFn.arguments.tail.head.asInstanceOf[DefinitionArgument.Specified]
bArg.defaultValue shouldBe defined
bArg.defaultValue.get shouldBe an[Function.Lambda]
val default = bArg.defaultValue.get.asInstanceOf[Function.Lambda]
val defaultArgName = default.arguments.head
.asInstanceOf[DefinitionArgument.Specified]
.name
bArg.defaultValue shouldBe defined
bArg.defaultValue.get shouldBe an[Function.Lambda]
val default = bArg.defaultValue.get.asInstanceOf[Function.Lambda]
val defaultArgName = default.arguments.head
.asInstanceOf[DefinitionArgument.Specified]
.name
default.body shouldBe an[Application.Prefix]
val defBody = default.body.asInstanceOf[Application.Prefix]
val defBodyArg1Name = defBody.arguments.head
.asInstanceOf[CallArgument.Specified]
.value
.asInstanceOf[Name.Literal]
default.body shouldBe an[Application.Prefix]
val defBody = default.body.asInstanceOf[Application.Prefix]
val defBodyArg1Name = defBody.arguments.head
.asInstanceOf[CallArgument.Specified]
.value
.asInstanceOf[Name.Literal]
defaultArgName.name shouldEqual defBodyArg1Name.name
defaultArgName.name shouldEqual defBodyArg1Name.name
}
}
}
"work for case expressions" in {
@ -575,4 +617,42 @@ class LambdaShorthandToLambdaTest extends CompilerTest {
secondLamArgName shouldEqual appArg2Name
}
}
"Mini lambda shorthand pass" should {
"Produce same results as mega pass" in {
val codeInputs = List(
"_.length",
"foo a _ b _",
"foo (a = _) b _",
"_ a b",
"if _ then a",
"""
|case _ of
| Nil -> 0
|""".stripMargin,
"(10 + _)",
"(_ +)",
"(_ + _)",
"(+ _)",
"[1, _, (3 + 4), _]",
"""
|case _ of
| Nil -> f _ b
|""".stripMargin,
"x = _",
"\\ x=_ -> x",
"(_ + 5) 5",
"(f _ _ b) b"
)
codeInputs.zipWithIndex.foreach { case (code, idx) =>
val testName = "test-" + idx
val msg = s"Code that failed to compile: `$code`"
val megaIr = desugarWithMegaPass(code)
val miniIr = desugarWithMiniPass(code)
CompilerTests.assertEqualsIR(msg, testName, megaIr, miniIr)
}
}
}
}

View File

@ -1,5 +1,8 @@
package org.enso.compiler.test.pass.desugar
import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
import org.enso.compiler.core.ir.Module
import org.enso.compiler.core.ir.{
CallArgument,
Empty,
@ -9,10 +12,38 @@ import org.enso.compiler.core.ir.{
Name
}
import org.enso.compiler.core.ir.expression.{Application, Operator}
import org.enso.compiler.pass.desugar.OperatorToFunction
import org.enso.compiler.test.CompilerTest
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis
}
import org.enso.compiler.pass.{
IRPass,
IRProcessingPass,
MiniIRPass,
MiniPassFactory,
PassConfiguration,
PassManager
}
import org.enso.compiler.pass.desugar.{
GenerateMethodBodies,
OperatorToFunction,
SectionsToBinOp
}
import org.enso.compiler.test.MiniPassTest
class OperatorToFunctionTest extends CompilerTest {
class OperatorToFunctionTest extends MiniPassTest {
override def testName: String = "OperatorToFunction"
override def miniPassFactory: MiniPassFactory = OperatorToFunction
override def megaPass: IRPass = OperatorToFunctionTestPass
override def megaPassManager: PassManager = {
val passes = new Passes(defaultConfig)
val precursors = passes.getPrecursors(OperatorToFunction).get
new PassManager(List(precursors), PassConfiguration())
}
// === Utilities ============================================================
@ -50,6 +81,16 @@ class OperatorToFunctionTest extends CompilerTest {
}
// === The Tests ============================================================
val opName =
Name.Literal("=:=", isMethod = true, null)
val left = Empty(null)
val right = Empty(null)
val rightArg = CallArgument.Specified(None, Empty(null), null)
val (operator, operatorFn) = genOprAndFn(opName, left, right)
val oprArg = CallArgument.Specified(None, operator, null)
val oprFnArg = CallArgument.Specified(None, operatorFn, null)
"Operators" should {
val opName =
@ -70,15 +111,59 @@ class OperatorToFunctionTest extends CompilerTest {
CallArgument.Specified(None, operatorFn, identifiedLocation = null)
"be translated to functions" in {
OperatorToFunction.runExpression(operator, ctx) shouldEqual operatorFn
OperatorToFunctionTestPass.runExpression(
operator,
ctx
) shouldEqual operatorFn
}
// "be translated in module contexts" in {
// val moduleInput = operator.asModuleDefs
// val moduleOutput = operatorFn.asModuleDefs
//
// OperatorToFunction.runModule(moduleInput, modCtx) shouldEqual moduleOutput
// }
"be translated recursively in synthetic IR" in {
val recursiveIR =
Operator.Binary(oprArg, opName, rightArg, null)
val recursiveIRResult = Application.Prefix(
opName,
List(oprFnArg, rightArg),
hasDefaultsSuspended = false,
null
)
OperatorToFunctionTestPass.runExpression(
recursiveIR,
ctx
) shouldEqual recursiveIRResult
}
"be translated recursively" in {
val code =
"""
|main =
| a = 1 + 2
| nested_method x y = x + y
| nested_method (3 * 4) a
|""".stripMargin
assertModuleCompilation(
code,
() =>
buildModuleContext(
freshNameSupply = Some(new FreshNameSupply())
),
ir => {
ir.preorder().foreach {
case _: Operator.Binary => fail("Operator.Binary found")
case _ =>
}
}
)
}
}
"Operators mini pass" should {
"be translated to functions" in {
val miniPass = OperatorToFunction.createForInlineCompilation(ctx)
val miniRes =
MiniIRPass.compile(classOf[Expression], operator, miniPass)
miniRes shouldEqual operatorFn
}
"be translated recursively" in {
val recursiveIR =
@ -90,10 +175,87 @@ class OperatorToFunctionTest extends CompilerTest {
identifiedLocation = null
)
OperatorToFunction.runExpression(
recursiveIR,
ctx
) shouldEqual recursiveIRResult
val miniPass = OperatorToFunction.createForInlineCompilation(ctx)
val miniRes =
MiniIRPass.compile(classOf[Expression], recursiveIR, miniPass)
miniRes shouldEqual recursiveIRResult
}
}
}
/** Copied from the original implementation in `OperatorToFunction`
* This pass converts usages of operators to calls to standard functions.
*
* This pass requires the context to provide:
*
* - Nothing
*/
case object OperatorToFunctionTestPass extends IRPass {
/** A purely desugaring pass has no analysis output. */
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
GenerateMethodBodies,
SectionsToBinOp.INSTANCE
)
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
DataflowAnalysis,
DemandAnalysis
)
/** Executes the conversion pass.
*
* @param ir the Enso IR to process
* @param moduleContext a context object that contains the information needed
* to process a module
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runModule(
ir: Module,
moduleContext: ModuleContext
): Module = {
val new_bindings = ir.bindings.map { a =>
a.mapExpressions(
runExpression(
_,
new InlineContext(
moduleContext,
compilerConfig = moduleContext.compilerConfig
)
)
)
}
ir.copy(bindings = new_bindings)
}
/** Executes the conversion pass in an inline context.
*
* @param ir the Enso IR to process
* @param inlineContext a context object that contains the information needed
* for inline evaluation
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runExpression(
ir: Expression,
inlineContext: InlineContext
): Expression = {
ir.transformExpressions { case operatorBinary: Operator.Binary =>
new Application.Prefix(
operatorBinary.operator,
List(
operatorBinary.left.mapExpressions(runExpression(_, inlineContext)),
operatorBinary.right.mapExpressions(runExpression(_, inlineContext))
),
hasDefaultsSuspended = false,
operatorBinary.location.orNull,
operatorBinary.passData,
operatorBinary.diagnostics
)
}
}
}

View File

@ -1,4 +1,4 @@
package org.enso.compiler.pass.desugar
package org.enso.compiler.test.pass.desugar
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
import org.enso.compiler.core.ir.{
@ -12,6 +12,7 @@ import org.enso.compiler.core.ir.{
import org.enso.compiler.core.CompilerError
import org.enso.compiler.core.ir.expression.{Application, Section}
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.IRProcessingPass
import org.enso.compiler.pass.analyse._
import org.enso.compiler.pass.lint.UnusedBindings
@ -23,19 +24,19 @@ import org.enso.compiler.pass.lint.UnusedBindings
*
* - A [[FreshNameSupply]].
*/
case object SectionsToBinOp extends IRPass {
case object SectionsToBinOpMegaPass extends IRPass {
override type Metadata = IRPass.Metadata.Empty
override type Config = IRPass.Configuration.Default
override lazy val precursorPasses: Seq[IRPass] = List(
GenerateMethodBodies
override lazy val precursorPasses: Seq[IRProcessingPass] = List(
org.enso.compiler.pass.desugar.GenerateMethodBodies
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
override lazy val invalidatedPasses: Seq[IRProcessingPass] = List(
AliasAnalysis,
CachePreferenceAnalysis,
DataflowAnalysis,
DemandAnalysis,
TailCall,
TailCall.INSTANCE,
UnusedBindings
)

View File

@ -5,50 +5,43 @@ import org.enso.compiler.context.{FreshNameSupply, InlineContext}
import org.enso.compiler.core.ir.{
CallArgument,
DefinitionArgument,
Expression,
Function,
Literal,
Name
}
import org.enso.compiler.pass.{
IRPass,
MiniPassFactory,
PassConfiguration,
PassGroup,
PassManager
}
import org.enso.compiler.core.IR
import org.enso.compiler.core.ir.expression.Application
import org.enso.compiler.pass.desugar.SectionsToBinOp
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
class SectionsToBinOpTest extends CompilerTest {
import org.enso.compiler.test.MiniPassTest
// === Test Configuration ===================================================
class SectionsToBinOpTest extends MiniPassTest {
override def testName: String = "Section To Bin Op"
override def miniPassFactory: MiniPassFactory = SectionsToBinOp.INSTANCE
val passes = new Passes(defaultConfig)
override def megaPass: IRPass = SectionsToBinOpMegaPass
val precursorPasses: PassGroup = passes.getPrecursors(SectionsToBinOp).get
override def megaPassManager: PassManager =
new PassManager(List(precursorPasses), passConfiguration)
// === Test Setup ===========================================================
val passes = new Passes(defaultConfig)
val passConfiguration: PassConfiguration = PassConfiguration()
val precursorPasses: PassGroup =
passes.getPrecursors(SectionsToBinOp.INSTANCE).get
implicit val passManager: PassManager =
new PassManager(List(precursorPasses), passConfiguration)
/** Adds an extension method for running desugaring on the input IR.
*
* @param ir the IR to desugar
*/
implicit class DesugarExpression(ir: Expression) {
/** Runs section desugaring on [[ir]].
*
* @param inlineContext the inline context in which the desugaring takes
* place
* @return [[ir]], with all sections desugared
*/
def desugar(implicit inlineContext: InlineContext): Expression = {
SectionsToBinOp.runExpression(ir, inlineContext)
}
}
/** Makes an inline context.
*
* @return a new inline context
*/
def mkInlineContext: InlineContext = {
buildInlineContext(freshNameSupply = Some(new FreshNameSupply))
}
@ -56,14 +49,21 @@ class SectionsToBinOpTest extends CompilerTest {
// === The Tests ============================================================
"Operator section desugaring" should {
"work for left sections" in {
implicit val ctx: InlineContext = mkInlineContext
val code = """
|(1 +)
|""".stripMargin
val ir =
"""
|(1 +)
|""".stripMargin.preprocessExpression.get.desugar
assertInlineCompilation(
code,
() => mkInlineContext,
assertWorkForLeftSection,
true
)
}
def assertWorkForLeftSection(ir: IR) = {
ir shouldBe an[Application.Prefix]
ir.location shouldBe defined
@ -81,13 +81,20 @@ class SectionsToBinOpTest extends CompilerTest {
}
"work for sides sections" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
val code =
"""
|(+)
|""".stripMargin.preprocessExpression.get.desugar
|""".stripMargin
assertInlineCompilation(
code,
() => mkInlineContext,
assertWorksForSidesSection,
true
)
}
def assertWorksForSidesSection(ir: IR) = {
ir shouldBe an[Function.Lambda]
ir.location shouldBe defined
@ -123,13 +130,21 @@ class SectionsToBinOpTest extends CompilerTest {
}
"work for right sections" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
val code =
"""
|(+ 1)
|""".stripMargin.preprocessExpression.get.desugar
|""".stripMargin
assertInlineCompilation(
code,
() => mkInlineContext,
assertWorkForRightSections,
true
)
}
def assertWorkForRightSections(ir: IR) = {
ir shouldBe an[Function.Lambda]
ir.location shouldBe defined
@ -152,13 +167,21 @@ class SectionsToBinOpTest extends CompilerTest {
}
"work when the section is nested" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
val code =
"""
|x -> (x +)
|""".stripMargin.preprocessExpression.get.desugar
.asInstanceOf[Function.Lambda]
|""".stripMargin
assertInlineCompilation(
code,
() => mkInlineContext,
assertWorkWhenTheSectionIsNested,
true
)
}
def assertWorkWhenTheSectionIsNested(x: IR) = {
val ir = x.asInstanceOf[Function.Lambda]
ir.body
.asInstanceOf[Application.Prefix]
@ -170,13 +193,21 @@ class SectionsToBinOpTest extends CompilerTest {
}
"flip the arguments when a right section's argument is a blank" in {
implicit val ctx: InlineContext = mkInlineContext
val ir =
val code =
"""
|(+ _)
|""".stripMargin.preprocessExpression.get.desugar
|""".stripMargin
assertInlineCompilation(
code,
() => mkInlineContext,
assertFlipTheArgumentsWhenARightSection,
true
)
}
def assertFlipTheArgumentsWhenARightSection(ir: IR) = {
ir shouldBe an[Function.Lambda]
ir.location shouldBe defined
val irFn = ir.asInstanceOf[Function.Lambda]

View File

@ -985,7 +985,7 @@ class IrToTruffle(
expression: Expression
): BaseNode.TailStatus = {
val isTailPosition =
expression.getMetadata(TailCall).isDefined
expression.getMetadata(TailCall.INSTANCE).isDefined
val isTailAnnotated = TailCall.isTailAnnotated(expression)
if (isTailPosition) {
if (isTailAnnotated) {