GithubHelp home page GithubHelp logo

gizmo's Introduction

Gizmo

A bytecode generation library.

Version GitHub Actions Status

About

Gizmo aims at simplifying bytecode generation. It is heavily used by Quarkus.

Usage

For more information about how to use Gizmo, please see USAGE.adoc.

Build

Gizmo is available on Maven Central but you can build it locally:

mvn clean install

Release

# Bump version and create the tag
mvn release:prepare -Prelease
# Build the tag and push to OSSRH
mvn release:perform -Prelease

The staging repository is automatically closed. The sync with Maven Central should take ~30 minutes.

gizmo's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gizmo's Issues

Reproducible bytecode output

Calling the same sequence of Gizmo methods with the same parameters at different occasions should yeld binary equal byte code.

A PR follows.

Add more if statements

Currently, we only have ifNonZero and ifNull. It would be useful to add more variants, ifgt or iflt at least.

Shorter bytecode sequences to record array and Set values

In Quarkus, and in particular in ArC, there's the frequent need to generate code to record a Set of Class constants; for example any created InjectableBean will have a field private final Set types; which needs to be initialized in the constructor.

The code being generated today is very verbose and generates intermediate variables for each individual element of the array; for example, focusing on the "type" use case we'd typically have:

		Object[] var1 = new Object[2];
		Class var2 = someClazz();
		var1[0] = var2;
		Class var3 = someClazz();
		var1[1] = var3;
		Set var4 = Set.of(var1);
		this.types = var4;

as bytecode:

         0: iconst_2
         1: anewarray     #2                  // class java/lang/Object
         4: astore_1
         5: invokestatic  #3                  // Method someClazz:()Ljava/lang/Class;
         8: astore_2
         9: aload_1
        10: iconst_0
        11: aload_2
        12: aastore
        13: invokestatic  #3                  // Method someClazz:()Ljava/lang/Class;
        16: astore_3
        17: aload_1
        18: iconst_1
        19: aload_3
        20: aastore
        21: aload_1
        22: invokestatic  #4                  // InterfaceMethod java/util/Set.of:([Ljava/lang/Object;)Ljava/util/Set;
        25: astore        4
        27: aload_0
        28: aload         4
        30: putfield      #5                  // Field types:Ljava/util/Set;

While this could have been expressed better as:

this.types = Set.of(someClazz(), someClazz());
         0: aload_0
         1: invokestatic  #3                  // Method someClazz:()Ljava/lang/Class;
         4: invokestatic  #3                  // Method someClazz:()Ljava/lang/Class;
         7: invokestatic  #7                  // InterfaceMethod java/util/Set.of:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Set;
        10: putfield      #5                  // Field types:Ljava/util/Set;

There's several minor benefits such ask bytecode size and the ability to skip allocating the array, but most importantly the lack of need for local variables would allow us to save space in the Constant Pool.

It would be nice to introduce some ad-hoc optimisations for these common patterns as required by ArC; we could simplify quite some code by just having specific support for recording immutable Sets and arrays.

I'm suggesting to add both optimisations:

ad-hoc optimized method for immutable, small Set creation

As made clear above, there's many benefits from this and set creation seems to be a very common need - in both ArC and likely other components too.

ad-hoc optimized method for storing arrays

This should help cover many other usecases; there are cases in which we need to store arrays that get non-trivial in size, and not all of them get folded into a Set. Having ad-hoc optimisations for arrays would give some more flexibility and address the largest consumers of space in the constant table.

Automatic elision of variable allocations

Ideally, but probably a much larger effort, it would be nice to see if gizmo could automatically chain directly consumed ResultHandler(s) which are consumed only once so to not need local variables in such cases but use the stack.

That would have a more pervasive effect in removing many more variables, however seems to require some substantial design changes and I also suspect it wouldn't be able to generate the above optimal bytecode, so I'd focus on the Set and Array optimisations first.

exception with

    [error]: Build step io.quarkiverse.cxf.deployment.QuarkusCxfProcessor#build threw an exception: java.lang.IllegalArgumentException: Unsupported api 589824
    at org.objectweb.asm.ClassVisitor.<init>(ClassVisitor.java:76)
    at io.quarkus.gizmo.GizmoClassVisitor.<init>(GizmoClassVisitor.java:22)

when I use
final GizmoClassVisitor cw = new GizmoClassVisitor(Gizmo.ASM_API_VERSION, file,
classOutput.getSourceWriter(postFixedName));

java 8 version seems to be 589824.
I do not understand why Gizmo produce a such error.

ResultHandle getType() has different access level than the ResultType that gets passed back

