Add support for Lambda expressions with parameters

This commit is contained in:
Volker Berlin 2021-04-03 22:06:39 +02:00
parent 6d4379b2e8
commit 1771ab1f39
8 changed files with 105 additions and 33 deletions

View File

@ -26,6 +26,7 @@ dependencies {
//testCompile 'de.inetsoftware:jwebassembly-api:+' //testCompile 'de.inetsoftware:jwebassembly-api:+'
testCompile 'com.github.i-net-software:jwebassembly-api:master-SNAPSHOT' testCompile 'com.github.i-net-software:jwebassembly-api:master-SNAPSHOT'
testCompile 'junit:junit:+' testCompile 'junit:junit:+'
testCompile 'org.mockito:mockito-core:+'
testCompile 'org.apache.commons:commons-compress:1.2' testCompile 'org.apache.commons:commons-compress:1.2'
testCompile 'com.google.code.gson:gson:+' testCompile 'com.google.code.gson:gson:+'
} }

View File

@ -58,6 +58,16 @@ public class BootstrapMethod {
instantiatedMethodType = (String)constantPool.get( input.readUnsignedShort() ); instantiatedMethodType = (String)constantPool.get( input.readUnsignedShort() );
} }
/**
* Signature and return type of method to be implemented by the function object.
*
* @see java.lang.invoke.LambdaMetafactory#metafactory parameter samMethodType
* @return the signature
*/
public String getSamMethodType() {
return samMethodType;
}
/** /**
* The real method in the parent class that implements the lambda expression * The real method in the parent class that implements the lambda expression
* *

View File

@ -87,13 +87,6 @@ class LocaleVariableManager {
void reset( LocalVariableTable variableTable, MethodInfo method, Iterator<AnyType> signature ) { void reset( LocalVariableTable variableTable, MethodInfo method, Iterator<AnyType> signature ) {
size = 0; size = 0;
if( method != null && method.isLambda() ) {
AnyType type = types.options.useGC() ? types.valueOf( "java/lang/Object" ) : ValueType.externref;
resetAddVar( type, -1 );
variables[0].name = "this";
}
int baseSize = size;
int maxLocals; int maxLocals;
if( variableTable == null ) { if( variableTable == null ) {
maxLocals = 0; maxLocals = 0;
@ -167,7 +160,7 @@ class LocaleVariableManager {
} }
// add missing slots from signature // add missing slots from signature
if( (maxLocals > 0 || variableTable == null) && size == baseSize && (method != null || signature != null )) { if( (maxLocals > 0 || variableTable == null) && size == 0 && (method != null || signature != null )) {
Iterator<AnyType> parser = signature == null ? new ValueTypeParser( method.getType(), types ) : signature; Iterator<AnyType> parser = signature == null ? new ValueTypeParser( method.getType(), types ) : signature;
if( method != null && !method.isStatic() ) { if( method != null && !method.isStatic() ) {
resetAddVar( ValueType.externref, size ); resetAddVar( ValueType.externref, size );
@ -177,12 +170,12 @@ class LocaleVariableManager {
if( type == null ) { if( type == null ) {
break; break;
} }
resetAddVar( type, size - baseSize ); resetAddVar( type, size );
} }
} }
// add all missing slots that we can add self temporary variables // add all missing slots that we can add self temporary variables
NEXT: for( int i = 0; i < maxLocals + baseSize; i++ ) { NEXT: for( int i = 0; i < maxLocals; i++ ) {
for( int j = 0; j < size; j++ ) { for( int j = 0; j < size; j++ ) {
Variable var = variables[j]; Variable var = variables[j];
if( var.idx == i ) { if( var.idx == i ) {

View File

@ -208,7 +208,7 @@ public class ModuleGenerator {
} else { } else {
functions.markAsImport( synth, synth.getAnnotation() ); functions.markAsImport( synth, synth.getAnnotation() );
} }
functions.markAsScanned( next, false ); functions.markAsScanned( next, !synth.istStatic() );
continue; continue;
} }
@ -226,7 +226,7 @@ public class ModuleGenerator {
createInstructions( functions.replace( next, method ) ); createInstructions( functions.replace( next, method ) );
boolean needThisParameter = !method.isStatic() // if not static there is a not declared THIS parameter boolean needThisParameter = !method.isStatic() // if not static there is a not declared THIS parameter
|| "<init>".equals( method.getName() ) // constructor method need also the THIS parameter also if marked as static || "<init>".equals( method.getName() ) // constructor method need also the THIS parameter also if marked as static
|| (method.isLambda() ); // lambda functions are static but will call with a THIS parameter which need be removed from stack /*|| (method.isLambda() )*/; // lambda functions are static but will call with a THIS parameter which need be removed from stack
functions.markAsScanned( next, needThisParameter ); functions.markAsScanned( next, needThisParameter );
if( needThisParameter ) { if( needThisParameter ) {
types.valueOf( next.className ); // for the case that the type unknown yet types.valueOf( next.className ); // for the case that the type unknown yet

View File

@ -1,5 +1,5 @@
/* /*
Copyright 2019 Volker Berlin (i-net software) Copyright 2019 - 2021 Volker Berlin (i-net software)
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -67,4 +67,13 @@ public abstract class SyntheticFunctionName extends FunctionName {
protected Function<String, Object> getAnnotation() { protected Function<String, Object> getAnnotation() {
return null; return null;
} }
/**
* Is a static method or if it need a this parameter.
*
* @return true, id static
*/
protected boolean istStatic() {
return true;
}
} }

