diff --git a/snaploader-examples/build.gradle b/snaploader-examples/build.gradle index d706d4e..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' } @@ -86,4 +92,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..b80d953 --- /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.CLEAN_EXTRACTION); + } 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); + } +} diff --git a/snaploader/build.gradle b/snaploader/build.gradle index 3be1ac0..c5e1323 100644 --- a/snaploader/build.gradle +++ b/snaploader/build.gradle @@ -27,5 +27,5 @@ jar { // assemble jar options [java -jar] } dependencies { - + api('com.github.oshi:oshi-core:6.7.0') } 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..07a10b8 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,16 @@ package electrostatic4j.snaploader.platform.util; +import java.util.Collection; +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; + /** * Wraps objects for native variant constituents (OS + ARCH={CPU + INSTRUCT_SET} + VM). * @@ -132,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() { } @@ -250,6 +269,113 @@ public static boolean isAMD() { public static boolean isARM() { return OS_ARCH.getProperty().contains("arm") || OS_ARCH.getProperty().contains("aarch"); } + + /** + * Reads named CPU features from the OSHI library and parses them into + * words. If system commands are executed, this might be an expensive + * operation. + */ + private static Collection readFeatureFlags() { + // 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(); + + Pattern pattern = Pattern.compile("[a-z][a-z0-9_]*"); + + // 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()) { + String featureName = matcher.group(); + result.add(featureName); + } + } + + return result; + } + + /** + * 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 + * 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; + } + } + } + + return true; + } } /** 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. *