Updated for openjdk-19-internal
[panamaz] / src / template / Native.java
1 /*
2  * Copyright (C) 2021 Michael Zucchi
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 package api;
18
19 import java.io.StringReader;
20 import java.lang.invoke.*;
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.List;
25 import java.util.function.Function;
26 import java.util.function.IntFunction;
27 import jdk.incubator.foreign.*;
28 import java.lang.ref.ReferenceQueue;
29 import java.lang.ref.WeakReference;
30 import java.lang.reflect.Constructor;
31 import java.lang.reflect.InvocationTargetException;
32 import java.lang.System.Logger.Level;
33
34 /**
35  * Base class for all native objects.
36  * <p>
37  * Handles instantiation and provides helper functions for native access.
38  * <p>
39  * Work in progress.
40  * <p>
41  * For better safety the 'p' field should be the CHandle, and addr() would
42  * call get(). Otherwise one must not release ANY object which might ever
43  * be used again - including any objects returned by the getInfo(). However ...
44  * it's a trade-off and it's a lot of code to change.
45  * <p>
46  * FIXME: there are MemorySegment based accessors for primitive types now, use those
47  */
48 public class Native implements Memory.Addressable {
49
50         private final MemoryAddress p;
51
52         private final static boolean dolog = true;
53
54         protected Native(MemoryAddress p) {
55                 this.p = p;
56         }
57
58         static System.Logger log() {
59                 return System.getLogger("notzed.native");
60         }
61
62         public MemoryAddress address() {
63                 return p;
64         }
65
66         /* ********************************************************************** */
67         /* GC handling */
68         /* ********************************************************************** */
69         /**
70          * Resource index.
71          */
72         static private final PointerTable map = new PointerTable();
73
74         /**
75          * Reference queue for stale objects.
76          */
77         static private final ReferenceQueue<Native> references = new ReferenceQueue<>();
78
79         private static <T extends Native> T createInstance(Class<T> jtype, MemoryAddress p) {
80                 cleanerStep();
81                 try {
82                         Class[] params = {MemoryAddress.class};
83                         Constructor<T> cc = jtype.getDeclaredConstructor(params);
84
85                         cc.setAccessible(true);
86
87                         return cc.newInstance(p);
88                 } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
89                         log().log(Level.ERROR, "createInstance", ex);
90                         throw new RuntimeException(ex);
91                 }
92         }
93
94         /*
95         public static <T extends Native> T resolve(Class<T> jtype, MemoryAddress p) {
96                 T o;
97
98                 //if (dolog)
99                 log().log(Level.DEBUG, () -> String.format("  resolve $%016x %s", p.offset(), jtype.getName()));
100
101                 // Instantiation needs to be synchronized for obvious reasons.
102                 synchronized (map) {
103                         CHandle h = (CHandle) map.get(p);
104
105                         if (h == null || (o = jtype.cast(h.get())) == null) {
106                                 o = createInstance(jtype, p);
107                                 h = new CHandle(o, references, p);
108                                 map.putAlways(h);
109                         }
110                 }
111                 return o;
112                 }*/
113         public static <T extends Native> T resolve(MemoryAddress p, Function<MemoryAddress, T> create) {
114                 T o;
115                 boolean step = false;
116
117                 //if (dolog)
118                 //      log().log(Level.DEBUG, () -> String.format("  resolv $%016x %s", Memory.toLong(p), create));
119                 if (p.toRawLongValue() == 0)
120                         return null;
121
122                 // Instantiation needs to be synchronized for obvious reasons.
123                 synchronized (map) {
124                         CHandle h = (CHandle)map.get(p);
125
126                         String fmt;
127
128                         if (h == null || (o = (T)(h.get())) == null) {
129                                 o = create.apply(p);
130
131                                 fmt = h == null ? "  create $%016x %s" : "  replac $%016x %s";
132
133                                 h = new CHandle(o, references, p);
134                                 map.put(h);
135                                 step = true;
136                         } else {
137                                 fmt = "  exists $%016x %s";
138                         }
139                         {
140                                 T x = o;
141                                 log().log(Level.DEBUG, () -> String.format(fmt, p.toRawLongValue(), x.getClass().getName()));
142                         }
143                 }
144
145                 if (step)
146                         cleanerStep();
147
148                 return o;
149         }
150
151         /*
152         public static <T extends Native> void register(T o) {
153                 T o;
154                 boolean step = false;
155
156                 if (dolog)
157                         log().log(Level.DEBUG, () -> String.format("  regist $%016x %s", o.addr().offset(), o.getClass().getName()));
158
159                 CHandle h = new CHandle(o, references, o.addr());
160
161                 synchronized (map) {
162                         map.put(h);
163                         step = true;
164                 }
165
166                 if (step)
167                         cleanerStep();
168
169                 return o;
170                 }*/
171         public void release() {
172                 WeakReference<? extends Native> ref;
173
174                 synchronized (map) {
175                         ref = map.remove(p);
176                 }
177
178                 if (ref != null) {
179                         if (dolog)
180                                 log().log(Level.DEBUG, () -> String.format("  force  $%016x %s", p.toRawLongValue(), getClass().getName()));
181
182                         ref.enqueue();
183                 }
184         }
185
186         public static <T extends Native> void release(T a) {
187                 if (a != null)
188                         a.release();
189         }
190
191         public static void release(Native... list) {
192                 for (Native o: list)
193                         release(o);
194         }
195
196         static {
197                 Thread cleanup = new Thread(Native::cleaner, "Native cleaner");
198                 cleanup.setPriority(Thread.MAX_PRIORITY);
199                 cleanup.setDaemon(true);
200                 cleanup.start();
201         }
202
203         private static void cleanerStep() {
204                 try {
205                         CHandle stale = (CHandle)references.poll();
206                         if (stale != null) {
207                                 synchronized (map) {
208                                         map.remove(stale.p);
209                                 }
210                                 stale.release();
211                         }
212                 } catch (Throwable ex) {
213                 }
214         }
215
216         /**
217          * Cleaner thread.
218          * <p>
219          * This polls the reference queue and releases objects via
220          * their static release method.
221          */
222         private static void cleaner() {
223                 if (dolog)
224                         log().log(Level.DEBUG, "Native finaliser started");
225                 try {
226                         while (true) {
227                                 CHandle stale = (CHandle)references.remove();
228                                 do {
229                                         try {
230                                                 synchronized (map) {
231                                                         map.remove(stale.p);
232                                                 }
233                                                 stale.release();
234                                         } catch (Throwable ex) {
235                                         }
236                                         stale = (CHandle)references.poll();
237                                 } while (stale != null);
238                         }
239                 } catch (InterruptedException ex) {
240                 }
241         }
242
243         private static class CHandle extends WeakReference<Native> {
244
245                 protected MemoryAddress p;
246                 final Class<? extends Native> jtype;
247                 CHandle next;
248
249                 CHandle(Native referent, ReferenceQueue<Native> references, MemoryAddress p) {
250                         super(referent, references);
251                         this.p = p;
252                         this.jtype = referent.getClass();
253                 }
254
255                 void release() {
256                         try {
257                                 if (p != null) {
258                                         if (dolog)
259                                                 log().log(Level.DEBUG, () -> String.format("  releas $%016x %s", p.toRawLongValue(), jtype.getName()));
260
261                                         Method mm = jtype.getDeclaredMethod("release", MemoryAddress.class);
262                                         mm.setAccessible(true);
263                                         mm.invoke(null, p);
264                                 }
265                         } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
266                                 log().log(Level.ERROR, jtype.getName(), ex);
267                         } finally {
268                                 p = null;
269                         }
270                 }
271
272                 @Override
273                 public boolean equals(Object obj) {
274                         return (obj instanceof CHandle) && ((CHandle)obj).p == p;
275                 }
276
277                 @Override
278                 public int hashCode() {
279                         //return p.hashCode();
280                         return hashCode(p);
281                 }
282
283                 /**
284                  * Simple hashcode for native pointers.
285                  * <p>
286                  * This simply strips the bottom 4 bits from the pointer as
287                  * on a 64-bit system the low 3 bits are typically zero and the 4th
288                  * isn't very well distributed.
289                  *
290                  * @param p
291                  * @return
292                  */
293                 public static final int hashCode(long p) {
294                         return (int)p >>> 4;
295                 }
296
297                 /**
298                  * Sigh, memoryaddress has a miserable hashCode(), it's even worse than Long.hashCode()
299                  */
300                 public static final int hashCode(MemoryAddress p) {
301                         return p.hashCode() >>> 5;
302                 }
303         }
304
305         public static void debugFlushAll() {
306                 for (int i = 0; i < 3; i++) {
307                         try {
308                                 System.gc();
309                                 Thread.sleep(100);
310                         } catch (InterruptedException x) {
311                         }
312                         CHandle stale = (CHandle)references.poll();
313                         while (stale != null) {
314                                 try {
315                                         synchronized (map) {
316                                                 map.remove(stale.p);
317                                         }
318                                         stale.release();
319                                 } catch (Throwable ex) {
320                                 }
321                                 stale = (CHandle)references.poll();
322                         }
323                 }
324         }
325
326         public static void debugDumpReachable(String title) {
327                 synchronized (map) {
328                         System.out.println(title);
329                         for (CHandle h: map.table) {
330                                 while (h != null) {
331                                         Native o = h.get();
332                                         System.out.printf(" $%016x: %s %-40s %s\n",
333                                                 h.p.toRawLongValue(),
334                                                 o == null ? "dead" : "live",
335                                                 h.jtype.getName(),
336                                                 o);
337                                         h = h.next;
338                                 }
339                         }
340                 }
341         }
342
343         /**
344          * Lightweight pointer hashtable.
345          * <p>
346          * This serves two purposes:
347          * <ol>
348          * <li>Track and resolve unique objects based on memory address;
349          * <li>Hold hard references to the WeakReference as required by the gc system.
350          * </ol>
351          * <p>
352          * CHandle's are chained directly from the index table, the p field
353          * is used as a key directly, and hash values are not cached. This combines
354          * to save significant memory per node.
355          */
356         private static class PointerTable {
357
358                 int mask = 63;
359                 int size = 0;
360                 CHandle[] table = new CHandle[64];
361
362                 private void resize(int length) {
363                         CHandle[] ntable = new CHandle[length];
364                         int nmask = length - 1;
365
366                         for (int i = 0; i < table.length; i++) {
367                                 CHandle h = table[i];
368
369                                 while (h != null) {
370                                         CHandle n = h.next;
371                                         int k = h.hashCode() & nmask;
372
373                                         h.next = ntable[k];
374                                         ntable[k] = h;
375
376                                         h = n;
377                                 }
378                         }
379
380                         table = ntable;
381                         mask = nmask;
382                 }
383
384                 public CHandle put(CHandle h) {
385                         CHandle o = remove(h.p);
386
387                         putAlways(h);
388
389                         return o;
390                 }
391
392                 public void putAlways(CHandle h) {
393                         if (size > table.length)
394                                 resize(table.length * 2);
395
396                         int i = h.hashCode() & mask;
397
398                         h.next = table[i];
399                         table[i] = h;
400                         size += 1;
401                 }
402
403                 public CHandle get(MemoryAddress p) {
404                         int i = CHandle.hashCode(p) & mask;
405                         CHandle h = table[i];
406
407                         while (h != null && !h.p.equals(p))
408                                 h = h.next;
409                         return h;
410                 }
411
412                 public CHandle remove(MemoryAddress p) {
413                         int i = CHandle.hashCode(p) & mask;
414                         CHandle h = table[i];
415                         CHandle a = null;
416
417                         while (h != null && !h.p.equals(p)) {
418                                 a = h;
419                                 h = h.next;
420                         }
421                         if (h != null) {
422                                 if (a != null)
423                                         a.next = h.next;
424                                 else
425                                         table[i] = h.next;
426                                 size -= 1;
427                         }
428
429                         return h;
430                 }
431         }
432 }