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

Closed
JaroslavTulach opened this issue Oct 15, 2024 · 15 comments · Fixed by #11363
Closed

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

JaroslavTulach opened this issue Oct 15, 2024 · 15 comments · 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.

@enso-bot
Copy link

enso-bot bot commented Oct 22, 2024

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

Progress: - Managed to reduce the stack size for every Vector.map call by 1:

  • On develop, every Vector.map occupies 6 frames, on my PR it occupies 5 frames.
  • Is that enough? It should be finished by 2024-10-24.

@JaroslavTulach
Copy link
Member Author

Please more @Tail_Calls to almost all methods that invoke the builtin.

@enso-bot
Copy link

enso-bot bot commented Oct 24, 2024

Pavel Marek reports a new STANDUP for yesterday (2024-10-23):

Progress: - Reduced Java stack frames for each Vector.map call from 150 to 103 and introduced regression test - 2024-10-22.txt

  • Ready for review and integration It should be finished by 2024-10-24.

@enso-bot
Copy link

enso-bot bot commented Oct 24, 2024

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

Progress: - Fixing tests

@enso-bot
Copy link

enso-bot bot commented Oct 25, 2024

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

Progress: - Still fixing JVM, stdlib and native image tests. It should be finished by 2024-10-29.

@enso-bot
Copy link

enso-bot bot commented Oct 28, 2024

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

Progress: - Tests fixed, benchmarks scheduled #11363 (comment).

  • If the benches are OK, will merge tomorrow, if not, will tweak a little. It should be finished by 2024-10-29.

@enso-bot
Copy link

enso-bot bot commented Oct 29, 2024

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

Progress: - When checking benchmark regressions locally, bumped into #11437

  • Many stdlib benchmarks are not reliable, because of the infinite opt-deopt loop.
  • Trying to run another iteration of benchmarks. It should be finished by 2024-10-29.

@enso-bot
Copy link

enso-bot bot commented Oct 30, 2024

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

Progress: - Created some issues for unstable or failing benchmarks.

@enso-bot
Copy link

enso-bot bot commented Nov 4, 2024

Pavel Marek reports a new STANDUP for today (2024-11-04):

Progress: - Review #11399

@enso-bot
Copy link

enso-bot bot commented Nov 5, 2024

Pavel Marek reports a new STANDUP for today (2024-11-05):

Progress: - Ensure that there is at most one native-image subprocess from sbt: #11497

@mergify mergify bot closed this as completed in #11363 Nov 6, 2024
@github-project-automation github-project-automation bot moved this from 👁️ Code review to 🟢 Accepted in Issues Board Nov 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 🟢 Accepted
Development

Successfully merging a pull request may close this issue.

2 participants