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] runnable args 54 run ffi-opencl mandel 55 run ffi-opencl nbody 4096 56 run ffi-opencl heal 4096 57 58 exp: [ffi|my|seq]-[opencl|java|cuda|mock|hip] experimentClassName args 59 exp ffi-opencl QuotedConstantArgs 60 61 test: [ffi|my|seq]-[opencl|java|cuda|mock|hip] 62 test ffi-opencl 63 64 sanity: Check source files for copyright and WS issues (tabs and trailing EOL WS) 65 """; 66 67 68 public static void main(String[] argArr) throws IOException, InterruptedException { 69 var args = new ArrayList<>(List.of(argArr)); 70 if (args.isEmpty()) { 71 System.out.println(help); 72 } else { 73 Path hatDir = Path.of(System.getProperty("user.dir")); 74 var project = new Project(hatDir, Reporter.progressAndErrors); 75 var cmake = project.isAvailable("cmake", "--version"); 76 if (!cmake.isAvailable()) { 77 System.out.println("We need cmake, to check the availability of opencl, cuda etc so we wont be able to build much "); 78 } 79 var jextract = project.isAvailable("jextract", "--version"); 80 if (!jextract.isAvailable()) { 81 System.out.println("We will need jextract to create jextracted backends and for examples requiring opengl "); 82 } 83 84 // A user defined optional dependency. Anything depending on this will only build if it is true 85 // In our case we pull the value from the headless system property 86 var ui = new Opt(project.id("ui"), !Boolean.getBoolean("headless")); 87 88 // These dependencies are only 'true' on the appropriate platform. 89 // So any target that depends on one of these, will only build on that platform 90 var mac = new Mac(project.id("os-mac")); 91 var linux = new Linux(project.id("os-linux")); 92 // var windows = new Windows(project.id("os-windows")); maybe one day 93 94 // These next three 'optional' dependencies use cmake to determine availability. We delegate to cmake which 95 // a) determines if capability is available, 96 // 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 97 var openclCmakeInfo = new OpenCL(project.id("cmake-info-opencl"), cmake); 98 var openglCmakeInfo = new OpenGL(project.id("cmake-info-opengl"), cmake); 99 var cudaCmakeInfo = new Cuda(project.id("cmake-info-cuda"), cmake); 100 101 // Now we just create jars and shared libs and declare dependencies 102 var core = Jar.of(project.id("core")); 103 var tools = Jar.of(project.id("tools"), core); 104 var tests = Jar.of(project.id("tests"), core, tools); 105 var backend_ffi_native = CMake.of(project.id("backend{s}-ffi"), core, cmake); 106 var ffiSharedBackend = Jar.of(project.id("backend{s}-ffi-shared"), backend_ffi_native); 107 var backend_ffi_cuda = Jar.of(project.id("backend{s}-ffi-cuda"), ffiSharedBackend); 108 var backend_ffi_opencl = Jar.of(project.id("backend{s}-ffi-opencl"), ffiSharedBackend); 109 var backend_ffi_mock = Jar.of(project.id("backend{s}-ffi-mock"), ffiSharedBackend); 110 var backend_mt_java = Jar.of(project.id("backend{s}-java-mt"), core); 111 var backend_seq_java = Jar.of(project.id("backend{s}-java-seq"), core); 112 var example_shared = Jar.of(project.id("example{s}-shared"), ui, core); 113 var example_blackscholes = Jar.of(project.id("example{s}-blackscholes"), example_shared); 114 var example_mandel = Jar.of(project.id("example{s}-mandel"), example_shared); 115 var example_life = Jar.of(project.id("example{s}-life"), example_shared); 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_heal = Jar.of(project.id("example{s}-heal"), example_shared); 119 var example_violajones = Jar.of(project.id("example{s}-violajones"), example_shared); 120 var example_experiments = Jar.of(project.id("example{s}-experiments"), backend_ffi_opencl); //experiments have some code that expect opencl backend 121 122 var wrapped_shared = Jar.of(project.id("wrap{s}-shared")); 123 var jextracted_opencl = JExtract.extract(project.id("extract{ions|ed}-opencl"), jextract, openclCmakeInfo, core); 124 var wrapped_jextracted_opencl = Jar.of(project.id("wrap{s}-opencl"), jextracted_opencl, wrapped_shared); 125 126 var jextracted_opengl = JExtract.extract(project.id("extract{ions|ed}-opengl"), jextract, ui, openglCmakeInfo, core); 127 128 // Sigh... We have different src exclusions for wrapped opengl depending on the OS 129 var excludedOpenGLWrapSrc = project.rootPath().resolve( 130 "wraps/opengl/src/main/java/wrap/opengl/GL" + (mac.isAvailable() ? "Callback" : "Func") + "EventHandler.java"); 131 132 var wrapped_jextracted_opengl = Jar.of(project.id("wrap{s}-opengl"), Set.of(excludedOpenGLWrapSrc), jextracted_opengl, wrapped_shared); 133 134 var example_nbody = Jar.of(project.id("example{s}-nbody"), ui, wrapped_jextracted_opengl, wrapped_jextracted_opencl); 135 136 while (!args.isEmpty()) { 137 var arg = args.removeFirst(); 138 switch (arg) { 139 case "help" -> System.out.println(logo + "\n" + help); 140 case "clean" -> project.clean(); 141 case "dot" -> { 142 var dag = project.all(); 143 var available = dag.available(); 144 Files.writeString(Path.of("bld.dot") , available.toDot()); 145 System.out.println("Consider...\n dot bld.dot -Tsvg > bld.svg"); 146 } 147 case "bld" -> { 148 var dag = project.all(); 149 var available = dag.available(); 150 project.build(available); 151 } 152 case "sanity" -> { 153 final var copyrightPattern = Pattern.compile("^.*Copyright.*202[0-9].*(Intel|Oracle).*$"); 154 final var copyrightExemptPattern = Pattern.compile("^(robertograham|CMakeFiles|hip)"); 155 final var tabOrEolWsPattern = Pattern.compile("^(.*\\t.*|.* )$"); 156 final var textSuffix = Pattern.compile("^(.*\\.(java|cpp|h|hpp|md)|pom.xml)$"); 157 final var sourceSuffix = Pattern.compile("^(.*\\.(java|cpp|h|hpp)|pom.xml)$"); 158 159 Stream.of("core","tools","examples","backends","docs","wraps") 160 .map(hatDir::resolve) 161 .forEach(dir-> { 162 Util.recurse(dir, 163 (d)-> true, // we do this for all subdirs 164 (f)-> textSuffix.matcher(f.getFileName().toString()).matches() && Util.grepLines(tabOrEolWsPattern, f), 165 (c)-> System.out.println("File contains WS issue (TAB or EOLWs) " + c) 166 ); 167 Util.recurse(dir, 168 (d)-> !copyrightExemptPattern.matcher(d.getFileName().toString()).matches(), // we skip these subdirs 169 (f)-> sourceSuffix.matcher(f.getFileName().toString()).matches() && !Util.grepLines(copyrightPattern, f), 170 (c)-> System.out.println("File does not contain copyright " + c) 171 ); 172 }); 173 args.clear(); 174 } 175 case "run" -> { 176 if (args.size() > 1) { 177 var backendName = args.removeFirst(); 178 var runnableName = args.removeFirst(); 179 if (project.get(backendName) instanceof Jar backend) { 180 if (project.get(runnableName) instanceof Jar runnable) { 181 var vmOpts = new ArrayList<String>(List.of( 182 // "-DnoModuleOp=true", 183 // "-DbufferTagging=true" 184 )); 185 if (runnableName.equals("nbody") && mac.isAvailable()) { // nbody (anything on mac using OpenGL 186 vmOpts.add("-XstartOnFirstThread"); 187 } 188 runnable.run(runnableName + ".Main", new job.Dag(runnable, backend).ordered(), vmOpts,args); 189 } else { 190 System.err.println("Failed to find runnable " + runnableName); 191 } 192 } else { 193 System.err.println("Failed to find " + backendName); 194 } 195 } else { 196 System.err.println("For run we expect 'run backend runnable' "); 197 } 198 args.clear(); //!! :) 199 } 200 case "test" -> { 201 if (args.size() > 0) { 202 var backendName = args.removeFirst(); 203 if (project.get(backendName) instanceof Jar backend) { 204 class Stats { 205 int passed = 0; 206 int failed = 0; 207 } 208 var test_reports_txt = Paths.get("test_report.txt"); 209 Files.deleteIfExists(test_reports_txt); // because we will append to it in the next loop 210 Stream.of( "Arrays", "MatMul", "Mandel", "Local", "Reductions") 211 .map(s->"oracle.code.hat.Test"+s) 212 .forEach(suite->{ 213 tests.run("oracle.code.hat.engine.HatTestEngine", 214 new job.Dag(tests, backend).ordered(), List.of(),List.of(suite)); 215 }); 216 System.out.println("\n\n"+logo+" HAT Test Report "); 217 System.out.println("************************************************"); 218 var pattern = Pattern.compile( "passed: (\\d+), failed: (\\d+)"); 219 var stats = new Stats(); 220 Files.readAllLines(test_reports_txt).forEach(line->{ 221 System.out.println(line); 222 if (pattern.matcher(line) instanceof Matcher matcher && matcher.find()){ 223 stats.passed+=Integer.parseInt(matcher.group(1)); 224 stats.failed+=Integer.parseInt(matcher.group(2)); 225 } 226 }); 227 System.out.printf("Global passed: %d, failed: %d, pass-rate: %.2f%%", 228 stats.passed, stats.failed, ((float)(stats.passed * 100 / (stats.passed + stats.failed)))); 229 } else { 230 System.err.println("Failed to find backend " + backendName); 231 } 232 } else { 233 System.err.println("For test we require a backend "); 234 } 235 args.clear(); //!! :) 236 } 237 case "exp" -> { 238 if (args.size() > 1) { 239 var backendName = args.removeFirst(); 240 var runnableName = "experiments"; 241 var className = args.removeFirst(); 242 if (project.get(backendName) instanceof Jar backend) { 243 if (project.get(runnableName) instanceof Jar runnable) { 244 var vmOpts = new ArrayList<String>(List.of( 245 // "-DnoModuleOp=true", 246 // "-DbufferTagging=true" 247 )); 248 runnable.run(runnableName + "."+className, new job.Dag(runnable, backend).ordered(), vmOpts,args); 249 } else { 250 System.err.println("Failed to find runnable " + runnableName); 251 } 252 } else { 253 System.err.println("Failed to find " + backendName); 254 } 255 } else { 256 System.err.println("For run we expect 'run backend runnable' "); 257 } 258 args.clear(); //!! :) 259 } 260 default -> { 261 System.out.println("'" + arg + "' was unexpected "); 262 System.out.println(help); 263 args.clear(); 264 } 265 } 266 } 267 } 268 }