import only the needed functions to reduce the needed JS glue code

This commit is contained in:
Volker Berlin 2019-06-23 20:50:11 +02:00
parent b90d7e1dea
commit 6bcda0daa5
5 changed files with 125 additions and 27 deletions

View File

@ -17,6 +17,7 @@ package de.inetsoftware.jwebassembly.module;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -57,6 +58,19 @@ public class FunctionManager {
return state; return state;
} }
/**
* Mark the a function as a import function. Only if the function is also needed then it will imported from
* compiler.
*
* @param name
* the function name
* @param importAnannotation
* the annotation of the import
*/
void markAsImport( FunctionName name, Map<String,Object> importAnannotation ) {
getOrCreate( name ).importAnannotation = importAnannotation;
}
/** /**
* Mark the a function as scanned in the prepare phase. This should only occur with needed functions. * Mark the a function as scanned in the prepare phase. This should only occur with needed functions.
* *
@ -82,16 +96,48 @@ public class FunctionManager {
* *
* @param name * @param name
* the function name * the function name
* @param isStatic
* true, if the method is static
* @return the real function name * @return the real function name
*/ */
FunctionName markAsNeeded( FunctionName name ) { FunctionName markAsNeeded( FunctionName name, boolean isStatic ) {
FunctionState state = getOrCreate( name ); FunctionState state = getOrCreate( name );
if( state.state == State.None ) { if( state.state == State.None ) {
state.state = State.Needed; state.state = State.Needed;
} }
state.isStatic = isStatic;
return state.alias == null ? name : state.alias; return state.alias == null ? name : state.alias;
} }
/**
* Get all FunctionNames that need imported
*
* @return an iterator
*/
Iterator<FunctionName> getNeededImports() {
return states.entrySet().stream().filter( entry -> {
FunctionState state = entry.getValue();
switch( state.state ) {
case Needed:
case Scanned:
return state.importAnannotation != null;
default:
}
return false;
} ).map( entry -> entry.getKey() ).iterator();
}
/**
* Get the annotation of an import function
*
* @param name
* the function name
* @return the annotation or null
*/
Map<String, Object> getImportAnannotation( FunctionName name ) {
return getOrCreate( name ).importAnannotation;
}
/** /**
* Get the first FunctionName that is required but was not scanned. * Get the first FunctionName that is required but was not scanned.
* *
@ -125,6 +171,24 @@ public class FunctionManager {
return null; return null;
} }
/**
* Get all FunctionNames that need imported
*
* @return an iterator
*/
Iterator<FunctionName> getNeededFunctions() {
return states.entrySet().stream().filter( entry -> {
FunctionState state = entry.getValue();
switch( state.state ) {
case Needed:
case Scanned:
return true;
default:
}
return false;
} ).map( entry -> entry.getKey() ).iterator();
}
/** /**
* if the given function is required but was not scanned. * if the given function is required but was not scanned.
* *
@ -158,6 +222,17 @@ public class FunctionManager {
} }
} }
/**
* if the given function is static.
*
* @param name
* the function name
* @return true, if the function is static
*/
boolean isStatic( FunctionName name ) {
return getOrCreate( name ).isStatic;
}
/** /**
* Add a replacement for a method * Add a replacement for a method
* *
@ -227,13 +302,17 @@ public class FunctionManager {
* State of a function/method * State of a function/method
*/ */
private static class FunctionState { private static class FunctionState {
private State state = State.None; private State state = State.None;
private MethodInfo method; private MethodInfo method;
private FunctionName alias; private FunctionName alias;
private int functionIdx = -1; private Map<String, Object> importAnannotation;
private int functionIdx = -1;
private boolean isStatic;
} }
private static enum State { private static enum State {

View File

@ -156,7 +156,6 @@ public class ModuleGenerator {
ClassFile classFile = ClassFile.get( next.className, libraries ); ClassFile classFile = ClassFile.get( next.className, libraries );
if( classFile == null ) { if( classFile == null ) {
if( next instanceof SyntheticFunctionName ) { if( next instanceof SyntheticFunctionName ) {
writeMethodSignature( next, true, null );
scanMethod( ((SyntheticFunctionName)next).getCodeBuilder( watParser ) ); scanMethod( ((SyntheticFunctionName)next).getCodeBuilder( watParser ) );
functions.markAsScanned( next ); functions.markAsScanned( next );
} }
@ -165,7 +164,6 @@ public class ModuleGenerator {
try { try {
FunctionName name = new FunctionName( method ); FunctionName name = new FunctionName( method );
if( functions.needToScan( name ) ) { if( functions.needToScan( name ) ) {
writeMethodSignature( name, method.isStatic(), null );
scanMethod( createInstructions( functions.replace( name, method ) ) ); scanMethod( createInstructions( functions.replace( name, method ) ) );
functions.markAsScanned( name ); functions.markAsScanned( name );
} }
@ -182,7 +180,7 @@ public class ModuleGenerator {
MethodInfo method = superClassFile.getMethod( next.methodName, next.signature ); MethodInfo method = superClassFile.getMethod( next.methodName, next.signature );
if( method != null ) { if( method != null ) {
FunctionName name = new FunctionName( method ); FunctionName name = new FunctionName( method );
functions.markAsNeeded( name ); functions.markAsNeeded( name, method.isStatic() );
functions.setAlias( next, name ); functions.setAlias( next, name );
continue NEXT; // we have found a super method continue NEXT; // we have found a super method
} }
@ -202,6 +200,25 @@ public class ModuleGenerator {
*/ */
public void prepareFinish() throws IOException { public void prepareFinish() throws IOException {
prepareFunctions(); prepareFunctions();
// write only the needed imports to the output
for( Iterator<FunctionName> iterator = functions.getNeededImports(); iterator.hasNext(); ) {
FunctionName name = iterator.next();
functions.markAsWritten( name );
Map<String, Object> importAnannotation = functions.getImportAnannotation( name );
String impoarModule = (String)importAnannotation.get( "module" );
String importName = (String)importAnannotation.get( "name" );
writer.prepareImport( name, impoarModule, importName );
writeMethodSignature( name, true, null );
}
// init/write the function types
for( Iterator<FunctionName> iterator = functions.getNeededFunctions(); iterator.hasNext(); ) {
FunctionName name = iterator.next();
writeMethodSignature( name, functions.isStatic( name ), null );
}
types.prepareFinish( writer, functions, libraries ); types.prepareFinish( writer, functions, libraries );
prepareFunctions(); // prepare of types can add some override methods as needed prepareFunctions(); // prepare of types can add some override methods as needed
functions.prepareFinish(); functions.prepareFinish();
@ -224,8 +241,10 @@ public class ModuleGenerator {
for( WasmInstruction instruction : instructions ) { for( WasmInstruction instruction : instructions ) {
switch( instruction.getType() ) { switch( instruction.getType() ) {
case Call: case Call:
((WasmCallInstruction)instruction).markAsNeeded( functions, true );
break;
case CallIndirect: case CallIndirect:
((WasmCallInstruction)instruction).markAsNeeded( functions ); ((WasmCallInstruction)instruction).markAsNeeded( functions, false );
break; break;
default: default:
} }
@ -315,28 +334,23 @@ public class ModuleGenerator {
try { try {
FunctionName name = new FunctionName( method ); FunctionName name = new FunctionName( method );
Map<String,Object> annotationValues; Map<String,Object> annotationValues;
if( (annotationValues = method.getAnnotation( JWebAssembly.REPLACE_ANNOTATION )) != null ) {
String signatureName = (String)annotationValues.get( "value" );
name = new FunctionName( signatureName );
functions.addReplacement( name, method );
}
if( (annotationValues = method.getAnnotation( JWebAssembly.IMPORT_ANNOTATION )) != null ) { if( (annotationValues = method.getAnnotation( JWebAssembly.IMPORT_ANNOTATION )) != null ) {
if( !method.isStatic() ) { if( !method.isStatic() ) {
throw new WasmException( "Import method must be static: " + name.fullName, -1 ); throw new WasmException( "Import method must be static: " + name.fullName, -1 );
} }
functions.markAsWritten( name ); functions.markAsImport( name, annotationValues );
String impoarModule = (String)annotationValues.get( "module" );
String importName = (String)annotationValues.get( "name" );
writer.prepareImport( name, impoarModule, importName );
writeMethodSignature( name, true, null );
return; return;
} }
if( (annotationValues = method.getAnnotation( JWebAssembly.EXPORT_ANNOTATION )) != null ) { if( (annotationValues = method.getAnnotation( JWebAssembly.EXPORT_ANNOTATION )) != null ) {
if( !method.isStatic() ) { if( !method.isStatic() ) {
throw new WasmException( "Export method must be static: " + name.fullName, -1 ); throw new WasmException( "Export method must be static: " + name.fullName, -1 );
} }
functions.markAsNeeded( name ); functions.markAsNeeded( name, true );
return;
}
if( (annotationValues = method.getAnnotation( JWebAssembly.REPLACE_ANNOTATION )) != null ) {
String signatureName = (String)annotationValues.get( "value" );
name = new FunctionName( signatureName );
functions.addReplacement( name, method );
return; return;
} }
} catch( Exception ioex ) { } catch( Exception ioex ) {
@ -446,8 +460,10 @@ public class ModuleGenerator {
} }
break; break;
case Call: case Call:
((WasmCallInstruction)instruction).markAsNeeded( functions, true );
break;
case CallIndirect: case CallIndirect:
((WasmCallInstruction)instruction).markAsNeeded( functions ); ((WasmCallInstruction)instruction).markAsNeeded( functions, false );
break; break;
case Struct: case Struct:
WasmStructInstruction instr = (WasmStructInstruction)instruction; WasmStructInstruction instr = (WasmStructInstruction)instruction;

View File

@ -42,6 +42,7 @@ import de.inetsoftware.jwebassembly.wasm.ValueType;
*/ */
public class TypeManager { public class TypeManager {
/** name of virtual function table, start with a point for an invalid Java identifier */
static final String VTABLE = ".vtable"; static final String VTABLE = ".vtable";
private Map<String, StructType> map = new LinkedHashMap<>(); private Map<String, StructType> map = new LinkedHashMap<>();
@ -204,7 +205,7 @@ public class TypeManager {
FunctionName func = methods.get( idx ); FunctionName func = methods.get( idx );
if( func.methodName.equals( funcName.methodName ) && func.signature.equals( funcName.signature ) ) { if( func.methodName.equals( funcName.methodName ) && func.signature.equals( funcName.signature ) ) {
methods.set( idx, funcName ); // use the override method methods.set( idx, funcName ); // use the override method
functions.markAsNeeded( funcName ); // mark all overridden methods also as needed if the super method is used functions.markAsNeeded( funcName, false ); // mark all overridden methods also as needed if the super method is used
break; break;
} }
} }

View File

@ -74,8 +74,8 @@ class WasmCallIndirectInstruction extends WasmCallInstruction {
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
void markAsNeeded( FunctionManager functions ) { void markAsNeeded( FunctionManager functions, boolean isStatic ) {
super.markAsNeeded( functions ); super.markAsNeeded( functions, isStatic );
virtualFunctionIdx = functions.getFunctionIndex( getFunctionName() ); virtualFunctionIdx = functions.getFunctionIndex( getFunctionName() );
} }

View File

@ -75,9 +75,11 @@ class WasmCallInstruction extends WasmInstruction {
* *
* @param functions * @param functions
* the function manager * the function manager
* @param isStatic
* true, if the method is static
*/ */
void markAsNeeded( @Nonnull FunctionManager functions ) { void markAsNeeded( @Nonnull FunctionManager functions, boolean isStatic ) {
name = functions.markAsNeeded( name ); name = functions.markAsNeeded( name, isStatic );
} }
/** /**