From 128ac192426bcb94e69a8dc92cfc8da6d5701d05 Mon Sep 17 00:00:00 2001 From: Volker Berlin Date: Fri, 10 Apr 2020 11:40:07 +0200 Subject: [PATCH] fix cyclic dependencies between static class initializers --- .../jwebassembly/module/ModuleGenerator.java | 24 +-- .../module/StaticCodeBuilder.java | 157 ++++++++++++++++++ .../module/WasmGlobalInstruction.java | 12 +- 3 files changed, 171 insertions(+), 22 deletions(-) create mode 100644 src/de/inetsoftware/jwebassembly/module/StaticCodeBuilder.java diff --git a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java index 121fd4e..290b367 100644 --- a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java +++ b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java @@ -350,27 +350,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 cinit = new SyntheticFunctionName( "", "", "()V" ) { - @Override - protected boolean hasWasmCode() { - return true; - } - - @Override - protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) { - watParser.reset( null, null, getSignature( null ) ); - - Iterator iterator = functions.getWriteLaterClinit(); - 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 ); + FunctionName start = new StaticCodeBuilder( writer.options, classFileLoader, javaCodeBuilder ).createStartFunction(); + functions.markAsNeeded( start ); + writeMethodSignature( start, FunctionType.Start, null ); } } diff --git a/src/de/inetsoftware/jwebassembly/module/StaticCodeBuilder.java b/src/de/inetsoftware/jwebassembly/module/StaticCodeBuilder.java new file mode 100644 index 0000000..a899d17 --- /dev/null +++ b/src/de/inetsoftware/jwebassembly/module/StaticCodeBuilder.java @@ -0,0 +1,157 @@ +/* + * Copyright 2020 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.inetsoftware.jwebassembly.module; + +import java.io.IOException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import de.inetsoftware.classparser.ClassFile; +import de.inetsoftware.classparser.Code; +import de.inetsoftware.classparser.MethodInfo; +import de.inetsoftware.jwebassembly.WasmException; +import de.inetsoftware.jwebassembly.watparser.WatParser; + +/** + * Write the static class initializer code. + * + * @author Volker Berlin + */ +class StaticCodeBuilder { + private WasmOptions options; + + private ClassFileLoader classFileLoader; + + private JavaMethodWasmCodeBuilder javaCodeBuilder; + + private final ArrayDeque clinits; + + /** + * Create a instance with a snapshot of all static class initializer. + * + * @param options + * the compiler options + * @param classFileLoader + * for loading the class files + * @param javaCodeBuilder + * global code builder + */ + StaticCodeBuilder( WasmOptions options, ClassFileLoader classFileLoader, JavaMethodWasmCodeBuilder javaCodeBuilder ) { + 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 + * + * @throws IOException + * if any I/O error occur + * @return the synthetic function name + */ + FunctionName createStartFunction() throws IOException { + return new SyntheticFunctionName( "", "", "()V" ) { + /** + * {@inheritDoc} + */ + @Override + protected boolean hasWasmCode() { + return true; + } + + /** + * {@inheritDoc} + */ + @Override + protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) { + watParser.reset( null, null, getSignature( null ) ); + + while( !clinits.isEmpty() ) { + FunctionName name = clinits.pop(); + watParser.addCallInstruction( name, 0, -1 ); + scanAndPatchIfNeeded( name ); + } + return watParser; + } + }; + } + + private void scanAndPatchIfNeeded( FunctionName name ) { + String className = name.className; + String sourceFile = null; + WasmInstruction instr = null; + try { + ClassFile classFile = classFileLoader.get( className ); + sourceFile = classFile.getSourceFile(); + MethodInfo method = classFile.getMethod( name.methodName, name.signature ); + + Code code = method.getCode(); + javaCodeBuilder.buildCode( code, method ); + + boolean patched = false; + List instructions = new ArrayList<>( javaCodeBuilder.getInstructions() ); + for( int i = 0; i < instructions.size(); i++ ) { + instr = instructions.get( i ); + 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 ); + } + } + break; + case Call: + //TODO + } + } + + if( patched ) { + options.functions.markAsNeededAndReplaceIfExists( new SyntheticFunctionName( className, name.methodName, name.signature ) { + + @Override + protected boolean hasWasmCode() { + // TODO Auto-generated method stub + return true; + } + + protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) { + WasmCodeBuilder codebuilder = watParser; + watParser.reset( null, null, null ); + codebuilder.getInstructions().addAll( instructions ); + return watParser; + } + } ); + } + } catch( IOException ex ) { + throw WasmException.create( ex, sourceFile, className, instr == null ? -1 : instr.getLineNumber() ); + } + + } +} diff --git a/src/de/inetsoftware/jwebassembly/module/WasmGlobalInstruction.java b/src/de/inetsoftware/jwebassembly/module/WasmGlobalInstruction.java index ca4efe7..0e9aabb 100644 --- a/src/de/inetsoftware/jwebassembly/module/WasmGlobalInstruction.java +++ b/src/de/inetsoftware/jwebassembly/module/WasmGlobalInstruction.java @@ -50,7 +50,7 @@ class WasmGlobalInstruction extends WasmInstruction { * @param lineNumber * the line number in the Java source code */ - WasmGlobalInstruction( boolean load, FunctionName name, AnyType type, int javaCodePos, int lineNumber ) { + WasmGlobalInstruction( boolean load, @Nonnull FunctionName name, AnyType type, int javaCodePos, int lineNumber ) { super( javaCodePos, lineNumber ); this.load = load; this.name = name; @@ -65,6 +65,16 @@ class WasmGlobalInstruction extends WasmInstruction { return Type.Global; } + /** + * The name of the field + * + * @return the field + */ + @Nonnull + FunctionName getFieldName() { + return name; + } + /** * {@inheritDoc} */