All Downloads are FREE. Search and download functionalities are using the official Maven repository.

wyil.interpreter.Interpreter Maven / Gradle / Ivy

// Copyright 2011 The Whiley Project Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package wyil.interpreter;

import static wycc.util.AbstractCompilationUnit.ITEM_bool;
import static wycc.util.AbstractCompilationUnit.ITEM_byte;
import static wycc.util.AbstractCompilationUnit.ITEM_int;
import static wycc.util.AbstractCompilationUnit.ITEM_null;
import static wycc.util.AbstractCompilationUnit.ITEM_utf8;
import static wyil.lang.WyilFile.*;

import java.io.PrintStream;
import java.math.BigInteger;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;

import wycc.util.ArrayUtils;
import wycc.util.Trie;
import wycc.lang.Syntactic;
import wycc.util.AbstractCompilationUnit.Identifier;
import wycc.util.AbstractCompilationUnit.Tuple;
import wycc.util.AbstractCompilationUnit.Value;
import wyc.util.ErrorMessages;
import wyil.interpreter.ConcreteSemantics.RValue;
import wyil.lang.WyilFile;
import wyil.lang.WyilFile.Decl;
import wyil.lang.WyilFile.Expr;
import wyil.lang.WyilFile.LVal;
import wyil.lang.WyilFile.QualifiedName;
import wyil.lang.WyilFile.Attr.StackFrame;
import wyil.lang.WyilFile.Stmt;
import wyil.lang.WyilFile.Type;
import wyil.util.AbstractVisitor;

