Skip to content

Commit bba58e6

Browse files
authored
Merge pull request #37 from stephengold/sgold/oshi
NativeVariant.Cpu: add the capability to detect instruction-set extensions
2 parents 304910b + 4102293 commit bba58e6

File tree

5 files changed

+266
-3
lines changed

5 files changed

+266
-3
lines changed

snaploader-examples/build.gradle

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ tasks.register("TestBasicFeatures2") {
2626
application.mainClass = 'electrostatic4j.snaploader.examples.TestBasicFeatures2'
2727
}
2828

29+
tasks.register("TestCpuFeatures", JavaExec) {
30+
classpath sourceSets.main.runtimeClasspath
31+
description = 'Runs the TestCpuFeatures example app.'
32+
mainClass = 'electrostatic4j.snaploader.examples.TestCpuFeatures'
33+
}
34+
2935
tasks.register("MonitorableExample") {
3036
application.mainClass = 'electrostatic4j.snaploader.examples.MonitorableExample'
3137
}
@@ -86,4 +92,11 @@ task createJar(type : Jar, dependsOn : copyLibs){
8692

8793
dependencies {
8894
implementation project(path: ':snaploader')
95+
implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3'
96+
97+
implementation 'com.github.stephengold:jolt-jni-Linux64:0.9.7'
98+
runtimeOnly 'com.github.stephengold:jolt-jni-Linux64:0.9.7:DebugSp'
99+
runtimeOnly 'com.github.stephengold:jolt-jni-Linux64_fma:0.9.7:DebugSp'
100+
runtimeOnly 'com.github.stephengold:jolt-jni-Windows64:0.9.7:DebugSp'
101+
runtimeOnly 'com.github.stephengold:jolt-jni-Windows64_avx2:0.9.7:DebugSp'
89102
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright (c) 2025, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* * Neither the name of 'AvrSandbox' nor the names of its contributors
17+
* may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
package electrostatic4j.snaploader.examples;
33+
34+
import com.github.stephengold.joltjni.Jolt;
35+
import electrostatic4j.snaploader.LibraryInfo;
36+
import electrostatic4j.snaploader.LoadingCriterion;
37+
import electrostatic4j.snaploader.NativeBinaryLoader;
38+
import electrostatic4j.snaploader.filesystem.DirectoryPath;
39+
import electrostatic4j.snaploader.platform.NativeDynamicLibrary;
40+
import electrostatic4j.snaploader.platform.util.NativeVariant;
41+
import electrostatic4j.snaploader.platform.util.PlatformPredicate;
42+
43+
/**
44+
* Tests selection between native libraries based on CPU features.
45+
*
46+
* @author Stephen Gold [email protected]
47+
*/
48+
public final class TestCpuFeatures {
49+
50+
public static void main(String[] argv) {
51+
// Test for each of the relevant CPU features:
52+
System.out.println("avx = " + NativeVariant.Cpu.hasExtensions("avx"));
53+
System.out.println("avx2 = " + NativeVariant.Cpu.hasExtensions("avx2"));
54+
System.out.println("bmi1 = " + NativeVariant.Cpu.hasExtensions("bmi1"));
55+
System.out.println("f16c = " + NativeVariant.Cpu.hasExtensions("f16c"));
56+
System.out.println("fma = " + NativeVariant.Cpu.hasExtensions("fma"));
57+
System.out.println("sse4_1 = " + NativeVariant.Cpu.hasExtensions("sse4_1"));
58+
System.out.println("sse4_2 = " + NativeVariant.Cpu.hasExtensions("sse4_2"));
59+
60+
// Define a custom predicate for Linux with all 7 CPU features:
61+
PlatformPredicate linuxWithFma = new PlatformPredicate(
62+
PlatformPredicate.LINUX_X86_64,
63+
"avx", "avx2", "bmi1", "f16c", "fma", "sse4_1", "sse4_2");
64+
System.out.println("linuxWithFma = " + linuxWithFma.evaluatePredicate());
65+
66+
// Define a custom predicate for Windows with 4 CPU features:
67+
PlatformPredicate windowsWithAvx2 = new PlatformPredicate(
68+
PlatformPredicate.WIN_X86_64,
69+
"avx", "avx2", "sse4_1", "sse4_2");
70+
System.out.println("windowsWithAvx2 = " + windowsWithAvx2.evaluatePredicate());
71+
System.out.flush();
72+
73+
LibraryInfo info = new LibraryInfo(
74+
new DirectoryPath("linux/x86-64/com/github/stephengold"),
75+
"joltjni", DirectoryPath.USER_DIR);
76+
NativeBinaryLoader loader = new NativeBinaryLoader(info);
77+
NativeDynamicLibrary[] libraries = {
78+
new NativeDynamicLibrary("linux/x86-64-fma/com/github/stephengold", linuxWithFma), // must precede vanilla LINUX_X86_64
79+
new NativeDynamicLibrary("linux/x86-64/com/github/stephengold", PlatformPredicate.LINUX_X86_64),
80+
new NativeDynamicLibrary("windows/x86-64-avx2/com/github/stephengold", windowsWithAvx2), // must precede vanilla WIN_X86_64
81+
new NativeDynamicLibrary("windows/x86-64/com/github/stephengold", PlatformPredicate.WIN_X86_64)
82+
};
83+
loader.registerNativeLibraries(libraries).initPlatformLibrary();
84+
loader.setLoggingEnabled(true);
85+
loader.setRetryWithCleanExtraction(true);
86+
try {
87+
loader.loadLibrary(LoadingCriterion.CLEAN_EXTRACTION);
88+
} catch (Exception e) {
89+
throw new IllegalStateException("Failed to load the joltjni library!");
90+
}
91+
System.err.flush();
92+
93+
// Invoke native code to obtain the configuration of the native library.
94+
String configuration = Jolt.getConfigurationString();
95+
/*
96+
* Depending which native library was loaded, the configuration string
97+
* should be one of the following:
98+
*
99+
* On LINUX_X86_64 platforms, either
100+
* 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)
101+
* or
102+
* Single precision x86 64-bit with instructions: SSE2 (Debug Renderer) (16-bit ObjectLayer) (Assertions) (ObjectStream) (Debug) (C++ RTTI) (C++ Exceptions)
103+
*
104+
* On WIN_X86_64 platforms, either
105+
* 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)
106+
* or
107+
* Single precision x86 64-bit with instructions: SSE2 (FP Exceptions) (Debug Renderer) (16-bit ObjectLayer) (Assertions) (ObjectStream) (Debug) (C++ RTTI) (C++ Exceptions)
108+
*/
109+
System.out.println(configuration);
110+
}
111+
}

snaploader/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ jar { // assemble jar options [java -jar]
2727
}
2828

2929
dependencies {
30-
30+
api('com.github.oshi:oshi-core:6.7.0')
3131
}

snaploader/src/main/java/electrostatic4j/snaploader/platform/util/NativeVariant.java

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023-2024, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader
2+
* Copyright (c) 2023-2025, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader
33
* All rights reserved.
44
*
55
* Redistribution and use in source and binary forms, with or without
@@ -32,6 +32,16 @@
3232

3333
package electrostatic4j.snaploader.platform.util;
3434

35+
import java.util.Collection;
36+
import java.util.List;
37+
import java.util.Locale;
38+
import java.util.TreeSet;
39+
import java.util.regex.Matcher;
40+
import java.util.regex.Pattern;
41+
import oshi.SystemInfo;
42+
import oshi.hardware.CentralProcessor;
43+
import oshi.hardware.HardwareAbstractionLayer;
44+
3545
/**
3646
* Wraps objects for native variant constituents (OS + ARCH={CPU + INSTRUCT_SET} + VM).
3747
*
@@ -132,6 +142,15 @@ public static boolean isAndroid() {
132142
* A namespace class exposing the CPU propositions.
133143
*/
134144
public static final class Cpu {
145+
/**
146+
* named CPU features that were detected by the OSHI library
147+
*/
148+
private static Collection<String> presentFeatures;
149+
/**
150+
* serialize access to presentFeatures
151+
*/
152+
private static Object synchronizeFeatures = new Object();
153+
135154
private Cpu() {
136155
}
137156

@@ -250,6 +269,113 @@ public static boolean isAMD() {
250269
public static boolean isARM() {
251270
return OS_ARCH.getProperty().contains("arm") || OS_ARCH.getProperty().contains("aarch");
252271
}
272+
273+
/**
274+
* Reads named CPU features from the OSHI library and parses them into
275+
* words. If system commands are executed, this might be an expensive
276+
* operation.
277+
*/
278+
private static Collection<String> readFeatureFlags() {
279+
// Obtain the list of CPU feature strings from OSHI:
280+
SystemInfo si = new SystemInfo();
281+
HardwareAbstractionLayer hal = si.getHardware();
282+
CentralProcessor cpu = hal.getProcessor();
283+
List<String> oshiList = cpu.getFeatureFlags();
284+
285+
Pattern pattern = Pattern.compile("[a-z][a-z0-9_]*");
286+
287+
// Convert the list to a collection of feature names:
288+
Collection<String> result = new TreeSet<>();
289+
for (String oshiString : oshiList) {
290+
/*
291+
* On macOS, strings ending with ": 0" indicate
292+
* disabled features, so ignore all such lines.
293+
*/
294+
if (oshiString.endsWith(": 0")) {
295+
continue;
296+
}
297+
String lcString = oshiString.toLowerCase(Locale.ROOT);
298+
Matcher matcher = pattern.matcher(lcString);
299+
while (matcher.find()) {
300+
String featureName = matcher.group();
301+
result.add(featureName);
302+
}
303+
}
304+
305+
return result;
306+
}
307+
308+
/**
309+
* Tests whether the named ISA extensions are all present.
310+
* <p>
311+
* Extension names are case-insensitive and might be reported
312+
* differently by different operating systems or even by different
313+
* versions of the same operating system.
314+
* <p>
315+
* Examples of extension names:<ul>
316+
* <li>"3dnow" for AMD 3D-Now</li>
317+
* <li>"avx" for x86 AVX</li>
318+
* <li>"avx2" for x86 AVX2</li>
319+
* <li>"avx512f" for x86 AVX512F</li>
320+
* <li>"bmi1" for x86 bit-manipulation instruction set 1</li>
321+
* <li>"f16c" for x86 half-precision floating-point</li>
322+
* <li>"fma" for x86 fused multiply-add</li>
323+
* <li>"fmac" for Arm floating-point multiply-accumulate</li>
324+
* <li>"mmx" for x86 MMX</li>
325+
* <li>"neon" for Arm NEON</li>
326+
* <li>"sse3" for x86 SSE3</li>
327+
* <li>"sse4_1" for x86 SSE4.1</li>
328+
* <li>"sse4_2" for x86 SSE4.2</li>
329+
* <li>"ssse3" for x86 SSSE3</li>
330+
* <li>"v8" for Arm V8</li>
331+
* <li>"v8_crc32" for Arm V8 extra CRC32</li>
332+
* <li>"v8_crypto" for Arm V8 extra cryptographic</li>
333+
* <li>"v81_atomic" for Arm V8.1 atomic</li>
334+
* <li>"v82_dp" for Arm V8.2 DP</li>
335+
* <li>"v83_jscvt" for Arm v8.3 JSCVT</li>
336+
* <li>"v83_lrcpc" for Arm v8.3 LRCPC</li>
337+
* </ul>
338+
* <p>
339+
* Wikipedia provides informal descriptions of many ISA extensions.
340+
* https://en.wikipedia.org/wiki/Template:Multimedia_extensions offers a
341+
* good starting point.
342+
*
343+
* @param requiredNames the names of the extensions to test for
344+
* @return {@code true} if the current platform supports all of the
345+
* specified extensions, otherwise {@code false}
346+
*/
347+
public static boolean hasExtensions(String... requiredNames) {
348+
synchronized (synchronizeFeatures) {
349+
if (presentFeatures == null) {
350+
presentFeatures = readFeatureFlags();
351+
}
352+
353+
// Test for each required extension:
354+
for (String extensionName : requiredNames) {
355+
String lcName = extensionName.toLowerCase(Locale.ROOT);
356+
/*
357+
* On Windows, ISA extensions are coded as features
358+
* with names like "PF_xxx_INSTRUCTIONS_AVAILABLE" and
359+
* "PF_ARM_xxx_INSTRUCTIONS_AVAILABLE".
360+
*
361+
* For details see
362+
* https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-isprocessorfeaturepresent
363+
*/
364+
String pfNameArm = "pf_arm_" + lcName + "_instructions_available";
365+
String pfNameX86 = "pf_" + lcName + "_instructions_available";
366+
boolean isPresent = presentFeatures.contains(lcName)
367+
|| presentFeatures.contains(pfNameX86)
368+
|| presentFeatures.contains(pfNameArm);
369+
370+
// conjunctive test: fails if any required extension is missing
371+
if (!isPresent) {
372+
return false;
373+
}
374+
}
375+
}
376+
377+
return true;
378+
}
253379
}
254380

255381
/**

snaploader/src/main/java/electrostatic4j/snaploader/platform/util/PlatformPredicate.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023-2024, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader
2+
* Copyright (c) 2023-2025, The Electrostatic-Sandbox Distributed Simulation Framework, jSnapLoader
33
* All rights reserved.
44
*
55
* Redistribution and use in source and binary forms, with or without
@@ -134,6 +134,19 @@ public PlatformPredicate(boolean predicate) {
134134
this.predicate = predicate;
135135
}
136136

137+
/**
138+
* Instantiates a predicate object that combines a pre-existing predicate
139+
* with one or more instruction-set extensions. The result is true if and
140+
* only if the base predicate is true and all named extensions are present.
141+
*
142+
* @param base a pre-existing predicate (not null)
143+
* @param isaExtensions names of required ISA extensions
144+
*/
145+
public PlatformPredicate(PlatformPredicate base, String... isaExtensions) {
146+
this.predicate = base.evaluatePredicate()
147+
&& NativeVariant.Cpu.hasExtensions(isaExtensions);
148+
}
149+
137150
/**
138151
* Evaluate the propositions of the predefined platform-predicate.
139152
*

0 commit comments

Comments
 (0)