1 /**
2 Copyright: Copyright (c) 2013-2014 Andrey Penechko.
3 License: a$(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 module emulator.dcpu.memoryanalyzer;
8 
9 import std.array;
10 import std.algorithm : sort, find;
11 import std.conv : to;
12 import std..string : format;
13 import std.stdio : writeln, writefln;
14 
15 import emulator.dcpu.dcpu;
16 import emulator.dcpu.constants;
17 import emulator.dcpu.instruction;
18 import emulator.dcpu.execution;
19 
20 class MemoryAnalyzer(Cpu)
21 {
22 protected:
23 	Cpu* _dcpu;
24 
25 public:
26 
27 	MemoryMap memoryMap;
28 
29 	this(Cpu* dcpu)
30 	{
31 		_dcpu = dcpu;
32 	}
33 
34 	// PC "jumps" to 0 at cpu start
35 	enum ushort defaultEntryPoint = 0;
36 
37 	MemoryBlock* blockAtPos(ushort position)
38 	{
39 		auto blocks = find!"a.position <= b && b < a.position + a.length"
40 				(memoryMap.blocks, position);
41 		
42 		if (blocks.length) return blocks[0];
43 		
44 		return null;
45 	}
46 
47 	Label* labelAt(ushort position, LabelType type)
48 	{
49 		auto labels = find!"a.position == b"(memoryMap.labels, position);
50 
51 		if (labels.length)
52 		{
53 			if (labels[0].type == LabelType.label && type == LabelType.subroutine)
54 				labels[0].type = type;
55 
56 			return labels[0];
57 		}
58 
59 		auto newLabel = new Label(position, type);
60 
61 		writefln("New %s label at %04x ", type, position);
62 
63 		memoryMap.labels ~= newLabel;
64 
65 		return newLabel;
66 	}
67 
68 	void buildMemoryMap()
69 	{
70 		auto processQueue = Appender!(Transition*[])([]); // control flow transitions (JMP and set, add, sub pc)
71 
72 		processQueue ~= new Transition(0,
73 			labelAt(defaultEntryPoint, LabelType.label),
74 			TransitionType.entry,
75 			false);
76 
77 		void processTransition(Transition* transition)
78 		{
79 			ushort entryPoint = transition.target.position;
80 
81 			// Transition to an existing block
82 			if (auto blockFound = blockAtPos(entryPoint))
83 			{
84 				blockFound.transitionsIn ~= transition;
85 				transition.toBlock = blockFound;
86 				return;
87 			}
88 
89 			// New block
90 			auto block = new MemoryBlock(entryPoint);
91 			block.type = BlockType.code;
92 			memoryMap.blocks ~= block;
93 			transition.toBlock = block;
94 
95 			writefln("New block at %04x", entryPoint);
96 
97 			ushort pointer = entryPoint;
98 			Instruction instr;
99 			bool inCondition = false;
100 
101 			while (pointer >= entryPoint)
102 			{
103 				// No place to expand, other (code) block detected
104 				if (auto blockFound = blockAtPos(pointer))
105 				{
106 					blockFound.transitionsIn ~= transition;
107 					writefln("Block ended [No space] %04x..%04x\n",
108 						blockFound.position, blockFound.position+blockFound.length-1);
109 					return;
110 				}
111 
112 				// Get instruction at pointer
113 				instr = fetchAt(*_dcpu, pointer);
114 
115 				void onBlockEnd()
116 				{
117 					block.length = pointer + instr.size - block.position;
118 					block.lastInstr = pointer;
119 					writefln("Block ended [Jump] %04x..%04x\n",
120 						block.position, block.position+block.length-1);
121 				}
122 
123 				// Check instruction
124 				// If SET PC, literal that it is jump
125 				if (instr.operands == 2 && instr.operandB == 0x1c/*PC*/)
126 				{
127 					// Unconditional branching 
128 					if (instr.opcode == SET || instr.opcode == STI || instr.opcode == STD) // temp TODO: add, sub with literals
129 					{
130 						if (isOperandImmediate[instr.operandA])
131 						{
132 							ushort pc = cast(ushort)(pointer+1), sp = 0xFFFF; 
133 							ushort transitionTo = getOperandA(*_dcpu, instr.operandA, pc, sp).get();
134 
135 							// outcoming transition
136 							auto newTransition = new Transition(pointer,
137 								labelAt(transitionTo, LabelType.label),
138 								TransitionType.jump,
139 								inCondition,
140 								block);
141 							
142 							block.transitionsFrom ~= newTransition;
143 							processQueue ~= newTransition;
144 
145 							writeln(*newTransition);
146 						}
147 
148 						if (!inCondition)
149 						{
150 							onBlockEnd();
151 							return;
152 						}
153 					}
154 				}
155 				// If JSR that it is call
156 				else if (instr.operands == 1 && isOperandImmediate[instr.operandA])
157 				{
158 					if (instr.opcode == JSR)
159 					{
160 						ushort pc = cast(ushort)(pointer+1), sp = 0xFFFF; 
161 						ushort transitionTo = getOperandA(*_dcpu, instr.operandA, pc, sp).get();
162 
163 						// outcoming transition
164 						auto newTransition = new Transition(pointer,
165 							labelAt(transitionTo, LabelType.subroutine),
166 							TransitionType.call,
167 							inCondition,
168 							block);
169 						
170 						block.transitionsFrom ~= newTransition;
171 						processQueue ~= newTransition;
172 
173 						writeln(*newTransition);
174 						
175 					}
176 					else if(instr.opcode == IAS)
177 					{
178 						ushort pc = cast(ushort)(pointer+1), sp = 0xFFFF; 
179 						ushort transitionTo = getOperandA(*_dcpu, instr.operandA, pc, sp).get();
180 
181 						// outcoming transition. Indirect
182 						auto newTransition = new Transition(pointer,
183 							labelAt(transitionTo, LabelType.int_handler),
184 							TransitionType.intHandler,
185 							inCondition,
186 							block);
187 						
188 						block.transitionsFrom ~= newTransition;
189 						processQueue ~= newTransition;
190 						
191 						writeln(*newTransition);
192 					}
193 				}
194 				else if (!isValidInstruction(instr))
195 				{
196 					writefln("Block ended [Invalid found] %04x..%04x\n",
197 						block.position, block.position+block.length-1);
198 					return;
199 				}
200 				
201 				inCondition = isConditionalInstruction(instr);
202 				pointer += instr.size;
203 				block.length += instr.size;
204 			}
205 		}
206 
207 		uint iterations;
208 		while(!processQueue.data.empty && iterations < 1000)
209 		{
210 			Transition* trans = processQueue.data[$-1];
211 
212 			memoryMap.transitions ~= trans;
213 
214 			processQueue.shrinkTo(processQueue.data.length-1);
215 			
216 			if (trans.target.position == trans.from && trans.type != TransitionType.entry)
217 			{
218 				trans.target.type = LabelType.crash;
219 			}
220 
221 			processTransition(trans);
222 		}
223 
224 		// sort blocks and transitions.
225 		memoryMap.transitions.sort!"a.from < b.from";
226 		memoryMap.blocks.sort!"a.position < b.position";
227 		memoryMap.labels.sort!"a.position < b.position";
228 
229 		foreach(i, transition; memoryMap.transitions)
230 		{
231 			transition.index = i;
232 		}
233 
234 		foreach(i, block; memoryMap.blocks)
235 		{
236 			block.index = i;
237 		}
238 
239 		uint[LabelType.max+1] labelCounters;
240 		Label*[][LabelType.max+1] typeLabels; // labels of the same type;
241 		
242 		foreach(label; memoryMap.labels)
243 		{
244 			label.index = labelCounters[label.type]++;
245 			typeLabels[label.type] ~= label;
246 			writeln(*label);
247 		}
248 
249 		foreach(labelArray; typeLabels)
250 		{
251 			foreach(label; labelArray)
252 			{
253 				label.typeCount = labelArray.length;
254 			}
255 		}
256 	}
257 }
258 
259 
260 enum TransitionType
261 {
262 	call,
263 	jump,
264 	intHandler,
265 	entry
266 }
267 
268 struct Transition
269 {
270 	ushort from;
271 	Label* target;
272 	TransitionType type;
273 	bool conditional;
274 	MemoryBlock* fromBlock;
275 	MemoryBlock* toBlock;
276 	size_t index; // index in transition list of specific type.
277 
278 	string toString()
279 	{
280 		return format("Transition %04x -> %04x %s from %04x to %04x",
281 			from, target.position, type, fromBlock ? fromBlock.position : 0, toBlock ? toBlock.position : 0);
282 	}
283 }
284 
285 enum LabelType
286 {
287 	subroutine,
288 	crash,
289 	label,
290 	int_handler,
291 	start
292 }
293 
294 struct Label
295 {
296 	ushort position;
297 	LabelType type;
298 	size_t index;
299 	size_t typeCount; // Count of labels of the same type
300 
301 	string toString()
302 	{
303 		if (typeCount == 1)
304 			return to!string(type);
305 		
306 		return format("%s_%s", type, index);
307 	}
308 }
309 
310 enum BlockType
311 {
312 	data,
313 	code,
314 	empty
315 }
316 
317 struct MemoryBlock
318 {
319 	size_t position; // in memory
320 	size_t lastInstr; // position
321 	size_t length; // in words;
322 	Transition*[] transitionsIn; // jumps made to this block
323 	Transition*[] transitionsFrom; // jumps from this block
324 	BlockType type;
325 	size_t index; // index in block list. Used for setting labels
326 
327 	string toString()
328 	{
329 		return format("[%3s] %04x..%s %s", index, position, length == 0 ? "Invalid" : format("%04x", lastInstr), type);
330 	}
331 }
332 
333 struct MemoryMap
334 {
335 	Transition*[] transitions;
336 	Label*[] labels;
337 	MemoryBlock*[] blocks;
338 }