From 69b8db16d138d502b2d618bd6eb12b054d2ed437 Mon Sep 17 00:00:00 2001 From: Volker Berlin Date: Tue, 8 Mar 2022 15:13:19 +0100 Subject: [PATCH] call class initializer before access to static fields --- .../jwebassembly/module/FunctionManager.java | 22 ++-- .../module/JavaMethodWasmCodeBuilder.java | 8 +- .../jwebassembly/module/ModuleGenerator.java | 4 +- .../jwebassembly/module/TypeManager.java | 112 ++++++++++-------- .../jwebassembly/module/WasmCodeBuilder.java | 25 +++- .../module/WasmGlobalInstruction.java | 17 ++- 6 files changed, 116 insertions(+), 72 deletions(-) diff --git a/src/de/inetsoftware/jwebassembly/module/FunctionManager.java b/src/de/inetsoftware/jwebassembly/module/FunctionManager.java index bfc7316..d96a0e5 100644 --- a/src/de/inetsoftware/jwebassembly/module/FunctionManager.java +++ b/src/de/inetsoftware/jwebassembly/module/FunctionManager.java @@ -15,6 +15,8 @@ */ package de.inetsoftware.jwebassembly.module; +import static de.inetsoftware.jwebassembly.module.WasmCodeBuilder.CLASS_INIT; + import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -42,10 +44,10 @@ class FunctionManager { private final Set usedClasses = new LinkedHashSet<>(); - private boolean isFinish; + private boolean isFinish; /** - * Finish the prepare. Now no new function should be added. + * Finish the prepare. Now no new function should be added. */ void prepareFinish() { isFinish = true; @@ -108,8 +110,8 @@ class FunctionManager { * @param importAnannotation * the annotation of the import */ - void markAsImport( @Nonnull FunctionName name, Map importAnannotation ) { - markAsImport( name, (key) -> importAnannotation.get( key ) ); + void markAsImport( @Nonnull FunctionName name, Map importAnannotation ) { + markAsImport( name, ( key ) -> importAnannotation.get( key ) ); } /** @@ -121,7 +123,7 @@ class FunctionManager { * @param importAnannotation * the annotation of the import */ - void markAsImport( @Nonnull FunctionName name, Function importAnannotation ) { + void markAsImport( @Nonnull FunctionName name, Function importAnannotation ) { getOrCreate( name ).importAnannotation = importAnannotation; } @@ -260,7 +262,7 @@ class FunctionManager { */ @Nonnull Iterator getWriteLaterClinit() { - return iterator( entry -> entry.getKey().methodName.equals( "" ) && entry.getValue().state != State.None ); + return iterator( entry -> entry.getKey().methodName.equals( CLASS_INIT ) && entry.getValue().state != State.None ); } /** @@ -299,7 +301,9 @@ class FunctionManager { /** * get a iterator for function names - * @param filter the filter + * + * @param filter + * the filter * @return the iterator */ @Nonnull @@ -448,7 +452,7 @@ class FunctionManager { * * @param name * the name - * @return the index in the itable + * @return the index in the itable */ int getITableIndex( @Nonnull FunctionName name ) { return getOrCreate( name ).itableIdx; @@ -458,7 +462,7 @@ class FunctionManager { * State of a function/method */ private static class FunctionState { - private State state = State.None; + private State state = State.None; private MethodInfo method; diff --git a/src/de/inetsoftware/jwebassembly/module/JavaMethodWasmCodeBuilder.java b/src/de/inetsoftware/jwebassembly/module/JavaMethodWasmCodeBuilder.java index f5a8789..ec28cbf 100644 --- a/src/de/inetsoftware/jwebassembly/module/JavaMethodWasmCodeBuilder.java +++ b/src/de/inetsoftware/jwebassembly/module/JavaMethodWasmCodeBuilder.java @@ -85,13 +85,13 @@ class JavaMethodWasmCodeBuilder extends WasmCodeBuilder { reset( code.getLocalVariableTable(), method, null ); branchManager.reset( code ); - if( "".equals( method.getName() ) ) { + if( CLASS_INIT.equals( method.getName() ) ) { // Add a hook to run the class/static constructor only once - FunctionName name = new FunctionName( method.getClassName(), "", "" ); - addGlobalInstruction( true, name, ValueType.i32, -1, -1 ); + FunctionName name = new FunctionName( method.getClassName(), "", "" ); + addGlobalInstruction( true, name, ValueType.i32, null, -1, -1 ); addBlockInstruction( WasmBlockOperator.BR_IF, 0, -1, -1 ); addConstInstruction( 1, ValueType.i32, -1, -1 ); - addGlobalInstruction( false, name, ValueType.i32, -1, -1 ); + addGlobalInstruction( false, name, ValueType.i32, null, -1, -1 ); } byteCode = code.getByteCode(); diff --git a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java index 07a006f..0877c5b 100644 --- a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java +++ b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java @@ -15,6 +15,8 @@ */ package de.inetsoftware.jwebassembly.module; +import static de.inetsoftware.jwebassembly.module.WasmCodeBuilder.CLASS_INIT; + import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; @@ -387,7 +389,7 @@ public class ModuleGenerator { String className = iterator.next(); ClassFile classFile = classFileLoader.get( className ); if( classFile != null ) { - MethodInfo method = classFile.getMethod( "", "()V" ); + MethodInfo method = classFile.getMethod( CLASS_INIT, "()V" ); if( method != null ) { functions.markAsNeeded( new FunctionName( method ), false ); } diff --git a/src/de/inetsoftware/jwebassembly/module/TypeManager.java b/src/de/inetsoftware/jwebassembly/module/TypeManager.java index c1d9478..359c84e 100644 --- a/src/de/inetsoftware/jwebassembly/module/TypeManager.java +++ b/src/de/inetsoftware/jwebassembly/module/TypeManager.java @@ -16,6 +16,8 @@ */ package de.inetsoftware.jwebassembly.module; +import static de.inetsoftware.jwebassembly.module.WasmCodeBuilder.CONSTRUCTOR; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; @@ -58,38 +60,39 @@ import de.inetsoftware.jwebassembly.watparser.WatParser; */ public class TypeManager { - /** name of virtual function table, start with a point for an invalid Java identifier */ - static final String FIELD_VTABLE = ".vtable"; + /** name of virtual function table, start with a point for an invalid Java identifier */ + static final String FIELD_VTABLE = ".vtable"; /** * Name of field with system hash code, start with a point for an invalid Java identifier. */ - static final String FIELD_HASHCODE = ".hashcode"; + static final String FIELD_HASHCODE = ".hashcode"; /** * Name of field with array value. */ - public static final String FIELD_VALUE = ".array"; + public static final String FIELD_VALUE = ".array"; /** * Byte position in the type description that contains the offset to the interfaces. Length 4 bytes. */ - public static final int TYPE_DESCRIPTION_INTERFACE_OFFSET = 0; + public static final int TYPE_DESCRIPTION_INTERFACE_OFFSET = 0; /** * Byte position in the type description that contains the offset to the instanceof list. Length 4 bytes. */ - public static final int TYPE_DESCRIPTION_INSTANCEOF_OFFSET = 4; + public static final int TYPE_DESCRIPTION_INSTANCEOF_OFFSET = 4; /** - * Byte position in the type description that contains the offset to class name idx in the string constant table. Length 4 bytes. + * Byte position in the type description that contains the offset to class name idx in the string constant table. + * Length 4 bytes. */ - public static final int TYPE_DESCRIPTION_TYPE_NAME = 8; + public static final int TYPE_DESCRIPTION_TYPE_NAME = 8; /** * Byte position in the type description that contains the type of the array (component type). Length 4 bytes. */ - public static final int TYPE_DESCRIPTION_ARRAY_TYPE = 12; + public static final int TYPE_DESCRIPTION_ARRAY_TYPE = 12; /** * The reserved position on start of the vtable: @@ -97,73 +100,74 @@ public class TypeManager { *
  • offset of instanceof list *
  • offset of class name idx in the string constant table */ - private static final int VTABLE_FIRST_FUNCTION_INDEX = 4; + private static final int VTABLE_FIRST_FUNCTION_INDEX = 4; - private static final FunctionName CLASS_CONSTANT_FUNCTION = new FunctionName( "java/lang/Class.classConstant(I)Ljava/lang/Class;" ); + private static final FunctionName CLASS_CONSTANT_FUNCTION = new FunctionName( "java/lang/Class.classConstant(I)Ljava/lang/Class;" ); /** * Type id of primitive class */ - public static final int BOOLEAN = 0; + public static final int BOOLEAN = 0; /** * Type id of primitive class */ - public static final int BYTE = 1; + public static final int BYTE = 1; /** * Type id of primitive class */ - public static final int CHAR = 2; + public static final int CHAR = 2; /** * Type id of primitive class */ - public static final int DOUBLE = 3; + public static final int DOUBLE = 3; /** * Type id of primitive class */ - public static final int FLOAT = 4; + public static final int FLOAT = 4; /** * Type id of primitive class */ - public static final int INT = 5; + public static final int INT = 5; /** * Type id of primitive class */ - public static final int LONG = 6; + public static final int LONG = 6; /** * Type id of primitive class */ - public static final int SHORT = 7; + public static final int SHORT = 7; /** * Type id of primitive class */ - public static final int VOID = 8; + public static final int VOID = 8; /** * the list of primitive types. The order is important and must correlate with getPrimitiveClass. * * @see ReplacementForClass#getPrimitiveClass(String) */ - private static final String[] PRIMITIVE_CLASSES = { "boolean", "byte", "char", "double", "float", "int", "long", "short", "void" }; + private static final String[] PRIMITIVE_CLASSES = + { "boolean", "byte", "char", "double", "float", "int", "long", "short", "void" }; - private final Map structTypes = new LinkedHashMap<>(); + private final Map structTypes = new LinkedHashMap<>(); - private final Map blockTypes = new LinkedHashMap<>(); + private final Map blockTypes = new LinkedHashMap<>(); - private int typeIndexCounter; + private int typeIndexCounter; - private boolean isFinish; + private boolean isFinish; - final WasmOptions options; + final WasmOptions options; - private int typeTableOffset; + private int typeTableOffset; private ClassFileLoader classFileLoader; @@ -179,7 +183,9 @@ public class TypeManager { /** * Initialize the type manager - * @param classFileLoader for loading the class files + * + * @param classFileLoader + * for loading the class files */ void init( ClassFileLoader classFileLoader ) { this.classFileLoader = classFileLoader; @@ -260,6 +266,7 @@ public class TypeManager { /** * Get the function name to get a constant class. + * * @return the function */ @Nonnull @@ -424,8 +431,8 @@ public class TypeManager { } /** - * Create the FunctionName for a virtual call. The function has 2 parameters (THIS, - * virtualfunctionIndex) and returns the index of the function. + * Create the FunctionName for a virtual call. The function has 2 parameters (THIS, virtualfunctionIndex) and + * returns the index of the function. * * @return the name */ @@ -453,7 +460,7 @@ public class TypeManager { static int callInterface( OBJECT THIS, int classIndex, int virtualfunctionIndex ) { int table = THIS.vtable; table += i32_load[table]; - + do { int nextClass = i32_load[table]; if( nextClass == classIndex ) { @@ -476,8 +483,7 @@ public class TypeManager { + "local.set 3 " // save $table, the itable start location + "loop" // + " local.get 3" // get $table - + " i32.load offset=0 align=4" - + " local.tee 4" // save $nextClass + + " i32.load offset=0 align=4" + " local.tee 4" // save $nextClass + " local.get 1" // get $classIndex + " i32.eq" // + " if" // $nextClass == $classIndex @@ -587,30 +593,30 @@ public class TypeManager { */ public static class StructType implements AnyType { - private final String name; + private final String name; - private final StructTypeKind kind; + private final StructTypeKind kind; - private final TypeManager manager; + private final TypeManager manager; - private final int classIndex; + private final int classIndex; - private int code = Integer.MAX_VALUE; + private int code = Integer.MAX_VALUE; - private HashSet neededFields = new HashSet<>(); + private HashSet neededFields = new HashSet<>(); - private List fields; + private List fields; - private List vtable; + private List vtable; - private Set instanceOFs; + private Set instanceOFs; - private Map> interfaceMethods; + private Map> interfaceMethods; /** * The offset to the vtable in the data section. */ - private int vtableOffset; + private int vtableOffset; /** * Create a reference to type @@ -770,7 +776,7 @@ public class TypeManager { // calculate the vtable (the function indexes of the virtual methods) for( MethodInfo method : classFile.getMethods() ) { - if( method.isStatic() || "".equals( method.getName() ) ) { + if( method.isStatic() || CONSTRUCTOR.equals( method.getName() ) ) { continue; } FunctionName funcName = new FunctionName( method ); @@ -1017,6 +1023,7 @@ public class TypeManager { /** * Get kind of the StructType + * * @return the type kind */ public StructTypeKind getKind() { @@ -1025,6 +1032,7 @@ public class TypeManager { /** * Get the name of the Java type + * * @return the name */ public String getName() { @@ -1051,6 +1059,7 @@ public class TypeManager { /** * Get the fields of this struct + * * @return the fields */ public List getFields() { @@ -1189,7 +1198,7 @@ public class TypeManager { 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) ) ); + paramFields.add( new NamedStorageType( params.get( i ), "", "arg$" + (i + 1) ) ); } this.interfaceType = interfaceType; this.interfaceMethodName = interfaceMethodName; @@ -1199,10 +1208,12 @@ public class TypeManager { protected boolean hasWasmCode() { return true; } + @Override protected boolean istStatic() { return false; } + @Override protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) { WasmCodeBuilder codebuilder = watParser; @@ -1290,12 +1301,15 @@ public class TypeManager { @Nonnull private final List params; + @Nonnull private final List results; - private int code; - private String name; - public BlockType(List params, List results) { + private int code; + + private String name; + + public BlockType( List params, List results ) { this.params = params; this.results = results; } diff --git a/src/de/inetsoftware/jwebassembly/module/WasmCodeBuilder.java b/src/de/inetsoftware/jwebassembly/module/WasmCodeBuilder.java index 3954b2a..dd71a57 100644 --- a/src/de/inetsoftware/jwebassembly/module/WasmCodeBuilder.java +++ b/src/de/inetsoftware/jwebassembly/module/WasmCodeBuilder.java @@ -57,6 +57,12 @@ import de.inetsoftware.jwebassembly.wasm.WasmBlockOperator; */ public abstract class WasmCodeBuilder { + /** Java method name of of constructor */ + static final String CONSTRUCTOR = ""; + + /** Java method name of static constructor or initialization method */ + static final String CLASS_INIT = ""; + private final LocaleVariableManager localVariables; private final List instructions; @@ -497,7 +503,16 @@ public abstract class WasmCodeBuilder { protected void addGlobalInstruction( boolean load, Member ref, int javaCodePos, int lineNumber ) { FunctionName name = new FunctionName( ref ); AnyType type = new ValueTypeParser( ref.getType(), types ).next(); - addGlobalInstruction( load, name, type, javaCodePos, lineNumber ); + FunctionName clinit; + if( load ) { + clinit = new FunctionName( name.className, CLASS_INIT, "()V" ); + if( !functions.isUsed( clinit ) ) { + clinit = null; + } + } else { + clinit = null; + } + addGlobalInstruction( load, name, type, clinit, javaCodePos, lineNumber ); } /** @@ -514,8 +529,8 @@ public abstract class WasmCodeBuilder { * @param lineNumber * the line number in the Java source code */ - protected void addGlobalInstruction( boolean load, FunctionName name, AnyType type, int javaCodePos, int lineNumber ) { - instructions.add( new WasmGlobalInstruction( load, name, type, javaCodePos, lineNumber ) ); + protected void addGlobalInstruction( boolean load, FunctionName name, AnyType type, FunctionName clinit, int javaCodePos, int lineNumber ) { + instructions.add( new WasmGlobalInstruction( load, name, type, clinit, javaCodePos, lineNumber ) ); functions.markClassAsUsed( name.className ); } @@ -655,7 +670,7 @@ public abstract class WasmCodeBuilder { name = functions.markAsNeeded( name, needThisParameter ); WasmCallInstruction instruction = new WasmCallInstruction( name, javaCodePos, lineNumber, types, needThisParameter ); - if( "".equals( name.methodName ) ) { + if( CONSTRUCTOR.equals( name.methodName ) ) { // check if there a factory for the constructor in JavaScript then we need to do some more complex patching Function importAnannotation = functions.getImportAnannotation( name ); FunctionName factoryName = null; @@ -667,7 +682,7 @@ public abstract class WasmCodeBuilder { factoryName = new ImportSyntheticFunctionName( "String", "init", signature, importAnannotation ); } else { MethodInfo replace = functions.replace( name, null ); - if( replace != null && !"".equals( replace.getName() ) ) { + if( replace != null && !CONSTRUCTOR.equals( replace.getName() ) ) { // the constructor was replaced with a factory method. Typical this method called then a JavaScript replacement factoryName = new FunctionName( replace ); } diff --git a/src/de/inetsoftware/jwebassembly/module/WasmGlobalInstruction.java b/src/de/inetsoftware/jwebassembly/module/WasmGlobalInstruction.java index bcacd10..f88923a 100644 --- a/src/de/inetsoftware/jwebassembly/module/WasmGlobalInstruction.java +++ b/src/de/inetsoftware/jwebassembly/module/WasmGlobalInstruction.java @@ -1,5 +1,5 @@ /* - Copyright 2018 - 2021 Volker Berlin (i-net software) + Copyright 2018 - 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. @@ -19,6 +19,7 @@ package de.inetsoftware.jwebassembly.module; import java.io.IOException; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import de.inetsoftware.jwebassembly.wasm.AnyType; @@ -30,11 +31,13 @@ import de.inetsoftware.jwebassembly.wasm.AnyType; */ class WasmGlobalInstruction extends WasmInstruction { - private boolean load; + private boolean load; private FunctionName name; - private AnyType type; + private AnyType type; + + private FunctionName clinit; /** * Create an instance of a load/store instruction @@ -45,16 +48,19 @@ class WasmGlobalInstruction extends WasmInstruction { * the name of the static field * @param type * the type of the static field + * @param clinit + * a reference to the class/static constructor which should executed before access a static field * @param javaCodePos * the code position/offset in the Java method * @param lineNumber * the line number in the Java source code */ - WasmGlobalInstruction( boolean load, @Nonnull FunctionName name, AnyType type, int javaCodePos, int lineNumber ) { + WasmGlobalInstruction( boolean load, @Nonnull FunctionName name, AnyType type, @Nullable FunctionName clinit, int javaCodePos, int lineNumber ) { super( javaCodePos, lineNumber ); this.load = load; this.name = name; this.type = type; + this.clinit = clinit; } /** @@ -80,6 +86,9 @@ class WasmGlobalInstruction extends WasmInstruction { */ @Override public void writeTo( @Nonnull ModuleWriter writer ) throws IOException { + if( clinit != null ) { + writer.writeFunctionCall( clinit, clinit.signatureName ); + } writer.writeGlobalAccess( load, name, type ); }