From 48c48b7a85320e83097c1556e8aba46bebfe6954 Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Thu, 12 Mar 2020 16:42:03 +0300 Subject: [PATCH] 8238696: x86: Enumerate all detected CPU features in VM_Version feature string Reviewed-by: dholmes, kvn --- src/hotspot/cpu/x86/assembler_x86.cpp | 16 ++-- src/hotspot/cpu/x86/stubGenerator_x86_64.cpp | 12 +-- src/hotspot/cpu/x86/vm_version_x86.cpp | 41 +++++++--- src/hotspot/cpu/x86/vm_version_x86.hpp | 27 ++++--- src/hotspot/cpu/x86/x86.ad | 4 +- test/jdk/lib/testlibrary/CPUInfoTest.java | 84 ++++++++++++++++++++ 6 files changed, 146 insertions(+), 38 deletions(-) create mode 100644 test/jdk/lib/testlibrary/CPUInfoTest.java diff --git a/src/hotspot/cpu/x86/assembler_x86.cpp b/src/hotspot/cpu/x86/assembler_x86.cpp index e86e91bb499..0318b65bbad 100644 --- a/src/hotspot/cpu/x86/assembler_x86.cpp +++ b/src/hotspot/cpu/x86/assembler_x86.cpp @@ -1328,7 +1328,7 @@ void Assembler::aesdec(XMMRegister dst, XMMRegister src) { } void Assembler::vaesdec(XMMRegister dst, XMMRegister nds, XMMRegister src, int vector_len) { - assert(VM_Version::supports_vaes(), ""); + assert(VM_Version::supports_avx512_vaes(), ""); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); attributes.set_is_evex_instruction(); int encode = vex_prefix_and_encode(dst->encoding(), nds->encoding(), src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_38, &attributes); @@ -1355,7 +1355,7 @@ void Assembler::aesdeclast(XMMRegister dst, XMMRegister src) { } void Assembler::vaesdeclast(XMMRegister dst, XMMRegister nds, XMMRegister src, int vector_len) { - assert(VM_Version::supports_vaes(), ""); + assert(VM_Version::supports_avx512_vaes(), ""); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); attributes.set_is_evex_instruction(); int encode = vex_prefix_and_encode(dst->encoding(), nds->encoding(), src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_38, &attributes); @@ -1381,7 +1381,7 @@ void Assembler::aesenc(XMMRegister dst, XMMRegister src) { } void Assembler::vaesenc(XMMRegister dst, XMMRegister nds, XMMRegister src, int vector_len) { - assert(VM_Version::supports_vaes(), "requires vaes support/enabling"); + assert(VM_Version::supports_avx512_vaes(), "requires vaes support/enabling"); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); attributes.set_is_evex_instruction(); int encode = vex_prefix_and_encode(dst->encoding(), nds->encoding(), src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_38, &attributes); @@ -1407,7 +1407,7 @@ void Assembler::aesenclast(XMMRegister dst, XMMRegister src) { } void Assembler::vaesenclast(XMMRegister dst, XMMRegister nds, XMMRegister src, int vector_len) { - assert(VM_Version::supports_vaes(), "requires vaes support/enabling"); + assert(VM_Version::supports_avx512_vaes(), "requires vaes support/enabling"); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); attributes.set_is_evex_instruction(); int encode = vex_prefix_and_encode(dst->encoding(), nds->encoding(), src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_38, &attributes); @@ -4103,7 +4103,7 @@ void Assembler::vpmaddwd(XMMRegister dst, XMMRegister nds, XMMRegister src, int void Assembler::evpdpwssd(XMMRegister dst, XMMRegister nds, XMMRegister src, int vector_len) { assert(VM_Version::supports_evex(), ""); - assert(VM_Version::supports_vnni(), "must support vnni"); + assert(VM_Version::supports_avx512_vnni(), "must support vnni"); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); attributes.set_is_evex_instruction(); int encode = vex_prefix_and_encode(dst->encoding(), nds->encoding(), src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_38, &attributes); @@ -4137,7 +4137,7 @@ void Assembler::popcntl(Register dst, Register src) { } void Assembler::vpopcntd(XMMRegister dst, XMMRegister src, int vector_len) { - assert(VM_Version::supports_vpopcntdq(), "must support vpopcntdq feature"); + assert(VM_Version::supports_avx512_vpopcntdq(), "must support vpopcntdq feature"); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); attributes.set_is_evex_instruction(); int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_38, &attributes); @@ -6544,7 +6544,7 @@ void Assembler::vpandq(XMMRegister dst, XMMRegister nds, XMMRegister src, int ve } void Assembler::vpshldvd(XMMRegister dst, XMMRegister src, XMMRegister shift, int vector_len) { - assert(VM_Version::supports_vbmi2(), "requires vbmi2"); + assert(VM_Version::supports_avx512_vbmi2(), "requires vbmi2"); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); attributes.set_is_evex_instruction(); int encode = vex_prefix_and_encode(dst->encoding(), src->encoding(), shift->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_38, &attributes); @@ -6553,7 +6553,7 @@ void Assembler::vpshldvd(XMMRegister dst, XMMRegister src, XMMRegister shift, in } void Assembler::vpshrdvd(XMMRegister dst, XMMRegister src, XMMRegister shift, int vector_len) { - assert(VM_Version::supports_vbmi2(), "requires vbmi2"); + assert(VM_Version::supports_avx512_vbmi2(), "requires vbmi2"); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ false, /* no_mask_reg */ true, /* uses_vl */ true); attributes.set_is_evex_instruction(); int encode = vex_prefix_and_encode(dst->encoding(), src->encoding(), shift->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_38, &attributes); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp index 11413db0573..295240fdf06 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp @@ -4445,7 +4445,7 @@ void roundDeclast(XMMRegister xmm_reg) { } address generate_cipherBlockChaining_decryptVectorAESCrypt() { - assert(VM_Version::supports_vaes(), "need AES instructions and misaligned SSE support"); + assert(VM_Version::supports_avx512_vaes(), "need AES instructions and misaligned SSE support"); __ align(CodeEntryAlignment); StubCodeMark mark(this, "StubRoutines", "cipherBlockChaining_decryptAESCrypt"); address start = __ pc(); @@ -5750,7 +5750,7 @@ address generate_avx_ghash_processBlocks() { // If vectorization is enabled, check if the number of iterations is at least 64 // If not, then go to ShifTwo processing 2 iterations - if (VM_Version::supports_vbmi2()) { + if (VM_Version::supports_avx512_vbmi2()) { __ cmpptr(totalNumIter, (AVX3Threshold/64)); __ jcc(Assembler::less, ShiftTwo); @@ -5874,7 +5874,7 @@ address generate_avx_ghash_processBlocks() { // If vectorization is enabled, check if the number of iterations is at least 64 // If not, then go to ShiftTwo shifting two numbers at a time - if (VM_Version::supports_vbmi2()) { + if (VM_Version::supports_avx512_vbmi2()) { __ cmpl(totalNumIter, (AVX3Threshold/64)); __ jcc(Assembler::less, ShiftTwo); @@ -6466,7 +6466,7 @@ address generate_avx_ghash_processBlocks() { StubRoutines::_aescrypt_encryptBlock = generate_aescrypt_encryptBlock(); StubRoutines::_aescrypt_decryptBlock = generate_aescrypt_decryptBlock(); StubRoutines::_cipherBlockChaining_encryptAESCrypt = generate_cipherBlockChaining_encryptAESCrypt(); - if (VM_Version::supports_vaes() && VM_Version::supports_avx512vl() && VM_Version::supports_avx512dq() ) { + if (VM_Version::supports_avx512_vaes() && VM_Version::supports_avx512vl() && VM_Version::supports_avx512dq() ) { StubRoutines::_cipherBlockChaining_decryptAESCrypt = generate_cipherBlockChaining_decryptVectorAESCrypt(); StubRoutines::_electronicCodeBook_encryptAESCrypt = generate_electronicCodeBook_encryptAESCrypt(); StubRoutines::_electronicCodeBook_decryptAESCrypt = generate_electronicCodeBook_decryptAESCrypt(); @@ -6475,7 +6475,7 @@ address generate_avx_ghash_processBlocks() { } } if (UseAESCTRIntrinsics) { - if (VM_Version::supports_vaes() && VM_Version::supports_avx512bw() && VM_Version::supports_avx512vl()) { + if (VM_Version::supports_avx512_vaes() && VM_Version::supports_avx512bw() && VM_Version::supports_avx512vl()) { StubRoutines::x86::_counter_mask_addr = counter_mask_addr(); StubRoutines::_counterMode_AESCrypt = generate_counterMode_VectorAESCrypt(); } else { @@ -6556,7 +6556,7 @@ address generate_avx_ghash_processBlocks() { if (UseMulAddIntrinsic) { StubRoutines::_mulAdd = generate_mulAdd(); } - if (VM_Version::supports_vbmi2()) { + if (VM_Version::supports_avx512_vbmi2()) { StubRoutines::_bigIntegerRightShiftWorker = generate_bigIntegerRightShift(); StubRoutines::_bigIntegerLeftShiftWorker = generate_bigIntegerLeftShift(); } diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 1e77fe7fce1..e55826e26e4 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -700,9 +700,9 @@ void VM_Version::get_processor_features() { _features &= ~CPU_AVX512VL; _features &= ~CPU_AVX512_VPOPCNTDQ; _features &= ~CPU_AVX512_VPCLMULQDQ; - _features &= ~CPU_VAES; - _features &= ~CPU_VNNI; - _features &= ~CPU_VBMI2; + _features &= ~CPU_AVX512_VAES; + _features &= ~CPU_AVX512_VNNI; + _features &= ~CPU_AVX512_VBMI2; } if (UseAVX < 2) @@ -730,10 +730,14 @@ void VM_Version::get_processor_features() { _has_intel_jcc_erratum = IntelJccErratumMitigation; } - char buf[256]; - jio_snprintf(buf, sizeof(buf), "(%u cores per cpu, %u threads per core) family %d model %d stepping %d%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + char buf[512]; + int res = jio_snprintf(buf, sizeof(buf), + "(%u cores per cpu, %u threads per core) family %d model %d stepping %d" + "%s%s%s%s%s%s%s%s%s%s" "%s%s%s%s%s%s%s%s%s%s" "%s%s%s%s%s%s%s%s%s%s" "%s%s%s%s%s%s%s%s%s%s" "%s%s%s%s%s%s", + cores_per_cpu(), threads_per_core(), cpu_family(), _model, _stepping, + (supports_cmov() ? ", cmov" : ""), (supports_cmpxchg8() ? ", cx8" : ""), (supports_fxsr() ? ", fxsr" : ""), @@ -744,7 +748,9 @@ void VM_Version::get_processor_features() { (supports_ssse3()? ", ssse3": ""), (supports_sse4_1() ? ", sse4.1" : ""), (supports_sse4_2() ? ", sse4.2" : ""), + (supports_popcnt() ? ", popcnt" : ""), + (supports_vzeroupper() ? ", vzeroupper" : ""), (supports_avx() ? ", avx" : ""), (supports_avx2() ? ", avx2" : ""), (supports_aes() ? ", aes" : ""), @@ -753,6 +759,7 @@ void VM_Version::get_processor_features() { (supports_rtm() ? ", rtm" : ""), (supports_mmx_ext() ? ", mmxext" : ""), (supports_3dnow_prefetch() ? ", 3dnowpref" : ""), + (supports_lzcnt() ? ", lzcnt": ""), (supports_sse4a() ? ", sse4a": ""), (supports_ht() ? ", ht": ""), @@ -762,12 +769,28 @@ void VM_Version::get_processor_features() { (supports_bmi1() ? ", bmi1" : ""), (supports_bmi2() ? ", bmi2" : ""), (supports_adx() ? ", adx" : ""), - (supports_evex() ? ", evex" : ""), + (supports_evex() ? ", avx512f" : ""), + + (supports_avx512dq() ? ", avx512dq" : ""), + (supports_avx512pf() ? ", avx512pf" : ""), + (supports_avx512er() ? ", avx512er" : ""), + (supports_avx512cd() ? ", avx512cd" : ""), + (supports_avx512bw() ? ", avx512bw" : ""), + (supports_avx512vl() ? ", avx512vl" : ""), + (supports_avx512_vpopcntdq() ? ", avx512_vpopcntdq" : ""), + (supports_avx512_vpclmulqdq() ? ", avx512_vpclmulqdq" : ""), + (supports_avx512_vbmi2() ? ", avx512_vbmi2" : ""), + (supports_avx512_vaes() ? ", avx512_vaes" : ""), + + (supports_avx512_vnni() ? ", avx512_vnni" : ""), (supports_sha() ? ", sha" : ""), (supports_fma() ? ", fma" : ""), - (supports_vbmi2() ? ", vbmi2" : ""), - (supports_vaes() ? ", vaes" : ""), - (supports_vnni() ? ", vnni" : "")); + (supports_clflush() ? ", clflush" : ""), + (supports_clflushopt() ? ", clflushopt" : ""), + (supports_clwb() ? ", clwb" : "")); + + assert(res > 0, "not enough temporary space allocated"); // increase 'buf' size + _features_string = os::strdup(buf); // UseSSE is set to the smaller of what hardware supports and what diff --git a/src/hotspot/cpu/x86/vm_version_x86.hpp b/src/hotspot/cpu/x86/vm_version_x86.hpp index 3526d1ab4fe..24b13460fa2 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.hpp +++ b/src/hotspot/cpu/x86/vm_version_x86.hpp @@ -338,17 +338,18 @@ protected: #define CPU_AVX512VL ((uint64_t)UCONST64(0x200000000)) // EVEX instructions with smaller vector length #define CPU_SHA ((uint64_t)UCONST64(0x400000000)) // SHA instructions #define CPU_FMA ((uint64_t)UCONST64(0x800000000)) // FMA instructions -#define CPU_VZEROUPPER ((uint64_t)UCONST64(0x1000000000)) // Vzeroupper instruction -#define CPU_AVX512_VPOPCNTDQ ((uint64_t)UCONST64(0x2000000000)) // Vector popcount -#define CPU_AVX512_VPCLMULQDQ ((uint64_t)UCONST64(0x4000000000)) //Vector carryless multiplication -#define CPU_VAES ((uint64_t)UCONST64(0x8000000000)) // Vector AES instructions -#define CPU_VNNI ((uint64_t)UCONST64(0x10000000000)) // Vector Neural Network Instructions +#define CPU_VZEROUPPER ((uint64_t)UCONST64(0x1000000000)) // Vzeroupper instruction +#define CPU_AVX512_VPOPCNTDQ ((uint64_t)UCONST64(0x2000000000)) // Vector popcount +#define CPU_AVX512_VPCLMULQDQ ((uint64_t)UCONST64(0x4000000000)) // Vector carryless multiplication +#define CPU_AVX512_VAES ((uint64_t)UCONST64(0x8000000000)) // Vector AES instructions +#define CPU_AVX512_VNNI ((uint64_t)UCONST64(0x10000000000)) // Vector Neural Network Instructions +#define CPU_AVX512_VBMI2 ((uint64_t)UCONST64(0x100000000000)) // VBMI2 shift left double instructions #define CPU_FLUSH ((uint64_t)UCONST64(0x20000000000)) // flush instruction #define CPU_FLUSHOPT ((uint64_t)UCONST64(0x40000000000)) // flushopt instruction #define CPU_CLWB ((uint64_t)UCONST64(0x80000000000)) // clwb instruction -#define CPU_VBMI2 ((uint64_t)UCONST64(0x100000000000)) // VBMI2 shift left double instructions +// NB! When adding new CPU feature detection consider updating feature string in VM_Version::get_processor_features(). enum Extended_Family { // AMD @@ -570,11 +571,11 @@ enum Extended_Family { if (_cpuid_info.sef_cpuid7_ecx.bits.avx512_vpclmulqdq != 0) result |= CPU_AVX512_VPCLMULQDQ; if (_cpuid_info.sef_cpuid7_ecx.bits.vaes != 0) - result |= CPU_VAES; + result |= CPU_AVX512_VAES; if (_cpuid_info.sef_cpuid7_ecx.bits.avx512_vnni != 0) - result |= CPU_VNNI; + result |= CPU_AVX512_VNNI; if (_cpuid_info.sef_cpuid7_ecx.bits.avx512_vbmi2 != 0) - result |= CPU_VBMI2; + result |= CPU_AVX512_VBMI2; } } if (_cpuid_info.sef_cpuid7_ebx.bits.bmi1 != 0) @@ -862,11 +863,11 @@ public: static bool supports_sha() { return (_features & CPU_SHA) != 0; } static bool supports_fma() { return (_features & CPU_FMA) != 0 && supports_avx(); } static bool supports_vzeroupper() { return (_features & CPU_VZEROUPPER) != 0; } - static bool supports_vpopcntdq() { return (_features & CPU_AVX512_VPOPCNTDQ) != 0; } + static bool supports_avx512_vpopcntdq() { return (_features & CPU_AVX512_VPOPCNTDQ) != 0; } static bool supports_avx512_vpclmulqdq() { return (_features & CPU_AVX512_VPCLMULQDQ) != 0; } - static bool supports_vaes() { return (_features & CPU_VAES) != 0; } - static bool supports_vnni() { return (_features & CPU_VNNI) != 0; } - static bool supports_vbmi2() { return (_features & CPU_VBMI2) != 0; } + static bool supports_avx512_vaes() { return (_features & CPU_AVX512_VAES) != 0; } + static bool supports_avx512_vnni() { return (_features & CPU_AVX512_VNNI) != 0; } + static bool supports_avx512_vbmi2() { return (_features & CPU_AVX512_VBMI2) != 0; } // Intel features static bool is_intel_family_core() { return is_intel() && diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index 150efdb95a1..1017863980c 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -1262,7 +1262,7 @@ const bool Matcher::match_rule_supported(int opcode) { } break; case Op_PopCountVI: - if (!UsePopCountInstruction || !VM_Version::supports_vpopcntdq()) { + if (!UsePopCountInstruction || !VM_Version::supports_avx512_vpopcntdq()) { return false; } break; @@ -5666,7 +5666,7 @@ instruct vmuladdS2I_reg_avx(vec dst, vec src1, vec src2) %{ // --------------------------------- Vector Multiply Add Add ---------------------------------- instruct vmuladdaddS2I_reg(vec dst, vec src1, vec src2) %{ - predicate(VM_Version::supports_vnni()); + predicate(VM_Version::supports_avx512_vnni()); match(Set dst (AddVI (MulAddVS2VI src1 src2) dst)); format %{ "evpdpwssd $dst,$src1,$src2\t! muladdadd packedStoI" %} ins_encode %{ diff --git a/test/jdk/lib/testlibrary/CPUInfoTest.java b/test/jdk/lib/testlibrary/CPUInfoTest.java new file mode 100644 index 00000000000..816589a7e02 --- /dev/null +++ b/test/jdk/lib/testlibrary/CPUInfoTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @library /test/lib / + * + * @build sun.hotspot.WhiteBox + * @run driver ClassFileInstaller sun.hotspot.WhiteBox + * sun.hotspot.WhiteBox$WhiteBoxPermission + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI + * CPUInfoTest + */ + +import java.util.Set; +import java.util.List; +import jdk.test.lib.Platform; +import sun.hotspot.WhiteBox; +import sun.hotspot.cpuinfo.CPUInfo; + +import static jdk.test.lib.Asserts.*; + +public class CPUInfoTest { + static final WhiteBox WB = WhiteBox.getWhiteBox(); + + private static final Set wellKnownCPUFeatures; + + static { + if (Platform.isX86() || Platform.isX64()) { + wellKnownCPUFeatures = Set.of( + "adx", "aes", "bmi1", "bmi2", "cmov", "cx8", "fxsr", "mmx", "clmul", "clflush", "clflushopt", "clwb", + "sha", "fma", "popcnt", "vzeroupper", "erms", "rtm", "mmxext", "3dnowpref", "lzcnt", "ht", + "tsc", "tscinvbit", "tscinv", "sse", "sse2", "sse3", "ssse3", "sse4.1", "sse4.2", "sse4a", "avx", "avx2", + "avx512f", "avx512dq", "avx512pf", "avx512er", "avx512cd", "avx512bw", "avx512vl", + "avx512_vpopcntdq", "avx512_vpclmulqdq", "avx512_vbmi2", "avx512_vaes", "avx512_vnni"); + } else { + wellKnownCPUFeatures = null; + } + } + + public static void main(String args[]) throws Throwable { + System.out.println("WB.getCPUFeatures(): \"" + WB.getCPUFeatures() + "\""); + + String additionalCpuInfo = CPUInfo.getAdditionalCPUInfo(); + assertTrue(additionalCpuInfo != null); + System.out.println("CPUInfo.getAdditionalCPUInfo(): \"" + additionalCpuInfo + "\""); + + List features = CPUInfo.getFeatures(); + assertTrue(features != null); + System.out.println("CPUInfo.getFeatures(): " + features); + + for (String feature : features) { + assertTrue(CPUInfo.hasFeature(feature), feature); + } + + if (wellKnownCPUFeatures != null) { + System.out.println("Well-known CPU features: " + wellKnownCPUFeatures); + assertTrue(wellKnownCPUFeatures.containsAll(features), "not all features are known"); + } + + System.out.println("TEST PASSED"); + } +}