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 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 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 breakStack = new Stack<>(); Stack 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", "", "(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(); 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("") ? 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(), "", _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(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 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, "", "()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, "", 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 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(); } } }