-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
wow much simpler runtime code generation!
- Loading branch information
1 parent
551c883
commit 97155bf
Showing
8 changed files
with
205 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
common/src/main/java/dev/progames723/hmmm/code_gen/AnnotationDefinition.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package dev.progames723.hmmm.code_gen; | ||
|
||
import java.util.Map; | ||
|
||
public record AnnotationDefinition( | ||
String descriptor, | ||
Map<String, Object> values | ||
) {} |
33 changes: 33 additions & 0 deletions
33
common/src/main/java/dev/progames723/hmmm/code_gen/ClassDefinition.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package dev.progames723.hmmm.code_gen; | ||
|
||
import org.jetbrains.annotations.Nullable; | ||
import org.objectweb.asm.Opcodes; | ||
|
||
import java.util.List; | ||
|
||
public record ClassDefinition( | ||
String className, | ||
@Nullable String superName, //set to null if you dont have a super class or just extend Object | ||
String[] interfaces, | ||
int version, | ||
int access, | ||
List<AnnotationDefinition> annotations, | ||
List<ClassDefinition> innerClasses, | ||
List<FieldDefinition> fields, | ||
List<MethodDefinition> methods, | ||
boolean createDefaultConstructor | ||
) { | ||
public ClassDefinition( | ||
String className, | ||
@Nullable String superName, //same here | ||
String[] interfaces, | ||
int access, | ||
List<AnnotationDefinition> annotations, | ||
List<ClassDefinition> innerClasses, | ||
List<FieldDefinition> fields, | ||
List<MethodDefinition> methods, | ||
boolean createDefaultConstructor | ||
) { | ||
this(className, superName, interfaces, Opcodes.V1_8, access, annotations, innerClasses, fields, methods, createDefaultConstructor); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
common/src/main/java/dev/progames723/hmmm/code_gen/FieldDefinition.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package dev.progames723.hmmm.code_gen; | ||
|
||
import java.util.List; | ||
|
||
public record FieldDefinition( | ||
String name, | ||
String descriptor, | ||
int access, | ||
List<AnnotationDefinition> annotations, | ||
Object initialFieldValue | ||
) {} |
15 changes: 15 additions & 0 deletions
15
common/src/main/java/dev/progames723/hmmm/code_gen/MethodDefinition.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package dev.progames723.hmmm.code_gen; | ||
|
||
import org.objectweb.asm.MethodVisitor; | ||
|
||
import java.util.List; | ||
import java.util.function.Consumer; | ||
|
||
public record MethodDefinition( | ||
String name, | ||
String descriptor, | ||
int access, | ||
List<AnnotationDefinition> annotations, | ||
String[] exceptions, | ||
Consumer<MethodVisitor> instructions | ||
) {} |
131 changes: 131 additions & 0 deletions
131
common/src/main/java/dev/progames723/hmmm/code_gen/RuntimeClassGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package dev.progames723.hmmm.code_gen; | ||
|
||
import org.objectweb.asm.*; | ||
import org.objectweb.asm.util.CheckClassAdapter; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
|
||
public class RuntimeClassGenerator { | ||
private static boolean DEBUG = false;//set manually or through reflection, dont leave as true in production | ||
|
||
private static final GeneratedClassLoader loader = new GeneratedClassLoader(); | ||
|
||
private static final class GeneratedClassLoader extends ClassLoader { | ||
public Class<?> defineClass(String className, byte[] data) {return defineClass(className, data, 0, data.length);} | ||
} | ||
|
||
public static Class<?> generateClass(ClassDefinition clazz) { | ||
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); | ||
|
||
//the class thing uhh idfk what im doing so i will now pray for it to start working correctly on my first try | ||
cw.visit( | ||
clazz.version(), | ||
clazz.access(), | ||
clazz.className(), | ||
null, | ||
clazz.superName() != null ? clazz.superName() : "java/lang/Object", | ||
clazz.interfaces() | ||
); | ||
|
||
//class annotation things | ||
for (AnnotationDefinition annotation : clazz.annotations()) { | ||
AnnotationVisitor av = cw.visitAnnotation( | ||
annotation.descriptor(), | ||
true | ||
); | ||
annotation.values().forEach(av::visit); | ||
av.visitEnd(); | ||
} | ||
|
||
//fields yeah | ||
for (FieldDefinition field : clazz.fields()) { | ||
generateField(cw, field); | ||
} | ||
|
||
//default constructor lol | ||
if (clazz.createDefaultConstructor()) { | ||
generateDefaultConstructor(cw, clazz.superName()); | ||
} | ||
|
||
//methods lol | ||
for (MethodDefinition method : clazz.methods()) { | ||
generateMethod(cw, method); | ||
} | ||
|
||
cw.visitEnd(); | ||
byte[] bytes = cw.toByteArray(); | ||
if (DEBUG) { | ||
ClassReader classReader = new ClassReader(bytes); | ||
classReader.accept(new CheckClassAdapter(null), 0); | ||
Path path = Paths.get(classReader.getClassName() + ".class"); | ||
try { | ||
Files.createDirectories(path.getParent()); | ||
Files.write(path, bytes); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
return loader.defineClass(clazz.className().replace('/', '.'), bytes); | ||
} | ||
|
||
private static void generateField(ClassWriter cw, FieldDefinition field) { | ||
FieldVisitor fv = cw.visitField( | ||
field.access(), | ||
field.name(), | ||
field.descriptor(), | ||
null, | ||
field.initialFieldValue() | ||
); | ||
for (AnnotationDefinition annotation : field.annotations()) { | ||
AnnotationVisitor av = fv.visitAnnotation( | ||
annotation.descriptor(), | ||
true | ||
); | ||
annotation.values().forEach(av::visit); | ||
av.visitEnd(); | ||
} | ||
fv.visitEnd(); | ||
} | ||
|
||
private static void generateDefaultConstructor(ClassWriter cw, String superName) { | ||
MethodVisitor mv = cw.visitMethod( | ||
Opcodes.ACC_PUBLIC, | ||
"<init>", | ||
"()V", | ||
null, | ||
null | ||
); | ||
mv.visitCode(); | ||
mv.visitVarInsn(Opcodes.ALOAD, 0); | ||
if (superName == null) superName = "java/lang/Object"; | ||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, "<init>", "()V", false); | ||
mv.visitInsn(Opcodes.RETURN); | ||
mv.visitMaxs(0, 0); //method code reuse | ||
mv.visitEnd(); | ||
} | ||
|
||
private static void generateMethod(ClassWriter cw, MethodDefinition method) { | ||
MethodVisitor mv = cw.visitMethod( | ||
method.access(), | ||
method.name(), | ||
method.descriptor(), | ||
null, | ||
method.exceptions() | ||
); | ||
for (AnnotationDefinition annotation : method.annotations()) { | ||
AnnotationVisitor av = mv.visitAnnotation( | ||
annotation.descriptor(), | ||
true | ||
); | ||
annotation.values().forEach(av::visit); | ||
av.visitEnd(); | ||
} | ||
mv.visitCode(); | ||
method.instructions().accept(mv); | ||
mv.visitMaxs(0, 0); //we dont need to do shit lol | ||
mv.visitEnd(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters