diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 092a3a54..d011b46b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,7 +47,7 @@ jobs: - name: build with maven run: | mvn -q -N "io.takari:maven:${{env.IO_TAKARI_MAVEN_WRAPPER_VERSION}}:wrapper" "-Dmaven=${{env.MAVEN_VERSION}}" - ./mvnw -B -ntp formatter:validate verify --file pom.xml "-Djava11.home=${{env.JAVA_HOME_11_X64}}" + ./mvnw -B -ntp formatter:validate verify --file pom.xml "-Dexpected-cpu=x64" "-Djava11.home=${{env.JAVA_HOME_11_X64}}" quality: needs: [ build ] diff --git a/cpu/pom.xml b/cpu/pom.xml index 8dadcccb..651eb81e 100644 --- a/cpu/pom.xml +++ b/cpu/pom.xml @@ -12,12 +12,32 @@ SmallRye Common: CPU + + + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + io.github.dmlloyd.module-info module-info + + maven-surefire-plugin + + + ${expected-cpu} + + + 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; + } +} diff --git a/cpu/src/test/java/io/smallrye/common/cpu/CPUTests.java b/cpu/src/test/java/io/smallrye/common/cpu/CPUTests.java new file mode 100644 index 00000000..3ca8d65f --- /dev/null +++ b/cpu/src/test/java/io/smallrye/common/cpu/CPUTests.java @@ -0,0 +1,18 @@ +package io.smallrye.common.cpu; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; + +/** + * + */ +public final class CPUTests { + + @Test + public void testCpu() { + String expectedCpuName = System.getProperty("expected-cpu"); + Assumptions.assumeTrue(expectedCpuName != null && !expectedCpuName.isEmpty()); + Assertions.assertEquals(CPU.host().name(), expectedCpuName); + } +}