/*
* $Id: DefaultInstructionDecoder.java 536 2008-02-19 06:03:27Z weiju $
*
* Created on 12/26/2005
* 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.instructions;
import java.util.HashMap;
import java.util.Map;
import org.zmpp.base.Memory;
import org.zmpp.instructions.AbstractInstruction.InstructionForm;
import org.zmpp.instructions.AbstractInstruction.OperandCount;
import org.zmpp.vm.Instruction;
import org.zmpp.vm.InstructionDecoder;
import org.zmpp.vm.Machine;
public class DefaultInstructionDecoder implements InstructionDecoder {
private Map<Integer, Instruction> instructionCache;
/**
* The Memory object.
*/
private Memory memory;
/**
* The machine state object.
*/
private Machine machine;
/**
* Constructor.
*
* @param memory the memory access object
*/
public DefaultInstructionDecoder() {
instructionCache = new HashMap<Integer, Instruction>();
}
public void initialize(final Machine machine,
final Memory memory) {
this.memory = memory;
this.machine = machine;
}
/**
* Decode the instruction at the specified address.
*
* @param instructionAddress the current instruction's address
* @return the instruction at the specified address
*/
public Instruction decodeInstruction(final int instructionAddress) {
final Integer key = Integer.valueOf(instructionAddress);
if (!instructionCache.containsKey(key)) {
AbstractInstruction info = createBasicInstructionInfo(instructionAddress);
int currentAddress = extractOperands(info, instructionAddress);
if (info.getInstructionForm() == InstructionForm.VARIABLE
&& info.getOperandCount() == OperandCount.C2OP) {
// Handle the VAR form of C2OP instructions here
final AbstractInstruction info2
= new LongInstruction(machine, OperandCount.VAR, info.getOpcode());
for (int i = 0; i < info.getNumOperands(); i++) {
info2.addOperand(info.getOperand(i));
}
info = info2;
}
currentAddress = extractStoreVariable(info, currentAddress);
currentAddress = extractBranchOffset(info, currentAddress);
info.setLength(currentAddress - instructionAddress);
//return info;
instructionCache.put(key, info);
}
return instructionCache.get(key);
}
// ***********************************************************************
// ****** Private functions
// ******************************************
/**
* Create the basic information about the current instruction to be decoded.
* The returned object contains the instruction form, the operand count type
* and possibly, the opcode, if it is not an extended opcode.
*
* @param the instruction's start address
* @return a DefaultInstructionInfo object with basic information
*/
private AbstractInstruction createBasicInstructionInfo(
final int instructionAddress) {
OperandCount operandCount;
int opcode;
final short firstByte = memory.readUnsignedByte(instructionAddress);
// Determine form and operand count type
if (firstByte == 0xbe) {
opcode = memory.readUnsignedByte(instructionAddress + 1);
return new ExtendedInstruction(machine, opcode);
} else if (0x00 <= firstByte && firstByte <= 0x7f) {
opcode = firstByte & 0x1f; // Bottom five bits contain the opcode number
return new LongInstruction(machine, opcode);
} else if (0x80 <= firstByte && firstByte <= 0xbf) {
opcode = firstByte & 0x0f; // Bottom four bits contain the opcode number
operandCount = (firstByte >= 0xb0) ? OperandCount.C0OP
: OperandCount.C1OP;
if (operandCount == OperandCount.C0OP) {
// Special case: print and print_ret are classified as C0OP, but
// in fact have a string literal as their parameter
if (opcode == PrintLiteralStaticInfo.OP_PRINT
|| opcode == PrintLiteralStaticInfo.OP_PRINT_RET) {
return new PrintLiteralInstruction(machine, opcode, memory,
instructionAddress);
}
return new Short0Instruction(machine, opcode);
} else {
return new Short1Instruction(machine, opcode);
}
} else {
opcode = firstByte & 0x1f; // Bottom five bits contain the opcode number
operandCount = (firstByte >= 0xe0) ? OperandCount.VAR : OperandCount.C2OP;
return new VariableInstruction(machine, operandCount, opcode);
}
}
/**
* Extracts the operands from the instruction data. At this step of decoding
* the some basic information about the instruction is available and could
* be used for extraction of parameters.
*
* @param info the instruction info object to write to
* @param instructionAddress the instruction address
* @return the current address in decoding
*/
private int extractOperands(final AbstractInstruction info,
final int instructionAddress) {
int currentAddress = instructionAddress;
if (info.getInstructionForm() == InstructionForm.SHORT) {
if (info.getOperandCount() == OperandCount.C1OP) {
final short firstByte = memory.readUnsignedByte(currentAddress);
final byte optype = (byte) ((firstByte & 0x30) >> 4);
currentAddress = extractOperand(info, optype, currentAddress + 1);
} else {
// 0 operand instructions of course still occupy 1 byte
currentAddress++;
}
} else if (info.getInstructionForm() == InstructionForm.LONG) {
final short firstByte = memory.readUnsignedByte(instructionAddress);
final byte optype1 = ((firstByte & 0x40) > 0) ? Operand.TYPENUM_VARIABLE
: Operand.TYPENUM_SMALL_CONSTANT;
final byte optype2 = ((firstByte & 0x20) > 0) ? Operand.TYPENUM_VARIABLE
: Operand.TYPENUM_SMALL_CONSTANT;
currentAddress = extractOperand(info, optype1, currentAddress + 1);
currentAddress = extractOperand(info, optype2, currentAddress);
} else if (info.getInstructionForm() == InstructionForm.VARIABLE) {
// The operand types start after the second opcode byte in EXT form,
// and after the first otherwise
currentAddress += (info.getOperandCount() == OperandCount.EXT) ? 2 : 1;
final short optypeByte1 = memory.readUnsignedByte(currentAddress++);
short optypeByte2 = 0;
// Extract more operands if necessary, if the opcode
// is call_vs2 or call_vn2 and there are four operands already,
// there is a need to check out the second op type byte
// (Standards document 1.0, S 4.4.3.1 and S 4.5.1)
// Note: we need to make sure that OperandCount is VAR, because
// ----- the opcode for CALL_VN2 overlaps with CALL_2N
boolean isVcall = false;
if (info.getOperandCount() == OperandCount.VAR
&& (info.getOpcode() == VariableStaticInfo.OP_CALL_VS2
|| info.getOpcode() == VariableStaticInfo.OP_CALL_VN2)) {
// There is a second op type byte
isVcall = true;
optypeByte2 = memory.readUnsignedByte(currentAddress++);
}
// Extract first operand half
currentAddress = extractOperandsWithTypeByte(info, optypeByte1,
currentAddress);
// Extract second operand half
if (isVcall && info.getNumOperands() == 4) {
currentAddress = extractOperandsWithTypeByte(info, optypeByte2,
currentAddress);
}
}
return currentAddress;
}
/**
* Extract operands for the given optype byte value starting at the given
* decoding address. This is outfactored in order to be called at least two
* times. The generated operands are added to the specified operand list.
*
* @param info the InstructionInfo to add to
* @param optypeByte the optype byte
* @param currentAddress the current decoding address
* @return the new decoding address after extracting the operands
*/
private int extractOperandsWithTypeByte(final AbstractInstruction info,
final int optypeByte,
final int currentAddress) {
int nextAddress = currentAddress;
int oldNumOperands;
byte optype = 0;
for (int i = 0; i < 4; i++) {
optype = (byte) ((optypeByte >> ((3 - i) * 2)) & 0x03);
oldNumOperands = info.getNumOperands();
nextAddress = extractOperand(info, optype, nextAddress);
if (info.getNumOperands() == oldNumOperands) {
break;
}
}
return nextAddress;
}
/**
* Extracts an operands from the current address with the specified operand
* type number. If the type is unknown or OMITTED, no operand will be added
* and the returned address will be equal to currentAddress.
*
* @param info the instruction info to add to
* @param optype the operand type number
* @param currentAddress the current address in the instruction
* @return the next address
*/
private int extractOperand(final AbstractInstruction info, final byte optype,
final int currentAddress) {
int nextAddress = currentAddress;
if (optype == Operand.TYPENUM_LARGE_CONSTANT) {
info.addOperand(new Operand(optype,
memory.readShort(nextAddress)));
nextAddress += 2;
} else if (optype == Operand.TYPENUM_VARIABLE
|| optype == Operand.TYPENUM_SMALL_CONSTANT) {
info.addOperand(new Operand(optype,
memory.readUnsignedByte(nextAddress)));
nextAddress += 1;
}
return nextAddress;
}
/**
* Extracts a store variable if this instruction has one.
*
* @param info the instruction info
* @param currentAddress the current address in the decoding
* @return the current decoding address after extraction
*/
private int extractStoreVariable(final AbstractInstruction info,
final int currentAddress) {
if (info.storesResult()) {
info.setStoreVariable(memory.readUnsignedByte(currentAddress));
return currentAddress + 1;
}
return currentAddress;
}
/**
* Extracts the branch offset if this instruction is a branch.
*
* @param info the instruction info object
* @param currentAddress the current address in decoding processing
* @return the current decoding address after extraction
*/
private int extractBranchOffset(final AbstractInstruction info,
final int currentAddress) {
if (info.isBranch()) {
final short offsetByte1 = memory.readUnsignedByte(currentAddress);
info.setBranchIfTrue((offsetByte1 & 0x80) > 0);
// Bit 6 set -> only one byte needs to be read
if ((offsetByte1 & 0x40) > 0) {
info.setBranchOffset((short) (offsetByte1 & 0x3f));
return currentAddress + 1;
} else {
final short offsetByte2
= memory.readUnsignedByte(currentAddress + 1);
short offset;
if ((offsetByte1 & 0x20) == 0x20) { // Bit 14 set = negative
offset = (short) ((0xC000 | ((offsetByte1 << 8) | (offsetByte2 & 0xff))));
} else {
offset = (short) (((offsetByte1 & 0x3f) << 8) | (offsetByte2 & 0xff));
}
info.setBranchOffset(offset);
return currentAddress + 2;
}
}
return currentAddress;
}
}