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 'com.github.i-net-software:jwebassembly-api:master-SNAPSHOT'
testCompile 'junit:junit:+'
testCompile 'org.mockito:mockito-core:+'
testCompile 'org.apache.commons:commons-compress:1.2'
testCompile 'com.google.code.gson:gson:+'
}

View File

@ -58,6 +58,16 @@ public class BootstrapMethod {
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
*

View File

@ -87,13 +87,6 @@ class LocaleVariableManager {
void reset( LocalVariableTable variableTable, MethodInfo method, Iterator<AnyType> signature ) {
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;
if( variableTable == null ) {
maxLocals = 0;
@ -167,7 +160,7 @@ class LocaleVariableManager {
}
// 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;
if( method != null && !method.isStatic() ) {
resetAddVar( ValueType.externref, size );
@ -177,12 +170,12 @@ class LocaleVariableManager {
if( type == null ) {
break;
}
resetAddVar( type, size - baseSize );
resetAddVar( type, size );
}
}
// 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++ ) {
Variable var = variables[j];
if( var.idx == i ) {

View File

@ -208,7 +208,7 @@ public class ModuleGenerator {
} else {
functions.markAsImport( synth, synth.getAnnotation() );
}
functions.markAsScanned( next, false );
functions.markAsScanned( next, !synth.istStatic() );
continue;
}
@ -226,7 +226,7 @@ public class ModuleGenerator {
createInstructions( functions.replace( next, method ) );
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
|| (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 );
if( needThisParameter ) {
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");
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() {
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.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@ -32,9 +33,11 @@ import java.util.function.ToIntFunction;
import javax.annotation.Nonnull;
import de.inetsoftware.classparser.BootstrapMethod;
import de.inetsoftware.classparser.ClassFile;
import de.inetsoftware.classparser.ClassFile.Type;
import de.inetsoftware.classparser.ConstantClass;
import de.inetsoftware.classparser.ConstantMethodRef;
import de.inetsoftware.classparser.FieldInfo;
import de.inetsoftware.classparser.MethodInfo;
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.LittleEndianOutputStream;
import de.inetsoftware.jwebassembly.wasm.NamedStorageType;
import de.inetsoftware.jwebassembly.wasm.StructOperator;
import de.inetsoftware.jwebassembly.wasm.ValueType;
import de.inetsoftware.jwebassembly.wasm.ValueTypeParser;
import de.inetsoftware.jwebassembly.watparser.WatParser;
/**
* Manage the written and to write types (classes)
@ -374,22 +379,24 @@ public class TypeManager {
/**
* Create a lambda type
*
* @param typeName
* the name (className) of the lambda class
* @param method
* the name BootstrapMethod from the parsed class file
* @param params
* the parameters of the constructor and type fields
* @param interfaceType
* the implemented interface
* @param methodName
* the real method in the parent class that implements the lambda expression
* @param interfaceMethodName
* the name of the implemented method in the interface
* @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 );
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 );
}
@ -1149,26 +1156,64 @@ public class TypeManager {
*
* @param name
* the Lambda Java class name
* @param method
* the name BootstrapMethod from the parsed class file
* @param params
* the parameters of the constructor and type fields
* @param interfaceType
* the implemented interface type
* @param methodName
* @param syntheticLambdaFunctionName
* the real method in the parent class that implements the lambda expression
* @param interfaceMethodName
* the name of the implemented method in the interface
* @param manager
* 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 );
this.paramFields = new ArrayList<>( params.size() );
for( int i = 0; i < params.size(); i++ ) {
paramFields.add( new NamedStorageType( params.get( i ), "", "arg$" + (i+1) ) );
}
this.interfaceType = interfaceType;
this.methodName = methodName;
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
*/
@Nonnull
FunctionName getLambdaMethod() {
return methodName;
}

View File

@ -29,7 +29,6 @@ import javax.annotation.Nullable;
import de.inetsoftware.classparser.BootstrapMethod;
import de.inetsoftware.classparser.ClassFile;
import de.inetsoftware.classparser.ConstantClass;
import de.inetsoftware.classparser.ConstantMethodRef;
import de.inetsoftware.classparser.LocalVariableTable;
import de.inetsoftware.classparser.Member;
import de.inetsoftware.classparser.MethodInfo;
@ -870,13 +869,7 @@ public abstract class WasmCodeBuilder {
* the line number in the Java source code
*/
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.
String lambdaTypeName = implMethod.getClassName() + "$$" + implMethod.getName() + "/" + Math.abs( name.hashCode() );
ValueTypeParser parser = new ValueTypeParser( factorySignature, types );
ArrayList<AnyType> params = new ArrayList<>();
do {
@ -887,7 +880,9 @@ public abstract class WasmCodeBuilder {
params.add( param );
} while( true );
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
ArrayList<NamedStorageType> paramFields = type.getParamFields();
@ -899,7 +894,10 @@ public abstract class WasmCodeBuilder {
int idx = StackInspector.findInstructionThatPushValue( instructions, paramCount, javaCodePos ).idx;
int pos = instructions.size();
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
Collections.rotate( instructions.subList( idx, instructions.size() ), idx - pos );
@ -908,7 +906,13 @@ public abstract class WasmCodeBuilder {
NamedStorageType field = paramFields.get( i );
idx = StackInspector.findInstructionThatPushValue( instructions, paramCount - i, javaCodePos ).idx;
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.assertFalse;
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.HashMap;
@ -25,6 +27,8 @@ import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import de.inetsoftware.classparser.BootstrapMethod;
import de.inetsoftware.classparser.ConstantMethodRef;
import de.inetsoftware.jwebassembly.JWebAssembly;
import de.inetsoftware.jwebassembly.module.TypeManager.LambdaType;
import de.inetsoftware.jwebassembly.module.TypeManager.StructType;
@ -104,7 +108,12 @@ public class StructTypeTest {
@Test
public void isSubTypeOf_Lambda() throws Throwable {
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 ) );
assertFalse( typeRunnable.isSubTypeOf( lambda ) );