-
Notifications
You must be signed in to change notification settings - Fork 323
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Simplify compilation of nested patterns (#4005)
`NestedPatternMatch` pass desugared complex patterns in a very inefficient way resulting in an exponential generation of the number of `case` IR (and Truffle) nodes. Every failed nested pattern would copy all the remaining patterns of the original case expression, in a desugared form. While the execution itself of such deeply nested `case` expression might not have many problems, the time spent in compilation phases certainly was a blocker. This change desugars deeply nested into individual cases with a fallthrough logic. However the fallthrough logic is implemented directly in Truffle nodes, rather than via IR. That way we can generate much simpler IR for nested patterns. Consider a simple case of ``` case x of Cons (Cons a b) Nil -> a + b Cons a Nil -> a _ -> 0 ``` Before the change, the compiler would generate rather large IR even for those two patterns: ``` case x of Cons w y -> case w of Cons a b -> case y of Nil -> a + b _ -> case x of Cons a z -> case z of Nil -> a _ -> case x of _ -> 0 _ -> 0 _ -> case x of Cons a z -> case z of Nil -> a _ -> case x of _ -> 0 _ -> 0 Cons a z -> case z of Nil -> a _ -> case x of _ -> 0 _ -> 0 ``` Now we generate simple patterns with fallthrough semantics and no catch-all branches: ``` case x of Cons w y -> case w of Cons a b -> case y of ## fallthrough on failed match ## Nil -> a + b ## fallthrough on failed match ## Cons a z -> case z of Nil -> a ## fallthrough on failed match ## _ -> 0 ``` # Important Notes If you wonder how much does it improve, then @radeusgd's example in https://www.pivotaltracker.com/story/show/183971366/comments/234688327 used to take at least 8 minutes to compile and run. Now it takes 5 seconds from cold start. Also, the example in the benchmark includes compilation time on purpose (that was the main culprit of the slowdown). For the old implementation I had to kill it after 15 minutes as it still wouldn't finish a single compilation. Now it runs 2 seconds or less. Bonus points: This PR will also fix problem reported in https://www.pivotaltracker.com/story/show/184071954 (duplicate errors for nested patterns)
- Loading branch information
Showing
28 changed files
with
443 additions
and
251 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
...va/org/enso/interpreter/bench/benchmarks/semantic/NestedPatternCompilationBenchmarks.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package org.enso.interpreter.bench.benchmarks.semantic; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.nio.file.Paths; | ||
import java.util.AbstractList; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.function.Function; | ||
import org.graalvm.polyglot.Context; | ||
import org.graalvm.polyglot.Value; | ||
import org.openjdk.jmh.annotations.Benchmark; | ||
import org.openjdk.jmh.annotations.BenchmarkMode; | ||
import org.openjdk.jmh.annotations.Fork; | ||
import org.openjdk.jmh.annotations.Measurement; | ||
import org.openjdk.jmh.annotations.Mode; | ||
import org.openjdk.jmh.annotations.OutputTimeUnit; | ||
import org.openjdk.jmh.annotations.Scope; | ||
import org.openjdk.jmh.annotations.Setup; | ||
import org.openjdk.jmh.annotations.State; | ||
import org.openjdk.jmh.annotations.Warmup; | ||
import org.openjdk.jmh.infra.BenchmarkParams; | ||
import org.openjdk.jmh.infra.Blackhole; | ||
|
||
|
||
@BenchmarkMode(Mode.AverageTime) | ||
@Fork(1) | ||
@Warmup(iterations = 3) | ||
@Measurement(iterations = 5) | ||
@OutputTimeUnit(TimeUnit.MILLISECONDS) | ||
@State(Scope.Benchmark) | ||
public class NestedPatternCompilationBenchmarks { | ||
private Value self; | ||
private String benchmarkName; | ||
private String code; | ||
private Context ctx; | ||
|
||
@Setup | ||
public void initializeBenchmark(BenchmarkParams params) throws Exception { | ||
ctx = Context.newBuilder() | ||
.allowExperimentalOptions(true) | ||
.allowIO(true) | ||
.allowAllAccess(true) | ||
.logHandler(new ByteArrayOutputStream()) | ||
.option( | ||
"enso.languageHomeOverride", | ||
Paths.get("../../distribution/component").toFile().getAbsolutePath() | ||
).build(); | ||
|
||
benchmarkName = params.getBenchmark().replaceFirst(".*\\.", ""); | ||
code = """ | ||
type List | ||
Cons a b | ||
Nil | ||
test x = | ||
case x of | ||
List.Nil -> 0 | ||
List.Cons a List.Nil -> a | ||
List.Cons a (List.Cons b List.Nil) -> a+b | ||
List.Cons a (List.Cons b (List.Cons c List.Nil)) -> a+b+c | ||
List.Cons a (List.Cons b (List.Cons c (List.Cons d List.Nil))) -> a+b+c+d | ||
List.Cons a (List.Cons b (List.Cons c (List.Cons d (List.Cons e List.Nil)))) -> a+b+c+d+e | ||
List.Cons a (List.Cons b (List.Cons c (List.Cons d (List.Cons e (List.Cons f List.Nil))))) -> a+b+c+d+e+f | ||
list_of_6 = | ||
List.Cons 1 (List.Cons 2 (List.Cons 3 (List.Cons 4 (List.Cons 5 (List.Cons 6 List.Nil))))) | ||
"""; | ||
} | ||
|
||
@Benchmark | ||
public void sumList(Blackhole hole) throws IOException { | ||
// Compilation is included in the benchmark on purpose | ||
var module = ctx.eval(SrcUtil.source(benchmarkName, code)); | ||
|
||
this.self = module.invokeMember("get_associated_type"); | ||
Function<String,Value> getMethod = (name) -> module.invokeMember("get_method", self, name); | ||
|
||
var list = getMethod.apply("list_of_6").execute(self); | ||
var result = getMethod.apply("test").execute(self, list); | ||
|
||
if (!result.fitsInDouble()) { | ||
throw new AssertionError("Shall be a double: " + result); | ||
} | ||
var calculated = (long) result.asDouble(); | ||
var expected = 21; | ||
if (calculated != expected) { | ||
throw new AssertionError("Expected " + expected + " from sum but got " + calculated); | ||
} | ||
hole.consume(result); | ||
} | ||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
...ne/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/BranchResult.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.enso.interpreter.node.controlflow.caseexpr; | ||
|
||
import com.oracle.truffle.api.nodes.Node; | ||
import org.enso.interpreter.runtime.EnsoContext; | ||
|
||
public record BranchResult(boolean isMatched, Object result) { | ||
|
||
public static BranchResult failure(Node node) { | ||
return new BranchResult(false, EnsoContext.get(node).getBuiltins().nothing()); | ||
} | ||
|
||
public static BranchResult success(Object result) { | ||
return new BranchResult(true, result); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.