forked from JavaTX/JavaCompilerCore
1522 lines
64 KiB
Java
1522 lines
64 KiB
Java
package de.dhbwstuttgart.bytecode;
|
|
|
|
import de.dhbwstuttgart.core.JavaTXCompiler;
|
|
import de.dhbwstuttgart.exceptions.NotImplementedException;
|
|
import de.dhbwstuttgart.parser.scope.JavaClassName;
|
|
import de.dhbwstuttgart.syntaxtree.ClassOrInterface;
|
|
import de.dhbwstuttgart.syntaxtree.type.RefType;
|
|
import de.dhbwstuttgart.target.tree.*;
|
|
import de.dhbwstuttgart.target.tree.expression.*;
|
|
import de.dhbwstuttgart.target.tree.type.*;
|
|
import org.objectweb.asm.*;
|
|
|
|
import java.lang.invoke.*;
|
|
import java.lang.reflect.Modifier;
|
|
import java.util.*;
|
|
|
|
import static org.objectweb.asm.Opcodes.*;
|
|
import static de.dhbwstuttgart.target.tree.expression.TargetBinaryOp.*;
|
|
import static de.dhbwstuttgart.target.tree.expression.TargetLiteral.*;
|
|
|
|
public class Codegen {
|
|
private final TargetStructure clazz;
|
|
private final ClassWriter cw;
|
|
public final String className;
|
|
private int lambdaCounter = 0;
|
|
private final HashMap<TargetLambdaExpression, TargetMethod> lambdas = new HashMap<>();
|
|
private final JavaTXCompiler compiler;
|
|
|
|
public Codegen(TargetStructure clazz, JavaTXCompiler compiler) {
|
|
this.clazz = clazz;
|
|
this.className = clazz.qualifiedName().getClassName();
|
|
this.cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) {
|
|
@Override
|
|
protected ClassLoader getClassLoader() {
|
|
return compiler.getClassLoader();
|
|
}
|
|
};
|
|
this.compiler = compiler;
|
|
}
|
|
|
|
private record LocalVar(int index, String name, TargetType type) {
|
|
}
|
|
|
|
private static class Scope {
|
|
Scope parent;
|
|
Map<String, LocalVar> locals = new HashMap<>();
|
|
|
|
Scope(Scope parent) {
|
|
this.parent = parent;
|
|
}
|
|
|
|
void add(LocalVar var) {
|
|
locals.put(var.name, var);
|
|
}
|
|
|
|
LocalVar get(String name) {
|
|
var local = locals.get(name);
|
|
if (local != null) {
|
|
return local;
|
|
}
|
|
if (parent != null) {
|
|
return parent.get(name);
|
|
}
|
|
throw new CodeGenException("Unknown symbol '" + name + "'");
|
|
}
|
|
}
|
|
|
|
private static class BreakEnv {
|
|
String labelName; // TODO This is for labeled statements (Not implemented)
|
|
Label startLabel;
|
|
Label endLabel;
|
|
}
|
|
|
|
private static class State {
|
|
Scope scope = new Scope(null);
|
|
int localCounter;
|
|
MethodVisitor mv;
|
|
TargetType returnType;
|
|
// This is used to remember the type from lambda expressions
|
|
TargetType contextType;
|
|
|
|
Stack<BreakEnv> breakStack = new Stack<>();
|
|
Stack<Integer> switchResultValue = new Stack<>();
|
|
|
|
State(TargetType returnType, MethodVisitor mv, int localCounter) {
|
|
this.returnType = returnType;
|
|
this.mv = mv;
|
|
this.localCounter = localCounter;
|
|
}
|
|
|
|
void enterScope() {
|
|
this.scope = new Scope(this.scope);
|
|
}
|
|
|
|
void exitScope() {
|
|
this.scope = this.scope.parent;
|
|
}
|
|
|
|
LocalVar createVariable(String name, TargetType type) {
|
|
var local = new LocalVar(localCounter, name, type);
|
|
scope.add(local);
|
|
localCounter += 1;
|
|
return local;
|
|
}
|
|
|
|
void pushSwitch() {
|
|
switchResultValue.push(this.localCounter++);
|
|
}
|
|
void popSwitch() {
|
|
switchResultValue.pop();
|
|
}
|
|
}
|
|
|
|
private void popValue(State state, TargetType type) {
|
|
if (type.equals(TargetType.Double) || type.equals(TargetType.Long))
|
|
state.mv.visitInsn(POP2);
|
|
else
|
|
state.mv.visitInsn(POP);
|
|
}
|
|
|
|
private void boxPrimitive(State state, TargetType type) {
|
|
var mv = state.mv;
|
|
if (type.equals(TargetType.Boolean) || type.equals(TargetType.boolean_)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
|
|
} else if (type.equals(TargetType.Byte) || type.equals(TargetType.byte_)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
|
|
} else if (type.equals(TargetType.Double) || type.equals(TargetType.double_)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
|
|
} else if (type.equals(TargetType.Long) || type.equals(TargetType.long_)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
|
|
} else if (type.equals(TargetType.Integer) || type.equals(TargetType.int_)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
|
|
} else if (type.equals(TargetType.Float) || type.equals(TargetType.float_)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
|
|
} else if (type.equals(TargetType.Short) || type.equals(TargetType.short_)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
|
|
} else if (type.equals(TargetType.Char) || type.equals(TargetType.char_)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Char", "valueOf", "(C)Ljava/lang/Char;", false);
|
|
}
|
|
}
|
|
|
|
private void unboxPrimitive(State state, TargetType type) {
|
|
var mv = state.mv;
|
|
if (type.equals(TargetType.Boolean)) {
|
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
|
|
} else if (type.equals(TargetType.Byte)) {
|
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
|
|
} else if (type.equals(TargetType.Long)) {
|
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
|
|
} else if (type.equals(TargetType.Integer)) {
|
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
|
|
} else if (type.equals(TargetType.Short)) {
|
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
|
|
} else if (type.equals(TargetType.Char)) {
|
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Char", "charValue", "()C", false);
|
|
}
|
|
}
|
|
|
|
private void generateRelationalOperator(State state, TargetRelationalOp op, TargetType type, int code) {
|
|
var mv = state.mv;
|
|
Label if_true = new Label();
|
|
Label end = new Label();
|
|
generate(state, op.left());
|
|
convertTo(state, op.left().type(), type);
|
|
generate(state, op.right());
|
|
convertTo(state, op.right().type(), type);
|
|
mv.visitJumpInsn(code, if_true);
|
|
mv.visitInsn(ICONST_0);
|
|
mv.visitJumpInsn(GOTO, end);
|
|
mv.visitLabel(if_true);
|
|
mv.visitInsn(ICONST_1);
|
|
mv.visitLabel(end);
|
|
}
|
|
|
|
private void generateRelationalOperator(State state, TargetRelationalOp op, TargetType type, int cmp, int code) {
|
|
var mv = state.mv;
|
|
Label if_true = new Label();
|
|
Label end = new Label();
|
|
generate(state, op.left());
|
|
convertTo(state, op.left().type(), type);
|
|
generate(state, op.right());
|
|
convertTo(state, op.right().type(), type);
|
|
mv.visitInsn(cmp);
|
|
mv.visitJumpInsn(code, if_true);
|
|
mv.visitInsn(ICONST_0);
|
|
mv.visitJumpInsn(GOTO, end);
|
|
mv.visitLabel(if_true);
|
|
mv.visitInsn(ICONST_1);
|
|
mv.visitLabel(end);
|
|
}
|
|
|
|
private void convertToString(State state, TargetType type) {
|
|
var mv = state.mv;
|
|
if (type.equals(TargetType.Boolean)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(Z)Ljava/lang/Boolean;", false);
|
|
} else if (type.equals(TargetType.Byte)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(B)Ljava/lang/Byte;", false);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(D)Ljava/lang/Double;", false);
|
|
} else if (type.equals(TargetType.Long)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(J)Ljava/lang/Long;", false);
|
|
} else if (type.equals(TargetType.Integer)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(I)Ljava/lang/Integer;", false);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(F)Ljava/lang/Float;", false);
|
|
} else if (type.equals(TargetType.Short)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(S)Ljava/lang/Short;", false);
|
|
} else if (type.equals(TargetType.Char)) {
|
|
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(C)Ljava/lang/Char;", false);
|
|
}
|
|
}
|
|
|
|
private void convertTo(State state, TargetType source, TargetType dest) {
|
|
var mv = state.mv;
|
|
if (source.equals(dest))
|
|
return;
|
|
if (source.equals(TargetType.Long)) {
|
|
if (dest.equals(TargetType.Integer)) {
|
|
mv.visitInsn(L2I);
|
|
} else if (dest.equals(TargetType.Float))
|
|
mv.visitInsn(L2F);
|
|
else if (dest.equals(TargetType.Double))
|
|
mv.visitInsn(L2D);
|
|
else if (dest.equals(TargetType.Byte) || dest.equals(TargetType.Char) || dest.equals(TargetType.Short)) {
|
|
mv.visitInsn(L2I);
|
|
convertTo(state, TargetType.Integer, dest);
|
|
}
|
|
} else if (source.equals(TargetType.Float)) {
|
|
if (dest.equals(TargetType.Integer))
|
|
mv.visitInsn(F2I);
|
|
else if (dest.equals(TargetType.Double))
|
|
mv.visitInsn(F2D);
|
|
else if (dest.equals(TargetType.Long))
|
|
mv.visitInsn(F2L);
|
|
else if (dest.equals(TargetType.Byte) || dest.equals(TargetType.Char) || dest.equals(TargetType.Short)) {
|
|
mv.visitInsn(F2I);
|
|
convertTo(state, TargetType.Integer, dest);
|
|
}
|
|
} else if (source.equals(TargetType.Double)) {
|
|
if (dest.equals(TargetType.Integer))
|
|
mv.visitInsn(D2I);
|
|
else if (dest.equals(TargetType.Float))
|
|
mv.visitInsn(D2F);
|
|
else if (dest.equals(TargetType.Long))
|
|
mv.visitInsn(D2L);
|
|
else if (dest.equals(TargetType.Byte) || dest.equals(TargetType.Char) || dest.equals(TargetType.Short)) {
|
|
mv.visitInsn(D2I);
|
|
convertTo(state, TargetType.Integer, dest);
|
|
}
|
|
} else if (source.equals(TargetType.Byte) || source.equals(TargetType.Char) || source.equals(TargetType.Short) || source.equals(TargetType.Integer)) {
|
|
if (dest.equals(TargetType.Byte))
|
|
mv.visitInsn(I2B);
|
|
else if (dest.equals(TargetType.Char))
|
|
mv.visitInsn(I2C);
|
|
else if (dest.equals(TargetType.Short))
|
|
mv.visitInsn(I2S);
|
|
else if (dest.equals(TargetType.Long))
|
|
mv.visitInsn(I2L);
|
|
else if (dest.equals(TargetType.Float))
|
|
mv.visitInsn(I2F);
|
|
else if (dest.equals(TargetType.Double))
|
|
mv.visitInsn(I2D);
|
|
} else if (!(dest instanceof TargetGenericType)) {
|
|
boxPrimitive(state, source);
|
|
mv.visitTypeInsn(CHECKCAST, dest.getInternalName());
|
|
unboxPrimitive(state, dest);
|
|
}
|
|
}
|
|
|
|
private TargetType largerType(TargetType left, TargetType right) {
|
|
if (left.equals(TargetType.String) || right.equals(TargetType.String)) {
|
|
return TargetType.String;
|
|
} else if (left.equals(TargetType.Double) || right.equals(TargetType.Double)) {
|
|
return TargetType.Double;
|
|
} else if (left.equals(TargetType.Float) || right.equals(TargetType.Float)) {
|
|
return TargetType.Float;
|
|
} else if (left.equals(TargetType.Long) || right.equals(TargetType.Long)) {
|
|
return TargetType.Long;
|
|
} else {
|
|
return TargetType.Integer;
|
|
}
|
|
}
|
|
|
|
private void generateBinaryOp(State state, TargetBinaryOp op) {
|
|
var mv = state.mv;
|
|
switch (op) {
|
|
case Add add: {
|
|
if (add.type().equals(TargetType.String)) {
|
|
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
|
|
mv.visitInsn(DUP);
|
|
generate(state, add.left());
|
|
convertToString(state, add.left().type());
|
|
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false);
|
|
} else {
|
|
generate(state, add.left());
|
|
convertTo(state, add.left().type(), add.type());
|
|
generate(state, add.right());
|
|
convertTo(state, add.right().type(), add.type());
|
|
var type = add.type();
|
|
if (type.equals(TargetType.Byte) || type.equals(TargetType.Char) || type.equals(TargetType.Integer) || type.equals(TargetType.Short)) {
|
|
mv.visitInsn(IADD);
|
|
} else if (type.equals(TargetType.Long)) {
|
|
mv.visitInsn(LADD);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
mv.visitInsn(FADD);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
mv.visitInsn(DADD);
|
|
} else {
|
|
throw new CodeGenException("Invalid argument to Add expression, type: " + add.type());
|
|
}
|
|
}
|
|
if (add.type().equals(TargetType.String)) {
|
|
generate(state, add.right());
|
|
convertToString(state, add.right().type());
|
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
|
|
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
|
|
}
|
|
break;
|
|
}
|
|
case Sub sub: {
|
|
generate(state, sub.left());
|
|
convertTo(state, sub.left().type(), op.type());
|
|
generate(state, sub.right());
|
|
convertTo(state, sub.right().type(), op.type());
|
|
var type = sub.type();
|
|
if (type.equals(TargetType.Byte) || type.equals(TargetType.Char) || type.equals(TargetType.Integer) || type.equals(TargetType.Short)) {
|
|
mv.visitInsn(ISUB);
|
|
} else if (type.equals(TargetType.Long)) {
|
|
mv.visitInsn(LSUB);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
mv.visitInsn(FSUB);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
mv.visitInsn(DSUB);
|
|
} else {
|
|
throw new CodeGenException("Invalid argument to Sub expression");
|
|
}
|
|
break;
|
|
}
|
|
case Div div: {
|
|
generate(state, div.left());
|
|
convertTo(state, div.left().type(), op.type());
|
|
generate(state, div.right());
|
|
convertTo(state, div.right().type(), op.type());
|
|
var type = div.type();
|
|
if (type.equals(TargetType.Byte) || type.equals(TargetType.Char) || type.equals(TargetType.Integer) || type.equals(TargetType.Short)) {
|
|
mv.visitInsn(IDIV);
|
|
} else if (type.equals(TargetType.Long)) {
|
|
mv.visitInsn(LDIV);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
mv.visitInsn(FDIV);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
mv.visitInsn(DDIV);
|
|
} else {
|
|
throw new CodeGenException("Invalid argument to Div expression");
|
|
}
|
|
break;
|
|
}
|
|
case Mul mul: {
|
|
generate(state, mul.left());
|
|
convertTo(state, mul.left().type(), op.type());
|
|
generate(state, mul.right());
|
|
convertTo(state, mul.right().type(), op.type());
|
|
var type = mul.type();
|
|
if (type.equals(TargetType.Byte) || type.equals(TargetType.Char) || type.equals(TargetType.Integer) || type.equals(TargetType.Short)) {
|
|
mv.visitInsn(IMUL);
|
|
} else if (type.equals(TargetType.Long)) {
|
|
mv.visitInsn(LMUL);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
mv.visitInsn(FMUL);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
mv.visitInsn(DMUL);
|
|
} else {
|
|
throw new CodeGenException("Invalid argument to Mul expression");
|
|
}
|
|
break;
|
|
}
|
|
case Rem rem: {
|
|
generate(state, rem.left());
|
|
convertTo(state, rem.left().type(), op.type());
|
|
generate(state, rem.right());
|
|
convertTo(state, rem.right().type(), op.type());
|
|
var type = rem.type();
|
|
if (type.equals(TargetType.Byte) || type.equals(TargetType.Char) || type.equals(TargetType.Integer) || type.equals(TargetType.Short)) {
|
|
mv.visitInsn(IREM);
|
|
} else if (type.equals(TargetType.Long)) {
|
|
mv.visitInsn(LREM);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
mv.visitInsn(FREM);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
mv.visitInsn(DREM);
|
|
} else {
|
|
throw new CodeGenException("Invalid argument to Rem expression");
|
|
}
|
|
break;
|
|
}
|
|
case Or or: {
|
|
Label or_false = new Label();
|
|
Label or_true = new Label();
|
|
Label end = new Label();
|
|
generate(state, or.left());
|
|
mv.visitJumpInsn(IFNE, or_true);
|
|
generate(state, or.right());
|
|
mv.visitJumpInsn(IFEQ, or_false);
|
|
mv.visitLabel(or_true);
|
|
mv.visitInsn(ICONST_1);
|
|
mv.visitJumpInsn(GOTO, end);
|
|
mv.visitLabel(or_false);
|
|
mv.visitInsn(ICONST_0);
|
|
mv.visitLabel(end);
|
|
break;
|
|
}
|
|
case And and: {
|
|
Label and_false = new Label();
|
|
Label end = new Label();
|
|
generate(state, and.left());
|
|
mv.visitJumpInsn(IFEQ, and_false);
|
|
generate(state, and.right());
|
|
mv.visitJumpInsn(IFEQ, and_false);
|
|
mv.visitInsn(ICONST_1);
|
|
mv.visitJumpInsn(GOTO, end);
|
|
mv.visitLabel(and_false);
|
|
mv.visitInsn(ICONST_0);
|
|
mv.visitLabel(end);
|
|
break;
|
|
}
|
|
case BAnd band: {
|
|
generate(state, band.left());
|
|
convertTo(state, band.left().type(), op.type());
|
|
generate(state, band.right());
|
|
convertTo(state, band.right().type(), op.type());
|
|
if (band.type().equals(TargetType.Long))
|
|
mv.visitInsn(LAND);
|
|
else
|
|
mv.visitInsn(IAND);
|
|
break;
|
|
}
|
|
case BOr bor: {
|
|
generate(state, bor.left());
|
|
convertTo(state, bor.left().type(), op.type());
|
|
generate(state, bor.right());
|
|
convertTo(state, bor.right().type(), op.type());
|
|
if (bor.type().equals(TargetType.Long))
|
|
mv.visitInsn(LOR);
|
|
else
|
|
mv.visitInsn(IOR);
|
|
break;
|
|
}
|
|
case XOr xor: {
|
|
generate(state, xor.left());
|
|
convertTo(state, xor.left().type(), op.type());
|
|
generate(state, xor.right());
|
|
convertTo(state, xor.right().type(), op.type());
|
|
if (xor.type().equals(TargetType.Long))
|
|
mv.visitInsn(LXOR);
|
|
else
|
|
mv.visitInsn(IXOR);
|
|
break;
|
|
}
|
|
case Instof instof: {
|
|
// TODO
|
|
throw new NotImplementedException();
|
|
}
|
|
case Shl shl: {
|
|
generate(state, shl.left());
|
|
convertTo(state, shl.left().type(), op.type());
|
|
generate(state, shl.right());
|
|
convertTo(state, shl.right().type(), op.type());
|
|
if (shl.type().equals(TargetType.Long))
|
|
mv.visitInsn(LSHL);
|
|
else
|
|
mv.visitInsn(ISHL);
|
|
break;
|
|
}
|
|
case Shr shr: {
|
|
generate(state, shr.left());
|
|
convertTo(state, shr.left().type(), op.type());
|
|
generate(state, shr.right());
|
|
convertTo(state, shr.right().type(), op.type());
|
|
if (shr.type().equals(TargetType.Long))
|
|
mv.visitInsn(LSHR);
|
|
else
|
|
mv.visitInsn(ISHR);
|
|
break;
|
|
}
|
|
case UShr ushr: {
|
|
generate(state, ushr.left());
|
|
convertTo(state, ushr.left().type(), op.type());
|
|
generate(state, ushr.right());
|
|
convertTo(state, ushr.right().type(), op.type());
|
|
if (ushr.type().equals(TargetType.Long))
|
|
mv.visitInsn(LUSHR);
|
|
else
|
|
mv.visitInsn(IUSHR);
|
|
break;
|
|
}
|
|
case Greater greater: {
|
|
var type = largerType(greater.left().type(), greater.right().type());
|
|
if (type.equals(TargetType.Long)) {
|
|
generateRelationalOperator(state, greater, type, LCMP, IFGT);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
generateRelationalOperator(state, greater, type, FCMPL, IFGT);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
generateRelationalOperator(state, greater, type, DCMPL, IFGT);
|
|
} else {
|
|
generateRelationalOperator(state, greater, type, IF_ICMPGT);
|
|
}
|
|
break;
|
|
}
|
|
case Less less: {
|
|
var type = largerType(less.left().type(), less.right().type());
|
|
if (type.equals(TargetType.Long)) {
|
|
generateRelationalOperator(state, less, type, LCMP, IFLT);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
generateRelationalOperator(state, less, type, FCMPL, IFLT);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
generateRelationalOperator(state, less, type, DCMPL, IFLT);
|
|
} else {
|
|
generateRelationalOperator(state, less, type, IF_ICMPLT);
|
|
}
|
|
break;
|
|
}
|
|
case GreaterOrEqual greaterOrEqual: {
|
|
var type = largerType(greaterOrEqual.left().type(), greaterOrEqual.right().type());
|
|
if (type.equals(TargetType.Long)) {
|
|
generateRelationalOperator(state, greaterOrEqual, type, LCMP, IFGE);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
generateRelationalOperator(state, greaterOrEqual, type, FCMPL, IFGE);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
generateRelationalOperator(state, greaterOrEqual, type, DCMPL, IFGE);
|
|
} else {
|
|
generateRelationalOperator(state, greaterOrEqual, type, IF_ICMPGE);
|
|
}
|
|
break;
|
|
}
|
|
case LessOrEqual lessOrEqual: {
|
|
var type = largerType(lessOrEqual.left().type(), lessOrEqual.right().type());
|
|
if (type.equals(TargetType.Long)) {
|
|
generateRelationalOperator(state, lessOrEqual, type, LCMP, IFLE);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
generateRelationalOperator(state, lessOrEqual, type, FCMPL, IFLE);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
generateRelationalOperator(state, lessOrEqual, type, DCMPL, IFLE);
|
|
} else {
|
|
generateRelationalOperator(state, lessOrEqual, type, IF_ICMPLE);
|
|
}
|
|
break;
|
|
}
|
|
case Equal equal: {
|
|
var type = largerType(equal.left().type(), equal.right().type());
|
|
if (type.equals(TargetType.Long)) {
|
|
generateRelationalOperator(state, equal, type, LCMP, IFEQ);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
generateRelationalOperator(state, equal, type, FCMPL, IFEQ);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
generateRelationalOperator(state, equal, type, DCMPL, IFEQ);
|
|
} else if (type.equals(TargetType.Char) || type.equals(TargetType.Short) || type.equals(TargetType.Byte) || type.equals(TargetType.Integer) || type.equals(TargetType.Boolean)) {
|
|
generateRelationalOperator(state, equal, type, IF_ICMPEQ);
|
|
} else {
|
|
generateRelationalOperator(state, equal, type, IF_ACMPEQ);
|
|
}
|
|
break;
|
|
}
|
|
case NotEqual notEqual: {
|
|
var type = largerType(notEqual.left().type(), notEqual.right().type());
|
|
if (type.equals(TargetType.Long)) {
|
|
generateRelationalOperator(state, notEqual, type, LCMP, IFNE);
|
|
} else if (type.equals(TargetType.Float)) {
|
|
generateRelationalOperator(state, notEqual, type, FCMPL, IFNE);
|
|
} else if (type.equals(TargetType.Double)) {
|
|
generateRelationalOperator(state, notEqual, type, DCMPL, IFNE);
|
|
} else if (type.equals(TargetType.Char) || type.equals(TargetType.Short) || type.equals(TargetType.Byte) || type.equals(TargetType.Integer)) {
|
|
generateRelationalOperator(state, notEqual, type, IF_ICMPNE);
|
|
} else {
|
|
generateRelationalOperator(state, notEqual, type, IF_ACMPNE);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void afterIncDec(State state, TargetUnaryOp op) {
|
|
var mv = state.mv;
|
|
if (op.expr() instanceof TargetLocalVar localVar) {
|
|
mv.visitVarInsn(ASTORE, state.scope.get(localVar.name()).index);
|
|
} else if (op.expr() instanceof TargetFieldVar fieldVar) {
|
|
generate(state, fieldVar.left());
|
|
mv.visitInsn(SWAP);
|
|
mv.visitFieldInsn(PUTFIELD, fieldVar.owner().getInternalName(), fieldVar.right(), fieldVar.type().toSignature());
|
|
}
|
|
}
|
|
|
|
private void generateUnaryOp(State state, TargetUnaryOp op) {
|
|
var mv = state.mv;
|
|
switch (op) {
|
|
case TargetUnaryOp.Add add ->
|
|
// This literally does nothing
|
|
generate(state, add.expr());
|
|
case TargetUnaryOp.Negate negate -> {
|
|
generate(state, negate.expr());
|
|
if (negate.type().equals(TargetType.Double))
|
|
mv.visitInsn(DNEG);
|
|
else if (negate.type().equals(TargetType.Float))
|
|
mv.visitInsn(FNEG);
|
|
else if (negate.type().equals(TargetType.Long))
|
|
mv.visitInsn(LNEG);
|
|
else
|
|
mv.visitInsn(INEG);
|
|
}
|
|
case TargetUnaryOp.Not not -> {
|
|
generate(state, not.expr());
|
|
if (not.type().equals(TargetType.Long)) {
|
|
mv.visitLdcInsn(-1L);
|
|
mv.visitInsn(LXOR);
|
|
} else {
|
|
mv.visitInsn(ICONST_M1);
|
|
mv.visitInsn(IXOR);
|
|
}
|
|
}
|
|
case TargetUnaryOp.PreIncrement preIncrement -> {
|
|
generate(state, preIncrement.expr());
|
|
if (preIncrement.type().equals(TargetType.Float)) {
|
|
mv.visitLdcInsn(1F);
|
|
mv.visitInsn(FADD);
|
|
mv.visitInsn(DUP);
|
|
} else if (preIncrement.type().equals(TargetType.Double)) {
|
|
mv.visitLdcInsn(1D);
|
|
mv.visitInsn(DADD);
|
|
mv.visitInsn(DUP2);
|
|
} else if (preIncrement.type().equals(TargetType.Long)) {
|
|
mv.visitLdcInsn(1L);
|
|
mv.visitInsn(LADD);
|
|
mv.visitInsn(DUP2);
|
|
} else {
|
|
mv.visitLdcInsn(1);
|
|
mv.visitInsn(IADD);
|
|
mv.visitInsn(DUP);
|
|
}
|
|
boxPrimitive(state, preIncrement.type());
|
|
afterIncDec(state, preIncrement);
|
|
}
|
|
case TargetUnaryOp.PreDecrement preDecrement -> {
|
|
generate(state, preDecrement.expr());
|
|
if (preDecrement.type().equals(TargetType.Float)) {
|
|
mv.visitLdcInsn(1F);
|
|
mv.visitInsn(FSUB);
|
|
mv.visitInsn(DUP);
|
|
} else if (preDecrement.type().equals(TargetType.Double)) {
|
|
mv.visitLdcInsn(1D);
|
|
mv.visitInsn(DSUB);
|
|
mv.visitInsn(DUP2);
|
|
} else if (preDecrement.type().equals(TargetType.Long)) {
|
|
mv.visitLdcInsn(1L);
|
|
mv.visitInsn(LSUB);
|
|
mv.visitInsn(DUP2);
|
|
} else {
|
|
mv.visitLdcInsn(1);
|
|
mv.visitInsn(ISUB);
|
|
mv.visitInsn(DUP);
|
|
}
|
|
boxPrimitive(state, preDecrement.type());
|
|
afterIncDec(state, preDecrement);
|
|
}
|
|
case TargetUnaryOp.PostIncrement postIncrement -> {
|
|
generate(state, postIncrement.expr());
|
|
if (postIncrement.type().equals(TargetType.Float)) {
|
|
mv.visitInsn(DUP);
|
|
mv.visitLdcInsn(1F);
|
|
mv.visitInsn(FADD);
|
|
} else if (postIncrement.type().equals(TargetType.Double)) {
|
|
mv.visitInsn(DUP2);
|
|
mv.visitLdcInsn(1D);
|
|
mv.visitInsn(DADD);
|
|
} else if (postIncrement.type().equals(TargetType.Long)) {
|
|
mv.visitInsn(DUP2);
|
|
mv.visitLdcInsn(1L);
|
|
mv.visitInsn(LADD);
|
|
} else {
|
|
mv.visitInsn(DUP);
|
|
mv.visitLdcInsn(1);
|
|
mv.visitInsn(IADD);
|
|
}
|
|
boxPrimitive(state, postIncrement.type());
|
|
afterIncDec(state, postIncrement);
|
|
}
|
|
case TargetUnaryOp.PostDecrement postDecrement -> {
|
|
generate(state, postDecrement.expr());
|
|
if (postDecrement.type().equals(TargetType.Float)) {
|
|
mv.visitInsn(DUP);
|
|
mv.visitLdcInsn(1F);
|
|
mv.visitInsn(FSUB);
|
|
} else if (postDecrement.type().equals(TargetType.Double)) {
|
|
mv.visitInsn(DUP2);
|
|
mv.visitLdcInsn(1D);
|
|
mv.visitInsn(DSUB);
|
|
} else if (postDecrement.type().equals(TargetType.Long)) {
|
|
mv.visitInsn(DUP2);
|
|
mv.visitLdcInsn(1L);
|
|
mv.visitInsn(LSUB);
|
|
} else {
|
|
mv.visitInsn(DUP);
|
|
mv.visitLdcInsn(1);
|
|
mv.visitInsn(ISUB);
|
|
}
|
|
boxPrimitive(state, postDecrement.type());
|
|
afterIncDec(state, postDecrement);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void generateLambdaExpression(State state, TargetLambdaExpression lambda) {
|
|
var mv = state.mv;
|
|
|
|
TargetMethod impl;
|
|
if (lambdas.containsKey(lambda)) {
|
|
impl = lambdas.get(lambda);
|
|
} else {
|
|
var name = "lambda$" + lambdaCounter++;
|
|
var parameters = new ArrayList<>(lambda.captures());
|
|
parameters.addAll(lambda.params().stream().map(param -> param.pattern().type() instanceof TargetGenericType ? param.withType(TargetType.Object) : param).toList());
|
|
|
|
impl = new TargetMethod(0, name, lambda.block(), new TargetMethod.Signature(Set.of(), parameters, lambda.returnType() instanceof TargetGenericType ? TargetType.Object : lambda.returnType()), null);
|
|
generateMethod(impl);
|
|
lambdas.put(lambda, 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(), impl.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";
|
|
|
|
var params = new ArrayList<TargetType>();
|
|
params.add(new TargetRefType(clazz.qualifiedName().getClassName()));
|
|
params.addAll(lambda.captures().stream().map(mp -> mp.pattern().type()).toList());
|
|
|
|
var descriptor = TargetMethod.getDescriptor(state.contextType, params.toArray(TargetType[]::new));
|
|
mv.visitVarInsn(ALOAD, 0);
|
|
for (var capture : lambda.captures()) {
|
|
var pattern = (TargetTypePattern) capture.pattern();
|
|
mv.visitVarInsn(ALOAD, state.scope.get(pattern.name()).index);
|
|
}
|
|
|
|
String methodName;
|
|
var intf = compiler.getClass(new JavaClassName(state.contextType.name()));
|
|
if (intf == null) methodName = "apply"; // TODO Weird fallback logic here
|
|
else methodName = intf.getMethods().stream().filter(m -> Modifier.isAbstract(m.modifier)).findFirst().orElseThrow().getName();
|
|
|
|
mv.visitInvokeDynamicInsn(methodName, descriptor, bootstrap, Type.getType(desugared), handle, Type.getType(TargetMethod.getDescriptor(impl.signature().returnType(), lambda.params().stream().map(mp -> mp.pattern().type()).toArray(TargetType[]::new))));
|
|
}
|
|
|
|
private void generate(State state, TargetExpression expr) {
|
|
var mv = state.mv;
|
|
switch (expr) {
|
|
case TargetClassName ignored:
|
|
break; // NOP
|
|
case TargetBlock block: {
|
|
var localCounter = state.localCounter;
|
|
state.enterScope();
|
|
for (var e : block.statements()) {
|
|
generate(state, e);
|
|
if (e instanceof TargetMethodCall) {
|
|
if (e.type() != null)
|
|
popValue(state, e.type());
|
|
} else if (e instanceof TargetAssign) {
|
|
mv.visitInsn(POP); // TODO Nasty fix, we don't know if it is a primitive double or long
|
|
} else if (e instanceof TargetStatementExpression se) {
|
|
popValue(state, se.type());
|
|
}
|
|
}
|
|
state.exitScope();
|
|
state.localCounter = localCounter;
|
|
break;
|
|
}
|
|
case TargetCast cast:
|
|
var ctx = state.contextType;
|
|
state.contextType = cast.type();
|
|
generate(state, cast.expr());
|
|
state.contextType = ctx;
|
|
convertTo(state, cast.expr().type(), cast.type());
|
|
break;
|
|
case TargetInstanceOf instanceOf:
|
|
generateInstanceOf(state, instanceOf);
|
|
break;
|
|
case TargetLiteral literal:
|
|
switch (literal) {
|
|
case IntLiteral intLiteral -> mv.visitLdcInsn(intLiteral.value());
|
|
case FloatLiteral floatLiteral -> mv.visitLdcInsn(floatLiteral.value());
|
|
case LongLiteral longLiteral -> mv.visitLdcInsn(longLiteral.value());
|
|
case StringLiteral stringLiteral -> mv.visitLdcInsn(stringLiteral.value());
|
|
case CharLiteral charLiteral -> mv.visitIntInsn(BIPUSH, charLiteral.value());
|
|
case DoubleLiteral doubleLiteral -> mv.visitLdcInsn(doubleLiteral.value());
|
|
case BooleanLiteral booleanLiteral -> {
|
|
if (booleanLiteral.value()) {
|
|
mv.visitInsn(ICONST_1);
|
|
} else {
|
|
mv.visitInsn(ICONST_0);
|
|
}
|
|
}
|
|
default -> {}
|
|
}
|
|
break;
|
|
case TargetVarDecl varDecl: {
|
|
var local = state.createVariable(varDecl.name(), varDecl.varType());
|
|
if (varDecl.value() != null) {
|
|
generate(state, varDecl.value());
|
|
boxPrimitive(state, varDecl.varType());
|
|
mv.visitVarInsn(ASTORE, local.index());
|
|
} else {
|
|
mv.visitInsn(ACONST_NULL);
|
|
mv.visitVarInsn(ASTORE, local.index());
|
|
}
|
|
break;
|
|
}
|
|
case TargetBinaryOp op:
|
|
generateBinaryOp(state, op);
|
|
break;
|
|
case TargetUnaryOp op:
|
|
generateUnaryOp(state, op);
|
|
break;
|
|
case TargetAssign assign: {
|
|
switch (assign.left()) {
|
|
case TargetLocalVar localVar -> {
|
|
var ctype = state.contextType;
|
|
state.contextType = localVar.type();
|
|
generate(state, assign.right());
|
|
state.contextType = ctype;
|
|
|
|
convertTo(state, assign.right().type(), localVar.type());
|
|
boxPrimitive(state, localVar.type());
|
|
var local = state.scope.get(localVar.name());
|
|
mv.visitInsn(DUP);
|
|
mv.visitVarInsn(ASTORE, local.index());
|
|
}
|
|
case TargetFieldVar dot -> {
|
|
var fieldType = dot.type();
|
|
if (!(dot.left() instanceof TargetThis && dot.isStatic()))
|
|
generate(state, dot.left());
|
|
|
|
var ctype = state.contextType;
|
|
state.contextType = fieldType;
|
|
generate(state, assign.right());
|
|
state.contextType = ctype;
|
|
|
|
convertTo(state, assign.right().type(), fieldType);
|
|
boxPrimitive(state, fieldType);
|
|
if (dot.isStatic())
|
|
mv.visitInsn(DUP);
|
|
else
|
|
mv.visitInsn(DUP_X1);
|
|
mv.visitFieldInsn(dot.isStatic() ? PUTSTATIC : PUTFIELD, dot.owner().getInternalName(), dot.right(), fieldType.toSignature());
|
|
}
|
|
default -> throw new CodeGenException("Invalid assignment");
|
|
}
|
|
break;
|
|
}
|
|
case TargetLocalVar localVar: {
|
|
LocalVar local = state.scope.get(localVar.name());
|
|
mv.visitVarInsn(ALOAD, local.index());
|
|
unboxPrimitive(state, local.type());
|
|
break;
|
|
}
|
|
case TargetFieldVar dot: {
|
|
if (!dot.isStatic())
|
|
generate(state, dot.left());
|
|
mv.visitFieldInsn(dot.isStatic() ? GETSTATIC : GETFIELD, dot.left().type().getInternalName(), dot.right(), dot.type().toSignature());
|
|
unboxPrimitive(state, dot.type());
|
|
break;
|
|
}
|
|
case TargetFor _for: {
|
|
state.enterScope();
|
|
var localCounter = state.localCounter;
|
|
if (_for.init() != null)
|
|
_for.init().forEach(e -> generate(state, e));
|
|
|
|
Label start = new Label();
|
|
Label end = new Label();
|
|
mv.visitLabel(start);
|
|
if (_for.termination() != null)
|
|
generate(state, _for.termination());
|
|
else
|
|
mv.visitInsn(ICONST_1);
|
|
mv.visitJumpInsn(IFEQ, end);
|
|
|
|
var env = new BreakEnv();
|
|
env.startLabel = start;
|
|
env.endLabel = end;
|
|
|
|
state.breakStack.push(env);
|
|
generate(state, _for.body());
|
|
state.breakStack.pop();
|
|
|
|
if (_for.increment() != null) {
|
|
_for.increment().forEach(e -> {
|
|
generate(state, e);
|
|
if (e.type() != null) {
|
|
popValue(state, e.type());
|
|
}
|
|
});
|
|
}
|
|
mv.visitJumpInsn(GOTO, start);
|
|
mv.visitLabel(end);
|
|
state.exitScope();
|
|
state.localCounter = localCounter;
|
|
break;
|
|
}
|
|
case TargetWhile _while: {
|
|
Label start = new Label();
|
|
Label end = new Label();
|
|
mv.visitLabel(start);
|
|
generate(state, _while.cond());
|
|
mv.visitJumpInsn(IFEQ, end);
|
|
|
|
var env = new BreakEnv();
|
|
env.startLabel = start;
|
|
env.endLabel = end;
|
|
|
|
state.breakStack.push(env);
|
|
generate(state, _while.body());
|
|
state.breakStack.pop();
|
|
|
|
mv.visitJumpInsn(GOTO, start);
|
|
mv.visitLabel(end);
|
|
break;
|
|
}
|
|
case TargetIf _if: {
|
|
generate(state, _if.cond());
|
|
Label _else = new Label();
|
|
Label end = new Label();
|
|
mv.visitJumpInsn(IFEQ, _else);
|
|
generate(state, _if.if_body());
|
|
mv.visitJumpInsn(GOTO, end);
|
|
mv.visitLabel(_else);
|
|
if (_if.else_body() != null) {
|
|
generate(state, _if.else_body());
|
|
}
|
|
mv.visitLabel(end);
|
|
break;
|
|
}
|
|
case TargetReturn ret: {
|
|
if (ret.expression() != null && state.returnType != null) {
|
|
var ctype = state.contextType;
|
|
state.contextType = state.returnType;
|
|
generate(state, ret.expression());
|
|
state.contextType = ctype;
|
|
convertTo(state, ret.expression().type(), state.returnType);
|
|
boxPrimitive(state, state.returnType);
|
|
mv.visitInsn(ARETURN);
|
|
} else
|
|
mv.visitInsn(RETURN);
|
|
break;
|
|
}
|
|
case TargetYield yield: {
|
|
generate(state, yield.expression());
|
|
try {
|
|
yieldValue(state, yield.expression().type());
|
|
mv.visitJumpInsn(GOTO, state.breakStack.peek().endLabel);
|
|
} catch (EmptyStackException e) {
|
|
throw new CodeGenException("Yield outside of switch expression");
|
|
}
|
|
break;
|
|
}
|
|
case TargetSwitch _switch: {
|
|
generateSwitch(state, _switch);
|
|
break;
|
|
}
|
|
case TargetBreak brk: {
|
|
if (state.breakStack.isEmpty()) throw new CodeGenException("Break outside of switch or loop");
|
|
mv.visitJumpInsn(GOTO, state.breakStack.peek().endLabel);
|
|
break;
|
|
}
|
|
case TargetContinue cnt: {
|
|
if (state.breakStack.isEmpty()) throw new CodeGenException("Continue outside of loop");
|
|
var env = state.breakStack.peek();
|
|
if (env.startLabel == null) throw new CodeGenException("Continue outside of loop");
|
|
mv.visitJumpInsn(GOTO, env.startLabel);
|
|
break;
|
|
}
|
|
case TargetThis _this: {
|
|
mv.visitVarInsn(ALOAD, 0);
|
|
break;
|
|
}
|
|
case TargetSuper _super: {
|
|
mv.visitVarInsn(ALOAD, 0);
|
|
break;
|
|
}
|
|
case TargetMethodCall call: {
|
|
generate(state, call.expr());
|
|
for (var i = 0; i < call.args().size(); i++) {
|
|
var e = call.args().get(i);
|
|
var arg = call.parameterTypes().get(i);
|
|
var ctype = state.contextType;
|
|
state.contextType = arg;
|
|
generate(state, e);
|
|
if (!(arg instanceof TargetPrimitiveType))
|
|
boxPrimitive(state, e.type());
|
|
state.contextType = ctype;
|
|
}
|
|
var descriptor = call.getDescriptor();
|
|
if (call.owner() instanceof TargetFunNType) // Decay FunN
|
|
descriptor = TargetMethod.getDescriptor(call.returnType() == null ? null : TargetType.Object, call.parameterTypes().stream().map(x -> TargetType.Object).toArray(TargetType[]::new));
|
|
|
|
mv.visitMethodInsn(call.isInterface() ? INVOKEINTERFACE : call.isStatic() ? INVOKESTATIC : call.name().equals("<init>") ? INVOKESPECIAL : INVOKEVIRTUAL, call.owner().getInternalName(), call.name(), descriptor, call.isInterface());
|
|
|
|
if (call.type() != null && call.returnType() != null && !(call.returnType() instanceof TargetPrimitiveType)) {
|
|
if (!call.returnType().equals(call.type()) && !(call.type() instanceof TargetGenericType))
|
|
mv.visitTypeInsn(CHECKCAST, call.type().getInternalName());
|
|
unboxPrimitive(state, call.type());
|
|
}
|
|
break;
|
|
}
|
|
case TargetLambdaExpression lambda:
|
|
generateLambdaExpression(state, lambda);
|
|
break;
|
|
case TargetNew _new: {
|
|
mv.visitTypeInsn(NEW, _new.type().getInternalName());
|
|
mv.visitInsn(DUP);
|
|
for (TargetExpression e : _new.params()) {
|
|
generate(state, e);
|
|
boxPrimitive(state, e.type());
|
|
}
|
|
mv.visitMethodInsn(INVOKESPECIAL, _new.type().getInternalName(), "<init>", _new.getDescriptor(), false);
|
|
break;
|
|
}
|
|
default:
|
|
throw new CodeGenException("Unexpected value: " + expr);
|
|
}
|
|
}
|
|
|
|
private void generateInstanceOf(State state, TargetInstanceOf instanceOf) {
|
|
var mv = state.mv;
|
|
|
|
if (instanceOf.right() instanceof TargetTypePattern right && right.name() == null) {
|
|
mv.visitTypeInsn(INSTANCEOF, right.type().getInternalName());
|
|
return;
|
|
}
|
|
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
private void yieldValue(State state, TargetType type) {
|
|
boxPrimitive(state, type);
|
|
state.mv.visitVarInsn(ASTORE, state.switchResultValue.peek());
|
|
}
|
|
|
|
private void generateClassicSwitch(State state, TargetSwitch aSwitch) {
|
|
// TODO Constant expressions are allowed, we need to evaluate them somehow...
|
|
// For now we just assume we get literals...
|
|
// TODO This always uses a lookupswitch, a tableswitch may be faster in some cases but we can't generate that every time
|
|
// TODO We can't switch on Strings yet, the idea for this (like javac does it) would be to implement the hash code at compile time
|
|
// and switch based on that, adding an equals check for every case and going to yet another tableswitch which finally decides which branch to take
|
|
var mv = state.mv;
|
|
if (aSwitch.isExpression())
|
|
state.pushSwitch();
|
|
|
|
generate(state, aSwitch.expr());
|
|
|
|
state.enterScope();
|
|
|
|
var keys = new int[aSwitch.cases().stream().mapToInt(c -> c.labels().size()).sum()];
|
|
var labels = new Label[keys.length];
|
|
var bodyLabels = new Label[aSwitch.cases().size()];
|
|
|
|
var end = new Label();
|
|
var env = new BreakEnv();
|
|
env.endLabel = end;
|
|
state.breakStack.push(env);
|
|
|
|
var i = 0;
|
|
var j = 0;
|
|
for (var case_ : aSwitch.cases()) {
|
|
bodyLabels[j] = new Label();
|
|
for (var label : case_.labels()) {
|
|
if (!(label instanceof TargetLiteral literal))
|
|
throw new CodeGenException("Labels may only be constants for now");
|
|
keys[i] = (int) literal.value();
|
|
labels[i] = bodyLabels[j];
|
|
i += 1;
|
|
}
|
|
j += 1;
|
|
}
|
|
|
|
var defaultLabel = end;
|
|
if (aSwitch.default_() != null) {
|
|
defaultLabel = new Label();
|
|
}
|
|
|
|
mv.visitLookupSwitchInsn(defaultLabel, keys, labels);
|
|
|
|
for (var k = 0; k < aSwitch.cases().size(); k++) {
|
|
mv.visitLabel(bodyLabels[k]);
|
|
var cse = aSwitch.cases().get(k);
|
|
generate(state, cse.body());
|
|
if (cse.isSingleExpression() && aSwitch.isExpression())
|
|
yieldValue(state, cse.body().statements().get(0).type());
|
|
if (aSwitch.isExpression()) mv.visitJumpInsn(GOTO, end);
|
|
}
|
|
|
|
if (aSwitch.default_() != null) {
|
|
mv.visitLabel(defaultLabel);
|
|
generate(state, aSwitch.default_().body());
|
|
if (aSwitch.default_().isSingleExpression() && aSwitch.isExpression())
|
|
yieldValue(state, aSwitch.default_().body().statements().get(0).type());
|
|
}
|
|
|
|
mv.visitLabel(end);
|
|
state.breakStack.pop();
|
|
|
|
if (aSwitch.isExpression()) {
|
|
mv.visitVarInsn(ALOAD, state.switchResultValue.peek());
|
|
unboxPrimitive(state, aSwitch.type());
|
|
state.popSwitch();
|
|
}
|
|
|
|
state.exitScope();
|
|
}
|
|
|
|
private void generateEnhancedSwitch(State state, TargetSwitch aSwitch) {
|
|
var mv = state.mv;
|
|
generate(state, aSwitch.expr());
|
|
var tmp = state.localCounter;
|
|
mv.visitInsn(DUP);
|
|
mv.visitVarInsn(ASTORE, tmp);
|
|
|
|
state.enterScope();
|
|
// This is the index to start the switch from
|
|
mv.visitInsn(ICONST_0);
|
|
if (aSwitch.isExpression())
|
|
state.pushSwitch();
|
|
|
|
// To be able to skip ahead to the next case
|
|
var start = new Label();
|
|
mv.visitLabel(start);
|
|
|
|
var end = new Label();
|
|
var env = new BreakEnv();
|
|
env.endLabel = end;
|
|
state.breakStack.push(env);
|
|
|
|
var mt = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class);
|
|
var bootstrap = new Handle(H_INVOKESTATIC, "java/lang/runtime/SwitchBootstraps", "typeSwitch", mt.toMethodDescriptorString(), false);
|
|
|
|
var types = new ArrayList<Object>(aSwitch.cases().size());
|
|
for (var cse : aSwitch.cases()) for (var label : cse.labels()) {
|
|
if (label instanceof TargetTypePattern || label instanceof TargetComplexPattern)
|
|
types.add(Type.getObjectType(label.type().getInternalName()));
|
|
else if (label instanceof TargetLiteral lit)
|
|
types.add(lit.value());
|
|
else if (label instanceof TargetGuard guard)
|
|
types.add(Type.getObjectType(guard.inner().type().getInternalName()));
|
|
// TODO Same here we need to evaluate constant;
|
|
else {
|
|
System.out.println(label);
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
mv.visitInvokeDynamicInsn("typeSwitch", "(Ljava/lang/Object;I)I", bootstrap, types.toArray());
|
|
|
|
var caseLabels = new Label[aSwitch.cases().size()];
|
|
var labels = new Label[aSwitch.cases().stream().mapToInt(c -> c.labels().size()).sum()];
|
|
var j = 0;
|
|
for (var i = 0; i < caseLabels.length; i++) {
|
|
var cse = aSwitch.cases().get(i);
|
|
var label = new Label();
|
|
caseLabels[i] = label;
|
|
for (var k = 0; k < cse.labels().size(); k++) {
|
|
labels[j] = label;
|
|
j += 1;
|
|
}
|
|
}
|
|
|
|
var defaultLabel = end;
|
|
if (aSwitch.default_() != null) {
|
|
defaultLabel = new Label();
|
|
}
|
|
|
|
mv.visitTableSwitchInsn(0, labels.length - 1, defaultLabel, labels);
|
|
|
|
for (var i = 0; i < aSwitch.cases().size(); i++) {
|
|
mv.visitLabel(caseLabels[i]);
|
|
var cse = aSwitch.cases().get(i);
|
|
|
|
if (cse.labels().size() == 1) {
|
|
var label = cse.labels().get(0);
|
|
if (label instanceof TargetGuard gd) {
|
|
state.mv.visitVarInsn(ALOAD, tmp);
|
|
bindPattern(state, aSwitch.expr().type(), gd.inner(), start, i, 1);
|
|
} else if (label instanceof TargetPattern pat) {
|
|
state.mv.visitVarInsn(ALOAD, tmp);
|
|
bindPattern(state, aSwitch.expr().type(), pat, start, i, 1);
|
|
}
|
|
|
|
if (label instanceof TargetGuard gd) {
|
|
generate(state, gd.expression());
|
|
var next = new Label();
|
|
mv.visitJumpInsn(IFNE, next);
|
|
mv.visitVarInsn(ALOAD, tmp);
|
|
// Push the offset onto the stack (this is used by the invokedynamic call)
|
|
mv.visitLdcInsn(i + 1);
|
|
mv.visitJumpInsn(GOTO, start);
|
|
mv.visitLabel(next);
|
|
}
|
|
}
|
|
|
|
generate(state, cse.body());
|
|
if (cse.isSingleExpression() && aSwitch.isExpression())
|
|
yieldValue(state, cse.body().statements().get(0).type());
|
|
if (aSwitch.isExpression()) mv.visitJumpInsn(GOTO, end);
|
|
}
|
|
|
|
if (aSwitch.default_() != null) {
|
|
mv.visitLabel(defaultLabel);
|
|
generate(state, aSwitch.default_().body());
|
|
if (aSwitch.default_().isSingleExpression() && aSwitch.isExpression())
|
|
yieldValue(state, aSwitch.default_().body().statements().get(0).type());
|
|
}
|
|
|
|
mv.visitLabel(end);
|
|
//mv.visitInsn(POP);
|
|
|
|
state.breakStack.pop();
|
|
if (aSwitch.isExpression()) {
|
|
mv.visitVarInsn(ALOAD, state.switchResultValue.peek());
|
|
unboxPrimitive(state, aSwitch.type());
|
|
state.popSwitch();
|
|
}
|
|
|
|
state.exitScope();
|
|
}
|
|
|
|
private void extractField(State state, TargetType type, int i, ClassOrInterface clazz) {
|
|
if (i >= clazz.getFieldDecl().size())
|
|
throw new CodeGenException("Couldn't find suitable field accessor for '" + type.name() + "'");
|
|
var field = clazz.getFieldDecl().get(i);
|
|
var fieldType = new TargetRefType(((RefType) field.getType()).getName().toString());
|
|
state.mv.visitMethodInsn(INVOKEVIRTUAL, type.getInternalName(), field.getName(), "()" + fieldType.toDescriptor(), false);
|
|
}
|
|
|
|
private void bindPattern(State state, TargetType type, TargetPattern pat, Label start, int index, int depth) {
|
|
if (pat.type() instanceof TargetPrimitiveType)
|
|
boxPrimitive(state, pat.type());
|
|
|
|
state.mv.visitInsn(DUP);
|
|
state.mv.visitTypeInsn(INSTANCEOF, pat.type().getInternalName());
|
|
|
|
var cont = new Label();
|
|
state.mv.visitJumpInsn(IFNE, cont);
|
|
for (var i = 0; i < depth; i++) {
|
|
state.mv.visitInsn(POP);
|
|
}
|
|
state.mv.visitVarInsn(ALOAD, state.switchResultValue.peek());
|
|
state.mv.visitLdcInsn(index + 1);
|
|
state.mv.visitJumpInsn(GOTO, start);
|
|
state.mv.visitLabel(cont);
|
|
|
|
state.mv.visitTypeInsn(CHECKCAST, pat.type().getInternalName());
|
|
|
|
if (pat instanceof TargetTypePattern sp) {
|
|
var local = state.createVariable(sp.name(), sp.type());
|
|
state.mv.visitVarInsn(ASTORE, local.index);
|
|
} else if (pat instanceof TargetComplexPattern cp) {
|
|
if (cp.name() != null) {
|
|
state.mv.visitInsn(DUP);
|
|
var local = state.createVariable(cp.name(), cp.type());
|
|
state.mv.visitVarInsn(ASTORE, local.index);
|
|
}
|
|
|
|
var clazz = findClass(new JavaClassName(cp.type().name()));
|
|
if (clazz == null) throw new CodeGenException("Class definition for '" + cp.type().name() + "' not found");
|
|
// TODO Check if class is a Record
|
|
|
|
for (var i = 0; i < cp.subPatterns().size(); i++) {
|
|
state.mv.visitInsn(DUP);
|
|
|
|
var subPattern = cp.subPatterns().get(i);
|
|
extractField(state, cp.type(), i, clazz);
|
|
bindPattern(state, subPattern.type(), subPattern, start, index, depth + 1);
|
|
}
|
|
state.mv.visitInsn(POP);
|
|
}
|
|
}
|
|
|
|
final Set<TargetType> wrapperTypes = Set.of(TargetType.Long, TargetType.Integer, TargetType.Byte, TargetType.Char, TargetType.Boolean, TargetType.Double, TargetType.Float);
|
|
|
|
private void generateSwitch(State state, TargetSwitch aSwitch) {
|
|
if (!wrapperTypes.contains(aSwitch.expr().type())) {
|
|
generateEnhancedSwitch(state, aSwitch);
|
|
return;
|
|
}
|
|
else for (var case_ : aSwitch.cases()) {
|
|
if (case_.labels().stream().anyMatch(c -> c instanceof TargetPattern)) {
|
|
generateEnhancedSwitch(state, aSwitch);
|
|
return;
|
|
}
|
|
}
|
|
generateClassicSwitch(state, aSwitch);
|
|
}
|
|
|
|
private void generateField(TargetField field) {
|
|
var access = field.access();
|
|
if ((access & ACC_PRIVATE) == 0 && (access & ACC_PROTECTED) == 0) // TODO Implement access modifiers properly
|
|
access |= ACC_PUBLIC;
|
|
|
|
cw.visitField(access, field.name(), field.type().toSignature(), field.type().toDescriptor(), null);
|
|
}
|
|
|
|
private void generateStaticConstructor(TargetMethod constructor) {
|
|
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "<clinit>", "()V", null, null);
|
|
mv.visitCode();
|
|
|
|
var state = new State(null, mv, 0);
|
|
generate(state, constructor.block());
|
|
|
|
mv.visitInsn(RETURN);
|
|
mv.visitMaxs(0, 0);
|
|
mv.visitEnd();
|
|
}
|
|
|
|
private void generateConstructor(TargetConstructor constructor) {
|
|
MethodVisitor mv = cw.visitMethod(constructor.access() | ACC_PUBLIC, "<init>", constructor.getDescriptor(), constructor.getSignature(), null);
|
|
if (constructor.txGenerics() != null)
|
|
mv.visitAttribute(new JavaTXSignatureAttribute(constructor.getTXSignature()));
|
|
|
|
mv.visitCode();
|
|
var state = new State(null, mv, 1);
|
|
for (var param : constructor.parameters()) {
|
|
var pattern = param.pattern();
|
|
if (pattern instanceof TargetTypePattern tp)
|
|
state.createVariable(tp.name(), tp.type());
|
|
else throw new NotImplementedException();
|
|
}
|
|
|
|
var stmts = constructor.block().statements();
|
|
generate(state, stmts.get(0));
|
|
if (constructor.fieldInitializer() != null) {
|
|
var stmts2 = constructor.fieldInitializer().statements();
|
|
for (var i = 1; i < stmts2.size(); i++) {
|
|
generate(state, stmts2.get(i));
|
|
}
|
|
}
|
|
for (var i = 1; i < stmts.size(); i++)
|
|
generate(state, stmts.get(i));
|
|
|
|
mv.visitInsn(RETURN);
|
|
mv.visitMaxs(0, 0);
|
|
mv.visitEnd();
|
|
}
|
|
|
|
private int bindLocalVariables(State state, TargetPattern pattern, int offset, int field) {
|
|
if (pattern instanceof TargetComplexPattern cp) {
|
|
state.mv.visitVarInsn(ALOAD, offset);
|
|
|
|
var clazz = findClass(new JavaClassName(cp.type().name()));
|
|
if (clazz == null) throw new CodeGenException("Class definition for '" + cp.type().name() + "' not found");
|
|
|
|
for (var i = 0; i < cp.subPatterns().size(); i++) {
|
|
var subPattern = cp.subPatterns().get(i);
|
|
|
|
if (i < cp.subPatterns().size() - 1)
|
|
state.mv.visitInsn(DUP);
|
|
|
|
extractField(state, cp.type(), i, clazz);
|
|
state.mv.visitTypeInsn(CHECKCAST, subPattern.type().getInternalName());
|
|
state.mv.visitVarInsn(ASTORE, offset);
|
|
offset = bindLocalVariables(state, subPattern, offset, i);
|
|
}
|
|
} else if (pattern instanceof TargetTypePattern tp) {
|
|
offset++;
|
|
state.createVariable(tp.name(), tp.type());
|
|
} else throw new NotImplementedException();
|
|
return offset;
|
|
}
|
|
|
|
private void generateMethod(TargetMethod method) {
|
|
var access = method.access() | ACC_PUBLIC;
|
|
if (method.block() == null)
|
|
access |= ACC_ABSTRACT;
|
|
|
|
// TODO The older codegen has set ACC_PUBLIC for all methods, good for testing but bad for everything else
|
|
MethodVisitor mv = cw.visitMethod(access, method.name(), method.getDescriptor(), method.getSignature(), null);
|
|
if (method.txSignature() != null) {
|
|
mv.visitAttribute(new JavaTXSignatureAttribute(method.getTXSignature()));
|
|
}
|
|
|
|
if (method.block() != null) {
|
|
mv.visitCode();
|
|
var state = new State(method.signature().returnType(), mv, method.isStatic() ? 0 : 1);
|
|
for (var param : method.signature().parameters()) {
|
|
bindLocalVariables(state, param.pattern(), 1, 0);
|
|
}
|
|
generate(state, method.block());
|
|
if (method.signature().returnType() == null)
|
|
mv.visitInsn(RETURN);
|
|
mv.visitMaxs(0, 0);
|
|
}
|
|
mv.visitEnd();
|
|
}
|
|
|
|
private static String generateSignature(TargetStructure clazz, Set<TargetGeneric> generics) {
|
|
String ret = "";
|
|
if (!generics.isEmpty()) {
|
|
ret += "<";
|
|
for (var generic : generics) {
|
|
ret += generic.name() + ":" + generic.bound().toDescriptor();
|
|
}
|
|
ret += ">";
|
|
}
|
|
if (clazz.superType() != null)
|
|
ret += clazz.superType().toDescriptor();
|
|
for (var intf : clazz.implementingInterfaces()) {
|
|
ret += intf.toSignature();
|
|
}
|
|
|
|
return ret.isEmpty() ? null : ret;
|
|
}
|
|
|
|
public byte[] generate() {
|
|
var access = clazz.modifiers();
|
|
if ((access & ACC_PRIVATE) == 0 && (access & ACC_PROTECTED) == 0) // TODO Implement access modifiers properly
|
|
access |= ACC_PUBLIC;
|
|
if (!(clazz instanceof TargetInterface))
|
|
access |= ACC_SUPER;
|
|
|
|
var signature = generateSignature(clazz, clazz.generics());
|
|
var interfaces = clazz.implementingInterfaces().stream().map(TargetType::getInternalName).toArray(String[]::new);
|
|
var superType = clazz.superType() != null ? clazz.superType().getInternalName() : "java/lang/Object";
|
|
|
|
cw.visit(V1_8, access, clazz.qualifiedName().toString().replaceAll("\\.", "/"), signature, superType, interfaces);
|
|
if (clazz.txGenerics() != null && signature != null)
|
|
cw.visitAttribute(new JavaTXSignatureAttribute(generateSignature(clazz, clazz.txGenerics())));
|
|
|
|
clazz.fields().forEach(this::generateField);
|
|
clazz.constructors().forEach(this::generateConstructor);
|
|
clazz.methods().forEach(this::generateMethod);
|
|
|
|
if (clazz.staticConstructor() != null)
|
|
generateStaticConstructor(clazz.staticConstructor());
|
|
|
|
if (clazz instanceof TargetRecord)
|
|
generateRecordMethods();
|
|
|
|
cw.visitEnd();
|
|
return cw.toByteArray();
|
|
}
|
|
|
|
private ClassOrInterface findClass(JavaClassName className) {
|
|
try {
|
|
for (var sf : compiler.sourceFiles.values()) {
|
|
for (var clazz : compiler.getAvailableClasses(sf)) {
|
|
if (clazz.getClassName().equals(className))
|
|
return clazz;
|
|
}
|
|
for (var clazz : sf.KlassenVektor) {
|
|
if (clazz.getClassName().equals(className))
|
|
return clazz;
|
|
}
|
|
}
|
|
} catch (ClassNotFoundException ignored) {}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void generateRecordMethods() {
|
|
var mt = MethodType.methodType(Object.class, MethodHandles.Lookup.class, String.class, TypeDescriptor.class, Class.class, String.class, MethodHandle[].class);
|
|
var bootstrap = new Handle(H_INVOKESTATIC, "java/lang/runtime/ObjectMethods", "bootstrap", mt.toMethodDescriptorString(), false);
|
|
var bootstrapArgs = new Object[2 + clazz.fields().size()];
|
|
|
|
bootstrapArgs[0] = Type.getObjectType(clazz.getName());
|
|
bootstrapArgs[1] = String.join(";", clazz.fields().stream().map(TargetField::name).toArray(String[]::new));
|
|
for (var i = 0; i < clazz.fields().size(); i++) {
|
|
var field = clazz.fields().get(i);
|
|
var fieldRef = new Handle(H_GETFIELD, clazz.getName(), field.name(), field.type().toDescriptor(), false);
|
|
bootstrapArgs[i + 2] = fieldRef;
|
|
}
|
|
|
|
{ // hashCode
|
|
var mv = cw.visitMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
|
|
mv.visitCode();
|
|
mv.visitVarInsn(ALOAD, 0);
|
|
mv.visitInvokeDynamicInsn("hashCode", "(L" + clazz.getName() + ";)I", bootstrap, bootstrapArgs);
|
|
mv.visitInsn(IRETURN);
|
|
mv.visitMaxs(0, 0);
|
|
mv.visitEnd();
|
|
}
|
|
{ // equals
|
|
var mv = cw.visitMethod(ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null);
|
|
mv.visitCode();
|
|
mv.visitVarInsn(ALOAD, 0);
|
|
mv.visitVarInsn(ALOAD, 1);
|
|
mv.visitInvokeDynamicInsn("equals", "(L" + clazz.getName() + ";Ljava/lang/Object;)Z", bootstrap, bootstrapArgs);
|
|
mv.visitInsn(IRETURN);
|
|
mv.visitMaxs(0, 0);
|
|
mv.visitEnd();
|
|
}
|
|
{ // toString
|
|
var mv = cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
|
|
mv.visitCode();
|
|
mv.visitVarInsn(ALOAD, 0);
|
|
mv.visitInvokeDynamicInsn("toString", "(L" + clazz.getName() + ";)Ljava/lang/String;", bootstrap, bootstrapArgs);
|
|
mv.visitInsn(ARETURN);
|
|
mv.visitMaxs(0, 0);
|
|
mv.visitEnd();
|
|
}
|
|
}
|
|
}
|