Split the ModuleGenerator in WasmCodeBuilder and ModuleGenerator

This commit is contained in:
Volker Berlin 2018-11-04 20:28:42 +01:00
parent a49288449d
commit 74e6c0db06
3 changed files with 859 additions and 738 deletions

View File

@ -0,0 +1,793 @@
/*
* Copyright 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.ArrayList;
import java.util.List;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import de.inetsoftware.classparser.Code;
import de.inetsoftware.classparser.CodeInputStream;
import de.inetsoftware.classparser.ConstantPool;
import de.inetsoftware.classparser.ConstantRef;
import de.inetsoftware.jwebassembly.WasmException;
/**
* Convert Java Byte Code to a list of WasmInstruction.
*
* @author Volker Berlin
*/
class JavaMethodWasmCodeBuilder extends WasmCodeBuilder {
private LocaleVariableManager localVariables = new LocaleVariableManager();
private final List<WasmInstruction> instructions = new ArrayList<>();
private BranchManger branchManager = new BranchManger( instructions );
/**
* {@inheritDoc}
*/
List<WasmInstruction> getInstructions() {
return instructions;
}
/**
* {@inheritDoc}
*/
@Override
List<ValueType> getLocalTypes( int paramCount ) {
return localVariables.getLocalTypes( paramCount );
}
/**
* Build the wasm instructions
*
* @param code
* the Java method code
* @param hasReturn
* true, if the method has a return value; false, if it is void
* @throws WasmException
* if some Java code can't converted
*/
void buildCode( @Nonnull Code code, boolean hasReturn ) {
CodeInputStream byteCode = null;
try {
localVariables.reset();
branchManager.reset( code.getExceptionTable() );
byteCode = code.getByteCode();
writeCode( byteCode, code.getConstantPool(), hasReturn );
localVariables.calculate();
} catch( Exception ioex ) {
int lineNumber = byteCode == null ? -1 : byteCode.getLineNumber();
throw WasmException.create( ioex, lineNumber );
}
}
/**
* Write the byte code of a method.
*
* @param byteCode
* a stream of byte code
* @param constantPool
* the constant pool of the the current class
* @param hasReturn
* if the method has a return value
* @throws WasmException
* if some Java code can't converted
*/
private void writeCode( CodeInputStream byteCode, ConstantPool constantPool, boolean hasReturn ) throws WasmException {
instructions.clear();
boolean endWithReturn = false;
try {
while( byteCode.available() > 0 ) {
WasmInstruction instr = null;
int codePos = byteCode.getCodePosition();
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
instr = new WasmConstInstruction( Integer.valueOf( op - 3 ), ValueType.i32, codePos );
break;
case 9: // lconst_0
case 10: // lconst_1
instr = new WasmConstInstruction( Long.valueOf( op - 9 ), ValueType.i64, codePos );
break;
case 11: // fconst_0
case 12: // fconst_1
case 13: // fconst_2
instr = new WasmConstInstruction( Float.valueOf( op - 11 ), ValueType.f32, codePos );
break;
case 14: // dconst_0
case 15: // dconst_1
instr = new WasmConstInstruction( Double.valueOf( op - 14 ), ValueType.f64, codePos );
break;
case 16: // bipush
instr = new WasmConstInstruction( Integer.valueOf( byteCode.readByte() ), ValueType.i32, codePos );
break;
case 17: // sipush
instr = new WasmConstInstruction( Integer.valueOf( byteCode.readShort() ), ValueType.i32, codePos );
break;
case 18: // ldc
instr = new WasmConstInstruction( (Number)constantPool.get( byteCode.readUnsignedByte() ), codePos );
break;
case 19: // ldc_w
case 20: // ldc2_w
instr = new WasmConstInstruction( (Number)constantPool.get( byteCode.readUnsignedShort() ), codePos );
break;
case 21: // iload
instr = loadStore( ValueType.i32, true, byteCode.readUnsignedByte(), codePos );
break;
case 22: // lload
instr = loadStore( ValueType.i64, true, byteCode.readUnsignedByte(), codePos );
break;
case 23: // fload
instr = loadStore( ValueType.f32, true, byteCode.readUnsignedByte(), codePos );
break;
case 24: // dload
instr = loadStore( ValueType.f64, true, byteCode.readUnsignedByte(), codePos );
break;
//TODO case 25: // aload
case 26: // iload_0
case 27: // iload_1
case 28: // iload_2
case 29: // iload_3
instr = loadStore( ValueType.i32, true, op - 26, codePos );
break;
case 30: // lload_0
case 31: // lload_1
case 32: // lload_2
case 33: // lload_3
instr = loadStore( ValueType.i64, true, op - 30, codePos );
break;
case 34: // fload_0
case 35: // fload_1
case 36: // fload_2
case 37: // fload_3
instr = loadStore( ValueType.f32, true, op - 34, codePos );
break;
case 38: // dload_0
case 39: // dload_1
case 40: // dload_2
case 41: // dload_3
instr = loadStore( ValueType.f64, true, op - 38, codePos );
break;
//TODO case 42: //aload_0
//TODO case 43: //aload_1
//TODO case 44: //aload_2
//TODO case 45: //aload_3
//TODO case 46: // iaload
//TODO case 47: // laload
//TODO case 48: // faload
//TODO case 49: // daload
//TODO case 50: // aaload
//TODO case 51: // baload
//TODO case 52: // caload
//TODO case 53: // saload
case 54: // istore
instr = loadStore( ValueType.i32, false, byteCode.readUnsignedByte(), codePos );
break;
case 55: // lstore
instr = loadStore( ValueType.i64, false, byteCode.readUnsignedByte(), codePos );
break;
case 56: // fstore
instr = loadStore( ValueType.f32, false, byteCode.readUnsignedByte(), codePos );
break;
case 57: // dstore
instr = loadStore( ValueType.f64, false, byteCode.readUnsignedByte(), codePos );
break;
//TODO case 58: // astore
case 59: // istore_0
case 60: // istore_1
case 61: // istore_2
case 62: // istore_3
instr = loadStore( ValueType.i32, false, op - 59, codePos );
break;
case 63: // lstore_0
case 64: // lstore_1
case 65: // lstore_2
case 66: // lstore_3
instr = loadStore( ValueType.i64, false, op - 63, codePos );
break;
case 67: // fstore_0
case 68: // fstore_1
case 69: // fstore_2
case 70: // fstore_3
instr = loadStore( ValueType.f32, false, op - 67, codePos );
break;
case 71: // dstore_0
case 72: // dstore_1
case 73: // dstore_2
case 74: // dstore_3
instr = loadStore( ValueType.f64, false, op - 71, codePos );
break;
case 75: // astore_0
case 76: // astore_1
case 77: // astore_2
case 78: // astore_3
instr = loadStore( ValueType.anyref, false, op - 75, codePos );
break;
//TODO case 79: // iastore
//TODO case 80: // lastore
//TODO case 81: // fastore
//TODO case 82: // dastore
//TODO case 83: // aastore
//TODO case 84: // bastore
//TODO case 85: // castore
//TODO case 86: // sastore
case 87: // pop
case 88: // pop2
instr = new WasmBlockInstruction( WasmBlockOperator.DROP, null, codePos );
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, byteCode.getLineNumber() );
case 96: // iadd
instr = new WasmNumericInstruction( NumericOperator.add, ValueType.i32, codePos);
break;
case 97: // ladd
instr = new WasmNumericInstruction( NumericOperator.add, ValueType.i64, codePos );
break;
case 98: // fadd
instr = new WasmNumericInstruction( NumericOperator.add, ValueType.f32, codePos );
break;
case 99: // dadd
instr = new WasmNumericInstruction( NumericOperator.add, ValueType.f64, codePos );
break;
case 100: // isub
instr = new WasmNumericInstruction( NumericOperator.sub, ValueType.i32, codePos );
break;
case 101: // lsub
instr = new WasmNumericInstruction( NumericOperator.sub, ValueType.i64, codePos );
break;
case 102: // fsub
instr = new WasmNumericInstruction( NumericOperator.sub, ValueType.f32, codePos );
break;
case 103: // dsub
instr = new WasmNumericInstruction( NumericOperator.sub, ValueType.f64, codePos );
break;
case 104: // imul;
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.i32, codePos );
break;
case 105: // lmul
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.i64, codePos );
break;
case 106: // fmul
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.f32, codePos );
break;
case 107: // dmul
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.f64, codePos );
break;
case 108: // idiv
instr = new WasmNumericInstruction( NumericOperator.div, ValueType.i32, codePos );
break;
case 109: // ldiv
instr = new WasmNumericInstruction( NumericOperator.div, ValueType.i64, codePos );
break;
case 110: // fdiv
instr = new WasmNumericInstruction( NumericOperator.div, ValueType.f32, codePos );
break;
case 111: // ddiv
instr = new WasmNumericInstruction( NumericOperator.div, ValueType.f64, codePos );
break;
case 112: // irem
instr = new WasmNumericInstruction( NumericOperator.rem, ValueType.i32, codePos );
break;
case 113: // lrem
instr = new WasmNumericInstruction( NumericOperator.rem, ValueType.i64, codePos );
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, byteCode.getLineNumber() );
case 116: // ineg
instructions.add( new WasmConstInstruction( -1, ValueType.i32, codePos ) );
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.i32, codePos );
break;
case 117: // lneg
instructions.add( new WasmConstInstruction( (long)-1, ValueType.i64, codePos ) ) ;
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.i64, codePos );
break;
case 118: // fneg
instr = new WasmNumericInstruction( NumericOperator.neg, ValueType.f32, codePos );
break;
case 119: // dneg
instr = new WasmNumericInstruction( NumericOperator.neg, ValueType.f64, codePos );
break;
case 120: // ishl
instr = new WasmNumericInstruction( NumericOperator.shl, ValueType.i32, codePos );
break;
case 121: // lshl
instructions.add( new WasmConvertInstruction( ValueTypeConvertion.i2l, codePos ) ); // the shift parameter must be of type long!!!
instr = new WasmNumericInstruction( NumericOperator.shl, ValueType.i64, codePos );
break;
case 122: // ishr
instr = new WasmNumericInstruction( NumericOperator.shr_s, ValueType.i32, codePos );
break;
case 123: // lshr
instructions.add( new WasmConvertInstruction( ValueTypeConvertion.i2l, codePos ) ); // the shift parameter must be of type long!!!
instr = new WasmNumericInstruction( NumericOperator.shr_s, ValueType.i64, codePos );
break;
case 124: // iushr
instr = new WasmNumericInstruction( NumericOperator.shr_u, ValueType.i32, codePos );
break;
case 125: // lushr
instructions.add( new WasmConvertInstruction( ValueTypeConvertion.i2l, codePos ) ); // the shift parameter must be of type long!!!
instr = new WasmNumericInstruction( NumericOperator.shr_u, ValueType.i64, codePos );
break;
case 126: // iand
instr = new WasmNumericInstruction( NumericOperator.and, ValueType.i32, codePos );
break;
case 127: // land
instr = new WasmNumericInstruction( NumericOperator.and, ValueType.i64, codePos );
break;
case 128: // ior
instr = new WasmNumericInstruction( NumericOperator.or, ValueType.i32, codePos );
break;
case 129: // lor
instr = new WasmNumericInstruction( NumericOperator.or, ValueType.i64, codePos );
break;
case 130: // ixor
instr = new WasmNumericInstruction( NumericOperator.xor, ValueType.i32, codePos );
break;
case 131: // lxor
instr = new WasmNumericInstruction( NumericOperator.xor, ValueType.i64, codePos );
break;
case 132: // iinc
int idx = byteCode.readUnsignedByte();
instructions.add( new WasmLoadStoreInstruction( true, idx, localVariables, codePos ) );
instructions.add( new WasmConstInstruction( (int)byteCode.readByte(), ValueType.i32, codePos ) );
instructions.add( new WasmNumericInstruction( NumericOperator.add, ValueType.i32, codePos) );
instr = new WasmLoadStoreInstruction( false, idx, localVariables, codePos );
break;
case 133: // i2l
instr = new WasmConvertInstruction( ValueTypeConvertion.i2l, codePos );
break;
case 134: // i2f
instr = new WasmConvertInstruction( ValueTypeConvertion.i2f, codePos );
break;
case 135: // i2d
instr = new WasmConvertInstruction( ValueTypeConvertion.i2d, codePos );
break;
case 136: // l2i
instr = new WasmConvertInstruction( ValueTypeConvertion.l2i, codePos );
break;
case 137: // l2f
instr = new WasmConvertInstruction( ValueTypeConvertion.l2f, codePos );
break;
case 138: // l2d
instr = new WasmConvertInstruction( ValueTypeConvertion.l2d, codePos );
break;
case 139: // f2i
instr = new WasmConvertInstruction( ValueTypeConvertion.f2i, codePos );
break;
case 140: // f2l
instr = new WasmConvertInstruction( ValueTypeConvertion.f2l, codePos );
break;
case 141: // f2d
instr = new WasmConvertInstruction( ValueTypeConvertion.f2d, codePos );
break;
case 142: // d2i
instr = new WasmConvertInstruction( ValueTypeConvertion.d2i, codePos );
break;
case 143: // d2l
instr = new WasmConvertInstruction( ValueTypeConvertion.d2l, codePos );
break;
case 144: // d2f
instr = new WasmConvertInstruction( ValueTypeConvertion.d2f, codePos );
break;
case 145: // i2b
instr = new WasmConvertInstruction( ValueTypeConvertion.i2b, codePos );
break;
case 146: // i2c
instructions.add( new WasmConstInstruction( 0xFFFF, ValueType.i32, codePos ) );
instr = new WasmNumericInstruction( NumericOperator.and, ValueType.i32, codePos );
break;
case 147: // i2s
instr = new WasmConvertInstruction( ValueTypeConvertion.i2s, codePos );
break;
case 148: // lcmp
opCompare( ValueType.i64, byteCode, codePos );
break;
case 149: // fcmpl
case 150: // fcmpg
opCompare( ValueType.f32, byteCode, codePos );
break;
case 151: // dcmpl
case 152: // dcmpg
opCompare( ValueType.f64, byteCode, codePos );
break;
case 153: // ifeq
opIfCondition( NumericOperator.eq, byteCode, codePos );
break;
case 154: // ifne
opIfCondition( NumericOperator.ne, byteCode, codePos );
break;
case 155: // iflt
opIfCondition( NumericOperator.lt, byteCode, codePos );
break;
case 156: // ifge
opIfCondition( NumericOperator.ge, byteCode, codePos );
break;
case 157: // ifgt
opIfCondition( NumericOperator.gt, byteCode, codePos );
break;
case 158: // ifle
opIfCondition( NumericOperator.le, byteCode, codePos );
break;
case 159: // if_icmpeq
opIfCompareCondition( NumericOperator.eq, byteCode, codePos );
break;
case 160: // if_icmpne
opIfCompareCondition( NumericOperator.ne, byteCode, codePos );
break;
case 161: // if_icmplt
opIfCompareCondition( NumericOperator.lt, byteCode, codePos );
break;
case 162: // if_icmpge
opIfCompareCondition( NumericOperator.ge, byteCode, codePos );
break;
case 163: // if_icmpgt
opIfCompareCondition( NumericOperator.gt, byteCode, codePos );
break;
case 164: // if_icmple
opIfCompareCondition( NumericOperator.le, byteCode, codePos );
break;
//TODO case 165: // if_acmpeq
//TODO case 166: // if_acmpne
case 167: // goto
int offset = byteCode.readShort();
branchManager.addGotoOperator( codePos, offset, byteCode.getLineNumber() );
instr = new WasmNopInstruction( codePos ); // marker of the line number for the branch manager
break;
//TODO case 168: // jsr
//TODO case 169: // ret
case 170: // tableswitch
case 171: // lookupswitch
writeSwitchCode( byteCode, op == 171 );
break;
case 172: // ireturn
case 173: // lreturn
case 174: // freturn
case 175: // dreturn
case 176: // areturn
case 177: // return void
ValueType type = null;
switch ( op ) {
case 172: // ireturn
type = ValueType.i32;
break;
case 173: // lreturn
type = ValueType.i64;
break;
case 174: // freturn
type = ValueType.f32;
break;
case 175: // dreturn
type = ValueType.f64;
break;
case 176: // areturn
type = ValueType.anyref;
break;
}
instr = new WasmBlockInstruction( WasmBlockOperator.RETURN, type, codePos );
endWithReturn = true;
break;
case 178: // getstatic
ConstantRef ref = (ConstantRef)constantPool.get( byteCode.readUnsignedShort() );
instr = new WasmGlobalInstruction( true, ref, codePos );
break;
case 179: // putstatic
ref = (ConstantRef)constantPool.get( byteCode.readUnsignedShort() );
instr = new WasmGlobalInstruction( false, ref, codePos );
break;
//TODO case 180: // getfield
//TODO case 181: // putfield
//TODO case 182: // invokevirtual
//TODO case 183: // invokespecial
case 184: // invokestatic
idx = byteCode.readUnsignedShort();
ref = (ConstantRef)constantPool.get( idx );
instr = new WasmCallInstruction( ref, codePos );
break;
//TODO case 185: // invokeinterface
//TODO case 186: // invokedynamic
//TODO case 187: // new
//TODO case 188: // newarray
//TODO case 189: // anewarray
//TODO case 190: // arraylength
//TODO case 191: // athrow
//TODO case 192: // checkcast
//TODO case 193: // instanceof
//TODO case 194: // monitorenter
//TODO case 195: // monitorexit
//TODO case 196: // wide
//TODO case 197: // multianewarray
//TODO case 198: // ifnull
//TODO case 199: // ifnonnull
//TODO case 200: // goto_w
//TODO case 201: // jsr_w
default:
throw new WasmException( "Unimplemented Java byte code operation: " + op, byteCode.getLineNumber() );
}
if( instr != null ) {
instructions.add( instr );
}
}
branchManager.calculate();
branchManager.handle( byteCode ); // add branch operations
if( !endWithReturn && hasReturn ) {
// 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
instructions.add( new WasmBlockInstruction( WasmBlockOperator.UNREACHABLE, null, byteCode.getCodePosition() ) );
}
} catch( WasmException ex ) {
throw ex;
} catch( Exception ex ) {
throw WasmException.create( ex, byteCode.getLineNumber() );
}
}
/**
* 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 lineNumber = byteCode.getLineNumber();
int codePos = byteCode.getCodePosition();
int startPosition = codePos;
int padding = startPosition % 4;
if( padding > 0 ) {
byteCode.skip( 4 - padding );
}
startPosition--;
int switchValuestartPosition = findPreviousPushCodePosition();
int defaultPosition = startPosition + byteCode.readInt();
int[] keys;
int[] positions;
if( isLookupSwitch ) { // lookupswitch
int count = byteCode.readInt();
keys = new int[count];
positions = new int[count];
for( int i = 0; i < count; i++ ) {
keys[i] = byteCode.readInt();
positions[i] = startPosition + byteCode.readInt();
}
int tempI32 = localVariables.getTempI32();
int block = 0;
int defaultBlock = -1;
int currentPos = -1;
instructions.add( new WasmLoadStoreInstruction( false, tempI32, localVariables, codePos ) );
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 ) {
instructions.add( new WasmLoadStoreInstruction( true, tempI32, localVariables, codePos ) );
instructions.add( new WasmConstInstruction( keys[i], ValueType.i32, codePos ) );
instructions.add( new WasmNumericInstruction( NumericOperator.eq, ValueType.i32, codePos ) );
instructions.add( new WasmBlockInstruction( WasmBlockOperator.BR_IF, block, codePos ) );
}
}
block++;
} while( true );
if( defaultBlock < 0 ) {
defaultBlock = block;
}
instructions.add( new WasmBlockInstruction( WasmBlockOperator.BR, defaultBlock, codePos ) );
} 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();
}
if( low != 0 ) { // the br_table starts ever with the value 0. That we need to subtract the start value if it different
instructions.add( new WasmConstInstruction( low, ValueType.i32, codePos ) );
instructions.add( new WasmNumericInstruction( NumericOperator.sub, ValueType.i32, codePos ) );
}
}
branchManager.addSwitchOperator( switchValuestartPosition, 0, lineNumber, keys, positions, defaultPosition );
}
/**
* We need one value from the stack inside of a block. We need to find the code position on which the block can
* start. If this a function call or numeric expression this can be complex to find the right point.
*
* @return the code position
*/
private int findPreviousPushCodePosition() {
int valueCount = 0;
for( int i = instructions.size() - 1; i >= 0; i-- ) {
WasmInstruction instr = instructions.get( i );
ValueType valueType = instr.getPushValueType();
if( valueType != null ) {
valueCount++;
}
valueCount -= instr.getPopCount();
if( valueCount == 1 ) {
return instr.getCodePosition();
}
}
throw new WasmException( "Switch start position not found", -1 ); // should never occur
}
/**
* 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;
}
/**
* Create a WasmLoadStoreInstruction.
*
* @param valueType
* the value type
* @param load
* true: if load
* @param idx
* the memory/slot idx of the variable
* @param codePos
* the code position/offset in the Java method
* @return the WasmLoadStoreInstruction
*/
@Nonnull
private WasmLoadStoreInstruction loadStore( ValueType valueType, boolean load, @Nonnegative int idx, int codePos ) {
localVariables.use( valueType, idx );
return new WasmLoadStoreInstruction( load, idx, localVariables, codePos );
}
/**
* Handle the if<condition> of the Java byte code. This Java instruction compare the first stack value with value 0.
* Important: In the Java IF expression 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 compareOp
* The condition for the continue of a loop.
* @param byteCode
* current byte code stream to read the target offset.
* @param codePos
* the code position/offset in the Java method
*
* @throws IOException
* if any I/O errors occur.
*/
private void opIfCondition( NumericOperator compareOp, CodeInputStream byteCode, int codePos ) throws IOException {
instructions.add( new WasmConstInstruction( 0, ValueType.i32, codePos ) );
opIfCompareCondition( compareOp, byteCode, codePos );
}
/**
* Handle the if<condition> of the Java byte code. This Java instruction compare 2 values from stack.
* Important: In the Java IF expression 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 compareOp
* The condition for the continue of a loop.
* @param byteCode
* current byte code stream to read the target offset.
* @param codePos
* the code position/offset in the Java method
*
* @throws IOException
* if any I/O errors occur.
*/
private void opIfCompareCondition( NumericOperator compareOp, CodeInputStream byteCode, int codePos ) throws IOException {
int offset = byteCode.readShort();
WasmNumericInstruction compare = new WasmNumericInstruction( compareOp, ValueType.i32, codePos );
branchManager.addIfOperator( codePos, offset, byteCode.getLineNumber(), compare );
instructions.add( compare );
}
/**
* 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.
* @param codePos
* the code position/offset in the Java method
* @throws IOException
* if any I/O errors occur.
*/
private void opCompare( ValueType valueType, CodeInputStream byteCode, int codePos ) throws IOException {
codePos = byteCode.getCodePosition();
NumericOperator numOp;
int nextOp = byteCode.read();
switch( nextOp ){
case 153: // ifeq
numOp = NumericOperator.eq;
break;
case 154: // ifne
numOp = NumericOperator.ne;
break;
case 155: // iflt
numOp = NumericOperator.lt;
break;
case 156: // ifge
numOp = NumericOperator.ge;
break;
case 157: // ifgt
numOp = NumericOperator.gt;
break;
case 158: // ifle
numOp = NumericOperator.le;
break;
default:
throw new WasmException( "Unexpected compare sub operation: " + nextOp, -1 );
}
int offset = byteCode.readShort();
WasmNumericInstruction compare = new WasmNumericInstruction( numOp, valueType, codePos );
branchManager.addIfOperator( codePos, offset, byteCode.getLineNumber(), compare );
instructions.add( compare );
}
}

