Search code examples
javascalabyte-buddy

@tailrec optimisation and @Advice.OnMethodEnter behaviour


I want to instrument a Scala method that is annotated with @tailrec. This causes the compiler to optimize the recursive method to a loop as long as it is tail recursive.

For example:

  @tailrec
  private def tailRecFunction(i: Int): Unit = {
    if (i > 0) {
      println(s"$i on Thread ${Thread.currentThread().getId}")
      Thread.sleep(20)
      tailRecFunction(i - 1)
    } else {
      println(s"0 on Thread ${Thread.currentThread().getId}")
      Thread.sleep(20)
      ()
    }
  }

And I have the method adviced as follows:

        @Override
        public ElementMatcher<? super MethodDescription> getMethodMatcher() {
            return named("tailRecFunction");
        }

        @Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
        public static void onEnter() {
            logger.info("onMethodEnter");
        }

        @Advice.OnMethodExit(suppress = Throwable.class, inline = false)
        public static void onExit() {
            logger.info("onMethodExit");
        }

This causes the following log-output for tailRecFunction(10):

2021-08-01 22:28:03,364 [main] INFO onMethodEnter
10 on Thread 1
9 on Thread 1
8 on Thread 1
7 on Thread 1
6 on Thread 1
5 on Thread 1
4 on Thread 1
3 on Thread 1
2 on Thread 1
1 on Thread 1
0 on Thread 1
2021-08-01 22:28:03,633 [main] INFO onMethodExit

Would there be any way on how I could instrument this method and actually get the onMethodEnter and onMethodExit call on every method call instead of one enter and exit?


Solution

  • Because tail call elimination means, at the bytecode level, that there's only one entry and exit (in terms of execution) of the method, for a tool like Byte Buddy which works at the level of the byte code can only ever see one entry/exit.

    Note that Byte Buddy is designed for Java: it only really understands bytecode which would be similar to what a Java compiler would emit. If Byte Buddy can instrument loop entry and exit, it might be able to work with a tail recursive method in Scala.

    As far as I know, there's no annotation or compiler option to disable tail call elimination in Scala, so this couldn't be accomplished with Byte Buddy. Assuming that you're trying to augment code which you own, it might be possible to instrument every iteration via a macro.