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