52 * Plugin to generate java.lang.invoke classes.
53 */
54 public final class GenerateJLIClassesPlugin implements Plugin {
55
56 private static final String NAME = "generate-jli-classes";
57
58 private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
59
60 private static final String DEFAULT_TRACE_FILE = "default_jli_trace.txt";
61
62 private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder";
63 private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual";
64 private static final String DMH_INVOKE_STATIC = "invokeStatic";
65 private static final String DMH_INVOKE_SPECIAL = "invokeSpecial";
66 private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial";
67 private static final String DMH_INVOKE_INTERFACE = "invokeInterface";
68 private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit";
69
70 private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder";
71 private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder";
72 private static final String INVOKERS_HOLDER = "java/lang/invoke/Invokers$Holder";
73
74 private static final JavaLangInvokeAccess JLIA
75 = SharedSecrets.getJavaLangInvokeAccess();
76
77 Set<String> speciesTypes = Set.of();
78
79 Set<String> invokerTypes = Set.of();
80
81 Map<String, Set<String>> dmhMethods = Map.of();
82
83 String mainArgument;
84
85 public GenerateJLIClassesPlugin() {
86 }
87
88 @Override
89 public String getName() {
90 return NAME;
91 }
92
93 @Override
94 public String getDescription() {
95 return DESCRIPTION;
96 }
97
98 @Override
99 public Set<State> getState() {
100 return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL);
192 new BufferedReader(
193 new InputStreamReader(traceFile)).lines());
194 }
195 } catch (Exception e) {
196 throw new PluginException("Couldn't read " + DEFAULT_TRACE_FILE, e);
197 }
198 } else {
199 File file = new File(mainArgument.substring(1));
200 if (file.exists()) {
201 readTraceConfig(fileLines(file));
202 }
203 }
204 }
205
206 private void readTraceConfig(Stream<String> lines) {
207 // Use TreeSet/TreeMap to keep things sorted in a deterministic
208 // order to avoid scrambling the layout on small changes and to
209 // ease finding methods in the generated code
210 speciesTypes = new TreeSet<>(speciesTypes);
211 invokerTypes = new TreeSet<>(invokerTypes);
212 TreeMap<String, Set<String>> newDMHMethods = new TreeMap<>();
213 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) {
214 newDMHMethods.put(entry.getKey(), new TreeSet<>(entry.getValue()));
215 }
216 dmhMethods = newDMHMethods;
217 lines.map(line -> line.split(" "))
218 .forEach(parts -> {
219 switch (parts[0]) {
220 case "[SPECIES_RESOLVE]":
221 // Allow for new types of species data classes being resolved here
222 if (parts.length == 3 && parts[1].startsWith("java.lang.invoke.BoundMethodHandle$Species_")) {
223 String species = parts[1].substring("java.lang.invoke.BoundMethodHandle$Species_".length());
224 if (!"L".equals(species)) {
225 speciesTypes.add(expandSignature(species));
226 }
227 }
228 break;
229 case "[LF_RESOLVE]":
230 String methodType = parts[3];
231 validateMethodType(methodType);
232 if (parts[1].contains("Invokers")) {
233 invokerTypes.add(methodType);
234 } else if (parts[1].contains("DirectMethodHandle")) {
235 String dmh = parts[2];
236 // ignore getObject etc for now (generated
237 // by default)
238 if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) {
239 addDMHMethodType(dmh, methodType);
240 }
241 }
242 break;
243 default: break; // ignore
244 }
245 });
246 }
247
248 private void addDMHMethodType(String dmh, String methodType) {
249 validateMethodType(methodType);
250 Set<String> methodTypes = dmhMethods.get(dmh);
251 if (methodTypes == null) {
252 methodTypes = new TreeSet<>();
253 dmhMethods.put(dmh, methodTypes);
277 throw new PluginException(
278 "Method type signature must be of form [LJIFD]*_[LJIFDV]");
279 }
280 // expand and check arguments (first part)
281 expandSignature(typeParts[0]);
282 }
283
284 private static void requireBasicType(char c) {
285 if ("LIJFD".indexOf(c) < 0) {
286 throw new PluginException(
287 "Character " + c + " must correspond to a basic field type: LIJFD");
288 }
289 }
290
291 @Override
292 public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
293 initialize(in);
294 // Copy all but DMH_ENTRY to out
295 in.transformAndCopy(entry -> {
296 // filter out placeholder entries
297 if (entry.path().equals(DIRECT_METHOD_HOLDER_ENTRY) ||
298 entry.path().equals(DELEGATING_METHOD_HOLDER_ENTRY) ||
299 entry.path().equals(INVOKERS_HOLDER_ENTRY) ||
300 entry.path().equals(BASIC_FORMS_HOLDER_ENTRY)) {
301 return null;
302 } else {
303 return entry;
304 }
305 }, out);
306
307 // Generate BMH Species classes
308 speciesTypes.forEach(types -> generateBMHClass(types, out));
309
310 // Generate LambdaForm Holder classes
311 generateHolderClasses(out);
312
313 // Let it go
314 speciesTypes = null;
315 invokerTypes = null;
316 dmhMethods = null;
317
318 return out.build();
319 }
320
344 }
345 MethodType[] directMethodTypes = new MethodType[count];
346 int[] dmhTypes = new int[count];
347 int index = 0;
348 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) {
349 String dmhType = entry.getKey();
350 for (String type : entry.getValue()) {
351 // The DMH type to actually ask for is retrieved by removing
352 // the first argument, which needs to be of Object.class
353 MethodType mt = asMethodType(type);
354 if (mt.parameterCount() < 1 ||
355 mt.parameterType(0) != Object.class) {
356 throw new PluginException(
357 "DMH type parameter must start with L");
358 }
359 directMethodTypes[index] = mt.dropParameterTypes(0, 1);
360 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType);
361 index++;
362 }
363 }
364 MethodType[] invokerMethodTypes = new MethodType[this.invokerTypes.size()];
365 int i = 0;
366 for (String invokerType : invokerTypes) {
367 // The invoker type to ask for is retrieved by removing the first
368 // and the last argument, which needs to be of Object.class
369 MethodType mt = asMethodType(invokerType);
370 final int lastParam = mt.parameterCount() - 1;
371 if (mt.parameterCount() < 2 ||
372 mt.parameterType(0) != Object.class ||
373 mt.parameterType(lastParam) != Object.class) {
374 throw new PluginException(
375 "Invoker type parameter must start and end with L");
376 }
377 mt = mt.dropParameterTypes(lastParam, lastParam + 1);
378 invokerMethodTypes[i] = mt.dropParameterTypes(0, 1);
379 i++;
380 }
381 try {
382 byte[] bytes = JLIA.generateDirectMethodHandleHolderClassBytes(
383 DIRECT_HOLDER, directMethodTypes, dmhTypes);
384 ResourcePoolEntry ndata = ResourcePoolEntry
385 .create(DIRECT_METHOD_HOLDER_ENTRY, bytes);
386 out.add(ndata);
387
388 bytes = JLIA.generateDelegatingMethodHandleHolderClassBytes(
389 DELEGATING_HOLDER, directMethodTypes);
390 ndata = ResourcePoolEntry.create(DELEGATING_METHOD_HOLDER_ENTRY, bytes);
391 out.add(ndata);
392
393 bytes = JLIA.generateInvokersHolderClassBytes(INVOKERS_HOLDER,
394 invokerMethodTypes);
395 ndata = ResourcePoolEntry.create(INVOKERS_HOLDER_ENTRY, bytes);
396 out.add(ndata);
397
398 bytes = JLIA.generateBasicFormsClassBytes(BASIC_FORMS_HOLDER);
399 ndata = ResourcePoolEntry.create(BASIC_FORMS_HOLDER_ENTRY, bytes);
400 out.add(ndata);
401 } catch (Exception ex) {
402 throw new PluginException(ex);
403 }
404 }
405 private static final String DIRECT_METHOD_HOLDER_ENTRY =
406 "/java.base/" + DIRECT_HOLDER + ".class";
407 private static final String DELEGATING_METHOD_HOLDER_ENTRY =
408 "/java.base/" + DELEGATING_HOLDER + ".class";
409 private static final String BASIC_FORMS_HOLDER_ENTRY =
410 "/java.base/" + BASIC_FORMS_HOLDER + ".class";
411 private static final String INVOKERS_HOLDER_ENTRY =
412 "/java.base/" + INVOKERS_HOLDER + ".class";
413
414 // Convert LL -> LL, L3 -> LLL
415 public static String expandSignature(String signature) {
416 StringBuilder sb = new StringBuilder();
417 char last = 'X';
418 int count = 0;
419 for (int i = 0; i < signature.length(); i++) {
420 char c = signature.charAt(i);
421 if (c >= '0' && c <= '9') {
422 count *= 10;
423 count += (c - '0');
424 } else {
425 requireBasicType(c);
426 for (int j = 1; j < count; j++) {
427 sb.append(last);
428 }
429 sb.append(c);
430 last = c;
431 count = 0;
432 }
|
52 * Plugin to generate java.lang.invoke classes.
53 */
54 public final class GenerateJLIClassesPlugin implements Plugin {
55
56 private static final String NAME = "generate-jli-classes";
57
58 private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
59
60 private static final String DEFAULT_TRACE_FILE = "default_jli_trace.txt";
61
62 private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder";
63 private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual";
64 private static final String DMH_INVOKE_STATIC = "invokeStatic";
65 private static final String DMH_INVOKE_SPECIAL = "invokeSpecial";
66 private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial";
67 private static final String DMH_INVOKE_INTERFACE = "invokeInterface";
68 private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit";
69
70 private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder";
71 private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder";
72
73 private static final String INVOKERS_HOLDER_NAME = "java.lang.invoke.Invokers$Holder";
74 private static final String INVOKERS_HOLDER_INTERNAL_NAME = INVOKERS_HOLDER_NAME.replace('.', '/');
75
76 private static final String INVOKERS_CS_HOLDER_NAME = "java.lang.invoke.Invokers$CSHolder";
77 private static final String INVOKERS_CS_HOLDER_INTERNAL_NAME = INVOKERS_CS_HOLDER_NAME.replace('.', '/');
78
79 private static final JavaLangInvokeAccess JLIA
80 = SharedSecrets.getJavaLangInvokeAccess();
81
82 Set<String> speciesTypes = Set.of();
83
84 Set<String> invokerTypes = Set.of();
85
86 Set<String> callSiteTypes = Set.of();
87
88 Map<String, Set<String>> dmhMethods = Map.of();
89
90 String mainArgument;
91
92 public GenerateJLIClassesPlugin() {
93 }
94
95 @Override
96 public String getName() {
97 return NAME;
98 }
99
100 @Override
101 public String getDescription() {
102 return DESCRIPTION;
103 }
104
105 @Override
106 public Set<State> getState() {
107 return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL);
199 new BufferedReader(
200 new InputStreamReader(traceFile)).lines());
201 }
202 } catch (Exception e) {
203 throw new PluginException("Couldn't read " + DEFAULT_TRACE_FILE, e);
204 }
205 } else {
206 File file = new File(mainArgument.substring(1));
207 if (file.exists()) {
208 readTraceConfig(fileLines(file));
209 }
210 }
211 }
212
213 private void readTraceConfig(Stream<String> lines) {
214 // Use TreeSet/TreeMap to keep things sorted in a deterministic
215 // order to avoid scrambling the layout on small changes and to
216 // ease finding methods in the generated code
217 speciesTypes = new TreeSet<>(speciesTypes);
218 invokerTypes = new TreeSet<>(invokerTypes);
219 callSiteTypes = new TreeSet<>(callSiteTypes);
220
221 TreeMap<String, Set<String>> newDMHMethods = new TreeMap<>();
222 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) {
223 newDMHMethods.put(entry.getKey(), new TreeSet<>(entry.getValue()));
224 }
225 dmhMethods = newDMHMethods;
226 lines.map(line -> line.split(" "))
227 .forEach(parts -> {
228 switch (parts[0]) {
229 case "[SPECIES_RESOLVE]":
230 // Allow for new types of species data classes being resolved here
231 if (parts.length == 3 && parts[1].startsWith("java.lang.invoke.BoundMethodHandle$Species_")) {
232 String species = parts[1].substring("java.lang.invoke.BoundMethodHandle$Species_".length());
233 if (!"L".equals(species)) {
234 speciesTypes.add(expandSignature(species));
235 }
236 }
237 break;
238 case "[LF_RESOLVE]":
239 String methodType = parts[3];
240 validateMethodType(methodType);
241 if (parts[1].equals(INVOKERS_HOLDER_NAME)) {
242 invokerTypes.add(methodType);
243 } else if (parts[1].equals(INVOKERS_CS_HOLDER_NAME)) {
244 callSiteTypes.add(methodType);
245 } else if (parts[1].contains("DirectMethodHandle")) {
246 String dmh = parts[2];
247 // ignore getObject etc for now (generated
248 // by default)
249 if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) {
250 addDMHMethodType(dmh, methodType);
251 }
252 }
253 break;
254 default: break; // ignore
255 }
256 });
257 }
258
259 private void addDMHMethodType(String dmh, String methodType) {
260 validateMethodType(methodType);
261 Set<String> methodTypes = dmhMethods.get(dmh);
262 if (methodTypes == null) {
263 methodTypes = new TreeSet<>();
264 dmhMethods.put(dmh, methodTypes);
288 throw new PluginException(
289 "Method type signature must be of form [LJIFD]*_[LJIFDV]");
290 }
291 // expand and check arguments (first part)
292 expandSignature(typeParts[0]);
293 }
294
295 private static void requireBasicType(char c) {
296 if ("LIJFD".indexOf(c) < 0) {
297 throw new PluginException(
298 "Character " + c + " must correspond to a basic field type: LIJFD");
299 }
300 }
301
302 @Override
303 public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
304 initialize(in);
305 // Copy all but DMH_ENTRY to out
306 in.transformAndCopy(entry -> {
307 // filter out placeholder entries
308 String path = entry.path();
309 if (path.equals(DIRECT_METHOD_HOLDER_ENTRY) ||
310 path.equals(DELEGATING_METHOD_HOLDER_ENTRY) ||
311 path.equals(INVOKERS_HOLDER_ENTRY) ||
312 path.equals(INVOKERS_CS_HOLDER_ENTRY) ||
313 path.equals(BASIC_FORMS_HOLDER_ENTRY)) {
314 return null;
315 } else {
316 return entry;
317 }
318 }, out);
319
320 // Generate BMH Species classes
321 speciesTypes.forEach(types -> generateBMHClass(types, out));
322
323 // Generate LambdaForm Holder classes
324 generateHolderClasses(out);
325
326 // Let it go
327 speciesTypes = null;
328 invokerTypes = null;
329 dmhMethods = null;
330
331 return out.build();
332 }
333
357 }
358 MethodType[] directMethodTypes = new MethodType[count];
359 int[] dmhTypes = new int[count];
360 int index = 0;
361 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) {
362 String dmhType = entry.getKey();
363 for (String type : entry.getValue()) {
364 // The DMH type to actually ask for is retrieved by removing
365 // the first argument, which needs to be of Object.class
366 MethodType mt = asMethodType(type);
367 if (mt.parameterCount() < 1 ||
368 mt.parameterType(0) != Object.class) {
369 throw new PluginException(
370 "DMH type parameter must start with L");
371 }
372 directMethodTypes[index] = mt.dropParameterTypes(0, 1);
373 dmhTypes[index] = DMH_METHOD_TYPE_MAP.get(dmhType);
374 index++;
375 }
376 }
377
378 // The invoker type to ask for is retrieved by removing the first
379 // and the last argument, which needs to be of Object.class
380 MethodType[] invokerMethodTypes = new MethodType[this.invokerTypes.size()];
381 int i = 0;
382 for (String invokerType : invokerTypes) {
383 MethodType mt = asMethodType(invokerType);
384 final int lastParam = mt.parameterCount() - 1;
385 if (mt.parameterCount() < 2 ||
386 mt.parameterType(0) != Object.class ||
387 mt.parameterType(lastParam) != Object.class) {
388 throw new PluginException(
389 "Invoker type parameter must start and end with Object: " + invokerType);
390 }
391 mt = mt.dropParameterTypes(lastParam, lastParam + 1);
392 invokerMethodTypes[i] = mt.dropParameterTypes(0, 1);
393 i++;
394 }
395
396 // The callSite type to ask for is retrieved by removing the last
397 // argument, which needs to be of Object.class
398 MethodType[] callSiteMethodTypes = new MethodType[this.callSiteTypes.size()];
399 i = 0;
400 for (String callSiteType : callSiteTypes) {
401 MethodType mt = asMethodType(callSiteType);
402 final int lastParam = mt.parameterCount() - 1;
403 if (mt.parameterCount() < 1 ||
404 mt.parameterType(lastParam) != Object.class) {
405 throw new PluginException(
406 "CallSite type parameter must end with Object: " + callSiteType);
407 }
408 callSiteMethodTypes[i] = mt.dropParameterTypes(lastParam, lastParam + 1);
409 i++;
410 }
411 try {
412 byte[] bytes = JLIA.generateDirectMethodHandleHolderClassBytes(
413 DIRECT_HOLDER, directMethodTypes, dmhTypes);
414 ResourcePoolEntry ndata = ResourcePoolEntry
415 .create(DIRECT_METHOD_HOLDER_ENTRY, bytes);
416 out.add(ndata);
417
418 bytes = JLIA.generateDelegatingMethodHandleHolderClassBytes(
419 DELEGATING_HOLDER, directMethodTypes);
420 ndata = ResourcePoolEntry.create(DELEGATING_METHOD_HOLDER_ENTRY, bytes);
421 out.add(ndata);
422
423 bytes = JLIA.generateInvokersHolderClassBytes(INVOKERS_HOLDER_INTERNAL_NAME,
424 invokerMethodTypes);
425 ndata = ResourcePoolEntry.create(INVOKERS_HOLDER_ENTRY, bytes);
426 out.add(ndata);
427
428 bytes = JLIA.generateCallSiteHolderClassBytes(INVOKERS_CS_HOLDER_INTERNAL_NAME,
429 callSiteMethodTypes);
430 ndata = ResourcePoolEntry.create(INVOKERS_CS_HOLDER_ENTRY, bytes);
431 out.add(ndata);
432
433 bytes = JLIA.generateBasicFormsClassBytes(BASIC_FORMS_HOLDER);
434 ndata = ResourcePoolEntry.create(BASIC_FORMS_HOLDER_ENTRY, bytes);
435 out.add(ndata);
436 } catch (Exception ex) {
437 throw new PluginException(ex);
438 }
439 }
440 private static final String DIRECT_METHOD_HOLDER_ENTRY =
441 "/java.base/" + DIRECT_HOLDER + ".class";
442 private static final String DELEGATING_METHOD_HOLDER_ENTRY =
443 "/java.base/" + DELEGATING_HOLDER + ".class";
444 private static final String BASIC_FORMS_HOLDER_ENTRY =
445 "/java.base/" + BASIC_FORMS_HOLDER + ".class";
446 private static final String INVOKERS_HOLDER_ENTRY =
447 "/java.base/" + INVOKERS_HOLDER_INTERNAL_NAME + ".class";
448 private static final String INVOKERS_CS_HOLDER_ENTRY =
449 "/java.base/" + INVOKERS_CS_HOLDER_INTERNAL_NAME + ".class";
450
451 // Convert LL -> LL, L3 -> LLL
452 public static String expandSignature(String signature) {
453 StringBuilder sb = new StringBuilder();
454 char last = 'X';
455 int count = 0;
456 for (int i = 0; i < signature.length(); i++) {
457 char c = signature.charAt(i);
458 if (c >= '0' && c <= '9') {
459 count *= 10;
460 count += (c - '0');
461 } else {
462 requireBasicType(c);
463 for (int j = 1; j < count; j++) {
464 sb.append(last);
465 }
466 sb.append(c);
467 last = c;
468 count = 0;
469 }
|