Finished MPROM support. Fixes #10
[Mograsim.git] / plugins / net.mograsim.plugin.core / src / net / mograsim / plugin / launch / MachineDebugTarget.java
1 package net.mograsim.plugin.launch;
2
3 import static org.eclipse.core.resources.IResourceDelta.CHANGED;
4
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.util.ArrayList;
8 import java.util.List;
9 import java.util.Optional;
10 import java.util.concurrent.atomic.AtomicBoolean;
11 import java.util.function.Consumer;
12 import java.util.function.Function;
13
14 import org.eclipse.core.resources.IFile;
15 import org.eclipse.core.resources.IMarkerDelta;
16 import org.eclipse.core.resources.IResourceChangeEvent;
17 import org.eclipse.core.resources.IResourceChangeListener;
18 import org.eclipse.core.resources.IResourceDelta;
19 import org.eclipse.core.resources.ResourcesPlugin;
20 import org.eclipse.core.runtime.CoreException;
21 import org.eclipse.core.runtime.IStatus;
22 import org.eclipse.core.runtime.PlatformObject;
23 import org.eclipse.core.runtime.Status;
24 import org.eclipse.debug.core.DebugEvent;
25 import org.eclipse.debug.core.DebugException;
26 import org.eclipse.debug.core.DebugPlugin;
27 import org.eclipse.debug.core.ILaunch;
28 import org.eclipse.debug.core.ILaunchConfiguration;
29 import org.eclipse.debug.core.model.IBreakpoint;
30 import org.eclipse.debug.core.model.IDebugElement;
31 import org.eclipse.debug.core.model.IDebugTarget;
32 import org.eclipse.debug.core.model.IMemoryBlock;
33 import org.eclipse.debug.core.model.IMemoryBlockExtension;
34 import org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension;
35 import org.eclipse.debug.core.model.IProcess;
36 import org.eclipse.debug.core.model.IStepFilters;
37 import org.eclipse.debug.core.model.IThread;
38 import org.eclipse.jface.dialogs.MessageDialog;
39 import org.eclipse.ui.PlatformUI;
40
41 import net.mograsim.logic.model.LogicExecuter;
42 import net.mograsim.machine.BitVectorMemory;
43 import net.mograsim.machine.BitVectorMemoryDefinition;
44 import net.mograsim.machine.Machine;
45 import net.mograsim.machine.MachineDefinition;
46 import net.mograsim.machine.StandardMainMemory;
47 import net.mograsim.machine.mi.MicroInstructionMemoryParser;
48 import net.mograsim.machine.mi.StandardMPROM;
49 import net.mograsim.machine.standard.memory.AbstractAssignableBitVectorMemory;
50 import net.mograsim.machine.standard.memory.BitVectorBasedMemoryParser;
51 import net.mograsim.plugin.MograsimActivator;
52
53 public class MachineDebugTarget extends PlatformObject implements IDebugTarget, IMemoryBlockRetrievalExtension
54 {
55         private final static boolean USE_PSEUDO_THREAD = true;
56
57         private final ILaunch launch;
58         private final Machine machine;
59         private final LogicExecuter exec;
60         private final MachineThread thread;
61         private final IFile mpmFile;
62         private final Optional<IFile> mpromFile;
63         private final Optional<IFile> memFile;
64
65         private boolean running;
66
67         private final List<Consumer<Double>> executionSpeedListeners;
68
69         private final IResourceChangeListener resChangedListener;
70
71         public MachineDebugTarget(ILaunch launch, IFile mpmFile, Optional<IFile> mpromFile, Optional<IFile> memFile,
72                         MachineDefinition machineDefinition) throws CoreException
73         {
74                 this.launch = launch;
75                 this.machine = machineDefinition.createNew();
76                 this.exec = new LogicExecuter(machine.getTimeline());
77
78                 this.executionSpeedListeners = new ArrayList<>();
79                 this.mpmFile = mpmFile;
80                 this.mpromFile = mpromFile;
81                 this.memFile = memFile;
82
83                 assignMicroInstructionMemory();
84                 assignMPROM();
85                 assignMainMemory();
86
87                 exec.startLiveExecution();
88                 running = true;
89
90                 getLaunch().addDebugTarget(this);
91                 fireCreationEvent();
92
93                 this.resChangedListener = this::resourceChanged;
94                 ResourcesPlugin.getWorkspace().addResourceChangeListener(resChangedListener, IResourceChangeEvent.POST_CHANGE);
95
96                 // create after creating ourself
97                 this.thread = USE_PSEUDO_THREAD ? new MachineThread(this) : null;
98         }
99
100         public Machine getMachine()
101         {
102                 return machine;
103         }
104
105         @Override
106         public String getName() throws DebugException
107         {
108                 return "Mograsim machine \"" + machine.getDefinition().getId() + '"';
109         }
110
111         @Override
112         public String getModelIdentifier()
113         {
114                 return MograsimActivator.PLUGIN_ID;
115         }
116
117         @Override
118         public IDebugTarget getDebugTarget()
119         {
120                 return this;
121         }
122
123         @Override
124         public ILaunch getLaunch()
125         {
126                 return launch;
127         }
128
129         public double getExecutionSpeed()
130         {
131                 return exec.getSpeedFactor();
132         }
133
134         public void setExecutionSpeed(double speed)
135         {
136                 if (getExecutionSpeed() != speed)
137                 {
138                         exec.setSpeedFactor(speed);
139                         callExecutionSpeedListener(speed);
140                 }
141         }
142
143         @Override
144         public boolean isSuspended()
145         {
146                 return exec.isPaused();
147         }
148
149         @Override
150         public boolean canSuspend()
151         {
152                 return !isTerminated() && !isSuspended();
153         }
154
155         @Override
156         public void suspend() throws DebugException
157         {
158                 if (isTerminated())
159                         throwDebugException("Can't suspend a terminated MachineProcess");
160                 if (isSuspended())
161                         throwDebugException("Can't suspend a suspended MachineProcess");
162
163                 exec.pauseLiveExecution();
164                 fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
165         }
166
167         @Override
168         public boolean canResume()
169         {
170                 return !isTerminated() && isSuspended();
171         }
172
173         @Override
174         public void resume() throws DebugException
175         {
176                 if (isTerminated())
177                         throwDebugException("Can't resume a terminated MachineProcess");
178                 if (!isSuspended())
179                         throwDebugException("Can't resume a non-suspended MachineProcess");
180
181                 exec.unpauseLiveExecution();
182                 fireResumeEvent(DebugEvent.CLIENT_REQUEST);
183         }
184
185         @Override
186         public boolean isTerminated()
187         {
188                 return !running;
189         }
190
191         @Override
192         public boolean canTerminate()
193         {
194                 return !isTerminated();
195         }
196
197         @Override
198         public void terminate() throws DebugException
199         {
200                 if (isTerminated())
201                         return;
202
203                 ResourcesPlugin.getWorkspace().removeResourceChangeListener(resChangedListener);
204                 exec.stopLiveExecution();
205                 running = false;
206                 fireTerminateEvent();
207         }
208
209         @Override
210         public boolean supportsBreakpoint(IBreakpoint breakpoint)
211         {
212                 return false;
213         }
214
215         @Override
216         public void breakpointAdded(IBreakpoint breakpoint)
217         {
218                 // ignore; we don't support breakpoints
219         }
220
221         @Override
222         public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta)
223         {
224                 // ignore; we don't support breakpoints
225         }
226
227         @Override
228         public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta)
229         {
230                 // ignore; we don't support breakpoints
231         }
232
233         @Override
234         public boolean isDisconnected()
235         {
236                 return false;
237         }
238
239         @Override
240         public boolean canDisconnect()
241         {
242                 return false;
243         }
244
245         @Override
246         public void disconnect() throws DebugException
247         {
248                 throw new DebugException(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.NOT_SUPPORTED,
249                                 "Can't disconnect from a MachineDebugTarget", null));
250         }
251
252         @Override
253         public boolean supportsStorageRetrieval()
254         {
255                 return true;
256         }
257
258         @SuppressWarnings("deprecation") // TODO can we throw a DebugException instead?
259         @Override
260         public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException
261         {
262                 return new MainMemoryBlock(this, startAddress, length);
263         }
264
265         @Override
266         public IMemoryBlockExtension getExtendedMemoryBlock(String expression, Object context) throws DebugException
267         {
268                 return new MainMemoryBlockExtension(this, expression, context);
269         }
270
271         @Override
272         public IProcess getProcess()
273         {
274                 return null;
275         }
276
277         @Override
278         public boolean hasThreads() throws DebugException
279         {
280                 return USE_PSEUDO_THREAD;
281         }
282
283         @Override
284         public IThread[] getThreads() throws DebugException
285         {
286                 return thread == null ? new IThread[0] : new IThread[] { thread };
287         }
288
289         public void addExecutionSpeedListener(Consumer<Double> executionSpeedListener)
290         {
291                 executionSpeedListeners.add(executionSpeedListener);
292         }
293
294         public void removeExecutionSpeedListener(Consumer<Double> executionSpeedListener)
295         {
296                 executionSpeedListeners.remove(executionSpeedListener);
297         }
298
299         private void callExecutionSpeedListener(double executionSpeed)
300         {
301                 executionSpeedListeners.forEach(l -> l.accept(executionSpeed));
302         }
303
304         @SuppressWarnings("unchecked")
305         @Override
306         public <T> T getAdapter(Class<T> adapter)
307         {
308                 if (adapter == IDebugElement.class)
309                         return (T) this;
310
311                 // leave this here; maybe we implement IStepFilters someday
312                 if (adapter == IStepFilters.class)
313                         if (this instanceof IStepFilters)
314                                 return (T) getDebugTarget();
315
316                 if (adapter == IDebugTarget.class)
317                         return (T) getDebugTarget();
318
319                 if (adapter == ILaunch.class)
320                         return (T) getLaunch();
321
322                 // CONTEXTLAUNCHING
323                 if (adapter == ILaunchConfiguration.class)
324                         return (T) getLaunch().getLaunchConfiguration();
325
326                 return super.getAdapter(adapter);
327         }
328
329         /**
330          * Fires a creation event for this debug element.
331          */
332         private void fireCreationEvent()
333         {
334                 fireEvent(new DebugEvent(this, DebugEvent.CREATE));
335         }
336
337         /**
338          * Fires a resume for this debug element with the specified detail code.
339          *
340          * @param detail detail code for the resume event, such as <code>DebugEvent.STEP_OVER</code>
341          */
342         private void fireResumeEvent(int detail)
343         {
344                 fireEvent(new DebugEvent(this, DebugEvent.RESUME, detail));
345         }
346
347         /**
348          * Fires a suspend event for this debug element with the specified detail code.
349          *
350          * @param detail detail code for the suspend event, such as <code>DebugEvent.BREAKPOINT</code>
351          */
352         private void fireSuspendEvent(int detail)
353         {
354                 fireEvent(new DebugEvent(this, DebugEvent.SUSPEND, detail));
355         }
356
357         /**
358          * Fires a terminate event for this debug element.
359          */
360         private void fireTerminateEvent()
361         {
362                 fireEvent(new DebugEvent(this, DebugEvent.TERMINATE));
363         }
364
365         /**
366          * Fires a debug event.
367          *
368          * @param event debug event to fire
369          */
370         private static void fireEvent(DebugEvent event)
371         {
372                 DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event });
373         }
374
375         private static void throwDebugException(String message) throws DebugException
376         {
377                 throw new DebugException(
378                                 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, message, null));
379         }
380
381         private void resourceChanged(IResourceChangeEvent event)
382         {
383                 if (event.getType() == IResourceChangeEvent.POST_CHANGE)
384                 {
385                         tryHotReplaceIfChanged(event, mpmFile, this::assignMicroInstructionMemory, "MPM");
386
387                         if (mpromFile.isPresent())
388                                 tryHotReplaceIfChanged(event, mpromFile.get(), this::assignMPROM, "MPROM");
389                 }
390         }
391
392         private static void tryHotReplaceIfChanged(IResourceChangeEvent event, IFile memFile, RunnableThrowingCoreException assign, String type)
393         {
394                 IResourceDelta mpmDelta = event.getDelta().findMember(memFile.getFullPath());
395                 if (mpmDelta != null && (mpmDelta.getKind() & CHANGED) == CHANGED && memFile.exists())
396                         tryHotReplace(memFile, assign, type);
397         }
398
399         private static void tryHotReplace(IFile memFile, RunnableThrowingCoreException assign, String type)
400         {
401                 AtomicBoolean doHotReplace = new AtomicBoolean();
402                 PlatformUI.getWorkbench().getDisplay().syncExec(() ->
403                 {
404                         if (MessageDialog.openConfirm(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Hot Replace " + type + "?",
405                                         String.format("The " + type + " %s has been modified on the file system. Replace simulated " + type
406                                                         + " with modified contents?", memFile.getName())))
407                                 doHotReplace.set(true);
408                 });
409                 if (doHotReplace.get())
410                 {
411                         try
412                         {
413                                 assign.run();
414                         }
415                         catch (CoreException e)
416                         {
417                                 PlatformUI.getWorkbench().getDisplay()
418                                                 .asyncExec(() -> MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
419                                                                 "Failed Hot Replace!",
420                                                                 "An error occurred trying to read the modified " + type + " from the file system: " + e.getMessage()));
421                         }
422                 }
423         }
424
425         private static interface RunnableThrowingCoreException
426         {
427                 public void run() throws CoreException;
428         }
429
430         private void assignMicroInstructionMemory() throws CoreException
431         {
432                 try (InputStream mpmStream = mpmFile.getContents())
433                 {
434                         machine.getMicroInstructionMemory().bind(
435                                         MicroInstructionMemoryParser.parseMemory(machine.getDefinition().getMicroInstructionMemoryDefinition(), mpmStream));
436                 }
437                 catch (IOException e)
438                 {
439                         throw new CoreException(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading MPM file", e));
440                 }
441         }
442
443         private void assignMPROM() throws CoreException
444         {
445                 assignMemory(mpromFile, machine.getMPROM(), machine.getDefinition().getMPROMDefinition(), StandardMPROM::new, "MPROM");
446         }
447
448         private void assignMainMemory() throws CoreException
449         {
450                 assignMemory(memFile, machine.getMainMemory(), machine.getDefinition().getMainMemoryDefinition(), StandardMainMemory::new,
451                                 "initial RAM");
452         }
453
454         private static <D extends BitVectorMemoryDefinition, M extends BitVectorMemory> void assignMemory(Optional<IFile> memFile,
455                         AbstractAssignableBitVectorMemory<M> memoryToAssign, D definition, Function<D, M> newMemory, String type) throws CoreException
456         {
457                 if (memFile.isPresent())
458                 {
459                         try (InputStream initialRAMStream = memFile.get().getContents())
460                         {
461                                 M mem = newMemory.apply(definition);
462                                 BitVectorBasedMemoryParser.parseMemory(mem, initialRAMStream);
463                                 memoryToAssign.bind(mem);
464                         }
465                         catch (IOException e)
466                         {
467                                 throw new CoreException(
468                                                 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading " + type + " file", e));
469                         }
470                 }
471         }
472
473         public IFile getMPMFile()
474         {
475                 return mpmFile;
476         }
477
478         public Optional<IFile> getMPROMFile()
479         {
480                 return mpromFile;
481         }
482
483         public Optional<IFile> getMEMFile()
484         {
485                 return memFile;
486         }
487 }