View File

@ -16,19 +16,16 @@
package de.inetsoftware.jwebassembly.module;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
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.LocalVariableTable;
import de.inetsoftware.classparser.MethodInfo;
import de.inetsoftware.jwebassembly.WasmException;
@ -40,19 +37,13 @@ import de.inetsoftware.jwebassembly.WasmException;
*/
public class ModuleGenerator {
private final ModuleWriter writer;
private final ModuleWriter writer;
private ValueType returnType;
private final JavaMethodWasmCodeBuilder codeBuilder = new JavaMethodWasmCodeBuilder();
private LocaleVariableManager localVariables = new LocaleVariableManager();
private String sourceFile;
private String sourceFile;
private String className;
private final List<WasmInstruction> instructions = new ArrayList<>();
private BranchManger branchManager = new BranchManger( instructions );
private String className;
/**
* Create a new generator.
@ -132,7 +123,7 @@ public class ModuleGenerator {
String impoarModule = (String)annotationValues.get( "module" );
String importName = (String)annotationValues.get( "name" );
writer.prepareImport( name, impoarModule, importName );
writeMethodSignature( method, null );
writeMethodSignature( method, null, null );
} else {
writer.prepareFunction( name );
}
@ -158,15 +149,10 @@ public class ModuleGenerator {
writeExport( name, method );
writer.writeMethodStart( name );
localVariables.reset();
branchManager.reset( code.getExceptionTable() );
codeBuilder.buildCode( code, !method.getDescription().endsWith( ")V" ) );
writeMethodSignature( method, code.getLocalVariableTable(), codeBuilder );
byteCode = code.getByteCode();
writeCode( byteCode, method.getConstantPool() );
localVariables.calculate();
writeMethodSignature( method, code.getLocalVariableTable() );
for( WasmInstruction instruction : instructions ) {
for( WasmInstruction instruction : codeBuilder.getInstructions() ) {
instruction.writeTo( writer );
}
writer.writeMethodFinish();
@ -204,12 +190,16 @@ public class ModuleGenerator {
*
* @param method
* the method
* @param variables
* Java variable table with names of the variables for debugging
* @param codeBuilder
* the calculated variables
* @throws IOException
* if any I/O error occur
* @throws WasmException
* if some Java code can't converted
*/
private void writeMethodSignature( MethodInfo method, LocalVariableTable variables ) throws IOException, WasmException {
private void writeMethodSignature( MethodInfo method, @Nullable LocalVariableTable variables, WasmCodeBuilder codeBuilder ) throws IOException, WasmException {
String signature = method.getDescription();
String kind = "param";
int paramCount = 0;
@ -231,723 +221,18 @@ public class ModuleGenerator {
writer.writeMethodParam( kind, type, name );
}
}
this.returnType = type;
List<ValueType> localTypes = localVariables.getLocalTypes( paramCount );
for( int i = 0; i < localTypes.size(); i++ ) {
type = localTypes.get( i );
String name = null;
if( variables != null ) {
name = variables.getPosition( paramCount ).getName( method.getConstantPool() );
if( codeBuilder != null ) {
List<ValueType> localTypes = codeBuilder.getLocalTypes( paramCount );
for( int i = 0; i < localTypes.size(); i++ ) {
type = localTypes.get( i );
String name = null;
if( variables != null ) {
name = variables.getPosition( paramCount ).getName( method.getConstantPool() );
}
writer.writeMethodParam( "local", type, name );
}
writer.writeMethodParam( "local", type, name );
}
writer.writeMethodParamFinish( );
}
/**
* Write the byte code of a method.
*
* @param byteCode
* a stream of byte code
* @param constantPool
* the constant pool of the the current class
* @throws WasmException
* if some Java code can't converted
*/
private void writeCode( CodeInputStream byteCode, ConstantPool constantPool ) throws WasmException {
instructions.clear();
boolean endWithReturn = false;
try {
while( byteCode.available() > 0 ) {
WasmInstruction instr = null;
int codePos = byteCode.getCodePosition();
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
instr = new WasmConstInstruction( Integer.valueOf( op - 3 ), ValueType.i32, codePos );
break;
case 9: // lconst_0
case 10: // lconst_1
instr = new WasmConstInstruction( Long.valueOf( op - 9 ), ValueType.i64, codePos );
break;
case 11: // fconst_0
case 12: // fconst_1
case 13: // fconst_2
instr = new WasmConstInstruction( Float.valueOf( op - 11 ), ValueType.f32, codePos );
break;
case 14: // dconst_0
case 15: // dconst_1
instr = new WasmConstInstruction( Double.valueOf( op - 14 ), ValueType.f64, codePos );
break;
case 16: // bipush
instr = new WasmConstInstruction( Integer.valueOf( byteCode.readByte() ), ValueType.i32, codePos );
break;
case 17: // sipush
instr = new WasmConstInstruction( Integer.valueOf( byteCode.readShort() ), ValueType.i32, codePos );
break;
case 18: // ldc
instr = new WasmConstInstruction( (Number)constantPool.get( byteCode.readUnsignedByte() ), codePos );
break;
case 19: // ldc_w
case 20: // ldc2_w
instr = new WasmConstInstruction( (Number)constantPool.get( byteCode.readUnsignedShort() ), codePos );
break;
case 21: // iload
instr = loadStore( ValueType.i32, true, byteCode.readUnsignedByte(), codePos );
break;
case 22: // lload
instr = loadStore( ValueType.i64, true, byteCode.readUnsignedByte(), codePos );
break;
case 23: // fload
instr = loadStore( ValueType.f32, true, byteCode.readUnsignedByte(), codePos );
break;
case 24: // dload
instr = loadStore( ValueType.f64, true, byteCode.readUnsignedByte(), codePos );
break;
//TODO case 25: // aload
case 26: // iload_0
case 27: // iload_1
case 28: // iload_2
case 29: // iload_3
instr = loadStore( ValueType.i32, true, op - 26, codePos );
break;
case 30: // lload_0
case 31: // lload_1
case 32: // lload_2
case 33: // lload_3
instr = loadStore( ValueType.i64, true, op - 30, codePos );
break;
case 34: // fload_0
case 35: // fload_1
case 36: // fload_2
case 37: // fload_3
instr = loadStore( ValueType.f32, true, op - 34, codePos );
break;
case 38: // dload_0
case 39: // dload_1
case 40: // dload_2
case 41: // dload_3
instr = loadStore( ValueType.f64, true, op - 38, codePos );
break;
//TODO case 42: //aload_0
//TODO case 43: //aload_1
//TODO case 44: //aload_2
//TODO case 45: //aload_3
//TODO case 46: // iaload
//TODO case 47: // laload
//TODO case 48: // faload
//TODO case 49: // daload
//TODO case 50: // aaload
//TODO case 51: // baload
//TODO case 52: // caload
//TODO case 53: // saload
case 54: // istore
instr = loadStore( ValueType.i32, false, byteCode.readUnsignedByte(), codePos );
break;
case 55: // lstore
instr = loadStore( ValueType.i64, false, byteCode.readUnsignedByte(), codePos );
break;
case 56: // fstore
instr = loadStore( ValueType.f32, false, byteCode.readUnsignedByte(), codePos );
break;
case 57: // dstore
instr = loadStore( ValueType.f64, false, byteCode.readUnsignedByte(), codePos );
break;
//TODO case 58: // astore
case 59: // istore_0
case 60: // istore_1
case 61: // istore_2
case 62: // istore_3
instr = loadStore( ValueType.i32, false, op - 59, codePos );
break;
case 63: // lstore_0
case 64: // lstore_1
case 65: // lstore_2
case 66: // lstore_3
instr = loadStore( ValueType.i64, false, op - 63, codePos );
break;
case 67: // fstore_0
case 68: // fstore_1
case 69: // fstore_2
case 70: // fstore_3
instr = loadStore( ValueType.f32, false, op - 67, codePos );
break;
case 71: // dstore_0
case 72: // dstore_1
case 73: // dstore_2
case 74: // dstore_3
instr = loadStore( ValueType.f64, false, op - 71, codePos );
break;
case 75: // astore_0
case 76: // astore_1
case 77: // astore_2
case 78: // astore_3
instr = loadStore( ValueType.anyref, false, op - 75, codePos );
break;
//TODO case 79: // iastore
//TODO case 80: // lastore
//TODO case 81: // fastore
//TODO case 82: // dastore
//TODO case 83: // aastore
//TODO case 84: // bastore
//TODO case 85: // castore
//TODO case 86: // sastore
case 87: // pop
case 88: // pop2
instr = new WasmBlockInstruction( WasmBlockOperator.DROP, null, codePos );
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, className, byteCode.getLineNumber() );
case 96: // iadd
instr = new WasmNumericInstruction( NumericOperator.add, ValueType.i32, codePos);
break;
case 97: // ladd
instr = new WasmNumericInstruction( NumericOperator.add, ValueType.i64, codePos );
break;
case 98: // fadd
instr = new WasmNumericInstruction( NumericOperator.add, ValueType.f32, codePos );
break;
case 99: // dadd
instr = new WasmNumericInstruction( NumericOperator.add, ValueType.f64, codePos );
break;
case 100: // isub
instr = new WasmNumericInstruction( NumericOperator.sub, ValueType.i32, codePos );
break;
case 101: // lsub
instr = new WasmNumericInstruction( NumericOperator.sub, ValueType.i64, codePos );
break;
case 102: // fsub
instr = new WasmNumericInstruction( NumericOperator.sub, ValueType.f32, codePos );
break;
case 103: // dsub
instr = new WasmNumericInstruction( NumericOperator.sub, ValueType.f64, codePos );
break;
case 104: // imul;
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.i32, codePos );
break;
case 105: // lmul
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.i64, codePos );
break;
case 106: // fmul
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.f32, codePos );
break;
case 107: // dmul
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.f64, codePos );
break;
case 108: // idiv
instr = new WasmNumericInstruction( NumericOperator.div, ValueType.i32, codePos );
break;
case 109: // ldiv
instr = new WasmNumericInstruction( NumericOperator.div, ValueType.i64, codePos );
break;
case 110: // fdiv
instr = new WasmNumericInstruction( NumericOperator.div, ValueType.f32, codePos );
break;
case 111: // ddiv
instr = new WasmNumericInstruction( NumericOperator.div, ValueType.f64, codePos );
break;
case 112: // irem
instr = new WasmNumericInstruction( NumericOperator.rem, ValueType.i32, codePos );
break;
case 113: // lrem
instr = new WasmNumericInstruction( NumericOperator.rem, ValueType.i64, codePos );
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, className, byteCode.getLineNumber() );
case 116: // ineg
instructions.add( new WasmConstInstruction( -1, ValueType.i32, codePos ) );
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.i32, codePos );
break;
case 117: // lneg
instructions.add( new WasmConstInstruction( (long)-1, ValueType.i64, codePos ) ) ;
instr = new WasmNumericInstruction( NumericOperator.mul, ValueType.i64, codePos );
break;
case 118: // fneg
instr = new WasmNumericInstruction( NumericOperator.neg, ValueType.f32, codePos );
break;
case 119: // dneg
instr = new WasmNumericInstruction( NumericOperator.neg, ValueType.f64, codePos );
break;
case 120: // ishl
instr = new WasmNumericInstruction( NumericOperator.shl, ValueType.i32, codePos );
break;
case 121: // lshl
instructions.add( new WasmConvertInstruction( ValueTypeConvertion.i2l, codePos ) ); // the shift parameter must be of type long!!!
instr = new WasmNumericInstruction( NumericOperator.shl, ValueType.i64, codePos );
break;
case 122: // ishr
instr = new WasmNumericInstruction( NumericOperator.shr_s, ValueType.i32, codePos );
break;
case 123: // lshr
instructions.add( new WasmConvertInstruction( ValueTypeConvertion.i2l, codePos ) ); // the shift parameter must be of type long!!!
instr = new WasmNumericInstruction( NumericOperator.shr_s, ValueType.i64, codePos );
break;
case 124: // iushr
instr = new WasmNumericInstruction( NumericOperator.shr_u, ValueType.i32, codePos );
break;
case 125: // lushr
instructions.add( new WasmConvertInstruction( ValueTypeConvertion.i2l, codePos ) ); // the shift parameter must be of type long!!!
instr = new WasmNumericInstruction( NumericOperator.shr_u, ValueType.i64, codePos );
break;
case 126: // iand
instr = new WasmNumericInstruction( NumericOperator.and, ValueType.i32, codePos );
break;
case 127: // land
instr = new WasmNumericInstruction( NumericOperator.and, ValueType.i64, codePos );
break;
case 128: // ior
instr = new WasmNumericInstruction( NumericOperator.or, ValueType.i32, codePos );
break;
case 129: // lor
instr = new WasmNumericInstruction( NumericOperator.or, ValueType.i64, codePos );
break;
case 130: // ixor
instr = new WasmNumericInstruction( NumericOperator.xor, ValueType.i32, codePos );
break;
case 131: // lxor
instr = new WasmNumericInstruction( NumericOperator.xor, ValueType.i64, codePos );
break;
case 132: // iinc
int idx = byteCode.readUnsignedByte();
instructions.add( new WasmLoadStoreInstruction( true, idx, localVariables, codePos ) );
instructions.add( new WasmConstInstruction( (int)byteCode.readByte(), ValueType.i32, codePos ) );
instructions.add( new WasmNumericInstruction( NumericOperator.add, ValueType.i32, codePos) );
instr = new WasmLoadStoreInstruction( false, idx, localVariables, codePos );
break;
case 133: // i2l
instr = new WasmConvertInstruction( ValueTypeConvertion.i2l, codePos );
break;
case 134: // i2f
instr = new WasmConvertInstruction( ValueTypeConvertion.i2f, codePos );
break;
case 135: // i2d
instr = new WasmConvertInstruction( ValueTypeConvertion.i2d, codePos );
break;
case 136: // l2i
instr = new WasmConvertInstruction( ValueTypeConvertion.l2i, codePos );
break;
case 137: // l2f
instr = new WasmConvertInstruction( ValueTypeConvertion.l2f, codePos );
break;
case 138: // l2d
instr = new WasmConvertInstruction( ValueTypeConvertion.l2d, codePos );
break;
case 139: // f2i
instr = new WasmConvertInstruction( ValueTypeConvertion.f2i, codePos );
break;
case 140: // f2l
instr = new WasmConvertInstruction( ValueTypeConvertion.f2l, codePos );
break;
case 141: // f2d
instr = new WasmConvertInstruction( ValueTypeConvertion.f2d, codePos );
break;
case 142: // d2i
instr = new WasmConvertInstruction( ValueTypeConvertion.d2i, codePos );
break;
case 143: // d2l
instr = new WasmConvertInstruction( ValueTypeConvertion.d2l, codePos );
break;
case 144: // d2f
instr = new WasmConvertInstruction( ValueTypeConvertion.d2f, codePos );
break;
case 145: // i2b
instr = new WasmConvertInstruction( ValueTypeConvertion.i2b, codePos );
break;
case 146: // i2c
instructions.add( new WasmConstInstruction( 0xFFFF, ValueType.i32, codePos ) );
instr = new WasmNumericInstruction( NumericOperator.and, ValueType.i32, codePos );
break;
case 147: // i2s
instr = new WasmConvertInstruction( ValueTypeConvertion.i2s, codePos );
break;
case 148: // lcmp
opCompare( ValueType.i64, byteCode, codePos );
break;
case 149: // fcmpl
case 150: // fcmpg
opCompare( ValueType.f32, byteCode, codePos );
break;
case 151: // dcmpl
case 152: // dcmpg
opCompare( ValueType.f64, byteCode, codePos );
break;
case 153: // ifeq
opIfCondition( NumericOperator.eq, byteCode, codePos );
break;
case 154: // ifne
opIfCondition( NumericOperator.ne, byteCode, codePos );
break;
case 155: // iflt
opIfCondition( NumericOperator.lt, byteCode, codePos );
break;
case 156: // ifge
opIfCondition( NumericOperator.ge, byteCode, codePos );
break;
case 157: // ifgt
opIfCondition( NumericOperator.gt, byteCode, codePos );
break;
case 158: // ifle
opIfCondition( NumericOperator.le, byteCode, codePos );
break;
case 159: // if_icmpeq
opIfCompareCondition( NumericOperator.eq, byteCode, codePos );
break;
case 160: // if_icmpne
opIfCompareCondition( NumericOperator.ne, byteCode, codePos );
break;
case 161: // if_icmplt
opIfCompareCondition( NumericOperator.lt, byteCode, codePos );
break;
case 162: // if_icmpge
opIfCompareCondition( NumericOperator.ge, byteCode, codePos );
break;
case 163: // if_icmpgt
opIfCompareCondition( NumericOperator.gt, byteCode, codePos );
break;
case 164: // if_icmple
opIfCompareCondition( NumericOperator.le, byteCode, codePos );
break;
//TODO case 165: // if_acmpeq
//TODO case 166: // if_acmpne
case 167: // goto
int offset = byteCode.readShort();
branchManager.addGotoOperator( codePos, offset, byteCode.getLineNumber() );
instr = new WasmNopInstruction( codePos ); // marker of the line number for the branch manager
break;
//TODO case 168: // jsr
//TODO case 169: // ret
case 170: // tableswitch
case 171: // lookupswitch
writeSwitchCode( byteCode, op == 171 );
break;
case 172: // ireturn
case 173: // lreturn
case 174: // freturn
case 175: // dreturn
case 176: // areturn
case 177: // return void
ValueType type = null;
switch ( op ) {
case 172: // ireturn
type = ValueType.i32;
break;
case 173: // lreturn
type = ValueType.i64;
break;
case 174: // freturn
type = ValueType.f32;
break;
case 175: // dreturn
type = ValueType.f64;
break;
case 176: // areturn
type = ValueType.anyref;
break;
}
instr = new WasmBlockInstruction( WasmBlockOperator.RETURN, type, codePos );
endWithReturn = true;
break;
case 178: // getstatic
ConstantRef ref = (ConstantRef)constantPool.get( byteCode.readUnsignedShort() );
instr = new WasmGlobalInstruction( true, ref, codePos );
break;
case 179: // putstatic
ref = (ConstantRef)constantPool.get( byteCode.readUnsignedShort() );
instr = new WasmGlobalInstruction( false, ref, codePos );
break;
//TODO case 180: // getfield
//TODO case 181: // putfield
//TODO case 182: // invokevirtual
//TODO case 183: // invokespecial
case 184: // invokestatic
idx = byteCode.readUnsignedShort();
ref = (ConstantRef)constantPool.get( idx );
instr = new WasmCallInstruction( ref, codePos );
break;
//TODO case 185: // invokeinterface
//TODO case 186: // invokedynamic
//TODO case 187: // new
//TODO case 188: // newarray
//TODO case 189: // anewarray
//TODO case 190: // arraylength
//TODO case 191: // athrow
//TODO case 192: // checkcast
//TODO case 193: // instanceof
//TODO case 194: // monitorenter
//TODO case 195: // monitorexit
//TODO case 196: // wide
//TODO case 197: // multianewarray
//TODO case 198: // ifnull
//TODO case 199: // ifnonnull
//TODO case 200: // goto_w
//TODO case 201: // jsr_w
default:
throw new WasmException( "Unimplemented Java byte code operation: " + op, sourceFile, className, byteCode.getLineNumber() );
}
if( instr != null ) {
instructions.add( instr );
}
}
branchManager.calculate();
branchManager.handle( byteCode ); // add branch operations
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
instructions.add( new WasmBlockInstruction( WasmBlockOperator.UNREACHABLE, null, byteCode.getCodePosition() ) );
}
} catch( WasmException ex ) {
throw ex;
} catch( Exception ex ) {
throw WasmException.create( ex, sourceFile, className, byteCode.getLineNumber() );
}
}
/**
* 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 lineNumber = byteCode.getLineNumber();
int codePos = byteCode.getCodePosition();
int startPosition = codePos;
int padding = startPosition % 4;
if( padding > 0 ) {
byteCode.skip( 4 - padding );
}
startPosition--;
int switchValuestartPosition = findPreviousPushCodePosition();
int defaultPosition = startPosition + byteCode.readInt();
int[] keys;
int[] positions;
if( isLookupSwitch ) { // lookupswitch
int count = byteCode.readInt();
keys = new int[count];
positions = new int[count];
for( int i = 0; i < count; i++ ) {
keys[i] = byteCode.readInt();
positions[i] = startPosition + byteCode.readInt();
}
int tempI32 = localVariables.getTempI32();
int block = 0;
int defaultBlock = -1;
int currentPos = -1;
instructions.add( new WasmLoadStoreInstruction( false, tempI32, localVariables, codePos ) );
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 ) {
instructions.add( new WasmLoadStoreInstruction( true, tempI32, localVariables, codePos ) );
instructions.add( new WasmConstInstruction( keys[i], ValueType.i32, codePos ) );
instructions.add( new WasmNumericInstruction( NumericOperator.eq, ValueType.i32, codePos ) );
instructions.add( new WasmBlockInstruction( WasmBlockOperator.BR_IF, block, codePos ) );
}
}
block++;
} while( true );
if( defaultBlock < 0 ) {
defaultBlock = block;
}
instructions.add( new WasmBlockInstruction( WasmBlockOperator.BR, defaultBlock, codePos ) );
} 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();
}
if( low != 0 ) { // the br_table starts ever with the value 0. That we need to subtract the start value if it different
instructions.add( new WasmConstInstruction( low, ValueType.i32, codePos ) );
instructions.add( new WasmNumericInstruction( NumericOperator.sub, ValueType.i32, codePos ) );
}
}
branchManager.addSwitchOperator( switchValuestartPosition, 0, lineNumber, keys, positions, defaultPosition );
}
/**
* We need one value from the stack inside of a block. We need to find the code position on which the block can
* start. If this a function call or numeric expression this can be complex to find the right point.
*
* @return the code position
*/
private int findPreviousPushCodePosition() {
int valueCount = 0;
for( int i = instructions.size() - 1; i >= 0; i-- ) {
WasmInstruction instr = instructions.get( i );
ValueType valueType = instr.getPushValueType();
if( valueType != null ) {
valueCount++;
}
valueCount -= instr.getPopCount();
if( valueCount == 1 ) {
return instr.getCodePosition();
}
}
throw new WasmException( "Switch start position not found", sourceFile, className, -1 ); // should never occur
}
/**
* 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;
}
/**
* Create a WasmLoadStoreInstruction.
*
* @param valueType
* the value type
* @param load
* true: if load
* @param idx
* the memory/slot idx of the variable
* @param codePos
* the code position/offset in the Java method
* @return the WasmLoadStoreInstruction
*/
@Nonnull
private WasmLoadStoreInstruction loadStore( ValueType valueType, boolean load, @Nonnegative int idx, int codePos ) {
localVariables.use( valueType, idx );
return new WasmLoadStoreInstruction( load, idx, localVariables, codePos );
}
/**
* Handle the if<condition> of the Java byte code. This Java instruction compare the first stack value with value 0.
* Important: In the Java IF expression 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 compareOp
* The condition for the continue of a loop.
* @param byteCode
* current byte code stream to read the target offset.
* @param codePos
* the code position/offset in the Java method
*
* @throws IOException
* if any I/O errors occur.
*/
private void opIfCondition( NumericOperator compareOp, CodeInputStream byteCode, int codePos ) throws IOException {
instructions.add( new WasmConstInstruction( 0, ValueType.i32, codePos ) );
opIfCompareCondition( compareOp, byteCode, codePos );
}
/**
* Handle the if<condition> of the Java byte code. This Java instruction compare 2 values from stack.
* Important: In the Java IF expression 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 compareOp
* The condition for the continue of a loop.
* @param byteCode
* current byte code stream to read the target offset.
* @param codePos
* the code position/offset in the Java method
*
* @throws IOException
* if any I/O errors occur.
*/
private void opIfCompareCondition( NumericOperator compareOp, CodeInputStream byteCode, int codePos ) throws IOException {
int offset = byteCode.readShort();
WasmNumericInstruction compare = new WasmNumericInstruction( compareOp, ValueType.i32, codePos );
branchManager.addIfOperator( codePos, offset, byteCode.getLineNumber(), compare );
instructions.add( compare );
}
/**
* 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.
* @param codePos
* the code position/offset in the Java method
* @throws IOException
* if any I/O errors occur.
*/
private void opCompare( ValueType valueType, CodeInputStream byteCode, int codePos ) throws IOException {
codePos = byteCode.getCodePosition();
NumericOperator numOp;
int nextOp = byteCode.read();
switch( nextOp ){
case 153: // ifeq
numOp = NumericOperator.eq;
break;
case 154: // ifne
numOp = NumericOperator.ne;
break;
case 155: // iflt
numOp = NumericOperator.lt;
break;
case 156: // ifge
numOp = NumericOperator.ge;
break;
case 157: // ifgt
numOp = NumericOperator.gt;
break;
case 158: // ifle
numOp = NumericOperator.le;
break;
default:
throw new WasmException( "Unexpected compare sub operation: " + nextOp, sourceFile, className, -1 );
}
int offset = byteCode.readShort();
WasmNumericInstruction compare = new WasmNumericInstruction( numOp, valueType, codePos );
branchManager.addIfOperator( codePos, offset, byteCode.getLineNumber(), compare );
instructions.add( compare );
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 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.util.List;
/**
* Base class for Code Building.
*
* @author Volker Berlin
*/
public abstract class WasmCodeBuilder {
/**
* Get the list of instructions
*
* @return the list
*/
abstract List<WasmInstruction> getInstructions();
/**
* Get the data types of the local variables. The value is only valid until the next call.
*
* @param paramCount
* the count of method parameter which should be exclude
* @return the reused list with fresh values
*/
abstract List<ValueType> getLocalTypes( int paramCount );
}