diff --git a/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java b/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java index 848348d..d1bbf8a 100644 --- a/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java +++ b/src/de/inetsoftware/jwebassembly/binary/BinaryModuleWriter.java @@ -1,234 +1,270 @@ -/* - * Copyright 2017 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.binary; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import de.inetsoftware.jwebassembly.module.ModuleWriter; -import de.inetsoftware.jwebassembly.module.ValueType; - -/** - * Module Writer for binary format. http://webassembly.org/docs/binary-encoding/ - * - * @author Volker Berlin - */ -public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcodes { - - private static final byte[] WASM_BINARY_MAGIC = { 0, 'a', 's', 'm' }; - - private static final int WASM_BINARY_VERSION = 1; - - private WasmOutputStream wasm; - - private WasmOutputStream codeStream = new WasmOutputStream(); - - private WasmOutputStream functionsStream = new WasmOutputStream(); - - private List functionTypes = new ArrayList<>(); - - private Map functions = new LinkedHashMap<>(); - - private Function function; - - private FunctionType functionType; - - /** - * Create new instance. - * - * @param output - * the target for the module data. - * @throws IOException - * if any I/O error occur - */ - public BinaryModuleWriter( OutputStream output ) throws IOException { - wasm = new WasmOutputStream( output ); - } - - /** - * {@inheritDoc} - */ - @Override - public void close() throws IOException { - wasm.write( WASM_BINARY_MAGIC ); - wasm.writeInt32( WASM_BINARY_VERSION ); - - writeTypeSection(); - writeFunctionSection(); - writeCodeSection(); - - wasm.close(); - } - - /** - * Write the type section to the output. This section contains the signatures of the functions. - * - * @throws IOException - * if any I/O error occur - */ - private void writeTypeSection() throws IOException { - int count = functionTypes.size(); - if( count > 0 ) { - WasmOutputStream stream = new WasmOutputStream(); - stream.writeVaruint32( count ); - for( FunctionType type : functionTypes ) { - stream.writeVarint32( ValueType.func.getCode() ); - stream.writeVaruint32( type.params.size() ); - for( ValueType valueType : type.params ) { - stream.writeVarint32( valueType.getCode() ); - } - if( type.result == null ) { - stream.writeVaruint32( 0 ); - } else { - stream.writeVaruint32( 1 ); - stream.writeVarint32( type.result.getCode() ); - } - } - wasm.writeSection( SectionType.Type, stream, null ); - } - } - - /** - * Write the function section to the output. This section contains a mapping from the function index to the type signature index. - * - * @throws IOException - * if any I/O error occur - */ - private void writeFunctionSection() throws IOException { - int count = functions.size(); - if( count > 0 ) { - WasmOutputStream stream = new WasmOutputStream(); - stream.writeVaruint32( count ); - for( Function func : functions.values() ) { - stream.writeVaruint32( func.typeId ); - } - wasm.writeSection( SectionType.Function, stream, null ); - } - } - - /** - * Write the code section to the output. This section contains the byte code. - * - * @throws IOException - * if any I/O error occur - */ - private void writeCodeSection() throws IOException { - int size = functions.size(); - if( size == 0 ) { - return; - } - WasmOutputStream stream = new WasmOutputStream(); - stream.writeVaruint32( size ); - functionsStream.writeTo( stream ); - wasm.writeSection( SectionType.Code, stream, null ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeMethodStart( String name ) throws IOException { - function = new Function(); - function.id = functions.size(); - functions.put( name, function ); - functionType = new FunctionType(); - codeStream.reset(); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeMethodParam( String kind, ValueType valueType ) throws IOException { - switch( kind ) { - case "param": - functionType.params.add( valueType ); - return; - case "return": - functionType.result = valueType; - return; - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeMethodFinish( List locals ) throws IOException { - // TODO optimize and search for duplicates - function.typeId = functionTypes.size(); - functionTypes.add( functionType ); - - - WasmOutputStream localsStream = new WasmOutputStream(); - localsStream.writeVaruint32( locals.size() ); - for( ValueType valueType : locals ) { - localsStream.writeVaruint32( 1 ); // TODO optimize, write the count of same types. - localsStream.writeVarint32( valueType.getCode() ); - } - functionsStream.writeVaruint32( localsStream.size() + codeStream.size() + 1 ); - localsStream.writeTo( functionsStream ); - codeStream.writeTo( functionsStream ); - functionsStream.write( END ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeConstInt( int value ) throws IOException { - codeStream.write( I32_CONST ); - codeStream.writeVarint32( value ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeLoad( int idx ) throws IOException { - codeStream.write( GET_LOCAL ); - codeStream.writeVaruint32( idx ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeStore( int idx ) throws IOException { - codeStream.write( SET_LOCAL ); - codeStream.writeVaruint32( idx ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeAddInt() throws IOException { - codeStream.write( I32_ADD ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeReturn() throws IOException { - codeStream.write( RETURN ); - } -} +/* + * Copyright 2017 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.binary; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import de.inetsoftware.jwebassembly.module.ModuleWriter; +import de.inetsoftware.jwebassembly.module.ValueType; + +/** + * Module Writer for binary format. http://webassembly.org/docs/binary-encoding/ + * + * @author Volker Berlin + */ +public class BinaryModuleWriter extends ModuleWriter implements InstructionOpcodes { + + private static final byte[] WASM_BINARY_MAGIC = { 0, 'a', 's', 'm' }; + + private static final int WASM_BINARY_VERSION = 1; + + private WasmOutputStream wasm; + + private WasmOutputStream codeStream = new WasmOutputStream(); + + private WasmOutputStream functionsStream = new WasmOutputStream(); + + private List functionTypes = new ArrayList<>(); + + private Map functions = new LinkedHashMap<>(); + + private Map exports = new LinkedHashMap<>(); + + private Function function; + + private FunctionType functionType; + + /** + * Create new instance. + * + * @param output + * the target for the module data. + * @throws IOException + * if any I/O error occur + */ + public BinaryModuleWriter( OutputStream output ) throws IOException { + wasm = new WasmOutputStream( output ); + } + + /** + * {@inheritDoc} + */ + @Override + public void close() throws IOException { + wasm.write( WASM_BINARY_MAGIC ); + wasm.writeInt32( WASM_BINARY_VERSION ); + + writeTypeSection(); + writeFunctionSection(); + writeExportSection(); + writeCodeSection(); + + wasm.close(); + } + + /** + * Write the type section to the output. This section contains the signatures of the functions. + * + * @throws IOException + * if any I/O error occur + */ + private void writeTypeSection() throws IOException { + int count = functionTypes.size(); + if( count > 0 ) { + WasmOutputStream stream = new WasmOutputStream(); + stream.writeVaruint32( count ); + for( FunctionType type : functionTypes ) { + stream.writeVarint32( ValueType.func.getCode() ); + stream.writeVaruint32( type.params.size() ); + for( ValueType valueType : type.params ) { + stream.writeVarint32( valueType.getCode() ); + } + if( type.result == null ) { + stream.writeVaruint32( 0 ); + } else { + stream.writeVaruint32( 1 ); + stream.writeVarint32( type.result.getCode() ); + } + } + wasm.writeSection( SectionType.Type, stream, null ); + } + } + + /** + * Write the function section to the output. This section contains a mapping from the function index to the type signature index. + * + * @throws IOException + * if any I/O error occur + */ + private void writeFunctionSection() throws IOException { + int count = functions.size(); + if( count > 0 ) { + WasmOutputStream stream = new WasmOutputStream(); + stream.writeVaruint32( count ); + for( Function func : functions.values() ) { + stream.writeVaruint32( func.typeId ); + } + wasm.writeSection( SectionType.Function, stream, null ); + } + } + + /** + * Write the export section to the output. This section contains a mapping from the external index to the type signature index. + * + * @throws IOException + * if any I/O error occur + */ + private void writeExportSection() throws IOException { + int count = exports.size(); + if( count > 0 ) { + WasmOutputStream stream = new WasmOutputStream(); + stream.writeVaruint32( count ); + for( Map.Entry entry : exports.entrySet() ) { + String exportName = entry.getKey(); + byte[] bytes = exportName.getBytes( StandardCharsets.UTF_8 ); + stream.writeVaruint32( bytes.length ); + stream.write( bytes ); + stream.writeVaruint32( ExternalKind.Function.ordinal() ); + int id = functions.get( entry.getValue() ).id; + stream.writeVaruint32( id ); + } + wasm.writeSection( SectionType.Export, stream, null ); + } + } + + /** + * Write the code section to the output. This section contains the byte code. + * + * @throws IOException + * if any I/O error occur + */ + private void writeCodeSection() throws IOException { + int size = functions.size(); + if( size == 0 ) { + return; + } + WasmOutputStream stream = new WasmOutputStream(); + stream.writeVaruint32( size ); + functionsStream.writeTo( stream ); + wasm.writeSection( SectionType.Code, stream, null ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeExport( String methodName, String exportName ) throws IOException { + exports.put( exportName, methodName ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeMethodStart( String name ) throws IOException { + function = new Function(); + function.id = functions.size(); + functions.put( name, function ); + functionType = new FunctionType(); + codeStream.reset(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeMethodParam( String kind, ValueType valueType ) throws IOException { + switch( kind ) { + case "param": + functionType.params.add( valueType ); + return; + case "return": + functionType.result = valueType; + return; + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeMethodFinish( List locals ) throws IOException { + // TODO optimize and search for duplicates + function.typeId = functionTypes.size(); + functionTypes.add( functionType ); + + + WasmOutputStream localsStream = new WasmOutputStream(); + localsStream.writeVaruint32( locals.size() ); + for( ValueType valueType : locals ) { + localsStream.writeVaruint32( 1 ); // TODO optimize, write the count of same types. + localsStream.writeVarint32( valueType.getCode() ); + } + functionsStream.writeVaruint32( localsStream.size() + codeStream.size() + 1 ); + localsStream.writeTo( functionsStream ); + codeStream.writeTo( functionsStream ); + functionsStream.write( END ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeConstInt( int value ) throws IOException { + codeStream.write( I32_CONST ); + codeStream.writeVarint32( value ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeLoad( int idx ) throws IOException { + codeStream.write( GET_LOCAL ); + codeStream.writeVaruint32( idx ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeStore( int idx ) throws IOException { + codeStream.write( SET_LOCAL ); + codeStream.writeVaruint32( idx ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeAddInt() throws IOException { + codeStream.write( I32_ADD ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeReturn() throws IOException { + codeStream.write( RETURN ); + } +} diff --git a/src/de/inetsoftware/jwebassembly/module/ModuleWriter.java b/src/de/inetsoftware/jwebassembly/module/ModuleWriter.java index 0c7b23c..5acc06f 100644 --- a/src/de/inetsoftware/jwebassembly/module/ModuleWriter.java +++ b/src/de/inetsoftware/jwebassembly/module/ModuleWriter.java @@ -1,300 +1,336 @@ -/* - * Copyright 2017 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.Closeable; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -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.LineNumberTable; -import de.inetsoftware.classparser.MethodInfo; -import de.inetsoftware.jwebassembly.WasmException; - -/** - * Module Writer for text format with S-expressions. - * - * @author Volker Berlin - */ -public abstract class ModuleWriter implements Closeable { - - private int paramCount; - - private ArrayList locals = new ArrayList<>(); - - /** - * Write the content of the class to the - * - * @param classFile - * the class file - * @throws IOException - * if any I/O error occur - * @throws WasmException - * if some Java code can't converted - */ - public void write( ClassFile classFile ) throws IOException, WasmException { - MethodInfo[] methods = classFile.getMethods(); - for( MethodInfo method : methods ) { - Code code = method.getCode(); - if( method.getName().equals( "" ) && method.getDescription().equals( "()V" ) - && code.isSuperInitReturn( classFile.getSuperClass() ) ) { - continue; //default constructor - } - writeMethod( method ); - } - } - - /** - * Write the content of a method. - * - * @param method - * the method - * @throws IOException - * if any I/O error occur - * @throws WasmException - * if some Java code can't converted - */ - private void writeMethod( MethodInfo method ) throws IOException, WasmException { - Code code = method.getCode(); - if( code != null ) { // abstract methods and interface methods does not have code - writeMethodStart( method.getName() ); - writeMethodSignature( method ); - locals.clear(); - LineNumberTable lineNumberTable = code.getLineNumberTable(); - if( lineNumberTable != null ) { - int lineNumber; - for( int i = 0; i < lineNumberTable.size(); i++ ) { - lineNumber = lineNumberTable.getLineNumber( i ); - int offset = lineNumberTable.getStartOffset( i ); - int nextOffset = - i + 1 == lineNumberTable.size() ? code.getCodeSize() - : lineNumberTable.getStartOffset( i + 1 ); - CodeInputStream byteCode = code.getByteCode( offset, nextOffset - offset ); - writeCodeChunk( byteCode, lineNumber ); - } - } else { - CodeInputStream byteCode = code.getByteCode(); - writeCodeChunk( byteCode, -1 ); - } - for( int i = Math.min( paramCount, locals.size() ); i > 0; i-- ) { - locals.remove( 0 ); - } - writeMethodFinish( locals ); - } - } - - /** - * Write the method header. - * - * @param name - * the method name - * @throws IOException - * if any I/O error occur - */ - protected abstract void writeMethodStart( String name ) throws IOException; - - /** - * 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; - for( int i = 1; i < signature.length(); i++ ) { - paramCount++; - String javaType; - switch( signature.charAt( i ) ) { - case '[': // array - javaType = "array"; - break; - case 'L': - javaType = "object"; - break; - case 'B': // byte - javaType = "byte"; - break; - case 'C': // char - javaType = "char"; - break; - case 'D': // double - writeMethodParam( kind, ValueType.f64 ); - continue; - case 'F': // float - writeMethodParam( kind, ValueType.f32 ); - continue; - case 'I': // int - writeMethodParam( kind, ValueType.i32 ); - continue; - case 'J': // long - writeMethodParam( kind, ValueType.i64 ); - continue; - case 'V': // void - continue; - case ')': - this.paramCount = paramCount - 1; - kind = "return"; - continue; - default: - javaType = signature.substring( i, i + 1 ); - } - int lineNumber = method.getCode().getFirstLineNr(); - throw new WasmException( "Not supported Java data type in method signature: " + javaType, lineNumber ); - } - } - - /** - * Write a method parameter. - * - * @param kind - * "param", "return" or "local" - * @param valueType - * the data type of the parameter - * @throws IOException - * if any I/O error occur - */ - protected abstract void writeMethodParam( String kind, ValueType valueType ) throws IOException; - - /** - * Complete the method - * - * @param locals - * a list with types of local variables - * - * @throws IOException - * if any I/O error occur - */ - protected abstract void writeMethodFinish( List locals ) throws IOException; - - /** - * Write a chunk of byte code. - * - * @param byteCode - * a stream of byte code - * @param lineNumber - * the current line number - * @throws WasmException - * if some Java code can't converted - */ - private void writeCodeChunk( CodeInputStream byteCode, int lineNumber ) throws WasmException { - try { - while( byteCode.available() > 0 ) { - int op = byteCode.readUnsignedByte(); - switch( op ) { - case 4: // iconst_1 - writeConstInt( 1 ); - break; - case 26: // iload_0 - case 27: // iload_1 - case 28: // iload_2 - case 29: // iload_3 - writeLoadStore( true, ValueType.i32, op - 26 ); - break; - case 59: // istore_0 - case 60: // istore_1 - case 61: // istore_2 - case 62: // istore_3 - writeLoadStore( false, ValueType.i32, op - 59 ); - break; - case 96: // iadd - writeAddInt(); - break; - case 177: // return void - writeReturn(); - break; - default: - throw new WasmException( "Unimplemented byte code operation: " + op, lineNumber ); - } - } - } catch( Exception ex ) { - throw WasmException.create( ex, lineNumber ); - } - } - - /** - * Write a constant integer value - * - * @param value - * the value - * @throws IOException - * if any I/O error occur - */ - protected abstract void writeConstInt( int value ) throws IOException; - - private void writeLoadStore( boolean load, @Nonnull ValueType valueType, @Nonnegative int idx ) throws WasmException, IOException { - while( locals.size() <= idx ) { - locals.add( null ); - } - ValueType oldType = locals.get( idx ); - if( oldType != null && oldType != valueType ) { - throw new WasmException( "Redefine local variable type from " + oldType + " to " + valueType, -1 ); - } - locals.set( idx, valueType ); - if( load ) { - writeLoad( idx ); - } else { - writeStore( idx ); - } - } - - /** - * Write a variable load. - * - * @param idx - * the index of the parameter variable - * @throws IOException - * if any I/O error occur - */ - protected abstract void writeLoad( int idx ) throws IOException; - - /** - * Write a variable store. - * - * @param idx - * the index of the parameter variable - * @throws IOException - * if any I/O error occur - */ - protected abstract void writeStore( int idx ) throws IOException; - - /** - * Write a add operator - * - * @throws IOException - * if any I/O error occur - */ - protected abstract void writeAddInt() throws IOException; - - /** - * Write a return - * - * @throws IOException - * if any I/O error occur - */ - protected abstract void writeReturn() throws IOException; -} +/* + * Copyright 2017 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.Closeable; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnegative; +import javax.annotation.Nonnull; + +import de.inetsoftware.classparser.Annotations; +import de.inetsoftware.classparser.ClassFile; +import de.inetsoftware.classparser.Code; +import de.inetsoftware.classparser.CodeInputStream; +import de.inetsoftware.classparser.LineNumberTable; +import de.inetsoftware.classparser.MethodInfo; +import de.inetsoftware.jwebassembly.WasmException; + +/** + * Module Writer for text format with S-expressions. + * + * @author Volker Berlin + */ +public abstract class ModuleWriter implements Closeable { + + private int paramCount; + + private ArrayList locals = new ArrayList<>(); + + /** + * Write the content of the class to the + * + * @param classFile + * the class file + * @throws IOException + * if any I/O error occur + * @throws WasmException + * if some Java code can't converted + */ + public void write( ClassFile classFile ) throws IOException, WasmException { + MethodInfo[] methods = classFile.getMethods(); + for( MethodInfo method : methods ) { + Code code = method.getCode(); + if( method.getName().equals( "" ) && method.getDescription().equals( "()V" ) + && code.isSuperInitReturn( classFile.getSuperClass() ) ) { + continue; //default constructor + } + writeMethod( method ); + } + } + + /** + * Write the content of a method. + * + * @param method + * the method + * @throws IOException + * if any I/O error occur + * @throws WasmException + * if some Java code can't converted + */ + private void writeMethod( MethodInfo method ) throws IOException, WasmException { + Code code = method.getCode(); + if( code != null ) { // abstract methods and interface methods does not have code + String methodName = method.getName(); // TODO naming conversion rule + writeExport( methodName, method ); + writeMethodStart( methodName ); + writeMethodSignature( method ); + locals.clear(); + LineNumberTable lineNumberTable = code.getLineNumberTable(); + if( lineNumberTable != null ) { + int lineNumber; + for( int i = 0; i < lineNumberTable.size(); i++ ) { + lineNumber = lineNumberTable.getLineNumber( i ); + int offset = lineNumberTable.getStartOffset( i ); + int nextOffset = + i + 1 == lineNumberTable.size() ? code.getCodeSize() + : lineNumberTable.getStartOffset( i + 1 ); + CodeInputStream byteCode = code.getByteCode( offset, nextOffset - offset ); + writeCodeChunk( byteCode, lineNumber ); + } + } else { + CodeInputStream byteCode = code.getByteCode(); + writeCodeChunk( byteCode, -1 ); + } + for( int i = Math.min( paramCount, locals.size() ); i > 0; i-- ) { + locals.remove( 0 ); + } + writeMethodFinish( locals ); + } + } + + /** + * Look for a Export annotation and if there write an export directive. + * @param methodName + * @param method + * @throws IOException + */ + private void writeExport( String methodName, MethodInfo method ) throws IOException { + Annotations annotations = method.getRuntimeInvisibleAnnotations(); + if( annotations != null ) { + Map export = annotations.get( "org.webassembly.annotation.Export" ); + if( export != null ) { + String exportName = (String)export.get( "name" ); + if( exportName == null ) { + exportName = methodName; + } + writeExport( methodName, exportName ); + } + } + } + + /** + * Write an export directive + * + * @param methodName + * the method name + * @param exportName + * the export name, if null then the same like the method name + * @throws IOException + * if any I/O error occur + */ + protected abstract void writeExport( String methodName, String exportName ) throws IOException; + + /** + * Write the method header. + * + * @param name + * the method name + * @throws IOException + * if any I/O error occur + */ + protected abstract void writeMethodStart( String name ) throws IOException; + + /** + * 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; + for( int i = 1; i < signature.length(); i++ ) { + paramCount++; + String javaType; + switch( signature.charAt( i ) ) { + case '[': // array + javaType = "array"; + break; + case 'L': + javaType = "object"; + break; + case 'B': // byte + javaType = "byte"; + break; + case 'C': // char + javaType = "char"; + break; + case 'D': // double + writeMethodParam( kind, ValueType.f64 ); + continue; + case 'F': // float + writeMethodParam( kind, ValueType.f32 ); + continue; + case 'I': // int + writeMethodParam( kind, ValueType.i32 ); + continue; + case 'J': // long + writeMethodParam( kind, ValueType.i64 ); + continue; + case 'V': // void + continue; + case ')': + this.paramCount = paramCount - 1; + kind = "return"; + continue; + default: + javaType = signature.substring( i, i + 1 ); + } + int lineNumber = method.getCode().getFirstLineNr(); + throw new WasmException( "Not supported Java data type in method signature: " + javaType, lineNumber ); + } + } + + /** + * Write a method parameter. + * + * @param kind + * "param", "return" or "local" + * @param valueType + * the data type of the parameter + * @throws IOException + * if any I/O error occur + */ + protected abstract void writeMethodParam( String kind, ValueType valueType ) throws IOException; + + /** + * Complete the method + * + * @param locals + * a list with types of local variables + * + * @throws IOException + * if any I/O error occur + */ + protected abstract void writeMethodFinish( List locals ) throws IOException; + + /** + * Write a chunk of byte code. + * + * @param byteCode + * a stream of byte code + * @param lineNumber + * the current line number + * @throws WasmException + * if some Java code can't converted + */ + private void writeCodeChunk( CodeInputStream byteCode, int lineNumber ) throws WasmException { + try { + while( byteCode.available() > 0 ) { + int op = byteCode.readUnsignedByte(); + switch( op ) { + case 4: // iconst_1 + writeConstInt( 1 ); + break; + case 26: // iload_0 + case 27: // iload_1 + case 28: // iload_2 + case 29: // iload_3 + writeLoadStore( true, ValueType.i32, op - 26 ); + break; + case 59: // istore_0 + case 60: // istore_1 + case 61: // istore_2 + case 62: // istore_3 + writeLoadStore( false, ValueType.i32, op - 59 ); + break; + case 96: // iadd + writeAddInt(); + break; + case 177: // return void + writeReturn(); + break; + default: + throw new WasmException( "Unimplemented byte code operation: " + op, lineNumber ); + } + } + } catch( Exception ex ) { + throw WasmException.create( ex, lineNumber ); + } + } + + /** + * Write a constant integer value + * + * @param value + * the value + * @throws IOException + * if any I/O error occur + */ + protected abstract void writeConstInt( int value ) throws IOException; + + private void writeLoadStore( boolean load, @Nonnull ValueType valueType, @Nonnegative int idx ) throws WasmException, IOException { + while( locals.size() <= idx ) { + locals.add( null ); + } + ValueType oldType = locals.get( idx ); + if( oldType != null && oldType != valueType ) { + throw new WasmException( "Redefine local variable type from " + oldType + " to " + valueType, -1 ); + } + locals.set( idx, valueType ); + if( load ) { + writeLoad( idx ); + } else { + writeStore( idx ); + } + } + + /** + * Write a variable load. + * + * @param idx + * the index of the parameter variable + * @throws IOException + * if any I/O error occur + */ + protected abstract void writeLoad( int idx ) throws IOException; + + /** + * Write a variable store. + * + * @param idx + * the index of the parameter variable + * @throws IOException + * if any I/O error occur + */ + protected abstract void writeStore( int idx ) throws IOException; + + /** + * Write a add operator + * + * @throws IOException + * if any I/O error occur + */ + protected abstract void writeAddInt() throws IOException; + + /** + * Write a return + * + * @throws IOException + * if any I/O error occur + */ + protected abstract void writeReturn() throws IOException; +} diff --git a/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java b/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java index 11611d1..151bc45 100644 --- a/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java +++ b/src/de/inetsoftware/jwebassembly/text/TextModuleWriter.java @@ -1,154 +1,163 @@ -/* - * Copyright 2017 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.text; - -import java.io.IOException; -import java.util.List; - -import de.inetsoftware.jwebassembly.module.ModuleWriter; -import de.inetsoftware.jwebassembly.module.ValueType; - -/** - * Module Writer for text format with S-expressions. - * - * @author Volker Berlin - * - */ -public class TextModuleWriter extends ModuleWriter { - - private Appendable output; - - private StringBuilder methodOutput = new StringBuilder(); - - private int inset; - - /** - * Create a new instance. - * - * @param output - * target for the result - * @throws IOException - * if any I/O error occur - */ - public TextModuleWriter( Appendable output ) throws IOException { - this.output = output; - output.append( "(module" ); - inset++; - } - - /** - * {@inheritDoc} - */ - @Override - public void close() throws IOException { - inset--; - newline( output ); - output.append( ')' ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeMethodStart( String name ) throws IOException { - newline( output ); - output.append( "(func $" ); - output.append( name ); - inset++; - methodOutput.setLength( 0 ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeMethodParam( String kind, ValueType valueType ) throws IOException { - output.append( " (" ).append( kind ).append( ' ' ).append( valueType.toString() ).append( ')' ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeMethodFinish( List locals ) throws IOException { - for( ValueType valueType : locals ) { - output.append( " (local " ).append( valueType.toString() ).append( ')' ); - } - output.append( methodOutput ); - inset--; - newline( output ); - output.append( ')' ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeConstInt( int value ) throws IOException { - newline( methodOutput ); - methodOutput.append( "i32.const " ).append( Integer.toString( value ) ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeLoad( int idx ) throws IOException { - newline( methodOutput ); - methodOutput.append( "get_local " ).append( Integer.toString( idx ) ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeStore( int idx ) throws IOException { - newline( methodOutput ); - methodOutput.append( "set_local " ).append( Integer.toString( idx ) ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeAddInt() throws IOException { - newline( methodOutput ); - methodOutput.append( "i32.add" ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void writeReturn() throws IOException { - newline( methodOutput ); - methodOutput.append( "return" ); - } - - /** - * Add a newline the insets. - * - * @throws IOException - * if any I/O error occur - */ - private void newline( Appendable output ) throws IOException { - output.append( '\n' ); - for( int i = 0; i < inset; i++ ) { - output.append( ' ' ); - output.append( ' ' ); - } - } -} +/* + * Copyright 2017 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.text; + +import java.io.IOException; +import java.util.List; + +import de.inetsoftware.jwebassembly.module.ModuleWriter; +import de.inetsoftware.jwebassembly.module.ValueType; + +/** + * Module Writer for text format with S-expressions. + * + * @author Volker Berlin + * + */ +public class TextModuleWriter extends ModuleWriter { + + private Appendable output; + + private StringBuilder methodOutput = new StringBuilder(); + + private int inset; + + /** + * Create a new instance. + * + * @param output + * target for the result + * @throws IOException + * if any I/O error occur + */ + public TextModuleWriter( Appendable output ) throws IOException { + this.output = output; + output.append( "(module" ); + inset++; + } + + /** + * {@inheritDoc} + */ + @Override + public void close() throws IOException { + inset--; + newline( output ); + output.append( ')' ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeExport( String methodName, String exportName ) throws IOException { + newline( output ); + output.append( "(export \"" ).append( exportName ).append( "\" (func $" ).append( methodName ).append( "))" ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeMethodStart( String name ) throws IOException { + newline( output ); + output.append( "(func $" ); + output.append( name ); + inset++; + methodOutput.setLength( 0 ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeMethodParam( String kind, ValueType valueType ) throws IOException { + output.append( " (" ).append( kind ).append( ' ' ).append( valueType.toString() ).append( ')' ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeMethodFinish( List locals ) throws IOException { + for( ValueType valueType : locals ) { + output.append( " (local " ).append( valueType.toString() ).append( ')' ); + } + output.append( methodOutput ); + inset--; + newline( output ); + output.append( ')' ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeConstInt( int value ) throws IOException { + newline( methodOutput ); + methodOutput.append( "i32.const " ).append( Integer.toString( value ) ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeLoad( int idx ) throws IOException { + newline( methodOutput ); + methodOutput.append( "get_local " ).append( Integer.toString( idx ) ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeStore( int idx ) throws IOException { + newline( methodOutput ); + methodOutput.append( "set_local " ).append( Integer.toString( idx ) ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeAddInt() throws IOException { + newline( methodOutput ); + methodOutput.append( "i32.add" ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void writeReturn() throws IOException { + newline( methodOutput ); + methodOutput.append( "return" ); + } + + /** + * Add a newline the insets. + * + * @throws IOException + * if any I/O error occur + */ + private void newline( Appendable output ) throws IOException { + output.append( '\n' ); + for( int i = 0; i < inset; i++ ) { + output.append( ' ' ); + output.append( ' ' ); + } + } +}