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