I just tripped over a little inconsistency in ResultHandle. The getResultType method is public:

    public ResultType getResultType() {
        return this.resultType;
    }

... but the ResultType class is package-private.

image

I'd submit a PR to either make the method package private or make the type public, but I wasn't sure which was preferable. Maybe there's a reason for it to be package private?

Very confusing exception when using storing an `int` constant to a `long` field

This code exhibits the exception:

public class GizmoTest {
    
    public static class MyEntity {
        boolean isPersistent;
        long version;
    }
    
    public static void main(String[] args) {
        try(ClassCreator modelClass = ClassCreator.builder().className("test")
                .classOutput((foo, bar) -> {
                    System.err.println("Printing "+foo+": "+bar);
                })
                .build()){

            FieldDescriptor fieldDescriptor = FieldDescriptor.of(MyEntity.class, "version", long.class);
            try(MethodCreator creator = modelClass.getMethodCreator("foo", void.class, MyEntity.class)){
                ResultHandle entityParam = creator.getMethodParam(0);
                creator.writeInstanceField(fieldDescriptor, entityParam, creator.load(0));
                creator.returnValue(creator.loadNull());
            }
        
        }
    }
}

When running it, I get:

Exception in thread "main" java.lang.NegativeArraySizeException
	at org.objectweb.asm.Frame.merge(Frame.java:1222)
	at org.objectweb.asm.MethodWriter.computeAllFrames(MethodWriter.java:1607)
	at org.objectweb.asm.MethodWriter.visitMaxs(MethodWriter.java:1543)
	at org.objectweb.asm.MethodVisitor.visitMaxs(MethodVisitor.java:762)
	at io.quarkus.gizmo.MethodCreatorImpl.write(MethodCreatorImpl.java:107)
	at io.quarkus.gizmo.ClassCreator.close(ClassCreator.java:173)
	at io.quarkus.panache.rx.deployment.GizmoTest.main(GizmoTest.java:36)

Which is very hard to explain the cause is the load(0) which should be a load(0l)

Implementing generic interfaces

While I was trying to generate Microprofile config converters at build time for user-defined classes, I came into struggle about how to specify the generic class when implementing the interface. I tried to following approaches.

Implementing via class

 try (ClassCreator classCreator = ClassCreator.builder()
                    .className(converterClassName)
                    .interfaces(Converter.class)
                    .classOutput(gizmoAdaptor)
                    .build()) {
...
}

Unsurprisingly, this wasn't working since the generic wasn't specified anywhere.

Via signature

After some research, I tried to use the JVM signature directly, which did not lead me to the desired result.

final String s = "Ljava/lang/Object;Lorg/eclipse/microprofile/config/spi/Converter<L" + className.replace(".", "/") + ";>;";

 try (ClassCreator classCreator = ClassCreator.builder()
                    .className(converterClassName)
                    .signature(s)
                    .classOutput(gizmoAdaptor)
                    .build()) {
...
}

With className as the fully qualified generic class name.

Is there a way the use generics with Gizmo at all? Maybe is there some (internal) documentation about how to use Gizmo?

Upgrade to ASM 9

Hi,

Byte Buddy is using ASM9 now; I'd like to update the Byte Buddy used in Hibernate ORM.

Technically the version used by Hibernate via Byte Buddy doesn't have to be the same as Gizmo as ASM is being shaded, but I'd not want too much trouble :)

@stuartwdouglas , WDYT ?

Thanks

Loan pattern api for ClassCreator to control life cycle of object from API

I would be nice if ClassCreator provides a static API to produce an instance of ClassCreator with try/catch with resource inside API, instead to delegate to a calling function the responsibility to implement try/catch

public static void withClassCreator(ClassCreator.Builder builder, Consumer<ClassCreator> consumer){
        try(ClassCreator classCreator = builder.build()){
            consumer.accept(classCreator);
        }
    } 

An example of usage

withClassCreator(
                ClassCreator.builder()
                        .classOutput(cl)
                        .className("com.MyTest")
                        .interfaces(MyInterface.class),
                creator -> {
                    MethodCreator method = creator.getMethodCreator("transform", String.class, String.class);
                    ResultHandle existingVal = method.load("complete");
                    TryBlock tb = method.tryBlock();
                    tb.addCatch(IllegalStateException.class).load(34);
                    tb.invokeStaticMethod(MethodDescriptor.ofMethod(ExceptionClass.class.getName(), "throwIllegalState", "V"));
                });

Support setting method parameter names

I need to generate methods with Bean Validation annotations, such as foo(@NotEmpty String param) and if I can't set the method parameter name, then I can't obtain the right validation constraint cause (param).

