View Javadoc

1   /*
2    * $Id: InstructionDecoder.java,v 1.14 2005/10/24 16:04:43 weiju Exp $
3    * 
4    * Created on 24.09.2005
5    * Copyright 2005 by Wei-ju Wu
6    *
7    * This file is part of The Z-machine Preservation Project (ZMPP).
8    *
9    * ZMPP is free software; you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as published by
11   * the Free Software Foundation; either version 2 of the License, or
12   * (at your option) any later version.
13   *
14   * ZMPP is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   *
19   * You should have received a copy of the GNU General Public License
20   * along with ZMPP; if not, write to the Free Software
21   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22   */
23  package org.zmpp.vm;
24  
25  import org.zmpp.base.MemoryReadAccess;
26  import org.zmpp.vm.AbstractInstruction.InstructionForm;
27  import org.zmpp.vm.AbstractInstruction.OperandCount;
28  
29  /***
30   * The instruction decoder decodes an instruction at a specified address.
31   * 
32   * @author Wei-ju Wu
33   * @version 1.0
34   */
35  public class InstructionDecoder {
36      
37    /***
38     * The memory access object.
39     */
40    private MemoryReadAccess memaccess;
41    
42    /***
43     * The machine state object.
44     */
45    private Machine machineState;
46    
47    /***
48     * Constructor.
49     * 
50     * @param memaccess the memory access object
51     */
52    public InstructionDecoder(Machine machineState,
53                              MemoryReadAccess memaccess) {
54    
55      this.memaccess = memaccess;
56      this.machineState = machineState;
57    }
58    
59    /***
60     * Decode the instruction at the specified address.
61     * 
62     * @param instructionAddress the current instruction's address
63     * @return the instruction at the specified address
64     */
65    public AbstractInstruction decodeInstruction(int instructionAddress) {
66    
67      AbstractInstruction info = createBasicInstructionInfo(instructionAddress);
68      int currentAddress = extractOperands(info, instructionAddress);
69      if (info.getInstructionForm() == InstructionForm.VARIABLE
70          && info.getOperandCount() == OperandCount.C2OP) {
71        
72        AbstractInstruction info2 =
73          new LongInstruction(machineState, OperandCount.VAR, info.getOpcode());
74        
75        for (int i = 0; i < info.getNumOperands(); i++) {
76        
77          info2.addOperand(info.getOperand(i));
78        }
79        info = info2;
80      }
81      currentAddress = extractStoreVariable(info, currentAddress);
82      currentAddress = extractBranchOffset(info, currentAddress);
83      info.setLength(currentAddress - instructionAddress);    
84      return info;
85    }
86    
87    // ***********************************************************************
88    // ****** Private functions
89    // ******************************************
90  
91    /***
92     * Create the basic information about the current instruction to be
93     * decoded. The returned object contains the instruction form, the
94     * operand count type and possibly, the opcode, if it is not an extended
95     * opcode.
96     * 
97     * @param the instruction's start address
98     * @return a DefaultInstructionInfo object with basic information
99     */
100   private AbstractInstruction createBasicInstructionInfo(int instructionAddress) {
101     
102     OperandCount operandCount;
103     int opcode;
104     short firstByte = memaccess.readUnsignedByte(instructionAddress);
105     
106     // Determine form and operand count type
107     if (0x00 <= firstByte && firstByte <= 0x7f) {
108       
109       opcode = firstByte & 0x1f; // Bottom five bits contain the opcode number
110       operandCount = OperandCount.C2OP;
111       return new LongInstruction(machineState, opcode);
112 
113     } else if (0x80 <= firstByte && firstByte <= 0xbf) {
114       
115       opcode = firstByte & 0x0f; // Bottom four bits contain the opcode number
116       operandCount = (firstByte >= 0xb0) ? OperandCount.C0OP :
117                                            OperandCount.C1OP;
118       if (operandCount == OperandCount.C0OP) {
119         
120         // Special case: print and print_ret are classified as C0OP, but
121         // in fact have a string literal as their parameter
122         if (opcode == PrintLiteralInstruction.OP_PRINT
123             || opcode == PrintLiteralInstruction.OP_PRINT_RET) {
124           
125           return new PrintLiteralInstruction(machineState, opcode, memaccess,
126                                              instructionAddress);
127         }
128         return new Short0Instruction(machineState, opcode);
129       }
130       else
131         return new Short1Instruction(machineState, opcode);
132       
133     } else {
134       
135       opcode = firstByte & 0x1f; // Bottom five bits contain the opcode number
136       operandCount = (firstByte >= 0xe0) ? OperandCount.VAR : OperandCount.C2OP;
137       return new VariableInstruction(machineState, operandCount, opcode);
138     }
139   }
140   
141   /***
142    * Extracts the operands from the instruction data. At this step of
143    * decoding the some basic information about the instruction is available
144    * and could be used for extraction of parameters.
145    * 
146    * @param info the instruction info object to write to
147    * @param instructionAddress the instruction address
148    * @return the current address in decoding
149    */
150   private int extractOperands(AbstractInstruction info, int instructionAddress) {
151 
152     int currentAddress = instructionAddress;
153     
154     if (info.getInstructionForm() == InstructionForm.SHORT) {
155       
156       if (info.getOperandCount() == OperandCount.C1OP) {
157         
158         short firstByte = memaccess.readUnsignedByte(instructionAddress);
159         /*
160         short secondByte = memaccess.readUnsignedByte(instructionAddress + 1);
161         System.out.printf("opcode: %x, firstByte: %x, secondByte: %x\n",
162             info.getOpcode(), firstByte, secondByte);
163             */
164         byte optype = (byte) ((firstByte & 0x30) >> 4);
165         //System.out.printf("optype: %x\n", optype);
166         
167         currentAddress = extractOperand(info, optype, instructionAddress + 1);
168         
169       } else {
170         
171         // 0 operand instructions of course still occupy 1 byte
172         currentAddress = instructionAddress + 1;
173       }
174     } else if (info.getInstructionForm() == InstructionForm.LONG) {
175 
176       short firstByte = memaccess.readUnsignedByte(instructionAddress);      
177       //System.out.printf("long opcode: %x\n", firstByte);
178       byte optype1 = ((firstByte & 0x40) > 0) ? Operand.TYPENUM_VARIABLE :
179                                                 Operand.TYPENUM_SMALL_CONSTANT;
180       byte optype2 = ((firstByte & 0x20) > 0) ? Operand.TYPENUM_VARIABLE :
181                                                 Operand.TYPENUM_SMALL_CONSTANT;
182       currentAddress = extractOperand(info, optype1, instructionAddress + 1);
183       currentAddress = extractOperand(info, optype2, currentAddress);
184       
185     } else if (info.getInstructionForm() == InstructionForm.VARIABLE){
186     
187       short secondByte = memaccess.readUnsignedByte(instructionAddress + 1);
188           
189       // operand types in next byte(s)
190       currentAddress = instructionAddress + 2;    
191       currentAddress = extractOperandsWithTypeByte(info, secondByte,
192                                                  currentAddress);
193     }
194     return currentAddress;
195   }
196   
197   /***
198    * Extract operands for the given optype byte value starting at the given
199    * decoding address. This is outfactored in order to be called at least two
200    * times. The generated operands are added to the specified operand list.
201    * 
202    * @param info the InstructionInfo to add to
203    * @param optypeByte the optype byte
204    * @param currentAddress the current decoding address
205    * @return the new decoding address after extracting the operands
206    */
207   private int extractOperandsWithTypeByte(AbstractInstruction info,
208                                           int optypeByte, int currentAddress) {
209     
210     int nextAddress = currentAddress;
211     int oldNumOperands;
212     byte optype = (byte) ((optypeByte >> 6) & 0x03);
213     
214     for (int i = 0; i < 4; i++) {
215       
216       optype = (byte) ((optypeByte >> ((3 - i) * 2)) & 0x03);
217       oldNumOperands = info.getNumOperands();
218       nextAddress = extractOperand(info, optype, nextAddress);
219       if (info.getNumOperands() == oldNumOperands) break;
220     }
221     return nextAddress;
222   }
223   
224   /***
225    * Extracts an operands from the current address with the specified operand
226    * type number. If the type is unknown or OMITTED, no operand will be
227    * added and the returned address will be equal to currentAddress.
228    * 
229    * @param info the instruction info to add to
230    * @param optype the operand type number
231    * @param currentAddress the current address in the instruction
232    * @return the next address
233    */
234   private int extractOperand(AbstractInstruction info, byte optype,
235                              int currentAddress) {
236     //if (info.getInstructionForm() == InstructionForm.SHORT)
237     //  System.out.printf("extractOperand() from address: %x, optype: %d\n",
238     //      currentAddress, optype);
239     
240     int nextAddress = currentAddress;
241     if (optype == Operand.TYPENUM_LARGE_CONSTANT) {
242       
243       info.addOperand(new Operand(optype,
244           memaccess.readShort(nextAddress)));
245       nextAddress += 2;
246       
247     } else if (optype == Operand.TYPENUM_VARIABLE
248         || optype == Operand.TYPENUM_SMALL_CONSTANT) {
249       
250       info.addOperand(new Operand(optype,
251           memaccess.readUnsignedByte(nextAddress)));
252       
253       nextAddress += 1; 
254     }
255     return nextAddress;
256   }
257   
258   /***
259    * Extracts a store variable if this instruction has one.
260    * 
261    * @param info the instruction info
262    * @param currentAddress the current address in the decoding
263    * @return the current decoding address after extraction
264    */
265   private int extractStoreVariable(AbstractInstruction info, int currentAddress) {
266     
267     if (info.storesResult()) {
268       
269       info.setStoreVariable(memaccess.readUnsignedByte(currentAddress));
270       return currentAddress + 1;
271     }
272     return currentAddress;
273   }
274   
275   /***
276    * Extracts the branch offset if this instruction is a branch.
277    * 
278    * @param info the instruction info object
279    * @param currentAddress the current address in decoding processing
280    * @return the current decoding address after extraction
281    */
282   private int extractBranchOffset(AbstractInstruction info, int currentAddress) {
283     
284     if (info.isBranch()) {
285       
286       short offsetByte1 = memaccess.readUnsignedByte(currentAddress);
287       info.setBranchIfTrue((offsetByte1 & 0x80) > 0);
288       //System.out.printf("offsetByte1: %x\n", offsetByte1);
289       
290       // Bit 6 set -> only one byte needs to be read
291       if ((offsetByte1 & 0x40) > 0) {
292         
293         info.setBranchOffset((short) (offsetByte1 & 0x3f));
294         return currentAddress + 1;
295         
296       } else {
297      
298         short offsetByte2 = memaccess.readUnsignedByte(currentAddress + 1);
299         short offset;
300         
301         if ((offsetByte1 & 0x20) != 0) { // Bit 14 set = negative
302           
303           offset = (short)
304             ((0xC000 | ((offsetByte1 << 8) | (offsetByte2 & 0xff))));
305           
306         } else {
307           
308           offset = (short)
309             (((offsetByte1 & 0x3f) << 8) | (offsetByte2 & 0xff));
310         }
311         //System.out.printf("14-bit offset, offsetByte2: %x, offset = %x\n",
312         //    offsetByte2, offset);
313         info.setBranchOffset(offset);
314         return currentAddress + 2;
315       }
316     }
317     return currentAddress;
318   }
319 }