1 /* 2 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 import job.*; 27 28 static String logo = """ 29 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 30 ⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀ 31 ⠀⠀⠀⠀⠀⠀⠀ ⠙⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠀⠀⠀⠀⠀⠀⠀ 32 ⠀⠀⠀⠀⠀⠀⠀⠀⣷⣶⣤⣄⣈⣉⣉⣉⣉⣉⣉⣉⣁⣤⡄⠀⠀⠀⠀⠀⠀⠀ 33 ⠀⠀⠀⠀⠀⠀ ⠀⣿⣿⣿⣿⣿ HAT ⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀ 34 ⠀⠀⢀⣠⣶⣾⡏⢀⡈⠛⠻⠿⢿⣿⣿⣿⣿⣿⠿⠿⠟⠛⢁⠀⢶⣤⣀⠀⠀⠀ 35 ⠀⢠⣿⣿⣿⣿⡇⠸⣿⣿⣶⣶⣤⣤⣤⣤⣤⣤⣤⣶⣶⣿⡿⠂⣸⣿⣿⣷⡄⠀ 36 ⠀⢸⣿⣿⣿⣿⣿⣦⣄⡉⠛⠛⠛⠿⠿⠿⠿⠛⠛⠛⢉⣁⣤⣾⣿⣿⣿⣿⡷⠀ 37 ⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⡿⠛⠁⠀ 38 ⠀⠀⠀⠀⠈⠙⠛⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠿⠛⠛⠉⠁⠀⠀⠀⠀ 39 """; 40 static String help = """ 41 Usage bld|clean|run ... 42 bld: 43 Compile all buildable (based on capabilities) available jars and native code. 44 45 dot: 46 Create dot graph (bld.dot) of buildable dependencies (based on capabilities) 47 dot bld.dot -Tsvg > bld.svg && chrome bld.svg 48 clean: 49 Removes build directory entirely 50 conf dir and jextracted artifacts (opencl/cuda/opengl) remain 51 52 53 run: [ffi|my|seq]-[opencl|java|cuda|mock|hip] [-DXXX ...] runnable args 54 run ffi-opencl mandel 55 run ffi-opencl nbody 4096 56 run ffi-opencl -DHAT=SHOW_CODE nbody 4096 57 run ffi-opencl -DHAT=SHOW_KERNEL_MODEL heal 58 run ffi-opencl -DHAT=MINIMIZE_BUFFERS life 59 60 exp: [ffi|my|seq]-[opencl|java|cuda|mock|hip] [-DXXX ... ] experimentClassName args 61 exp ffi-opencl QuotedConstantArgs 62 63 test: [ffi|my|seq]-[opencl|java|cuda|mock|hip] 64 test ffi-opencl 65 66 sanity: Check source files for copyright and WS issues (tabs and trailing EOL WS) 67 """; 68 69 70 public static void main(String[] argArr) throws IOException, InterruptedException { 71 var args = new ArrayList<>(List.of(argArr)); 72 if (args.isEmpty()) { 73 System.out.println(help); 74 } else { 75 Path hatDir = Path.of(System.getProperty("user.dir")); 76 var project = new Project(hatDir, Reporter.progressAndErrors); 77 var cmake = project.isAvailable("cmake", "--version"); 78 if (!cmake.isAvailable()) { 79 System.out.println("We need cmake, to check the availability of opencl, cuda etc so we wont be able to build much "); 80 } 81 var jextract = project.isAvailable("jextract", "--version"); 82 if (!jextract.isAvailable()) { 83 System.out.println("We will need jextract to create jextracted backends and for examples requiring opengl "); 84 } 85 86 // A user defined optional dependency. Anything depending on this will only build if it is true 87 // In our case we pull the value from the headless system property 88 var ui = new Opt(project.id("ui"), !Boolean.getBoolean("headless")); 89 90 // These dependencies are only 'true' on the appropriate platform. 91 // So any target that depends on one of these, will only build on that platform 92 var mac = new Mac(project.id("os-mac")); 93 var linux = new Linux(project.id("os-linux")); 94 // var windows = new Windows(project.id("os-windows")); maybe one day 95 96 // These next three 'optional' dependencies use cmake to determine availability. We delegate to cmake which 97 // a) determines if capability is available, 98 // b) if they are, they extract from cmake vars (see conf/cmake-info/OpenCL/properties for example) information export headers and libs needed by JExtract 99 var openclCmakeInfo = new OpenCL(project.id("cmake-info-opencl"), cmake); 100 var openglCmakeInfo = new OpenGL(project.id("cmake-info-opengl"), cmake); 101 var cudaCmakeInfo = new Cuda(project.id("cmake-info-cuda"), cmake); 102 103 // Now we just create jars and shared libs and declare dependencies 104 var core = Jar.of(project.id("core")); 105 var tools = Jar.of(project.id("tools"), core); 106 var tests = Jar.of(project.id("tests"), core, tools); 107 var backend_ffi_native = CMake.of(project.id("backend{s}-ffi"), core, cmake); 108 var ffiSharedBackend = Jar.of(project.id("backend{s}-ffi-shared"), backend_ffi_native); 109 var backend_ffi_cuda = Jar.of(project.id("backend{s}-ffi-cuda"), ffiSharedBackend); 110 var backend_ffi_opencl = Jar.of(project.id("backend{s}-ffi-opencl"), ffiSharedBackend); 111 var backend_ffi_mock = Jar.of(project.id("backend{s}-ffi-mock"), ffiSharedBackend); 112 113 // These examples just rely on core 114 var backend_mt_java = Jar.of(project.id("backend{s}-java-mt"), core); 115 var backend_seq_java = Jar.of(project.id("backend{s}-java-seq"), core); 116 var example_squares = Jar.of(project.id("example{s}-squares"), core); 117 var example_matmul = Jar.of(project.id("example{s}-matmul"), core); 118 var example_arrayview = Jar.of(project.id("example{s}-arrayview"), core); 119 var example_blackscholes = Jar.of(project.id("example{s}-blackscholes"), core); 120 var example_normmap = Jar.of(project.id("example{s}-normmap"), core); // will probabvly need shared when we hatify 121 122 // example_shared allows us to break out common UI functions, views, even loops etc 123 var example_shared = Jar.of(project.id("example{s}-shared"), ui, core); 124 125 // These examples use example_shared, so they are UI based 126 var example_mandel = Jar.of(project.id("example{s}-mandel"), example_shared); 127 var example_life = Jar.of(project.id("example{s}-life"), example_shared); 128 var example_heal = Jar.of(project.id("example{s}-heal"), example_shared); 129 var example_violajones = Jar.of(project.id("example{s}-violajones"), example_shared); 130 131 // experiments include code that expects an opencl backend, this is not idea, but we can accomodate 132 var example_experiments = Jar.of(project.id("example{s}-experiments"), backend_ffi_opencl); 133 134 // Now we have the more complex nonsense for nbody (which needs opengl and opencl extracted) 135 var wrapped_shared = Jar.of(project.id("wrap{s}-shared")); 136 var jextracted_opencl = JExtract.extract(project.id("extract{ions|ed}-opencl"), jextract, openclCmakeInfo, core); 137 var wrapped_jextracted_opencl = Jar.of(project.id("wrap{s}-opencl"), jextracted_opencl, wrapped_shared); 138 var jextracted_opengl = JExtract.extract(project.id("extract{ions|ed}-opengl"), jextract, ui, openglCmakeInfo, core); 139 140 // Sigh... We have different src exclusions for wrapped opengl depending on the OS 141 var excludedOpenGLWrapSrc = project.rootPath().resolve( 142 "wraps/opengl/src/main/java/wrap/opengl/GL" + (mac.isAvailable() ? "Callback" : "Func") + "EventHandler.java"); 143 144 var wrapped_jextracted_opengl = Jar.of(project.id("wrap{s}-opengl"), Set.of(excludedOpenGLWrapSrc), jextracted_opengl, wrapped_shared); 145 146 // Finally we have everything needed for nbody 147 var example_nbody = Jar.of(project.id("example{s}-nbody"), ui, wrapped_jextracted_opengl, wrapped_jextracted_opencl); 148 149 while (!args.isEmpty()) { 150 var arg = args.removeFirst(); 151 switch (arg) { 152 case "help" -> System.out.println(logo + "\n" + help); 153 case "clean" -> project.clean(); 154 case "dot" -> { 155 var dag = project.all(); 156 var available = dag.available(); 157 Files.writeString(Path.of("bld.dot") , available.toDot()); 158 System.out.println("Consider...\n dot bld.dot -Tsvg > bld.svg"); 159 } 160 case "bld" -> { 161 var dag = project.all(); 162 var available = dag.available(); 163 project.build(available); 164 } 165 case "sanity" -> { 166 final var copyrightPattern = Pattern.compile("^.*Copyright.*202[0-9].*(Intel|Oracle).*$"); 167 final var copyrightExemptPattern = Pattern.compile("^(robertograham|CMakeFiles|hip)"); 168 final var tabOrEolWsPattern = Pattern.compile("^(.*\\t.*|.* )$"); 169 final var textSuffix = Pattern.compile("^(.*\\.(java|cpp|h|hpp|md)|pom.xml)$"); 170 final var sourceSuffix = Pattern.compile("^(.*\\.(java|cpp|h|hpp)|pom.xml)$"); 171 172 Stream.of("core","tools","examples","backends","docs","wraps") 173 .map(hatDir::resolve) 174 .forEach(dir-> { 175 Util.recurse(dir, 176 (d)-> true, // we do this for all subdirs 177 (f)-> textSuffix.matcher(f.getFileName().toString()).matches() && Util.grepLines(tabOrEolWsPattern, f), 178 (c)-> System.out.println("File contains WS issue (TAB or EOLWs) " + c) 179 ); 180 Util.recurse(dir, 181 (d)-> !copyrightExemptPattern.matcher(d.getFileName().toString()).matches(), // we skip these subdirs 182 (f)-> sourceSuffix.matcher(f.getFileName().toString()).matches() && !Util.grepLines(copyrightPattern, f), 183 (c)-> System.out.println("File does not contain copyright " + c) 184 ); 185 }); 186 args.clear(); 187 } 188 case "run" -> { 189 if (args.size() > 1) { 190 var backendName = args.removeFirst(); 191 var vmOpts = new ArrayList<String>(List.of()); 192 while (args.getFirst() instanceof String possibleVmOpt && possibleVmOpt.startsWith("-")){ 193 vmOpts.add(args.removeFirst()); 194 } 195 var runnableName = args.removeFirst(); 196 if (project.get(backendName) instanceof Jar backend) { 197 if (project.get(runnableName) instanceof Jar runnable) { 198 if (runnableName.equals("nbody") && mac.isAvailable()) { // nbody (anything on mac using OpenGL 199 vmOpts.add("-XstartOnFirstThread"); 200 } 201 runnable.run(runnableName + ".Main", new job.Dag(runnable, backend).ordered(), vmOpts,args); 202 } else { 203 System.err.println("Failed to find runnable " + runnableName); 204 } 205 } else { 206 System.err.println("Failed to find " + backendName); 207 } 208 } else { 209 System.err.println("For run we expect 'run backend runnable' "); 210 } 211 args.clear(); //!! :) 212 } 213 case "test" -> { 214 if (args.size() > 0) { 215 var backendName = args.removeFirst(); 216 if (project.get(backendName) instanceof Jar backend) { 217 class Stats { 218 int passed = 0; 219 int failed = 0; 220 } 221 var test_reports_txt = Paths.get("test_report.txt"); 222 Files.deleteIfExists(test_reports_txt); // because we will append to it in the next loop 223 var suiteRe = Pattern.compile("(oracle/code/hat/Test[a-zA-Z0-9]*).class"); 224 var jarFile = new JarFile(tests.jarFile().toString()); 225 var testEngine = "oracle.code.hat.engine.HatTestEngine"; 226 var entries = jarFile.entries(); 227 var orderedDag = new job.Dag(tests, backend).ordered(); 228 while (entries.hasMoreElements()) { 229 if (suiteRe.matcher(entries.nextElement().getName()) instanceof Matcher matched && matched.matches()){ 230 tests.run(testEngine, orderedDag, List.of(),List.of(matched.group(1).replace('/','.'))); 231 } 232 } 233 System.out.println("\n\n"+logo+" HAT Test Report "); 234 System.out.println("************************************************"); 235 var pattern = Pattern.compile( "passed: (\\d+), failed: (\\d+)"); 236 var stats = new Stats(); 237 Files.readAllLines(test_reports_txt).forEach(line->{ 238 System.out.println(line); 239 if (pattern.matcher(line) instanceof Matcher matcher && matcher.find()){ 240 stats.passed+=Integer.parseInt(matcher.group(1)); 241 stats.failed+=Integer.parseInt(matcher.group(2)); 242 } 243 }); 244 System.out.printf("Global passed: %d, failed: %d, pass-rate: %.2f%%", 245 stats.passed, stats.failed, ((float)(stats.passed * 100 / (stats.passed + stats.failed)))); 246 } else { 247 System.err.println("Failed to find backend " + backendName); 248 } 249 } else { 250 System.err.println("For test we require a backend "); 251 } 252 args.clear(); //!! :) 253 } 254 case "exp" -> { 255 if (args.size() > 1) { 256 var backendName = args.removeFirst(); 257 var runnableName = "experiments"; 258 var vmOpts = new ArrayList<String>(List.of()); 259 while (args.getFirst() instanceof String possibleVmOpt && possibleVmOpt.startsWith("-")){ 260 vmOpts.add(args.removeFirst()); 261 } 262 var className = args.removeFirst(); 263 if (project.get(backendName) instanceof Jar backend) { 264 if (project.get(runnableName) instanceof Jar runnable) { 265 runnable.run(runnableName + "."+className, new job.Dag(runnable, backend).ordered(), vmOpts,args); 266 } else { 267 System.err.println("Failed to find runnable " + runnableName); 268 } 269 } else { 270 System.err.println("Failed to find " + backendName); 271 } 272 } else { 273 System.err.println("For exp we expect 'exp backend [-DXXX ...] testclass' "); 274 } 275 args.clear(); //!! :) 276 } 277 default -> { 278 System.out.println("'" + arg + "' was unexpected "); 279 System.out.println(help); 280 args.clear(); 281 } 282 } 283 } 284 } 285 }