From 324fa56569b02a26d7f0a0328c782c4cbb515320 Mon Sep 17 00:00:00 2001 From: stephengold Date: Fri, 28 Feb 2025 02:12:40 -0800 Subject: [PATCH 01/13] add a dependency on the oshi-core library (to obtain CPU feature flags) --- snaploader/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snaploader/build.gradle b/snaploader/build.gradle index 3be1ac0..a752cc9 100644 --- a/snaploader/build.gradle +++ b/snaploader/build.gradle @@ -27,5 +27,5 @@ jar { // assemble jar options [java -jar] } dependencies { - + implementation('com.github.oshi:oshi-core:6.7.0') } From dd7d34c03e50312b49d933f0fb47ffa7ec179345 Mon Sep 17 00:00:00 2001 From: stephengold Date: Fri, 28 Feb 2025 02:14:34 -0800 Subject: [PATCH 02/13] NativeVariant: add the Cpu.hasExtensions() method --- .../platform/util/NativeVariant.java | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java index 8aaa525..e154833 100644 --- a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java +++ b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader + * Copyright (c) 2023-2025, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,6 +32,14 @@ package electrostatic4j.snaploader.platform.util; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.TreeSet; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.HardwareAbstractionLayer; + /** * Wraps objects for native variant constituents (OS + ARCH={CPU + INSTRUCT_SET} + VM). * @@ -250,6 +258,77 @@ public static boolean isAMD() { public static boolean isARM() { return OS_ARCH.getProperty().contains("arm") || OS_ARCH.getProperty().contains("aarch"); } + + /** + * Cache the names of instruction-set architecture (ISA) extensions that + * are present. + */ + private static final Collection extNameCache = new TreeSet<>(); + + /** + * Lazily initializes the collection of ISA extensions that are present. + */ + private static synchronized void initializeExtNameCache() { + if (extNameCache.isEmpty()) { + // Obtain the list of CPU features from OSHI: + SystemInfo si = new SystemInfo(); + HardwareAbstractionLayer hal = si.getHardware(); + CentralProcessor cpu = hal.getProcessor(); + List strings = cpu.getFeatureFlags(); + + for (String string : strings) { + String lcString = string.toLowerCase(Locale.ROOT); + + // Matches to test for interesting X86 ISA extensions: + if (lcString.matches(".*\\bavx\\b.*")) { + extNameCache.add("avx"); + } + if (lcString.matches(".*\\bavx2\\b.*")) { + extNameCache.add("avx2"); + } + if (lcString.matches(".*\\bbmi1\\b.*")) { + extNameCache.add("bmi1"); + } + if (lcString.matches(".*\\bf16c\\b.*")) { + extNameCache.add("f16c"); + } + if (lcString.matches(".*\\bfma\\b.*")) { + extNameCache.add("fma"); + } + if (lcString.matches(".*\\bsse4_1\\b.*")) { + extNameCache.add("sse4_1"); + } + if (lcString.matches(".*\\bsse4_2\\b.*")) { + extNameCache.add("sse4_2"); + } + } + /* + * Add an empty string so that the name cache + * is guaranteed to no longer be empty. This ensures + * that getFeatureFlags() is invoked at most once. + */ + extNameCache.add(""); + } + } + + /** + * Tests whether the named ISA extensions are all present. + * + * @param names the names of the extensions to test for + * @return {@code true} if all are present, otherwise {@code false} + */ + public static boolean hasExtensions(String... names) { + initializeExtNameCache(); + + for (String name : names) { + String lcName = name.toLowerCase(Locale.ROOT); + boolean isPresent = extNameCache.contains(lcName); + if (!isPresent) { + return false; + } + } + return true; + } } /** From 2680449e89c2d23303fe9b5af90e54e28ece5392 Mon Sep 17 00:00:00 2001 From: stephengold Date: Sat, 1 Mar 2025 11:06:09 -0800 Subject: [PATCH 03/13] NativeVariant: solve Windows issue, eliminate caching of ISA extensions --- .../platform/util/NativeVariant.java | 85 ++++++------------- 1 file changed, 28 insertions(+), 57 deletions(-) diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java index e154833..fcbfb24 100644 --- a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java +++ b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java @@ -36,6 +36,8 @@ import java.util.List; import java.util.Locale; import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import oshi.SystemInfo; import oshi.hardware.CentralProcessor; import oshi.hardware.HardwareAbstractionLayer; @@ -260,73 +262,42 @@ public static boolean isARM() { } /** - * Cache the names of instruction-set architecture (ISA) extensions that - * are present. - */ - private static final Collection extNameCache = new TreeSet<>(); - - /** - * Lazily initializes the collection of ISA extensions that are present. + * Tests whether the named ISA extensions are all present. + * + * @param requiredNames the names of the extensions to test for + * @return {@code true} if all are present, otherwise {@code false} */ - private static synchronized void initializeExtNameCache() { - if (extNameCache.isEmpty()) { - // Obtain the list of CPU features from OSHI: - SystemInfo si = new SystemInfo(); - HardwareAbstractionLayer hal = si.getHardware(); - CentralProcessor cpu = hal.getProcessor(); - List strings = cpu.getFeatureFlags(); + public static boolean hasExtensions(String... requiredNames) { + // Obtain the list of CPU feature strings from OSHI: + SystemInfo si = new SystemInfo(); + HardwareAbstractionLayer hal = si.getHardware(); + CentralProcessor cpu = hal.getProcessor(); + List oshiList = cpu.getFeatureFlags(); - for (String string : strings) { - String lcString = string.toLowerCase(Locale.ROOT); + Pattern pattern = Pattern.compile("[a-z][a-z0-9_]*"); - // Matches to test for interesting X86 ISA extensions: - if (lcString.matches(".*\\bavx\\b.*")) { - extNameCache.add("avx"); - } - if (lcString.matches(".*\\bavx2\\b.*")) { - extNameCache.add("avx2"); - } - if (lcString.matches(".*\\bbmi1\\b.*")) { - extNameCache.add("bmi1"); - } - if (lcString.matches(".*\\bf16c\\b.*")) { - extNameCache.add("f16c"); - } - if (lcString.matches(".*\\bfma\\b.*")) { - extNameCache.add("fma"); - } - if (lcString.matches(".*\\bsse4_1\\b.*")) { - extNameCache.add("sse4_1"); - } - if (lcString.matches(".*\\bsse4_2\\b.*")) { - extNameCache.add("sse4_2"); - } + // Convert the list to a collection of feature names: + Collection presentFeatures = new TreeSet<>(); + for (String oshiString : oshiList) { + String lcString = oshiString.toLowerCase(Locale.ROOT); + Matcher matcher = pattern.matcher(lcString); + while (matcher.find()) { + String featureName = matcher.group(); + presentFeatures.add(featureName); } - /* - * Add an empty string so that the name cache - * is guaranteed to no longer be empty. This ensures - * that getFeatureFlags() is invoked at most once. - */ - extNameCache.add(""); } - } - /** - * Tests whether the named ISA extensions are all present. - * - * @param names the names of the extensions to test for - * @return {@code true} if all are present, otherwise {@code false} - */ - public static boolean hasExtensions(String... names) { - initializeExtNameCache(); - - for (String name : names) { - String lcName = name.toLowerCase(Locale.ROOT); - boolean isPresent = extNameCache.contains(lcName); + // Test for each required extension: + for (String extensionName : requiredNames) { + String lcName = extensionName.toLowerCase(Locale.ROOT); + String pfName = "pf_" + lcName + "_instructions_available"; + boolean isPresent = presentFeatures.contains(lcName) + || presentFeatures.contains(pfName); if (!isPresent) { return false; } } + return true; } } From 0baf927fed56e688503aa055602bec12ba624b96 Mon Sep 17 00:00:00 2001 From: stephengold Date: Mon, 3 Mar 2025 12:39:09 -0800 Subject: [PATCH 04/13] NativeVariant: re-implement caching, fix Windows-on-ARM, add comments --- .../platform/util/NativeVariant.java | 67 ++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java index fcbfb24..70681b1 100644 --- a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java +++ b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java @@ -91,6 +91,15 @@ public enum NativeVariant { private final String property; + /** + * named CPU features that were detected by the OSHI library + */ + private static Collection presentFeatures; + /** + * serialize access to presentFeatures + */ + private static Object synchronizeFeatures = new Object(); + NativeVariant(final String property) { this.property = property; } @@ -262,12 +271,11 @@ public static boolean isARM() { } /** - * Tests whether the named ISA extensions are all present. - * - * @param requiredNames the names of the extensions to test for - * @return {@code true} if all are present, otherwise {@code false} + * Reads named CPU features from the OSHI library and parses them into + * words. If system commands are executed, this might be an expensive + * operation. */ - public static boolean hasExtensions(String... requiredNames) { + private static Collection readFeatureFlags() { // Obtain the list of CPU feature strings from OSHI: SystemInfo si = new SystemInfo(); HardwareAbstractionLayer hal = si.getHardware(); @@ -277,24 +285,53 @@ public static boolean hasExtensions(String... requiredNames) { Pattern pattern = Pattern.compile("[a-z][a-z0-9_]*"); // Convert the list to a collection of feature names: - Collection presentFeatures = new TreeSet<>(); + Collection result = new TreeSet<>(); for (String oshiString : oshiList) { String lcString = oshiString.toLowerCase(Locale.ROOT); Matcher matcher = pattern.matcher(lcString); while (matcher.find()) { String featureName = matcher.group(); - presentFeatures.add(featureName); + result.add(featureName); } } - // Test for each required extension: - for (String extensionName : requiredNames) { - String lcName = extensionName.toLowerCase(Locale.ROOT); - String pfName = "pf_" + lcName + "_instructions_available"; - boolean isPresent = presentFeatures.contains(lcName) - || presentFeatures.contains(pfName); - if (!isPresent) { - return false; + return result; + } + + /** + * Tests whether the named ISA extensions are all present. + * + * @param requiredNames the names of the extensions to test for + * @return {@code true} if the current platform supports all of the + * specified extensions, otherwise {@code false} + */ + public static boolean hasExtensions(String... requiredNames) { + synchronized (synchronizeFeatures) { + if (presentFeatures == null) { + presentFeatures = readFeatureFlags(); + } + + // Test for each required extension: + for (String extensionName : requiredNames) { + String lcName = extensionName.toLowerCase(Locale.ROOT); + /* + * On Windows, ISA extensions are coded as features + * with names like "PF_xxx_INSTRUCTIONS_AVAILABLE" and + * "PF_ARM_xxx_INSTRUCTIONS_AVAILABLE". + * + * For details see + * https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-isprocessorfeaturepresent + */ + String pfNameArm = "pf_arm_" + lcName + "_instructions_available"; + String pfNameX86 = "pf_" + lcName + "_instructions_available"; + boolean isPresent = presentFeatures.contains(lcName) + || presentFeatures.contains(pfNameX86) + || presentFeatures.contains(pfNameArm); + + // conjunctive test: fails if any required extension is missing + if (!isPresent) { + return false; + } } } From b13ce2f8daa4412640b4298de9ad0ea848a473c6 Mon Sep 17 00:00:00 2001 From: stephengold Date: Mon, 3 Mar 2025 12:41:35 -0800 Subject: [PATCH 05/13] NativeVariant: document the names of some well-known ISA extensions --- .../platform/util/NativeVariant.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java index 70681b1..6a59228 100644 --- a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java +++ b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java @@ -300,6 +300,38 @@ private static Collection readFeatureFlags() { /** * Tests whether the named ISA extensions are all present. + *

+ * Extension names are case-insensitive and might be reported + * differently by different operating systems or even by different + * versions of the same operating system.

+ *

+ * Examples of extension names:

    + *
  • "3dnow" for AMD 3D-Now
  • + *
  • "avx" for x86 AVX
  • + *
  • "avx2" for x86 AVX2
  • + *
  • "avx512f" for x86 AVX512F
  • + *
  • "bmi1" for x86 bit-manipulation instruction set 1
  • + *
  • "f16c" for x86 half-precision floating-point
  • + *
  • "fma" for x86 fused multiply-add
  • + *
  • "fmac" for Arm floating-point multiply-accumulate
  • + *
  • "mmx" for x86 MMX
  • + *
  • "neon" for Arm NEON
  • + *
  • "sse3" for x86 SSE3
  • + *
  • "sse4_1" for x86 SSE4.1
  • + *
  • "sse4_2" for x86 SSE4.2
  • + *
  • "ssse3" for x86 SSSE3
  • + *
  • "v8" for Arm V8
  • + *
  • "v8_crc32" for Arm V8 extra CRC32
  • + *
  • "v8_crypto" for Arm V8 extra cryptographic
  • + *
  • "v81_atomic" for Arm V8.1 atomic
  • + *
  • "v82_dp" for Arm V8.2 DP
  • + *
  • "v83_jscvt" for Arm v8.3 JSCVT
  • + *
  • "v83_lrcpc" for Arm v8.3 LRCPC
  • + *

+ *

+ * Wikipedia provides informal descriptions of many ISA extensions. + * https://en.wikipedia.org/wiki/Template:Multimedia_extensions offers a + * good starting point. * * @param requiredNames the names of the extensions to test for * @return {@code true} if the current platform supports all of the From dd2be1bd1abd06c3488ef5419c9579c98f1964ca Mon Sep 17 00:00:00 2001 From: stephengold Date: Mon, 3 Mar 2025 14:57:22 -0800 Subject: [PATCH 06/13] NativeVariant: remove 2 HTML tags from the javadoc --- .../snaploader/platform/util/NativeVariant.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java index 6a59228..ed58c55 100644 --- a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java +++ b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java @@ -303,7 +303,7 @@ private static Collection readFeatureFlags() { *

* Extension names are case-insensitive and might be reported * differently by different operating systems or even by different - * versions of the same operating system.

+ * versions of the same operating system. *

* Examples of extension names:

    *
  • "3dnow" for AMD 3D-Now
  • @@ -327,7 +327,7 @@ private static Collection readFeatureFlags() { *
  • "v82_dp" for Arm V8.2 DP
  • *
  • "v83_jscvt" for Arm v8.3 JSCVT
  • *
  • "v83_lrcpc" for Arm v8.3 LRCPC
  • - *

+ * *

* Wikipedia provides informal descriptions of many ISA extensions. * https://en.wikipedia.org/wiki/Template:Multimedia_extensions offers a From 27992441bff98508aa663639c5217f5d8163a2a2 Mon Sep 17 00:00:00 2001 From: stephengold Date: Mon, 3 Mar 2025 15:23:28 -0800 Subject: [PATCH 07/13] NativeVariant: move 2 fields to the Cpu inner class (for readability) --- .../platform/util/NativeVariant.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java index ed58c55..597a0b2 100644 --- a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java +++ b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java @@ -91,15 +91,6 @@ public enum NativeVariant { private final String property; - /** - * named CPU features that were detected by the OSHI library - */ - private static Collection presentFeatures; - /** - * serialize access to presentFeatures - */ - private static Object synchronizeFeatures = new Object(); - NativeVariant(final String property) { this.property = property; } @@ -151,6 +142,15 @@ public static boolean isAndroid() { * A namespace class exposing the CPU propositions. */ public static final class Cpu { + /** + * named CPU features that were detected by the OSHI library + */ + private static Collection presentFeatures; + /** + * serialize access to presentFeatures + */ + private static Object synchronizeFeatures = new Object(); + private Cpu() { } From 7a9c74c90c958b1932b4f02d2f5d4bdbdb449fb7 Mon Sep 17 00:00:00 2001 From: stephengold Date: Mon, 3 Mar 2025 22:34:55 -0800 Subject: [PATCH 08/13] NativeVariant: readFeatureFlags() workaround for macOS --- .../snaploader/platform/util/NativeVariant.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java index 597a0b2..07a10b8 100644 --- a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java +++ b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java @@ -287,6 +287,13 @@ private static Collection readFeatureFlags() { // Convert the list to a collection of feature names: Collection result = new TreeSet<>(); for (String oshiString : oshiList) { + /* + * On macOS, strings ending with ": 0" indicate + * disabled features, so ignore all such lines. + */ + if (oshiString.endsWith(": 0")) { + continue; + } String lcString = oshiString.toLowerCase(Locale.ROOT); Matcher matcher = pattern.matcher(lcString); while (matcher.find()) { From d7577e9191f2a4ccca58f03953751ff47a7a89bf Mon Sep 17 00:00:00 2001 From: stephengold Date: Tue, 4 Mar 2025 13:30:06 -0800 Subject: [PATCH 09/13] PlatformPredicate: add a combining constructor --- .../platform/util/PlatformPredicate.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/PlatformPredicate.java b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/PlatformPredicate.java index 5e1c497..25de2c2 100644 --- a/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/PlatformPredicate.java +++ b/snaploader/src/main/java/electrostatic4j/snaploader/platform/util/PlatformPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader + * Copyright (c) 2023-2025, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -134,6 +134,19 @@ public PlatformPredicate(boolean predicate) { this.predicate = predicate; } + /** + * Instantiates a predicate object that combines a pre-existing predicate + * with one or more instruction-set extensions. The result is true if and + * only if the base predicate is true and all named extensions are present. + * + * @param base a pre-existing predicate (not null) + * @param isaExtensions names of required ISA extensions + */ + public PlatformPredicate(PlatformPredicate base, String... isaExtensions) { + this.predicate = base.evaluatePredicate() + && NativeVariant.Cpu.hasExtensions(isaExtensions); + } + /** * Evaluate the propositions of the predefined platform-predicate. * From 030916119074305d88bc31485f52d093111aece4 Mon Sep 17 00:00:00 2001 From: stephengold Date: Thu, 6 Mar 2025 19:42:17 -0800 Subject: [PATCH 10/13] make the OSHI dependency into a transitive one --- snaploader/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snaploader/build.gradle b/snaploader/build.gradle index a752cc9..c5e1323 100644 --- a/snaploader/build.gradle +++ b/snaploader/build.gradle @@ -27,5 +27,5 @@ jar { // assemble jar options [java -jar] } dependencies { - implementation('com.github.oshi:oshi-core:6.7.0') + api('com.github.oshi:oshi-core:6.7.0') } From cb518698e0f73fc66e154cf479bc8bf208150b0d Mon Sep 17 00:00:00 2001 From: stephengold Date: Fri, 7 Mar 2025 11:59:35 -0800 Subject: [PATCH 11/13] snaploader-examples: added the TestCpuFeatures test --- snaploader-examples/build.gradle | 7 ++ .../snaploader/examples/TestCpuFeatures.java | 111 ++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 snaploader-examples/src/main/java/electrostatic4j/snaploader/examples/TestCpuFeatures.java diff --git a/snaploader-examples/build.gradle b/snaploader-examples/build.gradle index d706d4e..4d9483b 100644 --- a/snaploader-examples/build.gradle +++ b/snaploader-examples/build.gradle @@ -86,4 +86,11 @@ task createJar(type : Jar, dependsOn : copyLibs){ dependencies { implementation project(path: ':snaploader') + implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3' + + implementation 'com.github.stephengold:jolt-jni-Linux64:0.9.7' + runtimeOnly 'com.github.stephengold:jolt-jni-Linux64:0.9.7:DebugSp' + runtimeOnly 'com.github.stephengold:jolt-jni-Linux64_fma:0.9.7:DebugSp' + runtimeOnly 'com.github.stephengold:jolt-jni-Windows64:0.9.7:DebugSp' + runtimeOnly 'com.github.stephengold:jolt-jni-Windows64_avx2:0.9.7:DebugSp' } \ No newline at end of file diff --git a/snaploader-examples/src/main/java/electrostatic4j/snaploader/examples/TestCpuFeatures.java b/snaploader-examples/src/main/java/electrostatic4j/snaploader/examples/TestCpuFeatures.java new file mode 100644 index 0000000..8fe894f --- /dev/null +++ b/snaploader-examples/src/main/java/electrostatic4j/snaploader/examples/TestCpuFeatures.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'AvrSandbox' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package electrostatic4j.snaploader.examples; + +import com.github.stephengold.joltjni.Jolt; +import electrostatic4j.snaploader.LibraryInfo; +import electrostatic4j.snaploader.LoadingCriterion; +import electrostatic4j.snaploader.NativeBinaryLoader; +import electrostatic4j.snaploader.filesystem.DirectoryPath; +import electrostatic4j.snaploader.platform.NativeDynamicLibrary; +import electrostatic4j.snaploader.platform.util.NativeVariant; +import electrostatic4j.snaploader.platform.util.PlatformPredicate; + +/** + * Tests selection between native libraries based on CPU features. + * + * @author Stephen Gold sgold@sonic.net + */ +public final class TestCpuFeatures { + + public static void main(String[] argv) { + // Test for each of the relevant CPU features: + System.out.println("avx = " + NativeVariant.Cpu.hasExtensions("avx")); + System.out.println("avx2 = " + NativeVariant.Cpu.hasExtensions("avx2")); + System.out.println("bmi1 = " + NativeVariant.Cpu.hasExtensions("bmi1")); + System.out.println("f16c = " + NativeVariant.Cpu.hasExtensions("f16c")); + System.out.println("fma = " + NativeVariant.Cpu.hasExtensions("fma")); + System.out.println("sse4_1 = " + NativeVariant.Cpu.hasExtensions("sse4_1")); + System.out.println("sse4_2 = " + NativeVariant.Cpu.hasExtensions("sse4_2")); + + // Define a custom predicate for Linux with all 7 CPU features: + PlatformPredicate linuxWithFma = new PlatformPredicate( + PlatformPredicate.LINUX_X86_64, + "avx", "avx2", "bmi1", "f16c", "fma", "sse4_1", "sse4_2"); + System.out.println("linuxWithFma = " + linuxWithFma.evaluatePredicate()); + + // Define a custom predicate for Windows with 4 CPU features: + PlatformPredicate windowsWithAvx2 = new PlatformPredicate( + PlatformPredicate.WIN_X86_64, + "avx", "avx2", "sse4_1", "sse4_2"); + System.out.println("windowsWithAvx2 = " + windowsWithAvx2.evaluatePredicate()); + System.out.flush(); + + LibraryInfo info = new LibraryInfo( + new DirectoryPath("linux/x86-64/com/github/stephengold"), + "joltjni", DirectoryPath.USER_DIR); + NativeBinaryLoader loader = new NativeBinaryLoader(info); + NativeDynamicLibrary[] libraries = { + new NativeDynamicLibrary("linux/x86-64-fma/com/github/stephengold", linuxWithFma), // must precede vanilla LINUX_X86_64 + new NativeDynamicLibrary("linux/x86-64/com/github/stephengold", PlatformPredicate.LINUX_X86_64), + new NativeDynamicLibrary("windows/x86-64-avx2/com/github/stephengold", windowsWithAvx2), // must precede vanilla WIN_X86_64 + new NativeDynamicLibrary("windows/x86-64/com/github/stephengold", PlatformPredicate.WIN_X86_64) + }; + loader.registerNativeLibraries(libraries).initPlatformLibrary(); + loader.setLoggingEnabled(true); + loader.setRetryWithCleanExtraction(true); + try { + loader.loadLibrary(LoadingCriterion.INCREMENTAL_LOADING); + } catch (Exception e) { + throw new IllegalStateException("Failed to load the joltjni library!"); + } + System.err.flush(); + + // Invoke native code to obtain the configuration of the native library. + String configuration = Jolt.getConfigurationString(); + /* + * Depending which native library was loaded, the configuration string + * should be one of the following: + * + * On LINUX_X86_64 platforms, either + * Single precision x86 64-bit with instructions: SSE2 SSE4.1 SSE4.2 AVX AVX2 F16C LZCNT TZCNT FMADD (Debug Renderer) (16-bit ObjectLayer) (Assertions) (ObjectStream) (Debug) (C++ RTTI) (C++ Exceptions) + * or + * Single precision x86 64-bit with instructions: SSE2 (Debug Renderer) (16-bit ObjectLayer) (Assertions) (ObjectStream) (Debug) (C++ RTTI) (C++ Exceptions) + * + * On WIN_X86_64 platforms, either + * Single precision x86 64-bit with instructions: SSE2 SSE4.1 SSE4.2 AVX AVX2 F16C LZCNT TZCNT (FP Exceptions) (Debug Renderer) (16-bit ObjectLayer) (Assertions) (ObjectStream) (Debug) (C++ RTTI) (C++ Exceptions) + * or + * Single precision x86 64-bit with instructions: SSE2 (FP Exceptions) (Debug Renderer) (16-bit ObjectLayer) (Assertions) (ObjectStream) (Debug) (C++ RTTI) (C++ Exceptions) + */ + System.out.println(configuration); + } +} From 73c35a79c85c1ad5d9849ccb9af9104458a0e1ee Mon Sep 17 00:00:00 2001 From: stephengold Date: Fri, 7 Mar 2025 12:08:56 -0800 Subject: [PATCH 12/13] snaploader-examples: register a Gradle task to run TestCpuFeatures --- snaploader-examples/build.gradle | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/snaploader-examples/build.gradle b/snaploader-examples/build.gradle index 4d9483b..057c1b7 100644 --- a/snaploader-examples/build.gradle +++ b/snaploader-examples/build.gradle @@ -26,6 +26,12 @@ tasks.register("TestBasicFeatures2") { application.mainClass = 'electrostatic4j.snaploader.examples.TestBasicFeatures2' } +tasks.register("TestCpuFeatures", JavaExec) { + classpath sourceSets.main.runtimeClasspath + description = 'Runs the TestCpuFeatures example app.' + mainClass = 'electrostatic4j.snaploader.examples.TestCpuFeatures' +} + tasks.register("MonitorableExample") { application.mainClass = 'electrostatic4j.snaploader.examples.MonitorableExample' } From 410229333c062a4f25e0fa0591075ea8c9cc31a7 Mon Sep 17 00:00:00 2001 From: stephengold Date: Fri, 7 Mar 2025 14:49:50 -0800 Subject: [PATCH 13/13] TestCpuFeatures: specify CLEAN_EXTRACTION to loadLibrary() --- .../electrostatic4j/snaploader/examples/TestCpuFeatures.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snaploader-examples/src/main/java/electrostatic4j/snaploader/examples/TestCpuFeatures.java b/snaploader-examples/src/main/java/electrostatic4j/snaploader/examples/TestCpuFeatures.java index 8fe894f..b80d953 100644 --- a/snaploader-examples/src/main/java/electrostatic4j/snaploader/examples/TestCpuFeatures.java +++ b/snaploader-examples/src/main/java/electrostatic4j/snaploader/examples/TestCpuFeatures.java @@ -84,7 +84,7 @@ public static void main(String[] argv) { loader.setLoggingEnabled(true); loader.setRetryWithCleanExtraction(true); try { - loader.loadLibrary(LoadingCriterion.INCREMENTAL_LOADING); + loader.loadLibrary(LoadingCriterion.CLEAN_EXTRACTION); } catch (Exception e) { throw new IllegalStateException("Failed to load the joltjni library!"); }