1 package net.mograsim.plugin.launch;
3 import static org.eclipse.core.resources.IResourceDelta.CHANGED;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.util.ArrayList;
9 import java.util.Optional;
10 import java.util.concurrent.atomic.AtomicBoolean;
11 import java.util.function.Consumer;
12 import java.util.function.Function;
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;
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;
53 public class MachineDebugTarget extends PlatformObject implements IDebugTarget, IMemoryBlockRetrievalExtension
55 private final static boolean USE_PSEUDO_THREAD = true;
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;
65 private boolean running;
67 private final List<Consumer<Double>> executionSpeedListeners;
69 private final IResourceChangeListener resChangedListener;
71 public MachineDebugTarget(ILaunch launch, IFile mpmFile, Optional<IFile> mpromFile, Optional<IFile> memFile,
72 MachineDefinition machineDefinition) throws CoreException
75 this.machine = machineDefinition.createNew();
76 this.exec = new LogicExecuter(machine.getTimeline());
78 this.executionSpeedListeners = new ArrayList<>();
79 this.mpmFile = mpmFile;
80 this.mpromFile = mpromFile;
81 this.memFile = memFile;
83 assignMicroInstructionMemory();
87 exec.startLiveExecution();
90 getLaunch().addDebugTarget(this);
93 this.resChangedListener = this::resourceChanged;
94 ResourcesPlugin.getWorkspace().addResourceChangeListener(resChangedListener, IResourceChangeEvent.POST_CHANGE);
96 // create after creating ourself
97 this.thread = USE_PSEUDO_THREAD ? new MachineThread(this) : null;
100 public Machine getMachine()
106 public String getName() throws DebugException
108 return "Mograsim machine \"" + machine.getDefinition().getId() + '"';
112 public String getModelIdentifier()
114 return MograsimActivator.PLUGIN_ID;
118 public IDebugTarget getDebugTarget()
124 public ILaunch getLaunch()
129 public double getExecutionSpeed()
131 return exec.getSpeedFactor();
134 public void setExecutionSpeed(double speed)
136 if (getExecutionSpeed() != speed)
138 exec.setSpeedFactor(speed);
139 callExecutionSpeedListener(speed);
144 public boolean isSuspended()
146 return exec.isPaused();
150 public boolean canSuspend()
152 return !isTerminated() && !isSuspended();
156 public void suspend() throws DebugException
159 throwDebugException("Can't suspend a terminated MachineProcess");
161 throwDebugException("Can't suspend a suspended MachineProcess");
163 exec.pauseLiveExecution();
164 fireSuspendEvent(DebugEvent.CLIENT_REQUEST);
168 public boolean canResume()
170 return !isTerminated() && isSuspended();
174 public void resume() throws DebugException
177 throwDebugException("Can't resume a terminated MachineProcess");
179 throwDebugException("Can't resume a non-suspended MachineProcess");
181 exec.unpauseLiveExecution();
182 fireResumeEvent(DebugEvent.CLIENT_REQUEST);
186 public boolean isTerminated()
192 public boolean canTerminate()
194 return !isTerminated();
198 public void terminate() throws DebugException
203 ResourcesPlugin.getWorkspace().removeResourceChangeListener(resChangedListener);
204 exec.stopLiveExecution();
206 fireTerminateEvent();
210 public boolean supportsBreakpoint(IBreakpoint breakpoint)
216 public void breakpointAdded(IBreakpoint breakpoint)
218 // ignore; we don't support breakpoints
222 public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta delta)
224 // ignore; we don't support breakpoints
228 public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta delta)
230 // ignore; we don't support breakpoints
234 public boolean isDisconnected()
240 public boolean canDisconnect()
246 public void disconnect() throws DebugException
248 throw new DebugException(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.NOT_SUPPORTED,
249 "Can't disconnect from a MachineDebugTarget", null));
253 public boolean supportsStorageRetrieval()
258 @SuppressWarnings("deprecation") // TODO can we throw a DebugException instead?
260 public IMemoryBlock getMemoryBlock(long startAddress, long length) throws DebugException
262 return new MainMemoryBlock(this, startAddress, length);
266 public IMemoryBlockExtension getExtendedMemoryBlock(String expression, Object context) throws DebugException
268 return new MainMemoryBlockExtension(this, expression, context);
272 public IProcess getProcess()
278 public boolean hasThreads() throws DebugException
280 return USE_PSEUDO_THREAD;
284 public IThread[] getThreads() throws DebugException
286 return thread == null ? new IThread[0] : new IThread[] { thread };
289 public void addExecutionSpeedListener(Consumer<Double> executionSpeedListener)
291 executionSpeedListeners.add(executionSpeedListener);
294 public void removeExecutionSpeedListener(Consumer<Double> executionSpeedListener)
296 executionSpeedListeners.remove(executionSpeedListener);
299 private void callExecutionSpeedListener(double executionSpeed)
301 executionSpeedListeners.forEach(l -> l.accept(executionSpeed));
304 @SuppressWarnings("unchecked")
306 public <T> T getAdapter(Class<T> adapter)
308 if (adapter == IDebugElement.class)
311 // leave this here; maybe we implement IStepFilters someday
312 if (adapter == IStepFilters.class)
313 if (this instanceof IStepFilters)
314 return (T) getDebugTarget();
316 if (adapter == IDebugTarget.class)
317 return (T) getDebugTarget();
319 if (adapter == ILaunch.class)
320 return (T) getLaunch();
323 if (adapter == ILaunchConfiguration.class)
324 return (T) getLaunch().getLaunchConfiguration();
326 return super.getAdapter(adapter);
330 * Fires a creation event for this debug element.
332 private void fireCreationEvent()
334 fireEvent(new DebugEvent(this, DebugEvent.CREATE));
338 * Fires a resume for this debug element with the specified detail code.
340 * @param detail detail code for the resume event, such as <code>DebugEvent.STEP_OVER</code>
342 private void fireResumeEvent(int detail)
344 fireEvent(new DebugEvent(this, DebugEvent.RESUME, detail));
348 * Fires a suspend event for this debug element with the specified detail code.
350 * @param detail detail code for the suspend event, such as <code>DebugEvent.BREAKPOINT</code>
352 private void fireSuspendEvent(int detail)
354 fireEvent(new DebugEvent(this, DebugEvent.SUSPEND, detail));
358 * Fires a terminate event for this debug element.
360 private void fireTerminateEvent()
362 fireEvent(new DebugEvent(this, DebugEvent.TERMINATE));
366 * Fires a debug event.
368 * @param event debug event to fire
370 private static void fireEvent(DebugEvent event)
372 DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event });
375 private static void throwDebugException(String message) throws DebugException
377 throw new DebugException(
378 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, message, null));
381 private void resourceChanged(IResourceChangeEvent event)
383 if (event.getType() == IResourceChangeEvent.POST_CHANGE)
385 tryHotReplaceIfChanged(event, mpmFile, this::assignMicroInstructionMemory, "MPM");
387 if (mpromFile.isPresent())
388 tryHotReplaceIfChanged(event, mpromFile.get(), this::assignMPROM, "MPROM");
392 private static void tryHotReplaceIfChanged(IResourceChangeEvent event, IFile memFile, RunnableThrowingCoreException assign, String type)
394 IResourceDelta mpmDelta = event.getDelta().findMember(memFile.getFullPath());
395 if (mpmDelta != null && (mpmDelta.getKind() & CHANGED) == CHANGED && memFile.exists())
396 tryHotReplace(memFile, assign, type);
399 private static void tryHotReplace(IFile memFile, RunnableThrowingCoreException assign, String type)
401 AtomicBoolean doHotReplace = new AtomicBoolean();
402 PlatformUI.getWorkbench().getDisplay().syncExec(() ->
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);
409 if (doHotReplace.get())
415 catch (CoreException e)
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()));
425 private static interface RunnableThrowingCoreException
427 public void run() throws CoreException;
430 private void assignMicroInstructionMemory() throws CoreException
432 try (InputStream mpmStream = mpmFile.getContents())
434 machine.getMicroInstructionMemory().bind(
435 MicroInstructionMemoryParser.parseMemory(machine.getDefinition().getMicroInstructionMemoryDefinition(), mpmStream));
437 catch (IOException e)
439 throw new CoreException(new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading MPM file", e));
443 private void assignMPROM() throws CoreException
445 assignMemory(mpromFile, machine.getMPROM(), machine.getDefinition().getMPROMDefinition(), StandardMPROM::new, "MPROM");
448 private void assignMainMemory() throws CoreException
450 assignMemory(memFile, machine.getMainMemory(), machine.getDefinition().getMainMemoryDefinition(), StandardMainMemory::new,
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
457 if (memFile.isPresent())
459 try (InputStream initialRAMStream = memFile.get().getContents())
461 M mem = newMemory.apply(definition);
462 BitVectorBasedMemoryParser.parseMemory(mem, initialRAMStream);
463 memoryToAssign.bind(mem);
465 catch (IOException e)
467 throw new CoreException(
468 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, "Unexpected IO exception reading " + type + " file", e));
473 public IFile getMPMFile()
478 public Optional<IFile> getMPROMFile()
483 public Optional<IFile> getMEMFile()