/*
* $Id: CpuImpl.java 536 2008-02-19 06:03:27Z weiju $
*
* Created on 2006/02/14
* Copyright 2005-2008 by Wei-ju Wu
* This file is part of The Z-machine Preservation Project (ZMPP).
*
* ZMPP is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ZMPP is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ZMPP. If not, see <http://www.gnu.org/licenses/>.
*/
package org.zmpp.vm;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.zmpp.base.Interruptable;
import org.zmpp.base.Memory;
import org.zmpp.encoding.ZsciiString;
import org.zmpp.vmutil.FastShortStack;
public class CpuImpl implements Cpu, Interruptable {
/**
* The stack size is now 64 K.
*/
private static final int STACKSIZE = 32768;
/**
* The machine object.
*/
private Machine machine;
/**
* This machine's current program counter.
*/
private int programCounter;
/**
* This machine's global stack.
*/
private FastShortStack stack;
/**
* The routine info.
*/
private List<RoutineContext> routineContextStack;
/**
* The start of global variables.
*/
private int globalsAddress;
/**
* The instruction decoder.
*/
private InstructionDecoder decoder;
/**
* This flag indicates the run status.
*/
private boolean running;
public CpuImpl(final Machine machine, final InstructionDecoder decoder) {
super();
this.machine = machine;
this.decoder = decoder;
this.running = true;
}
public void reset() {
final GameData gamedata = machine.getGameData();
decoder.initialize(machine, gamedata.getMemory());
stack = new FastShortStack(STACKSIZE);
routineContextStack = new ArrayList<RoutineContext>();
globalsAddress = gamedata.getStoryFileHeader().getGlobalsAddress();
if (gamedata.getStoryFileHeader().getVersion() == 6) {
// Call main function in version 6
call(gamedata.getStoryFileHeader().getProgramStart(), 0, new short[0],
(short) 0);
} else {
programCounter = gamedata.getStoryFileHeader().getProgramStart();
}
}
/**
* {@inheritDoc}
*/
public int getProgramCounter() {
return programCounter;
}
/**
* {@inheritDoc}
*/
public void setProgramCounter(final int address) {
programCounter = address;
}
public void incrementProgramCounter(final int offset) {
programCounter += offset;
}
/**
* {@inheritDoc}
*/
public Instruction nextStep() {
return decoder.decodeInstruction(getProgramCounter());
}
/**
* {@inheritDoc}
*/
public int translatePackedAddress(final int packedAddress,
final boolean isCall) {
// Version specific packed address translation
final GameData gamedata = machine.getGameData();
switch (gamedata.getStoryFileHeader().getVersion()) {
case 1:
case 2:
case 3:
return packedAddress * 2;
case 4:
case 5:
return packedAddress * 4;
case 6:
case 7:
return packedAddress * 4 + 8
* (isCall ? gamedata.getStoryFileHeader().getRoutineOffset()
: gamedata.getStoryFileHeader().getStaticStringOffset());
case 8:
default:
return packedAddress * 8;
}
}
/**
* {@inheritDoc}
*/
public int computeBranchTarget(final short offset,
final int instructionLength) {
return getProgramCounter() + instructionLength + offset - 2;
}
/**
* {@inheritDoc}
*/
public void halt(final String errormsg) {
machine.getOutput().print(new ZsciiString(errormsg));
running = false;
}
/**
* {@inheritDoc}
*/
public boolean isRunning() {
return running;
}
/**
* {@inheritDoc}
*/
public void setRunning(final boolean flag) {
running = flag;
}
// ********************************************************************
// ***** Stack operations
// ***************************************
/**
* {@inheritDoc}
*/
public int getStackPointer() {
return stack.getStackPointer();
}
/**
* Sets the global stack pointer to the specified value. This might pop off
* several values from the stack.
*
* @param stackpointer the new stack pointer value
*/
private void setStackPointer(final int stackpointer) {
// remove the last diff elements
final int diff = stack.getStackPointer() - stackpointer;
for (int i = 0; i < diff; i++) {
stack.pop();
}
}
/**
* {@inheritDoc}
*/
public short getStackTopElement() {
if (stack.size() > 0) {
return stack.top();
}
return -1;
}
/**
* {@inheritDoc}
*/
public void setStackTopElement(final short value) {
stack.replaceTopElement(value);
}
/**
* {@inheritDoc}
*/
public short getStackElement(final int index) {
return stack.getValueAt(index);
}
/**
* {@inheritDoc}
*/
public short popUserStack(int userstackAddress) {
Memory memory = machine.getGameData().getMemory();
int numFreeSlots = memory.readUnsignedShort(userstackAddress);
numFreeSlots++;
memory.writeUnsignedShort(userstackAddress, numFreeSlots);
return memory.readShort(userstackAddress + (numFreeSlots * 2));
}
/**
* {@inheritDoc}
*/
public boolean pushUserStack(int userstackAddress, short value) {
Memory memory = machine.getGameData().getMemory();
int numFreeSlots = memory.readUnsignedShort(userstackAddress);
if (numFreeSlots > 0) {
memory.writeShort(userstackAddress + (numFreeSlots * 2), value);
memory.writeUnsignedShort(userstackAddress, numFreeSlots - 1);
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
public short getVariable(final int variableNumber) {
final Cpu.VariableType varType = getVariableType(variableNumber);
if (varType == Cpu.VariableType.STACK) {
if (stack.size() == getInvocationStackPointer()) {
//throw new IllegalStateException("stack underflow error");
System.err.println("stack underflow error");
return 0;
} else {
return stack.pop();
}
} else if (varType == Cpu.VariableType.LOCAL) {
final int localVarNumber = getLocalVariableNumber(variableNumber);
checkLocalVariableAccess(localVarNumber);
return getCurrentRoutineContext().getLocalVariable(localVarNumber);
} else { // GLOBAL
return machine.getGameData().getMemory().readShort(globalsAddress
+ (getGlobalVariableNumber(variableNumber) * 2));
}
}
/**
* Returns the current invocation stack pointer.
*
* @return the invocation stack pointer
*/
private int getInvocationStackPointer() {
return getCurrentRoutineContext() == null ? 0
: getCurrentRoutineContext().getInvocationStackPointer();
}
/**
* {@inheritDoc}
*/
public void setVariable(final int variableNumber, final short value) {
final Cpu.VariableType varType = getVariableType(variableNumber);
if (varType == Cpu.VariableType.STACK) {
stack.push(value);
} else if (varType == Cpu.VariableType.LOCAL) {
final int localVarNumber = getLocalVariableNumber(variableNumber);
checkLocalVariableAccess(localVarNumber);
getCurrentRoutineContext().setLocalVariable(localVarNumber, value);
} else {
machine.getGameData().getMemory().writeShort(globalsAddress
+ (getGlobalVariableNumber(variableNumber) * 2), value);
}
}
/**
* Returns the variable type for the given variable number.
*
* @param variableNumber the variable number
* @return STACK if stack variable, LOCAL if local variable, GLOBAL if
* global
*/
public static Cpu.VariableType getVariableType(final int variableNumber) {
if (variableNumber == 0) {
return Cpu.VariableType.STACK;
} else if (variableNumber < 0x10) {
return Cpu.VariableType.LOCAL;
} else {
return Cpu.VariableType.GLOBAL;
}
}
/**
* {@inheritDoc}
*/
public void pushRoutineContext(final RoutineContext routineContext) {
routineContext.setInvocationStackPointer(getStackPointer());
routineContextStack.add(routineContext);
}
/**
* {@inheritDoc}
*/
public void popRoutineContext(final short returnValue) {
if (routineContextStack.size() > 0) {
final RoutineContext popped
= routineContextStack.remove(routineContextStack.size() - 1);
popped.setReturnValue(returnValue);
// Restore stack pointer and pc
setStackPointer(popped.getInvocationStackPointer());
setProgramCounter(popped.getReturnAddress());
final int returnVariable = popped.getReturnVariable();
if (returnVariable != RoutineContext.DISCARD_RESULT) {
setVariable(returnVariable, returnValue);
}
} else {
throw new IllegalStateException("no routine context active");
}
}
/**
* {@inheritDoc}
*/
public RoutineContext getCurrentRoutineContext() {
if (routineContextStack.size() == 0) {
return null;
}
return routineContextStack.get(routineContextStack.size() - 1);
}
/**
* {@inheritDoc}
*/
public List<RoutineContext> getRoutineContexts() {
return Collections.unmodifiableList(routineContextStack);
}
/**
* {@inheritDoc}
*/
public void setRoutineContexts(final List<RoutineContext> contexts) {
routineContextStack.clear();
for (RoutineContext context : contexts) {
routineContextStack.add(context);
}
}
/**
* This function is basically exposed to the debug application.
*
* @return the current routine stack pointer
*/
public int getRoutineStackPointer() {
return routineContextStack.size();
}
public RoutineContext call(final int packedRoutineAddress,
final int returnAddress, final short[] args, final short returnVariable) {
final int routineAddress
= translatePackedAddress(packedRoutineAddress, true);
final int numArgs = args == null ? 0 : args.length;
final RoutineContext routineContext = decodeRoutine(routineAddress);
// Sets the number of arguments
routineContext.setNumArguments(numArgs);
// Save return parameters
routineContext.setReturnAddress(returnAddress);
// Only if this instruction stores a result
if (returnVariable == RoutineContext.DISCARD_RESULT) {
routineContext.setReturnVariable(RoutineContext.DISCARD_RESULT);
} else {
routineContext.setReturnVariable(returnVariable);
}
// Set call parameters into the local variables
// if there are more parameters than local variables,
// those are thrown away
final int numToCopy = Math.min(routineContext.getNumLocalVariables(),
numArgs);
for (int i = 0; i < numToCopy; i++) {
routineContext.setLocalVariable(i, args[i]);
}
// save invocation stack pointer
routineContext.setInvocationStackPointer(getStackPointer());
// Pushes the routine context onto the routine stack
pushRoutineContext(routineContext);
// Jump to the address
setProgramCounter(routineContext.getStartAddress());
return routineContext;
}
// ************************************************************************
// ****** Private functions
// ************************************************
/**
* Decodes the routine at the specified address.
*
* @param routineAddress the routine address
* @return a RoutineContext object
*/
private RoutineContext decodeRoutine(final int routineAddress) {
final GameData gamedata = machine.getGameData();
final Memory memory = gamedata.getMemory();
final int numLocals = memory.readUnsignedByte(routineAddress);
final short[] locals = new short[numLocals];
int currentAddress = routineAddress + 1;
if (gamedata.getStoryFileHeader().getVersion() <= 4) {
// Only story files <= 4 actually store default values here,
// after V5 they are assumed as being 0 (standard document 1.0, S.5.2.1)
for (int i = 0; i < numLocals; i++) {
locals[i] = memory.readShort(currentAddress);
currentAddress += 2;
}
}
final RoutineContext info = new RoutineContext(currentAddress, numLocals);
for (int i = 0; i < numLocals; i++) {
info.setLocalVariable(i, locals[i]);
}
return info;
}
/**
* Returns the local variable number for a specified variable number.
*
* @param variableNumber the variable number in an operand (0x01-0x0f)
* @return the local variable number
*/
private int getLocalVariableNumber(final int variableNumber) {
return variableNumber - 1;
}
/**
* Returns the global variable for the specified variable number.
*
* @param variableNumber a variable number (0x10-0xff)
* @return the global variable number
*/
private int getGlobalVariableNumber(final int variableNumber) {
return variableNumber - 0x10;
}
/**
* This function throws an exception if a non-existing local variable is
* accessed on the current routine context or no current routine context is
* set.
*
* @param localVariableNumber the local variable number
*/
private void checkLocalVariableAccess(final int localVariableNumber) {
if (routineContextStack.size() == 0) {
throw new IllegalStateException("no routine context set");
}
if (localVariableNumber >= getCurrentRoutineContext().getNumLocalVariables()) {
throw new IllegalStateException("access to non-existent local variable: "
+ localVariableNumber);
}
}
// ************************************************************************
// ****** Interrupt functions
// *************************************
/**
* The flag to indicate interrupt output.
*/
private boolean interruptDidOutput;
/**
* The flag to indicate interrupt execution.
*/
private boolean executeInterrupt;
/**
* {@inheritDoc}
*/
public boolean interruptDidOutput() {
return interruptDidOutput;
}
/**
* {@inheritDoc}
*/
public short callInterrupt(final int routineAddress) {
interruptDidOutput = false;
executeInterrupt = true;
final int originalRoutineStackSize = getRoutineContexts().size();
final RoutineContext routineContext = call(routineAddress,
machine.getCpu().getProgramCounter(),
new short[0], (short) RoutineContext.DISCARD_RESULT);
for (;;) {
final Instruction instr = nextStep();
instr.execute();
// check if something was printed
if (instr.isOutput()) {
interruptDidOutput = true;
}
if (getRoutineContexts().size() == originalRoutineStackSize) {
break;
}
}
executeInterrupt = false;
return routineContext.getReturnValue();
}
public void setInterruptRoutine(final int routineAddress) {
// TODO
}
/**
* Returns the interrupt status of the cpu object.
*
* @return the interrup status
*/
public boolean isExecutingInterrupt() {
return executeInterrupt;
}
}