FunctionCreator - incorrecly allocated captured values inside an if statement

If you create a function with capture with an if statement inside the method body and attempt to use the captured result handles inside the statement you'll get java.lang.VerifyError: Bad local variable type.

The generated bytecode looks like this:

Method get : Ljava/lang/Object;
(
    // (no arguments)
) {
    ** label1
    ** label2
    LDC (Boolean) true
    IFNE label3
    ** label4
    ** label5
    GOTO label6
    ** label3
    ALOAD 1
    LDC (String) "-func"
    // Method descriptor: (Ljava/lang/String;)Ljava/lang/String;
    INVOKEVIRTUAL java/lang/String#concat
    ARETURN
    ** label6
    ALOAD 0
    // Field descriptor: Ljava/lang/String;
    GETFIELD com/MyTest$$function$$3#f0
    ASTORE 1
    ACONST_NULL
    ARETURN
    ** label7
    ** label8
 }

Note that the captured result handle is allocated after IFNE.

I'll send a PR with the test but I'm not sure how to fix this properly.

Allow generating interfaces

ClassCreator currently calls ASM ClassVisitor with ACC_PUBLIC | ACC_SUPER | ACC_SYNTHETIC | extraAccess around here:

cv.visit(Opcodes.V11, ACC_PUBLIC | ACC_SUPER | ACC_SYNTHETIC | extraAccess, className, signature, superClass, interfaces);

which is only suitable for common classes, but not interfaces. To make interfaces possible, we need

  • A way to set ACC_INTERFACE and ACC_ABSTRACT flags
  • A way to unset ACC_SUPER

Add support of `if (x instanceof y)`

At the moment, there is no specific method to implement:

if (x instanceOf y) {
    // true branch
} else {
   // false branch
}

There is a checkCast but that's not testing, just doing the cast.

missing enum support in AnnotatedElement

Hello,
I try to use jandex to add annotation like:
@XmlAccessorType(XmlAccessType.FIELD)

So I use the following code:
classCreator.addAnnotation(AnnotationInstance.create(
DotName.createSimple(XmlAccessorType.class.getName()), null,
new AnnotationValue[]{
AnnotationValue.createEnumValue("value",
DotName.createSimple(XmlAccessType.class.getName()),
String.valueOf(XmlAccessType.FIELD))}));

but I try a lot of different value for string value: "FIELD", String.valueOf(XmlAccessType.FIELD), XmlAccessType.FIELD.toString(), XmlAccessType.FIELD.toString(), XmlAccessType.FIELD.name() but it never work.
AnnotationValue.createStringValue work but impossible to have a string value for a regular enum.
generated class has the annotation but without value:
@XmlAccessorType
public class Reply {
instead of
@XmlAccessorType(XmlAccessType.FIELD)
public class Reply {

Indeed it seems that a code is missing in
https://github.com/quarkusio/gizmo/blob/0a6f1bd53288a76fbe6b28496833f644a96db710/src/main/java/io/quarkus/gizmo/AnnotatedElement.java
to handle Kind.Enum

I think something like
else if (member.kind() == AnnotationValue.Kind.ARRAY) {
Class enumType = (Class) Class.forName(member.asEnumType().toString()); Enum enumval = Enum.valueOf(enumType, member.asEnum());
ac.addValue(member.name(),enumval );
}

Smart `cast` operation

People are frequently tripping over casting, because primitive casts, object casts, and boxing/unboxing are all done in different ways. Provide a smart cast operation with the following support:

  • Cast primitives to primtives as per convertPrimitive
  • Cast objects to objects as per checkCast

And optionally:

  • Cast primitives to primitive box objects by doing primitive-to-primitive conversion (if necessary) followed by BoxType.valueOf(...)
  • Cast primitive box objects to primitives by doing BoxType.xxxValue() followed by primitive-to-primitive conversion (if necessary)
  • Cast box objects to box objects by unboxing, converting, and then re-boxing

Since checkCast allows a String argument, primitive types and box types would need to be recognized by name.

BytecodeCreator.invokeSpecialMethod() cannot be used for interface default methods

It should be possible to use the invokeSpecial instruction to invoke an interface default method. However, BytecodeCreator.invokeSpecialMethod() always adds an InvokeOperation with interfaceMethod=false (constant pool contains Methodref instead of InterfaceMethodref).

We could either add a special method, something like BytecodeCreator.invokeSpecialInterfaceMethod() or add a variant with interfaceMethod boolean parameter.

ClassOutput doesn't declare any exceptions

The ClassOutput interface methods don't declare any exceptions, even though they would frequently be used to write a class to disk. They should declare IOException probably. But of course this will affect source-level compatibility.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.