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 void logo(){
29 System.out.println("""
30 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
31 ⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀
32 ⠀⠀⠀⠀⠀⠀⠀ ⠙⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠀⠀⠀⠀⠀⠀⠀
33 ⠀⠀⠀⠀⠀⠀⠀⠀⣷⣶⣤⣄⣈⣉⣉⣉⣉⣉⣉⣉⣁⣤⡄⠀⠀⠀⠀⠀⠀⠀
34 ⠀⠀⠀⠀⠀⠀ ⠀⣿⣿⣿⣿⣿ HAT ⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀
35 ⠀⠀⢀⣠⣶⣾⡏⢀⡈⠛⠻⠿⢿⣿⣿⣿⣿⣿⠿⠿⠟⠛⢁⠀⢶⣤⣀⠀⠀⠀
36 ⠀⢠⣿⣿⣿⣿⡇⠸⣿⣿⣶⣶⣤⣤⣤⣤⣤⣤⣤⣶⣶⣿⡿⠂⣸⣿⣿⣷⡄⠀
37 ⠀⢸⣿⣿⣿⣿⣿⣦⣄⡉⠛⠛⠛⠿⠿⠿⠿⠛⠛⠛⢉⣁⣤⣾⣿⣿⣿⣿⡷⠀
38 ⠀⠀⠙⢿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⡿⠛⠁⠀
39 ⠀⠀⠀⠀⠈⠙⠛⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠿⠛⠛⠉⠁⠀⠀⠀⠀
40 """);
41 }
42 static void help(){
43 System.out.println("""
44 Usage bld|clean|run ...
45 bld:
46 Compile all buildable (based on capabilities) available jars and native code.
47
48 dot:
49 Create dot graph (bld.dot) of buildable dependencies (based on capabilities)
50 dot bld.dot -Tsvg > bld.svg && chrome bld.svg
51 clean:
52 Removes build directory entirely
53 conf dir and jextracted artifacts (opencl/cuda/opengl) remain
54
55
56 run: [ffi|my|seq]-[opencl|java|cuda|mock|hip] [-DXXX ...] runnable args
57 run ffi-opencl mandel
58 run ffi-opencl nbody HAT
59 run ffi-opencl nbodygl 4096
60 run ffi-opencl -DHAT=SHOW_CODE nbodygl 4096
61 run ffi-opencl -DHAT=SHOW_KERNEL_MODEL heal
62 run ffi-opencl -DHAT=MINIMIZE_BUFFERS life
63
64 exp: [ffi|my|seq]-[opencl|java|cuda|mock|hip] [-DXXX ... ] experimentClassName args
65 exp ffi-opencl QuotedConstantArgs
66
67 test-suite: [ffi|my|seq]-[opencl|java|cuda|mock|hip]
68 test-suite ffi-opencl
69
70 test: [ffi|my|seq]-[opencl|java|cuda|mock|hip] classToTest (also classToTest#method)
71 test ffi-opencl hat.test.TestMatMul
72
73 sanity: Check source files for copyright and WS issues (tabs and trailing EOL WS)
74 """);
75 }
76
77 static void logoAndHelp(){
78 logo();
79 help();
80 }
81
82 private static class Colours {
83 public static String RED = "\u001b[31m";
84 public static String GREEN = "\u001b[32m";
85 public static String BLUE = "\u001b[34m";
86 public static String CYAN = "\u001b[36m";
87 public static String YELLOW = "\u001b[33m";
88 public static String RESET = "\u001b[0m";
89 }
90
91 public static void main(String[] argArr) throws IOException, InterruptedException {
92 var args = new ArrayList<>(List.of(argArr));
93 if (args.isEmpty()) {
94 help();
95 } else {
96 var hat = new Project(
97 Util.currentDirAsPath(),
98 // These opts will be applied to all javac, cmake and jextract tools
99 Jar.JavacConfig.of(o -> o.progress(true)
100 .debug().enablePreview().source(26).addModules("jdk.incubator.code")
101 ),
102 CMake.Config.of(o -> o.progress(true)),
103 JExtract.Config.of(o -> o.progress(true))
104 );
105
106 var commonJavaOpts = Jar.JavaConfig.of(o -> o
107 .verbose(true)
108 .command(true)
109 .enablePreview()
110 .addModules("jdk.incubator.code")
111 .enableNativeAccess("ALL-UNNAMED"));
112
113 var cmake = hat.isAvailable("cmake", "--version");
114 if (!cmake.isAvailable()) {
115 System.out.println("We need cmake, to check the availability of opencl, cuda etc so we wont be able to build much ");
116 }
117 var jextract = hat.isAvailable("jextract", "--version");
118 if (!jextract.isAvailable()) {
119 System.out.println("We will need jextract to create jextracted backends and for examples requiring opengl ");
120 }
121
122 // This is an example of a user defined optional dependency.
123 // Anything depending on this will only build if (In this case) the property is true
124 // So in our case if the headless system property
125 var ui = new Opt(hat.id("ui"), !Boolean.getBoolean("headless")); //-Dheadless=true|false
126
127 // These dependencies are 'true' on the appropriate platform.
128 // So any target depending on one these, will only build on that platform
129 var mac = new Mac(hat.id("os-mac"));
130 var linux = new Linux(hat.id("os-linux"));
131
132 // These next three 'optional' dependencies use cmake to determine availability. We delegate to cmake which
133 // a) determines if capability is available,
134 // b) if they are, they extract from cmake vars (see conf/cmake-info/OpenCL/properties for example)
135 // information export headers and libs needed by JExtract
136 var jextractOpts = JExtract.Config.of(o -> o.command(true));
137 var cmakeOpts = CMake.Config.of(o -> o.command(true));
138 var openclCmakeInfo = new OpenCL(hat.id("cmake-info-opencl"), cmake);
139 var openglCmakeInfo = new OpenGL(hat.id("cmake-info-opengl"), cmake);
140 var cudaCmakeInfo = new Cuda(hat.id("cmake-info-cuda"), cmake);
141
142 // Now we just create jars and shared libs and declare dependencies
143 var optkl = hat.jar("optkl");
144 var core = hat.jar("core", optkl);
145 //var tools = hat.jar("tools", core);
146 var tests = hat.jar("tests", core);
147
148 var backend_ffi_native = hat.cmakeAndJar("backend{s}-ffi", core, cmake);
149 var ffiSharedBackend = hat.jar("backend{s}-ffi-shared", backend_ffi_native);
150 var backend_ffi_cuda = hat.jar("backend{s}-ffi-cuda", ffiSharedBackend);
151 var backend_ffi_opencl = hat.jar("backend{s}-ffi-opencl", ffiSharedBackend);
152 var backend_ffi_mock = hat.jar("backend{s}-ffi-mock", ffiSharedBackend);
153
154 // These examples just rely on core
155 var backend_mt_java = hat.jar("backend{s}-java-mt", core);
156 var backend_seq_java = hat.jar("backend{s}-java-seq", core);
157 var example_squares = hat.jar("example{s}-squares", core);
158 var example_blackscholes = hat.jar("example{s}-blackscholes", core);
159 var example_view = hat.jar("example{s}-view", core);
160 var example_normmap = hat.jar("example{s}-normmap", core); // will probabvly need shared when we hatify
161
162 // example_shared allows us to break out common UI functions, views, even loops etc
163 var example_shared = hat.jar("example{s}-shared", ui, core);
164 var example_nbody = hat.jar("example{s}-nbody", ui, core);
165
166 var example_flash_attention = hat.jar("example{s}-flashattention", core, example_shared);
167 var example_dft = hat.jar("example{s}-dft", core, example_shared);
168 var example_fft = hat.jar("example{s}-fft", core, example_shared);
169 var example_matmul = hat.jar("example{s}-matmul", core, example_shared);
170
171 // These examples use example_shared, so they are UI based
172 var example_mandel = hat.jar("example{s}-mandel", example_shared);
173 var example_life = hat.jar("example{s}-life", example_shared);
174 var example_heal = hat.jar("example{s}-heal", example_shared);
175 var example_shade = hat.jar("example{s}-shade", example_shared);
176 var example_violajones = hat.jar("example{s}-violajones", example_shared);
177
178 // experiments include code that expects an opencl backend, this is not idea, but we can accomodate
179 var example_experiments = hat.jar("example{s}-experiments", core);
180
181 // Now we have the more complex nonsense for nbodygl (which needs opengl and opencl extracted)
182 var wrapped_shared = hat.jar("wrap{s}-shared");
183 var jextracted_opencl = hat.jextract("extract{ions|ed}-opencl", jextract, openclCmakeInfo, core);
184 var wrapped_jextracted_opencl = hat.jar("wrap{s}-opencl", jextracted_opencl, wrapped_shared);
185 var backend_jextracted_shared = hat.jar("backend{s}-jextracted-shared", core);
186 var backend_jextracted_opencl = hat.jar("backend{s}-jextracted-opencl", wrapped_jextracted_opencl, backend_jextracted_shared);
187 var jextracted_opengl = hat.jextract("extract{ions|ed}-opengl", jextract, ui, openglCmakeInfo, core);
188
189 // Sigh... We have different src exclusions for wrapped opengl depending on the OS
190 var excludedOpenGLWrapSrc = hat.rootPath().resolve(
191 "wraps/opengl/src/main/java/wrap/opengl/GL" + (mac.isAvailable() ? "Callback" : "Func") + "EventHandler.java");
192
193 var wrapped_jextracted_opengl = hat.jar("wrap{s}-opengl", Set.of(excludedOpenGLWrapSrc), jextracted_opengl, wrapped_shared);
194
195 // Finally we have everything needed for nbodygl
196 var example_nbodygl = hat.jar("example{s}-nbodygl", ui, wrapped_jextracted_opengl, wrapped_jextracted_opencl);
197
198 var listOfExamples = List.of(
199 example_squares,
200 example_matmul,
201 example_flash_attention,
202 example_blackscholes,
203 example_view,
204 example_normmap,
205 example_nbody,
206 example_mandel,
207 example_life,
208 example_heal,
209 example_shade,
210 example_violajones,
211 example_experiments,
212 example_nbodygl
213 );
214 //listOfExamples.forEach(jar->System.out.println(jar.id().projectRelativeHyphenatedName()));
215 //System.exit(1);
216 var testEnginePackage = "hat.test.engine";
217 var testEngineClassName = "HATTestEngine";
218 while (!args.isEmpty()) {
219 var arg = args.removeFirst();
220 switch (arg) {
221 case "help" -> logoAndHelp();
222 case "clean" -> hat.clean(true);
223 case "dot" -> {
224 Files.writeString(Path.of("bld.dot"), hat.all().available().toDot());
225 System.out.println("Consider...\n dot bld.dot -Tsvg > bld.svg");
226 }
227 case "bld" -> {
228 hat.build(hat.all().available());
229 }
230 case "sanity" -> {
231 final var copyrightPattern = Pattern.compile("^.*Copyright.*202[0-9].*(Intel|Oracle).*$");
232 final var copyrightExemptPattern = Pattern.compile("^(robertograham|CMakeFiles|hip)");
233 final var tabOrEolWsPattern = Pattern.compile("^(.*\\t.*|.* )$");
234 final var textSuffix = Pattern.compile("^(.*\\.(java|cpp|h|hpp|md)|pom.xml)$");
235 final var sourceSuffix = Pattern.compile("^(.*\\.(java|cpp|h|hpp)|pom.xml)$");
236
237 Stream.of("hat", "tests", "optkl", "core", "examples", "backends", "docs", "wraps")
238 .map(hat.rootPath()::resolve)
239 .forEach(dir -> {
240 System.out.println("Checking " + dir);
241 Util.recurse(dir,
242 (d) -> true, // we do this for all subdirs
243 (f) -> textSuffix.matcher(f.getFileName().toString()).matches() && Util.grepLines(tabOrEolWsPattern, f),
244 (c) -> System.out.println("File contains WS issue (TAB or EOLWs) " + c)
245 );
246 Util.recurse(dir,
247 (d) -> !copyrightExemptPattern.matcher(d.getFileName().toString()).matches(), // we skip these subdirs
248 (f) -> sourceSuffix.matcher(f.getFileName().toString()).matches() && !Util.grepLines(copyrightPattern, f),
249 (c) -> System.out.println("File does not contain copyright " + c)
250 );
251 });
252 args.clear();
253 }
254 case "run" -> {
255 if (args.size() > 1) {
256 var backendName = args.removeFirst();
257 if (hat.get(backendName) instanceof Jar backend) {
258 var javaOpts = commonJavaOpts.with(o -> o
259 .collectVmOpts(args).mainClass(args.removeFirst(), "Main")
260 .startOnFirstThreadIf(o.packageName().equals("nbodygl") && mac.isAvailable()).collectArgs(args)
261 );
262 if (hat.get(javaOpts.packageName()) instanceof Jar runnable) {
263 runnable.run(javaOpts, runnable, backend);
264 } else {
265 System.err.println("Found backend " + backendName + " but failed to find runnable/example " + javaOpts.packageName());
266 }
267 } else {
268 System.err.println("Failed to find backend " + backendName);
269 }
270 } else {
271 System.err.println("For run we expect 'run backend runnable' ");
272 }
273 }
274 case "test-suite" -> {
275 if (!args.isEmpty()) {
276 var backendName = args.removeFirst();
277 if (hat.get(backendName) instanceof Jar backend) {
278 System.out.println("""
279 *****************************************************************
280 HAT Test Report
281 *****************************************************************
282 """);
283 var test_reports_txt = Paths.get("test_report.txt");
284 Files.deleteIfExists(test_reports_txt); // because we will append to it in the next loop
285 var commonTestSuiteJavaOpts = commonJavaOpts.with(o -> o
286 .command(false).collectVmOpts(args).mainClass(testEnginePackage, testEngineClassName)
287 // note no app args as add them below
288 );
289
290 // First run - checking the total number of tests
291 int[] totalTests = new int[]{0};
292 tests.forEachMatchingEntry("(hat/test/Test[a-zA-Z0-9]*).class", (_, matcher) -> {
293 tests.run(Jar.JavaConfig.of(commonTestSuiteJavaOpts, o -> o.arg(matcher.group(1).replace('/', '.')).arg("--count-tests")), tests, backend);
294 Path path = Path.of(".num_tests");
295 try {
296 // accomulate the total number of tests
297 String content = Files.readString(path);
298 totalTests[0] += Integer.parseInt(content);
299 } catch (IOException e) {}
300 });
301
302 // Second run - testing
303 tests.forEachMatchingEntry("(hat/test/Test[a-zA-Z0-9]*).class", (_, matcher) ->
304 tests.run(Jar.JavaConfig.of(commonTestSuiteJavaOpts, o -> o.arg(matcher.group(1).replace('/', '.'))), tests, backend)
305 );
306 args.clear();
307 var pattern = Pattern.compile("passed: (\\d+), failed: (\\d+), unsupported: (\\d+), precision-errors: (\\d+)");
308 class Stats {
309 int passed = 0;
310 int failed = 0;
311 int unsupported = 0;
312 int precisionError = 0;
313
314 @Override
315 public String toString() {
316 return String.format("Global passed: %d, failed: %d, unsupported: %d, precision-errors: %d, pass-rate: %.2f%%\\n",
317 passed, failed, unsupported, precisionError, ((float) (passed * 100 / (passed + failed + unsupported + precisionError))));
318 }
319 public int total() {
320 return passed + failed + unsupported + precisionError;
321 }
322 }
323 var stats = new Stats();
324 Files.readAllLines(test_reports_txt).forEach(line -> {
325 if (!commonTestSuiteJavaOpts.verbose()) { //We already dumped this info to stdout above
326 System.out.println(line);
327 }
328 if (pattern.matcher(line) instanceof Matcher matcher && matcher.find()) {
329 stats.passed += Integer.parseInt(matcher.group(1));
330 stats.failed += Integer.parseInt(matcher.group(2));
331 stats.unsupported += Integer.parseInt(matcher.group(3));
332 stats.precisionError += Integer.parseInt(matcher.group(4));
333 }
334 });
335
336 IO.println(Colours.BLUE);
337 IO.println(stats);
338 IO.println(Colours.RESET);
339
340 if (stats.total() == totalTests[0]) {
341 IO.println(Colours.GREEN);
342 IO.println("[REPORT] OK: All tests launched. Total: " + totalTests[0]);
343 IO.println(Colours.RESET);
344 } else {
345 IO.println(Colours.RED);
346 IO.println("[REPORT] Test failed. Some tests were not launched. Common reasons: seg-faults, driver-issues. Please, check again.");
347 IO.println(" - Expected to run: " + totalTests[0] + ". But launched " + stats.total());
348 IO.println(Colours.RESET);
349 }
350
351 if (stats.failed > 0) {
352 System.exit(-1);
353 } else {
354 System.exit(0);
355 }
356 } else {
357 System.err.println("Failed to find backend " + backendName);
358 }
359 } else {
360 System.err.println("For test-suite we require a backend ");
361 }
362 }
363 case "test" -> {
364 if (args.size() >= 2) {
365 var backendName = args.removeFirst();
366 if (hat.get(backendName) instanceof Jar backend) {
367 var javaOpts = commonJavaOpts.with(o -> o
368 .verbose(true).command(true).collectVmOpts(args).mainClass(testEnginePackage, testEngineClassName).collectArgs(args)
369 );
370 tests.run(javaOpts, tests, backend);
371 } else {
372 System.err.println("Failed to find backend " + backendName);
373 }
374 } else {
375 System.err.println("""
376 For test we require a backend and a TestClass.");
377 Examples:
378 $ test ffi-opencl hat.test.TestMatMul
379 $ test ffi-opencl hat.test.TestMatMul#method
380 """);
381 }
382 args.clear(); //!! :)
383 }
384 case "exp" -> {
385 if (args.size() > 1) {
386 var backendName = args.removeFirst();
387 if (hat.get(backendName) instanceof Jar backend) {
388 var javaOpts = Jar.JavaConfig.of(commonJavaOpts, o -> o
389 .collectVmOpts(args).mainClass("experiments", args.removeFirst()).collectArgs(args)
390 );
391 if (hat.get(javaOpts.packageName()) instanceof Jar runnable) {
392 runnable.run(javaOpts, runnable, backend);
393 } else {
394 System.err.println("Failed to find runnable " + javaOpts.mainClassName());
395 }
396 } else {
397 System.err.println("Failed to find " + backendName);
398 }
399 } else {
400 System.err.println("For exp we expect 'exp backend [-DXXX ...] testclass' ");
401 }
402 args.clear(); //!! :)
403 }
404 default -> {
405 System.err.println("'" + arg + "' was unexpected ");
406 help();
407 args.clear();
408 }
409 }
410 }
411 }
412 }