diff --git a/src/de/inetsoftware/jwebassembly/module/WasmCodeBuilder.java b/src/de/inetsoftware/jwebassembly/module/WasmCodeBuilder.java index 065f2d2..d7e2d8e 100644 --- a/src/de/inetsoftware/jwebassembly/module/WasmCodeBuilder.java +++ b/src/de/inetsoftware/jwebassembly/module/WasmCodeBuilder.java @@ -99,8 +99,8 @@ public abstract class WasmCodeBuilder { * the code position/offset in the Java method */ @Nonnull - protected void addLoadStoreInstruction( boolean load, @Nonnegative int wasmIdx, int javaCodePos ) { - instructions.add( new WasmLoadStoreInstruction( load, wasmIdx, null, javaCodePos ) ); + protected void addLocalInstruction( boolean load, @Nonnegative int wasmIdx, int javaCodePos ) { + instructions.add( new WasmLocalInstruction( load, wasmIdx, javaCodePos ) ); } /** diff --git a/src/de/inetsoftware/jwebassembly/module/WasmLocalInstruction.java b/src/de/inetsoftware/jwebassembly/module/WasmLocalInstruction.java new file mode 100644 index 0000000..0d5ee6e --- /dev/null +++ b/src/de/inetsoftware/jwebassembly/module/WasmLocalInstruction.java @@ -0,0 +1,77 @@ +/* + 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 javax.annotation.Nonnegative; +import javax.annotation.Nonnull; + +/** + * WasmInstruction for load and store local variables. + * + * @author Volker Berlin + * + */ +class WasmLocalInstruction extends WasmInstruction { + + private boolean load; + + private int idx; + + /** + * Create an instance of a load/store instruction + * + * @param load + * true: if load + * @param idx + * the memory/slot idx of the variable + * @param javaCodePos + * the code position/offset in the Java method + */ + WasmLocalInstruction( boolean load, @Nonnegative int idx, int javaCodePos ) { + super( javaCodePos ); + this.load = load; + this.idx = idx; + } + + /** + * {@inheritDoc} + */ + public void writeTo( @Nonnull ModuleWriter writer ) throws IOException { + if( load ) { + writer.writeLoad( idx ); + } else { + writer.writeStore( idx ); + } + } + + /** + * {@inheritDoc} + */ + ValueType getPushValueType() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + int getPopCount() { + return load ? 0 : 1; + } +} diff --git a/src/de/inetsoftware/jwebassembly/watparser/WatParser.java b/src/de/inetsoftware/jwebassembly/watparser/WatParser.java new file mode 100644 index 0000000..76e94ff --- /dev/null +++ b/src/de/inetsoftware/jwebassembly/watparser/WatParser.java @@ -0,0 +1,151 @@ +/* + 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.watparser; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nonnegative; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import de.inetsoftware.jwebassembly.WasmException; +import de.inetsoftware.jwebassembly.module.NumericOperator; +import de.inetsoftware.jwebassembly.module.ValueType; +import de.inetsoftware.jwebassembly.module.ValueTypeConvertion; +import de.inetsoftware.jwebassembly.module.WasmBlockOperator; +import de.inetsoftware.jwebassembly.module.WasmCodeBuilder; + +/** + * Parser for text format of a function. + * + * @author Volker Berlin + */ +public class WatParser extends WasmCodeBuilder { + + public WatParser() { + } + + /** + * Parse the given wasm text format and generate a list of WasmInstuctions + * + * @param wat + * the text format content of a function + */ + public void parse( String wat, int lineNumber ) { + try { + reset(); + + List tokens = splitTokens( wat ); + for( int i = 0; i < tokens.size(); i++ ) { + int javaCodePos = i; + String tok = tokens.get( i ); + switch( tok ) { + case "get_local": + addLocalInstruction( true, getInt( tokens, ++i), javaCodePos ); + break; + case "set_local": + addLocalInstruction( false, getInt( tokens, ++i), javaCodePos ); + break; +// case "get_global": +// addGlobalInstruction( true, ref, javaCodePos ); +// break; + case "i32.const": + addConstInstruction( getInt( tokens, ++i), ValueType.i32, javaCodePos ); + break; + case "i32.add": + addNumericInstruction( NumericOperator.add, ValueType.i32, javaCodePos ); + break; + case "i64.extend_s/i32": + addConvertInstruction( ValueTypeConvertion.i2l, javaCodePos ); + break; +// case "call": +// addCallInstruction( method, javaCodePos ); +// break; + case "return": + addBlockInstruction( WasmBlockOperator.RETURN, null, javaCodePos ); + break; + } + } + } catch( Exception ex ) { + throw WasmException.create( ex, lineNumber ); + } + } + + /** + * Get the token at given position as int. + * + * @param tokens + * the token list + * @param idx + * the position in the tokens + * @return the int value + */ + private int getInt( List tokens, @Nonnegative int idx ) { + return Integer.parseInt( get( tokens, idx ) ); + } + + /** + * Get the token at given position + * + * @param tokens + * the token list + * @param idx + * the position in the tokens + * @return the token + */ + @Nonnull + private String get( List tokens, @Nonnegative int idx ) { + if( idx >= tokens.size() ) { + String previous = tokens.get( Math.min( idx, tokens.size() ) - 1 ); + throw new WasmException( "Missing Token in wasm text format after token: " + previous, -1 ); + } + return tokens.get( idx ); + } + + /** + * Split the string in tokens. + * + * @param wat + * string with wasm text format + * @return the token list. + */ + private List splitTokens( @Nullable String wat ) { + ArrayList tokens = new ArrayList<>(); + int count = wat.length(); + + int off = 0; + for( int i = 0; i < count; i++ ) { + char ch = wat.charAt( i ); + switch( ch ) { + case ' ': + case '\n': + case '\r': + case '\t': + if( off + 1 < i ) { + tokens.add( wat.substring( off, i ) ); + } + off = i + 1; + break; + } + } + if( off < count ) { + tokens.add( wat.substring( off, count ) ); + } + return tokens; + } +} diff --git a/test/de/inetsoftware/jwebassembly/module/WatParserTest.java b/test/de/inetsoftware/jwebassembly/module/WatParserTest.java new file mode 100644 index 0000000..cc6d29a --- /dev/null +++ b/test/de/inetsoftware/jwebassembly/module/WatParserTest.java @@ -0,0 +1,123 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.HashMap; + +import javax.annotation.Nullable; + +import org.junit.Test; + +import de.inetsoftware.jwebassembly.WasmException; +import de.inetsoftware.jwebassembly.text.TextModuleWriter; +import de.inetsoftware.jwebassembly.watparser.WatParser; + +/** + * @author Volker Berlin + */ +public class WatParserTest { + + private void test( String wat ) throws IOException { + WatParser parser = new WatParser(); + parser.parse( wat, 100 ); + WasmCodeBuilder codeBuilder = parser; + StringBuilder builder = new StringBuilder(); + ModuleWriter writer = new TextModuleWriter( builder, new HashMap<>() ); + for( WasmInstruction instruction : codeBuilder.getInstructions() ) { + instruction.writeTo( writer ); + } + writer.writeMethodFinish(); + String expected = normalize( "(module " + wat + " )" ); + String actual = normalize( builder ); + assertEquals( expected, actual ); + } + + private String normalize( @Nullable CharSequence str ) { + boolean wasSpace = false; + StringBuilder builder = new StringBuilder(); + for( int i = 0; i < str.length(); i++ ) { + char ch = str.charAt( i ); + switch( ch ) { + case ' ': + case '\n': + case '\r': + case '\t': + if( !wasSpace ) { + builder.append( ' ' ); + wasSpace = true; + } + break; + default: + builder.append( ch ); + wasSpace = false; + } + } + return builder.toString(); + } + + private void testError( String wat, String errorMessage ) throws IOException { + try { + test( wat ); + fail( "Exception expected with message: " + errorMessage ); + } catch( WasmException ex ) { + String error = ex.getMessage(); + int newlineIdx = error.indexOf( '\n' ); + if( newlineIdx > 0 ) { + error = error.substring( 0, newlineIdx ); + } + assertEquals( errorMessage, error ); + } + } + + @Test + public void getLocal() throws IOException { + test( "get_local 1" ); + } + + @Test + public void setLocal() throws IOException { + test( "set_local 2" ); + } + + @Test + public void i32_add() throws IOException { + test( "i32.add" ); + } + + @Test + public void i32_const() throws IOException { + test( " i32.const -7 " ); + } + + @Test + public void i64_extend_s_i32() throws IOException { + test( "i64.extend_s/i32" ); + } + + @Test + public void return_() throws IOException { + test( "return\n" ); + } + + @Test + public void errorMissingToken() throws IOException { + testError( "i32.const", "Missing Token in wasm text format after token: i32.const" ); + } +}