Lambda Bytecodegenerierung implementieren

This commit is contained in:
JanUlrich 2016-08-17 00:45:14 +02:00
parent 592af65c08
commit dbe09c237c
6 changed files with 168 additions and 122 deletions

View File

@ -1,5 +1,6 @@
package de.dhbwstuttgart.bytecode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
@ -90,29 +91,47 @@ public class ClassGenerator extends ClassGen{
throw new DebugException("Ungültige Operation. ClassGenerator muss ein DHBWConstantPool besitzen");
}
private List<InnerClass> innerClasses = new ArrayList<>();
/**
* Anmerkung: Kann in diesem Zustand nur einmal aufgerufen werden.
* @param innerClassAttribute
*/
public void addInnerClass(InnerClass innerClassAttribute) {
//TODO: Muss vor dem ausführen von getJavaClass ausgeführt werden. getJavaClass überschreiben!
int numberOfInnerClasses = 1;
InnerClass[] innerClasses = new InnerClass[numberOfInnerClasses];
innerClasses[numberOfInnerClasses-1] = innerClassAttribute;
int innerClassesUTF8 = this.getConstantPool().addUtf8("InnerClasses");
this.addAttribute(new InnerClasses(innerClassesUTF8,numberOfInnerClasses*8+2,innerClasses,this.getConstantPool().getConstantPool()));
for(InnerClass innerClass : innerClasses){
//Equal-Check:
if(innerClassCompare(innerClassAttribute, innerClass)){
return; //Ist die Innere Klasse bereits vorhanden, ist ein weiteres Anfügen nicht notwendig
}
}
innerClasses.add(innerClassAttribute);
}
/**
* Prüft die InnerClass Attribute a und b auf Gleichheit.
* Hierbei wird davon ausgegangen, dass beide den gleichen ConstantPool benutzen und dieser keine redundanten Einträge enthält
* @param a
* @param b
* @return
*/
private boolean innerClassCompare(InnerClass a, InnerClass b){
if(a.getInnerAccessFlags() != b.getInnerAccessFlags()){
return false;
}else if(a.getInnerClassIndex() != b.getInnerClassIndex()){
return false;
}else if(a.getInnerNameIndex() != b.getInnerNameIndex()){
return false;
}else if(a.getOuterClassIndex() != b.getOuterClassIndex()){
return false;
}
return true;
}
private List<BootstrapMethod> bootstrapMethods= new ArrayList<>();
public int addBootstrapMethod(BootstrapMethod bMethod) {
int numberOfBootstrapMethods = 1;
int name_index = this.getConstantPool().addUtf8("BootstrapMethods");
int length = 2 + numberOfBootstrapMethods * 4 + bMethod.getNumBootstrapArguments() * 2;
BootstrapMethod[] bootstrap_methods = new BootstrapMethod[numberOfBootstrapMethods];
bootstrap_methods[numberOfBootstrapMethods-1] = bMethod;
BootstrapMethods bootstrapAttribute = new BootstrapMethods(name_index, length, bootstrap_methods, this.getConstantPool().getConstantPool());
this.addAttribute(bootstrapAttribute);
return numberOfBootstrapMethods-1;
bootstrapMethods.add(bMethod);
return bootstrapMethods.size()-1;
}
public void addUsedTPH(TypePlaceholder tph){
if(! this.getUsedTPH().contains(tph, (a, b)->{
@ -139,6 +158,21 @@ public class ClassGenerator extends ClassGen{
this.addAttribute(new Signature(cp.addUtf8("Signature"),2,cp.addUtf8(classSignature),cp.getConstantPool()));
}
//InnerClass Attribut hinzufügen:
int innerClassesUTF8 = this.getConstantPool().addUtf8("InnerClasses");
this.addAttribute(new InnerClasses(innerClassesUTF8,innerClasses.size()*8+2,
innerClasses.toArray(new InnerClass[innerClasses.size()]),this.getConstantPool().getConstantPool()));
//Bootstrap Methoden generieren:
int name_index = this.getConstantPool().addUtf8("BootstrapMethods");
int length = 2 + bootstrapMethods.size() * 4;// + bMethod.getNumBootstrapArguments() * 2;
for(BootstrapMethod bMethod : bootstrapMethods){
length += 2 * bMethod.getNumBootstrapArguments();
}
BootstrapMethod[] bootstrap_methods = bootstrapMethods.toArray(new BootstrapMethod[bootstrapMethods.size()]);
BootstrapMethods bootstrapAttribute = new BootstrapMethods(name_index, length, bootstrap_methods, this.getConstantPool().getConstantPool());
this.addAttribute(bootstrapAttribute);
return super.getJavaClass();
}

View File

@ -45,96 +45,17 @@ public class DHBWInstructionFactory extends InstructionFactory{
/**
*
* @param interfaceMethodName - Momentan immer "apply"
* @param invokeDynamicMethodType - Der Methodentyp
* @param interfaceMethodType
* @param lambdaMethod
* @param invokeDynamicMethodType - Der Typ der Methode, welche Dynamisch aufgerufen werden soll
* @return
*/
public INVOKEDYNAMIC createInvokeDynamic( String interfaceMethodName, String invokeDynamicMethodType, FunN interfaceMethodType, MethodGen lambdaMethod, TypeinferenceResultSet rs) {
//Zuerst die Bootstrap-Methode erstellen: Diese müssen dann in ein BootstrapMethods-Attribut zusammengefasst und dem Classfile hinzugefügt werden
//this.cp.addMethodref(lambdaMethod);
ArrayList<Integer> arguments = new ArrayList<Integer>();
/*
* Die Argumente für die Bootstrap-Methode. Im Decompilierten Beispiel sind das:
* #19 ()V
* #20 invokespecial Lambda3.lambda$methode$0:()V
* #19 ()V
*
* #20 ist ein MethodHandle_info Structure
* Die Argumente werden der Bootstrap Methode beim Aufrufen übergeben: @see https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html#metafactory-java.lang.invoke.MethodHandles.Lookup-java.lang.String-java.lang.invoke.MethodType-java.lang.invoke.MethodType-java.lang.invoke.MethodHandle-java.lang.invoke.MethodType-
* TODO: Die korrekten Argumente anfügen.
* - samMethodType - Signature and return type of method to be implemented by the function object.
* - implMethod - A direct method handle describing the implementation method which should be called (with suitable adaptation of argument types, return types, and with captured arguments prepended to the invocation arguments) at invocation time.
* - instantiatedMethodType - The signature and return type that should be enforced dynamically at invocation time. This may be the same as samMethodType, or may be a specialization of it.
*/
String lambdaTypeParameterList = "()";
ConstantMethodType lambdaMethodType1 = new ConstantMethodType(this.cp.addUtf8(interfaceMethodType.getBytecodeInvokeDynamicSignatureUpperBound(cg))); //TODO: Hier den Grund finden, warum Object stehen muss.
ConstantMethodType lambdaMethodType = new ConstantMethodType(this.cp.addUtf8(interfaceMethodType.getBytecodeInvokeDynamicSignature(cg, rs)));
int implMethodKind = 7; // 7 = InvokeSpecial @see https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.5
ConstantMethodHandle implMethod = new ConstantMethodHandle(implMethodKind,cg.getConstantPool().addMethodref(lambdaMethod)); //Das zweite Argument ist der MethodRef zur LambdaMethode
//Argumentliste für die Bootstrap Methode zusammensetzen:
arguments.add(cp.addConstant(lambdaMethodType1));
arguments.add(cp.addConstant(implMethod));
arguments.add(cp.addConstant(lambdaMethodType));
int innerClassIndex = cp.addClass("java.lang.invoke.MethodHandles$Lookup");
int innerClassName = cp.addUtf8("Lookup");
int outerClassIndex = cp.addClass("java.lang.invoke.MethodHandles");
int accessFlags = Constants.ACC_FINAL + Constants.ACC_STATIC + Constants.ACC_PUBLIC;
InnerClass innerClassAttribute = new InnerClass(innerClassIndex, outerClassIndex, innerClassName,accessFlags);
//Diese InnereKlasse muss später im ClassFile vorkommen, da sie von der Bootstrap Methode benutzt wird.
//TODO: Dies kann man möglicherweise auslagern. Die ClassGen Klasse erweiter, welche sich dann die InnerenKlassen mertk, welche gebraucht werden
cg.addInnerClass(innerClassAttribute);
String bootstrapSignature = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
int lambdaMetafactoryRefIndex = cp.addMethodref("java.lang.invoke.LambdaMetafactory", "metafactory", bootstrapSignature);
//reference kind 6 steht für invokestatic
int lambdaMetafactoryHandle = cp.addConstant(new ConstantMethodHandle(6, lambdaMetafactoryRefIndex));
int[] argumentsArray = new int[arguments.size()];
for(int i = 0; i<arguments.size();i++){
argumentsArray[i] = arguments.get(i);
}
BootstrapMethod bMethod = new BootstrapMethod(lambdaMetafactoryHandle, argumentsArray);
int index;
/*
* Spezifikation: @see https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
*
* CONSTANT_InvokeDynamic_info structure:
* - a symbolic reference to a method handle (bootstrap_method_attr_index)
* - a method name and a method descriptor (name_and_type_index)
*
* @see https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4.10
*/
/*
*
* Was es mit der Inneren Klasse auf sich hat:
* (public static final #48= #47 of #51; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
* Lösung: Ist wohl einfach nur nötig, um die Metafactory durch die Bootstrap-Methode aufzurufen
*
* TODO: Rausfinden was es mit dem NameAndType Eintrag in der InvokeDynamic_info Struktur auf sich hat:
* Für ein Runnable ()->{}; ist es: run:(LLambda3;)Ljava/lang/Runnable;
* Wieso das zusätzliche Argument vom Typ (this)
*
* TODO: bootstrap_methode den Klassenattributen hinzufügen
*
*/
public INVOKEDYNAMIC createInvokeDynamic( String interfaceMethodName, String invokeDynamicMethodType, BootstrapMethod bMethod) {
int bootstrap_method_attr_index = cg.addBootstrapMethod(bMethod);
//TODO: der Typ der Lambda Methode muss den Typ von this als erstes Argument enthalten, damit this innerhalb der Lambda Methode verwendet werden kann
int invokeDynamicFunktionstyp = cp.addNameAndType(interfaceMethodName, invokeDynamicMethodType);
ConstantInvokeDynamic cInvokeDynamic = new ConstantInvokeDynamic(bootstrap_method_attr_index, invokeDynamicFunktionstyp);
index = cp.addConstant(cInvokeDynamic);
int index = cp.addConstant(cInvokeDynamic);
return new INVOKEDYNAMIC(index);
}

View File

@ -52,7 +52,8 @@ public class MethodGenerator extends MethodGen{
}
}
public Method createMethod(ClassGenerator cg, ParameterList parameter, de.dhbwstuttgart.syntaxtree.type.Type retType, Block block, TypeinferenceResultSet rs){
public Method createMethod(ClassGenerator cg, ParameterList parameter, de.dhbwstuttgart.syntaxtree.type.Type retType,
Block block, TypeinferenceResultSet rs){
MethodGen method = this;
DHBWInstructionFactory factory = cg.getInstructionFactory();
@ -75,16 +76,9 @@ public class MethodGenerator extends MethodGen{
method.setMaxStack(); //Die Stack Größe automatisch berechnen lassen (erst nach dem alle Instructions angehängt wurden)
method.setMaxLocals();
//Die korrekte Signatur für die Methode anhängen. Hier sind dann auch die Parameter von RefTypes enthalten:
String paramTypesSig = "(";
for(FormalParameter p : parameter){
paramTypesSig += p.getType().getBytecodeSignature(cg, rs);
Logger.getLogger("MethodGenerator").error(paramTypesSig, Section.CODEGEN);
}
paramTypesSig += ")";
String retTypeSig = retType.getBytecodeSignature(cg, rs);
String methodDescriptor = getMethodDescriptor(parameter, retType, cg, rs);
method.addAttribute(factory.createSignatureAttribute(paramTypesSig+retTypeSig));
method.addAttribute(factory.createSignatureAttribute(methodDescriptor));
System.out.println(this.getInstructionList().size());
StackMap stackMap = new StackMapTableGen(this, cp).getStackMap();
@ -93,6 +87,27 @@ public class MethodGenerator extends MethodGen{
return method.getMethod();
}
/**
* Erstellt den Deskriptor für eine Method mit den angegebenen Parametern und Return Typ.
* Dies ist eine optionale Angabe im java Bytecode und enthält im Gegensatz zur Signatur der Methode auch Generics.
* @param parameter
* @param returnType
* @param cg
* @param rs
* @return
*/
public static String getMethodDescriptor(ParameterList parameter, de.dhbwstuttgart.syntaxtree.type.Type returnType, ClassGenerator cg, TypeinferenceResultSet rs){
String paramTypesSig = "(";
for(FormalParameter p : parameter){
paramTypesSig += p.getType().getBytecodeSignature(cg, rs);
//Logger.getLogger("MethodGenerator").error(paramTypesSig, Section.CODEGEN);
}
paramTypesSig += ")";
String retTypeSig = returnType.getBytecodeSignature(cg, rs);
return paramTypesSig+retTypeSig;
}
public LocalVariableInstruction createLoad(org.apache.bcel.generic.Type bytecodeType, String variableName) {
return InstructionFactory.createLoad(bytecodeType, getStoreIndex(variableName));
}

View File

@ -39,6 +39,10 @@ public class FormalParameter extends SyntaxTreeNode implements Typeable, TypeIns
this.set_DeclId(name);
}
public FormalParameter(String name, Type type){
this(new DeclId(name));
this.setType(type);
}
@Override
public boolean equals(Object object) {

View File

@ -1,10 +1,14 @@
package de.dhbwstuttgart.syntaxtree.statement;
import java.util.Hashtable;
import java.util.ArrayList;
import org.apache.bcel.Const;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.BootstrapMethod;
import org.apache.bcel.classfile.ConstantMethodHandle;
import org.apache.bcel.classfile.ConstantMethodType;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.InnerClass;
import org.apache.bcel.generic.BIPUSH;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
@ -12,9 +16,11 @@ import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.INVOKEDYNAMIC;
import org.apache.bcel.generic.Instruction;
import de.dhbwstuttgart.typeinference.Menge;
import de.dhbwstuttgart.bytecode.ClassGenerator;
import de.dhbwstuttgart.bytecode.DHBWConstantPoolGen;
import de.dhbwstuttgart.bytecode.MethodGenerator;
import de.dhbwstuttgart.bytecode.TypePlaceholderType;
import de.dhbwstuttgart.parser.JavaClassName;
@ -207,28 +213,87 @@ public class LambdaExpression extends Expr{
for(FormalParameter fp : this.params)ret.add(fp);
return ret;
}
@Override
public InstructionList genByteCode(ClassGenerator cg, TypeinferenceResultSet rs) {
ConstantPoolGen cp = cg.getConstantPool();
DHBWConstantPoolGen cp = cg.getConstantPool();
InstructionList il = new InstructionList();
org.apache.bcel.generic.Type[] paramTypes = params.getBytecodeTypeList(cg, rs);
/*
* Anpassen der Parameter
*/
ParameterList lambdaMethodParams = new ParameterList();
//if(isStatic) //TODO: Abfrage, ob der Lambda-Ausdruck sich in einem statischen Kontext befindet
lambdaMethodParams.set_AddParameter(new FormalParameter("this", this.getParentClass().getType()));
il.append(InstructionFactory.createLoad( org.apache.bcel.generic.Type.OBJECT, 0)); //This auf den Stack legen
for(Statement s : method_body.get_Statement()){
if(s instanceof LocalOrFieldVarOrClassname){
LocalOrFieldVarOrClassname var = (LocalOrFieldVarOrClassname) s;
lambdaMethodParams.set_AddParameter(new FormalParameter(var.get_Name(),var.getType()));
//Direkt die Load instruktionen für die Parameter anhängen:
il.append(var.createLoad(cg, rs)); //Hier kann noch der cg vom Lambda-Ausdruck verwendet werden
}
}
org.apache.bcel.generic.Type[] additionalParameters = lambdaMethodParams.getBytecodeTypeList(cg, rs);
for(FormalParameter param : params){
lambdaMethodParams.set_AddParameter(param);
}
/*
* Generieren der Methode
*/
org.apache.bcel.generic.Type retType = method_body.getType().getBytecodeType(cg, rs);
MethodGenerator lambdaMethod = new MethodGenerator(0, retType,
paramTypes, params.getParameterNameArray(), cg.createLambdaMethodName(),
lambdaMethodParams.getBytecodeTypeList(cg, rs), lambdaMethodParams.getParameterNameArray(), cg.createLambdaMethodName(),
this.getParentClass().getName().toString(), new InstructionList(), cg.getConstantPool());
//lambdaMethod.stripAttributes(true);
//lambdaMethod.getInstructionList().append(this.method_body.genByteCode(cg));
lambdaMethod.setAccessFlags(Const.ACC_PRIVATE+Const.ACC_SYNTHETIC);
cg.setMethodeGenerator(lambdaMethod);
cg.addMethod(lambdaMethod.createMethod(cg, lambdaMethodParams, method_body.getType(), method_body, rs));
/*
* Generieren der Bootstrap Methode
*/
//Argumentliste für die Bootstrap Methode zusammensetzen:
ArrayList<Integer> arguments = new ArrayList<Integer>();
ConstantMethodType functionalMethodType = new ConstantMethodType(cp.addUtf8(lambdaType.getBytecodeInvokeDynamicSignature(cg, rs)));
int implMethodKind = 7; // 7 = InvokeSpecial @see https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.5
ConstantMethodHandle implMethod = new ConstantMethodHandle(implMethodKind,cg.getConstantPool().addMethodref(lambdaMethod)); //Das zweite Argument ist der MethodRef zur LambdaMethode
arguments.add(cp.addConstant(functionalMethodType));
arguments.add(cp.addConstant(implMethod));
arguments.add(cp.addConstant(functionalMethodType));
String bootstrapSignature = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
int lambdaMetafactoryRefIndex = cp.addMethodref("java.lang.invoke.LambdaMetafactory", "metafactory", bootstrapSignature);
//reference kind 6 steht für invokestatic
int lambdaMetafactoryHandle = cp.addConstant(new ConstantMethodHandle(6, lambdaMetafactoryRefIndex));
int[] argumentsArray = new int[arguments.size()];
for(int i = 0; i<arguments.size();i++){
argumentsArray[i] = arguments.get(i);
}
BootstrapMethod bMethod = new BootstrapMethod(lambdaMetafactoryHandle, argumentsArray);
/*
* Innere Klasse für das funktionieren der Bootstrap-Methode anfügen
*/
int innerClassIndex = cp.addClass("java.lang.invoke.MethodHandles$Lookup");
int innerClassName = cp.addUtf8("Lookup");
int outerClassIndex = cp.addClass("java.lang.invoke.MethodHandles");
int accessFlags = Constants.ACC_FINAL + Constants.ACC_STATIC + Constants.ACC_PUBLIC;
InnerClass innerClassAttribute = new InnerClass(innerClassIndex, outerClassIndex, innerClassName,accessFlags);
cg.addInnerClass(innerClassAttribute);
/*
* InvokeDynamik-Instruktion anhängen
*/
String interfaceMethodName = "apply"; //Das ist ein Hack, funktioniert momentan, da nur FunN Interfaces für LambdaAusdrücke funktionieren
il.append(InstructionFactory.createLoad( org.apache.bcel.generic.Type.OBJECT, 0));
il.append(cg.getInstructionFactory().createInvokeDynamic(interfaceMethodName,this.getLambdaSignature(cg, rs),lambdaType,lambdaMethod, rs));
cg.addMethod(lambdaMethod.createMethod(cg, params, method_body.getType(), method_body, rs));
String invokeDynamicType = org.apache.bcel.generic.Type.getMethodSignature(lambdaType.getBytecodeType(cg, rs), additionalParameters);
il.append(cg.getInstructionFactory().createInvokeDynamic(interfaceMethodName,invokeDynamicType, bMethod));
return il;
}

View File

@ -7,11 +7,14 @@ import java.util.Hashtable;
import org.apache.bcel.Constants;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LocalVariableInstruction;
import org.apache.bcel.generic.ObjectType;
import de.dhbwstuttgart.typeinference.Menge;
import de.dhbwstuttgart.bytecode.ClassGenerator;
import de.dhbwstuttgart.bytecode.MethodGenerator;
import de.dhbwstuttgart.logger.Logger;
import de.dhbwstuttgart.logger.Section;
import de.dhbwstuttgart.syntaxtree.Class;
@ -160,13 +163,17 @@ public class LocalOrFieldVarOrClassname extends Expr
il.append(cg.getInstructionFactory().createFieldAccess(this.getParentClass().getName().toString(), this.get_Name(), this.getType().getBytecodeType(cg, rs), Constants.GETFIELD));
}
il.append(createLoad(cg, rs));
return il;
}
public LocalVariableInstruction createLoad(ClassGenerator cg, TypeinferenceResultSet rs){
Type type = this.getType();
org.apache.bcel.generic.Type byteCodeType = type.getBytecodeType(cg, rs);
String name = this.get_Name();
il.append(cg.getMethodGenerator().createLoad(byteCodeType, name));
return il;
return cg.getMethodGenerator().createLoad(byteCodeType, name);
}
}