Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions snaploader-examples/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
Expand Down Expand Up @@ -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'
}
Original file line number Diff line number Diff line change
@@ -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 [email protected]
*/
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);
}
}
2 changes: 1 addition & 1 deletion snaploader/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ jar { // assemble jar options [java -jar]
}

dependencies {

api('com.github.oshi:oshi-core:6.7.0')
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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).
*
Expand Down Expand Up @@ -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<String> presentFeatures;
/**
* serialize access to presentFeatures
*/
private static Object synchronizeFeatures = new Object();

private Cpu() {
}

Expand Down Expand Up @@ -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<String> readFeatureFlags() {
// Obtain the list of CPU feature strings from OSHI:
SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
CentralProcessor cpu = hal.getProcessor();
List<String> oshiList = cpu.getFeatureFlags();

Pattern pattern = Pattern.compile("[a-z][a-z0-9_]*");

// Convert the list to a collection of feature names:
Collection<String> 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.
* <p>
* Extension names are case-insensitive and might be reported
* differently by different operating systems or even by different
* versions of the same operating system.
* <p>
* Examples of extension names:<ul>
* <li>"3dnow" for AMD 3D-Now</li>
* <li>"avx" for x86 AVX</li>
* <li>"avx2" for x86 AVX2</li>
* <li>"avx512f" for x86 AVX512F</li>
* <li>"bmi1" for x86 bit-manipulation instruction set 1</li>
* <li>"f16c" for x86 half-precision floating-point</li>
* <li>"fma" for x86 fused multiply-add</li>
* <li>"fmac" for Arm floating-point multiply-accumulate</li>
* <li>"mmx" for x86 MMX</li>
* <li>"neon" for Arm NEON</li>
* <li>"sse3" for x86 SSE3</li>
* <li>"sse4_1" for x86 SSE4.1</li>
* <li>"sse4_2" for x86 SSE4.2</li>
* <li>"ssse3" for x86 SSSE3</li>
* <li>"v8" for Arm V8</li>
* <li>"v8_crc32" for Arm V8 extra CRC32</li>
* <li>"v8_crypto" for Arm V8 extra cryptographic</li>
* <li>"v81_atomic" for Arm V8.1 atomic</li>
* <li>"v82_dp" for Arm V8.2 DP</li>
* <li>"v83_jscvt" for Arm v8.3 JSCVT</li>
* <li>"v83_lrcpc" for Arm v8.3 LRCPC</li>
* </ul>
* <p>
* 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;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
*
Expand Down