From d8d603a8d296b158d399db1957111d67c75e308f Mon Sep 17 00:00:00 2001 From: "David M. Lloyd" Date: Thu, 18 Jan 2024 10:02:29 -0600 Subject: [PATCH] Add CPU detection As we start to use FFM, it may be useful to have a centralized and reliable means of detecting information about the current CPU architecture. --- .../main/java/io/smallrye/common/cpu/CPU.java | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 cpu/src/main/java/io/smallrye/common/cpu/CPU.java diff --git a/cpu/src/main/java/io/smallrye/common/cpu/CPU.java b/cpu/src/main/java/io/smallrye/common/cpu/CPU.java new file mode 100644 index 00000000..050f16c9 --- /dev/null +++ b/cpu/src/main/java/io/smallrye/common/cpu/CPU.java @@ -0,0 +1,192 @@ +package io.smallrye.common.cpu; + +import java.nio.ByteOrder; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import sun.misc.Unsafe; + +/** + * Enumerated type for CPU types. + */ +// We should try to support (at least) all of the CPU types defined in jdk.internal.util.Architecture +public enum CPU { + + // IMPORTANT: Only add new CPU types to the end of the list to preserve ordinal sequence. + /** + * An unknown 32-bit little-endian CPU. + */ + unknown32(4, ByteOrder.LITTLE_ENDIAN, Set.of(), true), + /** + * An unknown 32-bit big-endian CPU. + */ + unknown32be(4, ByteOrder.BIG_ENDIAN, Set.of(), true), + /** + * An unknown 64-bit little-endian CPU. + */ + unknown64(8, ByteOrder.LITTLE_ENDIAN, Set.of("unknown"), true), + /** + * An unknown 64-bit big-endian CPU. + */ + unknown64be(8, ByteOrder.BIG_ENDIAN, Set.of(), true), + + x64(8, ByteOrder.LITTLE_ENDIAN, Set.of("x86_64", "amd64"), false), + x86(4, ByteOrder.LITTLE_ENDIAN, Set.of("i386", "i486", "i586", "i686"), false), + + aarch64(8, ByteOrder.LITTLE_ENDIAN, Set.of("arm64"), false), + arm(4, ByteOrder.LITTLE_ENDIAN, Set.of("armv7", "armv7hl", "aarch32"), false), + + riscv(8, ByteOrder.LITTLE_ENDIAN, Set.of("riscv64"), false), + + ppc32(4, ByteOrder.BIG_ENDIAN, Set.of("ppc32be"), false), + ppc32le(4, ByteOrder.LITTLE_ENDIAN, Set.of(), false), + ppc(8, ByteOrder.BIG_ENDIAN, Set.of("ppc64", "ppcbe", "ppc64be"), false), + ppcle(8, ByteOrder.LITTLE_ENDIAN, Set.of("ppc64le"), false), + + wasm32(4, ByteOrder.LITTLE_ENDIAN, Set.of("wasm"), false), + + // todo: s390 + + mips(4, ByteOrder.BIG_ENDIAN, Set.of("mips32, mipsbe, mips32be"), false), + mipsel(4, ByteOrder.LITTLE_ENDIAN, Set.of("mips32el"), false), + mips64(4, ByteOrder.BIG_ENDIAN, Set.of("mips64be"), false), + mips64el(4, ByteOrder.LITTLE_ENDIAN, Set.of(), false), + ; + + /** + * All of the possible CPU values, in order. + */ + public static final List values = List.of(values()); + + private static final CPU hostCpu; + + // a "map" which is sorted by name, ignoring case + private static final List> index = values.stream() + .flatMap(cpu -> Stream.concat(Stream.of(cpu.name()), cpu.aliases().stream()).map(v -> Map.entry(v, cpu))) + .sorted(Map.Entry.comparingByKey(String::compareToIgnoreCase)) + .collect(Collectors.toUnmodifiableList()); + + private final int pointerSizeBytes; + private final ByteOrder nativeByteOrder; + private final Set aliases; + private final boolean unknown; + + CPU(final int pointerSizeBytes, final ByteOrder nativeByteOrder, final Set aliases, final boolean unknown) { + this.pointerSizeBytes = pointerSizeBytes; + this.nativeByteOrder = nativeByteOrder; + this.aliases = aliases; + this.unknown = unknown; + } + + /** + * {@return this CPU's pointer size, in bytes} + */ + public int pointerSizeBytes() { + return pointerSizeBytes; + } + + /** + * {@return this CPU's pointer size, in bits} + */ + public int pointerSizeBits() { + return pointerSizeBytes << 3; + } + + /** + * {@return this CPU's native byte order} + */ + public ByteOrder nativeByteOrder() { + return nativeByteOrder; + } + + /** + * {@return other names that this CPU is known by} + */ + public Set aliases() { + return aliases; + } + + /** + * {@return true if this CPU is unknown} + */ + public boolean isUnknown() { + return unknown; + } + + /** + * {@return the CPU for the given name} + * Names are compared case-insensitively. + * + * @throws NoSuchElementException if no such CPU is found + */ + public static CPU forName(String name) throws NoSuchElementException { + CPU cpu = forNameOrNull(name); + if (cpu == null) { + throw new NoSuchElementException(); + } + return cpu; + } + + /** + * {@return the CPU for the given name or null if it is not found} + * Names are compared case-insensitively. + */ + public static CPU forNameOrNull(String name) throws NoSuchElementException { + Comparator cmp = String::compareToIgnoreCase; + + int low = 0; + int high = index.size() - 1; + + while (low <= high) { + int mid = (low + high) >>> 1; + Map.Entry entry = index.get(mid); + int res = cmp.compare(entry.getKey(), name); + + if (res < 0) { + low = mid + 1; + } else if (res > 0) { + high = mid - 1; + } else { + return entry.getValue(); + } + } + return null; + } + + /** + * {@return the optional CPU for the given name} + * Names are compared case-insensitively. + */ + public static Optional forNameOpt(String name) throws NoSuchElementException { + return Optional.ofNullable(forNameOrNull(name)); + } + + /** + * {@return the host CPU type} + */ + public static CPU host() { + return hostCpu; + } + + static { + hostCpu = forNameOpt(System.getProperty("os.arch", "???")).map(CPU::check).orElseThrow(); + } + + private static CPU check(CPU cpu) { + ByteOrder no = ByteOrder.nativeOrder(); + // todo: in 22+, bytes = (int) ValueLayout.ADDRESS.byteSize(); + int bytes = Unsafe.ADDRESS_SIZE; + if (cpu.pointerSizeBytes() == bytes && cpu.nativeByteOrder() == no) { + // OK + return cpu; + } + // CPU is unknown or doesn't match observed host characteristics + return no == ByteOrder.BIG_ENDIAN ? bytes == 4 ? unknown32be : unknown64be : bytes == 4 ? unknown32 : unknown64; + } +}