2018-03-27 20:04:35 +02:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
|
|
|
|
*/
|
2018-03-25 12:57:04 +02:00
|
|
|
package de.inetsoftware.jwebassembly.module;
|
|
|
|
|
|
|
|
import java.io.IOException;
|
2018-03-27 20:04:35 +02:00
|
|
|
import java.util.ArrayList;
|
2018-05-04 20:52:54 +02:00
|
|
|
import java.util.Arrays;
|
2018-03-30 17:33:23 +02:00
|
|
|
import java.util.List;
|
2018-03-25 12:57:04 +02:00
|
|
|
|
|
|
|
import de.inetsoftware.classparser.CodeInputStream;
|
2018-05-01 11:46:42 +02:00
|
|
|
import de.inetsoftware.jwebassembly.WasmException;
|
2018-03-25 12:57:04 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This calculate the goto offsets from Java back to block operations
|
|
|
|
*
|
|
|
|
* @author Volker Berlin
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
class BranchManger {
|
|
|
|
|
2018-03-30 17:33:23 +02:00
|
|
|
private final ArrayList<ParsedBlock> allParsedOperations = new ArrayList<>();
|
|
|
|
|
|
|
|
private final BranchNode root = new BranchNode( 0, Integer.MAX_VALUE, null, null );
|
2018-03-27 20:04:35 +02:00
|
|
|
|
|
|
|
/**
|
2018-03-30 17:33:23 +02:00
|
|
|
* Remove all branch information for reusing the manager.
|
2018-03-27 20:04:35 +02:00
|
|
|
*/
|
|
|
|
void reset() {
|
2018-03-30 17:33:23 +02:00
|
|
|
allParsedOperations.clear();
|
|
|
|
root.clear();
|
2018-03-27 20:04:35 +02:00
|
|
|
}
|
2018-03-25 12:57:04 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Start a new block.
|
|
|
|
*
|
|
|
|
* @param op
|
|
|
|
* the start operation
|
2018-03-27 20:04:35 +02:00
|
|
|
* @param startPosition
|
2018-03-25 12:57:04 +02:00
|
|
|
* the byte position of the start position
|
|
|
|
* @param offset
|
|
|
|
* the relative jump position
|
2018-05-01 11:46:42 +02:00
|
|
|
* @param lineNumber
|
|
|
|
* the current line number
|
2018-03-25 12:57:04 +02:00
|
|
|
*/
|
2018-05-04 20:52:54 +02:00
|
|
|
void start( JavaBlockOperator op, int startPosition, int offset, int lineNumber ) {
|
2018-05-01 11:46:42 +02:00
|
|
|
allParsedOperations.add( new ParsedBlock( op, startPosition, offset, lineNumber ) );
|
2018-03-27 20:04:35 +02:00
|
|
|
}
|
|
|
|
|
2018-05-04 20:52:54 +02:00
|
|
|
/**
|
|
|
|
* Start a new switch block.
|
|
|
|
*
|
|
|
|
* @param startPosition
|
|
|
|
* the byte position of the start position
|
|
|
|
* @param offset
|
|
|
|
* the relative jump position
|
|
|
|
* @param lineNumber
|
|
|
|
* the current line number
|
|
|
|
* @param keys
|
|
|
|
* the values of the cases
|
|
|
|
* @param positions
|
|
|
|
* the code positions
|
|
|
|
* @param the
|
|
|
|
* code position of the default block
|
|
|
|
*/
|
|
|
|
void startSwitch( int startPosition, int offset, int lineNumber, int[] keys, int[] positions, int defaultPosition ) {
|
|
|
|
allParsedOperations.add( new SwitchParsedBlock( startPosition, offset, lineNumber, keys, positions, defaultPosition ) );
|
|
|
|
}
|
|
|
|
|
2018-03-27 20:04:35 +02:00
|
|
|
/**
|
|
|
|
* Calculate all block operators from the parsed information.
|
|
|
|
*/
|
|
|
|
void calculate() {
|
2018-03-30 17:33:23 +02:00
|
|
|
calculate( root, allParsedOperations );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the branch tree for the given branch and parsed sub operations.
|
|
|
|
*
|
|
|
|
* @param parent the parent branch/block in the hierarchy.
|
|
|
|
* @param parsedOperations the not consumed parsed operations of the parent block.
|
|
|
|
*/
|
|
|
|
private void calculate( BranchNode parent, List<ParsedBlock> parsedOperations ) {
|
|
|
|
while( !parsedOperations.isEmpty() ) {
|
|
|
|
ParsedBlock parsedBlock = parsedOperations.remove( 0 );
|
|
|
|
switch( parsedBlock.op ) {
|
2018-03-27 20:04:35 +02:00
|
|
|
case IF:
|
2018-03-30 17:33:23 +02:00
|
|
|
caculateIf( parent, parsedBlock, parsedOperations );
|
2018-03-27 20:04:35 +02:00
|
|
|
break;
|
2018-05-04 20:52:54 +02:00
|
|
|
case SWITCH:
|
|
|
|
caculateSwitch( parent, (SwitchParsedBlock)parsedBlock, parsedOperations );
|
|
|
|
break;
|
2018-03-30 17:33:23 +02:00
|
|
|
default:
|
2018-05-01 11:46:42 +02:00
|
|
|
throw new WasmException( "Unimplemented block code operation: " + parsedBlock.op, null, parsedBlock.lineNumber );
|
2018-03-27 20:04:35 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the ELSE and END position of an IF control structure.
|
|
|
|
*
|
2018-03-30 17:33:23 +02:00
|
|
|
* @param parent
|
|
|
|
* the parent branch
|
2018-03-27 20:04:35 +02:00
|
|
|
* @param startBlock
|
|
|
|
* the start block of the if control structure
|
2018-03-30 17:33:23 +02:00
|
|
|
* @param parsedOperations
|
|
|
|
* the not consumed operations in the parent branch
|
2018-03-27 20:04:35 +02:00
|
|
|
*/
|
2018-03-30 17:33:23 +02:00
|
|
|
private void caculateIf( BranchNode parent, ParsedBlock startBlock, List<ParsedBlock> parsedOperations ) {
|
|
|
|
int i = 0;
|
|
|
|
int endPos = Math.min( startBlock.endPosition, parent.endPos );
|
|
|
|
int gotoPos = endPos - 3; // 3 - byte size of goto instruction
|
|
|
|
BranchNode branch = null;
|
|
|
|
for( ; i < parsedOperations.size(); i++ ) {
|
|
|
|
ParsedBlock parsedBlock = parsedOperations.get( i );
|
2018-05-04 20:52:54 +02:00
|
|
|
if( parsedBlock.startPosition == gotoPos && parsedBlock.op == JavaBlockOperator.GOTO ) {
|
2018-03-30 17:33:23 +02:00
|
|
|
parsedOperations.remove( i );
|
2018-05-04 20:52:54 +02:00
|
|
|
branch = new BranchNode( startBlock.startPosition, startBlock.endPosition, WasmBlockOperator.IF, null );
|
2018-03-30 17:33:23 +02:00
|
|
|
parent.add( branch );
|
|
|
|
if( i > 0 ) {
|
|
|
|
calculate( branch, parsedOperations.subList( 0, i ) );
|
|
|
|
}
|
|
|
|
endPos = parsedBlock.endPosition;
|
2018-05-04 20:52:54 +02:00
|
|
|
branch = new BranchNode( startBlock.endPosition, endPos, WasmBlockOperator.ELSE, WasmBlockOperator.END );
|
2018-03-30 17:33:23 +02:00
|
|
|
parent.add( branch );
|
2018-03-27 20:04:35 +02:00
|
|
|
break;
|
|
|
|
}
|
2018-03-30 17:33:23 +02:00
|
|
|
if( parsedBlock.startPosition > gotoPos ) {
|
2018-03-27 20:04:35 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-30 17:33:23 +02:00
|
|
|
if( branch == null ) {
|
2018-05-04 20:52:54 +02:00
|
|
|
branch = new BranchNode( startBlock.startPosition, endPos, WasmBlockOperator.IF, WasmBlockOperator.END );
|
2018-03-30 17:33:23 +02:00
|
|
|
parent.add( branch );
|
|
|
|
}
|
|
|
|
|
2018-03-27 20:04:35 +02:00
|
|
|
/**
|
|
|
|
* Search the index in the stack to add the END operator
|
|
|
|
*/
|
2018-03-30 17:33:23 +02:00
|
|
|
for( ; i < parsedOperations.size(); i++ ) {
|
|
|
|
ParsedBlock parsedBlock = parsedOperations.get( i );
|
|
|
|
if( parsedBlock.startPosition >= endPos ) {
|
2018-03-27 20:04:35 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2018-03-30 17:33:23 +02:00
|
|
|
if( i > 0 ) {
|
|
|
|
calculate( branch, parsedOperations.subList( 0, i ) );
|
|
|
|
}
|
2018-03-25 12:57:04 +02:00
|
|
|
}
|
|
|
|
|
2018-05-04 20:52:54 +02:00
|
|
|
/**
|
|
|
|
* Calculate the blocks of a switch.
|
|
|
|
*
|
|
|
|
* Sample: The follow Java code:
|
|
|
|
*
|
|
|
|
* <pre>
|
|
|
|
* int b;
|
|
|
|
* switch( a ) {
|
|
|
|
* case 8:
|
|
|
|
* b = 1;
|
|
|
|
* break;
|
|
|
|
* default:
|
|
|
|
* b = 9;
|
|
|
|
* }
|
|
|
|
* return b;
|
|
|
|
* </pre>
|
|
|
|
*
|
|
|
|
* Should be converted to the follow Wasm code:
|
|
|
|
*
|
|
|
|
* <pre>
|
|
|
|
block
|
|
|
|
block
|
|
|
|
block
|
|
|
|
get_local 0
|
|
|
|
i32.const 8
|
|
|
|
i32.sub
|
|
|
|
br_table 0 1
|
|
|
|
end
|
|
|
|
i32.const 1
|
|
|
|
set_local 1
|
|
|
|
br 1
|
|
|
|
end
|
|
|
|
i32.const 9
|
|
|
|
set_local 1
|
|
|
|
end
|
|
|
|
get_local 1
|
|
|
|
return
|
|
|
|
* </pre>
|
|
|
|
*
|
|
|
|
* @param parent
|
|
|
|
* the parent branch
|
|
|
|
* @param switchBlock
|
|
|
|
* the start block of the if control structure
|
|
|
|
* @param parsedOperations
|
|
|
|
* the not consumed operations in the parent branch
|
|
|
|
*/
|
|
|
|
private void caculateSwitch( BranchNode parent, SwitchParsedBlock switchBlock, List<ParsedBlock> parsedOperations ) {
|
2018-05-05 21:43:30 +02:00
|
|
|
int startPosition = switchBlock.startPosition;
|
|
|
|
int posCount = switchBlock.positions.length;
|
|
|
|
boolean isTable = switchBlock.keys.length == 1;
|
|
|
|
if( isTable ) {
|
|
|
|
SwitchCase[] cases = new SwitchCase[posCount+1];
|
|
|
|
|
|
|
|
SwitchCase switchCase = cases[posCount] = new SwitchCase();
|
|
|
|
switchCase.key = Integer.MAX_VALUE;
|
|
|
|
switchCase.position = switchBlock.defaultPosition;
|
|
|
|
for( int i = 0; i < switchBlock.positions.length; i++ ) {
|
|
|
|
switchCase = cases[i] = new SwitchCase();
|
|
|
|
switchCase.key = i;
|
|
|
|
switchCase.position = switchBlock.positions[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate the block number for ever switch case depending its position order
|
|
|
|
Arrays.sort( cases, (a, b) -> Integer.compare( a.position, b.position ) );
|
|
|
|
int blockCount = 0;
|
|
|
|
int lastPosition = -1;
|
|
|
|
BranchNode brTableNode = new BranchNode( startPosition, startPosition, WasmBlockOperator.BR_TABLE, null );
|
|
|
|
BranchNode blockNode = brTableNode;
|
|
|
|
for( int i = 0; i < cases.length; i++ ) {
|
|
|
|
switchCase = cases[i];
|
|
|
|
switchCase.block = blockCount;
|
|
|
|
int currentPosition = switchCase.position;
|
|
|
|
if( lastPosition != currentPosition ) {
|
|
|
|
lastPosition = currentPosition;
|
|
|
|
blockCount++;
|
|
|
|
BranchNode node = new BranchNode( startPosition, startPosition + currentPosition, WasmBlockOperator.BLOCK, WasmBlockOperator.END );
|
|
|
|
node.add( blockNode );
|
|
|
|
blockNode = node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle the GOTO at the end of the CASE blocks.
|
|
|
|
int endPosition = startPosition + lastPosition;
|
|
|
|
for( int i = 0; i < parsedOperations.size(); i++ ) {
|
|
|
|
ParsedBlock parsedBlock = parsedOperations.get( i );
|
|
|
|
if( parsedBlock.startPosition < endPosition ) {
|
|
|
|
if( parsedBlock.endPosition >= endPosition && parsedBlock.op == JavaBlockOperator.GOTO ) {
|
|
|
|
parsedOperations.remove( i );
|
|
|
|
endPosition = parsedBlock.endPosition;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the main block around the switch
|
|
|
|
BranchNode switchNode = new BranchNode( startPosition, endPosition, WasmBlockOperator.BLOCK, WasmBlockOperator.END );
|
|
|
|
switchNode.add( blockNode );
|
|
|
|
parent.add( switchNode );
|
|
|
|
|
|
|
|
// sort back in the natural order and create a br_table
|
|
|
|
Arrays.sort( cases, (a, b) -> Integer.compare( a.key, b.key ) );
|
|
|
|
int[] data = new int[cases.length];
|
|
|
|
for( int i = 0; i < data.length; i++ ) {
|
|
|
|
data[i] = cases[i].block;
|
|
|
|
}
|
|
|
|
brTableNode.data = data;
|
|
|
|
} else {
|
|
|
|
throw new WasmException( "Unsupported switch with lookup", null, ((ParsedBlock)switchBlock).lineNumber );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper structure for caculateSwitch
|
|
|
|
*/
|
|
|
|
private static class SwitchCase {
|
|
|
|
int key;
|
|
|
|
int position;
|
|
|
|
int block;
|
2018-05-04 20:52:54 +02:00
|
|
|
}
|
|
|
|
|
2018-03-25 12:57:04 +02:00
|
|
|
/**
|
|
|
|
* 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 {
|
2018-03-30 17:33:23 +02:00
|
|
|
root.handle( byteCode.getCodePosition(), writer );
|
2018-03-25 12:57:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-03-30 17:33:23 +02:00
|
|
|
* Description of single block/branch from the parsed Java byte code. The parsed branches are plain.
|
2018-03-25 12:57:04 +02:00
|
|
|
*/
|
2018-03-30 17:33:23 +02:00
|
|
|
private static class ParsedBlock {
|
2018-05-04 20:52:54 +02:00
|
|
|
private JavaBlockOperator op;
|
2018-03-25 12:57:04 +02:00
|
|
|
|
2018-05-04 20:52:54 +02:00
|
|
|
int startPosition;
|
2018-03-25 12:57:04 +02:00
|
|
|
|
2018-05-04 20:52:54 +02:00
|
|
|
int endPosition;
|
2018-03-25 12:57:04 +02:00
|
|
|
|
2018-05-04 20:52:54 +02:00
|
|
|
private int lineNumber;
|
2018-05-01 11:46:42 +02:00
|
|
|
|
2018-05-04 20:52:54 +02:00
|
|
|
private ParsedBlock( JavaBlockOperator op, int startPosition, int offset, int lineNumber ) {
|
2018-03-25 12:57:04 +02:00
|
|
|
this.op = op;
|
2018-03-27 20:04:35 +02:00
|
|
|
this.startPosition = startPosition;
|
|
|
|
this.endPosition = startPosition + offset;
|
2018-05-01 11:46:42 +02:00
|
|
|
this.lineNumber = lineNumber;
|
2018-03-25 12:57:04 +02:00
|
|
|
}
|
|
|
|
}
|
2018-03-30 17:33:23 +02:00
|
|
|
|
2018-05-04 20:52:54 +02:00
|
|
|
/**
|
|
|
|
* Description of a parsed switch structure.
|
|
|
|
*/
|
|
|
|
private static class SwitchParsedBlock extends ParsedBlock {
|
|
|
|
private int[] keys;
|
|
|
|
|
|
|
|
private int[] positions;
|
|
|
|
|
|
|
|
private int defaultPosition;
|
|
|
|
|
|
|
|
public SwitchParsedBlock( int startPosition, int offset, int lineNumber, int[] keys, int[] positions, int defaultPosition ) {
|
|
|
|
super( JavaBlockOperator.SWITCH, startPosition, offset, lineNumber );
|
|
|
|
this.keys = keys;
|
|
|
|
this.positions = positions;
|
|
|
|
this.defaultPosition = defaultPosition;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-30 17:33:23 +02:00
|
|
|
/**
|
|
|
|
* Described a code branch/block node in a tree structure.
|
|
|
|
*/
|
|
|
|
private static class BranchNode extends ArrayList<BranchNode> {
|
|
|
|
|
2018-05-04 20:52:54 +02:00
|
|
|
final int startPos;
|
|
|
|
|
|
|
|
final int endPos;
|
2018-03-30 17:33:23 +02:00
|
|
|
|
2018-05-04 20:52:54 +02:00
|
|
|
private final WasmBlockOperator startOp;
|
2018-03-30 17:33:23 +02:00
|
|
|
|
2018-05-04 20:52:54 +02:00
|
|
|
private final WasmBlockOperator endOp;
|
2018-03-30 17:33:23 +02:00
|
|
|
|
2018-05-05 21:43:30 +02:00
|
|
|
private Object data;
|
2018-05-04 20:52:54 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new description.
|
|
|
|
*
|
|
|
|
* @param startPos
|
|
|
|
* the start position in the Java code. Limit also the children.
|
|
|
|
* @param endPos
|
|
|
|
* the end position in the Java code. Limit also the children.
|
|
|
|
* @param startOp
|
|
|
|
* The WASM operation on the start position. Can be null if there is nothing in WASM.
|
|
|
|
* @param endOp
|
|
|
|
* the WASM operation on the end position. Can be null if there is nothing in WASM.
|
|
|
|
*/
|
|
|
|
BranchNode( int startPos, int endPos, WasmBlockOperator startOp, WasmBlockOperator endOp ) {
|
|
|
|
this( startPos, endPos, startOp, endOp, null );
|
|
|
|
}
|
2018-03-30 17:33:23 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new description.
|
|
|
|
*
|
|
|
|
* @param startPos
|
|
|
|
* the start position in the Java code. Limit also the children.
|
|
|
|
* @param endPos
|
|
|
|
* the end position in the Java code. Limit also the children.
|
|
|
|
* @param startOp
|
|
|
|
* The WASM operation on the start position. Can be null if there is nothing in WASM.
|
|
|
|
* @param endOp
|
|
|
|
* the WASM operation on the end position. Can be null if there is nothing in WASM.
|
2018-05-04 20:52:54 +02:00
|
|
|
* @param data
|
|
|
|
* extra data depending of the start operator
|
2018-03-30 17:33:23 +02:00
|
|
|
*/
|
2018-05-04 20:52:54 +02:00
|
|
|
BranchNode( int startPos, int endPos, WasmBlockOperator startOp, WasmBlockOperator endOp, Object data ) {
|
2018-03-30 17:33:23 +02:00
|
|
|
this.startPos = startPos;
|
|
|
|
this.endPos = endPos;
|
|
|
|
this.startOp = startOp;
|
|
|
|
this.endOp = endOp;
|
2018-05-04 20:52:54 +02:00
|
|
|
this.data = data;
|
2018-03-30 17:33:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void handle( int codePositions, ModuleWriter writer ) throws IOException {
|
|
|
|
if( codePositions < startPos || codePositions > endPos ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if( codePositions == startPos && startOp != null ) {
|
2018-05-04 20:52:54 +02:00
|
|
|
writer.writeBlockCode( startOp, data );
|
2018-03-30 17:33:23 +02:00
|
|
|
}
|
|
|
|
for( BranchNode branch : this ) {
|
|
|
|
branch.handle( codePositions, writer );
|
|
|
|
}
|
|
|
|
if( codePositions == endPos && endOp != null ) {
|
2018-05-04 20:52:54 +02:00
|
|
|
writer.writeBlockCode( endOp, null );
|
2018-03-30 17:33:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-25 12:57:04 +02:00
|
|
|
}
|