// 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.util.interpreter;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigInteger;
import java.util.*;
import wybs.lang.Build;
import wybs.lang.NameID;
import wybs.lang.SyntacticElement;
import wybs.util.ResolveError;
import wyfs.lang.Path;
import wyil.lang.*;
import wyil.lang.Bytecode.*;
import static wyil.lang.SyntaxTree.*;
import wyil.util.TypeSystem;
/**
* <p>
* A simple interpreter for WyIL bytecodes. The purpose of this interpreter is
* to provide a reference implementation for the semantics of WyIL bytecodes.
* </p>
* <p>
* The interpreter is not intended to be time or space efficient. It also assume
* the underlying WyIL bytecodes are well formed and does not attempt to check
* them. Thus, malformed bytecodes can reuslt in the interpreter executing in an
* unpredictable fashion.
* </p>
*
* @author David J. Pearce
*
*/
public class Interpreter {
/**
* The build project provides access to compiled WyIL files.
*/
private final Build.Project project;
/**
* Provides mechanism for operating on types. For example, expanding them
* and performing subtype tests, etc.
*/
private final TypeSystem typeSystem;
/**
* Implementations for the internal operators
*/
private final InternalFunction[] operators;
/**
* The debug stream provides an I/O stream through which debug bytecodes can
* write their messages.
*/
private final PrintStream debug;
public Interpreter(Build.Project project, PrintStream debug) {
this.project = project;
this.debug = debug;
this.typeSystem = new TypeSystem(project);
this.operators = StandardFunctions.standardFunctions;
}
private enum Status {
RETURN,
BREAK,
CONTINUE,
NEXT
}
public TypeSystem getTypeSystem() {
return typeSystem;
}
/**
* Execute a function or method identified by a name and type signature with
* the given arguments, producing a return value or null (if none). If the
* function or method cannot be found, or the number of arguments is
* incorrect then an exception is thrown.
*
* @param nid
* The fully qualified identifier of the function or method
* @param sig
* The exact type signature identifying the method.
* @param args
* The supplied arguments
* @return
*/
public Constant[] execute(NameID nid, Type.FunctionOrMethod sig, Constant... args) {
// First, find the enclosing WyilFile
try {
Path.Entry<WyilFile> entry = project.get(nid.module(), WyilFile.ContentType);
if (entry == null) {
throw new IllegalArgumentException("no WyIL file found: " + nid.module());
}
// Second, find the given function or method
WyilFile wyilFile = entry.read();
WyilFile.FunctionOrMethodOrProperty fmp = wyilFile.functionOrMethodOrProperty(nid.name(), sig);
if (fmp == null) {
throw new IllegalArgumentException("no function or method found: " + nid + ", " + sig);
} else if (sig.params().length != args.length) {
throw new IllegalArgumentException("incorrect number of arguments: " + nid + ", " + sig);
}
// Fourth, construct the stack frame for execution
SyntaxTree tree = fmp.getTree();
Constant[] frame = new Constant[tree.getLocations().size()];
System.arraycopy(args, 0, frame, 0, sig.params().length);
// Check the precondition
checkInvariants(frame,fmp.getPrecondition());
if(fmp instanceof WyilFile.FunctionOrMethod) {
WyilFile.FunctionOrMethod fm = (WyilFile.FunctionOrMethod) fmp;
// check function or method body exists
if (fm.getBody() == null) {
// FIXME: Add support for native functions or methods. That is,
// allow native functions to be implemented and called from the
// interpreter.
throw new IllegalArgumentException("no function or method body found: " + nid + ", " + sig);
}
// Execute the method or function body
executeBlock(fm.getBody(), frame);
// Extra the return values
Constant[] returns = extractReturns(frame,fmp.type());
//
// Check the postcondition holds
System.arraycopy(args,0,frame,0,args.length);
checkInvariants(frame, fm.getPostcondition());
return returns;
} else {
// Properties always return true (provided their preconditions hold)
return new Constant[]{Constant.True};
}
//
} catch (IOException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
/**
* Given an execution frame, extract the return values from a given function
* or method. The parameters of the function or method are located first in
* the frame, followed by the return values.
*
* @param frame
* @param type
* @return
*/
private Constant[] extractReturns(Constant[] frame, Type.FunctionOrMethod type) {
if(type instanceof Type.Property) {
return new Constant[]{Constant.Bool(true)};
} else {
int paramsSize = type.params().length;
int returnsSize = type.returns().length;
Constant[] returns = new Constant[returnsSize];
for (int i = 0, j = paramsSize; i != returnsSize; ++i, ++j) {
returns[i] = frame[j];
}
return returns;
}
}
/**
* Execute a given block of statements starting from the beginning. Control
* may terminate prematurely in a number of situations. For example, when a
* return or break statement is encountered.
*
* @param block
* --- Statement block to execute
* @param frame
* --- The current stack frame
*
* @return
*/
private Status executeBlock(Location<Block> block, Constant[] frame) {
for (int i = 0; i != block.numberOfOperands(); ++i) {
Location<Stmt> stmt = (Location<Stmt>) block.getOperand(i);
Status r = executeStatement(stmt, frame);
// Now, see whether we are continuing or not
if (r != Status.NEXT) {
return r;
}
}
return Status.NEXT;
}
/**
* Execute a statement at a given point in the function or method body
*
* @param stmt
* --- The statement to be executed
* @param frame
* --- The current stack frame
* @return
*/
private Status executeStatement(Location<?> stmt, Constant[] frame) {
switch (stmt.getOpcode()) {
case Bytecode.OPCODE_assert:
case Bytecode.OPCODE_assume:
return executeAssertOrAssume((Location<AssertOrAssume>) stmt, frame);
case Bytecode.OPCODE_assign:
return executeAssign((Location<Assign>) stmt, frame);
case Bytecode.OPCODE_break:
return executeBreak((Location<Break>) stmt, frame);
case Bytecode.OPCODE_continue:
return executeContinue((Location<Continue>) stmt, frame);
case Bytecode.OPCODE_debug:
return executeDebug((Location<Debug>) stmt, frame);
case Bytecode.OPCODE_dowhile:
return executeDoWhile((Location<DoWhile>) stmt, frame);
case Bytecode.OPCODE_fail:
return executeFail((Location<Fail>) stmt, frame);
case Bytecode.OPCODE_if:
case Bytecode.OPCODE_ifelse:
return executeIf((Location<If>) stmt, frame);
case Bytecode.OPCODE_indirectinvoke:
executeIndirectInvoke((Location<IndirectInvoke>) stmt, frame);
return Status.NEXT;
case Bytecode.OPCODE_invoke:
executeInvoke((Location<Invoke>) stmt, frame);
return Status.NEXT;
case Bytecode.OPCODE_namedblock:
return executeNamedBlock((Location<NamedBlock>) stmt, frame);
case Bytecode.OPCODE_while:
return executeWhile((Location<While>) stmt, frame);
case Bytecode.OPCODE_return:
return executeReturn((Location<Return>) stmt, frame);
case Bytecode.OPCODE_skip:
return executeSkip((Location<Skip>) stmt, frame);
case Bytecode.OPCODE_switch:
return executeSwitch((Location<Switch>) stmt, frame);
case Bytecode.OPCODE_vardeclinit:
case Bytecode.OPCODE_vardecl:
return executeVariableDeclaration((Location<VariableDeclaration>) stmt, frame);
}
deadCode(stmt);
return null; // deadcode
}
private Status executeAssign(Location<Assign> stmt, Constant[] frame) {
// FIXME: handle multi-assignments properly
SyntaxTree.Location<?>[] lhs = stmt.getOperandGroup(LEFTHANDSIDE);
Constant[] rhs = executeExpressions(stmt.getOperandGroup(RIGHTHANDSIDE), frame);
for (int i = 0; i != lhs.length; ++i) {
// TODO: this is not a very efficient way of implement assignment.
// To improve performance, it would help if values were mutable,
// rather than immutable constants.
LVal lval = constructLVal(lhs[i], frame);
lval.write(frame, rhs[i]);
}
return Status.NEXT;
}
/**
* Execute an assert or assume statement. In both cases, if the condition
* evaluates to false an exception is thrown.
*
* @param stmt
* --- Assert or Assume statement.
* @param frame
* --- The current stack frame
* @return
*/
private Status executeAssertOrAssume(Location<AssertOrAssume> stmt, Constant[] frame) {
//
checkInvariants(frame,(Location<Expr>) stmt.getOperand(CONDITION));
return Status.NEXT;
}
/**
* Execute a break statement. This transfers to control out of the nearest
* enclosing loop.
*
* @param stmt
* --- Break statement.
* @param frame
* --- The current stack frame
* @return
*/
private Status executeBreak(Location<Break> stmt, Constant[] frame) {
// TODO: the break bytecode supports a non-nearest exit and eventually
// this should be supported.
return Status.BREAK;
}
/**
* Execute a continue statement. This transfers to control back to the start
* the nearest enclosing loop.
*
* @param stmt
* --- Break statement.
* @param frame
* --- The current stack frame
* @return
*/
private Status executeContinue(Location<Continue> stmt, Constant[] frame) {
// TODO: the continue bytecode supports a non-nearest exit and eventually
// this should be supported.
return Status.CONTINUE;
}
/**
* Execute a Debug statement at a given point in the function or method
* body. This will write the provided string out to the debug stream.
*
* @param stmt
* --- Debug statement to executed
* @param frame
* --- The current stack frame
* @return
*/
private Status executeDebug(Location<Debug> stmt, Constant[] frame) {
//
Constant.Array arr = executeExpression(ARRAY_T, stmt.getOperand(0), frame);
for (Constant item : arr.values()) {
BigInteger b = ((Constant.Integer) item).value();
char c = (char) b.intValue();
debug.print(c);
}
//
return Status.NEXT;
}
/**
* Execute a DoWhile statement at a given point in the function or method
* body. This will loop over the body zero or more times.
*
* @param stmt
* --- Loop statement to executed
* @param frame
* --- The current stack frame
* @return
*/
private Status executeDoWhile(Location<DoWhile> stmt, Constant[] frame) {
Status r = Status.NEXT;
while (r == Status.NEXT || r == Status.CONTINUE) {
r = executeBlock(stmt.getBlock(0), frame);
if (r == Status.NEXT) {
Constant.Bool operand = executeExpression(BOOL_T, stmt.getOperand(CONDITION), frame);
if (!operand.value()) {
return Status.NEXT;
}
}
}
// If we get here, then we have exited the loop body without falling
// through to the next bytecode.
if (r == Status.BREAK) {
return Status.NEXT;
} else {
return r;
}
}
/**
* Execute a fail statement at a given point in the function or method body.
* This will generate a runtime fault.
*
* @param stmt
* --- The fail statement to execute
* @param frame
* --- The current stack frame
* @return
*/
private Status executeFail(Location<Fail> stmt, Constant[] frame) {
throw new AssertionError("Runtime fault occurred");
}
/**
* Execute an if statement at a given point in the function or method body.
* This will proceed done either the true or false branch.
*
* @param stmt
* --- The if statement to execute
* @param frame
* --- The current stack frame
* @return
*/
private Status executeIf(Location<If> stmt, Constant[] frame) {
Bytecode.If bytecode = stmt.getBytecode();
Constant.Bool operand = executeExpression(BOOL_T, stmt.getOperand(CONDITION), frame);
if (operand.value()) {
// branch taken, so execute true branch
return executeBlock(stmt.getBlock(TRUEBRANCH), frame);
} else if (bytecode.hasFalseBranch()) {
// branch not taken, so execute false branch
return executeBlock(stmt.getBlock(FALSEBRANCH), frame);
} else {
return Status.NEXT;
}
}
/**
* Execute a named block which is simply a block of statements.
*
* @param stmt
* --- Block statement to executed
* @param frame
* --- The current stack frame
* @return
*/
private Status executeNamedBlock(Location<NamedBlock> stmt, Constant[] frame) {
Location<Block> block = stmt.getBlock(0);
return executeBlock(block,frame);
}
/**
* Execute a While statement at a given point in the function or method
* body. This will loop over the body zero or more times.
*
* @param stmt
* --- Loop statement to executed
* @param frame
* --- The current stack frame
* @return
*/
private Status executeWhile(Location<While> stmt, Constant[] frame) {
Status r;
do {
Constant.Bool operand = executeExpression(BOOL_T, stmt.getOperand(CONDITION), frame);
if (!operand.value()) {
return Status.NEXT;
}
// Keep executing the loop body until we exit it somehow.
r = executeBlock(stmt.getBlock(0), frame);
} while (r == Status.NEXT || r == Status.CONTINUE);
// If we get here, then we have exited the loop body without falling
// through to the next bytecode.
if (r == Status.BREAK) {
return Status.NEXT;
} else {
return r;
}
}
/**
* Execute a Return statement at a given point in the function or method
* body
*
* @param stmt
* --- The return statement to execute
* @param frame
* --- The current stack frame
* @return
*/
private Status executeReturn(Location<Return> stmt, Constant[] frame) {
// We know that a return statement can only appear in either a function
// or method declaration. It cannot appear, for example, in a type
// declaration. Therefore, the enclosing declaration is a function or
// method.
SyntaxTree tree = stmt.getEnclosingTree();
WyilFile.FunctionOrMethod fm = (WyilFile.FunctionOrMethod) tree.getEnclosingDeclaration();
Type.FunctionOrMethod type = fm.type();
int paramsSize = type.params().length;
Constant[] values = executeExpressions(stmt.getOperands(), frame);
for (int i = 0, j = paramsSize; i != values.length; ++i, ++j) {
frame[j] = values[i];
}
return Status.RETURN;
}
/**
* Execute a skip statement at a given point in the function or method
* body
*
* @param stmt
* --- The skip statement to execute
* @param frame
* --- The current stack frame
* @return
*/
private Status executeSkip(Location<Skip> stmt, Constant[] frame) {
// skip !
return Status.NEXT;
}
/**
* Execute a Switch statement at a given point in the function or method
* body
*
* @param stmt
* --- The swithc statement to execute
* @param frame
* --- The current stack frame
* @return
*/
private Status executeSwitch(Location<Switch> stmt, Constant[] frame) {
Bytecode.Switch bytecode = stmt.getBytecode();
Bytecode.Case[] cases = bytecode.cases();
//
Constant value = executeExpression(ANY_T, stmt.getOperand(CONDITION), frame);
for (int i = 0; i != cases.length; ++i) {
Bytecode.Case c = cases[i];
Location<Block> body = stmt.getBlock(i);
if (c.isDefault()) {
return executeBlock(body, frame);
} else {
for (Constant v : c.values()) {
if (v.equals(value)) {
return executeBlock(body, frame);
}
}
}
}
return Status.NEXT;
}
/**
* Execute a variable declaration statement at a given point in the function or method
* body
*
* @param stmt
* --- The statement to execute
* @param frame
* --- The current stack frame
* @return
*/
private Status executeVariableDeclaration(Location<VariableDeclaration> stmt, Constant[] frame) {
// We only need to do something if this has an initialiser
if(stmt.numberOfOperands() > 0) {
Constant value = executeExpression(ANY_T, stmt.getOperand(0), frame);
frame[stmt.getIndex()] = value;
}
return Status.NEXT;
}
// =============================================================
// Single expressions
// =============================================================
/**
* Execute a single expression which is expected to return a single result
* of an expected type. If a result of an incorrect type is returned, then
* an exception is raised.
*
* @param expected
* The expected type of the result
* @param expr
* The expression to be executed
* @param frame
* The frame in which the expression is executing
* @return
*/
private <T extends Constant> T executeExpression(Class<T> expected, Location<?> expr, Constant[] frame) {
try {
Constant val;
Bytecode.Expr bytecode = (Bytecode.Expr) expr.getBytecode();
switch (bytecode.getOpcode()) {
case Bytecode.OPCODE_const:
val = executeConst((Location<Const>) expr, frame);
break;
case Bytecode.OPCODE_convert:
val = executeConvert((Location<Bytecode.Convert>) expr, frame);
break;
case Bytecode.OPCODE_fieldload:
val = executeFieldLoad((Location<FieldLoad>) expr, frame);
break;
case Bytecode.OPCODE_indirectinvoke:
val = executeIndirectInvoke((Location<IndirectInvoke>) expr, frame)[0];
break;
case Bytecode.OPCODE_invoke:
val = executeInvoke((Location<Invoke>) expr, frame)[0];
break;
case Bytecode.OPCODE_lambda:
val = executeLambda((Location<Lambda>) expr, frame);
break;
case Bytecode.OPCODE_some:
case Bytecode.OPCODE_all:
val = executeQuantifier((Location<Quantifier>) expr, frame);
break;
case Bytecode.OPCODE_varmove:
case Bytecode.OPCODE_varcopy:
val = executeVariableAccess((Location<VariableAccess>) expr, frame);
break;
default:
val = executeOperator((Location<Operator>) expr, frame);
}
return checkType(val, expr, expected);
} catch (ResolveError err) {
error(err.getMessage(), expr);
return null;
}
}
/**
* Execute a Constant expression at a given point in the function or
* method body
*
* @param expr
* --- The expression to execute
* @param frame
* --- The current stack frame
* @return
*/
private Constant executeConst(Location<Const> expr, Constant[] frame) {
return expr.getBytecode().constant();
}
/**
* Execute a type conversion at a given point in the function or method body
*
* @param expr
* --- The expression to execute
* @param frame
* --- The current stack frame
* @return
*/
private Constant executeConvert(Location<Convert> expr, Constant[] frame) {
try {
Constant operand = executeExpression(ANY_T, expr.getOperand(0), frame);
return convert(operand, expr.getType(), expr);
} catch (ResolveError e) {
error(e.getMessage(), expr);
return null;
}
}
/**
* Execute a binary operator at a given point in the function or method
* body. This will check operands match their expected types.
*
* @param expr
* --- The expression to execute
* @param frame
* --- The current stack frame
* @return
* @throws ResolveError
* If a named type within this expression cannot be resolved
* within the enclosing project.
*/
private Constant executeOperator(Location<Operator> expr, Constant[] frame) throws ResolveError {
Bytecode bytecode = expr.getBytecode();
switch (bytecode.getOpcode()) {
case Bytecode.OPCODE_logicaland: {
// This is a short-circuiting operator
Constant.Bool lhs = executeExpression(BOOL_T, expr.getOperand(0), frame);
if (!lhs.value()) {
// Short-circuit
return Constant.False;
}
return executeExpression(BOOL_T, expr.getOperand(1), frame);
}
case Bytecode.OPCODE_logicalor: {
// This is a short-circuiting operator
Constant.Bool lhs = executeExpression(BOOL_T, expr.getOperand(0), frame);
if (lhs.value()) {
// Short-circuit
return Constant.True;
}
return executeExpression(BOOL_T, expr.getOperand(1), frame);
}
default: {
// This is the default case where can treat the operator as an
// external function and just call it with the evaluated operands.
SyntaxTree.Location<?>[] operands = expr.getOperands();
Constant[] values = new Constant[operands.length];
// Read all operands
for (int i = 0; i != operands.length; ++i) {
values[i] = executeExpression(ANY_T, operands[i], frame);
}
// Compute result
return operators[bytecode.getOpcode()].apply(values, this, expr);
}
}
}
private Constant executeFieldLoad(Location<FieldLoad> loc, Constant[] frame) {
Bytecode.FieldLoad bytecode = loc.getBytecode();
Constant.Record rec = executeExpression(RECORD_T, loc.getOperand(0), frame);
return rec.values().get(bytecode.fieldName());
}
private Constant executeQuantifier(Location<Quantifier> expr, Constant[] frame) {
boolean r = executeQuantifier(0, expr, frame);
// r ==> continued all the way through
// ! ==> terminated early
if (expr.getOpcode() == Bytecode.OPCODE_some) {
return r ? Constant.False : Constant.True;
} else {
return r ? Constant.True : Constant.False;
}
}
/**
* Execute one range of the quantifier, or the body if no ranges remain.
*
* @param index
* @param expr
* @param frame
* @param context
* @return
*/
private boolean executeQuantifier(int index, Location<Quantifier> expr, Constant[] frame) {
Bytecode.Quantifier bytecode = expr.getBytecode();
if (index == expr.numberOfOperandGroups()) {
// This is the base case where we evaluate the condition itself.
Constant.Bool r = executeExpression(BOOL_T, expr.getOperand(CONDITION), frame);
int opcode = bytecode.getOpcode();
if (r.value() && opcode == Bytecode.OPCODE_some) {
return false;
} else if (!r.value() && opcode == Bytecode.OPCODE_all) {
return false;
}
// This means that, for the given quantifier kind, we have to
// continue as is.
return true;
} else {
SyntaxTree.Location<?>[] range = expr.getOperandGroup(index);
int var = range[VARIABLE].getIndex();
Constant.Integer start = executeExpression(INT_T, range[START], frame);
Constant.Integer end = executeExpression(INT_T, range[END], frame);
long s = start.value().longValue();
long e = end.value().longValue();
for (long i = s; i < e; ++i) {
frame[var] = new Constant.Integer(BigInteger.valueOf(i));
boolean r = executeQuantifier(index + 1, expr, frame);
if (!r) {
// early termination
return r;
}
}
return true;
}
}
private Constant executeLambda(Location<Lambda> expr, Constant[] frame) {
// Clone the frame at this point, in order that changes seen after this
// bytecode is executed are not propagated into the lambda itself.
frame = Arrays.copyOf(frame, frame.length);
return new ConstantLambda(expr, frame);
}
/**
* Execute a variable access expression at a given point in the function or
* method body. This simply loads the value of the given variable from the
* frame.
*
* @param expr
* --- The expression to execute
* @param frame
* --- The current stack frame
* @return
*/
private Constant executeVariableAccess(Location<VariableAccess> expr, Constant[] frame) {
Location<VariableDeclaration> decl = getVariableDeclaration(expr);
return frame[decl.getIndex()];
}
// =============================================================
// Multiple expressions
// =============================================================
/**
* Execute one or more expressions. This is slightly more complex than for
* the single expression case because of the potential to encounter
* "positional operands". That is, severals which arise from executing the
* same expression.
*
* @param operands
* @param frame
* @return
*/
private Constant[] executeExpressions(Location<?>[] operands, Constant[] frame) {
Constant[][] results = new Constant[operands.length][];
int count = 0;
for(int i=0;i!=operands.length;++i) {
results[i] = executeMultiReturnExpression(operands[i],frame);
count += results[i].length;
}
Constant[] rs = new Constant[count];
int j = 0;
for(int i=0;i!=operands.length;++i) {
Constant[] r = results[i];
System.arraycopy(r, 0, rs, j, r.length);
j += r.length;
}
return rs;
}
/**
* Execute an expression which has the potential to return more than one
* result. Thus the return type must accommodate this by allowing zero or
* more returned values.
*
* @param expr
* @param frame
* @return
*/
private Constant[] executeMultiReturnExpression(Location<?> expr, Constant[] frame) {
Bytecode.Expr bytecode = (Expr) expr.getBytecode();
switch (bytecode.getOpcode()) {
case Bytecode.OPCODE_indirectinvoke:
return executeIndirectInvoke((Location<IndirectInvoke>) expr, frame);
case Bytecode.OPCODE_invoke:
return executeInvoke((Location<Invoke>) expr, frame);
case Bytecode.OPCODE_const:
case Bytecode.OPCODE_convert:
case Bytecode.OPCODE_fieldload:
case Bytecode.OPCODE_lambda:
case Bytecode.OPCODE_some:
case Bytecode.OPCODE_all:
default:
Constant val = executeExpression(ANY_T, expr, frame);
return new Constant[] { val };
}
}
/**
* Execute an IndirectInvoke bytecode instruction at a given point in the
* function or method body. This first checks the operand is a function
* reference, and then generates a recursive call to execute the given
* function. If the function does not exist, or is provided with the wrong
* number of arguments, then a runtime fault will occur.
*
* @param expr
* --- The expression to execute
* @param frame
* --- The current stack frame
* @param context
* --- Context in which bytecodes are executed
* @return
*/
private Constant[] executeIndirectInvoke(Location<IndirectInvoke> expr, Constant[] frame) {
// FIXME: This is implementation is *ugly* --- can we do better than
// this? One approach is to register an anonymous function so that we
// can reuse executeAllWithin in both bases. This is hard to setup
// though.
SyntaxTree.Location<?> src = expr.getOperand(0);
Constant operand = executeExpression(ANY_T, src,frame);
// Check that we have a function reference
if(operand instanceof Constant.FunctionOrMethod) {
Constant.FunctionOrMethod cl = checkType(operand, src, Constant.FunctionOrMethod.class);
Constant[] arguments = executeExpressions(expr.getOperandGroup(ARGUMENTS),frame);
return execute(cl.name(),cl.type(),arguments);
} else {
ConstantLambda cl = checkType(operand, src, ConstantLambda.class);
// Yes we do; now construct the arguments. This requires merging the
// constant arguments provided in the lambda itself along with those
// operands provided for the "holes".
Constant[] lambdaFrame = Arrays.copyOf(cl.frame, cl.frame.length);
int[] parameters = cl.lambda.getBytecode().getOperandGroup(PARAMETERS);
Constant[] arguments = executeExpressions(expr.getOperandGroup(ARGUMENTS),frame);
for(int i=0;i!=parameters.length;++i) {
lambdaFrame[parameters[i]] = arguments[i];
}
// Make the actual call. This may return multiple values since it is
// a function/method invocation.
return executeMultiReturnExpression(cl.lambda.getOperand(BODY), lambdaFrame);
}
}
/**
* Execute an Invoke bytecode instruction at a given point in the function
* or method body. This generates a recursive call to execute the given
* function. If the function does not exist, or is provided with the wrong
* number of arguments, then a runtime fault will occur.
*
* @param expr
* --- The expression to execute
* @param frame
* --- The current stack frame
* @return
*/
private Constant[] executeInvoke(Location<Invoke> expr, Constant[] frame) {
Bytecode.Invoke bytecode = expr.getBytecode();
SyntaxTree.Location<?>[] operands = expr.getOperands();
Constant[] arguments = executeExpressions(operands,frame);
return execute(bytecode.name(), bytecode.type(), arguments);
}
// =============================================================
// Constants
// =============================================================
/**
* Convert a given value of one into a value of a different type. For
* example, converting a int value into a real value.
*
* @param from
* @param value
* @param to
* @param context
* --- Context in which bytecodes are executed
* @return
* @throws ResolveError
* If a named type within this constant cannot be resolved
* within the enclosing project.
*/
private Constant convert(Constant value, Type to, SyntacticElement context) throws ResolveError {
Type type = value.type();
// Must expand here to ensure we get rid of any nominal type
// information.
to = typeSystem.expandOneLevel(to);
//
if (typeSystem.isSubtype(to, type)) {
// In this case, we don't need to do anything because the value is
// already of the correct type.
return value;
} else if (type instanceof Type.Reference && to instanceof Type.Reference) {
if (typeSystem.isSubtype(((Type.Reference) to).element(), ((Type.Reference) type).element())) {
// OK, it's just the lifetime that differs.
return value;
}
} else if (to instanceof Type.Record) {
return convert(value, (Type.Record) to, context);
} else if (to instanceof Type.Array) {
return convert(value, (Type.Array) to, context);
} else if (to instanceof Type.Union) {
return convert(value, (Type.Union) to, context);
} else if (to instanceof Type.FunctionOrMethod) {
return convert(value, (Type.FunctionOrMethod) to, context);
}
deadCode(context);
return null;
}
/**
* Convert a value into a record type. The value must be of record type for
* this to make sense and must either have the same fields or, in the case
* of an open record, have a superset of fields.
*
* @param value
* @param to
* @param context
* --- Context in which bytecodes are executed
* @return
* @throws ResolveError
* If a named type within this constant cannot be resolved
* within the enclosing project.
*/
private Constant convert(Constant value, Type.Record to, SyntacticElement context) throws ResolveError {
checkType(value, context, Constant.Record.class);
Constant.Record rv = (Constant.Record) value;
HashSet<String> rv_fields = new HashSet<>(rv.values().keySet());
String[] to_fields = to.getFieldNames();
// Check fields in value are subset of those in target type
if (!rv_fields.containsAll(Arrays.asList(to_fields))) {
error("cannot convert between records with differing fields", context);
return null; // deadcode
} else {
HashMap<String, Constant> nValues = new HashMap<>();
for (int i = 0; i != to_fields.length; ++i) {
String field = to_fields[i];
Type fieldType = to.getField(field);
Constant nValue = convert(rv.values().get(field), fieldType, context);
nValues.put(field, nValue);
}
return new Constant.Record(nValues);
}
}
/**
* Convert a value into a list type. The value must be of list type for this
* to make sense.
*
* @param value
* @param to
* @param context
* --- Context in which bytecodes are executed
* @return
* @throws ResolveError
* If a named type within this constant cannot be resolved
* within the enclosing project.
*/
private Constant convert(Constant value, Type.Array to, SyntacticElement context) throws ResolveError {
checkType(value, context, Constant.Array.class);
Constant.Array lv = (Constant.Array) value;
ArrayList<Constant> values = new ArrayList<>(lv.values());
for (int i = 0; i != values.size(); ++i) {
values.set(i, convert(values.get(i), to.element(), context));
}
return new Constant.Array(values);
}
/**
* Convert a value into a union type. In this case, we must find an
* appropriate bound for the type in question. If no such type can be found,
* then this is ambiguous or otherwise invalid.
*
* @param value
* @param to
* @param context
* --- Context in which bytecodes are executed
* @return
* @throws ResolveError
* If a named type within this constant cannot be resolved
* within the enclosing project.
*/
private Constant convert(Constant value, Type.Union to, SyntacticElement context) throws ResolveError {
Type type = value.type();
for (Type bound : to.bounds()) {
if (typeSystem.isExplicitCoerciveSubtype(bound, type)) {
return convert(value, bound, context);
}
}
deadCode(context);
return null;
}
/**
* Convert a value into a function type. In this case, we actually do
* nothing for now.
*
* @param value
* @param to
* @param context
* --- Context in which bytecodes are executed
* @return
*/
private Constant convert(Constant value, Type.FunctionOrMethod to, SyntacticElement context) {
return value;
}
/**
* This method constructs a "mutable" representation of the lval. This is a
* bit strange, but is necessary because values in the frame are currently
* immutable.
*
* @param operand
* @param frame
* @param context
* @return
*/
private LVal constructLVal(SyntaxTree.Location<?> expr, Constant[] frame) {
switch (expr.getOpcode()) {
case Bytecode.OPCODE_arrayindex: {
LVal src = constructLVal(expr.getOperand(0), frame);
Constant.Integer index = executeExpression(INT_T, expr.getOperand(1), frame);
int i = index.value().intValue();
return new ArrayLVal(src, i);
}
case Bytecode.OPCODE_dereference: {
LVal src = constructLVal(expr.getOperand(0), frame);
return new DereferenceLVal(src);
}
case Bytecode.OPCODE_fieldload: {
Bytecode.FieldLoad fl = (Bytecode.FieldLoad) expr.getBytecode();
LVal src = constructLVal(expr.getOperand(0), frame);
return new RecordLVal(src, fl.fieldName());
}
case Bytecode.OPCODE_varmove:
case Bytecode.OPCODE_varcopy: {
Location<VariableDeclaration> decl = getVariableDeclaration(expr);
return new VariableLVal(decl.getIndex());
}
}
deadCode(expr);
return null; // deadcode
}
private abstract class LVal {
abstract public Constant read(Constant[] frame);
abstract public void write(Constant[] frame,Constant rhs);
}
private class VariableLVal extends LVal {
private final int index;
public VariableLVal(int index) {
this.index = index;
}
@Override
public Constant read(Constant[] frame) {
return frame[index];
}
@Override
public void write(Constant[] frame, Constant rhs) {
frame[index] = rhs;
}
}
private class ArrayLVal extends LVal {
private final LVal src;
private final int index;
public ArrayLVal(LVal src, int index) {
this.src = src;
this.index = index;
}
@Override
public Constant read(Constant[] frame) {
Constant.Array src = checkType(this.src.read(frame),null,Constant.Array.class);
return src.values().get(index);
}
@Override
public void write(Constant[] frame,Constant rhs) {
Constant.Array arr = checkType(this.src.read(frame),null,Constant.Array.class);
ArrayList<Constant> values = new ArrayList<>(arr.values());
values.set(index, rhs);
src.write(frame,new Constant.Array(values));
}
}
private class RecordLVal extends LVal {
private final LVal src;
private final String field;
public RecordLVal(LVal src, String field) {
this.src = src;
this.field = field;
}
@Override
public Constant read(Constant[] frame) {
Constant.Record src = checkType(this.src.read(frame),null,Constant.Record.class);
return src.values().get(field);
}
@Override
public void write(Constant[] frame, Constant rhs) {
Constant.Record rec = checkType(this.src.read(frame),null,Constant.Record.class);
HashMap<String, Constant> values = new HashMap<>(rec.values());
values.put(field, rhs);
src.write(frame,new Constant.Record(values));
}
}
private class DereferenceLVal extends LVal {
private final LVal src;
public DereferenceLVal(LVal src) {
this.src = src;
}
@Override
public Constant read(Constant[] frame) {
ConstantObject objecy = checkType(src.read(frame),null,ConstantObject.class);
return objecy.read();
}
@Override
public void write(Constant[] frame, Constant rhs) {
ConstantObject object = checkType(src.read(frame),null,ConstantObject.class);
object.write(rhs);
}
}
/**
* Determine whether a given value is a member of a given type. In the case
* of a nominal type, then we must also check that any invariant(s) for that
* type hold true as well.
*
* @param value
* @param type
* @param context
* --- Context in which bytecodes are executed
* @return
* @throws ResolveError
* If a named type within the given type cannot be resolved
* within the enclosing project.
*/
public boolean isMemberOfType(Constant value, Type type, SyntacticElement context) throws ResolveError {
if (type == Type.T_ANY) {
return true;
} else if (type == Type.T_VOID) {
return false;
} else if (type == Type.T_NULL) {
return value instanceof Constant.Null;
} else if (type == Type.T_BOOL) {
return value instanceof Constant.Bool;
} else if (type == Type.T_BYTE) {
return value instanceof Constant.Byte;
} else if (type == Type.T_INT) {
return value instanceof Constant.Integer;
} else if (type instanceof Type.Reference) {
if (value instanceof ConstantObject) {
ConstantObject obj = (ConstantObject) value;
Type.Reference rt = (Type.Reference) type;
return isMemberOfType(obj.value, rt.element(), context);
}
return false;
} else if (type instanceof Type.Array) {
if (value instanceof Constant.Array) {
Constant.Array t = (Constant.Array) value;
Type element = ((Type.Array) type).element();
boolean r = true;
for (Constant val : t.values()) {
r &= isMemberOfType(val, element, context);
}
return r;
}
return false;
} else if (type instanceof Type.Record) {
if (value instanceof Constant.Record) {
Type.Record rt = (Type.Record) type;
Constant.Record t = (Constant.Record) value;
Set<String> fields = t.values().keySet();
List<String> rt_fields = Arrays.asList(rt.getFieldNames());
if (!fields.containsAll(rt_fields) || (!rt_fields.containsAll(fields) && !rt.isOpen())) {
// In this case, the set of fields does not match properly
return false;
}
boolean r = true;
for (String field : fields) {
r &= isMemberOfType(t.values().get(field), rt.getField(field), context);
}
return r;
}
return false;
} else if (type instanceof Type.Union) {
Type.Union t = (Type.Union) type;
for (Type element : t.bounds()) {
if (isMemberOfType(value, element, context)) {
return true;
}
}
return false;
} else if (type instanceof Type.Intersection) {
Type.Intersection t = (Type.Intersection) type;
for (Type element : t.bounds()) {
if (!isMemberOfType(value, element, context)) {
return false;
}
}
return true;
} else if (type instanceof Type.Negation) {
Type.Negation t = (Type.Negation) type;
return !isMemberOfType(value, t.element(), context);
} else if (type instanceof Type.FunctionOrMethod) {
if (value instanceof Constant.FunctionOrMethod) {
Constant.FunctionOrMethod l = (Constant.FunctionOrMethod) value;
if (typeSystem.isSubtype(type, l.type())) {
return true;
}
}
return false;
} else if (type instanceof Type.Nominal) {
Type.Nominal nt = (Type.Nominal) type;
NameID nid = nt.name();
try {
// First, attempt to locate the enclosing module for this
// nominal type.
Path.Entry<WyilFile> entry = project.get(nid.module(), WyilFile.ContentType);
if (entry == null) {
throw new IllegalArgumentException("no WyIL file found: " + nid.module());
}
// Read in the module. This may result in it being read from
// disk, or from a cache in memory, or even from somewhere else.
WyilFile wyilFile = entry.read();
WyilFile.Type td = wyilFile.type(nid.name());
if (td == null) {
error("undefined nominal type encountered: " + nid, context);
} else if (!isMemberOfType(value, td.type(), context)) {
return false;
}
// Check every invariant associated with this type evaluates to
// true.
List<Location<Expr>> invariants = td.getInvariant();
if (invariants.size() > 0) {
SyntaxTree tree = td.getTree();
Constant[] frame = new Constant[tree.getLocations().size()];
frame[0] = value;
checkInvariants(frame, invariants);
}
// Done
return true;
} catch (IOException e) {
error(e.getMessage(), context);
} catch (Error e) {
// This signals that a runtime fault occurred in the body of the
// type invariant.
return false;
}
}
deadCode(context);
return false; // deadcode
}
/**
* Evaluate zero or more conditional expressions, and check whether any is
* false. If so, raise an exception indicating a runtime fault.
*
* @param frame
* @param context
* @param invariants
*/
public void checkInvariants(Constant[] frame, List<Location<Expr>> invariants) {
for (int i = 0; i != invariants.size(); ++i) {
Constant.Bool b = executeExpression(BOOL_T, invariants.get(i), frame);
if (!b.value()) {
// FIXME: need to do more here
throw new AssertionError();
}
}
}
/**
* Evaluate zero or more conditional expressions, and check whether any is
* false. If so, raise an exception indicating a runtime fault.
*
* @param frame
* @param context
* @param invariants
*/
public void checkInvariants(Constant[] frame, Location<Expr>... invariants) {
for (int i = 0; i != invariants.length; ++i) {
Constant.Bool b = executeExpression(BOOL_T, invariants[i], frame);
if (!b.value()) {
// FIXME: need to do more here
throw new AssertionError();
}
}
}
/**
* Check that a given operand value matches an expected type.
*
* @param operand
* --- bytecode operand to be checked
* @param context
* --- Context in which bytecodes are executed
* @param types
* --- Types to be checked against
*/
@SafeVarargs
public static <T extends Constant> T checkType(Constant operand, SyntacticElement context, Class<T>... types) {
// Got through each type in turn checking for a match
for (int i = 0; i != types.length; ++i) {
if (types[i].isInstance(operand)) {
// Matched!
return (T) operand;
}
}
// No match, therefore through an error
if(operand == null) {
error("null operand", context);
} else {
error("operand returned " + operand.getClass().getName() + ", expecting one of " + Arrays.toString(types), context);
}
return null;
}
/**
* This method is provided as a generic mechanism for reporting runtime
* errors within the interpreter.
*
* @param msg
* --- Message to be printed when error arises.
* @param context
* --- Context in which bytecodes are executed
*/
public static Object error(String msg, SyntacticElement context) {
// FIXME: do more here
throw new RuntimeException(msg);
}
/**
* This method is provided to properly handled positions which should be
* dead code.
*
* @param context
* --- Context in which bytecodes are executed
*/
private Object deadCode(SyntacticElement element) {
// FIXME: do more here
throw new RuntimeException("internal failure --- dead code reached");
}
public Location<VariableDeclaration> getVariableDeclaration(Location<?> decl) {
switch (decl.getOpcode()) {
case Bytecode.OPCODE_aliasdecl:
case Bytecode.OPCODE_varmove:
case Bytecode.OPCODE_varcopy:
return getVariableDeclaration(decl.getOperand(0));
case Bytecode.OPCODE_vardecl:
case Bytecode.OPCODE_vardeclinit:
return (Location<VariableDeclaration>) decl;
default:
throw new RuntimeException("internal failure --- dead code reached");
}
}
private static final Class<Constant> ANY_T = Constant.class;
private static final Class<Constant.Bool> BOOL_T = Constant.Bool.class;
private static final Class<Constant.Integer> INT_T = Constant.Integer.class;
private static final Class<Constant.Array> ARRAY_T = Constant.Array.class;
private static final Class<Constant.Record> RECORD_T = Constant.Record.class;
/**
* Represents an object allocated on the heap.
*
* @author David J. Pearce
*
*/
public static class ConstantObject extends Constant {
private Constant value;
public ConstantObject(Constant value) {
this.value = value;
}
public Constant read() {
return value;
}
public void write(Constant newValue) {
value = newValue;
}
@Override
public boolean equals(Object o) {
return o == this;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public int compareTo(Constant o) {
// This method cannot be implmened because it does not make sense to
// compare a reference with another reference.
throw new UnsupportedOperationException("ConstantObject.compare() cannot be implemented");
}
@Override
public wyil.lang.Type type() {
// TODO: extend wyil.lang.Codes.NewObject with a lifetime and use it
return wyil.lang.Type.Reference("*", value.type());
}
}
/**
* Represents an object allocated on the heap.
*
* @author David J. Pearce
*
*/
public static class ConstantLambda extends Constant {
private final Location<Bytecode.Lambda> lambda;
private final Constant[] frame;
public ConstantLambda(Location<Bytecode.Lambda> lambda, Constant... frame) {
this.lambda = lambda;
this.frame = frame;
}
@Override
public boolean equals(Object o) {
return o == this;
}
@Override
public int hashCode() {
return Arrays.hashCode(frame);
}
@Override
public int compareTo(Constant o) {
// This method cannot be implmened because it does not make sense to
// compare a reference with another reference.
throw new UnsupportedOperationException("ConstantObject.compare() cannot be implemented");
}
@Override
public wyil.lang.Type type() {
return lambda.getType();
}
}
/**
* An internal function is simply a named internal function. This reads a
* bunch of operands and returns a set of results.
*
* @author David J. Pearce
*
*/
public static interface InternalFunction {
public Constant apply(Constant[] operands, Interpreter enclosing, Location<Operator> context) throws ResolveError;
}
}