View File

@ -22,6 +22,7 @@ import java.io.UncheckedIOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
@ -32,9 +33,11 @@ import java.util.function.ToIntFunction;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import de.inetsoftware.classparser.BootstrapMethod;
import de.inetsoftware.classparser.ClassFile; import de.inetsoftware.classparser.ClassFile;
import de.inetsoftware.classparser.ClassFile.Type; import de.inetsoftware.classparser.ClassFile.Type;
import de.inetsoftware.classparser.ConstantClass; import de.inetsoftware.classparser.ConstantClass;
import de.inetsoftware.classparser.ConstantMethodRef;
import de.inetsoftware.classparser.FieldInfo; import de.inetsoftware.classparser.FieldInfo;
import de.inetsoftware.classparser.MethodInfo; import de.inetsoftware.classparser.MethodInfo;
import de.inetsoftware.jwebassembly.JWebAssembly; import de.inetsoftware.jwebassembly.JWebAssembly;
@ -43,8 +46,10 @@ import de.inetsoftware.jwebassembly.wasm.AnyType;
import de.inetsoftware.jwebassembly.wasm.ArrayType; import de.inetsoftware.jwebassembly.wasm.ArrayType;
import de.inetsoftware.jwebassembly.wasm.LittleEndianOutputStream; import de.inetsoftware.jwebassembly.wasm.LittleEndianOutputStream;
import de.inetsoftware.jwebassembly.wasm.NamedStorageType; import de.inetsoftware.jwebassembly.wasm.NamedStorageType;
import de.inetsoftware.jwebassembly.wasm.StructOperator;
import de.inetsoftware.jwebassembly.wasm.ValueType; import de.inetsoftware.jwebassembly.wasm.ValueType;
import de.inetsoftware.jwebassembly.wasm.ValueTypeParser; import de.inetsoftware.jwebassembly.wasm.ValueTypeParser;
import de.inetsoftware.jwebassembly.watparser.WatParser;
/** /**
* Manage the written and to write types (classes) * Manage the written and to write types (classes)
@ -374,22 +379,24 @@ public class TypeManager {
/** /**
* Create a lambda type * Create a lambda type
* *
* @param typeName * @param method
* the name (className) of the lambda class * the name BootstrapMethod from the parsed class file
* @param params * @param params
* the parameters of the constructor and type fields * the parameters of the constructor and type fields
* @param interfaceType * @param interfaceType
* the implemented interface * the implemented interface
* @param methodName
* the real method in the parent class that implements the lambda expression
* @param interfaceMethodName * @param interfaceMethodName
* the name of the implemented method in the interface * the name of the implemented method in the interface
* @return the type * @return the type
*/ */
LambdaType lambdaType( String typeName, ArrayList<AnyType> params, StructType interfaceType, FunctionName methodName, String interfaceMethodName ) { LambdaType lambdaType( @Nonnull BootstrapMethod method, ArrayList<AnyType> params, StructType interfaceType, String interfaceMethodName ) {
ConstantMethodRef implMethod = method.getImplMethod();
FunctionName methodName = new FunctionName( implMethod );
String typeName = implMethod.getClassName() + "$$" + implMethod.getName() + "/" + Math.abs( implMethod.getName().hashCode() );
LambdaType type = (LambdaType)structTypes.get( typeName ); LambdaType type = (LambdaType)structTypes.get( typeName );
if( type == null ) { if( type == null ) {
type = new LambdaType( typeName, params, interfaceType, methodName, interfaceMethodName, this ); type = new LambdaType( typeName, method, params, interfaceType, methodName, interfaceMethodName, this );
structTypes.put( typeName, type ); structTypes.put( typeName, type );
} }
@ -1149,26 +1156,64 @@ public class TypeManager {
* *
* @param name * @param name
* the Lambda Java class name * the Lambda Java class name
* @param method
* the name BootstrapMethod from the parsed class file
* @param params * @param params
* the parameters of the constructor and type fields * the parameters of the constructor and type fields
* @param interfaceType * @param interfaceType
* the implemented interface type * the implemented interface type
* @param methodName * @param syntheticLambdaFunctionName
* the real method in the parent class that implements the lambda expression * the real method in the parent class that implements the lambda expression
* @param interfaceMethodName * @param interfaceMethodName
* the name of the implemented method in the interface * the name of the implemented method in the interface
* @param manager * @param manager
* the manager which hold all StructTypes * the manager which hold all StructTypes
*/ */
LambdaType( String name, ArrayList<AnyType> params, StructType interfaceType, FunctionName methodName, String interfaceMethodName, TypeManager manager ) { LambdaType( @Nonnull String name, @Nonnull BootstrapMethod method, ArrayList<AnyType> params, StructType interfaceType, FunctionName syntheticLambdaFunctionName, String interfaceMethodName, @Nonnull TypeManager manager ) {
super( name, StructTypeKind.lambda, manager ); super( name, StructTypeKind.lambda, manager );
this.paramFields = new ArrayList<>( params.size() ); this.paramFields = new ArrayList<>( params.size() );
for( int i = 0; i < params.size(); i++ ) { for( int i = 0; i < params.size(); i++ ) {
paramFields.add( new NamedStorageType( params.get( i ), "", "arg$" + (i+1) ) ); paramFields.add( new NamedStorageType( params.get( i ), "", "arg$" + (i+1) ) );
} }
this.interfaceType = interfaceType; this.interfaceType = interfaceType;
this.methodName = methodName;
this.interfaceMethodName = interfaceMethodName; this.interfaceMethodName = interfaceMethodName;
methodName = new SyntheticFunctionName( name, interfaceMethodName, method.getSamMethodType() ) {
@Override
protected boolean hasWasmCode() {
return true;
}
@Override
protected boolean istStatic() {
return false;
}
@Override
protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) {
WasmCodeBuilder codebuilder = watParser;
ArrayList<AnyType> sig = new ArrayList<>();
sig.add( LambdaType.this );
for( Iterator<AnyType> it = getSignature( manager ); it.hasNext(); ) {
sig.add( it.next() );
}
watParser.reset( null, null, sig.iterator() );
for( int i = 1; i < sig.size(); i++ ) {
AnyType anyType = sig.get( i );
if( anyType == null ) {
break;
}
codebuilder.addLoadStoreInstruction( anyType, true, i, 0, -1 );
}
for( int i = 0; i < paramFields.size(); i++ ) {
codebuilder.addLoadStoreInstruction( LambdaType.this, true, 0, 0, -1 );
codebuilder.addStructInstruction( StructOperator.GET, name, paramFields.get( i ), 0, -1 );
}
codebuilder.addCallInstruction( syntheticLambdaFunctionName, 0, -1 );
return watParser;
}
};
} }
/** /**
@ -1194,6 +1239,7 @@ public class TypeManager {
* *
* @return the function name * @return the function name
*/ */
@Nonnull
FunctionName getLambdaMethod() { FunctionName getLambdaMethod() {
return methodName; return methodName;
} }

View File

@ -29,7 +29,6 @@ import javax.annotation.Nullable;
import de.inetsoftware.classparser.BootstrapMethod; import de.inetsoftware.classparser.BootstrapMethod;
import de.inetsoftware.classparser.ClassFile; import de.inetsoftware.classparser.ClassFile;
import de.inetsoftware.classparser.ConstantClass; import de.inetsoftware.classparser.ConstantClass;
import de.inetsoftware.classparser.ConstantMethodRef;
import de.inetsoftware.classparser.LocalVariableTable; import de.inetsoftware.classparser.LocalVariableTable;
import de.inetsoftware.classparser.Member; import de.inetsoftware.classparser.Member;
import de.inetsoftware.classparser.MethodInfo; import de.inetsoftware.classparser.MethodInfo;
@ -870,13 +869,7 @@ public abstract class WasmCodeBuilder {
* the line number in the Java source code * the line number in the Java source code
*/ */
protected void addInvokeDynamic( BootstrapMethod method, String factorySignature, String interfaceMethodName, int javaCodePos, int lineNumber ) { protected void addInvokeDynamic( BootstrapMethod method, String factorySignature, String interfaceMethodName, int javaCodePos, int lineNumber ) {
// mark the static, synthetic method which implement the lambda code, as needed
ConstantMethodRef implMethod = method.getImplMethod();
FunctionName name = new FunctionName( implMethod );
functions.markAsNeeded( name );
// Create the synthetic lambda class that hold the lambda expression. // Create the synthetic lambda class that hold the lambda expression.
String lambdaTypeName = implMethod.getClassName() + "$$" + implMethod.getName() + "/" + Math.abs( name.hashCode() );
ValueTypeParser parser = new ValueTypeParser( factorySignature, types ); ValueTypeParser parser = new ValueTypeParser( factorySignature, types );
ArrayList<AnyType> params = new ArrayList<>(); ArrayList<AnyType> params = new ArrayList<>();
do { do {
@ -887,7 +880,9 @@ public abstract class WasmCodeBuilder {
params.add( param ); params.add( param );
} while( true ); } while( true );
StructType interfaceType = (StructType)parser.next(); StructType interfaceType = (StructType)parser.next();
LambdaType type = types.lambdaType( lambdaTypeName, params, interfaceType, name, interfaceMethodName ); LambdaType type = types.lambdaType( method, params, interfaceType, interfaceMethodName );
functions.markAsNeeded( type.getLambdaMethod() );
String lambdaTypeName = type.getName();
// Create the instance of the synthetic lambda class and save the parameters in fields // Create the instance of the synthetic lambda class and save the parameters in fields
ArrayList<NamedStorageType> paramFields = type.getParamFields(); ArrayList<NamedStorageType> paramFields = type.getParamFields();
@ -899,7 +894,10 @@ public abstract class WasmCodeBuilder {
int idx = StackInspector.findInstructionThatPushValue( instructions, paramCount, javaCodePos ).idx; int idx = StackInspector.findInstructionThatPushValue( instructions, paramCount, javaCodePos ).idx;
int pos = instructions.size(); int pos = instructions.size();
addStructInstruction( StructOperator.NEW_DEFAULT, lambdaTypeName, null, javaCodePos, lineNumber ); addStructInstruction( StructOperator.NEW_DEFAULT, lambdaTypeName, null, javaCodePos, lineNumber );
int slot = ((WasmLocalInstruction)findInstructionThatPushValue( 1, javaCodePos )).getSlot(); if( !options.useGC() ) {
addDupInstruction( javaCodePos, lineNumber );
}
int slot = ((WasmLocalInstruction)findInstructionThatPushValue( 1, javaCodePos )).getSlot();
// move the creating of the lambda instance before the parameters on the stack // move the creating of the lambda instance before the parameters on the stack
Collections.rotate( instructions.subList( idx, instructions.size() ), idx - pos ); Collections.rotate( instructions.subList( idx, instructions.size() ), idx - pos );
@ -908,7 +906,13 @@ public abstract class WasmCodeBuilder {
NamedStorageType field = paramFields.get( i ); NamedStorageType field = paramFields.get( i );
idx = StackInspector.findInstructionThatPushValue( instructions, paramCount - i, javaCodePos ).idx; idx = StackInspector.findInstructionThatPushValue( instructions, paramCount - i, javaCodePos ).idx;
instructions.add( idx, new WasmLoadStoreInstruction( VariableOperator.get, slot, localVariables, javaCodePos, lineNumber ) ); instructions.add( idx, new WasmLoadStoreInstruction( VariableOperator.get, slot, localVariables, javaCodePos, lineNumber ) );
instructions.add( new WasmStructInstruction( StructOperator.SET, lambdaTypeName, field, javaCodePos, lineNumber, types ) ); pos = instructions.size();
idx = paramCount - i - 1;
idx = idx == 0 ? pos : StackInspector.findInstructionThatPushValue( instructions, idx, javaCodePos ).idx;
addStructInstruction( StructOperator.SET, lambdaTypeName, field, javaCodePos, lineNumber );
if( idx < pos ) {
Collections.rotate( instructions.subList( idx, instructions.size() ), idx - pos );
}
} }
} }
} }

View File

@ -18,6 +18,8 @@ package de.inetsoftware.jwebassembly.module;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -25,6 +27,8 @@ import java.util.HashMap;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import de.inetsoftware.classparser.BootstrapMethod;
import de.inetsoftware.classparser.ConstantMethodRef;
import de.inetsoftware.jwebassembly.JWebAssembly; import de.inetsoftware.jwebassembly.JWebAssembly;
import de.inetsoftware.jwebassembly.module.TypeManager.LambdaType; import de.inetsoftware.jwebassembly.module.TypeManager.LambdaType;
import de.inetsoftware.jwebassembly.module.TypeManager.StructType; import de.inetsoftware.jwebassembly.module.TypeManager.StructType;
@ -104,7 +108,12 @@ public class StructTypeTest {
@Test @Test
public void isSubTypeOf_Lambda() throws Throwable { public void isSubTypeOf_Lambda() throws Throwable {
StructType typeRunnable = manager.valueOf( "java/lang/Runnable" ); StructType typeRunnable = manager.valueOf( "java/lang/Runnable" );
LambdaType lambda = manager.lambdaType( "typeName", new ArrayList(), typeRunnable, new FunctionName( "", "", "" ), "run" );
ConstantMethodRef implMethod = mock( ConstantMethodRef.class );
when( implMethod.getName() ).thenReturn( "" );
BootstrapMethod method = mock( BootstrapMethod.class );
when( method.getImplMethod() ).thenReturn( implMethod );
LambdaType lambda = manager.lambdaType( method, new ArrayList(), typeRunnable, "run" );
assertTrue( lambda.isSubTypeOf( typeRunnable ) ); assertTrue( lambda.isSubTypeOf( typeRunnable ) );
assertFalse( typeRunnable.isSubTypeOf( lambda ) ); assertFalse( typeRunnable.isSubTypeOf( lambda ) );