Fixed a bug: colors/fonts did not update properly in InstructionTable
[Mograsim.git] / plugins / net.mograsim.plugin.core / src / net / mograsim / plugin / launch / MainMemoryBlockExtension.java
1 package net.mograsim.plugin.launch;
2
3 import java.math.BigInteger;
4 import java.util.HashSet;
5 import java.util.Set;
6 import java.util.concurrent.atomic.AtomicBoolean;
7
8 import org.eclipse.core.runtime.IStatus;
9 import org.eclipse.core.runtime.PlatformObject;
10 import org.eclipse.core.runtime.Status;
11 import org.eclipse.debug.core.DebugEvent;
12 import org.eclipse.debug.core.DebugException;
13 import org.eclipse.debug.core.DebugPlugin;
14 import org.eclipse.debug.core.ILaunch;
15 import org.eclipse.debug.core.model.IDebugTarget;
16 import org.eclipse.debug.core.model.IMemoryBlockExtension;
17 import org.eclipse.debug.core.model.IMemoryBlockRetrievalExtension;
18 import org.eclipse.debug.core.model.MemoryByte;
19
20 import net.mograsim.machine.MainMemory;
21 import net.mograsim.machine.MainMemoryDefinition;
22 import net.mograsim.machine.Memory.MemoryCellModifiedListener;
23 import net.mograsim.plugin.MograsimActivator;
24 import net.mograsim.preferences.Preferences;
25
26 public class MainMemoryBlockExtension extends PlatformObject implements IMemoryBlockExtension
27 {
28         // TODO do we want to make the memory accessible byte-wise?
29
30         private final String expression;
31         private final MachineDebugTarget debugTarget;
32         private final MainMemory mem;
33
34         private final MainMemoryDefinition memDef;
35         private final int cellWidthBits;
36         private final int cellWidthBytes;
37         private final BigInteger cellWidthBytesBI;
38         private final BigInteger minAddrWords;
39         private final BigInteger maxAddrWords;
40
41         private BigInteger baseAddrWords;
42         private BigInteger lengthWords;
43
44         private final Set<Object> clients;
45         private final MemoryCellModifiedListener memListener;
46         private final AtomicBoolean memListenerRegistered;
47
48         private final int maxContentChangeInterval;
49         private final Object contentChangeLock;
50         private Thread contentChangeThread;
51         private long nextContentChangeAllowedMillis;
52         private boolean contentChangeQueued;
53
54         public MainMemoryBlockExtension(MachineDebugTarget debugTarget, String expression, @SuppressWarnings("unused") Object expressionContext)
55                         throws DebugException
56         {
57                 this.expression = expression;
58                 this.debugTarget = debugTarget;
59                 this.mem = debugTarget.getMachine().getMainMemory();
60
61                 this.memDef = mem.getDefinition();
62                 this.cellWidthBits = memDef.getCellWidth();
63                 this.cellWidthBytes = (cellWidthBits + 7) / 8;
64                 this.cellWidthBytesBI = BigInteger.valueOf(cellWidthBytes);
65                 this.minAddrWords = BigInteger.valueOf(memDef.getMinimalAddress());
66                 this.maxAddrWords = BigInteger.valueOf(memDef.getMaximalAddress());
67
68                 // TODO parse expression better
69                 this.baseAddrWords = new BigInteger(expression, 16);
70                 this.lengthWords = BigInteger.ONE;
71
72                 if (baseAddrWords.compareTo(minAddrWords) < 0 || baseAddrWords.compareTo(maxAddrWords) > 0)
73                         throwDebugException("Base address out of range");
74                 if (baseAddrWords.add(lengthWords).compareTo(maxAddrWords) > 0)
75                         throwDebugException("End address out of range");
76
77                 this.clients = new HashSet<>();
78                 // don't check whether the address is in range, because this memory block could be read outside its "range"
79                 this.memListener = a -> queueFireContentChangeEvent();
80                 this.memListenerRegistered = new AtomicBoolean();
81
82                 this.maxContentChangeInterval = Preferences.current().getInt("net.mograsim.plugin.core.maxmemchangeinterval");
83                 this.contentChangeLock = new Object();
84                 this.nextContentChangeAllowedMillis = System.currentTimeMillis() - maxContentChangeInterval - 1;
85         }
86
87         @Override
88         public long getStartAddress()
89         {
90                 return baseAddrWords.multiply(cellWidthBytesBI).longValueExact();
91         }
92
93         @Override
94         public long getLength()
95         {
96                 return lengthWords.multiply(cellWidthBytesBI).longValueExact();
97         }
98
99         @Override
100         public byte[] getBytes() throws DebugException
101         {
102                 BigInteger endAddrWords = baseAddrWords.add(lengthWords);
103                 if (endAddrWords.compareTo(maxAddrWords) > 0)
104                         throwDebugException("End address out of range");
105                 int lengthBytes = lengthWords.multiply(cellWidthBytesBI).intValueExact();
106
107                 byte[] bytes = new byte[lengthBytes];
108                 int i;
109                 long j;
110                 for (i = 0, j = baseAddrWords.longValue(); i < lengthBytes; i += cellWidthBytes, j++)
111                 {
112                         BigInteger word = mem.getCellAsBigInteger(j);
113                         System.arraycopy(word.toByteArray(), 0, bytes, i, cellWidthBytes);
114                 }
115                 return bytes;
116         }
117
118         @Override
119         public boolean supportsValueModification()
120         {
121                 return true;
122         }
123
124         @Override
125         public void setValue(long offset, byte[] bytes) throws DebugException
126         {
127                 if (offset % cellWidthBytes != 0 || bytes.length % cellWidthBytes != 0)
128                         throwDebugException("Requested unaligned memory write");
129                 BigInteger startAddrWords = baseAddrWords.add(BigInteger.valueOf(offset / cellWidthBytes));
130                 if (startAddrWords.compareTo(minAddrWords) < 0 || startAddrWords.compareTo(maxAddrWords) > 0)
131                         throwDebugException("Start address out of range");
132
133                 BigInteger endAddrWords = startAddrWords.add(BigInteger.valueOf(bytes.length / cellWidthBytes));
134                 if (endAddrWords.compareTo(maxAddrWords) > 0)
135                         throwDebugException("End address out of range");
136
137                 int i;
138                 long j;
139                 for (i = 0, j = startAddrWords.longValue(); i < bytes.length; i += cellWidthBytes, j++)
140                 {
141                         BigInteger word = new BigInteger(bytes, i, cellWidthBytes);
142                         mem.setCellAsBigInteger(j, word);
143                 }
144         }
145
146         @Override
147         public String getModelIdentifier()
148         {
149                 return MograsimActivator.PLUGIN_ID;
150         }
151
152         @Override
153         public IDebugTarget getDebugTarget()
154         {
155                 return debugTarget;
156         }
157
158         @Override
159         public ILaunch getLaunch()
160         {
161                 return debugTarget.getLaunch();
162         }
163
164         @Override
165         public String getExpression()
166         {
167                 return expression;
168         }
169
170         @Override
171         public BigInteger getBigBaseAddress() throws DebugException
172         {
173                 return baseAddrWords;
174         }
175
176         @Override
177         public BigInteger getMemoryBlockStartAddress() throws DebugException
178         {
179                 return minAddrWords;
180         }
181
182         @Override
183         public BigInteger getMemoryBlockEndAddress() throws DebugException
184         {
185                 return maxAddrWords;
186         }
187
188         @Override
189         public BigInteger getBigLength() throws DebugException
190         {
191                 return maxAddrWords.subtract(minAddrWords);
192         }
193
194         @Override
195         public int getAddressSize() throws DebugException
196         {
197                 return Long.BYTES;
198         }
199
200         @Override
201         public boolean supportBaseAddressModification() throws DebugException
202         {
203                 return true;
204         }
205
206         @Override
207         public boolean supportsChangeManagement()
208         {
209                 return false;
210         }
211
212         @Override
213         public void setBaseAddress(BigInteger address) throws DebugException
214         {
215                 if (address.compareTo(minAddrWords) < 0 || address.compareTo(maxAddrWords) > 0)
216                         throwDebugException("Address out of range");
217                 this.baseAddrWords = address;
218         }
219
220         @Override
221         public MemoryByte[] getBytesFromOffset(BigInteger unitOffset, long addressableUnits) throws DebugException
222         {
223                 return getBytesFromAddress(getBigBaseAddress().add(unitOffset), addressableUnits);
224         }
225
226         @Override
227         public MemoryByte[] getBytesFromAddress(BigInteger address, long units) throws DebugException
228         {
229                 if (units < 0)
230                         throwDebugException("Requested negative amount of unites");
231                 int lengthBytes = BigInteger.valueOf(units).multiply(cellWidthBytesBI).intValueExact();
232
233                 MemoryByte[] bytes = new MemoryByte[lengthBytes];
234                 int i;
235                 BigInteger j;
236                 for (i = 0, j = address; i < lengthBytes; i += cellWidthBytes, j = j.add(BigInteger.ONE))
237                 {
238                         if (j.compareTo(minAddrWords) >= 0 && j.compareTo(maxAddrWords) <= 0)
239                         {
240                                 BigInteger word = mem.getCellAsBigInteger(j.longValue());
241                                 byte[] wordBytes = word.toByteArray();
242                                 int l = wordBytes[0] == 0 ? 1 : 0;
243                                 int k;
244                                 for (k = 0; k < cellWidthBytes - wordBytes.length + l; k++)
245                                         bytes[i + k] = new MemoryByte();
246                                 for (; k < cellWidthBytes; k++, l++)
247                                         bytes[i + k] = new MemoryByte(wordBytes[l]);
248                         } else
249                                 for (int k = 0; k < cellWidthBytes; k++)
250                                         bytes[i + k] = new MemoryByte((byte) 0, (byte) 0);
251                 }
252                 return bytes;
253         }
254
255         @Override
256         public void setValue(BigInteger offset, byte[] bytes) throws DebugException
257         {
258                 if (bytes.length % cellWidthBytes != 0)
259                         throwDebugException("Requested unaligned memory write");
260                 BigInteger startAddrWords = baseAddrWords.add(offset);
261                 if (startAddrWords.compareTo(minAddrWords) < 0 || startAddrWords.compareTo(maxAddrWords) > 0)
262                         throwDebugException("Start address out of range");
263                 BigInteger endAddrWords = startAddrWords.add(BigInteger.valueOf(bytes.length / cellWidthBytes));
264                 if (endAddrWords.compareTo(maxAddrWords) > 0)
265                         throwDebugException("End address out of range");
266
267                 unregisterMemoryListener();
268
269                 int i;
270                 long j;
271                 for (i = 0, j = startAddrWords.longValue(); i < bytes.length; i += cellWidthBytes, j++)
272                 {
273                         BigInteger word = new BigInteger(bytes, i, cellWidthBytes);
274                         mem.setCellAsBigInteger(j, word);
275                 }
276
277                 if (!clients.isEmpty())
278                         registerMemoryListener();
279                 queueFireContentChangeEvent();
280         }
281
282         @Override
283         public void connect(Object client)
284         {
285                 registerMemoryListener();
286                 clients.add(client);
287         }
288
289         @Override
290         public void disconnect(Object client)
291         {
292                 clients.remove(client);
293
294                 if (clients.isEmpty())
295                         unregisterMemoryListener();
296         }
297
298         @Override
299         public Object[] getConnections()
300         {
301
302                 Set<Object> clientsLocal = clients;
303                 return clientsLocal == null ? new Object[0] : clientsLocal.toArray();
304         }
305
306         private void registerMemoryListener()
307         {
308                 if (!memListenerRegistered.getAndSet(true))
309                         mem.registerCellModifiedListener(memListener);
310         }
311
312         private void unregisterMemoryListener()
313         {
314                 if (memListenerRegistered.getAndSet(false))
315                         mem.deregisterCellModifiedListener(memListener);
316
317                 Thread contentChangeThreadLocal;
318                 synchronized (contentChangeLock)
319                 {
320                         contentChangeThreadLocal = contentChangeThread;
321                         // set contentChangeQueued here to prevent the following scenario:
322                         // 1. A change event is requested -> it gets fired "directly"
323                         // 2. A second change event is requested during the "cooldown time" -> a queue thread gets started
324                         // 3. The last client is disconnected -> the queue thread is interrupted
325                         // 4. A new client is connected
326                         // 5. A third change event is requested; queueFireContentChangeEvent locks contentChangeLock
327                         // before the queue thread locks contentChangeLock.
328                         // Now queueFireContentChangeEvent would return doing nothing, since contentChangeQueued still is true,
329                         // causing a change event to be missed.
330                         contentChangeQueued = false;
331                 }
332                 if (contentChangeThreadLocal != null)
333                         contentChangeThreadLocal.interrupt();
334         }
335
336         @Override
337         public void dispose() throws DebugException
338         {
339                 clients.clear();
340                 unregisterMemoryListener();
341         }
342
343         @Override
344         public IMemoryBlockRetrievalExtension getMemoryBlockRetrieval()
345         {
346                 return debugTarget;
347         }
348
349         @Override
350         public int getAddressableSize() throws DebugException
351         {
352                 return cellWidthBytes;
353         }
354
355         private void queueFireContentChangeEvent()
356         {
357                 long sleepTime;
358                 boolean fireInOwnThread = false;
359                 synchronized (contentChangeLock)
360                 {
361                         if (contentChangeQueued)
362                                 return;
363                         long nextContentChangeAllowedMillisLocal = nextContentChangeAllowedMillis;
364                         long now = System.currentTimeMillis();
365                         sleepTime = nextContentChangeAllowedMillisLocal - now;
366                         if (sleepTime >= 0)
367                         {
368                                 fireInOwnThread = true;
369                                 contentChangeQueued = true;
370                                 nextContentChangeAllowedMillis = nextContentChangeAllowedMillisLocal + maxContentChangeInterval;
371                         } else
372                         {
373                                 fireInOwnThread = false;
374                                 nextContentChangeAllowedMillis = now + maxContentChangeInterval;
375                         }
376                 }
377                 if (fireInOwnThread)
378                 {
379                         // the following two statements can't cause racing problems since we set contentChangeQueued to true,
380                         // which means no-one will write this field until the thread started:
381                         // this method will never get here, and in this moment there is no (other) content change thread running,
382                         // since contentChangeQueued was false
383                         contentChangeThread = new Thread(() ->
384                         {
385                                 boolean interrupted = false;
386                                 try
387                                 {
388                                         Thread.sleep(sleepTime);
389                                 }
390                                 catch (@SuppressWarnings("unused") InterruptedException e)
391                                 {
392                                         interrupted = true;
393                                 }
394                                 synchronized (contentChangeLock)
395                                 {
396                                         contentChangeThread = null;
397                                         contentChangeQueued = false;
398                                 }
399                                 if (!interrupted && !Thread.interrupted())
400                                         fireContentChangeEventNow();
401                         });
402                         contentChangeThread.start();
403                 } else
404                         fireContentChangeEventNow();
405         }
406
407         /**
408          * Fires a terminate event for this debug element.
409          */
410         private void fireContentChangeEventNow()
411         {
412                 fireEvent(new DebugEvent(this, DebugEvent.CHANGE, DebugEvent.CONTENT));
413         }
414
415         /**
416          * Fires a debug event.
417          *
418          * @param event debug event to fire
419          */
420         private static void fireEvent(DebugEvent event)
421         {
422                 DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event });
423         }
424
425         private static void throwDebugException(String message) throws DebugException
426         {
427                 throw new DebugException(
428                                 new Status(IStatus.ERROR, MograsimActivator.PLUGIN_ID, DebugException.TARGET_REQUEST_FAILED, message, null));
429         }
430 }