Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert Array_Like_Helpers.map to a builtin to reduce stack size #11329

Open
JaroslavTulach opened this issue Oct 15, 2024 · 5 comments · May be fixed by #11363
Open

Convert Array_Like_Helpers.map to a builtin to reduce stack size #11329

JaroslavTulach opened this issue Oct 15, 2024 · 5 comments · May be fixed by #11363
Assignees

Comments

@JaroslavTulach
Copy link
Member

JaroslavTulach commented Oct 15, 2024

@jdunkerley complained about strange stacktraces when invoking .map:

map : (Any -> Any) -> Problem_Behavior | No_Wrap -> Vector Any
map self function on_problems:(Problem_Behavior | No_Wrap)=..Report_Error =
       Array_Like_Helpers.map self function on_problems

we end up with 5 stack frames or so in debugger for each, map etc ?

The stacktraces were much simpler prior to https://github.com/enso-org/enso/pull/8307/files#diff-c5e24473d47930ce7b6fd3f7f643967fcb54fcb01df1e6803438203749988f70R39 when all the logic was just a simple builtin. Now, when there is bunch of wrapper functions in Enso, we see them in the stack. The following program yields an exception:

from Standard.Base import all

main =
    [1, 2, 3, 0, ""] . map (5/)

and the stacktrace is quite deep:

Execution finished with an error: Type error: expected `that` to be Integer, but got Text.
        at <enso> Integer./(Numbers.enso:897:5-60)
        at <enso> Array_Like_Helpers.map.Array_Like_Helpers.map(Array_Like_Helpers.enso:261:45-66)
        at <enso> Array_Like_Helpers.vector_from_function.wrapped_function(Array_Like_Helpers.enso:90:18-27)
        at <enso> Array_Like_Helpers.vector_from_function(Internal)
        at <enso> Array_Like_Helpers.vector_from_function(Array_Like_Helpers.enso:105:15-68)
        at <enso> Array_Like_Helpers.map(Array_Like_Helpers.enso:261:5-79)
        at <enso> Vector.map(Vector.enso:703:9-56)
        at <enso> m.main(m.enso:4:5-31)

Goal: rewrite the logic into a builtin and make the call appear as a single element on the stack - e.g. the above stack should compose of only three lines:

Execution finished with an error: Type error: expected `that` to be Integer, but got Text.
        at <enso> Integer./(Numbers.enso:897:5-60)
        at <enso> map(Internal)
        at <enso> m.main(m.enso:4:5-31)

Btw. such a stack compression should also help with @AdRiley problem of too deep stacktraces as one .map invocation is more than hundred Java stack frames in the interpreter right now:

By having .map as a builtin we lower the stack requirements a lot given how frequently we are using .map & co.

@Akirathan
Copy link
Member

Experiment to collect the biggest (Truffle) stack during execution of Base_Tests

Apply the following patch:

diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java
index f2ac6c965..bcac8eeab 100644
--- a/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java
+++ b/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java
@@ -1,10 +1,13 @@
 package org.enso.interpreter.node;
 
 import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.Truffle;
 import com.oracle.truffle.api.dsl.ReportPolymorphism;
 import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.nodes.NodeInfo;
 import com.oracle.truffle.api.source.SourceSection;
+import java.util.ArrayList;
+import java.util.List;
 import org.enso.compiler.context.LocalScope;
 import org.enso.interpreter.EnsoLanguage;
 import org.enso.interpreter.runtime.scope.ModuleScope;
@@ -71,6 +74,10 @@ public class ClosureRootNode extends EnsoRootNode {
         usedInBinding);
   }
 
