1152 lines
48 KiB
Java

/*
* Copyright 2017 - 2018 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 java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Consumer;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import de.inetsoftware.classparser.ClassFile;
import de.inetsoftware.classparser.Code;
import de.inetsoftware.classparser.CodeInputStream;
import de.inetsoftware.classparser.ConstantPool;
import de.inetsoftware.classparser.ConstantRef;
import de.inetsoftware.classparser.MethodInfo;
import de.inetsoftware.jwebassembly.WasmException;
/**
* Generate the WebAssembly output.
*
* @author Volker Berlin
*/
public class ModuleGenerator {
private final ModuleWriter writer;
private int paramCount;
private ValueType returnType;
private LocaleVariableManager localVariables = new LocaleVariableManager();
private String sourceFile;
private BranchManger branchManager = new BranchManger();
private ValueStackManger stackManager = new ValueStackManger();
/**
* Create a new generator.
*
* @param writer
* the target writer
*/
public ModuleGenerator( @Nonnull ModuleWriter writer ) {
this.writer = writer;
}
/**
* Prepare the content of the class.
*
* @param classFile
* the class file
* @throws WasmException
* if some Java code can't converted
*/
public void prepare( ClassFile classFile ) {
iterateMethods( classFile, m -> prepareMethod( m ) );
}
/**
* Finish the prepare after all classes/methods are prepare. This must be call before we can start with write the
* first method.
*/
public void prepareFinish() {
writer.prepareFinish();
}
/**
* Write the content of the class to the writer.
*
* @param classFile
* the class file
* @throws WasmException
* if some Java code can't converted
*/
public void write( ClassFile classFile ) throws WasmException {
iterateMethods( classFile, m -> writeMethod( m ) );
}
private void iterateMethods( ClassFile classFile, Consumer<MethodInfo> handler ) throws WasmException {
sourceFile = null; // clear previous value for the case an IO exception occur
try {
sourceFile = classFile.getSourceFile();
if( sourceFile == null ) {
sourceFile = classFile.getThisClass().getName();
}
MethodInfo[] methods = classFile.getMethods();
for( MethodInfo method : methods ) {
Code code = method.getCode();
if( method.getName().equals( "<init>" ) && method.getDescription().equals( "()V" )
&& code.isSuperInitReturn( classFile.getSuperClass() ) ) {
continue; //default constructor
}
handler.accept( method );
}
} catch( IOException ioex ) {
throw WasmException.create( ioex, sourceFile, -1 );
}
}
/**
* Prepare the method.
*
* @param method
* the method
* @throws WasmException
* if some Java code can't converted
*/
private void prepareMethod( MethodInfo method ) throws WasmException {
try {
FunctionName name = new FunctionName( method );
Map<String,Object> annotationValues = method.getAnnotation( "org.webassembly.annotation.Import" );
if( annotationValues != null ) {
String impoarModule = (String)annotationValues.get( "module" );
String importName = (String)annotationValues.get( "name" );
writer.prepareImport( name, impoarModule, importName );
writeMethodSignature( method );
} else {
writer.prepareFunction( name );
}
} catch( IOException ioex ) {
throw WasmException.create( ioex, sourceFile, -1 );
}
}
/**
* Write the content of a method.
*
* @param method
* the method
* @throws WasmException
* if some Java code can't converted
*/
private void writeMethod( MethodInfo method ) throws WasmException {
int lineNumber = -1;
try {
Code code = method.getCode();
lineNumber = code.getFirstLineNr();
if( code != null && method.getAnnotation( "org.webassembly.annotation.Import" ) == null ) { // abstract methods and interface methods does not have code
FunctionName name = new FunctionName( method );
writeExport( name, method );
writer.writeMethodStart( name );
writeMethodSignature( method );
localVariables.reset();
stackManager.reset();
branchManager.reset();
for( CodeInputStream byteCode : code.getByteCodes() ) {
prepareCodeChunk( byteCode, lineNumber = byteCode.getLineNumber(), method.getConstantPool() );
}
branchManager.calculate();
localVariables.calculate();
CodeInputStream byteCode = null;
boolean endWithReturn = false;
Iterator<CodeInputStream> byteCodes = code.getByteCodes().iterator();
while( byteCodes.hasNext() ) {
byteCode = byteCodes.next();
endWithReturn = writeCodeChunk( byteCode, lineNumber = byteCode.getLineNumber(), method.getConstantPool() );
}
branchManager.handle( byteCode, writer ); // write the last end operators
if( !endWithReturn && returnType != null ) {
// if a method ends with a loop without a break then code after the loop is no reachable
// Java does not need a return byte code in this case
// But WebAssembly need the dead code to validate
switch( returnType ) {
case i32:
writer.writeConstInt( 0 );
break;
case i64:
writer.writeConstLong( 0 );
break;
case f32:
writer.writeConstFloat( 0 );
break;
case f64:
writer.writeConstDouble( 0 );
break;
}
writer.writeBlockCode( WasmBlockOperator.RETURN, null );
}
writer.writeMethodFinish( localVariables.getLocalTypes( paramCount ) );
}
} catch( Exception ioex ) {
throw WasmException.create( ioex, sourceFile, lineNumber );
}
}
/**
* Look for a Export annotation and if there write an export directive.
*
* @param name
* the function name
* @param method
* the method
*
* @throws IOException
* if any IOException occur
*/
private void writeExport( FunctionName name, MethodInfo method ) throws IOException {
Map<String,Object> export = method.getAnnotation( "org.webassembly.annotation.Export" );
if( export != null ) {
String exportName = (String)export.get( "name" );
if( exportName == null ) {
exportName = method.getName(); // TODO naming conversion rule if no name was set
}
writer.writeExport( name, exportName );
}
}
/**
* Write the parameter and return signatures
*
* @param method
* the method
* @throws IOException
* if any I/O error occur
* @throws WasmException
* if some Java code can't converted
*/
private void writeMethodSignature( MethodInfo method ) throws IOException, WasmException {
String signature = method.getDescription();
String kind = "param";
int paramCount = 0;
ValueType type = null;
for( int i = 1; i < signature.length(); i++ ) {
paramCount++;
if( signature.charAt( i ) == ')' ) {
this.paramCount = paramCount - 1;
kind = "result";
continue;
}
type = getValueType( signature, i );
if( type != null ) {
writer.writeMethodParam( kind, type );
}
}
this.returnType = type;
writer.writeMethodParamFinish();
}
/**
* Get the WebAssembly value type from a Java signature.
*
* @param signature
* the signature
* @param idx
* the index in the signature
* @return the value type or null if void
*/
private ValueType getValueType( String signature, int idx ) {
String javaType;
switch( signature.charAt( idx ) ) {
case '[': // array
javaType = "array";
break;
case 'L':
javaType = "object";
break;
case 'B': // byte
case 'C': // char
case 'S': // short
case 'I': // int
return ValueType.i32;
case 'D': // double
return ValueType.f64;
case 'F': // float
return ValueType.f32;
case 'J': // long
return ValueType.i64;
case 'V': // void
return null;
default:
javaType = signature.substring( idx, idx + 1 );
}
throw new WasmException( "Not supported Java data type in method signature: " + javaType, sourceFile, -1 );
}
/**
* Analyze and prepare a chunk of byte code.
*
* @param byteCode
* a stream of byte code
* @param lineNumber
* the current line number
* @param constantPool
* the constant pool of the the current class
* @throws WasmException
* if some Java code can't converted
*/
private void prepareCodeChunk( CodeInputStream byteCode, int lineNumber, ConstantPool constantPool ) throws WasmException {
try {
while( byteCode.available() > 0 ) {
int codePosition = byteCode.getCodePosition();
int op = byteCode.readUnsignedByte();
switch( op ) {
case 2: // iconst_m1
case 3: // iconst_0
case 4: // iconst_1
case 5: // iconst_2
case 6: // iconst_3
case 7: // iconst_4
case 8: // iconst_5
stackManager.add( ValueType.i32, codePosition );
break;
case 9: // lconst_0
case 10: // lconst_1
stackManager.add( ValueType.i64, codePosition );
break;
case 11: // fconst_0
case 12: // fconst_1
case 13: // fconst_2
stackManager.add( ValueType.f32, codePosition );
break;
case 14: // dconst_0
case 15: // dconst_1
stackManager.add( ValueType.f64, codePosition );
break;
case 16: // bipush
stackManager.add( ValueType.i32, codePosition );
byteCode.skip(1);
break;
case 17: // sipush
stackManager.add( ValueType.i32, codePosition );
byteCode.skip(2);
break;
case 18: // ldc
stackManager.add( null, codePosition );
byteCode.skip(1);
break;
case 19: // ldc_w
case 20: // ldc2_w
stackManager.add( null, codePosition );
byteCode.skip(2);
break;
case 21: // iload
stackManager.add( ValueType.i32, codePosition );
localVariables.use( ValueType.i32, byteCode.readUnsignedByte() );
break;
case 22: // lload
stackManager.add( ValueType.i64, codePosition );
localVariables.use( ValueType.i64, byteCode.readUnsignedByte() );
break;
case 23: // fload
stackManager.add( ValueType.f32, codePosition );
localVariables.use( ValueType.f32, byteCode.readUnsignedByte() );
break;
case 24: // dload
stackManager.add( ValueType.f64, codePosition );
localVariables.use( ValueType.f64, byteCode.readUnsignedByte() );
break;
case 26: // iload_0
case 27: // iload_1
case 28: // iload_2
case 29: // iload_3
stackManager.add( ValueType.i32, codePosition );
localVariables.use( ValueType.i32, op - 26 );
break;
case 30: // lload_0
case 31: // lload_1
case 32: // lload_2
case 33: // lload_3
stackManager.add( ValueType.i64, codePosition );
localVariables.use( ValueType.i64, op - 30 );
break;
case 34: // fload_0
case 35: // fload_1
case 36: // fload_2
case 37: // fload_3
stackManager.add( ValueType.f32, codePosition );
localVariables.use( ValueType.f32, op - 34 );
break;
case 38: // dload_0
case 39: // dload_1
case 40: // dload_2
case 41: // dload_3
stackManager.add( ValueType.f64, codePosition );
localVariables.use( ValueType.f64, op - 38 );
break;
case 54: // istore
stackManager.remove();
localVariables.use( ValueType.i32, byteCode.readUnsignedByte() );
break;
case 55: // lstore
stackManager.remove();
localVariables.use( ValueType.i64, byteCode.readUnsignedByte() );
break;
case 56: // fstore
stackManager.remove();
localVariables.use( ValueType.f32, byteCode.readUnsignedByte() );
break;
case 57: // dstore
stackManager.remove();
localVariables.use( ValueType.f64, byteCode.readUnsignedByte() );
break;
case 59: // istore_0
case 60: // istore_1
case 61: // istore_2
case 62: // istore_3
stackManager.remove();
localVariables.use( ValueType.i32, op - 59 );
break;
case 63: // lstore_0
case 64: // lstore_1
case 65: // lstore_2
case 66: // lstore_3
stackManager.remove();
localVariables.use( ValueType.i64, op - 63 );
break;
case 67: // fstore_0
case 68: // fstore_1
case 69: // fstore_2
case 70: // fstore_3
stackManager.remove();
localVariables.use( ValueType.f32, op - 67 );
break;
case 71: // dstore_0
case 72: // dstore_1
case 73: // dstore_2
case 74: // dstore_3
stackManager.remove();
localVariables.use( ValueType.f64, op - 71 );
break;
case 87: // pop
case 88: // pop2
stackManager.remove();
break;
case 25: //aload
case 58: // astore
case 179: // putstatic
case 181: // putfield
byteCode.skip(1);
break;
case 132: // iinc
byteCode.skip( 2);
break;
case 153: // ifeq
case 154: // ifne
case 155: // iflt
case 156: // ifge
case 157: // ifgt
case 158: // ifle
case 159: // if_icmpeq
case 160: // if_icmpne
case 161: // if_icmplt
case 162: // if_icmpge
case 163: // if_icmpgt
case 164: // if_icmple
case 165: // if_acmpeq
case 166: // if_acmpne
int offset = byteCode.readShort();
branchManager.start( JavaBlockOperator.IF, codePosition + 3, offset - 3, lineNumber );
break;
case 167: // goto
offset = byteCode.readShort();
branchManager.start( JavaBlockOperator.GOTO, codePosition, offset, lineNumber );
break;
case 170: // tableswitch
case 171: // lookupswitch
prepareSwitchCode( byteCode, op == 171, lineNumber );
break;
case 184: // invokestatic
ConstantRef method = (ConstantRef)constantPool.get( byteCode.readUnsignedShort() );
String signature = method.getType();
ValueType type = getValueType( signature, signature.indexOf( ')' ) + 1 );
if( type != null ) {
stackManager.add( type, codePosition );
}
break;
}
}
} catch( Exception ex ) {
throw WasmException.create( ex, sourceFile, lineNumber );
}
}
/**
* Prepare the both switch operation codes.
*
* @param byteCode
* the current stream with a position after the operation code
* @param isLookupSwitch
* true, if the operation was a loopupswitch; false, if the operation was a tableswitch
* @param lineNumber
* the current line number
* @throws IOException
* if any I/O error occur
*/
private void prepareSwitchCode( CodeInputStream byteCode, boolean isLookupSwitch, int lineNumber ) throws IOException {
int startPosition = byteCode.getCodePosition();
int padding = startPosition % 4;
if( padding > 0 ) {
byteCode.skip( 4 - padding );
}
startPosition--;
int defaultPosition = startPosition + byteCode.readInt();
int[] keys;
int[] positions;
if( isLookupSwitch ) { // lookupswitch
localVariables.useTempI32();
int nPairs = byteCode.readInt();
keys = new int[nPairs];
positions = new int[nPairs];
for( int i = 0; i < nPairs; i++ ) {
keys[i] = byteCode.readInt();
positions[i] = startPosition + byteCode.readInt();
}
} else {
int low = byteCode.readInt();
keys = null;
int count = byteCode.readInt() - low + 1;
positions = new int[count];
for( int i = 0; i < count; i++ ) {
positions[i] = startPosition + byteCode.readInt();
}
}
int switchValuestartPosition = stackManager.getCodePosition( 0 );
branchManager.startSwitch( switchValuestartPosition, 0, lineNumber, keys, positions, defaultPosition );
}
/**
* Write a chunk of byte code.
*
* @param byteCode
* a stream of byte code
* @param lineNumber
* the current line number
* @param constantPool
* the constant pool of the the current class
* @throws WasmException
* if some Java code can't converted
* @return true, if the last operation was a return
*/
private boolean writeCodeChunk( CodeInputStream byteCode, int lineNumber, ConstantPool constantPool ) throws WasmException {
boolean endWithReturn = false;
try {
while( byteCode.available() > 0 ) {
branchManager.handle( byteCode, writer );
endWithReturn = false;
int op = byteCode.readUnsignedByte();
switch( op ) {
case 0: // nop
break;
//TODO case 1: // aconst_null
case 2: // iconst_m1
case 3: // iconst_0
case 4: // iconst_1
case 5: // iconst_2
case 6: // iconst_3
case 7: // iconst_4
case 8: // iconst_5
writer.writeConstInt( op - 3 );
break;
case 9: // lconst_0
case 10: // lconst_1
writer.writeConstLong( op - 9 );
break;
case 11: // fconst_0
case 12: // fconst_1
case 13: // fconst_2
writer.writeConstFloat( op - 11 );
break;
case 14: // dconst_0
case 15: // dconst_1
writer.writeConstDouble( op - 14 );
break;
case 16: // bipush
writer.writeConstInt( byteCode.readByte() );
break;
case 17: // sipush
writer.writeConstInt( byteCode.readShort() );
break;
case 18: // ldc
writeConst( constantPool.get( byteCode.readUnsignedByte() ) );
break;
case 19: // ldc_w
case 20: // ldc2_w
writeConst( constantPool.get( byteCode.readUnsignedShort() ) );
break;
case 21: // iload
writeLoadStore( true, byteCode.readUnsignedByte() );
break;
case 22: // lload
writeLoadStore( true, byteCode.readUnsignedByte() );
break;
case 23: // fload
writeLoadStore( true, byteCode.readUnsignedByte() );
break;
case 24: // dload
writeLoadStore( true, byteCode.readUnsignedByte() );
break;
//TODO case 25: // aload
case 26: // iload_0
case 27: // iload_1
case 28: // iload_2
case 29: // iload_3
writeLoadStore( true, op - 26 );
break;
case 30: // lload_0
case 31: // lload_1
case 32: // lload_2
case 33: // lload_3
writeLoadStore( true, op - 30 );
break;
case 34: // fload_0
case 35: // fload_1
case 36: // fload_2
case 37: // fload_3
writeLoadStore( true, op - 34 );
break;
case 38: // dload_0
case 39: // dload_1
case 40: // dload_2
case 41: // dload_3
writeLoadStore( true, op - 38 );
break;
case 54: // istore
writeLoadStore( false, byteCode.readUnsignedByte() );
break;
case 55: // lstore
writeLoadStore( false, byteCode.readUnsignedByte() );
break;
case 56: // fstore
writeLoadStore( false, byteCode.readUnsignedByte() );
break;
case 57: // dstore
writeLoadStore( false, byteCode.readUnsignedByte() );
break;
//TODO case 58: // astore
case 59: // istore_0
case 60: // istore_1
case 61: // istore_2
case 62: // istore_3
writeLoadStore( false, op - 59 );
break;
case 63: // lstore_0
case 64: // lstore_1
case 65: // lstore_2
case 66: // lstore_3
writeLoadStore( false, op - 63 );
break;
case 67: // fstore_0
case 68: // fstore_1
case 69: // fstore_2
case 70: // fstore_3
writeLoadStore( false, op - 67 );
break;
case 71: // dstore_0
case 72: // dstore_1
case 73: // dstore_2
case 74: // dstore_3
writeLoadStore( false, op - 71 );
break;
case 87: // pop
case 88: // pop2
writer.writeBlockCode( WasmBlockOperator.DROP, null );
break;
case 89: // dup: duplicate the value on top of the stack
case 90: // dup_x1
case 91: // dup_x2
case 92: // dup2
case 93: // dup2_x1
case 94: // dup2_x2
case 95: // swap
// can be do with functions with more as one return value in future WASM standard
throw new WasmException( "Stack duplicate is not supported in current WASM. try to save immediate values in a local variable: " + op, sourceFile, lineNumber );
case 96: // iadd
writer.writeNumericOperator( NumericOperator.add, ValueType.i32);
break;
case 97: // ladd
writer.writeNumericOperator( NumericOperator.add, ValueType.i64 );
break;
case 98: // fadd
writer.writeNumericOperator( NumericOperator.add, ValueType.f32 );
break;
case 99: // dadd
writer.writeNumericOperator( NumericOperator.add, ValueType.f64 );
break;
case 100: // isub
writer.writeNumericOperator( NumericOperator.sub, ValueType.i32 );
break;
case 101: // lsub
writer.writeNumericOperator( NumericOperator.sub, ValueType.i64 );
break;
case 102: // fsub
writer.writeNumericOperator( NumericOperator.sub, ValueType.f32 );
break;
case 103: // dsub
writer.writeNumericOperator( NumericOperator.sub, ValueType.f64 );
break;
case 104: // imul;
writer.writeNumericOperator( NumericOperator.mul, ValueType.i32 );
break;
case 105: // lmul
writer.writeNumericOperator( NumericOperator.mul, ValueType.i64 );
break;
case 106: // fmul
writer.writeNumericOperator( NumericOperator.mul, ValueType.f32 );
break;
case 107: // dmul
writer.writeNumericOperator( NumericOperator.mul, ValueType.f64 );
break;
case 108: // idiv
writer.writeNumericOperator( NumericOperator.div, ValueType.i32 );
break;
case 109: // ldiv
writer.writeNumericOperator( NumericOperator.div, ValueType.i64 );
break;
case 110: // fdiv
writer.writeNumericOperator( NumericOperator.div, ValueType.f32 );
break;
case 111: // ddiv
writer.writeNumericOperator( NumericOperator.div, ValueType.f64 );
break;
case 112: // irem
writer.writeNumericOperator( NumericOperator.rem, ValueType.i32 );
break;
case 113: // lrem
writer.writeNumericOperator( NumericOperator.rem, ValueType.i64 );
break;
case 114: // frem
case 115: // drem
//TODO can be implemented with a helper function like: (a - (long)(a / b) * (double)b)
throw new WasmException( "Modulo/Remainder for floating numbers is not supported in WASM. Use int or long data types." + op, sourceFile, lineNumber );
case 116: // ineg
writer.writeConstInt( -1 );
writer.writeNumericOperator( NumericOperator.mul, ValueType.i32 );
break;
case 117: // lneg
writer.writeConstLong( -1 );
writer.writeNumericOperator( NumericOperator.mul, ValueType.i64 );
break;
case 118: // fneg
writer.writeNumericOperator( NumericOperator.neg, ValueType.f32 );
break;
case 119: // dneg
writer.writeNumericOperator( NumericOperator.neg, ValueType.f64 );
break;
case 120: // ishl
writer.writeNumericOperator( NumericOperator.shl, ValueType.i32 );
break;
case 121: // lshl
writer.writeCast( ValueTypeConvertion.i2l ); // the shift parameter must be of type long!!!
writer.writeNumericOperator( NumericOperator.shl, ValueType.i64 );
break;
case 122: // ishr
writer.writeNumericOperator( NumericOperator.shr_s, ValueType.i32 );
break;
case 123: // lshr
writer.writeCast( ValueTypeConvertion.i2l ); // the shift parameter must be of type long!!!
writer.writeNumericOperator( NumericOperator.shr_s, ValueType.i64 );
break;
case 124: // iushr
writer.writeNumericOperator( NumericOperator.shr_u, ValueType.i32 );
break;
case 125: // lushr
writer.writeCast( ValueTypeConvertion.i2l ); // the shift parameter must be of type long!!!
writer.writeNumericOperator( NumericOperator.shr_u, ValueType.i64 );
break;
case 126: // iand
writer.writeNumericOperator( NumericOperator.and, ValueType.i32 );
break;
case 127: // land
writer.writeNumericOperator( NumericOperator.and, ValueType.i64 );
break;
case 128: // ior
writer.writeNumericOperator( NumericOperator.or, ValueType.i32 );
break;
case 129: // lor
writer.writeNumericOperator( NumericOperator.or, ValueType.i64 );
break;
case 130: // ixor
writer.writeNumericOperator( NumericOperator.xor, ValueType.i32 );
break;
case 131: // lxor
writer.writeNumericOperator( NumericOperator.xor, ValueType.i64 );
break;
case 132: // iinc
int idx = byteCode.readUnsignedByte();
writeLoadStore( true, idx );
writer.writeConstInt( byteCode.readUnsignedByte() );
writer.writeNumericOperator( NumericOperator.add, ValueType.i32);
writeLoadStore( false, idx );
break;
case 133: // i2l
writer.writeCast( ValueTypeConvertion.i2l );
break;
case 134: // i2f
writer.writeCast( ValueTypeConvertion.i2f );
break;
case 135: // i2d
writer.writeCast( ValueTypeConvertion.i2d );
break;
case 136: // l2i
writer.writeCast( ValueTypeConvertion.l2i );
break;
case 137: // l2f
writer.writeCast( ValueTypeConvertion.l2f );
break;
case 138: // l2d
writer.writeCast( ValueTypeConvertion.l2d );
break;
case 139: // f2i
writer.writeCast( ValueTypeConvertion.f2i );
break;
case 140: // f2l
writer.writeCast( ValueTypeConvertion.f2l );
break;
case 141: // f2d
writer.writeCast( ValueTypeConvertion.f2d );
break;
case 142: // d2i
writer.writeCast( ValueTypeConvertion.d2i );
break;
case 143: // d2l
writer.writeCast( ValueTypeConvertion.d2l );
break;
case 144: // d2f
writer.writeCast( ValueTypeConvertion.d2f );
break;
case 145: // i2b
writer.writeConstInt( 24 );
writer.writeNumericOperator( NumericOperator.shl, ValueType.i32 );
writer.writeConstInt( 24 );
writer.writeNumericOperator( NumericOperator.shr_s, ValueType.i32 );
break;
case 146: // i2c
writer.writeConstInt( 0xFFFF );
writer.writeNumericOperator( NumericOperator.and, ValueType.i32 );
break;
case 147: // i2s
writer.writeConstInt( 16 );
writer.writeNumericOperator( NumericOperator.shl, ValueType.i32 );
writer.writeConstInt( 16 );
writer.writeNumericOperator( NumericOperator.shr_s, ValueType.i32 );
break;
case 148: // lcmp
opCompare( ValueType.i64, byteCode );
break;
case 149: // fcmpl
case 150: // fcmpg
opCompare( ValueType.f32, byteCode );
break;
case 151: // dcmpl
case 152: // dcmpg
opCompare( ValueType.f64, byteCode );
break;
case 153: // ifeq
opIfCondition( NumericOperator.ne, byteCode );
break;
case 154: // ifne
opIfCondition( NumericOperator.eq, byteCode );
break;
case 155: // iflt
opIfCondition( NumericOperator.ge_s, byteCode );
break;
case 156: // ifge
opIfCondition( NumericOperator.lt_s, byteCode );
break;
case 157: // ifgt
opIfCondition( NumericOperator.le_s, byteCode );
break;
case 158: // ifle
opIfCondition( NumericOperator.gt, byteCode );
break;
case 159: // if_icmpeq
opIfCompareCondition( NumericOperator.ne, byteCode );
break;
case 160: // if_icmpne
opIfCompareCondition( NumericOperator.eq, byteCode );
break;
case 161: // if_icmplt
opIfCompareCondition( NumericOperator.ge_s, byteCode );
break;
case 162: // if_icmpge
opIfCompareCondition( NumericOperator.lt_s, byteCode );
break;
case 163: // if_icmpgt
opIfCompareCondition( NumericOperator.le_s, byteCode );
break;
case 164: // if_icmple
opIfCompareCondition( NumericOperator.gt, byteCode );
break;
//TODO case 165: // if_acmpeq
//TODO case 166: // if_acmpne
case 167: // goto
byteCode.skip(2); // handle in the branch manager
break;
case 170: // tableswitch
case 171: // lookupswitch
writeSwitchCode( byteCode, op == 171 );
break;
case 172: // ireturn
case 173: // lreturn
case 174: // freturn
case 175: // dreturn
case 177: // return void
writer.writeBlockCode( WasmBlockOperator.RETURN, null );
endWithReturn = true;
break;
case 184: // invokestatic
idx = byteCode.readUnsignedShort();
ConstantRef method = (ConstantRef)constantPool.get( idx );
writer.writeFunctionCall( method.getConstantClass().getName() + '.' + method.getName() + method.getType() );
break;
default:
throw new WasmException( "Unimplemented Java byte code operation: " + op, sourceFile, lineNumber );
}
}
} catch( WasmException ex ) {
throw ex;
} catch( Exception ex ) {
throw WasmException.create( ex, sourceFile, lineNumber );
}
return endWithReturn;
}
/**
* Write the both switch operation codes
*
* @param byteCode
* the current stream with a position after the operation code
* @param isLookupSwitch
* true, if the operation was a loopupswitch; false, if the operation was a tableswitch
* @throws IOException
* if any I/O error occur
*/
private void writeSwitchCode( CodeInputStream byteCode, boolean isLookupSwitch ) throws IOException {
int startPosition = byteCode.getCodePosition();
int padding = startPosition % 4;
if( padding > 0 ) {
byteCode.skip( 4 - padding );
}
startPosition--;
int defaultPosition = byteCode.readInt();
if( isLookupSwitch ) { // lookupswitch
int count = byteCode.readInt();
int[] keys = new int[count];
int[] positions = new int[count];
for( int i = 0; i < count; i++ ) {
keys[i] = byteCode.readInt();
positions[i] = byteCode.readInt();
}
int tempI32 = localVariables.getTempI32();
int block = 0;
int defaultBlock = -1;
int currentPos = -1;
writeLoadStore( false, tempI32 );
do {
int nextPos = findNext( currentPos, positions );
if( nextPos == currentPos ) {
break;
}
currentPos = nextPos;
if( defaultBlock < 0 ) {
if( defaultPosition <= currentPos ) {
defaultBlock = block;
if( defaultPosition < currentPos ) {
block++;
}
}
}
for( int i = 0; i < positions.length; i++ ) {
if( positions[i] == currentPos ) {
writeLoadStore( true, tempI32 );
writer.writeConstInt( keys[i] );
writer.writeNumericOperator( NumericOperator.eq, ValueType.i32 );
writer.writeBlockCode( WasmBlockOperator.BR_IF, block );
}
}
block++;
} while( true );
if( defaultBlock < 0 ) {
defaultBlock = block;
}
writer.writeBlockCode( WasmBlockOperator.BR, defaultBlock );
} else {
int low = byteCode.readInt();
int count = byteCode.readInt() - low + 1;
for( int i = 0; i < count; i++ ) {
byteCode.readInt();
}
if( low != 0 ) { // the br_table starts ever with the value 0. That we need to subtract the start value if it different
writer.writeConstInt( low );
writer.writeNumericOperator( NumericOperator.sub, ValueType.i32 );
}
}
}
/**
* Find the next higher value.
*
* @param current
* the current value
* @param values
* the unordered list of values
* @return the next value or current value if not found.
*/
private static int findNext( int current, int[] values ) {
boolean find = false;
int next = Integer.MAX_VALUE;
for( int val : values ) {
if( val > current && val <= next ) {
next = val;
find = true;
}
}
return find ? next : current;
}
/**
* Handle the if<condition> of the Java byte code. This Java instruction compare the first stack value with value 0.
* Important: In Java the condition for the jump to the else block is saved. In WebAssembler we need to use
* condition for the if block. The caller of the method must already negate this
*
* @param numOp
* The condition for the if block.
* @param byteCode
* current byte code stream to read the taget offset.
* @throws IOException
* if any I/O errors occur.
*/
private void opIfCondition( NumericOperator numOp, CodeInputStream byteCode ) throws IOException {
byteCode.skip(2);
writer.writeConstInt( 0 );
writer.writeNumericOperator( numOp, ValueType.i32 );
}
/**
* Handle the if<condition> of the Java byte code. This Java instruction compare the first stack value with value 0.
* Important: In Java the condition for the jump to the else block is saved. In WebAssembler we need to use
* condition for the if block. The caller of the method must already negate this
*
* @param numOp
* The condition for the if block.
* @param byteCode
* current byte code stream to read the target offset.
* @throws IOException
* if any I/O errors occur.
*/
private void opIfCompareCondition( NumericOperator numOp, CodeInputStream byteCode ) throws IOException {
byteCode.skip(2);
writer.writeNumericOperator( numOp, ValueType.i32 );
}
/**
* Handle the different compare operator. The compare operator returns the integer values -1, 0 or 1. There is no
* equivalent in WebAssembly. That we need to read the next operation to find an equivalent.
*
* @param valueType
* the value type of the compared
* @param byteCode
* current byte code stream to read the next operation.
* @throws IOException
* if any I/O errors occur.
*/
private void opCompare( ValueType valueType, CodeInputStream byteCode ) throws IOException {
NumericOperator numOp;
int nextOp = byteCode.read();
switch( nextOp ){
case 153: // ifeq
numOp = NumericOperator.ne;
break;
case 154: // ifne
numOp = NumericOperator.eq;
break;
case 155: // iflt
numOp = NumericOperator.gt;
break;
case 156: // ifge
numOp = NumericOperator.le_s;
break;
case 157: // ifgt
numOp = NumericOperator.lt_s;
break;
case 158: // ifle
numOp = NumericOperator.ge_s;
break;
default:
throw new WasmException( "Unexpected compare sub operation: " + nextOp, null, -1 );
}
byteCode.skip(2);
writer.writeNumericOperator( numOp, valueType );
}
/**
* Write a constant value.
*
* @param value
* the value
* @throws IOException
* if any I/O error occur
* @throws WasmException
* if the value type is not supported
*/
private void writeConst( Object value ) throws IOException, WasmException {
Class<?> clazz = value.getClass();
if( clazz == Integer.class ) {
writer.writeConstInt( ((Integer)value).intValue() );
} else if( clazz == Long.class ) {
writer.writeConstLong( ((Long)value).longValue() );
} else if( clazz == Float.class ) {
writer.writeConstFloat( ((Float)value).floatValue() );
} else if( clazz == Double.class ) {
writer.writeConstDouble( ((Double)value).doubleValue() );
} else {
throw new WasmException( "Not supported constant type: " + clazz, sourceFile, -1 );
}
}
/**
* Write or Load a local variable.
*
* @param load
* true: if load
* @param idx
* the memory/slot idx of the variable
* @throws WasmException
* occur a if a variable was used for a different type
* @throws IOException
* if any I/O error occur
*/
private void writeLoadStore( boolean load, @Nonnegative int idx ) throws WasmException, IOException {
idx = localVariables.get( idx ); // translate slot index to position index
if( load ) {
writer.writeLoad( idx );
} else {
writer.writeStore( idx );
}
}
}