// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz)
// All rights reserved.
//
// This software may be modified and distributed under the terms
// of the BSD license. See the LICENSE file for details.
package wyil.lang;
import java.util.*;
import wyil.util.AbstractBytecode;
import wybs.lang.NameID;
/**
* <p>
* Each bytecode has a binary format which identifies the <i>opcode</i>,
* <i>operand registers</i>, <i>operand groups</i>, <i>blocks</code> and
* <i>other items</i> used (e.g. names, constants, etc). The generic
* organisation of a bytecode is as follows:
* </p>
*
* <pre>
* +--------+----------+----------------+--------+-------------+
* | opcode | operands | operand groups | blocks | other items |
* +--------+----------+----------------+--------+-------------+
* </pre>
* <p>
* The opcode is currently always 1 byte, whilst the remainder varies between
* instructions. The opcode itself splits into two components:
* </p>
*
* <pre>
* 7 6 5 0
* +-----+-----------+
* | fmt | operation |
* +-----+-----------+
* </pre>
* <p>
* Here, <i>operation</i> identifies the bytecode operation (e.g. add, invoke,
* etc), whilst <i>fmt</i> identifies the bytecode format.
* </p>
*
* @author David J. Pearce
*/
public interface Bytecode {
/**
* Return the top-level operands in this bytecode.
*
* @return
*/
public int[] getOperands();
/**
* Get the number of operands in this bytecode
*
* @return
*/
public int numberOfOperands();
/**
* Return the ith top-level operand in this bytecode.
*
* @param i
* @return
*/
public int getOperand(int i);
/**
* Get the number of operand groups in this bytecode
*
* @return
*/
public int numberOfOperandGroups();
/**
* Get the ith operand group in this bytecode
*
* @param i
* @return
*/
public int[] getOperandGroup(int i);
/**
* Return the opcode value of this bytecode.
*
* @return
*/
public int getOpcode();
// ===============================================================
// Bytecode Expressions
// ===============================================================
/**
* Represents the class of bytecodes which correspond to expressions in the
* source language.
*
* @author David J. Pearce
*
*/
public interface Expr extends Bytecode {
}
/**
* <p>
* A convert bytecode has the following layout:
* </p>
*
* <pre>
* +--------+---------+
* | opcode | operand |
* +--------+---------+
* </pre>
*
* <p>
* This corresponds to an explicit or implicit cast in the source language.
* This bytecode is the only way to change the type of a value. It's purpose
* is to simplify implementations which have different representations of
* data types. A convert bytecode must be inserted whenever the type of a
* value changes. For example, when a variable is retyped using the
* <code>is</code> operator, we must convert its value into a value of the
* new type.
* </p>
*
* <p>
* <b>NOTE:</b> In many cases, this bytecode may correspond to a nop on the
* hardware. Consider converting from <code>any[]</code> to <code>any</code>
* . On the JVM, <code>any</code> translates to <code>Object</code>, whilst
* <code>any[]</code> translates to <code>List</code> (which is an instance
* of <code>Object</code>). Thus, no conversion is necessary since
* <code>List</code> can safely flow into <code>Object</code>.
* </p>
*
*/
public static final class Convert extends AbstractBytecode implements Expr {
public Convert(int operand) {
super(operand);
}
public int operand() {
return getOperand(0);
}
@Override
public int getOpcode() {
return OPCODE_convert;
}
@Override
public String toString() {
return "castt " + Util.arrayToString(getOperands());
}
}
/**
* <p>
* A constant bytecode has the following layout:
* </p>
*
* <pre>
* +--------+----------+
* | opcode | constant |
* +--------+----------+
* </pre>
*
* Here, constant represents a value, such as an <i>integer</i>,
* <i>array</i>, etc.
*
* @author David J. Pearce
*
*/
public static final class Const extends AbstractBytecode implements Expr {
private final Constant constant;
public Const(Constant constant) {
this.constant = constant;
}
@Override
public int getOpcode() {
return OPCODE_const;
}
public Constant constant() {
return constant;
}
@Override
public int hashCode() {
return constant.hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof Const) {
Const c = (Const) o;
return constant.equals(c.constant);
}
return false;
}
@Override
public String toString() {
return "const " + constant.toString();
}
}
/**
* <p>
* A field load bytecode has the following layout:
* </p>
*
* <pre>
* +--------+---------+-----------+
* | opcode | operand | fieldName |
* +--------+---------+-----------+
* </pre>
*
* <p>
* The bytecode reads the field of the given name out of the record value
* returned by the operand
* </p>
*
* @author David J. Pearce
*
*/
public static final class FieldLoad extends AbstractBytecode implements Expr {
private final String field;
public FieldLoad(int operand, String field) {
super(operand);
if (field == null) {
throw new IllegalArgumentException("FieldLoad field argument cannot be null");
}
this.field = field;
}
@Override
public int getOpcode() {
return OPCODE_fieldload;
}
public int operand() {
return getOperand(0);
}
public String fieldName() {
return field;
}
@Override
public boolean equals(Object o) {
if (o instanceof FieldLoad) {
FieldLoad i = (FieldLoad) o;
return super.equals(i) && field.equals(i.field);
}
return false;
}
@Override
public int hashCode() {
return super.hashCode() + field.hashCode();
}
@Override
public String toString() {
return "recfield %" + getOperand(0) + " " + field;
}
}
/**
* <p>
* A lambda bytecode has the following layout:
* </p>
*
* <pre>
* +--------+------+-------------+---------------+
* | opcode | body | parameter[] | environment[] |
* +--------+------+-------------+---------------+
* </pre>
*
* <p>
* Here, the body operand identifies an expression which constitutes the
* body of this lambda. The parameters are those variables which are
* declared by the lambda itself for use within the lambda body. The
* environment identifies those variables from the surrounding environment.
* </p>
*/
public static final class Lambda extends AbstractBytecode implements Expr {
private final Type.FunctionOrMethod type;
/**
* Create a new lambda bytecode
*
* @param type
* The type of the resulting lambda.
* @param body
* The expression corresponding to the body of the lambda
* expression.
* @param parameters
* The set of declared variables using within the lambda
* expression.
* @param environment
* The set of variables from the enclosing scope which are
* used within the lambda body.
*/
public Lambda(Type.FunctionOrMethod type, int body, int[] parameters, int[] environment) {
super(body, new int[][]{parameters, environment});
this.type = type;
}
@Override
public int getOpcode() {
return OPCODE_lambda;
}
public Type.FunctionOrMethod type() {
return type;
}
public int body() {
return getOperand(0);
};
public int[] parameters() {
return getOperandGroup(0);
}
public int[] environment() {
return getOperandGroup(1);
}
@Override
public boolean equals(Object o) {
if (o instanceof Lambda) {
Lambda i = (Lambda) o;
return type.equals(i.type) && super.equals(i);
}
return false;
}
@Override
public int hashCode() {
return type.hashCode() + super.hashCode();
}
@Override
public String toString() {
return "lambda " + Util.arrayToString(getOperands()) + " " + type;
}
}
/**
* Represents the set of valid operators (e.g. '+','-',etc).
*
* @author David J. Pearce
*
*/
public enum OperatorKind {
// Unary
NEG(OPCODE_neg) {
@Override
public String toString() {
return "neg";
}
},
NOT(OPCODE_logicalnot) {
@Override
public String toString() {
return "not";
}
},
BITWISEINVERT(OPCODE_bitwiseinvert) {
@Override
public String toString() {
return "invert";
}
},
DEREFERENCE(OPCODE_dereference) {
@Override
public String toString() {
return "deref";
}
},
ARRAYLENGTH(OPCODE_arraylength) {
@Override
public String toString() {
return "arrlen";
}
},
// Binary
ADD(OPCODE_add) {
@Override
public String toString() {
return "add";
}
},
SUB(OPCODE_sub) {
@Override
public String toString() {
return "sub";
}
},
MUL(OPCODE_mul) {
@Override
public String toString() {
return "mul";
}
},
DIV(OPCODE_div) {
@Override
public String toString() {
return "div";
}
},
REM(OPCODE_rem) {
@Override
public String toString() {
return "rem";
}
},
EQ(OPCODE_eq) {
@Override
public String toString() {
return "eq";
}
},
NEQ(OPCODE_ne) {
@Override
public String toString() {
return "neq";
}
},
LT(OPCODE_lt) {
@Override
public String toString() {
return "lt";
}
},
LTEQ(OPCODE_le) {
@Override
public String toString() {
return "lteq";
}
},
GT(OPCODE_gt) {
@Override
public String toString() {
return "gt";
}
},
GTEQ(OPCODE_ge) {
@Override
public String toString() {
return "gteq";
}
},
AND(OPCODE_logicaland) {
@Override
public String toString() {
return "land";
}
},
OR(OPCODE_logicalor) {
@Override
public String toString() {
return "lor";
}
},
BITWISEOR(OPCODE_bitwiseor) {
@Override
public String toString() {
return "bor";
}
},
BITWISEXOR(OPCODE_bitwisexor) {
@Override
public String toString() {
return "bxor";
}
},
BITWISEAND(OPCODE_bitwiseand) {
@Override
public String toString() {
return "band";
}
},
LEFTSHIFT(OPCODE_shl) {
@Override
public String toString() {
return "bshl";
}
},
RIGHTSHIFT(OPCODE_shr) {
@Override
public String toString() {
return "bshr";
}
},
ARRAYINDEX(OPCODE_arrayindex) {
@Override
public String toString() {
return "arridx";
}
},
ARRAYGENERATOR(OPCODE_arraygen) {
@Override
public String toString() {
return "arrgen";
}
},
ARRAYCONSTRUCTOR(OPCODE_array) {
@Override
public String toString() {
return "arrinit";
}
},
RECORDCONSTRUCTOR(OPCODE_record) {
@Override
public String toString() {
return "recinit";
}
},
IS(OPCODE_is) {
@Override
public String toString() {
return "istype";
}
},
NEW(OPCODE_newobject) {
@Override
public String toString() {
return "new";
}
};
public int opcode;
private OperatorKind(int offset) {
this.opcode = offset;
}
};
/**
* <p>
* An operator bytecode has the following layout:
* </p>
*
* <pre>
* +--------+-----------+
* | opcode | operand[] |
* +--------+-----------+
* </pre>
*
* <p>
* Here, the operand array identifies one or more operands in which this
* operator operators. The operator produces exactly one value.
* </p>
*
* @author David J. Pearce
*
*/
public static final class Operator extends AbstractBytecode implements Expr {
private final OperatorKind kind;
public Operator(int[] operands, OperatorKind bop) {
super(operands);
this.kind = bop;
}
@Override
public int getOpcode() {
return kind().opcode;
}
public OperatorKind kind() {
return kind;
}
@Override
public String toString() {
return kind() + " " + Util.arrayToString(getOperands());
}
}
public enum QuantifierKind {
SOME(OPCODE_some), ALL(OPCODE_all);
public int opcode;
private QuantifierKind(int offset) {
this.opcode = offset;
}
}
/**
* <p>
* A quantifier bytecode has the following layout:
* </p>
*
* <pre>
* +--------+-----------+---------+-----+---------+
* | opcode | condition | range[] | ... | range[] |
* +--------+-----------+---------+-----+---------+
* </pre>
*
* <p>
* Here, the condition operand identifies an expression which the quantifier
* is asserting over the given ranges. The ranges themselves identifier one
* or more operand groups, each of which holds three entries: the declared
* variable, the start operand, the end operand.
* </p>
*/
public static final class Quantifier extends AbstractBytecode implements Expr {
private final QuantifierKind kind;
public Quantifier(QuantifierKind kind, int operand, Range... ranges) {
super(operand, extract(ranges));
this.kind = kind;
}
public QuantifierKind kind() {
return kind;
}
@Override
public int getOpcode() {
return kind.opcode;
}
public int body() {
return getOperand(0);
}
public Range[] ranges() {
Bytecode.Range[] ranges = new Bytecode.Range[numberOfOperandGroups()];
for (int i = 0; i != ranges.length; i = i + 1) {
int[] group = getOperandGroup(i);
ranges[i] = new Bytecode.Range(group[0], group[1], group[2]);
}
return ranges;
}
private static int[][] extract(Range[] ranges) {
int[][] groups = new int[ranges.length][3];
for (int i = 0; i != ranges.length; ++i) {
Range r = ranges[i];
groups[i][0] = r.variable;
groups[i][1] = r.startOperand;
groups[i][2] = r.endOperand;
}
return groups;
}
@Override
public String toString() {
return "quantifier";
}
}
public static final class Range {
private final int variable;
private final int startOperand;
private final int endOperand;
public Range(int variable, int startOperand, int endOperand) {
this.variable = variable;
this.startOperand = startOperand;
this.endOperand = endOperand;
}
/**
* Return the location index for the variable this range is declaring.
*
* @return
*/
public int variable() {
return variable;
}
/**
* Return the start operand of this range.
*
* @return
*/
public int startOperand() {
return startOperand;
}
/**
* Return the end operand of this range.
*
* @return
*/
public int endOperand() {
return endOperand;
}
@Override
public boolean equals(Object o) {
if (o instanceof Range) {
Range r = (Range) o;
return variable == r.variable && startOperand == r.startOperand && r.endOperand == endOperand;
}
return false;
}
@Override
public int hashCode() {
return variable ^ startOperand ^ endOperand;
}
}
/**
* <p>
* A variable access bytecode represents a specific read of a given variable.
* </p>
*
* <pre>
* +--------+---------+
* | opcode | operand |
* +--------+---------+
* </pre>
*
* <p>
* Here, the operand refers to the variable declaration corresponding to
* this variable Access.
* </p>
*/
public static final class VariableAccess extends AbstractBytecode implements Expr {
private boolean isCopy;
public VariableAccess(boolean isCopy, int operand) {
super(operand);
this.isCopy = isCopy;
}
@Override
public int getOpcode() {
return isCopy ? OPCODE_varcopy : OPCODE_varmove;
}
@Override
public String toString() {
return "read " + Util.arrayToString(getOperands());
}
}
// ===============================================================
// Bytecode Statements
// ===============================================================
/**
* A statement bytecode represents a bytecode that contains a sequence of
* zero or more bytecodes. For example, a loop bytecode contains its loop
* body. The nested blocks of bytecodes are represented as a block
* identifier in the enclosing forest.
*
* @author David J. Pearce
*
*/
public interface Stmt extends Bytecode {
/**
* Determine the number of blocks contained in this bytecode.
*
* @return
*/
public int numberOfBlocks();
/**
* Get the ith block contained in this statement
*
* @param i
* @return
*/
public int getBlock(int i);
/**
* Get the blocks contained in this statement
*
* @param i
* @return
*/
public int[] getBlocks();
}
/**
* <p>
* An alias declaration bytecode has the following form:
* </p>
*
* <pre>
* +--------+---------+
* | opcode | operand |
* +--------+---------+
* </pre>
*
* <p>
* Here, the operand identifies the variable declaration being aliased
* (which may be an alias declaration itself).
* </p>
*
* @author David J. Pearce
*
*/
public static final class AliasDeclaration extends AbstractBytecode implements Stmt {
public AliasDeclaration(int initialiser) {
super(initialiser);
}
@Override
public int getOpcode() {
return OPCODE_aliasdecl;
}
@Override
public String toString() {
return "alias (%" + getOperand(0) + ")";
}
}
/**
* An abstract class representing either an <code>assert</code> or
* <code>assume</code> bytecode.
*
* @author David J. Pearce
*
*/
public static abstract class AssertOrAssume extends AbstractBytecode implements Stmt {
private AssertOrAssume(int operand) {
super(operand);
}
public int operand() {
return getOperand(0);
}
}
/**
* <p>
* An assert bytecode has the following layout:
* </p>
*
* <pre>
* +--------+-----------+
* | opcode | condition |
* +--------+-----------+
* </pre>
*
* <p>
* Here, the condition identifies an operand which should always evaluate to
* true. This condition should be enforced at compile time.
* </p>
*
* @author David J. Pearce
*
*/
public static class Assert extends AssertOrAssume {
public Assert(int operand) {
super(operand);
}
@Override
public int getOpcode() {
return OPCODE_assert;
}
@Override
public String toString() {
return "assert %" + getOperand(0);
}
}
/**
* <p>
* An assignment bytecode has the following layout:
* </p>
*
* <pre>
* +--------+----------------+-----------------+
* | opcode | leftHandSide[] | rightHandSide[] |
* +--------+----------------+-----------------+
* </pre>
*
* <p>
* Here, the left-hand side identifies zero or more operands which are being
* assigned to, whilst the right-hand side identifies one or more operands
* which whose results are being assigned. The left-hand side may have fewer
* operands than the right-hand side. This happens, for example, in the case
* of an invocation where the result is ignored.
* </p>
*
* @author David J. Pearce
*
*/
public static final class Assign extends AbstractBytecode implements Stmt {
/**
* Construct an assignment from a right-hand operand to a left-hand
* operand.
*
* @param lhs
* LVal on left-hand side which is assigned to
* @param rhs
* Operand on right-hand side whose value is assigned
*/
public Assign(int lhs, int rhs) {
this(new int[] { lhs }, new int[] { rhs });
}
/**
* Construct an assignment from a right-hand operand to a left-hand
* operand.
*
* @param lhs
* LVal on left-hand side which is assigned to
* @param rhs
* Operand on right-hand side whose value is assigned
*/
public Assign(int[] lhs, int[] rhs) {
super(new int[][] { lhs, rhs });
}
@Override
public int getOpcode() {
return OPCODE_assign;
}
/**
* Returns operand(s) from which assigned value is written to. This is
* also known as the "left-hand side".
*
* @return
*/
public int[] leftHandSide() {
return getOperandGroup(0);
}
/**
* Returns operand(s) from which assigned value is read. This is also
* known as the "right-hand side".
*
* @return
*/
public int[] rightHandSide() {
return getOperandGroup(1);
}
@Override
public String toString() {
return "assign " + Util.arrayToString(leftHandSide()) + " = " + Util.arrayToString(rightHandSide());
}
}
/**
* <p>
* An assume bytecode has the following layout:
* </p>
*
* <pre>
* +--------+-----------+
* | opcode | condition |
* +--------+-----------+
* </pre>
*
* <p>
* Here, the condition identifies an operand which is assumed to always
* evaluate to true. This assumption should be tested at runtime.
* </p>
*
* @author David J. Pearce
*
*/
public static final class Assume extends AssertOrAssume {
public Assume(int operand) {
super(operand);
}
@Override
public int getOpcode() {
return OPCODE_assume;
}
@Override
public String toString() {
return "assume %" + operand();
}
}
/**
* <p>
* A break bytecode has the following layout:
* </p>
*
* <pre>
* +--------+
* | opcode |
* +--------+
* </pre>
*
* <p>
* Here, control will immediately exit the enclosing loop upon executing
* this bytecode.
* </p>
*
* @author David J. Pearce
*
*/
public static final class Break extends AbstractBytecode implements Stmt {
public Break() {
super(new int[0]);
}
@Override
public int getOpcode() {
return OPCODE_break;
}
@Override
public String toString() {
return "break ";
}
}
/**
* <p>
* A continue bytecode has the following layout:
* </p>
*
* <pre>
* +--------+
* | opcode |
* +--------+
* </pre>
*
* <p>
* Here, control will immediately complete the enclosing loop body upon
* executing this bytecode.
* </p>
*
* @author David J. Pearce
*
*/
public static final class Continue extends AbstractBytecode implements Stmt {
public Continue() {
super(new int[]{});
}
@Override
public int getOpcode() {
return OPCODE_continue;
}
@Override
public String toString() {
return "cont";
}
}
/**
* <p>
* A debug bytecode has the following layout:
* </p>
*
* <pre>
* +--------+--------+
* | opcode | string |
* +--------+--------+
* </pre>
*
* <p>
* Here, the string identifies an operand returning a string result which
* will be printed to the debug console.
* </p>
*
* <b>NOTE</b> This bytecode is not intended to form part of the program's
* operation. Rather, it is to facilitate debugging within functions (since
* they cannot have side-effects). Furthermore, if debugging is disabled,
* this bytecode is a nop.
*
* @author David J. Pearce
*
*/
public static final class Debug extends AbstractBytecode implements Stmt {
public Debug(int operand) {
super(operand);
}
@Override
public int getOpcode() {
return OPCODE_debug;
}
@Override
public String toString() {
return "debug %" + getOperand(0);
}
}
/**
* A Do While bytecode has the same format as the underlying loop bytecode.
*
* @author David J. Pearce
*
*/
public static class DoWhile extends Loop {
public DoWhile(int body, int condition, int[] invariants, int[] modified) {
super(body, condition, invariants, modified);
}
@Override
public int getOpcode() {
return OPCODE_dowhile;
}
@Override
public String toString() {
return "dowhile";
}
}
/**
* <p>
* A panic bytecode has the following layout:
* </p>
*
* <pre>
* +--------+
* | opcode |
* +--------+
* </pre>
*
* <p>
* Upon execution of this bytecode, the machine will halt immediately and
* indicate an unrecoverable error. At this time, there is no way to recover
* from a panic, though this may be supported in the future.
* </p>
*
* @author David J. Pearce
*
*/
public static final class Fail extends AbstractBytecode implements Stmt {
// FIXME: should be renamed to panic as this is a more descriptive name
@Override
public int getOpcode() {
return OPCODE_fail;
}
@Override
public String toString() {
return "fail";
}
}
/**
* <p>
* An if bytecode has one of the the following layouts:
* </p>
*
* <pre>
* +--------+-----------+------------+
* | opcode | condition | trueBranch |
* +--------+-----------+------------+
* </pre>
*
* <pre>
* +--------+-----------+------------+-------------+
* | opcode | condition | trueBranch | falseBranch |
* +--------+-----------+------------+-------------+
* </pre>
*
* <p>
* Here, the condition identifies the condition which determines whether the
* true branch is taken or not. If not, and there is a false branch, then
* that is executed. Otherwise, control proceeds to the next logical
* statement in the enclosing block.
* </p>
*/
public static final class If extends AbstractBytecode implements Stmt {
public If(int operand, int trueBranch) {
super(operand, null, new int[] {trueBranch});
}
public If(int operand, int trueBranch, int falseBranch) {
super(operand, null, new int[]{trueBranch, falseBranch});
}
@Override
public int getOpcode() {
return numberOfBlocks() == 1 ? OPCODE_if : OPCODE_ifelse;
}
public int condition() {
return getOperand(0);
}
/**
* Check whether this bytecode has a false branch of not.
*
* @return
*/
public boolean hasFalseBranch() {
return numberOfBlocks() > 1;
}
/**
* Return the block identifier for the true branch associated with this
* bytecode.
*
* @return
*/
public int trueBranch() {
return getBlock(0);
}
/**
* Return the block identifier for the false branch associated with this
* bytecode.
*
* @return
*/
public int falseBranch() {
return getBlock(1);
}
@Override
public String toString() {
int c = getOperand(0);
int tb = trueBranch();
if (hasFalseBranch()) {
int fb = falseBranch();
return "if" + " (%" + c + ", %" + tb + ", %" + fb + ")";
} else {
return "if" + " (%" + c + ", %" + tb + ")";
}
}
}
/**
* <p>
* A loop bytecode has the following layout:
* </p>
*
* <pre>
* +--------+-----------+--------------+------------+------+
* | opcode | condition | invariants[] | modified[] | body |
* +--------+-----------+--------------+------------+------+
* </pre>
*
* <p>
* Here, the condition identifies the loop condition which, when false, will
* cause the loop to terminate. The invariants identifies zero or more
* operands which must be true on every iteration of the loop. The modified
* variables are those variables which are, in some sense, modified in the
* body of the loop. The body identifies a block which forms the body of the
* loop.
* </p>
*/
public static abstract class Loop extends AbstractBytecode implements Stmt {
public Loop(int body, int condition, int[] invariants, int[] modified) {
super(condition, new int[][]{invariants,modified}, new int[]{body});
}
/**
* Return the block identifier of the loop body.
*
* @return
*/
public int body() {
return getBlock(0);
}
/**
* Return the loop condition operand. This must be true for iteration to
* continue around the loop.
*
* @return
*/
public int condition() {
return getOperand(0);
}
/**
* Return the array of operands making up the loop invariant. Each of
* these corresponds to a "where" clause in the original program.
*
* @return
*/
public int[] invariants() {
return getOperandGroup(0);
}
/**
* Return the array of modified variables which are those assigned (in
* some way) in the body of the loop. These cannot be operands, they
* must be variables. These are needed for verification.
*/
public int[] modifiedVariables() {
return getOperandGroup(1);
}
}
/**
* A While bytecode has the same format as the underlying loop bytecode. The
* loop invariant must hold on entry to the loop, and will hold on normal
* exit (though not necessarily for a break exit).
*
* @author David J. Pearce
*
*/
public static class While extends Loop {
public While(int body, int condition, int[] invariants, int[] modified) {
super(body, condition, invariants, modified);
}
@Override
public int getOpcode() {
return OPCODE_while;
}
@Override
public String toString() {
return "while " + condition() + " do " + getBlock(0);
}
}
/**
* <p>
* A return bytecode has the following layout:
* </p>
*
* <pre>
* +--------+------------+
* | opcode | operands[] |
* +--------+------------+
* </pre>
*
* <p>
* Here, there are zero or more operands which can be returned.
* </p>
*
* @author David J. Pearce
*
*/
public static final class Return extends AbstractBytecode implements Stmt {
public Return() {
}
public Return(int... operands) {
super(operands);
}
@Override
public int getOpcode() {
return OPCODE_return;
}
@Override
public String toString() {
return "return " + Util.arrayToString(getOperands());
}
}
/**
* <p>
* A skip bytecode has the following layout:
* </p>
*
* <pre>
* +--------+
* | opcode |
* +--------+
* </pre>
*
* <p>
* Upon execution of this bytecode, the machine simply moves on to the next
* instruction.
* </p>
*
* @author David J. Pearce
*
*/
public static final class Skip extends AbstractBytecode implements Stmt {
// FIXME: should be renamed to panic as this is a more descriptive name
@Override
public int getOpcode() {
return OPCODE_skip;
}
@Override
public String toString() {
return "skip";
}
}
/**
* <p>
* A switch bytecode has the following layout:
* </p>
*
* <pre>
* +--------+-----------+-------+-----+-------+------------+-----+------------+
* | opcode | condition | block | ... | block | Constant[] | ... | Constant[] |
* +--------+-----------+-------+-----+-------+------------+-----+------------+
* </pre>
*
* <p>
* Here, the condition identifies the expression being switched on. There
* are zero or more case blocks, and the same number of constant arrays. An
* empty constant array indicates the default block.
* </p>
*
* @author David J. Pearce
*
*/
public static final class Switch extends AbstractBytecode implements Stmt {
private final Constant[][] constants;
public Switch(int operand, Case[] cases) {
super(operand, null, extractBlocks(cases));
this.constants = extractConstants(cases);
}
@Override
public int getOpcode() {
return OPCODE_switch;
}
public int operand() {
return getOperand(0);
}
public Case[] cases() {
Case[] cases = new Case[numberOfBlocks()];
for (int i = 0; i != cases.length; ++i) {
cases[i] = new Case(getBlock(i), constants[i]);
}
return cases;
}
@Override
public boolean equals(Object o) {
if (o instanceof Switch) {
Switch s = (Switch) o;
return Arrays.deepEquals(constants, s.constants) && super.equals(s);
}
return false;
}
@Override
public int hashCode() {
return Arrays.hashCode(constants) ^ super.hashCode();
}
@Override
public String toString() {
return "switch";
}
private static int[] extractBlocks(Case[] cases) {
int[] blocks = new int[cases.length];
for(int i=0;i!=cases.length;++i) {
blocks[i] = cases[i].block;
}
return blocks;
}
private static Constant[][] extractConstants(Case[] cases) {
Constant[][] blocks = new Constant[cases.length][];
for (int i = 0; i != cases.length; ++i) {
blocks[i] = cases[i].values;
}
return blocks;
}
}
public static final class Case {
private final Constant[] values;
private final int block;
public Case(int block, Constant... values) {
this.block = block;
this.values = values;
}
public Case(int block, List<Constant> values) {
this.block = block;
this.values = values.toArray(new Constant[values.size()]);
}
public boolean isDefault() {
return values.length == 0;
}
public int block() {
return block;
}
public Constant[] values() {
return values;
}
@Override
public boolean equals(Object o) {
if (o instanceof Case) {
Case c = (Case) o;
return block == c.block && Arrays.equals(values, c.values);
}
return false;
}
@Override
public int hashCode() {
return block ^ Arrays.hashCode(values);
}
}
/**
* <p>
* A variable declaration bytecode has one of the following two layouts:
* </p>
*
* <pre>
* +--------+------+
* | opcode | name |
* +--------+------+
* </pre>
*
* Or, with an initialiser operand:
*
* <pre>
* +--------+---------+------+
* | opcode | operand | name |
* +--------+---------+------+
* </pre>
*
* <p>
* Here, the condition identifies the expression being switched on. There
* are zero or more case blocks, and the same number of constant arrays. An
* empty constant array indicates the default block.
* </p>
*
* @author David J. Pearce
*
*/
public static final class VariableDeclaration extends AbstractBytecode implements Stmt {
/**
* Variable name
*/
private final String name;
public VariableDeclaration(String name) {
super();
this.name = name;
}
public VariableDeclaration(String name, int initialiser) {
super(initialiser);
this.name = name;
}
public String getName() {
return name;
}
@Override
public int getOpcode() {
if (numberOfOperands() == 0) {
return OPCODE_vardecl;
} else {
return OPCODE_vardeclinit;
}
}
@Override
public boolean equals(Object o) {
if(o instanceof VariableDeclaration) {
VariableDeclaration vd = (VariableDeclaration) o;
return name.equals(vd.name) && super.equals(o);
}
return false;
}
@Override
public int hashCode() {
return name.hashCode() ^ super.hashCode();
}
@Override
public String toString() {
if(numberOfOperands() == 0) {
return "decl " + name;
} else {
return "decl " + name + " = " + getOperand(0);
}
}
}
// ===============================================================
// Bytecode Block & Index
// ===============================================================
public static class Block extends AbstractBytecode implements Bytecode {
public Block(int... operands) {
super(operands);
}
@Override
public int getOpcode() {
return OPCODE_block;
}
@Override
public String toString() {
return "block " + Util.arrayToString(getOperands());
}
}
public static final class NamedBlock extends AbstractBytecode implements Bytecode.Stmt {
private final String name;
public NamedBlock(int block, String name) {
super(new int[0], new int[0][], new int[] { block });
this.name = name;
}
public String getName() {
return name;
}
@Override
public int getOpcode() {
return OPCODE_namedblock;
}
@Override
public boolean equals(Object o) {
if(o instanceof NamedBlock) {
NamedBlock n = (NamedBlock) o;
return name.equals(n.name) && super.equals(o);
}
return false;
}
@Override
public int hashCode() {
return super.hashCode() ^ name.hashCode();
}
@Override
public String toString() {
return "block(" + name +") " + Util.arrayToString(getBlocks());
}
}
/**
* Represents a bytecode location within a code forest. This is simply a
* pair of the block identifier and the position within that block.
*
* @author David J. Pearce
*
*/
public static final class Index {
private int block;
private int offset;
public Index(int block, int offset) {
this.block = block;
this.offset = offset;
}
public int block() {
return block;
}
public int offset() {
return offset;
}
@Override
public boolean equals(Object o) {
if (o instanceof Index) {
Index i = (Index) o;
return block == i.block && offset == i.offset;
}
return false;
}
@Override
public int hashCode() {
return block ^ offset;
}
public Index next() {
return new Index(block, offset + 1);
}
public Index next(int i) {
return new Index(block, offset + i);
}
@Override
public String toString() {
return block + ":" + offset;
}
}
// ===============================================================
// Bytecode "Statement Expressions"
// ===============================================================
/**
* A "statement expression" is a rather unusual beast. It is both a
* statement and an expression! There are very few bytecodes which can be
* classified in this way.
*
* @author David J. Pearce
*
*/
public interface StmtExpr extends Expr,Stmt {
}
/**
* <p>
* An indirect invocation bytecode has the following layout:
* </p>
*
* <pre>
* +--------+---------+-------------+------+
* | opcode | operand | parameter[] | type |
* +--------+---------+-------------+------+
* </pre>
*
* <p>
* Here, the operand returns the function pointer which this bytecode
* indirects upon. The parameter array identifies zero or more operands
* which are pass as arguments, whilst the type gives the signature of the
* target function/method. For example, consider the following:
* </p>
*
* <pre>
* type func_t is function(int)->int
*
* function fun(func_t f, int x) -> int:
* return f(x)
* </pre>
*
* Here, the function call <code>f(x)</code> is indirect as the called
* function is determined by the variable <code>f</code>.
*
* @author David J. Pearce
*
*/
public static final class IndirectInvoke extends AbstractBytecode implements StmtExpr {
private final Type.FunctionOrMethod type;
/**
* Construct an indirect invocation bytecode which assigns to an
* optional target register the result from indirectly invoking a
* function in a given operand with a given set of parameter operands.
*
* @param type.
* Function or method type.
* @param operand
* Operand holding function pointer through which indirect
* invocation is made.
* @param operands
* Operands holding parameters for the invoked function
*/
public IndirectInvoke(Type.FunctionOrMethod type, int operand, int[] operands) {
super(operand, new int[][]{operands});
this.type = type;
}
/**
* Return operand holding the indirect function/method reference.
*
* @return
*/
public int reference() {
return getOperand(0);
}
/**
* Return operand holding the ith parameter for the invoked function.
*
* @param i
* @return
*/
public int argument(int i) {
return getOperandGroup(0)[i];
}
/**
* Return operands holding parameters for the invoked function.
*
* @return
*/
public int[] arguments() {
return getOperandGroup(0);
}
@Override
public int getOpcode() {
return OPCODE_indirectinvoke;
}
public Type.FunctionOrMethod type() {
return type;
}
@Override
public boolean equals(Object o) {
if (o instanceof IndirectInvoke) {
IndirectInvoke i = (IndirectInvoke) o;
return type.equals(i.type) && super.equals(o);
}
return false;
}
@Override
public int hashCode() {
return super.hashCode() + type.hashCode();
}
@Override
public String toString() {
return "icall %" + reference() + " " + Util.arrayToString(arguments());
}
}
/**
* <p>
* An indirect invocation bytecode has the following layout:
* </p>
*
* <pre>
* +--------+-------------+------+------+
* | opcode | parameter[] | type | name |
* +--------+-------------+------+------+
* </pre>
*
* <p>
* Here, the parameter array identifies zero or more operands which are pass
* as arguments, whilst the type gives the signature of the target
* function/method and name identifies it's fully qualitified name.
*
* @author David J. Pearce
*
*/
public static final class Invoke extends AbstractBytecode implements StmtExpr {
private final NameID name;
private final Type.FunctionOrMethod type;
public Invoke(Type.FunctionOrMethod type, int[] operands, NameID name) {
super(operands);
this.name = name;
this.type = type;
}
@Override
public int getOpcode() {
return OPCODE_invoke;
}
public NameID name() {
return name;
}
public Type.FunctionOrMethod type() {
return type;
}
@Override
public boolean equals(Object o) {
if (o instanceof Invoke) {
Invoke i = (Invoke) o;
return name().equals(i.name) && super.equals(i);
}
return false;
}
@Override
public int hashCode() {
return name.hashCode() + super.hashCode();
}
@Override
public String toString() {
return "call " + name + Util.arrayToString(getOperands());
}
}
// ===============================================================
// Helpers
// ===============================================================
public static final class Util {
public static String arrayToString(int... operands) {
String r = "(";
for (int i = 0; i != operands.length; ++i) {
if (i != 0) {
r = r + ", ";
}
r = r + "%" + operands[i];
}
return r + ")";
}
}
// =========================================================================
// Opcodes
// =========================================================================
public static final int OPCODE_vardecl = 0;
public static final int OPCODE_fail = 1;
public static final int OPCODE_assert = 2;
public static final int OPCODE_assume = 3;
public static final int OPCODE_break = 4;
public static final int OPCODE_continue = 5;
public static final int OPCODE_vardeclinit = 6;
public static final int OPCODE_aliasdecl = 7;
// Unary Operators
public static final int UNARY_OPERATOR = 8;
public static final int OPCODE_return = UNARY_OPERATOR + 0;
public static final int OPCODE_ifis = UNARY_OPERATOR + 1;
public static final int OPCODE_switch = UNARY_OPERATOR + 2;
public static final int OPCODE_skip = UNARY_OPERATOR + 3;
public static final int OPCODE_debug = UNARY_OPERATOR + 4;
// Unary Assignables
public static final int UNARY_ASSIGNABLE = UNARY_OPERATOR + 5;
public static final int OPCODE_fieldload = UNARY_ASSIGNABLE + 7;
public static final int OPCODE_convert = UNARY_ASSIGNABLE + 8;
public static final int OPCODE_const = UNARY_ASSIGNABLE + 9;
// Binary Operators
public static final int BINARY_OPERATOR = UNARY_ASSIGNABLE + 10;
public static final int OPCODE_if = BINARY_OPERATOR + 0;
public static final int OPCODE_ifelse = BINARY_OPERATOR + 1;
// Binary Assignables
public static final int BINARY_ASSIGNABLE = BINARY_OPERATOR + 6;
public static final int OPCODE_neg = BINARY_ASSIGNABLE + 0;
public static final int OPCODE_add = BINARY_ASSIGNABLE + 1;
public static final int OPCODE_sub = BINARY_ASSIGNABLE + 2;
public static final int OPCODE_mul = BINARY_ASSIGNABLE + 3;
public static final int OPCODE_div = BINARY_ASSIGNABLE + 4;
public static final int OPCODE_rem = BINARY_ASSIGNABLE + 5;
public static final int OPCODE_eq = BINARY_ASSIGNABLE + 6;
public static final int OPCODE_ne = BINARY_ASSIGNABLE + 7;
public static final int OPCODE_lt = BINARY_ASSIGNABLE + 8;
public static final int OPCODE_le = BINARY_ASSIGNABLE + 9;
public static final int OPCODE_gt = BINARY_ASSIGNABLE + 10;
public static final int OPCODE_ge = BINARY_ASSIGNABLE + 11;
public static final int OPCODE_logicalnot = BINARY_ASSIGNABLE + 12;
public static final int OPCODE_logicaland = BINARY_ASSIGNABLE + 13;
public static final int OPCODE_logicalor = BINARY_ASSIGNABLE + 14;
public static final int OPCODE_bitwiseinvert = BINARY_ASSIGNABLE + 15;
public static final int OPCODE_bitwiseor = BINARY_ASSIGNABLE + 16;
public static final int OPCODE_bitwisexor = BINARY_ASSIGNABLE + 17;
public static final int OPCODE_bitwiseand = BINARY_ASSIGNABLE + 18;
public static final int OPCODE_shl = BINARY_ASSIGNABLE + 19;
public static final int OPCODE_shr = BINARY_ASSIGNABLE + 20;
public static final int OPCODE_arraylength = BINARY_ASSIGNABLE + 21;
public static final int OPCODE_arrayindex = BINARY_ASSIGNABLE + 22;
public static final int OPCODE_arraygen = BINARY_ASSIGNABLE + 23;
public static final int OPCODE_array = BINARY_ASSIGNABLE + 24;
public static final int OPCODE_record = BINARY_ASSIGNABLE + 25;
public static final int OPCODE_is = BINARY_ASSIGNABLE + 26;
public static final int OPCODE_dereference = BINARY_ASSIGNABLE + 27;
public static final int OPCODE_newobject = BINARY_ASSIGNABLE + 28;
public static final int OPCODE_varcopy = BINARY_ASSIGNABLE + 29;
public static final int OPCODE_varmove = BINARY_ASSIGNABLE + 30;
// Nary Assignables
public static final int NARY_ASSIGNABLE = BINARY_ASSIGNABLE + 31;
public static final int OPCODE_invoke = NARY_ASSIGNABLE + 1;
public static final int OPCODE_indirectinvoke = NARY_ASSIGNABLE + 2;
public static final int OPCODE_lambda = NARY_ASSIGNABLE + 3;
public static final int OPCODE_while = NARY_ASSIGNABLE + 4;
public static final int OPCODE_dowhile = NARY_ASSIGNABLE + 5;
public static final int OPCODE_some = NARY_ASSIGNABLE + 6;
public static final int OPCODE_all = NARY_ASSIGNABLE + 7;
public static final int OPCODE_assign = NARY_ASSIGNABLE + 8;
public static final int OPCODE_block = NARY_ASSIGNABLE + 9;
public static final int OPCODE_namedblock = NARY_ASSIGNABLE + 10;
// =========================================================================
// Bytecode Schemas
// =========================================================================
public enum Operands {
ZERO, ONE, TWO, MANY
}
public enum OperandGroups {
ZERO, ONE, TWO, MANY
}
public enum Blocks {
ZERO, ONE, TWO, MANY
}
public enum Extras {
STRING, // index into string pool
CONSTANT, // index into constant pool
TYPE, // index into type pool
NAME, // index into name pool
STRING_ARRAY, // determined on the fly
SWITCH_ARRAY, // determined on the fly
}
public static abstract class Schema {
private final Operands operands;
private final OperandGroups groups;
private final Blocks blocks;
private final Extras[] extras;
public Schema(Operands operands, Extras... extras) {
this.operands = operands;
this.groups = OperandGroups.ZERO;
this.blocks = Blocks.ZERO;
this.extras = extras;
}
public Schema(Operands operands, OperandGroups groups, Extras... extras) {
this.operands = operands;
this.groups = groups;
this.blocks = Blocks.ZERO;
this.extras = extras;
}
public Schema(Operands operands, OperandGroups groups, Blocks blocks, Extras... extras) {
this.operands = operands;
this.groups = groups;
this.blocks = blocks;
this.extras = extras;
}
public Extras[] extras() {
return extras;
}
public Operands getOperands() {
return operands;
}
public OperandGroups getOperandGroups() {
return groups;
}
public Blocks getBlocks() {
return blocks;
}
public abstract Bytecode construct(int opcode, int[] operands, int[][] groups, int[] blocks, Object[] extras);
@Override
public String toString() {
return "<" + operands + " operands, " + Arrays.toString(extras) + ">";
}
}
}