This commit is contained in:
Victorious3 2022-05-24 14:35:30 +02:00
parent 6c584f92e9
commit 20f11a5bef
19 changed files with 256 additions and 48 deletions

View File

@ -1,17 +1,18 @@
package de.dhbwstuttgart.target.bytecode;
import de.dhbwstuttgart.target.tree.TargetClass;
import de.dhbwstuttgart.target.tree.TargetConstructor;
import de.dhbwstuttgart.target.tree.TargetField;
import de.dhbwstuttgart.target.tree.TargetMethod;
import de.dhbwstuttgart.target.tree.*;
import de.dhbwstuttgart.target.tree.expression.*;
import de.dhbwstuttgart.target.tree.type.TargetFunNType;
import de.dhbwstuttgart.target.tree.type.TargetRefType;
import de.dhbwstuttgart.target.tree.type.TargetType;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.*;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.objectweb.asm.Opcodes.*;
@ -56,6 +57,7 @@ public class Codegen {
private static class State {
Scope scope = new Scope(null);
int localCounter;
int lambdaCounter;
MethodVisitor mv;
State(MethodVisitor mv, int localCounter) {
@ -216,7 +218,8 @@ public class Codegen {
else if (dest.equals(TargetType.Double))
mv.visitInsn(I2D);
} else {
mv.visitTypeInsn(CHECKCAST, ((TargetRefType) dest).getName());
mv.visitTypeInsn(CHECKCAST, dest.getName());
unboxPrimitive(state, dest);
}
}
@ -515,6 +518,38 @@ public class Codegen {
}
private void generateLambdaExpression(State state, TargetLambdaExpression lambda) {
var mv = state.mv;
var name = "lambda$" + state.lambdaCounter;
var impl = new TargetMethod(
ACC_PRIVATE, new TargetRefType(clazz.qualifiedName()), name,
lambda.params(), lambda.returnType(), lambda.block()
);
generateMethod(impl);
var mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
MethodType.class, MethodType.class, MethodHandle.class, MethodType.class);
var bootstrap = new Handle(H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory",
mt.toMethodDescriptorString(), false);
var handle = new Handle(
H_INVOKEVIRTUAL, clazz.getName(), name,
impl.getDescriptor(), false
);
// TODO maybe make this a function?
var desugared = "(";
for (var param : lambda.params())
desugared += "Ljava/lang/Object;";
desugared += ")";
if (lambda.returnType() != null)
desugared += "Ljava/lang/Object;";
else desugared += "V";
mv.visitVarInsn(ALOAD, 0);
mv.visitInvokeDynamicInsn("apply", TargetMethod.getDescriptor(lambda.type(), new TargetRefType(clazz.qualifiedName())),
bootstrap, Type.getType(desugared), handle, Type.getType(impl.getDescriptor()));
state.lambdaCounter++;
}
private void generate(State state, TargetExpression expr) {
@ -542,7 +577,7 @@ public class Codegen {
convertTo(state, cast.expr().type(), cast.type());
break;
case TargetInstanceOf instanceOf:
mv.visitTypeInsn(INSTANCEOF, ((TargetRefType) instanceOf.right()).getName());
mv.visitTypeInsn(INSTANCEOF, instanceOf.right().getName());
break;
case TargetLiteral literal:
switch (literal) {
@ -595,13 +630,12 @@ public class Codegen {
}
case TargetFieldVar dot: {
generate(state, dot.left());
TargetRefType type = (TargetRefType) dot.type();
generate(state, assign.right());
boxPrimitive(state, assign.right().type());
if (dot.isStatic())
mv.visitInsn(DUP);
else mv.visitInsn(DUP_X1);
mv.visitFieldInsn(dot.isStatic() ? PUTSTATIC : PUTFIELD, dot.right(), type.getName(), type.toSignature());
mv.visitFieldInsn(dot.isStatic() ? PUTSTATIC : PUTFIELD, dot.owner().getName(), dot.right(), dot.type().toSignature());
break;
}
default:
@ -618,8 +652,7 @@ public class Codegen {
case TargetFieldVar dot: {
if (!dot.isStatic())
generate(state, dot.left());
var type = (TargetRefType) dot.left().type();
mv.visitFieldInsn(dot.isStatic() ? GETSTATIC : GETFIELD, type.getName(), dot.right(), dot.type().toSignature());
mv.visitFieldInsn(dot.isStatic() ? GETSTATIC : GETFIELD, dot.left().type().getName(), dot.right(), dot.type().toSignature());
break;
}
case TargetFor _for: {
@ -685,7 +718,7 @@ public class Codegen {
break;
}
case TargetSuper _super: {
// TODO
mv.visitVarInsn(ALOAD, 0);
break;
}
case TargetMethodCall call: {
@ -694,12 +727,29 @@ public class Codegen {
generate(state, e);
boxPrimitive(state, e.type());
}
mv.visitMethodInsn(call.isStatic() ? INVOKESTATIC: INVOKEVIRTUAL, call.owner().getName(), call.name(), call.descriptor(), false);
if (call.expr() instanceof TargetThis)
mv.visitMethodInsn(INVOKESPECIAL, clazz.getName(), "<init>", call.getDescriptor(), false);
else if (call.expr() instanceof TargetSuper)
mv.visitMethodInsn(INVOKESPECIAL, clazz.superType().getName(), "<init>", call.getDescriptor(), false);
else mv.visitMethodInsn(call.isInterface() ? INVOKEINTERFACE : call.isStatic() ? INVOKESTATIC: INVOKEVIRTUAL,
call.owner().getName(), call.name(), call.getDescriptor(), call.isInterface());
if (call.type() != null)
unboxPrimitive(state, call.type());
break;
}
case TargetLambdaExpression lambda:
generateLambdaExpression(state, lambda);
break;
case TargetNew _new: {
mv.visitTypeInsn(NEW, _new.type().getName());
mv.visitInsn(DUP);
for (TargetExpression e : _new.params()) {
generate(state, e);
boxPrimitive(state, e.type());
}
mv.visitMethodInsn(INVOKESPECIAL, _new.type().getName(), "<init>", _new.getDescriptor(), false);
break;
}
default:
throw new CodeGenException("Unexpected value: " + expr);
}
@ -709,12 +759,26 @@ public class Codegen {
cw.visitField(field.access(), field.name(), field.getDescriptor(), null, null);
}
private boolean hasThisOrSuperCall(TargetBlock block) {
if (block.statements().size() == 0) return false;
TargetExpression first = block.statements().get(0);
if (!(first instanceof TargetMethodCall)) return false;
var methodCall = (TargetMethodCall) first;
if (methodCall.expr() instanceof TargetThis || methodCall.expr() instanceof TargetSuper)
return true;
return false;
}
private void generateConstructor(TargetConstructor constructor) {
MethodVisitor mv = cw.visitMethod(constructor.access(), "<init>", constructor.getDescriptor(), null, null);
mv.visitCode();
var state = new State(mv, 1);
for (var param: constructor.parameters())
state.createVariable(param.name(), param.type());
if (!hasThisOrSuperCall(constructor.block())) {
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, clazz.superType().getName(), "<init>", "()V", false);
}
generate(state, constructor.block());
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
@ -736,20 +800,11 @@ public class Codegen {
public byte[] generate() {
cw.visit(V1_8, clazz.modifiers(), clazz.qualifiedName(),
null, ((TargetRefType) clazz.superType()).getName(),
null, clazz.superType().getName(),
clazz.implementingInterfaces().stream().map(TargetType::toSignature).toArray(String[]::new)
);
clazz.fields().forEach(this::generateField);
if (clazz.constructors().size() == 0) {
var mv = cw.visitMethod(ACC_PROTECTED, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, ((TargetRefType) clazz.superType()).getName(), "<init>", "()V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
else clazz.constructors().forEach(this::generateConstructor);
clazz.constructors().forEach(this::generateConstructor);
clazz.methods().forEach(this::generateMethod);
cw.visitEnd();

View File

@ -17,6 +17,10 @@ public record TargetClass(int modifiers, String qualifiedName, TargetType superT
this(modifiers, qualifiedName, TargetType.Object, implementingInterfaces, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
public String getName() {
return qualifiedName.replaceAll("\\.", "/");
}
public void addMethod(int access, String name, List<MethodParameter> parameterTypes, TargetType returnType, TargetBlock block) {
this.methods.add(new TargetMethod(access, new TargetRefType(this.qualifiedName, List.of()), name, parameterTypes, returnType, block));
}
@ -25,7 +29,7 @@ public record TargetClass(int modifiers, String qualifiedName, TargetType superT
this.constructors.add(new TargetConstructor(access, new TargetRefType(this.qualifiedName, List.of()), paramterTypes, block));
}
public void addField(int access, TargetType type, String name) {
public void addField(int access, TargetRefType type, String name) {
this.fields.add(new TargetField(access, new TargetRefType(this.qualifiedName, List.of()), type, name));
}
}

View File

@ -2,14 +2,14 @@ package de.dhbwstuttgart.target.tree;
import de.dhbwstuttgart.target.tree.expression.TargetBlock;
import de.dhbwstuttgart.target.tree.type.TargetRefType;
import de.dhbwstuttgart.target.tree.type.TargetType;
import java.util.List;
public record TargetConstructor(int access, TargetRefType owner, List<MethodParameter> parameters, TargetBlock block) {
public record TargetConstructor(int access, TargetType owner, List<MethodParameter> parameters, TargetBlock block) {
public String getDescriptor() {
// TODO
return null;
return TargetMethod.getDescriptor(null, parameters.stream().map(MethodParameter::type).toArray(TargetType[]::new));
}
}

View File

@ -1,13 +1,11 @@
package de.dhbwstuttgart.target.tree;
import de.dhbwstuttgart.target.tree.type.TargetRefType;
import de.dhbwstuttgart.target.tree.type.TargetType;
import org.objectweb.asm.Opcodes;
public record TargetField(int access, TargetRefType owner, TargetType type, String name) {
public record TargetField(int access, TargetType owner, TargetType type, String name) {
public String getDescriptor() {
// TODO
return null;
return type.toSignature();
}
public boolean isStatic() {

View File

@ -8,7 +8,7 @@ import org.objectweb.asm.Opcodes;
import java.util.List;
import java.util.stream.Collectors;
public record TargetMethod(int access, TargetRefType owner, String name, List<MethodParameter> parameters, TargetType returnType, TargetBlock block) {
public record TargetMethod(int access, TargetType owner, String name, List<MethodParameter> parameters, TargetType returnType, TargetBlock block) {
public static String getDescriptor(TargetType returnType, TargetType... parameters) {
String ret = "(";
for (var parameterType : parameters) {

View File

@ -35,6 +35,7 @@ public sealed interface TargetBinaryOp extends TargetExpression {
}
// Comparison
// exprType is the type that both arguments get converted to before comparison
record Equal(TargetType exprType, TargetExpression left, TargetExpression right) implements TargetRelationalOp {}
record Greater(TargetType exprType, TargetExpression left, TargetExpression right) implements TargetRelationalOp {}
record GreaterOrEqual(TargetType exprType, TargetExpression left, TargetExpression right) implements TargetRelationalOp {}

View File

@ -1,6 +1,7 @@
package de.dhbwstuttgart.target.tree.expression;
import de.dhbwstuttgart.target.tree.type.TargetRefType;
import de.dhbwstuttgart.target.tree.type.TargetType;
public record TargetFieldVar(TargetType type, boolean isStatic, TargetExpression left, String right) implements TargetExpression {
public record TargetFieldVar(TargetType type, TargetRefType owner, boolean isStatic, TargetExpression left, String right) implements TargetExpression {
}

View File

@ -5,5 +5,5 @@ import de.dhbwstuttgart.target.tree.type.TargetType;
import java.util.List;
public record TargetLambdaExpression(TargetType type, List<MethodParameter> params, TargetExpression block) implements TargetExpression {
public record TargetLambdaExpression(TargetType type, List<MethodParameter> params, TargetType returnType, TargetBlock block) implements TargetExpression {
}

View File

@ -1,10 +1,19 @@
package de.dhbwstuttgart.target.tree.expression;
import de.dhbwstuttgart.target.tree.MethodParameter;
import de.dhbwstuttgart.target.tree.TargetMethod;
import de.dhbwstuttgart.target.tree.type.TargetRefType;
import de.dhbwstuttgart.target.tree.type.TargetType;
import java.util.List;
public record TargetMethodCall(TargetType type, TargetExpression expr, List<TargetExpression> args, TargetRefType owner, String name, String descriptor, boolean isStatic) implements TargetStatementExpression {
public record TargetMethodCall(TargetType type, List<TargetType> parameterTypes, TargetExpression expr, List<TargetExpression> args, TargetType owner, String name, boolean isStatic, boolean isInterface) implements TargetStatementExpression {
public TargetMethodCall(TargetType type, TargetExpression expr, List<TargetExpression> args, TargetType owner, String name, boolean isStatic, boolean isInterface) {
this(type, args.stream().map(TargetExpression::type).toList(), expr, args, owner, name, isStatic, isInterface);
}
public String getDescriptor() {
return TargetMethod.getDescriptor(type, parameterTypes.toArray(TargetType[]::new));
}
}

View File

@ -1,8 +1,12 @@
package de.dhbwstuttgart.target.tree.expression;
import de.dhbwstuttgart.target.tree.TargetMethod;
import de.dhbwstuttgart.target.tree.type.TargetType;
import java.util.List;
public record TargetNew(TargetType type, List<TargetExpression> params) implements TargetStatementExpression {
public String getDescriptor() {
return TargetMethod.getDescriptor(null, params.stream().map(TargetExpression::type).toArray(TargetType[]::new));
}
}

View File

@ -1,11 +1,14 @@
package de.dhbwstuttgart.target.tree.type;
import java.util.List;
public record TargetExtendsWildcard(TargetType innerType) implements TargetType {
@Override
public String toSignature() {
return null;
}
@Override
public String getName() {
return null;
}
}

View File

@ -2,9 +2,14 @@ package de.dhbwstuttgart.target.tree.type;
import java.util.List;
public record TargetFunNType(int N, List<TargetRefType> params) implements TargetType {
public record TargetFunNType(int N, List<TargetType> params) implements TargetType {
@Override
public String getName() {
return "Fun" + N + "$$";
}
@Override
public String toSignature() {
return "Fun$$" + N;
return "L" + getName() + ";";
}
}

View File

@ -5,4 +5,9 @@ public record TargetGenericType(String name) implements TargetType {
public String toSignature() {
return null;
}
@Override
public java.lang.String getName() {
return null;
}
}

View File

@ -5,4 +5,9 @@ public record TargetSuperWildcard(TargetType innerType) implements TargetType {
public String toSignature() {
return null;
}
@Override
public String getName() {
return null;
}
}

View File

@ -18,4 +18,5 @@ public sealed interface TargetType
TargetRefType Object = new TargetRefType("java.lang.Object");
String toSignature();
String getName();
}

View File

@ -1,9 +1,16 @@
package targetast;
import java.util.Map;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class ByteArrayClassLoader extends ClassLoader {
public Class loadClass(byte[] code) {
return this.defineClass(null, code, 0, code.length);
}
public Class loadClass(Path path) throws IOException {
var code = Files.readAllBytes(path);
return this.defineClass(null, code, 0, code.length);
}
}

View File

@ -0,0 +1,3 @@
public interface Fun1$$<R, T> {
public R apply(T t);
}

View File

@ -3,7 +3,6 @@ package targetast;
import de.dhbwstuttgart.target.bytecode.Codegen;
import de.dhbwstuttgart.target.tree.MethodParameter;
import de.dhbwstuttgart.target.tree.TargetClass;
import de.dhbwstuttgart.target.tree.TargetMethod;
import de.dhbwstuttgart.target.tree.expression.*;
import de.dhbwstuttgart.target.tree.type.TargetRefType;
import de.dhbwstuttgart.target.tree.type.TargetType;
@ -20,13 +19,15 @@ import java.util.List;
public class TestCodegen {
private static ByteArrayClassLoader loader = new ByteArrayClassLoader();
private static Class generateClass(TargetClass clazz) throws IOException {
var codegen = new Codegen(clazz);
var bytes = codegen.generate();
var path = Path.of(System.getProperty("user.dir"), "src/test/resources/target/");
Files.createDirectories(path);
Files.write(path.resolve(clazz.qualifiedName() + ".class"), bytes, StandardOpenOption.CREATE);
return new ByteArrayClassLoader().loadClass(bytes);
return loader.loadClass(bytes);
}
@Test
@ -85,6 +86,34 @@ public class TestCodegen {
assertEquals(clazz.getDeclaredMethod("rem", Integer.class, Integer.class).invoke(null, 10, 3), 1);
}
@Test
public void testConditional() throws Exception {
var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "Conditional");
targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "and",
List.of(new MethodParameter(TargetType.Boolean, "a"), new MethodParameter(TargetType.Boolean, "b")),
TargetType.Boolean,
new TargetBlock(List.of(new TargetReturn(
new TargetBinaryOp.And(TargetType.Boolean, new TargetLocalVar(TargetType.Boolean, "a"), new TargetLocalVar(TargetType.Boolean, "b")))
))
);
targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "or",
List.of(new MethodParameter(TargetType.Boolean, "a"), new MethodParameter(TargetType.Boolean, "b")),
TargetType.Boolean,
new TargetBlock(List.of(new TargetReturn(
new TargetBinaryOp.Or(TargetType.Boolean, new TargetLocalVar(TargetType.Boolean, "a"), new TargetLocalVar(TargetType.Boolean, "b")))
))
);
var clazz = generateClass(targetClass);
var and = clazz.getDeclaredMethod("and", Boolean.class, Boolean.class);
var or = clazz.getDeclaredMethod("or", Boolean.class, Boolean.class);
assertEquals(and.invoke(null, true, false), false);
assertEquals(and.invoke(null, true, true), true);
assertEquals(or.invoke(null, false, false), false);
assertEquals(or.invoke(null, true, false), true);
}
// When adding two numbers and the return type is Long it needs to convert both values to Long
@Test
public void testArithmeticConvert() throws Exception {
@ -104,15 +133,16 @@ public class TestCodegen {
targetClass.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "helloWorld", List.of(), null,
new TargetBlock(List.of(new TargetMethodCall(null,
new TargetFieldVar(
new TargetRefType("java.io.PrintStream"), true,
new TargetRefType("java.io.PrintStream"),
new TargetRefType("java.lang.System"),
true,
new TargetClassName(new TargetRefType("java.lang.System")),
"out"
),
List.of(new TargetLiteral.StringLiteral("Hello World!")),
new TargetRefType("java.io.PrintStream"),
"println",
TargetMethod.getDescriptor(null, TargetType.String),
false
false, false
)))
);
@ -190,4 +220,72 @@ public class TestCodegen {
var clazz = generateClass(targetClass);
assertEquals(clazz.getDeclaredMethod("whileLoop").invoke(null), 10);
}
@Test
public void testNew() throws Exception {
var pointType = new TargetRefType("Point");
var pointTarget = new TargetClass(Opcodes.ACC_PUBLIC, "Point");
pointTarget.addField(Opcodes.ACC_PUBLIC, TargetType.Integer, "x");
pointTarget.addField(Opcodes.ACC_PUBLIC, TargetType.Integer, "y");
pointTarget.addConstructor(Opcodes.ACC_PUBLIC,
List.of(new MethodParameter(TargetType.Integer, "x"), new MethodParameter(TargetType.Integer, "y")),
new TargetBlock(List.of(
new TargetAssign(TargetType.Integer,
new TargetFieldVar(TargetType.Integer, pointType, false, new TargetThis(pointType), "x"),
new TargetLocalVar(TargetType.Integer, "x")
),
new TargetAssign(TargetType.Integer,
new TargetFieldVar(TargetType.Integer, pointType, false, new TargetThis(pointType), "y"),
new TargetLocalVar(TargetType.Integer, "y")
)
))
);
var mainTarget = new TargetClass(Opcodes.ACC_PUBLIC, "New");
mainTarget.addMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "makePoint",
List.of(new MethodParameter(TargetType.Integer, "x"), new MethodParameter(TargetType.Integer, "y")), pointType,
new TargetBlock(List.of(
new TargetReturn(new TargetNew(pointType, List.of(
new TargetLocalVar(TargetType.Integer, "x"),
new TargetLocalVar(TargetType.Integer, "y")
)))
))
);
var pointClass = generateClass(pointTarget);
var mainClass = generateClass(mainTarget);
var point = mainClass.getDeclaredMethod("makePoint", Integer.class, Integer.class).invoke(null, 10, 20);
assertEquals(point.getClass().getDeclaredField("x").get(point), 10);
assertEquals(point.getClass().getDeclaredField("y").get(point), 20);
}
@Test
public void testLambda() throws Exception {
var fun = loader.loadClass(Path.of(System.getProperty("user.dir"), "src/test/java/targetast/Fun1$$.class"));
var interfaceType = new TargetRefType("Fun1$$");
var targetClass = new TargetClass(Opcodes.ACC_PUBLIC, "Lambda");
targetClass.addConstructor(Opcodes.ACC_PUBLIC, List.of(), new TargetBlock(List.of()));
targetClass.addMethod(Opcodes.ACC_PUBLIC, "lambda", List.of(), TargetType.Integer,
new TargetBlock(List.of(
new TargetVarDecl(interfaceType, "by2",
new TargetLambdaExpression(interfaceType, List.of(new MethodParameter(TargetType.Integer, "num")), TargetType.Integer,
new TargetBlock(List.of(
new TargetReturn(new TargetBinaryOp.Mul(TargetType.Integer,
new TargetLocalVar(TargetType.Integer, "num"),
new TargetLiteral.IntLiteral(2)
))
)
))
),
new TargetReturn(new TargetCast(TargetType.Integer, new TargetMethodCall(TargetType.Object, List.of(TargetType.Object), new TargetLocalVar(interfaceType, "by2"), List.of(
new TargetLiteral.IntLiteral(10)
), interfaceType, "apply", false, true)))
))
);
var clazz = generateClass(targetClass);
var instance = clazz.getConstructor().newInstance();
assertEquals(clazz.getDeclaredMethod("lambda").invoke(instance), 20);
}
}

View File

@ -0,0 +1,9 @@
public class Test {
public void lambda() {
Interface<Integer, Integer> mul2 = (Integer a) -> a * 2;
}
}
interface Interface<R, T> {
R apply(T t);
}