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;
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<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() {
isFinish = true;
@ -108,8 +110,8 @@ class FunctionManager {
* @param importAnannotation
* the annotation of the import
*/
void markAsImport( @Nonnull FunctionName name, Map<String,Object> importAnannotation ) {
markAsImport( name, (key) -> importAnannotation.get( key ) );
void markAsImport( @Nonnull FunctionName name, Map<String, Object> 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<String,Object> importAnannotation ) {
void markAsImport( @Nonnull FunctionName name, Function<String, Object> importAnannotation ) {
getOrCreate( name ).importAnannotation = importAnannotation;
}
@ -260,7 +262,7 @@ class FunctionManager {
*/
@Nonnull
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
* @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;

View File

@ -85,13 +85,13 @@ class JavaMethodWasmCodeBuilder extends WasmCodeBuilder {
reset( code.getLocalVariableTable(), method, null );
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
FunctionName name = new FunctionName( method.getClassName(), "<clisinit>", "" );
addGlobalInstruction( true, name, ValueType.i32, -1, -1 );
FunctionName name = new FunctionName( method.getClassName(), "<class_isInit>", "" );
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();

View File

@ -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( "<clinit>", "()V" );
MethodInfo method = classFile.getMethod( CLASS_INIT, "()V" );
if( method != null ) {
functions.markAsNeeded( new FunctionName( method ), false );
}

View File

@ -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 {
* <li>offset of instanceof list
* <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
*/
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<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;
@ -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<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.
*/
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() || "<init>".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<NamedStorageType> 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<AnyType> params;
@Nonnull
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.results = results;
}

View File

@ -57,6 +57,12 @@ import de.inetsoftware.jwebassembly.wasm.WasmBlockOperator;
*/
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 List<WasmInstruction> 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( "<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
Function<String, Object> 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 && !"<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
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");
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 );
}