From 946e9119826a57d5137bf25962d834b3c142aa8e Mon Sep 17 00:00:00 2001 From: Volker Berlin Date: Sat, 14 Mar 2020 19:21:37 +0100 Subject: [PATCH] implementation of a a replacement for java.lang.Class --- .../jwebassembly/module/ModuleGenerator.java | 8 +- .../module/ReplacementForClass.java | 151 ++++++++++++++++++ .../jwebassembly/module/StringManager.java | 2 +- .../jwebassembly/module/TypeManager.java | 68 +++++++- .../module/WasmStructInstruction.java | 2 +- 5 files changed, 223 insertions(+), 8 deletions(-) create mode 100644 src/de/inetsoftware/jwebassembly/module/ReplacementForClass.java diff --git a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java index 65a8e8a..2baf348 100644 --- a/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java +++ b/src/de/inetsoftware/jwebassembly/module/ModuleGenerator.java @@ -472,7 +472,11 @@ public class ModuleGenerator { javaCodeBuilder.buildCode( code, method ); return javaCodeBuilder; } else { - throw new WasmException( "Abstract or native method can not be used: " + new FunctionName( method ).signatureName, -1 ); + FunctionName name = new FunctionName( method ); + if( "de/inetsoftware/jwebassembly/module/ReplacementForClass.typeTableMemoryOffset()I".equals( name.signatureName ) ) { + return types.getTypeTableMemoryOffsetFunctionName().getCodeBuilder( watParser ); + } + throw new WasmException( "Abstract or native method can not be used: " + name.signatureName, -1 ); } } catch( Exception ioex ) { int lineNumber = code == null ? -1 : code.getFirstLineNr(); @@ -533,7 +537,7 @@ public class ModuleGenerator { StructType structType = instr.getStructType(); List list = structType.getFields(); for( NamedStorageType storageType : list ) { - if( TypeManager.VTABLE == storageType.getName() ) { + if( TypeManager.FIELD_VTABLE == storageType.getName() ) { writer.writeConst( structType.getVTable(), ValueType.i32 ); } else { writer.writeDefaultValue( storageType.getType() ); diff --git a/src/de/inetsoftware/jwebassembly/module/ReplacementForClass.java b/src/de/inetsoftware/jwebassembly/module/ReplacementForClass.java new file mode 100644 index 0000000..acec700 --- /dev/null +++ b/src/de/inetsoftware/jwebassembly/module/ReplacementForClass.java @@ -0,0 +1,151 @@ +/* + 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 de.inetsoftware.jwebassembly.api.annotation.Replace; +import de.inetsoftware.jwebassembly.api.annotation.WasmTextCode; + +/** + * Replacement for java.lang.Class + * + * @author Volker Berlin + */ +@Replace( "java/lang/Class" ) +class ReplacementForClass { + + private final int vtable; + + /** + * Create a instance + * + * @param vtable + * the pointer in the memory for the class/type description. + */ + private ReplacementForClass( int vtable ) { + this.vtable = vtable; + } + + /** + * Replacement for {@link Class#getName()} + * + * @return the name + */ + String getName() { + return StringManager.stringConstant( getIntFromMemory( vtable + TypeManager.TYPE_DESCRIPTION_TYPE_NAME ) ); + } + + /** + * Replacement for {@link Object#getClass()} + * + * @param obj + * the instance + * @return the class + */ + @WasmTextCode( "local.get 0 " // THIS + + "struct.get java/lang/Object .vtable " // vtable is on index 0 + + "call $de/inetsoftware/jwebassembly/module/ReplacementForClass.classConstant(I)Lde/inetsoftware/jwebassembly/module/ReplacementForClass; " // + + "return " // + ) + @Replace( "java/lang/Object.getClass()Ljava/lang/Class;" ) + private static native ReplacementForClass getClassObject( Object obj ); + + /** + * WASM code + *

+ * Get a constant Class from the table. + * + * @param classIdx + * the id/index of the Class. + * @return the string + * @see #CLASS_CONSTANT_FUNCTION + */ + private static ReplacementForClass classConstant( int classIdx ) { + ReplacementForClass clazz = getClassFromTable( classIdx ); + if( clazz != null ) { + return clazz; + } + + /* + The memory/data section has the follow content: + ┌──────────────────────────────────┐ + | Type/Class descriptions (vtable) | + ├──────────────────────────────────┤ + | Type/Class table | + ├──────────────────────────────────┤ + | String table | + └──────────────────────────────────┘ + */ + int vtable = getIntFromMemory( classIdx * 4 + typeTableMemoryOffset() ); + clazz = new ReplacementForClass( vtable ); + // save the string for future use + setClassIntoTable( classIdx, clazz ); + return clazz; + } + + /** + * WASM code + *

+ * Get a Class instance from the Class table. Should be inlined from the optimizer. + * + * @param classIdx + * the id/index of the Class. + * @return the string or null if not already set. + */ + @WasmTextCode( "local.get 0 " + // + "table.get 2 " + // table 2 is used for classes + "return" ) + private static native ReplacementForClass getClassFromTable( int classIdx ); + + /** + * WASM code + *

+ * Set a string from the string table. Should be inlined from the optimizer. + * + * @param strIdx + * the id/index of the string. + * @param str + * the string + */ + @WasmTextCode( "local.get 0 " + // + "local.get 1 " + // + "table.set 2 " + // + "return" ) + private static native void setClassIntoTable( int strIdx, ReplacementForClass clazz ); + + /** + * WASM code + *

+ * Placeholder for a synthetic function. Should be inlined from the optimizer. + * + * @return the memory offset of the string data in the element section + */ + private static native int typeTableMemoryOffset(); + + /** + * WASM code + *

+ * Load an i32 from memory. The offset must be aligned. Should be inlined from the optimizer. + * + * @param pos + * the memory position + * @return the value from the memory + */ + @WasmTextCode( "local.get 0 " + // + "i32.load offset=0 align=4 " + // + "return" ) + private static native int getIntFromMemory( int pos ); +} diff --git a/src/de/inetsoftware/jwebassembly/module/StringManager.java b/src/de/inetsoftware/jwebassembly/module/StringManager.java index 8bc69a0..b412589 100644 --- a/src/de/inetsoftware/jwebassembly/module/StringManager.java +++ b/src/de/inetsoftware/jwebassembly/module/StringManager.java @@ -190,7 +190,7 @@ public class StringManager extends LinkedHashMap { * @return the string * @see #STRING_CONSTANT_FUNCTION */ - private static String stringConstant( int strIdx ) { + static String stringConstant( int strIdx ) { String str = getStringFromTable( strIdx ); if( str != null ) { return str; diff --git a/src/de/inetsoftware/jwebassembly/module/TypeManager.java b/src/de/inetsoftware/jwebassembly/module/TypeManager.java index 82c82d1..78f04af 100644 --- a/src/de/inetsoftware/jwebassembly/module/TypeManager.java +++ b/src/de/inetsoftware/jwebassembly/module/TypeManager.java @@ -50,12 +50,27 @@ import de.inetsoftware.jwebassembly.wasm.ValueType; public class TypeManager { /** name of virtual function table, start with a point for an invalid Java identifier */ - static final String VTABLE = ".vtable"; + 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 HASHCODE = ".hashcode"; + static final String FIELD_HASHCODE = ".hashcode"; + + /** + * Byte position in the type description that contains the offset to the interfaces. Length 4 bytes. + */ + 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. + */ + static final int TYPE_DESCRIPTION_INSTANCEOF_OFFSET = 4; + + /** + * Byte position in the type description that contains the offset to class name idx. Length 4 bytes. + */ + static final int TYPE_DESCRIPTION_TYPE_NAME = 8; /** * The reserved position on start of the vtable: @@ -72,6 +87,8 @@ public class TypeManager { private final WasmOptions options; + private int typeTableOffset; + /** * Initialize the type manager. * @@ -108,6 +125,29 @@ public class TypeManager { for( StructType type : structTypes.values() ) { type.writeStructType( writer, functions, this, classFileLoader ); } + + // write type table + ByteArrayOutputStream dataStream = writer.dataStream; + typeTableOffset = dataStream.size(); + for( StructType type : structTypes.values() ) { + dataStream.write( type.vtableOffset ); + } + } + + /** + * Create an accessor for typeTableOffset and mark it. + * + * @return the function name + */ + WatCodeSyntheticFunctionName getTypeTableMemoryOffsetFunctionName() { + WatCodeSyntheticFunctionName offsetFunction = + new WatCodeSyntheticFunctionName( "de/inetsoftware/jwebassembly/module/ReplacementForClass", "typeTableMemoryOffset", "()I", "", null, ValueType.i32 ) { + protected String getCode() { + return "i32.const " + typeTableOffset; + } + }; + options.functions.markAsNeeded( offsetFunction ); + return offsetFunction; } /** @@ -352,8 +392,8 @@ public class TypeManager { String superClassName = superClass.getName(); listStructFields( superClassName, functions, types, classFileLoader, allNeededFields ); } else { - fields.add( new NamedStorageType( ValueType.i32, className, VTABLE ) ); - fields.add( new NamedStorageType( ValueType.i32, className, HASHCODE ) ); + fields.add( new NamedStorageType( ValueType.i32, className, FIELD_VTABLE ) ); + fields.add( new NamedStorageType( ValueType.i32, className, FIELD_HASHCODE ) ); } for( FieldInfo field : classFile.getFields() ) { @@ -443,6 +483,23 @@ public class TypeManager { * should never occur */ public void writeToStream( ByteArrayOutputStream dataStream, ToIntFunction getFunctionsID ) throws IOException { + /* + ┌───────────────────────────────────────┐ + | Offset to the interfaces (4 bytes) | + ├───────────────────────────────────────┤ + | Offset to the instanceof (4 bytes) | + ├───────────────────────────────────────┤ + | String id of the class name (4 bytes) | + ├───────────────────────────────────────┤ + | first vtable entry (4 bytes) | + ├───────────────────────────────────────┤ + | ..... | + ├───────────────────────────────────────┤ + | interface calls (itable) | + ├───────────────────────────────────────┤ + | list of implemented interface | + └───────────────────────────────────────┘ + */ this.vtableOffset = dataStream.size(); LittleEndianOutputStream header = new LittleEndianOutputStream( dataStream ); @@ -452,9 +509,11 @@ public class TypeManager { data.writeInt32( functIdx ); } + // header position TYPE_DESCRIPTION_INTERFACE_OFFSET header.writeInt32( data.size() + VTABLE_FIRST_FUNCTION_INDEX * 4 ); // offset of interface calls //TODO interface calls + // header position TYPE_DESCRIPTION_INSTANCEOF_OFFSET header.writeInt32( data.size() + VTABLE_FIRST_FUNCTION_INDEX * 4 ); // offset of instanceeof list data.writeInt32( instanceOFs.size() ); for( StructType type : instanceOFs ) { @@ -462,6 +521,7 @@ public class TypeManager { } int classNameIdx = options.strings.get( getName().replace( '/', '.' ) ); + // header position TYPE_DESCRIPTION_TYPE_NAME header.writeInt32( classNameIdx ); // string id of the className data.writeTo( dataStream ); diff --git a/src/de/inetsoftware/jwebassembly/module/WasmStructInstruction.java b/src/de/inetsoftware/jwebassembly/module/WasmStructInstruction.java index 8e72152..b6d8706 100644 --- a/src/de/inetsoftware/jwebassembly/module/WasmStructInstruction.java +++ b/src/de/inetsoftware/jwebassembly/module/WasmStructInstruction.java @@ -91,7 +91,7 @@ class WasmStructInstruction extends WasmInstruction { } js.append( i ).append( ':' ); NamedStorageType storageType = list.get( i ); - if( TypeManager.VTABLE == storageType.getName() ) { + if( TypeManager.FIELD_VTABLE == storageType.getName() ) { js.append( type.getVTable() ); } else { AnyType fieldType = storageType.getType();