Skip to content

Conversation

@uschindler
Copy link
Contributor

@uschindler uschindler commented Apr 30, 2025

First step in getting rid of ASM dependency:

Java 24 added the classfile API (https://openjdk.org/jeps/484) in non-preview mode. This allows to generate class files with a builder-based API (unfurtunately its is not the usual ASM visitor desaster, it is more a lambda-inside-lambda desaster).

This PR removes the license files and all ASM dependencies from runtime code (and module descriptors). The code was rewritten. The rewrite may not yet be the most elegant lambda-way to make it, but the PR is easier to review with not so many changes and reshuffled code.

We still have ASM core as a dependncy for Gradle's APIJAR generator, but this one can also be removed soon.

Copy link
Contributor

@dweiss dweiss left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty cool.

Copy link
Contributor

@rjernst rjernst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! It seems like ASM translated relatively well (in this case) to the ClassFile API (much of it was just changing imports/methods to call but looks very similar).

…od describeConstable(). For normal types the optional can never be empty
Copy link
Contributor

@jdconrad jdconrad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I appreciate the fairly direct replacement as the first step since it made it so easy to review.

Copy link
Member

@rmuir rmuir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great! also, I trust the tests here have good coverage. Thank you for doing this.

@uschindler
Copy link
Contributor Author

I did some final cleanup and added changes entry. Will merge soon.

Thanks for the reviews by @rjernst and @jdconrad, very appreciated.

@uschindler
Copy link
Contributor Author

uschindler commented Apr 30, 2025

Looks great! It seems like ASM translated relatively well (in this case) to the ClassFile API (much of it was just changing imports/methods to call but looks very similar).

The hard part was more to understand and figure out how it works. After that transforming the Antlr visitor to Classfile API was trivial.

Very hard was that I was unable to figure out how to get a ConstantDesc which is needed for the bootstrapper from a String or Integer. I had to figure out first that all those classes implement ConstantDesc so you can just pass the String instance as parameter where type ConstantDesc is expected:

image

There are still some nice goodies missing in Classfile API:

  • No replacement for GeneratorAdaptor#loadArgs and a better named function like GeneratorAdaptor#loadThis (as synonmy for aload(0)). This would make the typical "pass all parameters automatically to the invoke super call" in ctors or overridden methods easier to write.
  • More methods with TypeKind to also generate compare code without knowing the exact tyes.

@uschindler uschindler merged commit 1b5c25f into apache:main Apr 30, 2025
7 checks passed
@uschindler uschindler deleted the dev/rewriteExpressionClassfileAPI branch April 30, 2025 21:06
@dweiss
Copy link
Contributor

dweiss commented Apr 30, 2025

The hard part was more to understand and figure out how it works.

For the fun of it, I asked chatgpt to do it. Uwe - you're still much, much better. :)

@rmuir
Copy link
Member

rmuir commented Apr 30, 2025

lol the day we have an army of Uwe AIs, I feel like I've already seen this movie

@dweiss
Copy link
Contributor

dweiss commented May 1, 2025

Uncanny resemblance!

asfgit pushed a commit that referenced this pull request May 1, 2025
…fficial documentation of classfile API recommends (#14597)
@uschindler
Copy link
Contributor Author

I made a small followup commit to make usage more like recommended by Classfile API docs: e2d0685

@uschindler
Copy link
Contributor Author

uschindler commented May 1, 2025

Finally here is an example Classfile output with this PR for this code:

  public void testNamespacesWithoutDirectMH() throws Exception {
    Map<String, MethodHandle> functions =
        Map.of(
            "foo.bar",
            MethodHandles.constant(double.class, 9),
            "bar.foo",
            MethodHandles.identity(double.class));
    Expression expr = compile("foo.bar() + bar.foo(7)", functions);
    assertEquals(16, expr.evaluate(null), DELTA);
  }
//  (version 24 : 68.0, no super bit)
public final class org.apache.lucene.expressions.js.JavascriptCompiler$CompiledExpression extends org.apache.lucene.expressions.Expression {
  
  // Method descriptor #6 (Ljava/lang/String;[Ljava/lang/String;)V
  // Stack: 3, Locals: 3
  public JavascriptCompiler$CompiledExpression(java.lang.String arg0, java.lang.String[] arg1);
    0  aload_0 [this]
    1  aload_1 [arg0]
    2  aload_2 [arg1]
    3  invokespecial org.apache.lucene.expressions.Expression(java.lang.String, java.lang.String[]) [8]
    6  return

  
  // Method descriptor #10 ([Lorg/apache/lucene/search/DoubleValues;)D
  // Stack: 5, Locals: 2
  public double evaluate(org.apache.lucene.search.DoubleValues[] arg0) throws java.io.IOException;
     0  ldc <Dynamic> 0 func0 java.lang.invoke.MethodHandle [23]
     2  invokevirtual java.lang.invoke.MethodHandle.invokeExact() : double [29]
     5  ldc <Dynamic> 1 func1 java.lang.invoke.MethodHandle [34]
     7  ldc2_w <Double 7.0> [35]
    10  invokevirtual java.lang.invoke.MethodHandle.invokeExact(double) : double [39]
    13  dadd
    14  dreturn
    15  aload_0 [this]
    16  invokestatic org.apache.lucene.expressions.js.JavascriptCompiler.patchStackTrace(java.lang.Throwable, org.apache.lucene.expressions.Expression) : java.lang.Throwable [45]
    19  athrow
      Exception Table:
        [pc: 0, pc: 15] -> 15 when : java.lang.Throwable
      Stack map table: number of frames 1
        [pc: 15, same_locals_1_stack_item, stack: {java.lang.Throwable}]

Bootstrap methods:
  0 : # 19 invokestatic org/apache/lucene/expressions/js/JavascriptCompiler.dynamicConstantBootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/invoke/MethodHandle;
	Method arguments:
		#12 foo.bar,
  1 : # 19 invokestatic org/apache/lucene/expressions/js/JavascriptCompiler.dynamicConstantBootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/invoke/MethodHandle;
	Method arguments:
		#31 bar.foo
}

The previous version as generated by the old code in branch_10x is:

//  (version 21 : 65.0, super bit)
public final class org.apache.lucene.expressions.js.JavascriptCompiler$CompiledExpression extends org.apache.lucene.expressions.Expression {
  
  // Method descriptor #6 (Ljava/lang/String;[Ljava/lang/String;)V
  // Stack: 3, Locals: 3
  public JavascriptCompiler$CompiledExpression(java.lang.String arg0, java.lang.String[] arg1);
    0  aload_0 [this]
    1  aload_1 [arg0]
    2  aload_2 [arg1]
    3  invokespecial org.apache.lucene.expressions.Expression(java.lang.String, java.lang.String[]) [8]
    6  return

  
  // Method descriptor #10 ([Lorg/apache/lucene/search/DoubleValues;)D
  // Stack: 5, Locals: 2
  public double evaluate(org.apache.lucene.search.DoubleValues[] arg0) throws java.io.IOException;
     0  ldc <Dynamic> 0 func0 java.lang.invoke.MethodHandle [27]
     2  invokevirtual java.lang.invoke.MethodHandle.invokeExact() : double [33]
     5  ldc <Dynamic> 1 func1 java.lang.invoke.MethodHandle [38]
     7  ldc2_w <Double 7.0> [39]
    10  invokevirtual java.lang.invoke.MethodHandle.invokeExact(double) : double [43]
    13  dadd
    14  dreturn
    15  aload_0 [this]
    16  invokestatic org.apache.lucene.expressions.js.JavascriptCompiler.patchStackTrace(java.lang.Throwable, org.apache.lucene.expressions.Expression) : java.lang.Throwable [47]
    19  athrow
      Exception Table:
        [pc: 0, pc: 14] -> 15 when : java.lang.Throwable
      Stack map table: number of frames 1
        [pc: 15, same_locals_1_stack_item, stack: {java.lang.Throwable}]

Bootstrap methods:
  0 : # 23 invokestatic org/apache/lucene/expressions/js/JavascriptCompiler.dynamicConstantBootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/invoke/MethodHandle;
	Method arguments:
		#16 foo.bar,
  1 : # 23 invokestatic org/apache/lucene/expressions/js/JavascriptCompiler.dynamicConstantBootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/invoke/MethodHandle;
	Method arguments:
		#35 bar.foo
}

This is the same code, all operators are identical. Only differences in constant pool. The changes here are only the class flags no longer having obsolete ACC_SUPER and the Classfile API automatically generates class files for the current Java version (no need to configure anything).

Theres a slight difference in the try/catch. The old version was a bit incorrect, because the return statement was not part of the try block. But this does not matter as a return statement can't throw exceptions.

@uschindler
Copy link
Contributor Author

I made some followup changes here: #14602

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants