795 lines
25 KiB
Java
Raw Normal View History

/*
* Copyright 2017 - 2019 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.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
2017-04-09 18:46:27 +02:00
import javax.annotation.Nullable;
import de.inetsoftware.jwebassembly.JWebAssembly;
2019-04-27 21:14:55 +02:00
import de.inetsoftware.jwebassembly.WasmException;
2018-05-21 14:29:32 +02:00
import de.inetsoftware.jwebassembly.module.FunctionName;
import de.inetsoftware.jwebassembly.module.ModuleWriter;
import de.inetsoftware.jwebassembly.module.TypeManager.StructType;
import de.inetsoftware.jwebassembly.module.ValueTypeConvertion;
2019-06-30 14:43:45 +02:00
import de.inetsoftware.jwebassembly.module.WasmTarget;
import de.inetsoftware.jwebassembly.wasm.AnyType;
2018-12-03 21:09:22 +01:00
import de.inetsoftware.jwebassembly.wasm.ArrayOperator;
import de.inetsoftware.jwebassembly.wasm.NamedStorageType;
2018-12-03 21:09:22 +01:00
import de.inetsoftware.jwebassembly.wasm.NumericOperator;
2018-12-05 22:14:26 +01:00
import de.inetsoftware.jwebassembly.wasm.StructOperator;
2018-12-03 21:09:22 +01:00
import de.inetsoftware.jwebassembly.wasm.ValueType;
import de.inetsoftware.jwebassembly.wasm.VariableOperator;
2018-12-03 21:09:22 +01:00
import de.inetsoftware.jwebassembly.wasm.WasmBlockOperator;
/**
* Module Writer for text format with S-expressions.
*
* @author Volker Berlin
*
*/
public class TextModuleWriter extends ModuleWriter {
private final boolean spiderMonkey = Boolean.getBoolean( "SpiderMonkey" );
private Appendable output;
private final boolean debugNames;
private final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
private final ArrayList<String> methodParamNames = new ArrayList<>();
private StringBuilder typeOutput = new StringBuilder();
private ArrayList<String> types = new ArrayList<>();
private StringBuilder methodOutput;
private StringBuilder imports = new StringBuilder();
private Map<String, Function> functions = new LinkedHashMap<>();
2018-08-14 18:17:48 +02:00
private final Set<String> functionNames = new HashSet<>();
private int inset;
private boolean isImport;
2019-06-04 18:09:34 +02:00
private boolean isPrepared;
private HashSet<String> globals = new HashSet<>();
private boolean useExceptions;
2019-05-05 17:25:43 +02:00
private boolean callIndirect;
/**
* Create a new instance.
*
2019-06-30 15:18:09 +02:00
* @param target
* target for the result
* @param properties
* compiler properties
* @throws IOException
* if any I/O error occur
*/
2019-06-30 14:43:45 +02:00
public TextModuleWriter( WasmTarget target, HashMap<String, String> properties ) throws IOException {
this.output = target.getTextOutput();
debugNames = Boolean.parseBoolean( properties.get( JWebAssembly.DEBUG_NAMES ) );
output.append( "(module" );
inset++;
if( spiderMonkey ) {
output.append( " (gc_feature_opt_in 3)" ); // enable GcFeatureOptIn for SpiderMonkey https://github.com/lars-t-hansen/moz-gc-experiments/blob/master/version2.md
}
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
for( int i = 0; i < types.size(); i++ ) {
newline( output );
output.append( "(type $t" ).append( Integer.toString( i ) ).append( " (func" ).append( types.get( i ) ).append( "))" );
}
output.append( imports );
for( Function func : functions.values() ) {
output.append( func.output );
}
2019-05-05 17:25:43 +02:00
if( callIndirect ) {
int count = functions.size();
2019-05-05 17:25:43 +02:00
String countStr = Integer.toString( count );
2019-05-14 21:36:04 +02:00
newline( output );
2019-05-05 17:25:43 +02:00
output.append( "(table " ).append( countStr ).append( ' ' ).append( countStr ).append( " anyfunc)" );
newline( output );
output.append( "(elem (i32.const 0) " );
for( int i = 0; i < count; i++ ) {
output.append( Integer.toString( i ) ).append( ' ' );
}
output.append( ')' );
}
int dataSize = dataStream.size();
if( dataSize > 0 ) {
int pages = (dataSize + 0xFFFF) / 0x10000;
newline( output );
String pagesStr = Integer.toString( pages );
output.append( "(memory " ).append( pagesStr ).append( ' ' ).append( pagesStr ).append( ')' );
newline( output );
output.append( "(data (i32.const 0) \"" );
byte[] data = dataStream.toByteArray();
for( byte b : data ) {
output.append( '\\' ).append( Character.forDigit( (b >> 4) & 0xF, 16 ) ).append( Character.forDigit( b & 0xF, 16 ) );
}
output.append( "\")" );
}
inset--;
newline( output );
output.append( ')' );
}
/**
* {@inheritDoc}
*/
@Override
protected int writeStructType( StructType type ) throws IOException {
type.setVTable( dataStream.size() );
for( FunctionName funcName : type.getMethods() ) {
int functIdx = getFunction( funcName ).id;
// little-endian byte order
dataStream.write( functIdx >>> 0 );
dataStream.write( functIdx >>> 8 );
dataStream.write( functIdx >>> 16 );
dataStream.write( functIdx >>> 24 );
}
int oldInset = inset;
inset = 1;
newline( output );
String typeName = normalizeName( type.getName() );
output.append( "(type $" ).append( typeName ).append( " (struct" );
inset++;
for( NamedStorageType field : type.getFields() ) {
newline( output );
output.append( "(field" );
if( debugNames && field.getName() != null ) {
output.append( " $" ).append( typeName ).append( '.' ).append( field.getName() );
}
output.append( " (mut " );
2019-04-27 21:14:55 +02:00
writeTypeName( output, field.getType() );
output.append( "))" );
}
inset--;
newline( output );
output.append( "))" );
inset = oldInset;
return 0;
}
/**
* {@inheritDoc}
*/
@Override
protected void writeException() throws IOException {
if( !useExceptions ) {
useExceptions = true;
int oldInset = inset;
inset = 1;
newline( output );
output.append( "(event (param anyref))" );
inset = oldInset;
}
}
2019-06-04 18:09:34 +02:00
/**
* {@inheritDoc}
*/
@Override
protected void prepareFinish() {
isPrepared = true;
}
2018-05-30 18:57:36 +02:00
/**
* {@inheritDoc}
*/
@Override
2018-05-31 21:35:01 +02:00
protected void prepareImport( FunctionName name, String importModule, String importName ) throws IOException {
2018-05-30 18:57:36 +02:00
if( importName != null ) {
methodOutput = imports;
newline( methodOutput );
methodOutput.append( "(import \"" ).append( importModule ).append( "\" \"" ).append( importName ).append( "\" (func $" ).append( normalizeName( name ) );
2018-08-14 18:17:48 +02:00
isImport = true;
2018-05-30 18:57:36 +02:00
}
}
/**
* Normalize the function name for the text format
*
* @param name
* the name
* @return the normalized name
*/
@Nonnull
private String normalizeName( FunctionName name ) {
Function function = getFunction( name );
if( function.name == null ) {
String base;
String str = base = normalizeName( name.fullName );
for( int i = 1; functionNames.contains( str ); i++ ) {
str = base + '.' + i;
}
functionNames.add( str );
function.name = str;
}
return function.name;
}
/**
* Normalize the function name for the text format
*
* @param name
* the name
* @return the normalized name
*/
@Nonnull
private String normalizeName( String name ) {
if( spiderMonkey ) {
name = name.replace( '/', '.' ).replace( '<', '_' ).replace( '>', '_' ); // TODO HACK for https://bugzilla.mozilla.org/show_bug.cgi?id=1511485
}
return name;
}
/**
* {@inheritDoc}
*/
@Override
2018-05-21 14:29:32 +02:00
protected void writeExport( FunctionName name, String exportName ) throws IOException {
newline( output );
output.append( "(export \"" ).append( exportName ).append( "\" (func $" ).append( normalizeName( name ) ).append( "))" );
}
2019-04-27 21:14:55 +02:00
/**
* Write the name of a type.
*
* @param output
* the target
* @param type
* the type
* @throws IOException
* if any I/O error occur
*/
private void writeTypeName( Appendable output, AnyType type ) throws IOException {
if( type.getCode() < 0 ) {
output.append( type.toString() );
} else {
output.append( "(ref " ).append( normalizeName( type.toString() ) ).append( ')' );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void writeMethodParamStart( @Nonnull FunctionName name ) throws IOException {
typeOutput.setLength( 0 );
methodParamNames.clear();
}
/**
* {@inheritDoc}
*/
@Override
2019-01-14 20:09:00 +01:00
protected void writeMethodParam( String kind, AnyType valueType, @Nullable String name ) throws IOException {
if( kind != "local" ) {
typeOutput.append( '(' ).append( kind ).append( ' ' );
writeTypeName( typeOutput, valueType );
typeOutput.append( ')' );
}
if( methodOutput == null ) {
2019-06-04 18:09:34 +02:00
return;
}
methodOutput.append( '(' ).append( kind );
if( debugNames ) {
if( name != null ) {
methodOutput.append( " $" ).append( name );
}
if( kind != "result" ) {
methodParamNames.add( name );
}
}
methodOutput.append( ' ' );
2019-04-27 21:14:55 +02:00
writeTypeName( methodOutput, valueType );
methodOutput.append( ')' );
}
/**
* {@inheritDoc}
*/
@Override
protected void writeMethodParamFinish( @Nonnull FunctionName name ) throws IOException {
String typeStr = typeOutput.toString();
int idx = types.indexOf( typeStr );
if( idx < 0 ) {
idx = types.size();
types.add( typeStr );
}
getFunction( name ).typeId = idx;
2018-08-14 18:17:48 +02:00
if( isImport ) {
isImport = false;
methodOutput.append( "))" );
methodOutput = null;
2018-08-14 18:17:48 +02:00
}
}
private Function getFunction( FunctionName name ) {
Function func = functions.get( name.signatureName );
if( func == null ) {
func = new Function();
func.id = functions.size();
functions.put( name.signatureName, func );
}
return func;
}
/**
* {@inheritDoc}
*/
@Override
protected void writeMethodStart( FunctionName name, String sourceFile ) throws IOException {
methodOutput = getFunction( name ).output;
newline( methodOutput );
methodOutput.append( "(func $" );
methodOutput.append( normalizeName( name ) );
inset++;
}
/**
* {@inheritDoc}
*/
@Override
protected void markSourceLine( int javaSourceLine ) {
// nothing
}
/**
* {@inheritDoc}
*/
@Override
protected void writeMethodFinish() throws IOException {
inset--;
newline( methodOutput );
methodOutput.append( ')' );
methodOutput = null;
}
/**
* {@inheritDoc}
*/
@Override
protected void writeConst( Number value, ValueType valueType ) throws IOException {
newline( methodOutput );
methodOutput.append( valueType ).append( ".const " );
switch( valueType ) {
case f32:
methodOutput.append( Float.toHexString( value.floatValue() ).toLowerCase() ).append( " ;;" ).append( value );
break;
case f64:
methodOutput.append( Double.toHexString( value.doubleValue() ).toLowerCase() ).append( " ;;" ).append( value );
break;
default:
methodOutput.append( value );
break;
}
2017-04-09 12:44:01 +02:00
}
/**
* {@inheritDoc}
*/
@Override
protected void writeLocal( VariableOperator op, int idx ) throws IOException {
newline( methodOutput );
methodOutput.append( "local." ).append( op ).append( ' ' );
String name = idx < methodParamNames.size() ? methodParamNames.get( idx ) : null;
if( name == null ) {
methodOutput.append( Integer.toString( idx ) );
} else {
methodOutput.append( '$' ).append( name );
}
}
/**
* {@inheritDoc}
*/
@Override
2019-04-27 21:14:55 +02:00
protected void writeGlobalAccess( boolean load, FunctionName name, AnyType type ) throws IOException {
String fullName = normalizeName( name.fullName );
if( !globals.contains( fullName ) ) {
// declare global variable if not already declared.
output.append( "\n " );
2019-04-27 21:14:55 +02:00
output.append( "(global $" ).append( fullName ).append( " (mut " );
writeTypeName( output, type );
output.append( ')' );
writeDefaultValue( output, type );
output.append( ')' );
globals.add( fullName );
}
newline( methodOutput );
methodOutput.append( load ? "global.get $" : "global.set $" ).append( fullName );
}
2019-04-27 21:14:55 +02:00
/**
* {@inheritDoc}
*/
@Override
protected void writeDefaultValue( AnyType type ) throws IOException {
newline( methodOutput );
writeDefaultValue( methodOutput, type );
}
/**
* Write the default/initial value for type.
*
* @param output
* the target
* @param type
* the type
* @throws IOException
* if an I/O error occurs.
*/
private static void writeDefaultValue( Appendable output, AnyType type ) throws IOException {
if( type.getCode() < 0 ) {
ValueType valueType = (ValueType)type;
switch( valueType ) {
case i32:
case i64:
case f32:
case f64:
output.append( type.toString() ).append( ".const 0" );
2019-04-27 21:14:55 +02:00
break;
case i8:
case i16:
writeDefaultValue( output, ValueType.i32 );
break;
case anyref:
output.append( "ref.null" );
2019-04-27 21:14:55 +02:00
break;
default:
throw new WasmException( "Not supported storage type: " + type, -1 );
}
} else {
output.append( "ref.null" );
2019-04-27 21:14:55 +02:00
}
}
/**
* {@inheritDoc}
*/
@Override
2017-04-11 21:12:27 +02:00
protected void writeNumericOperator( NumericOperator numOp, @Nullable ValueType valueType ) throws IOException {
newline( methodOutput );
String op = numOp.toString();
switch( valueType ) {
case i32:
case i64:
switch( numOp ) {
case div:
case rem:
case gt:
case lt:
case le:
case ge:
op += "_s";
2018-12-15 22:33:25 +01:00
break;
case ifnonnull:
methodOutput.append( "ref.is_null" );
2018-12-15 22:33:25 +01:00
writeNumericOperator( NumericOperator.eqz, ValueType.i32 );
return;
case ifnull:
methodOutput.append( "ref.is_null" );
2018-12-15 22:33:25 +01:00
return;
case ref_ne:
methodOutput.append( "ref.eq" );
writeNumericOperator( NumericOperator.eqz, ValueType.i32 );
return;
case ref_eq:
methodOutput.append( "ref.eq" );
return;
2019-01-13 11:36:07 +01:00
default:
}
2018-12-15 22:33:25 +01:00
break;
2019-01-13 11:36:07 +01:00
default:
}
methodOutput.append( valueType ).append( '.' ).append( op );
}
/**
* {@inheritDoc}
*/
@Override
protected void writeCast( ValueTypeConvertion cast ) throws IOException {
String op;
switch( cast ) {
case i2l:
op = "i64.extend_i32_s";
break;
case i2f:
op = "f32.convert_i32_s";
break;
case i2d:
op = "f64.convert_i32_s";
break;
case l2i:
op = "i32.wrap_i64";
break;
case l2f:
op = "f32.convert_i64_s";
break;
case l2d:
op = "f64.convert_i64_s";
break;
case f2i:
op = "i32.trunc_sat_f32_s";
break;
case f2l:
op = "i64.trunc_sat_f32_s";
break;
case f2d:
op = "f64.promote_f32";
break;
case d2i:
op = "i32.trunc_sat_f64_s";
break;
case d2l:
op = "i64.trunc_sat_f64_s";
break;
case d2f:
op = "f32.demote_f64";
break;
case i2b:
op = "i32.extend8_s";
break;
case i2s:
op = "i32.extend16_s";
break;
case f2i_re:
op = "i32.reinterpret_f32";
break;
case i2f_re:
op = "f32.reinterpret_i32";
break;
case d2l_re:
op = "i64.reinterpret_f64";
break;
case l2d_re:
op = "f64.reinterpret_i64";
break;
default:
throw new Error( "Unknown cast/type conversion: " + cast );
}
newline( methodOutput );
methodOutput.append( op );
}
/**
2019-01-13 11:36:07 +01:00
* Add a newline with the insets.
*
2019-01-13 11:36:07 +01:00
* @param output
* the target
* @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( ' ' );
}
}
/**
* {@inheritDoc}
*/
@Override
2018-11-24 16:14:52 +01:00
protected void writeFunctionCall( FunctionName name ) throws IOException {
newline( methodOutput );
methodOutput.append( "call $" ).append( normalizeName( name ) );
}
2018-03-25 12:57:04 +02:00
2019-05-05 17:25:43 +02:00
/**
* {@inheritDoc}
*/
@Override
protected void writeVirtualFunctionCall( FunctionName name, AnyType type, int virtualFunctionIdx, int tempVarIdx ) throws IOException {
2019-05-05 17:25:43 +02:00
callIndirect = true;
// duplicate this on the stack
writeLocal( VariableOperator.tee, tempVarIdx );
writeLocal( VariableOperator.get, tempVarIdx );
2019-05-05 17:25:43 +02:00
newline( methodOutput );
2019-06-04 18:09:34 +02:00
methodOutput.append( "struct.get " ).append( normalizeName( type.toString() ) ).append( " 0 ;;vtable" ); // vtable is ever on position 0
2019-05-18 21:37:19 +02:00
newline( methodOutput );
methodOutput.append( "i32.load offset=" ).append( virtualFunctionIdx * 4 ); // use default alignment
newline( methodOutput );
2019-06-04 18:09:34 +02:00
if(spiderMonkey)
methodOutput.append( "call_indirect $t" ).append( functions.get( name.signatureName ).typeId ); // https://bugzilla.mozilla.org/show_bug.cgi?id=1556779
2019-06-04 18:09:34 +02:00
else
methodOutput.append( "call_indirect (type $t" ).append( functions.get( name.signatureName ).typeId ).append( ')' );
2019-05-05 17:25:43 +02:00
}
2018-03-25 12:57:04 +02:00
/**
* {@inheritDoc}
*/
@Override
protected void writeBlockCode( @Nonnull WasmBlockOperator op, @Nullable Object data ) throws IOException {
2018-04-02 11:53:12 +02:00
String name;
int insetAfter = 0;
2018-03-25 12:57:04 +02:00
switch( op ) {
2018-04-02 11:53:12 +02:00
case RETURN:
name = "return";
break;
2018-03-25 12:57:04 +02:00
case IF:
2018-04-02 11:53:12 +02:00
name = "if";
if( data != ValueType.empty ) {
2019-05-08 17:26:28 +02:00
name += " (result " + data + ")";
}
2018-04-02 11:53:12 +02:00
insetAfter++;
2018-03-25 12:57:04 +02:00
break;
2018-03-27 20:04:35 +02:00
case ELSE:
inset--;
2018-04-02 11:53:12 +02:00
name = "else";
insetAfter++;
2018-03-27 20:04:35 +02:00
break;
2018-03-25 12:57:04 +02:00
case END:
inset--;
2018-04-02 11:53:12 +02:00
name = "end";
break;
case DROP:
name = "drop";
2018-03-25 12:57:04 +02:00
break;
2018-05-03 22:57:44 +02:00
case BLOCK:
name = "block";
2019-03-02 21:54:27 +01:00
if( data != null ) {
name += " (result " + data + ")";
}
2018-05-03 22:57:44 +02:00
insetAfter++;
break;
case BR:
name = "br " + data;
break;
2018-05-11 21:39:04 +02:00
case BR_IF:
name = "br_if " + data;
break;
2018-05-05 21:39:36 +02:00
case BR_TABLE:
StringBuilder builder = new StringBuilder( "br_table");
for( int i : (int[])data ) {
builder.append( ' ' ).append( i );
}
name = builder.toString();
break;
2018-05-20 11:52:16 +02:00
case LOOP:
name = "loop";
insetAfter++;
break;
case UNREACHABLE:
name = "unreachable";
break;
2018-11-03 18:01:42 +01:00
case TRY:
name = "try";
insetAfter++;
break;
case CATCH:
inset--;
name = "catch";
insetAfter++;
break;
2019-03-02 21:54:27 +01:00
case THROW:
name = "throw 0"; // currently there is only one event/exception with anyref
break;
case RETHROW:
name = "rethrow";
break;
case BR_ON_EXN:
name = "br_on_exn " + data + " 0"; // br_on_exn, break depth, event; // currently there is only one event/exception with anyref
break;
case MONITOR_ENTER:
case MONITOR_EXIT:
name = "drop";
break;
2018-03-25 12:57:04 +02:00
default:
throw new Error( "Unknown block: " + op );
}
2018-04-02 11:53:12 +02:00
newline( methodOutput );
methodOutput.append( name );
inset += insetAfter;
2018-03-25 12:57:04 +02:00
}
2018-12-02 19:54:59 +01:00
2018-12-05 22:14:26 +01:00
/**
* {@inheritDoc}
*/
2018-12-02 19:54:59 +01:00
@Override
2019-01-14 20:09:00 +01:00
protected void writeArrayOperator( @Nonnull ArrayOperator op, AnyType type ) throws IOException {
2018-12-02 19:54:59 +01:00
String operation;
switch( op ) {
case NEW:
operation = "new";
break;
case GET:
operation = "get";
break;
case SET:
operation = "set";
break;
case LENGTH:
operation = "len";
break;
default:
throw new Error( "Unknown operator: " + op );
}
newline( methodOutput );
methodOutput.append( "array." ).append( operation ).append( ' ' ).append( type );
2018-12-02 19:54:59 +01:00
}
2018-12-05 22:14:26 +01:00
/**
* {@inheritDoc}
*/
@Override
protected void writeStructOperator( StructOperator op, AnyType type, NamedStorageType fieldName, int idx ) throws IOException {
2018-12-05 22:14:26 +01:00
String operation;
switch( op ) {
case NEW:
case NEW_DEFAULT:
operation = "struct.new";
2018-12-05 22:14:26 +01:00
break;
case GET:
2018-12-14 20:47:53 +01:00
operation = "struct.get";
2018-12-05 22:14:26 +01:00
break;
case SET:
2018-12-14 20:47:53 +01:00
operation = "struct.set";
break;
case NULL:
operation = "ref.null";
type = null;
2018-12-05 22:14:26 +01:00
break;
default:
throw new Error( "Unknown operator: " + op );
}
newline( methodOutput );
2018-12-14 20:47:53 +01:00
methodOutput.append( operation );
if( type != null ) {
methodOutput.append( ' ' ).append( normalizeName( type.toString() ) );
2019-01-13 11:36:07 +01:00
}
if( fieldName != null ) {
methodOutput.append( ' ' ).append( idx ).append( " ;; $" ).append( normalizeName( fieldName.getName() ) );
2018-12-14 20:47:53 +01:00
}
2018-12-05 22:14:26 +01:00
}
}