001/*
002 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package jdk.internal.jvmci.hotspotvmconfig.processor;
024
025import java.io.*;
026import java.lang.annotation.*;
027import java.util.*;
028import java.util.Map.Entry;
029import java.util.function.*;
030
031import javax.annotation.processing.*;
032import javax.lang.model.*;
033import javax.lang.model.element.*;
034import javax.tools.Diagnostic.Kind;
035import javax.tools.*;
036
037import jdk.internal.jvmci.common.*;
038import jdk.internal.jvmci.hotspotvmconfig.*;
039
040@SupportedAnnotationTypes({
041    // @formatter:off
042    "jdk.internal.jvmci.hotspotvmconfig.HotSpotVMConstant",
043    "jdk.internal.jvmci.hotspotvmconfig.HotSpotVMFlag",
044    "jdk.internal.jvmci.hotspotvmconfig.HotSpotVMField",
045    "jdk.internal.jvmci.hotspotvmconfig.HotSpotVMType",
046    "jdk.internal.jvmci.hotspotvmconfig.HotSpotVMValue"})
047    // @formatter:on
048public class HotSpotVMConfigProcessor extends AbstractProcessor {
049
050    public HotSpotVMConfigProcessor() {
051    }
052
053    @Override
054    public SourceVersion getSupportedSourceVersion() {
055        return SourceVersion.latest();
056    }
057
058    /**
059     * Set to true to enable logging to a local file during annotation processing. There's no normal
060     * channel for any debug messages and debugging annotation processors requires some special
061     * setup.
062     */
063    private static final boolean DEBUG = false;
064
065    private PrintWriter log;
066
067    /**
068     * Logging facility for debugging the annotation processor.
069     */
070
071    private PrintWriter getLog() {
072        if (log == null) {
073            try {
074                // Create the log file within the generated source directory so it's easy to find.
075                // /tmp isn't platform independent and java.io.tmpdir can map anywhere, particularly
076                // on the mac.
077                FileObject file = processingEnv.getFiler().createResource(StandardLocation.SOURCE_OUTPUT, "", getClass().getSimpleName() + "log");
078                log = new PrintWriter(new FileWriter(file.toUri().getPath(), true));
079            } catch (IOException e) {
080                // Do nothing
081            }
082        }
083        return log;
084    }
085
086    private void logMessage(String format, Object... args) {
087        if (!DEBUG) {
088            return;
089        }
090        PrintWriter bw = getLog();
091        if (bw != null) {
092            bw.printf(format, args);
093            bw.flush();
094        }
095    }
096
097    private void logException(Throwable t) {
098        if (!DEBUG) {
099            return;
100        }
101        PrintWriter bw = getLog();
102        if (bw != null) {
103            t.printStackTrace(bw);
104            bw.flush();
105        }
106    }
107
108    /**
109     * Bugs in an annotation processor can cause silent failure so try to report any exception
110     * throws as errors.
111     */
112    private void reportExceptionThrow(Element element, Throwable t) {
113        if (element != null) {
114            logMessage("throw for %s:\n", element);
115        }
116        logException(t);
117        errorMessage(element, "Exception throw during processing: %s %s", t, Arrays.toString(Arrays.copyOf(t.getStackTrace(), 4)));
118    }
119
120    //@formatter:off
121    String[] prologue = new String[]{
122        "// The normal wrappers CommandLineFlags::boolAt and CommandLineFlags::intxAt skip constant flags",
123        "static bool boolAt(char* name, bool* value) {",
124        "  Flag* result = Flag::find_flag(name, strlen(name), true, true);",
125        "  if (result == NULL) return false;",
126        "  if (!result->is_bool()) return false;",
127        "  *value = result->get_bool();",
128        "  return true;",
129        "}",
130        "",
131        "static bool intxAt(char* name, intx* value) {",
132        "  Flag* result = Flag::find_flag(name, strlen(name), true, true);",
133        "  if (result == NULL) return false;",
134        "  if (!result->is_intx()) return false;",
135        "  *value = result->get_intx();",
136        "  return true;",
137        "}",
138        "",
139        "#define set_boolean(name, value) vmconfig_oop->bool_field_put(fs.offset(), value)",
140        "#define set_byte(name, value) vmconfig_oop->byte_field_put(fs.offset(), (jbyte)(value))",
141        "#define set_short(name, value) vmconfig_oop->short_field_put(fs.offset(), (jshort)(value))",
142        "#define set_int(name, value) vmconfig_oop->int_field_put(fs.offset(), (int)(value))",
143        "#define set_long(name, value) vmconfig_oop->long_field_put(fs.offset(), value)",
144        "#define set_address(name, value) do { set_long(name, (jlong)(value)); } while (0)",
145        "",
146        "#define set_optional_boolean_flag(varName, flagName) do { bool flagValue; if (boolAt((char*) flagName, &flagValue)) { set_boolean(varName, flagValue); } } while (0)",
147        "#define set_optional_int_flag(varName, flagName) do { intx flagValue; if (intxAt((char*) flagName, &flagValue)) { set_int(varName, flagValue); } } while (0)",
148        "#define set_optional_long_flag(varName, flagName) do { intx flagValue; if (intxAt((char*) flagName, &flagValue)) { set_long(varName, flagValue); } } while (0)",
149        "",
150        "void VMStructs::initHotSpotVMConfig(oop vmconfig_oop) {",
151        "  InstanceKlass* vmconfig_klass = InstanceKlass::cast(vmconfig_oop->klass());",
152        "",
153    };
154    //@formatter:on
155
156    String outputName = "HotSpotVMConfig.inline.hpp";
157    String outputDirectory = "hotspot";
158
159    private void createFiles(Map<String, VMConfigField> annotations, Element element) {
160
161        Filer filer = processingEnv.getFiler();
162        try (PrintWriter out = createSourceFile(outputDirectory, outputName, filer, element)) {
163
164            for (String line : prologue) {
165                out.println(line);
166            }
167
168            Map<String, Integer> expectedValues = new HashMap<>();
169            for (VMConfigField value : annotations.values()) {
170                if (!value.optional) {
171                    String key = value.define != null ? value.define : "";
172                    if (expectedValues.get(key) == null) {
173                        expectedValues.put(key, 1);
174                    } else {
175                        expectedValues.put(key, expectedValues.get(key) + 1);
176                    }
177                }
178            }
179
180            out.printf("  int expected = %s;%n", expectedValues.get(""));
181            for (Entry<String, Integer> entry : expectedValues.entrySet()) {
182                if (entry.getKey().equals("")) {
183                    continue;
184                }
185                out.printf("#if %s%n", entry.getKey());
186                out.printf("  expected += %s;%n", entry.getValue());
187                out.printf("#endif%n");
188            }
189            out.println("  int assigned = 0;");
190            out.println("  for (JavaFieldStream fs(vmconfig_klass); !fs.done(); fs.next()) {");
191
192            Set<String> fieldTypes = new HashSet<>();
193            for (VMConfigField key : annotations.values()) {
194                fieldTypes.add(key.getType());
195            }
196            // For each type of field, generate a switch on the length of the symbol and then do a
197            // direct compare. In general this reduces each operation to 2 tests plus a string
198            // compare. Being more perfect than that is probably not worth it.
199            for (String type : fieldTypes) {
200                String sigtype = type.equals("boolean") ? "bool" : type;
201                out.println("    if (fs.signature() == vmSymbols::" + sigtype + "_signature()) {");
202                Set<Integer> lengths = new HashSet<>();
203                for (Entry<String, VMConfigField> entry : annotations.entrySet()) {
204                    if (entry.getValue().getType().equals(type)) {
205                        lengths.add(entry.getKey().length());
206                    }
207                }
208                out.println("      switch (fs.name()->utf8_length()) {");
209                for (int len : lengths) {
210                    out.println("        case " + len + ":");
211                    for (Entry<String, VMConfigField> entry : annotations.entrySet()) {
212                        if (entry.getValue().getType().equals(type) && entry.getKey().length() == len) {
213                            out.println("          if (fs.name()->equals(\"" + entry.getKey() + "\")) {");
214                            entry.getValue().emit(out);
215                            out.println("            continue;");
216                            out.println("          }");
217                        }
218                    }
219                    out.println("          continue;");
220                }
221                out.println("      } // switch");
222                out.println("      continue;");
223                out.println("    } // if");
224            }
225            out.println("  } // for");
226            out.println("  guarantee(assigned == expected, \"Didn't find all fields during init of HotSpotVMConfig.  Maybe recompile?\");");
227            out.println("}");
228        }
229    }
230
231    protected PrintWriter createSourceFile(String pkg, String relativeName, Filer filer, Element... originatingElements) {
232        try {
233            // Ensure Unix line endings to comply with code style guide checked by Checkstyle
234            FileObject sourceFile = filer.createResource(StandardLocation.SOURCE_OUTPUT, pkg, relativeName, originatingElements);
235            logMessage("%s\n", sourceFile);
236            return new PrintWriter(sourceFile.openWriter()) {
237
238                @Override
239                public void println() {
240                    print("\n");
241                }
242            };
243        } catch (IOException e) {
244            throw new RuntimeException(e);
245        }
246    }
247
248    static class VMConfigField {
249        final String setter;
250        final String define;
251        private boolean optional;
252        final VariableElement field;
253
254        public VMConfigField(VariableElement field, HotSpotVMField value) {
255            this.field = field;
256            define = archDefines(value.archs());
257            String type = field.asType().toString();
258            String name = value.name();
259            int i = name.lastIndexOf("::");
260            switch (value.get()) {
261                case OFFSET:
262                    setter = String.format("set_%s(\"%s\", offset_of(%s, %s));", type, field.getSimpleName(), name.substring(0, i), name.substring(i + 2));
263                    break;
264                case ADDRESS:
265                    setter = String.format("set_address(\"%s\", &%s);", field.getSimpleName(), name);
266                    break;
267                case VALUE:
268                    setter = String.format("set_%s(\"%s\", (%s) (intptr_t) %s);", type, field.getSimpleName(), type, name);
269                    break;
270                default:
271                    throw new JVMCIError("unexpected type: " + value.get());
272            }
273        }
274
275        public VMConfigField(VariableElement field, HotSpotVMType value) {
276            this.field = field;
277            define = null; // ((HotSpotVMType) annotation).archs();
278            String type = field.asType().toString();
279            setter = String.format("set_%s(\"%s\", sizeof(%s));", type, field.getSimpleName(), value.name());
280        }
281
282        public VMConfigField(VariableElement field, HotSpotVMValue value) {
283            this.field = field;
284            String[] defines = value.defines();
285            int length = defines.length;
286            if (length != 0) {
287                for (int i = 0; i < length; i++) {
288                    defines[i] = "defined(" + defines[i] + ")";
289                }
290                define = String.join(" || ", defines);
291            } else {
292                define = null; // ((HotSpotVMValue) annotation).archs();
293            }
294            String type = field.asType().toString();
295            if (value.get() == HotSpotVMValue.Type.ADDRESS) {
296                setter = String.format("set_address(\"%s\", %s);", field.getSimpleName(), value.expression());
297            } else {
298                setter = String.format("set_%s(\"%s\", %s);", type, field.getSimpleName(), value.expression());
299            }
300        }
301
302        public VMConfigField(VariableElement field, HotSpotVMConstant value) {
303            this.field = field;
304            define = archDefines(value.archs());
305            String type = field.asType().toString();
306            setter = String.format("set_%s(\"%s\", %s);", type, field.getSimpleName(), value.name());
307        }
308
309        public VMConfigField(VariableElement field, HotSpotVMFlag value) {
310            this.field = field;
311            define = archDefines(value.archs());
312            optional = value.optional();
313            String type = field.asType().toString();
314            if (value.optional()) {
315                setter = String.format("set_optional_%s_flag(\"%s\",  \"%s\");", type, field.getSimpleName(), value.name());
316            } else {
317                setter = String.format("set_%s(\"%s\", %s);", type, field.getSimpleName(), value.name());
318            }
319        }
320
321        public String getType() {
322            return field.asType().toString();
323        }
324
325        private static String archDefine(String arch) {
326            switch (arch) {
327                case "amd64":
328                    return "defined(AMD64)";
329                case "sparcv9":
330                    return "(defined(SPARC) && defined(_LP64))";
331                case "sparc":
332                    return "defined(SPARC)";
333                default:
334                    throw new JVMCIError("unexpected arch: " + arch);
335            }
336        }
337
338        private static String archDefines(String[] archs) {
339            if (archs == null || archs.length == 0) {
340                return null;
341            }
342            if (archs.length == 1) {
343                return archDefine(archs[0]);
344            }
345            String[] defs = new String[archs.length];
346            int i = 0;
347            for (String arch : archs) {
348                defs[i++] = archDefine(arch);
349            }
350            return String.join(" || ", defs);
351        }
352
353        public void emit(PrintWriter out) {
354            if (define != null) {
355                out.printf("#if %s\n", define);
356            }
357            out.printf("            %s%n", setter);
358            if (!optional) {
359                out.printf("            assigned++;%n");
360            }
361            if (define != null) {
362                out.printf("#endif\n");
363            }
364        }
365
366    }
367
368    @SuppressWarnings("unchecked")
369    private <T extends Annotation> void collectAnnotations(RoundEnvironment roundEnv, Map<String, VMConfigField> annotationMap, Class<T> annotationClass,
370                    BiFunction<VariableElement, T, VMConfigField> builder) {
371        for (Element element : roundEnv.getElementsAnnotatedWith(annotationClass)) {
372            Annotation constant = element.getAnnotation(annotationClass);
373            if (element.getKind() != ElementKind.FIELD) {
374                errorMessage(element, "%s annotations may only be on fields", annotationClass.getSimpleName());
375            }
376            if (annotationClass == HotSpotVMValue.class) {
377                HotSpotVMValue value = (HotSpotVMValue) constant;
378                if (value.get() == HotSpotVMValue.Type.ADDRESS && !element.asType().toString().equals("long")) {
379                    errorMessage(element, "HotSpotVMValue with get == ADDRESS must be of type long, but found %s", element.asType());
380                }
381            }
382            if (currentTypeElement == null) {
383                currentTypeElement = element.getEnclosingElement();
384            } else {
385                if (!currentTypeElement.equals(element.getEnclosingElement())) {
386                    errorMessage(element, "Multiple types encountered.  Only HotSpotVMConfig is supported");
387                }
388            }
389            annotationMap.put(element.getSimpleName().toString(), builder.apply((VariableElement) element, (T) constant));
390        }
391    }
392
393    private void errorMessage(Element element, String format, Object... args) {
394        processingEnv.getMessager().printMessage(Kind.ERROR, String.format(format, args), element);
395    }
396
397    Element currentTypeElement = null;
398
399    @Override
400    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
401        if (roundEnv.processingOver()) {
402            return true;
403        }
404        logMessage("Starting round %s %s\n", roundEnv, annotations);
405        try {
406
407            currentTypeElement = null;
408
409            // First collect all the annotations.
410            Map<String, VMConfigField> annotationMap = new HashMap<>();
411            collectAnnotations(roundEnv, annotationMap, HotSpotVMConstant.class, (e, v) -> new VMConfigField(e, v));
412            collectAnnotations(roundEnv, annotationMap, HotSpotVMFlag.class, (e, v) -> new VMConfigField(e, v));
413            collectAnnotations(roundEnv, annotationMap, HotSpotVMField.class, (e, v) -> new VMConfigField(e, v));
414            collectAnnotations(roundEnv, annotationMap, HotSpotVMType.class, (e, v) -> new VMConfigField(e, v));
415            collectAnnotations(roundEnv, annotationMap, HotSpotVMValue.class, (e, v) -> new VMConfigField(e, v));
416
417            if (annotationMap.isEmpty()) {
418                return true;
419            }
420
421            logMessage("type element %s\n", currentTypeElement);
422            createFiles(annotationMap, currentTypeElement);
423
424        } catch (Throwable t) {
425            reportExceptionThrow(null, t);
426        }
427
428        return true;
429    }
430}