call class initializer before access to static fields

This commit is contained in:
Volker Berlin 2022-03-08 15:13:19 +01:00
parent 710127bb44
commit 69b8db16d1
6 changed files with 116 additions and 72 deletions

View File

@ -15,6 +15,8 @@
*/ */
package de.inetsoftware.jwebassembly.module; package de.inetsoftware.jwebassembly.module;
import static de.inetsoftware.jwebassembly.module.WasmCodeBuilder.CLASS_INIT;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -42,10 +44,10 @@ class FunctionManager {
private final Set<String> usedClasses = new LinkedHashSet<>(); private final Set<String> 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() { void prepareFinish() {
isFinish = true; isFinish = true;
@ -108,8 +110,8 @@ class FunctionManager {
* @param importAnannotation * @param importAnannotation
* the annotation of the import * the annotation of the import
*/ */
void markAsImport( @Nonnull FunctionName name, Map<String,Object> importAnannotation ) { void markAsImport( @Nonnull FunctionName name, Map<String, Object> importAnannotation ) {
markAsImport( name, (key) -> importAnannotation.get( key ) ); markAsImport( name, ( key ) -> importAnannotation.get( key ) );
} }
/** /**
@ -121,7 +123,7 @@ class FunctionManager {
* @param importAnannotation * @param importAnannotation
* the annotation of the import * the annotation of the import
*/ */
void markAsImport( @Nonnull FunctionName name, Function<String,Object> importAnannotation ) { void markAsImport( @Nonnull FunctionName name, Function<String, Object> importAnannotation ) {
getOrCreate( name ).importAnannotation = importAnannotation; getOrCreate( name ).importAnannotation = importAnannotation;
} }
@ -260,7 +262,7 @@ class FunctionManager {
*/ */
@Nonnull @Nonnull
Iterator<FunctionName> getWriteLaterClinit() { Iterator<FunctionName> getWriteLaterClinit() {
return iterator( entry -> entry.getKey().methodName.equals( "<clinit>" ) && 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 * get a iterator for function names
* @param filter the filter *
* @param filter
* the filter
* @return the iterator * @return the iterator
*/ */
@Nonnull @Nonnull
@ -448,7 +452,7 @@ class FunctionManager {
* *
* @param name * @param name
* the name * the name
* @return the index in the itable * @return the index in the itable
*/ */
int getITableIndex( @Nonnull FunctionName name ) { int getITableIndex( @Nonnull FunctionName name ) {
return getOrCreate( name ).itableIdx; return getOrCreate( name ).itableIdx;
@ -458,7 +462,7 @@ 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;

View File

@ -85,13 +85,13 @@ class JavaMethodWasmCodeBuilder extends WasmCodeBuilder {
reset( code.getLocalVariableTable(), method, null ); reset( code.getLocalVariableTable(), method, null );
branchManager.reset( code ); branchManager.reset( code );
if( "<clinit>".equals( method.getName() ) ) { if( CLASS_INIT.equals( method.getName() ) ) {
// Add a hook to run the class/static constructor only once // Add a hook to run the class/static constructor only once
FunctionName name = new FunctionName( method.getClassName(), "<clisinit>", "" ); FunctionName name = new FunctionName( method.getClassName(), "<class_isInit>", "" );
addGlobalInstruction( true, name, ValueType.i32, -1, -1 ); addGlobalInstruction( true, name, ValueType.i32, null, -1, -1 );
addBlockInstruction( WasmBlockOperator.BR_IF, 0, -1, -1 ); addBlockInstruction( WasmBlockOperator.BR_IF, 0, -1, -1 );
addConstInstruction( 1, ValueType.i32, -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(); byteCode = code.getByteCode();

View File

@ -15,6 +15,8 @@
*/ */
package de.inetsoftware.jwebassembly.module; package de.inetsoftware.jwebassembly.module;
import static de.inetsoftware.jwebassembly.module.WasmCodeBuilder.CLASS_INIT;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -387,7 +389,7 @@ public class ModuleGenerator {
String className = iterator.next(); String className = iterator.next();
ClassFile classFile = classFileLoader.get( className ); ClassFile classFile = classFileLoader.get( className );
if( classFile != null ) { if( classFile != null ) {
MethodInfo method = classFile.getMethod( "<clinit>", "()V" ); MethodInfo method = classFile.getMethod( CLASS_INIT, "()V" );
if( method != null ) { if( method != null ) {
functions.markAsNeeded( new FunctionName( method ), false ); functions.markAsNeeded( new FunctionName( method ), false );
} }

View File

@ -16,6 +16,8 @@
*/ */
package de.inetsoftware.jwebassembly.module; package de.inetsoftware.jwebassembly.module;
import static de.inetsoftware.jwebassembly.module.WasmCodeBuilder.CONSTRUCTOR;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
@ -58,38 +60,39 @@ import de.inetsoftware.jwebassembly.watparser.WatParser;
*/ */
public class TypeManager { public class TypeManager {
/** name of virtual function table, start with a point for an invalid Java identifier */ /** name of virtual function table, start with a point for an invalid Java identifier */
static final String FIELD_VTABLE = ".vtable"; static final String FIELD_VTABLE = ".vtable";
/** /**
* Name of field with system hash code, start with a point for an invalid Java identifier. * 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. * 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. * 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. * 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. * 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: * The reserved position on start of the vtable:
@ -97,73 +100,74 @@ public class TypeManager {
* <li>offset of instanceof list * <li>offset of instanceof list
* <li>offset of class name idx in the string constant table * <li>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 * Type id of primitive class
*/ */
public static final int BOOLEAN = 0; public static final int BOOLEAN = 0;
/** /**
* Type id of primitive class * Type id of primitive class
*/ */
public static final int BYTE = 1; public static final int BYTE = 1;
/** /**
* Type id of primitive class * Type id of primitive class
*/ */
public static final int CHAR = 2; public static final int CHAR = 2;
/** /**
* Type id of primitive class * Type id of primitive class
*/ */
public static final int DOUBLE = 3; public static final int DOUBLE = 3;
/** /**
* Type id of primitive class * Type id of primitive class
*/ */
public static final int FLOAT = 4; public static final int FLOAT = 4;
/** /**
* Type id of primitive class * Type id of primitive class
*/ */
public static final int INT = 5; public static final int INT = 5;
/** /**
* Type id of primitive class * Type id of primitive class
*/ */
public static final int LONG = 6; public static final int LONG = 6;
/** /**
* Type id of primitive class * Type id of primitive class
*/ */
public static final int SHORT = 7; public static final int SHORT = 7;
/** /**
* Type id of primitive class * 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. * the list of primitive types. The order is important and must correlate with getPrimitiveClass.
* *
* @see ReplacementForClass#getPrimitiveClass(String) * @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<Object, StructType> structTypes = new LinkedHashMap<>(); private final Map<Object, StructType> structTypes = new LinkedHashMap<>();
private final Map<BlockType, BlockType> blockTypes = new LinkedHashMap<>(); private final Map<BlockType, BlockType> 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; private ClassFileLoader classFileLoader;
@ -179,7 +183,9 @@ public class TypeManager {
/** /**
* Initialize the type manager * Initialize the type manager
* @param classFileLoader for loading the class files *
* @param classFileLoader
* for loading the class files
*/ */
void init( ClassFileLoader classFileLoader ) { void init( ClassFileLoader classFileLoader ) {
this.classFileLoader = classFileLoader; this.classFileLoader = classFileLoader;
@ -260,6 +266,7 @@ public class TypeManager {
/** /**
* Get the function name to get a constant class. * Get the function name to get a constant class.
*
* @return the function * @return the function
*/ */
@Nonnull @Nonnull
@ -424,8 +431,8 @@ public class TypeManager {
} }
/** /**
* Create the FunctionName for a virtual call. The function has 2 parameters (THIS, * Create the FunctionName for a virtual call. The function has 2 parameters (THIS, virtualfunctionIndex) and
* virtualfunctionIndex) and returns the index of the function. * returns the index of the function.
* *
* @return the name * @return the name
*/ */
@ -453,7 +460,7 @@ public class TypeManager {
static int callInterface( OBJECT THIS, int classIndex, int virtualfunctionIndex ) { static int callInterface( OBJECT THIS, int classIndex, int virtualfunctionIndex ) {
int table = THIS.vtable; int table = THIS.vtable;
table += i32_load[table]; table += i32_load[table];
do { do {
int nextClass = i32_load[table]; int nextClass = i32_load[table];
if( nextClass == classIndex ) { if( nextClass == classIndex ) {
@ -476,8 +483,7 @@ public class TypeManager {
+ "local.set 3 " // save $table, the itable start location + "local.set 3 " // save $table, the itable start location
+ "loop" // + "loop" //
+ " local.get 3" // get $table + " local.get 3" // get $table
+ " i32.load offset=0 align=4" + " i32.load offset=0 align=4" + " local.tee 4" // save $nextClass
+ " local.tee 4" // save $nextClass
+ " local.get 1" // get $classIndex + " local.get 1" // get $classIndex
+ " i32.eq" // + " i32.eq" //
+ " if" // $nextClass == $classIndex + " if" // $nextClass == $classIndex
@ -587,30 +593,30 @@ public class TypeManager {
*/ */
public static class StructType implements AnyType { 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<String> neededFields = new HashSet<>(); private HashSet<String> neededFields = new HashSet<>();
private List<NamedStorageType> fields; private List<NamedStorageType> fields;
private List<FunctionName> vtable; private List<FunctionName> vtable;
private Set<StructType> instanceOFs; private Set<StructType> instanceOFs;
private Map<StructType,List<FunctionName>> interfaceMethods; private Map<StructType, List<FunctionName>> interfaceMethods;
/** /**
* The offset to the vtable in the data section. * The offset to the vtable in the data section.
*/ */
private int vtableOffset; private int vtableOffset;
/** /**
* Create a reference to type * Create a reference to type
@ -770,7 +776,7 @@ public class TypeManager {
// calculate the vtable (the function indexes of the virtual methods) // calculate the vtable (the function indexes of the virtual methods)
for( MethodInfo method : classFile.getMethods() ) { for( MethodInfo method : classFile.getMethods() ) {
if( method.isStatic() || "<init>".equals( method.getName() ) ) { if( method.isStatic() || CONSTRUCTOR.equals( method.getName() ) ) {
continue; continue;
} }
FunctionName funcName = new FunctionName( method ); FunctionName funcName = new FunctionName( method );
@ -1017,6 +1023,7 @@ public class TypeManager {
/** /**
* Get kind of the StructType * Get kind of the StructType
*
* @return the type kind * @return the type kind
*/ */
public StructTypeKind getKind() { public StructTypeKind getKind() {
@ -1025,6 +1032,7 @@ public class TypeManager {
/** /**
* Get the name of the Java type * Get the name of the Java type
*
* @return the name * @return the name
*/ */
public String getName() { public String getName() {
@ -1051,6 +1059,7 @@ public class TypeManager {
/** /**
* Get the fields of this struct * Get the fields of this struct
*
* @return the fields * @return the fields
*/ */
public List<NamedStorageType> getFields() { public List<NamedStorageType> getFields() {
@ -1189,7 +1198,7 @@ public class TypeManager {
super( name, StructTypeKind.lambda, manager ); super( name, StructTypeKind.lambda, manager );
this.paramFields = new ArrayList<>( params.size() ); this.paramFields = new ArrayList<>( params.size() );
for( int i = 0; i < params.size(); i++ ) { 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.interfaceType = interfaceType;
this.interfaceMethodName = interfaceMethodName; this.interfaceMethodName = interfaceMethodName;
@ -1199,10 +1208,12 @@ public class TypeManager {
protected boolean hasWasmCode() { protected boolean hasWasmCode() {
return true; return true;
} }
@Override @Override
protected boolean istStatic() { protected boolean istStatic() {
return false; return false;
} }
@Override @Override
protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) { protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) {
WasmCodeBuilder codebuilder = watParser; WasmCodeBuilder codebuilder = watParser;
@ -1290,12 +1301,15 @@ public class TypeManager {
@Nonnull @Nonnull
private final List<AnyType> params; private final List<AnyType> params;
@Nonnull @Nonnull
private final List<AnyType> results; private final List<AnyType> results;
private int code;
private String name;
public BlockType(List<AnyType> params, List<AnyType> results) { private int code;
private String name;
public BlockType( List<AnyType> params, List<AnyType> results ) {
this.params = params; this.params = params;
this.results = results; this.results = results;
} }

View File

@ -57,6 +57,12 @@ import de.inetsoftware.jwebassembly.wasm.WasmBlockOperator;
*/ */
public abstract class WasmCodeBuilder { public abstract class WasmCodeBuilder {
/** Java method name of of constructor */
static final String CONSTRUCTOR = "<init>";
/** Java method name of static constructor or initialization method */
static final String CLASS_INIT = "<clinit>";
private final LocaleVariableManager localVariables; private final LocaleVariableManager localVariables;
private final List<WasmInstruction> instructions; private final List<WasmInstruction> instructions;
@ -497,7 +503,16 @@ public abstract class WasmCodeBuilder {
protected void addGlobalInstruction( boolean load, Member ref, int javaCodePos, int lineNumber ) { protected void addGlobalInstruction( boolean load, Member ref, int javaCodePos, int lineNumber ) {
FunctionName name = new FunctionName( ref ); FunctionName name = new FunctionName( ref );
AnyType type = new ValueTypeParser( ref.getType(), types ).next(); 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 * @param lineNumber
* the line number in the Java source code * the line number in the Java source code
*/ */
protected void addGlobalInstruction( boolean load, FunctionName name, AnyType type, int javaCodePos, int lineNumber ) { protected void addGlobalInstruction( boolean load, FunctionName name, AnyType type, FunctionName clinit, int javaCodePos, int lineNumber ) {
instructions.add( new WasmGlobalInstruction( load, name, type, javaCodePos, lineNumber ) ); instructions.add( new WasmGlobalInstruction( load, name, type, clinit, javaCodePos, lineNumber ) );
functions.markClassAsUsed( name.className ); functions.markClassAsUsed( name.className );
} }
@ -655,7 +670,7 @@ public abstract class WasmCodeBuilder {
name = functions.markAsNeeded( name, needThisParameter ); name = functions.markAsNeeded( name, needThisParameter );
WasmCallInstruction instruction = new WasmCallInstruction( name, javaCodePos, lineNumber, types, needThisParameter ); WasmCallInstruction instruction = new WasmCallInstruction( name, javaCodePos, lineNumber, types, needThisParameter );
if( "<init>".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 // check if there a factory for the constructor in JavaScript then we need to do some more complex patching
Function<String, Object> importAnannotation = functions.getImportAnannotation( name ); Function<String, Object> importAnannotation = functions.getImportAnannotation( name );
FunctionName factoryName = null; FunctionName factoryName = null;
@ -667,7 +682,7 @@ public abstract class WasmCodeBuilder {
factoryName = new ImportSyntheticFunctionName( "String", "init", signature, importAnannotation ); factoryName = new ImportSyntheticFunctionName( "String", "init", signature, importAnannotation );
} else { } else {
MethodInfo replace = functions.replace( name, null ); MethodInfo replace = functions.replace( name, null );
if( replace != null && !"<init>".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 // the constructor was replaced with a factory method. Typical this method called then a JavaScript replacement
factoryName = new FunctionName( replace ); factoryName = new FunctionName( replace );
} }

View File

@ -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"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with 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 java.io.IOException;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import de.inetsoftware.jwebassembly.wasm.AnyType; import de.inetsoftware.jwebassembly.wasm.AnyType;
@ -30,11 +31,13 @@ import de.inetsoftware.jwebassembly.wasm.AnyType;
*/ */
class WasmGlobalInstruction extends WasmInstruction { class WasmGlobalInstruction extends WasmInstruction {
private boolean load; private boolean load;
private FunctionName name; private FunctionName name;
private AnyType type; private AnyType type;
private FunctionName clinit;
/** /**
* Create an instance of a load/store instruction * Create an instance of a load/store instruction
@ -45,16 +48,19 @@ class WasmGlobalInstruction extends WasmInstruction {
* the name of the static field * the name of the static field
* @param type * @param type
* the type of the static field * 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 * @param javaCodePos
* the code position/offset in the Java method * the code position/offset in the Java method
* @param lineNumber * @param lineNumber
* the line number in the Java source code * 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 ); super( javaCodePos, lineNumber );
this.load = load; this.load = load;
this.name = name; this.name = name;
this.type = type; this.type = type;
this.clinit = clinit;
} }
/** /**
@ -80,6 +86,9 @@ class WasmGlobalInstruction extends WasmInstruction {
*/ */
@Override @Override
public void writeTo( @Nonnull ModuleWriter writer ) throws IOException { public void writeTo( @Nonnull ModuleWriter writer ) throws IOException {
if( clinit != null ) {
writer.writeFunctionCall( clinit, clinit.signatureName );
}
writer.writeGlobalAccess( load, name, type ); writer.writeGlobalAccess( load, name, type );
} }