Fix the memory offset of strings with a dynamic synthetic function.

This commit is contained in:
Volker Berlin 2019-11-24 14:44:56 +01:00
parent f61cebd285
commit 8668d71b6c
7 changed files with 143 additions and 65 deletions

View File

@ -20,6 +20,7 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -184,21 +185,21 @@ public class FunctionManager {
} }
/** /**
* Get the first FunctionName that is required but was not written. * Get all FunctionName that is required but was not written.
* *
* @return the FunctionName or null * @return the FunctionName or null
*/ */
@Nullable @Nullable
FunctionName nextWriteLater() { Iterator<FunctionName> getWriteLater() {
for( Entry<FunctionName, FunctionState> entry : states.entrySet() ) { return iterator( entry -> {
switch( entry.getValue().state ) { switch( entry.getValue().state ) {
case Needed: case Needed:
case Scanned: case Scanned:
return entry.getKey(); return true;
default: default:
return false;
} }
} } );
return null;
} }
/** /**
@ -207,16 +208,25 @@ public class FunctionManager {
* @return an iterator * @return an iterator
*/ */
Iterator<FunctionName> getNeededFunctions() { Iterator<FunctionName> getNeededFunctions() {
return states.entrySet().stream().filter( entry -> { return iterator( entry -> {
FunctionState state = entry.getValue(); FunctionState state = entry.getValue();
switch( state.state ) { switch( state.state ) {
case Needed: case Needed:
case Scanned: case Scanned:
return true; return true;
default: default:
return false;
} }
return false; } );
} ).map( entry -> entry.getKey() ).iterator(); }
/**
* get a iterator for function names
* @param filter the filter
* @return the iterator
*/
private Iterator<FunctionName> iterator( Predicate<Entry<FunctionName, FunctionState>> filter ) {
return states.entrySet().stream().filter( filter ).map( entry -> entry.getKey() ).iterator();
} }
/** /**

View File

@ -137,7 +137,8 @@ public class FunctionName {
if( obj == null ) { if( obj == null ) {
return false; return false;
} }
if( getClass() != obj.getClass() ) { // synthetic functions should be replace/equals to real functions.
if( !(obj instanceof FunctionName) ) {
return false; return false;
} }
FunctionName other = (FunctionName)obj; FunctionName other = (FunctionName)obj;

View File

@ -292,41 +292,40 @@ public class ModuleGenerator {
* if any I/O error occur * if any I/O error occur
*/ */
public void finish() throws IOException { public void finish() throws IOException {
FunctionName next; for( Iterator<FunctionName> it = functions.getWriteLater(); it.hasNext(); ) {
while( (next = functions.nextWriteLater()) != null ) { FunctionName next = it.next();
ClassFile classFile = ClassFile.get( next.className, libraries ); if( next instanceof SyntheticFunctionName ) {
if( classFile == null ) { writeMethodImpl( next, true, ((SyntheticFunctionName)next).getCodeBuilder( watParser ) );
if( next instanceof SyntheticFunctionName ) {
writeMethodImpl( next, true, ((SyntheticFunctionName)next).getCodeBuilder( watParser ) );
} else {
throw new WasmException( "Missing function: " + next.signatureName, -1 );
}
} else { } else {
iterateMethods( classFile, method -> { ClassFile classFile = ClassFile.get( next.className, libraries );
try { if( classFile == null ) {
FunctionName name; throw new WasmException( "Missing function: " + next.signatureName, -1 );
Map<String, Object> wat = method.getAnnotation( JWebAssembly.TEXTCODE_ANNOTATION ); } else {
if( wat != null ) { MethodInfo method = classFile.getMethod( next.methodName, next.signature );
String signature = (String)wat.get( "signature" ); if( method != null ) {
if( signature == null ) { try {
signature = method.getType(); Map<String, Object> wat = method.getAnnotation( JWebAssembly.TEXTCODE_ANNOTATION );
if( wat != null ) {
String signature = (String)wat.get( "signature" );
if( signature == null ) {
signature = method.getType();
}
next = new FunctionName( method, signature );
} else {
method = functions.replace( next, method );
} }
name = new FunctionName( method, signature ); if( functions.needToWrite( next ) ) {
} else { writeMethod( next, method );
name = new FunctionName( method ); }
method = functions.replace( name, method ); } catch (IOException ioex){
throw WasmException.create( ioex, sourceFile, className, -1 );
} }
if( functions.needToWrite( name ) ) { } else {
writeMethod( name, method ); if( functions.needToWrite( next ) ) {
throw new WasmException( "Missing function: " + next.signatureName, -1 );
} }
} catch (IOException ioex){
throw WasmException.create( ioex, sourceFile, className, -1 );
} }
} ); }
}
if( functions.needToWrite( next ) ) {
throw new WasmException( "Missing function: " + next.signatureName, -1 );
} }
} }
javaScript.finish(); javaScript.finish();

View File

@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import javax.annotation.Nonnegative; import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import de.inetsoftware.jwebassembly.api.annotation.WasmTextCode; import de.inetsoftware.jwebassembly.api.annotation.WasmTextCode;
import de.inetsoftware.jwebassembly.wasm.ValueType; import de.inetsoftware.jwebassembly.wasm.ValueType;
@ -34,12 +35,51 @@ import de.inetsoftware.jwebassembly.wasm.ValueType;
*/ */
class StringManager extends LinkedHashMap<String, Integer> { class StringManager extends LinkedHashMap<String, Integer> {
private FunctionManager functions; /**
* Signature of method stringConstant.
*
* @see #stringConstant(int)
*/
private static final FunctionName STRING_CONSTANT_FUNCTION =
new FunctionName( "de/inetsoftware/jwebassembly/module/StringManager.stringConstant(I)Ljava/lang/String;" );
private FunctionManager functions;
private int stringMemoryOffset = -1;
/**
* Initialize the string manager.
*
* @param functions
* the function manager
*/
void init( FunctionManager functions ) { void init( FunctionManager functions ) {
this.functions = functions; this.functions = functions;
} }
/**
* Get the function name object for the {@link #stringConstant(int)}.
*
* @see #stringConstant(int)
* @return the name
*/
@Nonnull
FunctionName getStringConstantFunction() {
if( stringMemoryOffset < 0 ) {
// register the function stringsMemoryOffset() as synthetic function
stringMemoryOffset = 0;
FunctionName offsetFunction =
new WatCodeSyntheticFunctionName( "de/inetsoftware/jwebassembly/module/StringManager", "stringsMemoryOffset", "()I", "", null, ValueType.i32 ) {
protected String getCode() {
return "i32.const " + stringMemoryOffset;
}
};
functions.markAsNeeded( offsetFunction, true );
}
return STRING_CONSTANT_FUNCTION;
}
/** /**
* Finish the prepare. Now no new strings should be added. * Finish the prepare. Now no new strings should be added.
* *
@ -82,10 +122,9 @@ class StringManager extends LinkedHashMap<String, Integer> {
ByteArrayOutputStream stringOut = new ByteArrayOutputStream(); ByteArrayOutputStream stringOut = new ByteArrayOutputStream();
ByteArrayOutputStream dataStream = writer.dataStream; ByteArrayOutputStream dataStream = writer.dataStream;
int offset = dataStream.size(); // save the offset of the string data for later code inlining
WatCodeSyntheticFunctionName stringMemoryOffset = new WatCodeSyntheticFunctionName( "de/inetsoftware/jwebassembly/module/StringManager", "stringsMemoryOffset", "()I", "i32.const " + offset, null, ValueType.i32 ); stringMemoryOffset = dataStream.size();
// functions.markAsNeeded( stringMemoryOffset, true ); int offset = stringMemoryOffset + size * 4;
offset += size * 4;
for( String str : this.keySet() ) { for( String str : this.keySet() ) {
// write the position where the string starts in the data section // write the position where the string starts in the data section
@ -129,18 +168,13 @@ class StringManager extends LinkedHashMap<String, Integer> {
} }
/** /**
* Signature of method stringConstant. * WASM code<p>
* @see #stringConstant(int) * Get a constant string from the table.
*/
static final String STRING_CONSTANT_SIGNATURE = "de/inetsoftware/jwebassembly/module/StringManager.stringConstant(I)Ljava/lang/String;";
/**
* WASM code
* Get a constant string from the table.
* *
* @param strIdx the id/index of the string. * @param strIdx
* the id/index of the string.
* @return the string * @return the string
* @see #STRING_CONSTANT_SIGNATURE * @see #STRING_CONSTANT_FUNCTION
*/ */
private static String stringConstant( int strIdx ) { private static String stringConstant( int strIdx ) {
String str = getStringFromTable( strIdx ); String str = getStringFromTable( strIdx );
@ -148,25 +182,30 @@ class StringManager extends LinkedHashMap<String, Integer> {
return str; return str;
} }
// read the compact string length
int offset = getIntFromMemory( strIdx * 4 + stringsMemoryOffset() ); int offset = getIntFromMemory( strIdx * 4 + stringsMemoryOffset() );
int length = 0; int length = 0;
int b; int b;
int shift = 0;
do { do {
b = getUnsignedByteFromMemory( offset++ ); b = getUnsignedByteFromMemory( offset++ );
length = (length << 7) + (b & 0x7F); length += (b & 0x7F) << shift;
shift += 7;
} while( b >= 0x80 ); } while( b >= 0x80 );
// copy the bytes from the data section
byte[] bytes = new byte[length]; byte[] bytes = new byte[length];
for( int i = 0; i < length; i++ ) { for( int i = 0; i < length; i++ ) {
bytes[i] = getUnsignedByteFromMemory( i + offset ); bytes[i] = getUnsignedByteFromMemory( i + offset );
} }
str = new String( bytes ); str = new String( bytes );
// save the string for future use
setStringIntoTable( strIdx, str ); setStringIntoTable( strIdx, str );
return str; return str;
} }
/** /**
* WASM code * WASM code<p>
* Get a string from the string table. Should be inlined from the optimizer. * Get a string from the string table. Should be inlined from the optimizer.
* *
* @param strIdx * @param strIdx
@ -179,7 +218,7 @@ class StringManager extends LinkedHashMap<String, Integer> {
private static native String getStringFromTable( int strIdx ); private static native String getStringFromTable( int strIdx );
/** /**
* WASM code * WASM code<p>
* Set a string from the string table. Should be inlined from the optimizer. * Set a string from the string table. Should be inlined from the optimizer.
* *
* @param strIdx * @param strIdx
@ -193,12 +232,18 @@ class StringManager extends LinkedHashMap<String, Integer> {
"return" ) "return" )
private static native void setStringIntoTable( int strIdx, String str ); private static native void setStringIntoTable( int strIdx, String str );
/**
* WASM code<p>
* Placeholder for a synthetic function. Should be inlined from the optimizer.
* @return the memory offset of the string data in the element section
*/
//TODO the annotation can be removed if ModuleGenerator.prepareFunctions() can detect Synthetic functions correctly
@WasmTextCode( "i32.const 0" ) @WasmTextCode( "i32.const 0" )
private static native int stringsMemoryOffset(); private static native int stringsMemoryOffset();
/** /**
* WASM code * WASM code<p>
* Load an i32 from memory. The offset must be aligned. * Load an i32 from memory. The offset must be aligned. Should be inlined from the optimizer.
* *
* @param pos * @param pos
* the memory position * the memory position
@ -210,8 +255,8 @@ class StringManager extends LinkedHashMap<String, Integer> {
private static native int getIntFromMemory( int pos ); private static native int getIntFromMemory( int pos );
/** /**
* WASM code * WASM code<p>
* Load a byte from the memory. * Load a byte from the memory. Should be inlined from the optimizer.
* *
* @param pos * @param pos
* the memory position * the memory position

View File

@ -357,7 +357,7 @@ public abstract class WasmCodeBuilder {
if( id == null ) { if( id == null ) {
strings.put( (String)value, id = strings.size() ); strings.put( (String)value, id = strings.size() );
} }
FunctionName name = new FunctionName( StringManager.STRING_CONSTANT_SIGNATURE ); FunctionName name = strings.getStringConstantFunction();
instructions.add( new WasmConstInstruction( id, ValueType.i32, javaCodePos, lineNumber ) ); instructions.add( new WasmConstInstruction( id, ValueType.i32, javaCodePos, lineNumber ) );
addCallInstruction( name, javaCodePos, lineNumber ); addCallInstruction( name, javaCodePos, lineNumber );
} else { } else {

View File

@ -16,6 +16,8 @@
*/ */
package de.inetsoftware.jwebassembly.module; package de.inetsoftware.jwebassembly.module;
import javax.annotation.Nonnull;
import de.inetsoftware.jwebassembly.wasm.AnyType; import de.inetsoftware.jwebassembly.wasm.AnyType;
import de.inetsoftware.jwebassembly.watparser.WatParser; import de.inetsoftware.jwebassembly.watparser.WatParser;
@ -57,11 +59,21 @@ class WatCodeSyntheticFunctionName extends ArraySyntheticFunctionName {
* @param signatureTypes * @param signatureTypes
* the method signature, first the parameters, then null and the the return types * the method signature, first the parameters, then null and the the return types
*/ */
public WatCodeSyntheticFunctionName( String className, String name, String signature, String code, AnyType... signatureTypes ) { public WatCodeSyntheticFunctionName( String className, String name, String signature, @Nonnull String code, AnyType... signatureTypes ) {
super( className, name, signature, signatureTypes ); super( className, name, signature, signatureTypes );
this.code = code; this.code = code;
} }
/**
* Get Wat code, can be overridden.
*
* @return the code
*/
@Nonnull
protected String getCode() {
return code;
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -75,7 +87,7 @@ class WatCodeSyntheticFunctionName extends ArraySyntheticFunctionName {
*/ */
@Override @Override
protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) { protected WasmCodeBuilder getCodeBuilder( WatParser watParser ) {
watParser.parse( code, null, -1 ); watParser.parse( getCode(), null, -1 );
return watParser; return watParser;
} }
} }

View File

@ -42,6 +42,7 @@ public class StringOperations extends AbstractBaseTest {
for( ScriptEngine script : ScriptEngine.testEngines() ) { for( ScriptEngine script : ScriptEngine.testEngines() ) {
addParam( list, script, "newFromChars" ); addParam( list, script, "newFromChars" );
addParam( list, script, "newFromBytes" ); addParam( list, script, "newFromBytes" );
addParam( list, script, "constant" );
} }
rule.setTestParameters( list ); rule.setTestParameters( list );
return list; return list;
@ -60,5 +61,15 @@ public class StringOperations extends AbstractBaseTest {
byte[] bytes = {(byte)0xC3,(byte)0xA4}; byte[] bytes = {(byte)0xC3,(byte)0xA4};
return new String( bytes ); return new String( bytes );
} }
@Export
static String constant() {
// string larger as 128 bytes
String constant = "1234567890 äöüäöüß " // umlaute
+ "𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡 𝒥𝒶𝓋𝒶𝓈𝒸𝓇𝒾𝓅𝓉 " // surrogate chars
+ "abcdefghijklmnopqrstuvwxyz";
return constant;
}
} }
} }