+  private static List<String> biggestTruffleStackTrace = List.of();
+  private static int jStackSize;
+  private static boolean shutdownHookRegistered;
+
   /**
    * Executes the node.
    *
@@ -82,9 +89,51 @@ public class ClosureRootNode extends EnsoRootNode {
     if (CompilerDirectives.inCompilationRoot() || CompilerDirectives.inInterpreter()) {
       com.oracle.truffle.api.TruffleSafepoint.poll(this);
     }
+    if (!shutdownHookRegistered) {
+      Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+        System.out.printf("[mylog] biggestStackTrace [java stack size = %d][truffle stack size = %d]:%n",
+            jStackSize,
+            biggestTruffleStackTrace.size());
+        biggestTruffleStackTrace.forEach(stackFrame -> System.out.println("  " + stackFrame));
+      }));
+      shutdownHookRegistered = true;
+    }
+
+    var truffleStack = collectTruffleStackTrace();
+    if (truffleStack.size() > biggestTruffleStackTrace.size()) {
+      biggestTruffleStackTrace = truffleStack;
+      jStackSize = Thread.currentThread().getStackTrace().length;
+    }
     return body.executeGeneric(frame);
   }
 
+  private List<String> collectTruffleStackTrace() {
+    List<String> stackTrace = new ArrayList<>();
+    Truffle.getRuntime().iterateFrames((frameInstance) -> {
+      var callNode = frameInstance.getCallNode();
+      if (callNode != null) {
+        var rootNode = callNode.getRootNode();
+        if (rootNode != null) {
+          var nodeName = rootNode.getName();
+          if (rootNode.getSourceSection() != null) {
+            nodeName += " (" +
+                rootNode.getSourceSection().getSource().getName() +
+                "[" +
+                rootNode.getSourceSection().getStartLine() +
+                ":" +
+                rootNode.getSourceSection().getStartColumn() +
+                "])";
+          }
+          stackTrace.add(nodeName);
+          return null;
+        }
+      }
+      stackTrace.add("<unknown>");
+      return null;
+    });
+    return stackTrace;
+  }
+
   final ExpressionNode getBody() {
     return body;
   }

And run enso --run test/Base_Tests (Warning: will take a long time to run the whole test suite)

Output:

[mylog] biggestStackTrace [java stack size = 1024][truffle stack size = 120]:
  <unknown>
  Array_Like_Helpers.vector_from_function<arg-1> (Array_Like_Helpers.enso[106:26])
  Array_Like_Helpers.vector_from_function<arg-0> (Array_Like_Helpers.enso[106:8])
  Array_Like_Helpers.vector_from_function (Array_Like_Helpers.enso[87:1])
  Array_Like_Helpers.map (Array_Like_Helpers.enso[260:1])
  Vector.map (Vector.enso[702:5])
  Builder.add_warnings (Vector.enso[1421:5])
  Builder.append<arg-1> (Vector.enso[1366:48])
  Builder.append<arg-1> (Vector.enso[1364:33])
  Any.if_not_error (Any.enso[434:5])
  Builder.append<arg-1> (Vector.enso[1363:42])
  Builder.append (Vector.enso[1362:5])
  Array_Like_Helpers.filter.predicate.case predicate<arg-1> (Array_Like_Helpers.enso[347:36])
  Array_Like_Helpers.filter.predicate.case predicate (Array_Like_Helpers.enso[346:20])
  Array_Like_Helpers.map.Array_Like_Helpers.map (Array_Like_Helpers.enso[261:41])
  Array_Like_Helpers.vector_from_function.wrapped_function (Array_Like_Helpers.enso[89:5])
  Array_Like_Helpers.vector_from_function
  Array_Like_Helpers.vector_from_function (Array_Like_Helpers.enso[87:1])
  Array_Like_Helpers.map (Array_Like_Helpers.enso[260:1])
  Vector.map (Vector.enso[702:5])
  Array_Like_Helpers.filter.predicate (Array_Like_Helpers.enso[345:18])
  Vector.type.build.results_multiple (Vector.enso[194:53])
  Vector.type.build_multiple<arg-0> (Vector.enso[249:17])
  Vector.type.build_multiple<arg-1> (Vector.enso[248:51])
  Panic.catch
  Panic.type.handle_wrapped_dataflow_error (Panic.enso[255:5])
  Vector.type.build_multiple<arg-2> (Vector.enso[246:113])
  Vector.type.build_multiple (Vector.enso[245:5])
  Vector.type.build (Vector.enso[192:5])
  Array_Like_Helpers.filter (Array_Like_Helpers.enso[343:1])
  Vector.filter (Vector.enso[557:5])
  File.with_output_stream<arg-2> (File.enso[200:112])
  File.with_output_stream<arg-1> (File.enso[198:167])
  Context.if_enabled (Runtime.enso[187:5])
  File.with_output_stream (File.enso[185:5])
  Local_File_Write_Strategy.write_file_backing_up_old_one.go<arg-1> (Local_File_Write_Strategy.enso[79:38])
  Local_File_Write_Strategy.write_file_backing_up_old_one.go<arg-1> (Local_File_Write_Strategy.enso[75:91])
  Panic.catch
  Local_File_Write_Strategy.write_file_backing_up_old_one.go<arg-1> (Local_File_Write_Strategy.enso[75:67])
  Panic.catch
  Local_File_Write_Strategy.write_file_backing_up_old_one.go<arg-1> (Local_File_Write_Strategy.enso[75:39])
  Panic.catch
  Local_File_Write_Strategy.write_file_backing_up_old_one.go (Local_File_Write_Strategy.enso[58:5])
  Local_File_Write_Strategy.write_file_backing_up_old_one.go.handle_existing_file (Local_File_Write_Strategy.enso[61:9])
  case_branch (Local_File_Write_Strategy.enso[134:40])
  Local_File_Write_Strategy.catch_already_exists.panic_handler (Local_File_Write_Strategy.enso[133:5])
  Panic.catch
  Local_File_Write_Strategy.write_file_backing_up_old_one.go (Local_File_Write_Strategy.enso[58:5])
  Local_File_Write_Strategy.write_file_backing_up_old_one.go.handle_existing_file (Local_File_Write_Strategy.enso[61:9])
  case_branch (Local_File_Write_Strategy.enso[134:40])
  Local_File_Write_Strategy.catch_already_exists.panic_handler (Local_File_Write_Strategy.enso[133:5])
  Panic.catch
  Local_File_Write_Strategy.write_file_backing_up_old_one.go (Local_File_Write_Strategy.enso[58:5])
  Local_File_Write_Strategy.write_file_backing_up_old_one.go.handle_existing_file (Local_File_Write_Strategy.enso[61:9])
  case_branch (Local_File_Write_Strategy.enso[134:40])
  Local_File_Write_Strategy.catch_already_exists.panic_handler (Local_File_Write_Strategy.enso[133:5])
  Panic.catch
  Local_File_Write_Strategy.write_file_backing_up_old_one.go (Local_File_Write_Strategy.enso[58:5])
  Local_File_Write_Strategy.write_file_backing_up_old_one<arg-2> (Local_File_Write_Strategy.enso[115:72])
  Local_File_Write_Strategy.write_file_backing_up_old_one<arg-2> (Local_File_Write_Strategy.enso[114:82])
  Local_File_Write_Strategy.write_file_backing_up_old_one<arg-1> (Local_File_Write_Strategy.enso[54:72])
  Local_File_Write_Strategy.recover_io_and_not_found<arg-1> (Local_File_Write_Strategy.enso[145:80])
  Panic.catch
  Local_File_Write_Strategy.recover_io_and_not_found<arg-1> (Local_File_Write_Strategy.enso[144:52])
  Panic.catch
  Local_File_Write_Strategy.recover_io_and_not_found (Local_File_Write_Strategy.enso[139:1])
  Local_File_Write_Strategy.write_file_backing_up_old_one (Local_File_Write_Strategy.enso[54:1])
  Local_File_Write_Strategy.moving_backup.handle_existing_file (Local_File_Write_Strategy.enso[37:5])
  case_branch (Local_File_Write_Strategy.enso[134:40])
  Local_File_Write_Strategy.catch_already_exists.panic_handler (Local_File_Write_Strategy.enso[133:5])
  Panic.catch
  Local_File_Write_Strategy.moving_backup<arg-1> (Local_File_Write_Strategy.enso[36:56])
  Local_File_Write_Strategy.recover_io_and_not_found<arg-1> (Local_File_Write_Strategy.enso[145:80])
  Panic.catch
  Local_File_Write_Strategy.recover_io_and_not_found<arg-1> (Local_File_Write_Strategy.enso[144:52])
  Panic.catch
  Local_File_Write_Strategy.recover_io_and_not_found (Local_File_Write_Strategy.enso[139:1])
  Local_File_Write_Strategy.moving_backup (Local_File_Write_Strategy.enso[36:1])
  case_branch (File_Write_Strategy.enso[58:46])
  File_Write_Strategy.write (File_Write_Strategy.enso[55:5])
  File_Write_Strategy.write_handling_dry_run<arg-1> (File_Write_Strategy.enso[75:43])
  File_Write_Strategy.write_handling_dry_run (File_Write_Strategy.enso[74:5])
  Writable_File.write_handling_dry_run<arg-0> (Writable_File.enso[55:9])
  Writable_File.write_handling_dry_run (Writable_File.enso[54:5])
  Text.write<arg-1> (Write_Extensions.enso[61:26])
  Any.if_not_error (Any.enso[434:5])
  Text.write (Write_Extensions.enso[59:1])
  File_Spec.add_specs.<internal-574><arg-0> (File_Spec.enso[713:13])
  File_Spec.add_specs.<internal-574><arg-0> (File_Spec.enso[713:13])
  File_Spec.add_specs.<internal-574><arg-1> (File_Spec.enso[691:78])
  case_branch.case <internal-559> (Group.enso[30:56])
  Helpers.execute_spec_code<arg-1> (Helpers.enso[56:36])
  Panic.catch
  Panic.type.recover (Panic.enso[185:5])
  Helpers.execute_spec_code (Helpers.enso[55:1])
  case_branch<arg-3> (Helpers.enso[48:37])
  case_branch<arg-1> (Helpers.enso[47:46])
  Runtime.no_inline
  Duration.type.time_execution (Duration.enso[129:5])
  case_branch (Helpers.enso[47:20])
  Helpers.run_spec (Helpers.enso[45:1])
  case_branch.test_results (Helpers.enso[24:38])
  Array_Like_Helpers.map.Array_Like_Helpers.map (Array_Like_Helpers.enso[261:41])
  Array_Like_Helpers.vector_from_function.wrapped_function (Array_Like_Helpers.enso[89:5])
  Array_Like_Helpers.vector_from_function
  Array_Like_Helpers.vector_from_function (Array_Like_Helpers.enso[87:1])
  Array_Like_Helpers.map (Array_Like_Helpers.enso[260:1])
  Vector.map (Vector.enso[702:5])
  case_branch (Helpers.enso[23:17])
  Helpers.run_specs_from_group (Helpers.enso[19:1])
  case_branch (Suite.enso[96:29])
  Suite.run_with_filter.junit_sb_builder (Suite.enso[92:33])
  Array_Like_Helpers.each.Array_Like_Helpers.each (Array_Like_Helpers.enso[300:34])
  Range.each.go<arg-1> (Range.enso[262:36])
  Range.each.go<arg-2> (Range.enso[261:68])
  Range.each.go (Range.enso[260:13])
  Test_Reporter.wrap_junit_testsuites (Test_Reporter.enso[24:1])
  Suite.run_with_filter (Suite.enso[63:5])
  Main.main (Main.enso[95:1])
  org.graalvm.polyglot.Value<Function>.execute

We can see that there are a lot of frames related to Vector.map. I will do the same experiment at the end to see how the biggest stack trace was affected.

@JaroslavTulach
Copy link
Member Author

I was just profiling one of the "sieve benchmarks" (as introduced by #11351):

sbt:std-benchmarks> withDebug --dumpGraphs benchOnly -- Sieve_Lazy

and this is the beginning of the compilation:

Array_Like_Helpers everywhere

Array_Like_Helpers & Vector functionality occupies a lot of time. The hope of having a builtin is that we can remove most of the initial compilation overhead as processing builtin code will be easier for the compiler.

@enso-bot
Copy link

enso-bot bot commented Oct 18, 2024

Pavel Marek reports a new STANDUP for today (2024-10-18):

Progress: - Experimenting with a regression test for stack trace sizes - #11363 (comment)

  • Encountered Chrome inspector issue - Hitting Debug.breakpoint with chrome inspector fails with NullPointerException #11362
  • Idea to retain 100% backward compatibility in the generated IR: The annotation processor generates Scala classes.
    • 68a070b
    • Scala classes can be generated as resources, and sbt can be easily convinced to pick these generated scala classes and compile them.
    • There is no way to provide default arguments to Java methods from Scala, so we need to generate Scala classes.
      • Scala compiler encodes information about default arguments into special class @ScalaSignature annotation. It should be finished by 2024-10-24.

@JaroslavTulach
Copy link
Member Author

There is an option to

  • no way to provide default arguments to Java methods from Scala

use overloaded methods:

  public int minus(int a, int b) {
    return a-b;
  }
  public int minus(int a) {
    return minus(a, 5);
  }

when compiled with -parameters argument, then the methods can be called as:

minus(b=7, a=3)
minus(a=7)

that provides (limited) support for default arguments. Things will get trickier with the increased number of arguments, but when one doesn't need all combination, but only few - as is the case with IR.copy most of the time, then overloaded methods might be a decent solution.

PS: You are allowed to modify Scala source to keep the number of needed overloads up to three. If the Scala code continues to compile against Scala IR and keeps number of needed Java IR methods overloads up to three, then it is worth the change even right now.

PPS: Probably better to explicitly spell the default values and work with overloads than generate Scala code...

@enso-bot
Copy link

enso-bot bot commented Oct 21, 2024

Pavel Marek reports a new STANDUP for today (2024-10-21):

Progress: - Started converting Array_Like_Helpers.vector_from_function to a builtin.

  • Still a lot of tests fail
  • Will introduce a regression test - The test will run n nested map calls with 256k stack.
    • On develop, only 6 nested calls are possible. It should be finished by 2024-10-24.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 🔧 Implementation
Development

Successfully merging a pull request may close this issue.

2 participants