From d26b9bbb94df9139daac70d027b9d37d68c4a290 Mon Sep 17 00:00:00 2001 From: Volker Berlin Date: Sun, 25 Mar 2018 12:57:04 +0200 Subject: [PATCH] first step of a "if" implementation --- .../binary/BinaryModuleWriter.java | 51 +++++++++++++- .../jwebassembly/module/BlockOperator.java | 12 ++++ .../jwebassembly/module/BranchManger.java | 69 +++++++++++++++++++ .../jwebassembly/module/ModuleWriter.java | 42 +++++++++++ .../jwebassembly/module/NumericOperator.java | 2 + .../jwebassembly/module/ValueType.java | 1 + .../jwebassembly/text/TextModuleWriter.java | 22 ++++++ .../runtime/ControlFlowOperators.java | 54 +++++++++++++++ 8 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 src/de/inetsoftware/jwebassembly/module/BlockOperator.java create mode 100644 src/de/inetsoftware/jwebassembly/module/BranchManger.java create mode 100644 test/de/inetsoftware/jwebassembly/runtime/ControlFlowOperators.java diff --git a/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java b/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java index f16f03f..0533e45 100644 --- a/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java +++ b/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java @@ -27,6 +27,7 @@ import javax.annotation.Nullable; import de.inetsoftware.classparser.MethodInfo; import de.inetsoftware.jwebassembly.WasmException; +import de.inetsoftware.jwebassembly.module.BlockOperator; import de.inetsoftware.jwebassembly.module.ModuleWriter; import de.inetsoftware.jwebassembly.module.NumericOperator; import de.inetsoftware.jwebassembly.module.ValueType; @@ -437,9 +438,39 @@ public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcod break; } break; + case eq: + switch( valueType ) { + case i32: + op = I32_EQ; + break; + case i64: + op = I64_EQ; + break; + case f32: + op = F32_EQ; + break; + case f64: + op = F64_EQ; + break; + } + case ne: + switch( valueType ) { + case i32: + op = I32_NE; + break; + case i64: + op = I64_NE; + break; + case f32: + op = F32_NE; + break; + case f64: + op = F64_NE; + break; + } } if( op == 0 ) { - throw new Error(); + throw new Error( valueType + "." + numOp ); } codeStream.write( op ); } @@ -483,4 +514,22 @@ public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcod } codeStream.writeVaruint32( func.id ); } + + /** + * {@inheritDoc} + */ + @Override + protected void writeBlockCode( BlockOperator op ) throws IOException { + switch( op ) { + case IF: + codeStream.write( IF ); + codeStream.write( 0x40 ); // void; the return type of the block. currently we does not use it + break; + case END: + codeStream.write( END ); + break; + default: + throw new Error( "Unknown block: " + op ); + } + } } diff --git a/src/de/inetsoftware/jwebassembly/module/BlockOperator.java b/src/de/inetsoftware/jwebassembly/module/BlockOperator.java new file mode 100644 index 0000000..bcc5f86 --- /dev/null +++ b/src/de/inetsoftware/jwebassembly/module/BlockOperator.java @@ -0,0 +1,12 @@ +package de.inetsoftware.jwebassembly.module; + +/** + * Block operators + * + * @author Volker Berlin + * + */ +public enum BlockOperator { + IF, + END, +} diff --git a/src/de/inetsoftware/jwebassembly/module/BranchManger.java b/src/de/inetsoftware/jwebassembly/module/BranchManger.java new file mode 100644 index 0000000..cb03971 --- /dev/null +++ b/src/de/inetsoftware/jwebassembly/module/BranchManger.java @@ -0,0 +1,69 @@ +package de.inetsoftware.jwebassembly.module; + +import java.io.IOException; +import java.util.ArrayDeque; + +import de.inetsoftware.classparser.CodeInputStream; + +/** + * This calculate the goto offsets from Java back to block operations + * + * @author Volker Berlin + * + */ +class BranchManger { + + private final ArrayDeque stack = new ArrayDeque<>(); + + /** + * Start a new block. + * + * @param op + * the start operation + * @param basePosition + * the byte position of the start position + * @param offset + * the relative jump position + */ + void start( BlockOperator op, int basePosition, int offset ) { + stack.push( new Block( op, basePosition, offset ) ); + } + + /** + * Check on every instruction position if there any branch is ending + * + * @param byteCode + * the byte code stream + * @param writer + * the current module writer + * @throws IOException + * if any I/O exception occur + */ + void handle( CodeInputStream byteCode, ModuleWriter writer ) throws IOException { + int position = byteCode.getCodePosition(); + Block block = stack.peek(); + if( block != null ) { + if( block.endPosition == position ) { + writer.writeBlockCode( BlockOperator.END ); + stack.pop(); + } + } + } + + /** + * Description of single block/branch + */ + private static class Block { + private BlockOperator op; + + private int basePosition; + + private int endPosition; + + private Block( BlockOperator op, int basePosition, int offset ) { + this.op = op; + this.basePosition = basePosition; + this.endPosition = basePosition + offset; + } + } +} diff --git a/src/de/inetsoftware/jwebassembly/module/ModuleWriter.java b/src/de/inetsoftware/jwebassembly/module/ModuleWriter.java index 99af813..8742036 100644 --- a/src/de/inetsoftware/jwebassembly/module/ModuleWriter.java +++ b/src/de/inetsoftware/jwebassembly/module/ModuleWriter.java @@ -52,6 +52,8 @@ public abstract class ModuleWriter implements Closeable { private String sourceFile; + private BranchManger branchManager = new BranchManger(); + /** * Prepare the content of the class. * @@ -303,6 +305,7 @@ public abstract class ModuleWriter implements Closeable { private void writeCodeChunk( CodeInputStream byteCode, int lineNumber, ConstantPool constantPool ) throws WasmException { try { while( byteCode.available() > 0 ) { + branchManager.handle( byteCode, this ); int op = byteCode.readUnsignedByte(); switch( op ) { case 2: // iconst_m1 @@ -493,6 +496,14 @@ public abstract class ModuleWriter implements Closeable { case 136: // l2i writeCast( ValueTypeConvertion.l2i ); break; + case 153: // ifeq + opIfCondition( NumericOperator.ne, byteCode ); + break; + case 167: // goto + int baseCodePosition = byteCode.getCodePosition() - 1; + int offset = byteCode.readUnsignedShort(); + if(true)throw new WasmException( "Unimplemented byte code operation: " + op, sourceFile, lineNumber ); + break; case 172: // ireturn case 173: // lreturn case 174: // freturn @@ -514,6 +525,27 @@ public abstract class ModuleWriter implements Closeable { } } + /** + * Handle the if 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 { + int baseCodePosition = byteCode.getCodePosition() - 1; + int offset = byteCode.readUnsignedShort(); + branchManager.start( BlockOperator.IF, baseCodePosition, offset ); + writeConstInt( 0 ); + writeNumericOperator( numOp, ValueType.i32 ); + writeBlockCode( BlockOperator.IF ); + } + /** * Write a constant value. * @@ -670,4 +702,14 @@ public abstract class ModuleWriter implements Closeable { * if any I/O error occur */ protected abstract void writeFunctionCall( String name ) throws IOException; + + /** + * Write a block/branch code + * + * @param op + * the operation + * @throws IOException + * if any I/O error occur + */ + protected abstract void writeBlockCode( BlockOperator op ) throws IOException; } diff --git a/src/de/inetsoftware/jwebassembly/module/NumericOperator.java b/src/de/inetsoftware/jwebassembly/module/NumericOperator.java index 15408bc..1102ce8 100644 --- a/src/de/inetsoftware/jwebassembly/module/NumericOperator.java +++ b/src/de/inetsoftware/jwebassembly/module/NumericOperator.java @@ -30,4 +30,6 @@ public enum NumericOperator { shl, shr_s, shr_u, + eq, + ne, } diff --git a/src/de/inetsoftware/jwebassembly/module/ValueType.java b/src/de/inetsoftware/jwebassembly/module/ValueType.java index ebe16a1..2058c18 100644 --- a/src/de/inetsoftware/jwebassembly/module/ValueType.java +++ b/src/de/inetsoftware/jwebassembly/module/ValueType.java @@ -24,6 +24,7 @@ public enum ValueType { f32(-3), f64(-4), func(-0x20); + // void(-0x40); private int code; diff --git a/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java b/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java index 28a25f2..66da6ab 100644 --- a/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java +++ b/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java @@ -20,6 +20,7 @@ import java.util.List; import javax.annotation.Nullable; +import de.inetsoftware.jwebassembly.module.BlockOperator; import de.inetsoftware.jwebassembly.module.ModuleWriter; import de.inetsoftware.jwebassembly.module.NumericOperator; import de.inetsoftware.jwebassembly.module.ValueType; @@ -221,4 +222,25 @@ public class TextModuleWriter extends ModuleWriter { name = name.substring( 0, name.indexOf( '(' ) ); methodOutput.append( "call $" ).append( name ); } + + /** + * {@inheritDoc} + */ + @Override + protected void writeBlockCode( BlockOperator op ) throws IOException { + switch( op ) { + case IF: + newline( methodOutput ); + methodOutput.append( "if" ); + inset++; + break; + case END: + inset--; + newline( methodOutput ); + methodOutput.append( "end" ); + break; + default: + throw new Error( "Unknown block: " + op ); + } + } } diff --git a/test/de/inetsoftware/jwebassembly/runtime/ControlFlowOperators.java b/test/de/inetsoftware/jwebassembly/runtime/ControlFlowOperators.java new file mode 100644 index 0000000..481dfcf --- /dev/null +++ b/test/de/inetsoftware/jwebassembly/runtime/ControlFlowOperators.java @@ -0,0 +1,54 @@ +package de.inetsoftware.jwebassembly.runtime; + +import java.util.ArrayList; +import java.util.Collection; + +import org.junit.ClassRule; +import org.junit.runners.Parameterized.Parameters; +import org.webassembly.annotation.Export; + +import de.inetsoftware.jwebassembly.ScriptEngine; +import de.inetsoftware.jwebassembly.WasmRule; + +public class ControlFlowOperators extends AbstractBaseTest { + + @ClassRule + public static WasmRule rule = new WasmRule( TestClass.class ); + + public ControlFlowOperators( ScriptEngine script, String method, Object[] params ) { + super( rule, script, method, params ); + } + + @Parameters( name = "{0}-{1}" ) + public static Collection data() { + ArrayList list = new ArrayList<>(); + for( ScriptEngine[] val : ScriptEngine.testParams() ) { + ScriptEngine script = val[0]; + addParam( list, script, "ifThenElse_Int0" ); + addParam( list, script, "forLoop" ); + } + return list; + } + + static class TestClass { + + @Export + static int ifThenElse_Int0() { + int condition = 0; + if( condition != 0 ) { + return 13; + } else { + return 76; + } + } + + @Export + static int forLoop() { + int a = 0; + // for( int i=0; i<10;i++) { + // a += i; + // } + return a; + } + } +}