/**
 * 

* A simple interpreter for WyIL bytecodes. The purpose of this interpreter is * to provide a reference implementation for the semantics of WyIL bytecodes. *

*

* 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. *

* * @author David J. Pearce * */ public class Interpreter { /** * Determines the underlying semantics used for this interpreter. */ private final ConcreteSemantics semantics; /** * The debug stream provides an I/O stream through which debug bytecodes can * write their messages. */ private final PrintStream debug; /** * Set of loaded methods / functions which can be called. */ private final HashMap> callables; /** * Set of defined native mathods. */ private final IdentityHashMap> natives; /** * Defines the heap */ private final Heap globalHeap; /** * The maximum permissable stack depth. */ private final int maxStackDepth = 32; public Interpreter(PrintStream debug, Collection modules) { this(debug, modules.toArray(new WyilFile[modules.size()])); } public Interpreter(PrintStream debug, WyilFile... modules) { this.debug = debug; this.semantics = new ConcreteSemantics(); this.callables = new HashMap<>(); this.natives = new IdentityHashMap<>(); this.globalHeap = new Heap(); // for (WyilFile mod : modules) { load(mod); } } public enum Status { RETURN, BREAK, CONTINUE, NEXT } /** * Bind a native declaration against an actual implementation. * * @param callable * @param fn */ public void bindNative(QualifiedName name, BiFunction fn) { // NOTE: must use toCanonicalString() here in order to guarantee that we get the // same string as at the declaration site. Map candidates = callables.get(name); if(candidates == null) { throw new IllegalArgumentException("no native declaration found for " + name); } else { for (Decl.Callable decl : candidates.values()) { natives.put(decl, fn); } } } /** * Load a given module and make sure that all static variables are properly * initialised. * * @param mid * @param frame */ private void load(WyilFile module) { // Load all invokable items for (Decl.Unit unit : module.getModule().getUnits()) { for (Decl d : unit.getDeclarations()) { switch (d.getOpcode()) { case DECL_function: case DECL_method: case DECL_property: case DECL_variant: Decl.Callable decl = (Decl.Callable) d; Map map = callables.get(decl.getQualifiedName()); if (map == null) { map = new HashMap<>(); callables.put(decl.getQualifiedName(), map); } // NOTE: must use canonical string here to ensure unique signature for lookup. map.put(decl.getType().toCanonicalString(), decl); break; } } } // Execute all static variable initialisers for (Decl.Unit unit : module.getModule().getUnits()) { for (Decl d : unit.getDeclarations()) { switch (d.getOpcode()) { case DECL_staticvar: { Decl.StaticVariable decl = (Decl.StaticVariable) d; if (globalHeap.read(decl.getQualifiedName()) == null) { // Static variable has not been initialised yet, therefore force its // initialisation. RValue value = executeExpression(ANY_T, decl.getInitialiser(), new CallStack(), globalHeap); // Check type invariants. checkTypeInvariants(decl.getType(), value, new CallStack(), globalHeap, decl); // Done. globalHeap.write(decl.getQualifiedName(), value); } break; } } } } } public Decl.Callable getCallable(QualifiedName name, Type.Callable signature) { // NOTE: must use toCanonicalString() here in order to guarantee that we get the // same string as at the declaration site. Map candidates = callables.get(name); if(candidates == null) { return null; } else { return candidates.get(signature.toCanonicalString()); } } /** * 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 signature The exact type signature identifying the method. * @param args The supplied arguments * @return */ public RValue execute(QualifiedName name, Type.Callable signature, CallStack frame, RValue... args) { return execute(name, signature, frame, globalHeap, args, null); } /** * 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 signature The exact type signature identifying the method. * @param args The supplied arguments * @param context Identifies the position where this was invoked from to use * when reporting precondition violations. * @return */ public RValue execute(QualifiedName name, Type.Callable signature, CallStack frame, Heap heap, RValue[] args, Syntactic.Item context) { Decl.Callable lambda = getCallable(name, signature); if (lambda == null) { throw new IllegalArgumentException("no function or method found: " + name + ", " + signature); } else if (lambda.getParameters().size() != args.length) { throw new IllegalArgumentException( "incorrect number of arguments: " + lambda.getName() + ", " + lambda.getType()); } else if (frame.depth() >= maxStackDepth) { throw new RuntimeError(WyilFile.RUNTIME_FAULT, frame, context); } else if (lambda.getModifiers().match(Modifier.Native.class) != null) { // This is a native method. Check whether or no there is a definition for it! BiFunction def = natives.get(lambda); if(def == null) { // No definition given throw new IllegalArgumentException("native function or method has no definition: " + name + ", " + signature); } else { // Yes, found a definition so run it :) return def.apply(heap,args); } } // Enter a new frame for executing this callable item frame = frame.enter(lambda); // Execute the lambda we've extracted return execute(lambda, frame, heap, args, context); } /** * Execute a given callable entity, which could be a function, method, property * or lambda. * * @param lambda * @param frame * @param args The supplied arguments * @param context Identifies the position where this was invoked from to use * when reporting precondition violations. * @return */ public RValue execute(Decl.Callable lambda, CallStack frame, Heap heap, RValue[] args, Syntactic.Item context) { // Fourth, construct the stack frame for execution extractParameters(frame, args, lambda); // Check the precondition if (lambda instanceof Decl.FunctionOrMethod) { Decl.FunctionOrMethod fm = (Decl.FunctionOrMethod) lambda; // Check preconditions hold checkPrecondition(WyilFile.RUNTIME_PRECONDITION_FAILURE, frame, heap, fm.getRequires(), context); // check function or method body exists if (fm.getBody().size() == 0) { // 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: " + lambda.getQualifiedName() + " : " + lambda.getType()); } else if (fm instanceof Decl.Method) { // Stash the prestate as it is frame.putLocal(OLD, heap.clone()); } // Execute the method or function body executeBlock(fm.getBody(), frame, heap, new CallableScope(fm)); // Restore original parameter values extractParameters(frame, args, fm); // Check the postcondition holds checkInvariants(WyilFile.RUNTIME_POSTCONDITION_FAILURE, frame, heap, fm.getEnsures(), context); // Extract the return values return packReturns(frame, lambda); } else if (lambda instanceof Decl.Lambda) { Decl.Lambda l = (Decl.Lambda) lambda; return executeExpression(ANY_T, l.getBody(), frame, heap); } else if (lambda instanceof Decl.Property) { Decl.Property p = (Decl.Property) lambda; // Check preconditions hold checkPrecondition(WyilFile.RUNTIME_PRECONDITION_FAILURE, frame, heap, p.getRequires(), context); // Execute the method or function body executeBlock(p.getBody(), frame, heap, new CallableScope(p)); // Extract the return values return packReturns(frame, lambda); } else { Decl.Variant p = (Decl.Variant) lambda; Tuple invariant = p.getInvariant(); // Evaluate clauses of property, and terminate early as soon as one doesn't // hold. for (int i = 0; i != invariant.size(); ++i) { RValue.Bool retval = executeExpression(BOOL_T, invariant.get(i), frame, heap); if (!retval.boolValue()) { // Short circuit return retval; } } // return RValue.True; } } private void extractParameters(CallStack frame, RValue[] args, Decl.Callable decl) { Tuple parameters = decl.getParameters(); for (int i = 0; i != parameters.size(); ++i) { Decl.Variable parameter = parameters.get(i); frame.putLocal(parameter.getName(), args[i]); } } /** * 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 RValue packReturns(CallStack frame, Decl.Callable decl) { if (decl instanceof Decl.Variant) { return RValue.True; } else { Tuple returns = decl.getReturns(); if (returns.size() == 1) { Decl.Variable v = returns.get(0); return frame.getLocal(v.getName()); } else { RValue[] items = new RValue[returns.size()]; for (int i = 0; i != items.length; ++i) { Decl.Variable v = returns.get(i); items[i] = frame.getLocal(v.getName()); } return RValue.Tuple(items); } } } /** * 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(Stmt.Block block, CallStack frame, Heap heap, EnclosingScope scope) { for (int i = 0; i != block.size(); ++i) { Stmt stmt = block.get(i); Status r = executeStatement(stmt, frame, heap, scope); // 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(Stmt stmt, CallStack frame, Heap heap, EnclosingScope scope) { checkForTimeout(frame); // try { // switch (stmt.getOpcode()) { case STMT_assert: return executeAssert((Stmt.Assert) stmt, frame, heap, scope); case STMT_assume: return executeAssume((Stmt.Assume) stmt, frame, heap, scope); case STMT_assign: return executeAssign((Stmt.Assign) stmt, frame, heap, scope); case STMT_break: return executeBreak((Stmt.Break) stmt, frame, heap, scope); case STMT_continue: return executeContinue((Stmt.Continue) stmt, frame, heap, scope); case STMT_debug: return executeDebug((Stmt.Debug) stmt, frame, heap, scope); case STMT_dowhile: return executeDoWhile((Stmt.DoWhile) stmt, frame, heap, scope); case STMT_fail: return executeFail((Stmt.Fail) stmt, frame, heap, scope); case STMT_for: return executeFor((Stmt.For) stmt, frame, heap, scope); case STMT_if: case STMT_ifelse: return executeIf((Stmt.IfElse) stmt, frame, heap, scope); case STMT_initialiser: case STMT_initialiservoid: return executeInitialiser((Stmt.Initialiser) stmt, frame, heap, scope); case EXPR_indirectinvoke: executeIndirectInvoke((Expr.IndirectInvoke) stmt, frame, heap); return Status.NEXT; case EXPR_invoke: executeInvoke((Expr.Invoke) stmt, frame, heap); return Status.NEXT; case STMT_namedblock: return executeNamedBlock((Stmt.NamedBlock) stmt, frame, heap, scope); case STMT_while: return executeWhile((Stmt.While) stmt, frame, heap, scope); case STMT_return: case STMT_returnvoid: return executeReturn((Stmt.Return) stmt, frame, heap, scope); case STMT_skip: return executeSkip((Stmt.Skip) stmt, frame, heap, scope); case STMT_switch: return executeSwitch((Stmt.Switch) stmt, frame, heap, scope); case DECL_variable: return executeVariableDeclaration((Decl.Variable) stmt, frame, heap); } deadCode(stmt); return null; // deadcode } catch (StackOverflowError e) { throw new RuntimeError(WyilFile.RUNTIME_FAULT, frame, stmt); } } private Status executeAssign(Stmt.Assign stmt, CallStack frame, Heap heap, EnclosingScope scope) { Tuple lhs = stmt.getLeftHandSide(); Tuple rhs = stmt.getRightHandSide(); RValue[] rvals = executeExpressions(stmt.getRightHandSide(), frame, heap); // Execute the assignment for (int i = 0; i != rhs.size(); ++i) { Expr r = rhs.get(i); // Determine width of expression executeAssignLVal(lhs.get(i), rvals[i], frame, heap, scope, r); } // Check type invariants. This has to happen after every variable has been // assigned as type invariants are allowed to be temporarily broken within // multiple assignments. for (int i = 0; i != lhs.size(); ++i) { Expr r = rhs.get(i); checkAssignedLVal(lhs.get(i), frame, heap, scope, r); } return Status.NEXT; } private void executeAssignLVal(LVal lval, RValue rval, CallStack frame, Heap heap, EnclosingScope scope, Syntactic.Item context) { switch (lval.getOpcode()) { case EXPR_arrayborrow: case EXPR_arrayaccess: { executeAssignArray((Expr.ArrayAccess) lval, rval, frame, heap, scope, context); break; } case EXPR_dereference: { executeAssignDereference((Expr.Dereference) lval, rval, frame, heap, scope, context); break; } case EXPR_fielddereference: { executeAssignFieldDereference((Expr.FieldDereference) lval, rval, frame, heap, scope, context); break; } case EXPR_recordaccess: case EXPR_recordborrow: { executeAssignRecord((Expr.RecordAccess) lval, rval, frame, heap, scope, context); break; } case EXPR_staticvariable: { executeStaticAssignVariable((Expr.StaticVariableAccess) lval, rval, frame, heap, scope, context); break; } case EXPR_tupleinitialiser: { executeAssignTuple((Expr.TupleInitialiser) lval, rval, frame, heap, scope, context); break; } case EXPR_variablemove: case EXPR_variablecopy: { executeAssignVariable((Expr.VariableAccess) lval, rval, frame, heap, scope, context); break; } default: deadCode(lval); } } private void executeAssignArray(Expr.ArrayAccess lval, RValue rval, CallStack frame, Heap heap, EnclosingScope scope, Syntactic.Item context) { RValue.Array array = executeExpression(ARRAY_T, lval.getFirstOperand(), frame, heap); RValue.Int index = executeExpression(INT_T, lval.getSecondOperand(), frame, heap); // Sanity check access checkArrayBounds(array, index, frame, lval.getSecondOperand()); // Update the array array = array.write(index, rval); // Write the results back executeAssignLVal((LVal) lval.getFirstOperand(), array, frame, heap, scope, context); } private void executeAssignDereference(Expr.Dereference lval, RValue rval, CallStack frame, Heap heap, EnclosingScope scope, Syntactic.Item context) { RValue.Reference ref = executeExpression(REF_T, lval.getOperand(), frame, heap); // FIXME: need to check type invariant here?? int address = ref.deref(); // Write to heap heap.write(address, rval); } private void executeAssignFieldDereference(Expr.FieldDereference lval, RValue rval, CallStack frame, Heap heap, EnclosingScope scope, Syntactic.Item context) { RValue.Reference ref = executeExpression(REF_T, lval.getOperand(), frame, heap); // Extract target cell int address = ref.deref(); // FIXME: need to check type invariant here?? RValue.Record record = checkType(heap.read(address), lval.getOperand(), RECORD_T); // Update target heap.write(address, record.write(lval.getField(), rval)); } private void executeAssignRecord(Expr.RecordAccess lval, RValue rval, CallStack frame, Heap heap, EnclosingScope scope, Syntactic.Item context) { RValue.Record record = executeExpression(RECORD_T, lval.getOperand(), frame, heap); // Write rval to field record = record.write(lval.getField(), rval); // executeAssignLVal((LVal) lval.getOperand(), record, frame, heap, scope, context); } private void executeAssignVariable(Expr.VariableAccess lval, RValue rval, CallStack frame, Heap heap, EnclosingScope scope, Syntactic.Item context) { frame.putLocal(lval.getVariableDeclaration().getName(), rval); } private void executeStaticAssignVariable(Expr.StaticVariableAccess lval, RValue rval, CallStack frame, Heap heap, EnclosingScope scope, Syntactic.Item context) { Decl.StaticVariable decl = lval.getLink().getTarget(); // heap.write(decl.getQualifiedName(), rval); } private void executeAssignTuple(Expr.TupleInitialiser lval, RValue rval, CallStack frame, Heap heap, EnclosingScope scope, Syntactic.Item context) { Tuple operands = lval.getOperands(); // Check we have a tuple as expected! RValue.Tuple tuple = checkType(rval, lval, TUPLE_T); // if (tuple.size() != operands.size()) { deadCode(lval); } // for (int i = 0; i != operands.size(); ++i) { executeAssignLVal((LVal) operands.get(i), tuple.get(i), frame, heap, scope, context); } } private void checkAssignedLVal(LVal lval, CallStack frame, Heap heap, EnclosingScope scope, Syntactic.Item context) { switch (lval.getOpcode()) { case EXPR_arrayborrow: case EXPR_arrayaccess: { Expr.ArrayAccess v = (Expr.ArrayAccess) lval; checkAssignedLVal((LVal) v.getFirstOperand(), frame, heap, scope, context); break; } case EXPR_dereference: { Expr.Dereference v = (Expr.Dereference) lval; checkAssignedLVal((LVal) v.getOperand(), frame, heap, scope, context); break; } case EXPR_fielddereference: { Expr.FieldDereference v = (Expr.FieldDereference) lval; checkAssignedLVal((LVal) v.getOperand(), frame, heap, scope, context); break; } case EXPR_recordaccess: case EXPR_recordborrow: { Expr.RecordAccess v = (Expr.RecordAccess) lval; checkAssignedLVal((LVal) v.getOperand(), frame, heap, scope, context); break; } case EXPR_staticvariable: { Expr.StaticVariableAccess v = (Expr.StaticVariableAccess) lval; Decl.StaticVariable d = v.getLink().getTarget(); checkTypeInvariants(d.getType(), heap.read(d.getQualifiedName()), frame, heap, context); break; } case EXPR_tupleinitialiser: { Expr.TupleInitialiser v = (Expr.TupleInitialiser) lval; Tuple operands = v.getOperands(); for (int i = 0; i != operands.size(); ++i) { checkAssignedLVal((LVal) operands.get(i), frame, heap, scope, context); } break; } case EXPR_variablemove: case EXPR_variablecopy: { Expr.VariableAccess v = (Expr.VariableAccess) lval; Decl.Variable d = v.getVariableDeclaration(); checkTypeInvariants(d.getType(), frame.getLocal(d.getName()), frame, heap, context); break; } default: deadCode(lval); } } /** * Execute an assert or assume statement. In both cases, if the condition * evaluates to false an exception is thrown. * * @param stmt --- Assert statement. * @param frame --- The current stack frame * @return */ private Status executeAssert(Stmt.Assert stmt, CallStack frame, Heap heap, EnclosingScope scope) { // checkInvariants(WyilFile.RUNTIME_ASSERTION_FAILURE, frame, heap, stmt.getCondition()); 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 statement. * @param frame --- The current stack frame * @return */ private Status executeAssume(Stmt.Assume stmt, CallStack frame, Heap heap, EnclosingScope scope) { // checkInvariants(WyilFile.RUNTIME_ASSUMPTION_FAILURE, frame, heap, stmt.getCondition()); 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(Stmt.Break stmt, CallStack frame, Heap heap, EnclosingScope scope) { // 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(Stmt.Continue stmt, CallStack frame, Heap heap, EnclosingScope scope) { 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 */ protected Status executeDebug(Stmt.Debug stmt, CallStack frame, Heap heap, EnclosingScope scope) { // RValue.Array arr = executeExpression(ARRAY_T, stmt.getOperand(), frame, heap); // for (RValue item : arr.getElements()) { RValue.Int i = (RValue.Int) item; char c = (char) i.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(Stmt.DoWhile stmt, CallStack frame, Heap heap, EnclosingScope scope) { int errcode = WyilFile.RUNTIME_LOOPINVARIANT_ESTABLISH_FAILURE; Status r = Status.NEXT; while (r == Status.NEXT || r == Status.CONTINUE) { r = executeBlock(stmt.getBody(), frame, heap, scope); if (r == Status.NEXT) { // NOTE: only check loop invariant if normal execution, since breaks are handled // differently. checkInvariants(errcode, frame, heap, stmt.getInvariant(), null); errcode = WyilFile.RUNTIME_LOOPINVARIANT_RESTORED_FAILURE; RValue.Bool operand = executeExpression(BOOL_T, stmt.getCondition(), frame, heap); if (operand == RValue.False) { 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(Stmt.Fail stmt, CallStack frame, Heap heap, EnclosingScope scope) { throw new RuntimeError(WyilFile.RUNTIME_FAULT, frame, stmt); } /** * Execute a for statement at a given point in the function or method body. This * will loop over the body zero or more times. * * * @param stmt --- The for statement to execute * @param frame --- The current stack frame * @return */ private Status executeFor(Stmt.For stmt, CallStack frame, Heap heap, EnclosingScope scope) { Decl.StaticVariable var = stmt.getVariable(); int errcode = WyilFile.RUNTIME_LOOPINVARIANT_ESTABLISH_FAILURE; Status r = Status.NEXT; // Evaluate index array RValue[] indices = executeExpression(ARRAY_T, var.getInitialiser(), frame, heap).getElements(); // Execute loop for (int i = 0; i != indices.length; ++i) { // Assign index variable frame.putLocal(var.getName(), indices[i]); // Check loop invariants checkInvariants(errcode, frame, heap, stmt.getInvariant(), null); // Keep executing the loop body until we exit it somehow. r = executeBlock(stmt.getBody(), frame, heap, scope); if (r == Status.BREAK || r == Status.RETURN) { break; } else if (r == Status.CONTINUE) { r = Status.NEXT; } errcode = WyilFile.RUNTIME_LOOPINVARIANT_RESTORED_FAILURE; } // 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 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(Stmt.IfElse stmt, CallStack frame, Heap heap, EnclosingScope scope) { RValue.Bool operand = executeExpression(BOOL_T, stmt.getCondition(), frame, heap); if (operand == RValue.True) { // branch taken, so execute true branch return executeBlock(stmt.getTrueBranch(), frame, heap, scope); } else if (stmt.hasFalseBranch()) { // branch not taken, so execute false branch return executeBlock(stmt.getFalseBranch(), frame, heap, scope); } else { return Status.NEXT; } } private Status executeInitialiser(Stmt.Initialiser stmt, CallStack frame, Heap heap, EnclosingScope scope) { Tuple variables = stmt.getVariables(); // if (!stmt.hasInitialiser()) { // Do nothing as no initialiser! } else if (variables.size() == 1) { // Easy case --- unit assignment RValue value = executeExpression(ANY_T, stmt.getInitialiser(), frame, heap); // Check type invariants are established checkTypeInvariants(stmt.getType(), value, frame, heap, stmt.getInitialiser()); // Assign variable frame.putLocal(variables.get(0).getName(), value); } else { // Construct lhs type Type type = Type.Tuple.create(variables.map(v -> v.getType())); // Harder case --- tuple assignment RValue.Tuple value = executeExpression(TUPLE_T, stmt.getInitialiser(), frame, heap); // Check type invariants are established checkTypeInvariants(type, value, frame, heap, stmt.getInitialiser()); // Assign individual components for (int i = 0; i != variables.size(); ++i) { frame.putLocal(variables.get(i).getName(), value.get(i)); } } // Done 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(Stmt.NamedBlock stmt, CallStack frame, Heap heap, EnclosingScope scope) { return executeBlock(stmt.getBlock(), frame, heap, scope); } /** * 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(Stmt.While stmt, CallStack frame, Heap heap, EnclosingScope scope) { int errcode = WyilFile.RUNTIME_LOOPINVARIANT_ESTABLISH_FAILURE; Status r; int count = 0; do { checkInvariants(errcode, frame, heap, stmt.getInvariant(), null); RValue.Bool operand = executeExpression(BOOL_T, stmt.getCondition(), frame, heap); if (operand == RValue.False) { return Status.NEXT; } // Keep executing the loop body until we exit it somehow. r = executeBlock(stmt.getBody(), frame, heap, scope); // errcode = WyilFile.RUNTIME_LOOPINVARIANT_RESTORED_FAILURE; } 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(Stmt.Return stmt, CallStack frame, Heap heap, EnclosingScope scope) { // 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. CallableScope enclosingScope = scope.getEnclosingScope(CallableScope.class); // Extract relevant information Decl.Callable context = enclosingScope.getContext(); Tuple returns = context.getReturns(); Type.Callable type = context.getType(); if (stmt.hasReturn()) { // Execute return expressions RValue value = executeExpression(ANY_T, stmt.getReturn(), frame, heap); // Check type invariants checkTypeInvariants(type.getReturn(), value, frame, heap, stmt.getReturn()); // if (returns.size() > 1) { RValue.Tuple t = (RValue.Tuple) value; for (int i = 0; i != returns.size(); ++i) { Decl.Variable r = returns.get(i); frame.putLocal(r.getName(), t.get(i)); } } else { Decl.Variable r = returns.get(0); frame.putLocal(r.getName(), value); } } // 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(Stmt.Skip stmt, CallStack frame, Heap heap, EnclosingScope scope) { // 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(Stmt.Switch stmt, CallStack frame, Heap heap, EnclosingScope scope) { Tuple cases = stmt.getCases(); // Object value = executeExpression(ANY_T, stmt.getCondition(), frame, heap); for (int i = 0; i != cases.size(); ++i) { Stmt.Case c = cases.get(i); Stmt.Block body = c.getBlock(); if (c.isDefault()) { return executeBlock(body, frame, heap, scope); } else { // FIXME: this is a temporary hack until a proper notion of // ConstantExpr is introduced. RValue[] values = executeExpressions(c.getConditions(), frame, heap); for (RValue v : values) { if (v.equals(value)) { return executeBlock(body, frame, heap, scope); } } } } 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(Decl.Variable stmt, CallStack frame, Heap heap) { // We only need to do something if this has an initialiser 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 */ public T executeExpression(Class expected, Expr expr, CallStack frame, Heap heap) { checkForTimeout(frame); // RValue val; switch (expr.getOpcode()) { case EXPR_constant: val = executeConst((Expr.Constant) expr, frame, heap); break; case EXPR_cast: val = executeConvert((Expr.Cast) expr, frame, heap); break; case EXPR_recordinitialiser: val = executeRecordInitialiser((Expr.RecordInitialiser) expr, frame, heap); break; case EXPR_recordaccess: case EXPR_recordborrow: val = executeRecordAccess((Expr.RecordAccess) expr, frame, heap); break; case EXPR_recordupdate: val = executeRecordUpdate((Expr.RecordUpdate) expr, frame, heap); break; case EXPR_indirectinvoke: val = executeIndirectInvoke((Expr.IndirectInvoke) expr, frame, heap); break; case EXPR_invoke: val = executeInvoke((Expr.Invoke) expr, frame, heap); break; case EXPR_variablemove: case EXPR_variablecopy: val = executeVariableAccess((Expr.VariableAccess) expr, frame, heap); break; case EXPR_staticvariable: val = executeStaticVariableAccess((Expr.StaticVariableAccess) expr, frame, heap); break; case EXPR_is: val = executeIs((Expr.Is) expr, frame, heap); break; case EXPR_logicalnot: val = executeLogicalNot((Expr.LogicalNot) expr, frame, heap); break; case EXPR_logicaland: val = executeLogicalAnd((Expr.LogicalAnd) expr, frame, heap); break; case EXPR_logicalor: val = executeLogicalOr((Expr.LogicalOr) expr, frame, heap); break; case EXPR_logicalimplication: val = executeLogicalImplication((Expr.LogicalImplication) expr, frame, heap); break; case EXPR_logicaliff: val = executeLogicalIff((Expr.LogicalIff) expr, frame, heap); break; case EXPR_logicalexistential: case EXPR_logicaluniversal: val = executeQuantifier((Expr.Quantifier) expr, frame, heap); break; case EXPR_equal: val = executeEqual((Expr.Equal) expr, frame, heap); break; case EXPR_notequal: val = executeNotEqual((Expr.NotEqual) expr, frame, heap); break; case EXPR_integernegation: val = executeIntegerNegation((Expr.IntegerNegation) expr, frame, heap); break; case EXPR_integeraddition: val = executeIntegerAddition((Expr.IntegerAddition) expr, frame, heap); break; case EXPR_integersubtraction: val = executeIntegerSubtraction((Expr.IntegerSubtraction) expr, frame, heap); break; case EXPR_integermultiplication: val = executeIntegerMultiplication((Expr.IntegerMultiplication) expr, frame, heap); break; case EXPR_integerdivision: val = executeIntegerDivision((Expr.IntegerDivision) expr, frame, heap); break; case EXPR_integerremainder: val = executeIntegerRemainder((Expr.IntegerRemainder) expr, frame, heap); break; case EXPR_integerexponent: val = executeIntegerExponent((Expr.IntegerExponent) expr, frame, heap); break; case EXPR_integerlessthan: val = executeIntegerLessThan((Expr.IntegerLessThan) expr, frame, heap); break; case EXPR_integerlessequal: val = executeIntegerLessThanOrEqual((Expr.IntegerLessThanOrEqual) expr, frame, heap); break; case EXPR_integergreaterthan: val = executeIntegerGreaterThan((Expr.IntegerGreaterThan) expr, frame, heap); break; case EXPR_integergreaterequal: val = executeIntegerGreaterThanOrEqual((Expr.IntegerGreaterThanOrEqual) expr, frame, heap); break; case EXPR_bitwisenot: val = executeBitwiseNot((Expr.BitwiseComplement) expr, frame, heap); break; case EXPR_bitwiseor: val = executeBitwiseOr((Expr.BitwiseOr) expr, frame, heap); break; case EXPR_bitwisexor: val = executeBitwiseXor((Expr.BitwiseXor) expr, frame, heap); break; case EXPR_bitwiseand: val = executeBitwiseAnd((Expr.BitwiseAnd) expr, frame, heap); break; case EXPR_bitwiseshl: val = executeBitwiseShiftLeft((Expr.BitwiseShiftLeft) expr, frame, heap); break; case EXPR_bitwiseshr: val = executeBitwiseShiftRight((Expr.BitwiseShiftRight) expr, frame, heap); break; case EXPR_arrayborrow: case EXPR_arrayaccess: val = executeArrayAccess((Expr.ArrayAccess) expr, frame, heap); break; case EXPR_arraygenerator: val = executeArrayGenerator((Expr.ArrayGenerator) expr, frame, heap); break; case EXPR_arraylength: val = executeArrayLength((Expr.ArrayLength) expr, frame, heap); break; case EXPR_arrayinitialiser: val = executeArrayInitialiser((Expr.ArrayInitialiser) expr, frame, heap); break; case EXPR_arrayrange: val = executeArrayRange((Expr.ArrayRange) expr, frame, heap); break; case EXPR_arrayupdate: val = executeArrayUpdate((Expr.ArrayUpdate) expr, frame, heap); break; case EXPR_new: val = executeNew((Expr.New) expr, frame, heap); break; case EXPR_old: val = executeOld((Expr.Old) expr, frame, heap); break; case EXPR_dereference: val = executeDereference((Expr.Dereference) expr, frame, heap); break; case EXPR_fielddereference: val = executeFieldDereference((Expr.FieldDereference) expr, frame, heap); break; case EXPR_lambdaaccess: val = executeLambdaAccess((Expr.LambdaAccess) expr, frame, heap); break; case DECL_lambda: val = executeLambdaDeclaration((Decl.Lambda) expr, frame, heap); break; case EXPR_tupleinitialiser: val = executeTupleInitialiser((Expr.TupleInitialiser) expr, frame, heap); break; default: return deadCode(expr); } return checkType(val, expr, expected); } /** * 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 RValue executeConst(Expr.Constant expr, CallStack frame, Heap heap) { Value v = expr.getValue(); switch (v.getOpcode()) { case ITEM_null: return RValue.Null; case ITEM_bool: { Value.Bool b = (Value.Bool) v; if (b.get()) { return RValue.True; } else { return RValue.False; } } case ITEM_byte: { Value.Byte b = (Value.Byte) v; return semantics.Byte(b.get()); } case ITEM_int: { Value.Int i = (Value.Int) v; return semantics.Int(i.get()); } case ITEM_utf8: { Value.UTF8 s = (Value.UTF8) v; byte[] bytes = s.get(); RValue[] elements = new RValue[bytes.length]; for (int i = 0; i != elements.length; ++i) { // FIXME: something tells me this is wrong for signed byte // values? elements[i] = semantics.Int(BigInteger.valueOf(bytes[i])); } return semantics.Array(elements); } default: throw new RuntimeException("unknown value encountered (" + expr + ")"); } } /** * 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 RValue executeConvert(Expr.Cast expr, CallStack frame, Heap heap) { RValue operand = executeExpression(ANY_T, expr.getOperand(), frame, heap); return operand.convert(expr.getType()); } private RValue executeRecordAccess(Expr.RecordAccess expr, CallStack frame, Heap heap) { RValue.Record rec = executeExpression(RECORD_T, expr.getOperand(), frame, heap); return rec.read(expr.getField()); } private RValue executeRecordInitialiser(Expr.RecordInitialiser expr, CallStack frame, Heap heap) { Tuple fields = expr.getFields(); Tuple operands = expr.getOperands(); RValue.Field[] values = new RValue.Field[operands.size()]; for (int i = 0; i != operands.size(); ++i) { Identifier field = fields.get(i); Expr operand = operands.get(i); RValue value = executeExpression(ANY_T, operand, frame, heap); values[i] = semantics.Field(field, value); } return semantics.Record(values); } public RValue executeRecordUpdate(Expr.RecordUpdate expr, CallStack frame, Heap heap) { RValue.Record rec = executeExpression(RECORD_T, expr.getFirstOperand(), frame, heap); // Evaluate new element RValue value = executeExpression(ANY_T, expr.getSecondOperand(), frame, heap); // Update the array return rec.write(expr.getField(), value); } private RValue executeTupleInitialiser(Expr.TupleInitialiser expr, CallStack frame, Heap heap) { Tuple operands = expr.getOperands(); RValue[] values = new RValue[operands.size()]; for (int i = 0; i != values.length; ++i) { values[i] = executeExpression(ANY_T, operands.get(i), frame, heap); } return semantics.Tuple(values); } private RValue executeQuantifier(Expr.Quantifier expr, CallStack frame, Heap heap) { boolean r = executeQuantifier(0, expr, frame, heap); boolean q = (expr instanceof Expr.UniversalQuantifier); return r == q ? RValue.True : RValue.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, Expr.Quantifier expr, CallStack frame, Heap heap) { Tuple vars = expr.getParameters(); if (index == vars.size()) { // This is the base case where we evaluate the condition itself. RValue.Bool r = executeExpression(BOOL_T, expr.getOperand(), frame, heap); boolean q = (expr instanceof Expr.UniversalQuantifier); // If this evaluates to true, then we will continue executing the // quantifier. return r.boolValue() == q; } else { Decl.StaticVariable var = vars.get(index); RValue.Array range = executeExpression(ARRAY_T, var.getInitialiser(), frame, heap); RValue[] elements = range.getElements(); for (int i = 0; i != elements.length; ++i) { frame.putLocal(var.getName(), elements[i]); boolean r = executeQuantifier(index + 1, expr, frame, heap); if (!r) { // early termination return r; } } return true; } } /** * 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 RValue executeVariableAccess(Expr.VariableAccess expr, CallStack frame, Heap heap) { Decl.Variable decl = expr.getVariableDeclaration(); return frame.getLocal(decl.getName()); } private RValue executeStaticVariableAccess(Expr.StaticVariableAccess expr, CallStack frame, Heap heap) { Decl.StaticVariable decl = expr.getLink().getTarget(); RValue v = heap.read(decl.getQualifiedName()); if (v == null) { // NOTE: it's possible to get here without the static variable having been // initialised in the special case that we have just loaded a module. frame = frame.enter(decl); v = executeExpression(ANY_T, decl.getInitialiser(), frame, heap); heap.write(decl.getQualifiedName(), v); } return v; } private RValue executeIs(Expr.Is expr, CallStack frame, Heap heap) { RValue lhs = executeExpression(ANY_T, expr.getOperand(), frame, heap); return lhs.is(expr.getTestType(), frame, heap); } public RValue executeIntegerNegation(Expr.IntegerNegation expr, CallStack frame, Heap heap) { RValue.Int lhs = executeExpression(INT_T, expr.getOperand(), frame, heap); return lhs.negate(); } public RValue executeIntegerAddition(Expr.IntegerAddition expr, CallStack frame, Heap heap) { RValue.Int lhs = executeExpression(INT_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); return lhs.add(rhs); } public RValue executeIntegerSubtraction(Expr.IntegerSubtraction expr, CallStack frame, Heap heap) { RValue.Int lhs = executeExpression(INT_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); return lhs.subtract(rhs); } public RValue executeIntegerMultiplication(Expr.IntegerMultiplication expr, CallStack frame, Heap heap) { RValue.Int lhs = executeExpression(INT_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); return lhs.multiply(rhs); } public RValue executeIntegerDivision(Expr.IntegerDivision expr, CallStack frame, Heap heap) { RValue.Int lhs = executeExpression(INT_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); checkDivisionByZero(rhs, frame, expr.getSecondOperand()); return lhs.divide(rhs); } public RValue executeIntegerRemainder(Expr.IntegerRemainder expr, CallStack frame, Heap heap) { RValue.Int lhs = executeExpression(INT_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); checkDivisionByZero(rhs, frame, expr.getSecondOperand()); return lhs.remainder(rhs); } public RValue executeIntegerExponent(Expr.IntegerExponent expr, CallStack frame, Heap heap) { RValue.Int lhs = executeExpression(INT_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); checkNonNegative(rhs, frame, expr.getSecondOperand()); return lhs.pow(rhs); } public RValue executeEqual(Expr.Equal expr, CallStack frame, Heap heap) { RValue lhs = executeExpression(ANY_T, expr.getFirstOperand(), frame, heap); RValue rhs = executeExpression(ANY_T, expr.getSecondOperand(), frame, heap); return lhs.equal(rhs); } public RValue executeNotEqual(Expr.NotEqual expr, CallStack frame, Heap heap) { RValue lhs = executeExpression(ANY_T, expr.getFirstOperand(), frame, heap); RValue rhs = executeExpression(ANY_T, expr.getSecondOperand(), frame, heap); return lhs.notEqual(rhs); } public RValue executeIntegerLessThan(Expr.IntegerLessThan expr, CallStack frame, Heap heap) { RValue.Int lhs = executeExpression(INT_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); return lhs.lessThan(rhs); } public RValue executeIntegerLessThanOrEqual(Expr.IntegerLessThanOrEqual expr, CallStack frame, Heap heap) { RValue.Int lhs = executeExpression(INT_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); return lhs.lessThanOrEqual(rhs); } public RValue executeIntegerGreaterThan(Expr.IntegerGreaterThan expr, CallStack frame, Heap heap) { RValue.Int lhs = executeExpression(INT_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); return rhs.lessThan(lhs); } public RValue executeIntegerGreaterThanOrEqual(Expr.IntegerGreaterThanOrEqual expr, CallStack frame, Heap heap) { RValue.Int lhs = executeExpression(INT_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); return rhs.lessThanOrEqual(lhs); } public RValue executeLogicalNot(Expr.LogicalNot expr, CallStack frame, Heap heap) { RValue.Bool lhs = executeExpression(BOOL_T, expr.getOperand(), frame, heap); return lhs.not(); } public RValue executeLogicalAnd(Expr.LogicalAnd expr, CallStack frame, Heap heap) { // This is a short-circuiting operator. Therefore, we fail as soon as one // argument fails. Tuple operands = expr.getOperands(); for (int i = 0; i != operands.size(); ++i) { RValue.Bool b = executeExpression(BOOL_T, operands.get(i), frame, heap); if (b == RValue.False) { return b; } } return RValue.True; } public RValue executeLogicalOr(Expr.LogicalOr expr, CallStack frame, Heap heap) { // This is a short-circuiting operator. Therefore, we succeed as soon as one // argument succeeds. Tuple operands = expr.getOperands(); for (int i = 0; i != operands.size(); ++i) { RValue.Bool b = executeExpression(BOOL_T, operands.get(i), frame, heap); if (b == RValue.True) { return b; } } return RValue.False; } public RValue executeLogicalImplication(Expr.LogicalImplication expr, CallStack frame, Heap heap) { RValue.Bool lhs = executeExpression(BOOL_T, expr.getFirstOperand(), frame, heap); if (lhs == RValue.False) { return RValue.True; } else { RValue.Bool rhs = executeExpression(BOOL_T, expr.getSecondOperand(), frame, heap); return lhs.equal(rhs); } } public RValue executeLogicalIff(Expr.LogicalIff expr, CallStack frame, Heap heap) { RValue.Bool lhs = executeExpression(BOOL_T, expr.getFirstOperand(), frame, heap); RValue.Bool rhs = executeExpression(BOOL_T, expr.getSecondOperand(), frame, heap); return lhs.equal(rhs); } public RValue executeBitwiseNot(Expr.BitwiseComplement expr, CallStack frame, Heap heap) { RValue.Byte lhs = executeExpression(BYTE_T, expr.getOperand(), frame, heap); return lhs.invert(); } public RValue executeBitwiseAnd(Expr.BitwiseAnd expr, CallStack frame, Heap heap) { Tuple operands = expr.getOperands(); RValue.Byte val = executeExpression(BYTE_T, operands.get(0), frame, heap); for (int i = 1; i != operands.size(); ++i) { val = val.and(executeExpression(BYTE_T, operands.get(i), frame, heap)); } return val; } public RValue executeBitwiseOr(Expr.BitwiseOr expr, CallStack frame, Heap heap) { Tuple operands = expr.getOperands(); RValue.Byte val = executeExpression(BYTE_T, operands.get(0), frame, heap); for (int i = 1; i != operands.size(); ++i) { val = val.or(executeExpression(BYTE_T, operands.get(i), frame, heap)); } return val; } public RValue executeBitwiseXor(Expr.BitwiseXor expr, CallStack frame, Heap heap) { Tuple operands = expr.getOperands(); RValue.Byte val = executeExpression(BYTE_T, operands.get(0), frame, heap); for (int i = 1; i != operands.size(); ++i) { val = val.xor(executeExpression(BYTE_T, operands.get(i), frame, heap)); } return val; } public RValue executeBitwiseShiftLeft(Expr.BitwiseShiftLeft expr, CallStack frame, Heap heap) { RValue.Byte lhs = executeExpression(BYTE_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); return lhs.shl(rhs); } public RValue executeBitwiseShiftRight(Expr.BitwiseShiftRight expr, CallStack frame, Heap heap) { RValue.Byte lhs = executeExpression(BYTE_T, expr.getFirstOperand(), frame, heap); RValue.Int rhs = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); return lhs.shr(rhs); } public RValue executeArrayLength(Expr.ArrayLength expr, CallStack frame, Heap heap) { RValue.Array array = executeExpression(ARRAY_T, expr.getOperand(), frame, heap); return array.length(); } public RValue executeArrayAccess(Expr.ArrayAccess expr, CallStack frame, Heap heap) { RValue.Array array = executeExpression(ARRAY_T, expr.getFirstOperand(), frame, heap); RValue.Int index = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); // Sanity check access checkArrayBounds(array, index, frame, expr.getSecondOperand()); // Perform the read return array.read(index); } public RValue executeArrayGenerator(Expr.ArrayGenerator expr, CallStack frame, Heap heap) { RValue element = executeExpression(ANY_T, expr.getFirstOperand(), frame, heap); RValue.Int count = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); int n = count.intValue(); if (n < 0) { throw new RuntimeError(WyilFile.RUNTIME_NEGATIVE_LENGTH_FAILURE, frame, expr.getSecondOperand()); } RValue[] values = new RValue[n]; for (int i = 0; i != n; ++i) { values[i] = element; } return semantics.Array(values); } public RValue executeArrayInitialiser(Expr.ArrayInitialiser expr, CallStack frame, Heap heap) { Tuple operands = expr.getOperands(); RValue[] elements = new RValue[operands.size()]; for (int i = 0; i != elements.length; ++i) { elements[i] = executeExpression(ANY_T, operands.get(i), frame, heap); } return semantics.Array(elements); } public RValue executeArrayRange(Expr.ArrayRange expr, CallStack frame, Heap heap) { int start = executeExpression(INT_T, expr.getFirstOperand(), frame, heap).intValue(); int end = executeExpression(INT_T, expr.getSecondOperand(), frame, heap).intValue(); if (end < start) { throw new RuntimeError(WyilFile.RUNTIME_NEGATIVE_RANGE_FAILURE, frame, expr); } RValue[] elements = new RValue[end - start]; for (int i = start; i < end; ++i) { elements[i - start] = semantics.Int(BigInteger.valueOf(i)); } return semantics.Array(elements); } public RValue executeArrayUpdate(Expr.ArrayUpdate expr, CallStack frame, Heap heap) { RValue.Array array = executeExpression(ARRAY_T, expr.getFirstOperand(), frame, heap); RValue.Int index = executeExpression(INT_T, expr.getSecondOperand(), frame, heap); // Sanity check access checkArrayBounds(array, index, frame, expr.getSecondOperand()); // Evaluate new element RValue value = executeExpression(ANY_T, expr.getThirdOperand(), frame, heap); // Update the array return array.write(index, value); } public RValue executeNew(Expr.New expr, CallStack frame, Heap heap) { // Evaluate operand RValue initialiser = executeExpression(ANY_T, expr.getOperand(), frame, heap); // Allocate operand into heap int address = heap.alloc(initialiser); // Done return semantics.Reference(address); } public RValue executeOld(Expr.Old expr, CallStack frame, Heap heap) { // Extract prestate (if present) Heap oldHeap = (Heap) frame.getLocal(OLD); // if (oldHeap == null) { throw new RuntimeException("internal failure --- invalid old heap"); } // return executeExpression(ANY_T, expr.getOperand(), frame, oldHeap); } public RValue executeDereference(Expr.Dereference expr, CallStack frame, Heap heap) { // Evaluate operand RValue.Reference ref = executeExpression(REF_T, expr.getOperand(), frame, heap); // int address = ref.deref(); // Sanity check if (address >= 0 && address < heap.size()) { return heap.read(address); } else { throw new RuntimeError(WyilFile.RUNTIME_FAULT, frame, expr.getOperand()); } } public RValue executeFieldDereference(Expr.FieldDereference expr, CallStack frame, Heap heap) { // Evaluate operand RValue.Reference ref = executeExpression(REF_T, expr.getOperand(), frame, heap); // Extract target RValue val = heap.read(ref.deref()); // Extract record value RValue.Record rec = checkType(val, expr, RECORD_T); // Read the given field return rec.read(expr.getField()); } public RValue executeLambdaAccess(Expr.LambdaAccess expr, CallStack frame, Heap heap) { // Locate the function or method body in order to execute it Decl.Callable decl = expr.getLink().getTarget(); // if (decl instanceof Decl.FunctionOrMethod) { Decl.FunctionOrMethod fm = (Decl.FunctionOrMethod) decl; // Clone frame to ensure it executes in this exact environment. return semantics.Lambda(decl, frame.clone()); } else { throw new Syntactic.Exception("cannot take address of property", null, expr); } } private RValue executeLambdaDeclaration(Decl.Lambda decl, CallStack frame, Heap heap) { // FIXME: this needs a clone of the frame? Otherwise, it's just // executing in the later environment. return semantics.Lambda(decl, frame.clone()); } // ============================================================= // 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 expressions * @param frame * @return */ private RValue[] executeExpressions(Tuple expressions, CallStack frame, Heap heap) { RValue[] results = new RValue[expressions.size()]; for (int i = 0; i != expressions.size(); ++i) { results[i] = executeExpression(ANY_T, expressions.get(i), frame, heap); } return results; } /** * 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 RValue executeIndirectInvoke(Expr.IndirectInvoke expr, CallStack frame, Heap heap) { RValue.Lambda src = executeExpression(LAMBDA_T, expr.getSource(), frame, heap); RValue[] arguments = executeExpressions(expr.getArguments(), frame, heap); // Extract concrete type Type.Callable type = src.getType(); // Check parameter type invariants checkTypeInvariants(type.getParameter(), arguments, expr.getArguments(), frame, heap); // Here we supply the enclosing frame when the lambda was created. // The reason for this is that the lambda may try to access enclosing // variables in the scope it was created. return src.execute(this, arguments, heap, expr); } /** * 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 * @throws ResolutionError */ private RValue executeInvoke(Expr.Invoke expr, CallStack frame, Heap heap) { // Resolve function or method being invoked to a concrete declaration Decl.Callable decl = expr.getLink().getTarget(); // Evaluate argument expressions RValue[] arguments = executeExpressions(expr.getOperands(), frame, heap); // Extract the concrete type Type.Callable type = expr.getBinding().getConcreteType(); // Check type invariants checkTypeInvariants(type.getParameter(), arguments, expr.getOperands(), frame, heap); // Invoke the function or method in question // FIXME: could potentially optimise this by calling execute with decl directly. // This currently fails for external symbols which are represented as // prototypes. return execute(decl.getQualifiedName(), decl.getType(), frame, heap, arguments, expr); } // ============================================================= // Constants // ============================================================= public void checkArrayBounds(RValue.Array array, RValue.Int index, CallStack frame, Syntactic.Item context) { int len = array.length().intValue(); int idx = index.intValue(); if (idx < 0) { throw new RuntimeError(WyilFile.RUNTIME_BELOWBOUNDS_INDEX_FAILURE, frame, context); } else if (idx >= len) { throw new RuntimeError(WyilFile.RUNTIME_ABOVEBOUNDS_INDEX_FAILURE, frame, context); } } public void checkDivisionByZero(RValue.Int value, CallStack frame, Syntactic.Item context) { if (value.intValue() == 0) { throw new RuntimeError(WyilFile.RUNTIME_DIVIDEBYZERO_FAILURE, frame, context); } } public void checkNonNegative(RValue.Int value, CallStack frame, Syntactic.Item context) { if (value.intValue() < 0) { throw new RuntimeError(WyilFile.RUNTIME_NEGATIVE_EXPONENT_FAILURE, frame, context); } } public void checkTypeInvariants(Type variables, RValue[] values, Tuple rvals, CallStack frame, Heap heap) { // This is a bit tricky because we have to account for multi-expressions which, // as always, are annoying. int index = 0; for (int i = 0; i != rvals.size(); ++i) { Expr rval = rvals.get(i); checkTypeInvariants(variables.dimension(index), values[index++], frame, heap, rval); } } public void checkTypeInvariants(Type type, RValue value, CallStack frame, Heap heap, Syntactic.Item context) { if (value.is(type, frame, heap).boolValue() == false) { throw new RuntimeError(WyilFile.RUNTIME_TYPEINVARIANT_FAILURE, frame, context); } } /** * 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(int code, CallStack frame, Heap heap, Tuple invariants, Syntactic.Item context) { for (int i = 0; i != invariants.size(); ++i) { Expr invariant = invariants.get(i); // Execute invariant RValue.Bool b = executeExpression(BOOL_T, invariant, frame, heap); // Check whether it holds or not if (b == RValue.False) { if (context == null) { throw new RuntimeError(code, frame, invariant); } else { throw new RuntimeError(code, frame, context, invariant); } } } } /** * 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 checkPrecondition(int code, CallStack frame, Heap heap, Tuple invariants, Syntactic.Item context) { for (int i = 0; i != invariants.size(); ++i) { Expr invariant = invariants.get(i); // Execute invariant RValue.Bool b = executeExpression(BOOL_T, invariant, frame, heap); // Check whether it holds or not if (b == RValue.False) { if (context != null) { throw new RuntimeError(code, frame, context, invariant); } else { throw new RuntimeError(code, frame, invariant); } } } } /** * 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(int code, CallStack frame, Heap heap, Expr... invariants) { for (int i = 0; i != invariants.length; ++i) { Expr invariant = invariants[i]; // Execute invariant RValue.Bool b = executeExpression(BOOL_T, invariant, frame, heap); // Check whether it holds or not if (b == RValue.False) { throw new RuntimeError(code, frame, invariant); } } } /** * 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 checkType(RValue operand, Syntactic.Item context, Class... 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 throw an error if (operand == null) { throw new Syntactic.Exception("null operand", null, context); } else { throw new Syntactic.Exception( "operand returned " + operand.getClass().getName() + ", expecting one of " + Arrays.toString(types), null, context); } } private void checkForTimeout(CallStack frame) { long time = System.currentTimeMillis(); if (time > frame.getTimeout()) { throw new TimeoutException("timeout!"); } } /** * This method is provided to properly handled positions which should be dead * code. * * @param context --- Context in which bytecodes are executed */ private T deadCode(Syntactic.Item element) { // FIXME: do more here throw new RuntimeException("internal failure --- dead code reached"); } private static final Identifier OLD = new Identifier("old"); private static final Class ANY_T = RValue.class; private static final Class BOOL_T = RValue.Bool.class; private static final Class BYTE_T = RValue.Byte.class; private static final Class INT_T = RValue.Int.class; private static final Class REF_T = RValue.Reference.class; protected static final Class ARRAY_T = RValue.Array.class; private static final Class RECORD_T = RValue.Record.class; private static final Class LAMBDA_T = RValue.Lambda.class; private static final Class TUPLE_T = RValue.Tuple.class; public final static class RuntimeError extends Syntactic.Exception { private final int code; private final CallStack frame; public RuntimeError(int code, CallStack frame, Syntactic.Item element, Syntactic.Item... context) { super(ErrorMessages.getErrorMessage(code, new Tuple<>(context)), extractEntry(element), element); this.code = code; this.frame = frame; } public int getErrorCode() { return code; } public CallStack getFrame() { return frame; } private static Syntactic.Heap extractEntry(Syntactic.Item item) { // FIXME: this feels like a hack Syntactic.Heap h = item.getHeap(); if (h instanceof WyilFile) { return (h); } else { return null; } } public Trie getSource() { Decl.Unit unit = getElement().getAncestor(Decl.Unit.class); String nameStr = unit.getName().toString().replace("::", "/"); return Trie.fromString(nameStr); } } public final static class TimeoutException extends RuntimeException { /** * */ private static final long serialVersionUID = 1L; public TimeoutException(String msg) { super(msg); } } public final static class Heap extends ConcreteSemantics.RValue { private final HashMap statics; private final ArrayList values; public Heap() { this.values = new ArrayList<>(); this.statics = new HashMap<>(); } public Heap(Map statics, Collection values) { this.statics = new HashMap<>(statics); this.values = new ArrayList<>(values); } public int alloc(RValue value) { int address = values.size(); values.add(value); return address; } public int size() { return values.size(); } public void dealloc(int address) { values.set(address, null); } public RValue read(int address) { return values.get(address); } public RValue read(QualifiedName name) { return statics.get(name); } public RValue write(int address, RValue val) { return values.set(address, val); } public RValue write(QualifiedName name, RValue val) { return statics.put(name, val); } @Override public Heap clone() { return new Heap(statics, values); } @Override public Value toValue() { throw new UnsupportedOperationException(); } } public final class CallStack { private CallStack parent; private long timeout; private final HashMap locals; private final Decl.Named context; public CallStack() { this.parent = null; this.timeout = Long.MAX_VALUE; this.locals = new HashMap<>(); this.context = null; } private CallStack(CallStack parent, Decl.Named context) { this(parent, new HashMap<>(), context); } private CallStack(CallStack parent, HashMap locals, Decl.Named context) { this.parent = parent; this.timeout = parent.timeout; this.context = context; this.locals = locals; } public int depth() { if (parent == null) { return 1; } else { return parent.depth() + 1; } } public long getTimeout() { return timeout; } public RValue getLocal(Identifier name) { return locals.get(name); } public void putLocal(Identifier name, RValue value) { locals.put(name, value); } public Map getLocals() { return locals; } public CallStack enter(Decl.Named context) { CallStack cs = new CallStack(this, context); // When a property is called, we cannot tell whether it requires a prestate or // not. Therefore, we copy over any existing prestate. if (context instanceof Decl.Variant) { cs.putLocal(OLD, locals.get(OLD)); } return cs; } public T execute(Class expected, Expr expr, CallStack frame, Heap heap) { return Interpreter.this.executeExpression(expected, expr, frame, heap); } public CallStack setTimeout(long timeout) { if (timeout != Long.MAX_VALUE) { long start = System.currentTimeMillis(); this.timeout = start + timeout; } return this; } @Override public CallStack clone() { CallStack frame = new CallStack(this, new HashMap<>(locals), this.context); // Reset parent frame.parent = this.parent; return frame; } public StackFrame[] toStackFrame() { if (context == null) { return new StackFrame[0]; } else { Value[] arguments; // switch (context.getOpcode()) { case DECL_function: case DECL_method: case DECL_property: case DECL_variant: { Decl.Callable fm = (Decl.Callable) context; Tuple parameters = fm.getParameters(); arguments = new Value[parameters.size()]; for (int i = 0; i != arguments.length; ++i) { Decl.Variable parameter = parameters.get(i); // FIXME: why is this needed? if (getLocal(parameter.getName()) != null) { arguments[i] = getLocal(parameter.getName()).toValue(); } else { arguments[i] = new Value.Null(); } } break; } case DECL_rectype: case DECL_type: { Decl.Type t = (Decl.Type) context; Decl.Variable parameter = t.getVariableDeclaration(); arguments = new Value[1]; arguments[0] = getLocal(parameter.getName()).toValue(); break; } case DECL_staticvar: { arguments = new Value[0]; break; } default: throw new IllegalArgumentException("unknown context: " + context.getQualifiedName()); } // Extract parent stack frame (if applicable) StackFrame[] sf = parent != null ? parent.toStackFrame() : new StackFrame[0]; // Append new item return ArrayUtils.append(new StackFrame(context, new Tuple<>(arguments)), sf); } } } /** * An enclosing scope captures the nested of declarations, blocks and other * staments (e.g. loops). It is used to store information associated with these * things such they can be accessed further down the chain. It can also be used * to propagate information up the chain (for example, the environments arising * from a break or continue statement). * * @author David J. Pearce * */ protected abstract static class EnclosingScope { private final EnclosingScope parent; public EnclosingScope(EnclosingScope parent) { this.parent = parent; } /** * Get the innermost enclosing block of a given kind. For example, when * processing a return statement we may wish to get the enclosing function or * method declaration such that we can type check the return types. * * @param kind */ public T getEnclosingScope(Class kind) { if (kind.isInstance(this)) { return (T) this; } else if (parent != null) { return parent.getEnclosingScope(kind); } else { // FIXME: better error propagation? return null; } } } /** * Represents the enclosing scope for a function, method or property * declaration. * * @author David J. Pearce * */ private static class CallableScope extends EnclosingScope { /** * The declaration being invoked */ private final Decl.Callable context; public CallableScope(Decl.Callable context) { super(null); this.context = context; } public Decl.Callable getContext() { return context; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy