diff --git a/README.md b/README.md index d0d06c0..1c16578 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ The project is currently not production ready but you can run already some tests * [x] invoke default method calls * [x] String support * [x] Simple Class object support -* [ ] static constructors +* [x] static constructors * [x] Optimizer - Optimize the WASM output of a single method after transpiling before writing to output * [x] Hello World sample [(live)](https://i-net-software.github.io/JWebAssembly/samples/HelloWorld/HelloWorld.html), [(source code)](https://github.com/i-net-software/JWebAssembly/blob/master/docs/samples/HelloWorld/HelloWorld.java) diff --git a/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java b/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java index df314bb..bd76487 100644 --- a/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java +++ b/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java @@ -91,6 +91,8 @@ public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcod private boolean callIndirect; + private FunctionName startFunction; + /** * Create new instance. * @@ -133,6 +135,7 @@ public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcod writeEventSection(); writeSection( SectionType.Global, globals.values() ); writeSection( SectionType.Export, exports ); + writeStartSection(); writeElementSection(); writeCodeSection(); writeDataSection(); @@ -253,6 +256,22 @@ public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcod } } + /** + * Write a start section. The id of the function that should be automatically executed. + * + * @throws IOException + * if any I/O error occur + */ + private void writeStartSection() throws IOException { + if( startFunction == null ) { + return; + } + int id = getFunction( startFunction ).id; + WasmOutputStream stream = new WasmOutputStream(); + stream.writeVaruint32( id ); + wasm.writeSection( SectionType.Start, stream ); + } + /** * Write element section. This section create a matching between direct and indirect function call IDs. * @@ -518,10 +537,15 @@ public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcod */ @Override protected void writeMethodParamStart( FunctionName name, FunctionType funcType ) throws IOException { - if( funcType == FunctionType.Abstract ) { - abstracts.put( name.signatureName, function = new Function() ); - } else { - function = getFunction( name ); + switch( funcType ) { + case Abstract: + abstracts.put( name.signatureName, function = new Function() ); + break; + case Start: + startFunction = name; + //$FALL-THROUGH$ + default: + function = getFunction( name ); } functionType = new FunctionTypeEntry(); locals.clear(); diff --git a/src/de/inetsoftware/jwebassembly/module/FunctionManager.java b/src/de/inetsoftware/jwebassembly/module/FunctionManager.java index c835f8b..8fbad7f 100644 --- a/src/de/inetsoftware/jwebassembly/module/FunctionManager.java +++ b/src/de/inetsoftware/jwebassembly/module/FunctionManager.java @@ -37,9 +37,9 @@ import de.inetsoftware.jwebassembly.WasmException; */ class FunctionManager { - private final Map states = new LinkedHashMap<>(); + private final Map states = new LinkedHashMap<>(); - private final Map classesWithCInit = new HashMap<>(); + private final Map classesWithClinit = new HashMap<>(); private boolean isFinish; @@ -90,10 +90,10 @@ class FunctionManager { * Mark that a class has static initializer. * * @param name - * the function name + * the "<clinit>" function name */ - void markClassWithCInit( FunctionName name ) { - classesWithCInit.put( name.className, name ); + void markClassWithClinit( FunctionName name ) { + classesWithClinit.put( name.className, name ); } /** @@ -152,7 +152,7 @@ class FunctionManager { } state.state = State.Needed; JWebAssembly.LOGGER.fine( "\t\tcall: " + name.signatureName ); - FunctionName cInit = classesWithCInit.get( name.className ); + FunctionName cInit = classesWithClinit.get( name.className ); if( cInit != null ) { markAsNeeded( cInit ); } @@ -243,10 +243,23 @@ class FunctionManager { return null; } + /** + * Get all static constructor FunctionName of used classes. + * + * @return an iterator + */ + @Nullable + Iterator getWriteLaterClinit() { + return classesWithClinit.values().stream().filter( ( name ) -> { + FunctionState state = states.get( name ); + return state != null && (state.state == State.Needed || state.state == State.Scanned); + } ).iterator(); + } + /** * Get all FunctionName that is required but was not written. * - * @return the FunctionName or null + * @return an iterator */ @Nullable Iterator getWriteLater() { diff --git a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java index 4025ac4..40dfbe7 100644 --- a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java +++ b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java @@ -299,6 +299,9 @@ public class ModuleGenerator { javaScript.addImport( importModule, importName, importAnannotation ); } + // add a start method for the static class constructors + prepareStartFunction(); + // init/write the function types for( Iterator iterator = functions.getWriteLater(); iterator.hasNext(); ) { FunctionName name = iterator.next(); @@ -318,6 +321,39 @@ public class ModuleGenerator { writer.prepareFinish(); } + /** + * Add a start method for the static class constructors + * + * @throws IOException + * if any I/O error occur + */ + private void prepareStartFunction() throws IOException { + // add the start function/section only if there are static code + Iterator iterator = functions.getWriteLaterClinit(); + if( iterator.hasNext() ) { + FunctionName cinit = new SyntheticFunctionName( "", "", "()V" ) { + @Override + protected boolean hasWasmCode() { + return true; + } + + @Override + protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) { + watParser.reset( null, null, getSignature( null ) ); + + while( iterator.hasNext() ) { + FunctionName name = iterator.next(); + //TODO if not in the debug mode then inlining would produce smaller output and should be faster + watParser.addCallInstruction( name, 0, -1 ); + } + return watParser; + } + }; + functions.markAsNeeded( cinit ); + writeMethodSignature( cinit, FunctionType.Start, null ); + } + } + /** * Finish the code generation. * @@ -405,7 +441,7 @@ public class ModuleGenerator { try { FunctionName name = new FunctionName( method ); if( "".equals( name.methodName ) ) { - functions.markClassWithCInit( name ); + functions.markClassWithClinit( name ); } if( functions.isKnown( name ) ) { return; diff --git a/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java b/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java index cab1979..40ecd4a 100644 --- a/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java +++ b/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java @@ -329,8 +329,14 @@ public class TextModuleWriter extends ModuleWriter { */ @Override protected void writeMethodParamStart( @Nonnull FunctionName name, FunctionType funcType ) throws IOException { - if( funcType == FunctionType.Abstract ) { - abstracts.put( name.signatureName, new Function() ); + switch( funcType ) { + case Abstract: + abstracts.put( name.signatureName, new Function() ); + break; + case Start: + newline( imports ); + imports.append( "(start $" ).append( normalizeName( name ) ).append( ")" ); + break; } typeOutput.setLength( 0 ); methodParamNames.clear(); diff --git a/src/de/inetsoftware/jwebassembly/wasm/FunctionType.java b/src/de/inetsoftware/jwebassembly/wasm/FunctionType.java index cafe8e2..54b6ef9 100644 --- a/src/de/inetsoftware/jwebassembly/wasm/FunctionType.java +++ b/src/de/inetsoftware/jwebassembly/wasm/FunctionType.java @@ -26,5 +26,7 @@ public enum FunctionType { /** has real code */ Code, /** abstract or interface, only used for indirrect call */ - Abstract; + Abstract, + /** the function of start section, should occur only once */ + Start, }