diff --git a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java index 1ec7092..07a006f 100644 --- a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java +++ b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java @@ -80,6 +80,7 @@ public class ModuleGenerator { private final CodeOptimizer optimizer; + private final StaticCodeBuilder staticCodeBuilder; /** * Create a new generator. * @@ -104,6 +105,7 @@ public class ModuleGenerator { javaCodeBuilder.init( options, classFileLoader ); ((WasmCodeBuilder)watParser).init( options, classFileLoader ); types.init( classFileLoader ); + staticCodeBuilder = new StaticCodeBuilder( writer.options, classFileLoader, javaCodeBuilder ); scanLibraries( libraries ); } @@ -401,8 +403,9 @@ public class ModuleGenerator { */ private void prepareStartFunction() throws IOException { // add the start function/section only if there are static code - if( functions.getWriteLaterClinit().hasNext() ) { - FunctionName start = new StaticCodeBuilder( writer.options, classFileLoader, javaCodeBuilder ).createStartFunction(); + Iterator writeLaterClinit = functions.getWriteLaterClinit(); + if( writeLaterClinit.hasNext() ) { + FunctionName start = staticCodeBuilder.createStartFunction( writeLaterClinit ); functions.markAsNeeded( start, false ); writeMethodSignature( start, FunctionType.Start, null ); } diff --git a/src/de/inetsoftware/jwebassembly/module/StaticCodeBuilder.java b/src/de/inetsoftware/jwebassembly/module/StaticCodeBuilder.java index e564e3d..c43b4af 100644 --- a/src/de/inetsoftware/jwebassembly/module/StaticCodeBuilder.java +++ b/src/de/inetsoftware/jwebassembly/module/StaticCodeBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 - 2021 Volker Berlin (i-net software) + * Copyright 2020 - 2022 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. @@ -16,15 +16,20 @@ package de.inetsoftware.jwebassembly.module; import java.io.IOException; -import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map.Entry; + +import javax.annotation.Nonnull; import de.inetsoftware.classparser.ClassFile; import de.inetsoftware.classparser.Code; import de.inetsoftware.classparser.MethodInfo; import de.inetsoftware.jwebassembly.WasmException; +import de.inetsoftware.jwebassembly.module.LocaleVariableManager.Variable; import de.inetsoftware.jwebassembly.watparser.WatParser; /** @@ -39,8 +44,6 @@ class StaticCodeBuilder { private JavaMethodWasmCodeBuilder javaCodeBuilder; - private final ArrayDeque clinits; - /** * Create a instance with a snapshot of all static class initializer. * @@ -55,18 +58,65 @@ class StaticCodeBuilder { this.options = options; this.classFileLoader = classFileLoader; this.javaCodeBuilder = javaCodeBuilder; - this.clinits = new ArrayDeque<>(); - options.functions.getWriteLaterClinit().forEachRemaining( clinits::push ); } /** * Create a start function for the static class constructors * + * @param writeLaterClinit iterator of all needed static constructors * @throws IOException * if any I/O error occur * @return the synthetic function name */ - FunctionName createStartFunction() throws IOException { + @Nonnull + FunctionName createStartFunction( Iterator writeLaterClinit ) throws IOException { + // list all static constructors (class constructors) + LinkedHashMap constructors = new LinkedHashMap<>(); + while( writeLaterClinit.hasNext() ) { + FunctionName name = writeLaterClinit.next(); + constructors.put( name.className, name ); + } + + // scan for recursions between the classes + ArrayList clinits = new ArrayList<>(); + LinkedHashMap scans = new LinkedHashMap<>(); + for( Iterator> it = constructors.entrySet().iterator(); it.hasNext(); ) { + Entry entry = it.next(); + FunctionName name = entry.getValue(); + ScanState scan = scan( name, constructors ); + if( scan == null ) { + // no dependency to any other static class constructor + it.remove(); + clinits.add( name ); + } else { + scans.put( name.className, scan ); + } + } + + boolean scanAgain; + do { + scanAgain = false; + for( Iterator> it = scans.entrySet().iterator(); it.hasNext(); ) { + Entry entry = it.next(); + ScanState scan = entry.getValue(); + HashSet dependenciesClasses = scan.dependenciesClasses; + dependenciesClasses.retainAll( scans.keySet() ); + if( dependenciesClasses.isEmpty() ) { + clinits.add( scan.name ); + it.remove(); + scanAgain = true; + } + } + } while( scanAgain); + + // scan for recursions between the classes + for( Iterator it = scans.values().iterator(); it.hasNext(); ) { + ScanState scan = it.next(); + it.remove(); + patch( scan, scans ); + clinits.add( scan.name ); + } + return new SyntheticFunctionName( "", "", "()V" ) { /** * {@inheritDoc} @@ -83,24 +133,16 @@ class StaticCodeBuilder { protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) { watParser.reset( null, null, getSignature( null ) ); - while( !clinits.isEmpty() ) { - FunctionName name = clinits.pop(); + for( FunctionName name : clinits ) { watParser.addCallInstruction( name, false, 0, -1 ); - scanAndPatchIfNeeded( name ); } return watParser; } }; } - /** - * Scan a class initializer (static constructor). If it reference to another class with has class initializer which - * was not call then it patch the code and call it before the other class is access. - * - * @param name - * name of the static constuctor - */ - private void scanAndPatchIfNeeded( FunctionName name ) { + private ScanState scan( FunctionName name, LinkedHashMap constructors ) { + ScanState state = null; String className = name.className; String sourceFile = null; WasmInstruction instr = null; @@ -113,51 +155,120 @@ class StaticCodeBuilder { Code code = method.getCode(); javaCodeBuilder.buildCode( code, method ); - boolean patched = false; - List instructions = new ArrayList<>( javaCodeBuilder.getInstructions() ); + List instructions = javaCodeBuilder.getInstructions(); + + // search for references to other classes for( int i = 0; i < instructions.size(); i++ ) { instr = instructions.get( i ); + String otherClassName; switch( instr.getType() ) { case Global: WasmGlobalInstruction global = (WasmGlobalInstruction)instr; - String fieldClassName = global.getFieldName().className; - if( className.equals( fieldClassName ) ) { - continue; // field in own class - } - for( Iterator it = clinits.iterator(); it.hasNext(); ) { - FunctionName clinit = it.next(); - if( fieldClassName.equals( clinit.className ) ) { - instructions.add( new WasmCallInstruction( clinit, instr.getCodePosition(), instr.getLineNumber(), options.types, false ) ); - i++; - patched = true; - it.remove(); - scanAndPatchIfNeeded( clinit ); - } - } + otherClassName = global.getFieldName().className; break; case Call: - //TODO + WasmCallInstruction call = (WasmCallInstruction)instr; + otherClassName = call.getFunctionName().className; + break; + default: + continue; } - } - - if( patched ) { - options.functions.markAsNeededAndReplaceIfExists( new SyntheticFunctionName( className, name.methodName, name.signature ) { - @Override - protected boolean hasWasmCode() { - return true; + if( className.equals( otherClassName ) ) { + continue; // field or method in own class + } + // search if the other class has a static constructor + FunctionName clinit = constructors.get( className ); + if( clinit != null ) { + if( state == null ) { + state = new ScanState(); + state.name = name; + state.instructions = new ArrayList<>( instructions ); + state.localVariables = javaCodeBuilder.getLocalVariables().getCopy(); } - - protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) { - WasmCodeBuilder codebuilder = watParser; - watParser.reset( null, null, null ); - codebuilder.getInstructions().addAll( instructions ); - return watParser; - } - } ); + state.dependenciesClasses.add( otherClassName ); + } } } catch( IOException ex ) { throw WasmException.create( ex, sourceFile, className, name.methodName, instr == null ? -1 : instr.getLineNumber() ); } + return state; + } + /** + * Scan a class initializer (static constructor). If it references another class with a static constructor which was + * not called before then it patch the code and call it before the other class is access. + * + * @param name + * name of the static constructor + * @param queue + * a list with all static constructors which was not called + */ + private void patch( ScanState scan, LinkedHashMap scans ) { + FunctionName name = scan.name; + String className = name.className; + + boolean patched = false; + List instructions = scan.instructions; + + // search for references to other classes + for( int i = 0; i < instructions.size(); i++ ) { + WasmInstruction instr = instructions.get( i ); + String otherClassName; + switch( instr.getType() ) { + case Global: + WasmGlobalInstruction global = (WasmGlobalInstruction)instr; + otherClassName = global.getFieldName().className; + break; + case Call: + WasmCallInstruction call = (WasmCallInstruction)instr; + otherClassName = call.getFunctionName().className; + break; + default: + continue; + } + if( className.equals( otherClassName ) ) { + continue; // field or method in own class + } + + // search if the other class has a static constructor + ScanState otherScan = scans.remove( otherClassName ); + if( otherScan != null ) { + // add a call to the other static consturctor + instructions.add( i, new WasmCallInstruction( otherScan.name, instr.getCodePosition(), instr.getLineNumber(), options.types, false ) ); + i++; + + if( !patched ) { + // create patched method + patched = true; + + // create a patched version of the static constructor which call other constructors on specific points. + options.functions.markAsNeededAndReplaceIfExists( new SyntheticFunctionName( className, name.methodName, name.signature ) { + @Override + protected boolean hasWasmCode() { + return true; + } + + protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) { + WasmCodeBuilder codebuilder = watParser; + watParser.reset( null, null, null ); + ((WasmCodeBuilder)watParser).getLocalVariables().setCopy( scan.localVariables ); + codebuilder.getInstructions().addAll( instructions ); + return watParser; + } + } ); + } + + // remove and scan it that we does not execute it two times + patch( otherScan, scans ); + break; + } + } + } + + private static class ScanState { + private final HashSet dependenciesClasses = new HashSet<>(); + private FunctionName name; + private List instructions; + private Variable[] localVariables; } }