[core:sys/info] Remove @(init) where practical

This commit is contained in:
Jeroen van Rijn
2026-02-21 21:53:23 +01:00
parent 4581f57953
commit ea80eab75a
26 changed files with 792 additions and 551 deletions

View File

@@ -0,0 +1,14 @@
#+build essence
#+private
package runtime
_HAS_RAND_BYTES :: false
// TODO(bill): reimplement `os.write`
_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) {
return 0, -1
}
_exit :: proc "contextless" (code: int) -> ! {
trap()
}

View File

@@ -6,10 +6,7 @@ import "core:sys/info"
// is_supported returns true iff hardware accelerated AES
// is supported.
is_supported :: proc "contextless" () -> bool {
features, ok := info.cpu.features.?
if !ok {
return false
}
features := info.cpu_features() or_return
// Note: Everything with AES-NI and PCLMULQDQ has support for
// the required SSE extxtensions.

View File

@@ -227,10 +227,7 @@ is_performant :: proc "contextless" () -> bool {
req_features :: info.CPU_Features{.V}
}
features, ok := info.cpu.features.?
if !ok {
return false
}
features := info.cpu_features() or_return
return features >= req_features
} else when ODIN_ARCH == .wasm64p32 || ODIN_ARCH == .wasm32 {

View File

@@ -39,12 +39,9 @@ _VEC_TWO: simd.u64x4 : {2, 0, 2, 0}
// is_performant returns true iff the target and current host both support
// "enough" SIMD to make this implementation performant.
is_performant :: proc "contextless" () -> bool {
req_features :: info.CPU_Features{.avx, .avx2}
features := info.cpu_features() or_return
features, ok := info.cpu.features.?
if !ok {
return false
}
req_features :: info.CPU_Features{.avx, .avx2}
return features >= req_features
}

View File

@@ -52,10 +52,7 @@ K_15 :: simd.u64x2{0xa4506ceb90befffa, 0xc67178f2bef9a3f7}
// is_hardware_accelerated_256 returns true iff hardware accelerated
// SHA-224/SHA-256 is supported.
is_hardware_accelerated_256 :: proc "contextless" () -> bool {
features, ok := info.cpu.features.?
if !ok {
return false
}
features := info.cpu_features() or_return
req_features :: info.CPU_Features{
.sse2,

View File

@@ -40,35 +40,34 @@ CPU_Feature :: enum u64 {
}
CPU_Features :: distinct bit_set[CPU_Feature; u64]
CPU :: struct {
name: Maybe(string),
features: Maybe(CPU_Features),
physical_cores: int,
logical_cores: int,
}
cpu: CPU
// Retrieving CPU features on ARM is even more expensive than on Intel, so we're doing this lookup only once, before `main()`
@(private) _features: CPU_Features
@(private) _features_ok: bool
@(private) _name_buf: [256]u8
@(private) _name: string
@(private)
cpu_name_buf: [128]byte
_cpu_name :: proc() -> (name: string) {
return _name
}
@(init, private)
init_cpu_name :: proc "contextless" () {
generic := true
_init_cpu_name :: proc "contextless" () {
when ODIN_OS == .Darwin {
if unix.sysctlbyname("machdep.cpu.brand_string", &cpu_name_buf) {
cpu.name = string(cstring(rawptr(&cpu_name_buf)))
generic = false
if unix.sysctlbyname("machdep.cpu.brand_string", &_name_buf) {
_name = string(cstring(rawptr(&_name_buf)))
return
}
}
if generic {
when ODIN_ARCH == .arm64 {
copy(cpu_name_buf[:], "ARM64")
cpu.name = string(cpu_name_buf[:len("ARM64")])
} else {
copy(cpu_name_buf[:], "ARM")
cpu.name = string(cpu_name_buf[:len("ARM")])
}
when ODIN_ARCH == .arm64 {
copy(_name_buf[:], "ARM64")
_name = string(_name_buf[:len("ARM64")])
} else {
copy(_name_buf[:], "ARM")
_name = string(_name_buf[:len("ARM")])
}
}
}

View File

@@ -2,11 +2,11 @@ package sysinfo
import "core:sys/unix"
@(init, private)
init_cpu_core_count :: proc "contextless" () {
physical, logical: i64
unix.sysctlbyname("hw.physicalcpu", &physical)
unix.sysctlbyname("hw.logicalcpu", &logical)
cpu.physical_cores = int(physical)
cpu.logical_cores = int(logical)
}
@(private)
_cpu_core_count :: proc "contextless" () -> (physical: int, logical: int, ok: bool) {
_physical, _logical: i64
unix.sysctlbyname("hw.physicalcpu", &_physical)
unix.sysctlbyname("hw.logicalcpu", &_logical)
return int(_physical), int(_logical), true
}

View File

@@ -2,15 +2,17 @@ package sysinfo
import "core:sys/unix"
@(init, private)
init_cpu_features :: proc "contextless" () {
@(static) features: CPU_Features
defer cpu.features = features
@(private)
_cpu_features :: proc "contextless" () -> (features: CPU_Features, ok: bool) {
return _features, true
}
try_set :: proc "contextless" (name: cstring, feature: CPU_Feature) -> (ok: bool) {
@(init, private)
_init_cpu_features :: proc "contextless" () -> () {
try_set :: proc "contextless" (features: ^CPU_Features, name: cstring, feature: CPU_Feature) -> (ok: bool) {
support: b32
if ok = unix.sysctlbyname(name, &support); ok && support {
features += { feature }
features^ += { feature }
}
return
}
@@ -20,79 +22,79 @@ init_cpu_features :: proc "contextless" () {
// Advanced SIMD & floating-point capabilities:
{
if !try_set("hw.optional.AdvSIMD", .asimd) {
try_set("hw.optional.neon", .asimd)
if !try_set(&_features, "hw.optional.AdvSIMD", .asimd) {
try_set(&_features, "hw.optional.neon", .asimd)
}
try_set("hw.optional.floatingpoint", .floatingpoint)
try_set(&_features, "hw.optional.floatingpoint", .floatingpoint)
if !try_set("hw.optional.AdvSIMD_HPFPCvt", .asimdhp) {
try_set("hw.optional.neon_hpfp", .asimdhp)
if !try_set(&_features, "hw.optional.AdvSIMD_HPFPCvt", .asimdhp) {
try_set(&_features, "hw.optional.neon_hpfp", .asimdhp)
}
try_set("hw.optional.arm.FEAT_BF16", .bf16)
// try_set("hw.optional.arm.FEAT_DotProd", .dotprod)
try_set(&_features, "hw.optional.arm.FEAT_BF16", .bf16)
// try_set(&_features, "hw.optional.arm.FEAT_DotProd", .dotprod)
if !try_set("hw.optional.arm.FEAT_FCMA", .fcma) {
try_set("hw.optional.armv8_3_compnum", .fcma)
if !try_set(&_features, "hw.optional.arm.FEAT_FCMA", .fcma) {
try_set(&_features, "hw.optional.armv8_3_compnum", .fcma)
}
if !try_set("hw.optional.arm.FEAT_FHM", .fhm) {
try_set("hw.optional.armv8_2_fhm", .fhm)
if !try_set(&_features, "hw.optional.arm.FEAT_FHM", .fhm) {
try_set(&_features, "hw.optional.armv8_2_fhm", .fhm)
}
if !try_set("hw.optional.arm.FEAT_FP16", .fp16) {
try_set("hw.optional.neon_fp16", .fp16)
if !try_set(&_features, "hw.optional.arm.FEAT_FP16", .fp16) {
try_set(&_features, "hw.optional.neon_fp16", .fp16)
}
try_set("hw.optional.arm.FEAT_FRINTTS", .frint)
try_set("hw.optional.arm.FEAT_I8MM", .i8mm)
try_set("hw.optional.arm.FEAT_JSCVT", .jscvt)
try_set("hw.optional.arm.FEAT_RDM", .rdm)
try_set(&_features, "hw.optional.arm.FEAT_FRINTTS", .frint)
try_set(&_features, "hw.optional.arm.FEAT_I8MM", .i8mm)
try_set(&_features, "hw.optional.arm.FEAT_JSCVT", .jscvt)
try_set(&_features, "hw.optional.arm.FEAT_RDM", .rdm)
}
// Integer capabilities:
{
try_set("hw.optional.arm.FEAT_FlagM", .flagm)
try_set("hw.optional.arm.FEAT_FlagM2", .flagm2)
try_set("hw.optional.armv8_crc32", .crc32)
try_set(&_features, "hw.optional.arm.FEAT_FlagM", .flagm)
try_set(&_features, "hw.optional.arm.FEAT_FlagM2", .flagm2)
try_set(&_features, "hw.optional.armv8_crc32", .crc32)
}
// Atomic and memory ordering instruction capabilities:
{
try_set("hw.optional.arm.FEAT_LRCPC", .lrcpc)
try_set("hw.optional.arm.FEAT_LRCPC2", .lrcpc2)
try_set(&_features, "hw.optional.arm.FEAT_LRCPC", .lrcpc)
try_set(&_features, "hw.optional.arm.FEAT_LRCPC2", .lrcpc2)
if !try_set("hw.optional.arm.FEAT_LSE", .lse) {
try_set("hw.optional.armv8_1_atomics", .lse)
if !try_set(&_features, "hw.optional.arm.FEAT_LSE", .lse) {
try_set(&_features, "hw.optional.armv8_1_atomics", .lse)
}
// try_set("hw.optional.arm.FEAT_LSE2", .lse2)
// try_set(&_features, "hw.optional.arm.FEAT_LSE2", .lse2)
}
// Encryption capabilities:
{
try_set("hw.optional.arm.FEAT_AES", .aes)
try_set("hw.optional.arm.FEAT_PMULL", .pmull)
try_set("hw.optional.arm.FEAT_SHA1", .sha1)
try_set("hw.optional.arm.FEAT_SHA256", .sha256)
try_set(&_features, "hw.optional.arm.FEAT_AES", .aes)
try_set(&_features, "hw.optional.arm.FEAT_PMULL", .pmull)
try_set(&_features, "hw.optional.arm.FEAT_SHA1", .sha1)
try_set(&_features, "hw.optional.arm.FEAT_SHA256", .sha256)
if !try_set("hw.optional.arm.FEAT_SHA512", .sha512) {
try_set("hw.optional.armv8_2_sha512", .sha512)
if !try_set(&_features, "hw.optional.arm.FEAT_SHA512", .sha512) {
try_set(&_features, "hw.optional.armv8_2_sha512", .sha512)
}
if !try_set("hw.optional.arm.FEAT_SHA3", .sha3) {
try_set("hw.optional.armv8_2_sha3", .sha3)
if !try_set(&_features, "hw.optional.arm.FEAT_SHA3", .sha3) {
try_set(&_features, "hw.optional.armv8_2_sha3", .sha3)
}
}
// General capabilities:
{
// try_set("hw.optional.arm.FEAT_BTI", .bti)
// try_set("hw.optional.arm.FEAT_DPB", .dpb)
// try_set("hw.optional.arm.FEAT_DPB2", .dpb2)
// try_set("hw.optional.arm.FEAT_ECV", .ecv)
try_set("hw.optional.arm.FEAT_SB", .sb)
try_set("hw.optional.arm.FEAT_SSBS", .ssbs)
// try_set(&_features, "hw.optional.arm.FEAT_BTI", .bti)
// try_set(&_features, "hw.optional.arm.FEAT_DPB", .dpb)
// try_set(&_features, "hw.optional.arm.FEAT_DPB2", .dpb2)
// try_set(&_features, "hw.optional.arm.FEAT_ECV", .ecv)
try_set(&_features, "hw.optional.arm.FEAT_SB", .sb)
try_set(&_features, "hw.optional.arm.FEAT_SSBS", .ssbs)
}
}
}

View File

@@ -3,6 +3,12 @@ package sysinfo
import "base:intrinsics"
// cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) ---
cpuid :: intrinsics.x86_cpuid
// xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
xgetbv :: intrinsics.x86_xgetbv
CPU_Feature :: enum u64 {
aes, // AES hardware implementation (AES NI)
adx, // Multi-precision add-carry instruction extensions
@@ -43,16 +49,14 @@ CPU_Feature :: enum u64 {
}
CPU_Features :: distinct bit_set[CPU_Feature; u64]
CPU :: struct {
name: Maybe(string),
features: Maybe(CPU_Features),
physical_cores: int, // Initialized by cpu_<os>.odin
logical_cores: int, // Initialized by cpu_<os>.odin
}
cpu: CPU
// `cpuid` is a barrier so we try not performing this query this more than necessary.
// `atomic_load_explicit` is also a barrier, so we're doing this lookup only once, before `main()`
@(private) _features: CPU_Features
@(private) _features_ok: bool
@(init, private)
init_cpu_features :: proc "contextless" () {
_init_cpu_features :: proc "contextless" () {
is_set :: #force_inline proc "c" (bit: u32, value: u32) -> bool {
return (value>>bit) & 0x1 != 0
}
@@ -64,29 +68,30 @@ init_cpu_features :: proc "contextless" () {
max_id, _, _, _ := cpuid(0, 0)
if max_id < 1 {
_features = {}
_features_ok = false
return
}
set: CPU_Features
_, _, ecx1, edx1 := cpuid(1, 0)
try_set(&set, .sse2, 26, edx1)
try_set(&set, .sse3, 0, ecx1)
try_set(&set, .pclmulqdq, 1, ecx1)
try_set(&set, .ssse3, 9, ecx1)
try_set(&set, .fma, 12, ecx1)
try_set(&set, .sse41, 19, ecx1)
try_set(&set, .sse42, 20, ecx1)
try_set(&set, .popcnt, 23, ecx1)
try_set(&set, .aes, 25, ecx1)
try_set(&set, .os_xsave, 27, ecx1)
try_set(&set, .rdrand, 30, ecx1)
try_set(&_features, .sse2, 26, edx1)
try_set(&_features, .sse3, 0, ecx1)
try_set(&_features, .pclmulqdq, 1, ecx1)
try_set(&_features, .ssse3, 9, ecx1)
try_set(&_features, .fma, 12, ecx1)
try_set(&_features, .sse41, 19, ecx1)
try_set(&_features, .sse42, 20, ecx1)
try_set(&_features, .popcnt, 23, ecx1)
try_set(&_features, .aes, 25, ecx1)
try_set(&_features, .os_xsave, 27, ecx1)
try_set(&_features, .rdrand, 30, ecx1)
_features_ok = true
when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
// xgetbv is an illegal instruction under FreeBSD 13, OpenBSD 7.1 and NetBSD 10
// return before probing further
cpu.features = set
return
}
@@ -97,15 +102,15 @@ init_cpu_features :: proc "contextless" () {
// after they started checking both OSXSAVE and XSAVE.
//
// See: crbug.com/375968
os_supports_avx := false
os_supports_avx := false
os_supports_avx512 := false
if .os_xsave in set && is_set(26, ecx1) {
if .os_xsave in _features && is_set(26, ecx1) {
eax, _ := xgetbv(0)
os_supports_avx = is_set(1, eax) && is_set(2, eax)
os_supports_avx = is_set(1, eax) && is_set(2, eax)
os_supports_avx512 = is_set(5, eax) && is_set(6, eax) && is_set(7, eax)
}
if os_supports_avx {
try_set(&set, .avx, 28, ecx1)
try_set(&_features, .avx, 28, ecx1)
}
if max_id < 7 {
@@ -113,56 +118,61 @@ init_cpu_features :: proc "contextless" () {
}
_, ebx7, ecx7, edx7 := cpuid(7, 0)
try_set(&set, .bmi1, 3, ebx7)
try_set(&set, .sha, 29, ebx7)
try_set(&_features, .bmi1, 3, ebx7)
try_set(&_features, .sha, 29, ebx7)
if os_supports_avx {
try_set(&set, .avx2, 5, ebx7)
try_set(&_features, .avx2, 5, ebx7)
}
if os_supports_avx512 {
try_set(&set, .avx512f, 16, ebx7)
try_set(&set, .avx512dq, 17, ebx7)
try_set(&set, .avx512ifma, 21, ebx7)
try_set(&set, .avx512pf, 26, ebx7)
try_set(&set, .avx512er, 27, ebx7)
try_set(&set, .avx512cd, 28, ebx7)
try_set(&set, .avx512bw, 30, ebx7)
try_set(&_features, .avx512f, 16, ebx7)
try_set(&_features, .avx512dq, 17, ebx7)
try_set(&_features, .avx512ifma, 21, ebx7)
try_set(&_features, .avx512pf, 26, ebx7)
try_set(&_features, .avx512er, 27, ebx7)
try_set(&_features, .avx512cd, 28, ebx7)
try_set(&_features, .avx512bw, 30, ebx7)
// XMM/YMM are also required for 128/256-bit instructions
if os_supports_avx {
try_set(&set, .avx512vl, 31, ebx7)
try_set(&_features, .avx512vl, 31, ebx7)
}
try_set(&set, .avx512vbmi, 1, ecx7)
try_set(&set, .avx512vbmi2, 6, ecx7)
try_set(&set, .avx512vnni, 11, ecx7)
try_set(&set, .avx512bitalg, 12, ecx7)
try_set(&set, .avx512vpopcntdq, 14, ecx7)
try_set(&_features, .avx512vbmi, 1, ecx7)
try_set(&_features, .avx512vbmi2, 6, ecx7)
try_set(&_features, .avx512vnni, 11, ecx7)
try_set(&_features, .avx512bitalg, 12, ecx7)
try_set(&_features, .avx512vpopcntdq, 14, ecx7)
try_set(&set, .avx512vp2intersect, 8, edx7)
try_set(&set, .avx512fp16, 23, edx7)
try_set(&_features, .avx512vp2intersect, 8, edx7)
try_set(&_features, .avx512fp16, 23, edx7)
eax7_1, _, _, _ := cpuid(7, 1)
try_set(&set, .avx512bf16, 5, eax7_1)
try_set(&_features, .avx512bf16, 5, eax7_1)
}
try_set(&set, .bmi2, 8, ebx7)
try_set(&set, .erms, 9, ebx7)
try_set(&set, .rdseed, 18, ebx7)
try_set(&set, .adx, 19, ebx7)
cpu.features = set
try_set(&_features, .bmi2, 8, ebx7)
try_set(&_features, .erms, 9, ebx7)
try_set(&_features, .rdseed, 18, ebx7)
try_set(&_features, .adx, 19, ebx7)
}
@(private)
_cpu_name_buf: [72]u8
_cpu_features :: proc "contextless" () -> (features: CPU_Features, ok: bool) {
return _features, _features_ok
}
// `cpuid` is a barrier so we try not performing this query this more than necessary.
// `atomic_load_explicit` is also a barrier, so we're doing this lookup only once, before `main()`
@(private) _name_buf: [72]u8
@(private) _name: string
@(init, private)
init_cpu_name :: proc "contextless" () {
_init_cpu_name :: proc "contextless" () {
number_of_extended_ids, _, _, _ := cpuid(0x8000_0000, 0)
if number_of_extended_ids < 0x8000_0004 {
return
}
_buf := (^[0x12]u32)(&_cpu_name_buf)
_buf := (^[0x12]u32)(&_name_buf)
_buf[ 0], _buf[ 1], _buf[ 2], _buf[ 3] = cpuid(0x8000_0002, 0)
_buf[ 4], _buf[ 5], _buf[ 6], _buf[ 7] = cpuid(0x8000_0003, 0)
_buf[ 8], _buf[ 9], _buf[10], _buf[11] = cpuid(0x8000_0004, 0)
@@ -170,18 +180,16 @@ init_cpu_name :: proc "contextless" () {
// Some CPUs like may include leading or trailing spaces. Trim them.
// e.g. ` Intel(R) Xeon(R) CPU E5-1650 v2 @ 3.50GHz`
brand := string(_cpu_name_buf[:])
for len(brand) > 0 && brand[0] == 0 || brand[0] == ' ' {
brand = brand[1:]
_name = string(_name_buf[:])
for len(_name) > 0 && _name[0] == 0 || _name[0] == ' ' {
_name = _name[1:]
}
for len(brand) > 0 && brand[len(brand) - 1] == 0 || brand[len(brand) - 1] == ' ' {
brand = brand[:len(brand) - 1]
for len(_name) > 0 && _name[len(_name) - 1] == 0 || _name[len(_name) - 1] == ' ' {
_name = _name[:len(_name) - 1]
}
cpu.name = brand
}
// cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) ---
cpuid :: intrinsics.x86_cpuid
// xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
xgetbv :: intrinsics.x86_xgetbv
@(private)
_cpu_name :: proc "contextless" () -> (name: string) {
return _name
}

View File

@@ -4,10 +4,16 @@ package sysinfo
import "base:runtime"
import "core:sys/linux"
import "core:strconv"
import "core:strings"
@(private)
_cpu_features :: proc "contextless" () -> (features: CPU_Features, ok: bool) {
return _features, _features_ok
}
@(init, private)
init_cpu_features :: proc "contextless" () {
_init_cpu_features :: proc "contextless" () {
context = runtime.default_context()
fd, err := linux.open("/proc/cpuinfo", {})
if err != .NONE { return }
@@ -18,9 +24,6 @@ init_cpu_features :: proc "contextless" () {
n, rerr := linux.read(fd, buf[:])
if rerr != .NONE || n == 0 { return }
features: CPU_Features
defer cpu.features = features
str := string(buf[:n])
for line in strings.split_lines_iterator(&str) {
key, _, value := strings.partition(line, ":")
@@ -29,39 +32,72 @@ init_cpu_features :: proc "contextless" () {
if key != "Features" { continue }
_features_ok = true
for feature in strings.split_by_byte_iterator(&value, ' ') {
switch feature {
case "asimd", "neon": features += { .asimd }
case "fp": features += { .floatingpoint }
case "asimdhp": features += { .asimdhp }
case "asimdbf16": features += { .bf16 }
case "fcma": features += { .fcma }
case "asimdfhm": features += { .fhm }
case "fphp", "half": features += { .fp16 }
case "frint": features += { .frint }
case "i8mm": features += { .i8mm }
case "jscvt": features += { .jscvt }
case "asimdrdm": features += { .rdm }
case "asimd", "neon": _features += { .asimd }
case "fp": _features += { .floatingpoint }
case "asimdhp": _features += { .asimdhp }
case "asimdbf16": _features += { .bf16 }
case "fcma": _features += { .fcma }
case "asimdfhm": _features += { .fhm }
case "fphp", "half": _features += { .fp16 }
case "frint": _features += { .frint }
case "i8mm": _features += { .i8mm }
case "jscvt": _features += { .jscvt }
case "asimdrdm": _features += { .rdm }
case "flagm": features += { .flagm }
case "flagm2": features += { .flagm2 }
case "crc32": features += { .crc32 }
case "flagm": _features += { .flagm }
case "flagm2": _features += { .flagm2 }
case "crc32": _features += { .crc32 }
case "atomics": features += { .lse }
case "lrcpc": features += { .lrcpc }
case "ilrcpc": features += { .lrcpc2 }
case "atomics": _features += { .lse }
case "lrcpc": _features += { .lrcpc }
case "ilrcpc": _features += { .lrcpc2 }
case "aes": features += { .aes }
case "pmull": features += { .pmull }
case "sha1": features += { .sha1 }
case "sha2": features += { .sha256 }
case "sha3": features += { .sha3 }
case "sha512": features += { .sha512 }
case "aes": _features += { .aes }
case "pmull": _features += { .pmull }
case "sha1": _features += { .sha1 }
case "sha2": _features += { .sha256 }
case "sha3": _features += { .sha3 }
case "sha512": _features += { .sha512 }
case "sb": features += { .sb }
case "ssbs": features += { .ssbs }
case "sb": _features += { .sb }
case "ssbs": _features += { .ssbs }
}
}
break
}
}
@(private)
_cpu_core_count :: proc "contextless" () -> (physical: int, logical: int, ok: bool) {
context = runtime.default_context()
fd, err := linux.open("/proc/cpuinfo", {})
if err != .NONE { return }
defer linux.close(fd)
// This is probably enough right?
buf: [4096]byte
n, rerr := linux.read(fd, buf[:])
if rerr != .NONE || n == 0 { return }
physical_ok, logical_ok: bool
str := string(buf[:n])
for line in strings.split_lines_iterator(&str) {
key, _, value := strings.partition(line, ":")
key = strings.trim_space(key)
value = strings.trim_space(value)
if key == "cpu cores" && !physical_ok{
physical, physical_ok = strconv.parse_int(value)
}
if key == "siblings" && !logical_ok{
logical, logical_ok = strconv.parse_int(value)
}
}
return physical, logical, physical_ok || logical_ok
}

View File

@@ -7,10 +7,9 @@ import "core:sys/linux"
import "core:strings"
import "core:strconv"
@(init, private)
init_cpu_core_count :: proc "contextless" () {
@(private)
_cpu_core_count :: proc "contextless" () -> (physical: int, logical: int, ok: bool) {
context = runtime.default_context()
fd, err := linux.open("/proc/cpuinfo", {})
if err != .NONE { return }
defer linux.close(fd)
@@ -20,22 +19,21 @@ init_cpu_core_count :: proc "contextless" () {
n, rerr := linux.read(fd, buf[:])
if rerr != .NONE || n == 0 { return }
physical_ok, logical_ok: bool
str := string(buf[:n])
for line in strings.split_lines_iterator(&str) {
key, _, value := strings.partition(line, ":")
key = strings.trim_space(key)
value = strings.trim_space(value)
if key == "cpu cores" {
if num_physical_cores, ok := strconv.parse_int(value); ok {
cpu.physical_cores = num_physical_cores
}
if key == "cpu cores" && !physical_ok{
physical, physical_ok = strconv.parse_int(value)
}
if key == "siblings" {
if num_logical_cores, ok := strconv.parse_int(value); ok {
cpu.logical_cores = num_logical_cores
}
if key == "siblings" && !logical_ok{
logical, logical_ok = strconv.parse_int(value)
}
}
return physical, logical, physical_ok || logical_ok
}

View File

@@ -3,14 +3,13 @@
package sysinfo
import "base:intrinsics"
import "base:runtime"
import "core:sys/linux"
import "core:strconv"
import "core:strings"
@(init, private)
init_cpu_features :: proc "contextless" () {
_features: CPU_Features
defer cpu.features = _features
_init_cpu_features :: proc "contextless" () {
HWCAP_Bits :: enum u64 {
I = 'I' - 'A',
M = 'M' - 'A',
@@ -72,6 +71,8 @@ init_cpu_features :: proc "contextless" () {
}
}
_features_ok = true
// hwprobe for other features.
{
pairs := []linux.RISCV_HWProbe{
@@ -107,7 +108,43 @@ init_cpu_features :: proc "contextless" () {
}
}
@(init, private)
init_cpu_name :: proc "contextless" () {
cpu.name = "RISCV64"
@(private)
_cpu_features :: proc "contextless" () -> (features: CPU_Features, ok: bool) {
return _features, _features_ok
}
@(private)
_cpu_name :: proc() -> (name: string) {
return "RISCV64"
}
@(private)
_cpu_core_count :: proc "contextless" () -> (physical: int, logical: int, ok: bool) {
context = runtime.default_context() // No allocations, only needed because `core:strings` wants it.
fd, err := linux.open("/proc/cpuinfo", {})
if err != .NONE { return }
defer linux.close(fd)
// This is probably enough right?
buf: [4096]byte
n, rerr := linux.read(fd, buf[:])
if rerr != .NONE || n == 0 { return }
physical_ok, logical_ok: bool
str := string(buf[:n])
for line in strings.split_lines_iterator(&str) {
key, _, value := strings.partition(line, ":")
key = strings.trim_space(key)
value = strings.trim_space(value)
if key == "cpu cores" && !physical_ok{
physical, physical_ok = strconv.parse_int(value)
}
if key == "siblings" && !logical_ok{
logical, logical_ok = strconv.parse_int(value)
}
}
return physical, logical, physical_ok || logical_ok
}

View File

@@ -0,0 +1,14 @@
#+build openbsd, freebsd, netbsd, essence, haiku
package sysinfo
@(private)
_cpu_core_count :: proc "contextless" () -> (physical: int, logical: int, ok: bool) {
return 0, 0, false
}
when ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64 {
@(private)
_cpu_features :: proc "contextless" () -> (features: CPU_Features, ok: bool) {
return {}, false
}
}

View File

@@ -95,10 +95,8 @@ CPU_Feature :: enum u64 {
}
CPU_Features :: distinct bit_set[CPU_Feature; u64]
CPU :: struct {
name: Maybe(string),
features: Maybe(CPU_Features),
physical_cores: int,
logical_cores: int,
}
cpu: CPU
// Looking up CPU features is expensive on RISCV, and `atomic_load_explicit`
// is also a barrier, so we're doing this lookup only once, before `main()`
@(private) _features: CPU_Features
@(private) _features_ok: bool

View File

@@ -0,0 +1,20 @@
#+build wasm32, wasm64p32
package sysinfo
@(private)
_cpu_core_count :: proc "contextless" () -> (physical: int, logical: int, ok: bool) {
return 0, 0, false
}
CPU_Feature :: enum u64 {}
CPU_Features :: distinct bit_set[CPU_Feature; u64]
@(private)
_cpu_features :: proc "contextless" () -> (features: CPU_Features, ok: bool) {
return {}, false
}
@(private)
_cpu_name :: proc() -> (name: string) {
return "wasm32" when ODIN_ARCH == .wasm32 else "wasm64p32"
}

View File

@@ -2,30 +2,34 @@ package sysinfo
import sys "core:sys/windows"
import "base:intrinsics"
import "base:runtime"
@(init, private)
init_cpu_core_count :: proc "contextless" () {
context = runtime.default_context()
@(private)
_cpu_core_count :: proc "contextless" () -> (physical: int, logical: int, ok: bool) {
// Reportedly, Windows Server supports a maximum of 256 logical cores.
// The most scratch memory we need therefore is 8192 bytes = 256 * size_of(sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION)
infos: [256]sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION
infos: []sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION
defer delete(infos)
returned_length: sys.DWORD
// Query for the required buffer size.
if ok := sys.GetLogicalProcessorInformation(raw_data(infos), &returned_length); !ok {
infos = make([]sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION, returned_length / size_of(sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION))
returned_length: sys.DWORD
sys.GetLogicalProcessorInformation(nil, &returned_length)
if int(returned_length) > size_of(infos) {
return 0, 0, false
}
count := int(returned_length) / size_of(sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION)
// If it still doesn't work, return
if ok := sys.GetLogicalProcessorInformation(raw_data(infos), &returned_length); !ok {
return
if ok := sys.GetLogicalProcessorInformation(raw_data(infos[:]), &returned_length); !ok {
return 0, 0, false
}
for info in infos {
for info in infos[:count] {
#partial switch info.Relationship {
case .RelationProcessorCore: cpu.physical_cores += 1
case .RelationNumaNode: cpu.logical_cores += int(intrinsics.count_ones(info.ProcessorMask))
case .RelationProcessorCore: physical += 1
case .RelationNumaNode: logical += int(intrinsics.count_ones(info.ProcessorMask))
}
}
return physical, logical, true
}

View File

@@ -16,66 +16,93 @@ Example:
import si "core:sys/info"
main :: proc() {
fmt.printfln("Odin: %v", ODIN_VERSION)
fmt.printfln("OS: %v", si.os_version.as_string)
fmt.printfln("OS: %#v", si.os_version)
fmt.printfln("CPU: %v", si.cpu.name)
fmt.printfln("CPU cores: %vc/%vt", si.cpu.physical_cores, si.cpu.logical_cores)
fmt.printfln("RAM: %#.1M", si.ram.total_ram)
fmt.printfln("Odin: %v", ODIN_VERSION)
if version, version_ok := si.os_version(context.allocator); version_ok {
defer si.destroy_os_version(version, context.allocator)
fmt.printfln("OS (full): %v", version.full)
fmt.printfln("OS (rel): %v", version.release)
fmt.printfln("OS: %v", version.os)
fmt.printfln("Kernel: %v", version.kernel)
}
fmt.printfln("CPU: %v", si.cpu_name())
fmt.println()
for gpu, i in si.gpus {
fmt.printfln("GPU #%v:", i)
fmt.printfln("\tVendor: %v", gpu.vendor_name)
fmt.printfln("\tModel: %v", gpu.model_name)
fmt.printfln("\tVRAM: %#.1M", gpu.total_ram)
if features, features_ok := si.cpu_features(); features_ok {
fmt.printfln(" %v", features)
}
if physical, logical, cores_ok := si.cpu_core_count(); cores_ok {
fmt.printfln("CPU cores: %vc/%vt", physical, logical)
}
if total_ram, free_ram, total_swap, free_swap, ram_ok := si.ram_stats(); ram_ok {
fmt.printfln("RAM: %#.1M/%#.1M", free_ram, total_ram)
fmt.printfln("SWAP: %#.1M/%#.1M", free_swap, total_swap)
}
it: si.GPU_Iterator
for gpu, i in si.iterate_gpus(&it) {
fmt.printfln("%d:", i)
fmt.printfln("\tVendor: %v", gpu.vendor)
fmt.printfln("\tModel: %v", gpu.model)
fmt.printfln("\tVRAM: %#.1M", gpu.vram)
fmt.printfln("\tDriver: %v", gpu.driver)
}
}
- Example Windows output:
Odin: dev-2025-10
OS: Windows 10 Professional (version: 22H2), build: 19045.6396
OS: OS_Version{
platform = "Windows",
_ = Version{
major = 10,
minor = 0,
patch = 0,
},
build = [
19045,
6396,
],
version = "22H2",
as_string = "Windows 10 Professional (version: 22H2), build: 19045.6396",
}
Odin: dev-2026-02
OS (full): Windows 10 Professional (version: 22H2), build: 19045.6575
OS (rel): 22H2
OS: Version{major = 10, minor = 0, patch = 0}
Kernel: Version{major = 10, minor = 19045, patch = 6575}
CPU: AMD Ryzen 9 5950X 16-Core Processor
CPU_Features{aes, adx, avx, avx2, bmi1, bmi2, erms, fma, os_xsave, pclmulqdq, popcnt, rdrand, rdseed, sha, sse2, sse3, ssse3, sse41, sse42}
CPU cores: 16c/32t
RAM: 63.9 GiB
RAM: 32.1 GiB/63.9 GiB
SWAP: 21.6 GiB/73.4 GiB
GPU #0:
Vendor: Advanced Micro Devices, Inc.
Model: AMD Radeon RX 9070
VRAM: 15.9 GiB
- Example Linux output:
Odin: dev-2026-02
OS (full): Ubuntu 24.04.3 LTS, Linux 6.6.87.2-microsoft-standard-WSL2
OS (rel): microsoft-standard-WSL2
OS: Version{major = 24, minor = 4, patch = 3}
Kernel: Version{major = 6, minor = 6, patch = 87}
CPU: AMD Ryzen 9 5950X 16-Core Processor
CPU_Features{aes, adx, avx, avx2, bmi1, bmi2, erms, fma, os_xsave, pclmulqdq, popcnt, rdrand, rdseed, sha, sse2, sse3, ssse3, sse41, sse42}
CPU cores: 16c/32t
RAM: 29.2 GiB/31.3 GiB
SWAP: 8.0 GiB/8.0 GiB
- Example macOS output:
ODIN: dev-2022-09
OS: OS_Version{
platform = "MacOS",
major = 21,
minor = 5,
patch = 0,
build = [
0,
0,
],
version = "21F79",
as_string = "macOS Monterey 12.4 (build 21F79, kernel 21.5.0)",
}
CPU: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
RAM: 8.0 GiB
Odin: dev-2026-02
OS (full): macOS Tahoe 26.3.0 (build 25D125, kernel 25.3.0)
OS (rel): 25D125
OS: Version{major = 26, minor = 3, patch = 0}
Kernel: Version{major = 25, minor = 3, patch = 0}
CPU: Apple M4 Pro
CPU_Features{asimd, floatingpoint, asimdhp, bf16, fcma, fhm, fp16, frint, i8mm, jscvt, rdm, flagm, flagm2, crc32, lse, lrcpc, lrcpc2, aes, pmull, sha1, sha256, sha512, sha3, sb}
CPU cores: 12c/12t
RAM: 0.0 B/24.0 GiB
SWAP: 0.0 B/0.0 B
- Example FreeBSD output:
Odin: dev-2026-02
OS (full): FreeBSD 15.0-RELEASE-p2 releng/15.0-n281005-5fb0f8e9e61d GENERIC, revision 199506
OS (rel):
OS: Version{major = 15, minor = 0, patch = 199506}
Kernel: Version{major = 15, minor = 0, patch = 199506}
CPU: AMD Ryzen 9 5950X 16-Core Processor
CPU_Features{aes, fma, os_xsave, pclmulqdq, popcnt, rdrand, sse2, sse3, ssse3, sse41, sse42}
RAM: 7.6 GiB/7.9 GiB
SWAP: 0.0 B/0.0 B
*/
package sysinfo

View File

@@ -1,24 +1,20 @@
#+build openbsd, netbsd
package sysinfo
import sys "core:sys/unix"
import "core:strings"
import "core:strconv"
import "base:runtime"
import "core:strings"
import sys "core:sys/unix"
@(init, private)
init_os_version :: proc "contextless" () {
context = runtime.default_context()
_os_version :: proc (allocator: runtime.Allocator, loc := #caller_location) -> (res: OS_Version, ok: bool) {
when ODIN_OS == .NetBSD {
os_version.platform = .NetBSD
res.platform = .NetBSD
} else {
os_version.platform = .OpenBSD
res.platform = .OpenBSD
}
kernel_version_buf: [1024]u8
b := strings.builder_from_bytes(version_string_buf[:])
b := strings.builder_make_none(allocator = allocator, loc = loc)
// Retrieve kernel info using `sysctl`, e.g. OpenBSD and NetBSD
mib := []i32{sys.CTL_KERN, sys.KERN_OSTYPE}
if !sys.sysctl(mib, &kernel_version_buf) {
@@ -36,19 +32,8 @@ init_os_version :: proc "contextless" () {
version := string(cstring(raw_data(kernel_version_buf[:])))
strings.write_string(&b, version)
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
// Parse kernel version
triplet := strings.split(version, ".", context.temp_allocator)
if len(triplet) == 2 {
major, major_ok := strconv.parse_int(triplet[0])
minor, minor_ok := strconv.parse_int(triplet[1])
if major_ok && minor_ok {
os_version.major = major
os_version.minor = minor
}
}
res.kernel = _parse_version(version)
// Retrieve kernel revision using `sysctl`, e.g. 199506
mib = []i32{sys.CTL_KERN, sys.KERN_OSREV}
@@ -56,20 +41,27 @@ init_os_version :: proc "contextless" () {
if !sys.sysctl(mib, &revision) {
return
}
os_version.patch = revision
res.kernel.patch = revision
strings.write_string(&b, ", build ")
strings.write_int(&b, revision)
// Finalize pretty name.
os_version.as_string = strings.to_string(b)
res.full = strings.to_string(b)
return res, true
}
@(init, private)
init_ram :: proc "contextless" () {
@(private)
_ram_stats :: proc "contextless" () -> (total_ram, free_ram, total_swap, free_swap: i64, ok: bool) {
// Retrieve RAM info using `sysctl`
mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM64}
mem_size: u64
if sys.sysctl(mib, &mem_size) {
ram.total_ram = int(mem_size)
if sys.sysctl(mib, &total_ram) {
ok = true
}
}
mib = []i32{sys.CTL_HW, sys.HW_USERMEM64}
if sys.sysctl(mib, &free_ram) {
ok = true
}
return
}

View File

@@ -1,19 +1,16 @@
package sysinfo
import "base:runtime"
import "core:strconv"
import "base:runtime"
import "core:strings"
import "core:sys/unix"
import NS "core:sys/darwin/Foundation"
@(init, private)
init_platform :: proc "contextless" () {
context = runtime.default_context()
@(private)
_os_version :: proc (allocator: runtime.Allocator, loc := #caller_location) -> (res: OS_Version, ok: bool) {
ws :: strings.write_string
wi :: strings.write_int
b := strings.builder_from_bytes(version_string_buf[:])
b := strings.builder_make_none(allocator = allocator, loc = loc)
version: NS.OperatingSystemVersion
{
@@ -21,18 +18,15 @@ init_platform :: proc "contextless" () {
info := NS.ProcessInfo.processInfo()
version = info->operatingSystemVersion()
mem := info->physicalMemory()
ram.total_ram = int(mem)
}
macos_version = {int(version.majorVersion), int(version.minorVersion), int(version.patchVersion)}
res.os = {int(version.majorVersion), int(version.minorVersion), int(version.patchVersion)}
when ODIN_PLATFORM_SUBTARGET_IOS {
os_version.platform = .iOS
res.platform = .iOS
ws(&b, "iOS")
} else {
os_version.platform = .MacOS
res.platform = .MacOS
switch version.majorVersion {
case 26: ws(&b, "macOS Tahoe")
case 15: ws(&b, "macOS Sequoia")
@@ -69,34 +63,37 @@ init_platform :: proc "contextless" () {
{
build_buf: [12]u8
mib := []i32{unix.CTL_KERN, unix.KERN_OSVERSION}
ok := unix.sysctl(mib, &build_buf)
build := string(cstring(raw_data(build_buf[:]))) if ok else "Unknown"
build := "Unknown"
if unix.sysctl(mib, &build_buf) {
build = string(cstring(raw_data(build_buf[:])))
}
ws(&b, " (build ")
build_start := len(b.buf)
ws(&b, build)
os_version.version = string(b.buf[build_start:][:len(build)])
res.release = string(b.buf[build_start:][:len(build)])
}
{
// Match on XNU kernel version
version_bits: [12]u8 // enough for 999.999.999\x00
mib := []i32{unix.CTL_KERN, unix.KERN_OSRELEASE}
ok := unix.sysctl(mib, &version_bits)
kernel := string(cstring(raw_data(version_bits[:]))) if ok else "Unknown"
major, _, tail := strings.partition(kernel, ".")
minor, _, patch := strings.partition(tail, ".")
os_version.major, _ = strconv.parse_int(major, 10)
os_version.minor, _ = strconv.parse_int(minor, 10)
os_version.patch, _ = strconv.parse_int(patch, 10)
kernel := "Unknown"
if unix.sysctl(mib, &version_bits) {
kernel = string(cstring(raw_data(version_bits[:])))
res.kernel = _parse_version(kernel)
}
ws(&b, ", kernel ")
ws(&b, kernel)
ws(&b, ")")
}
os_version.as_string = string(b.buf[:])
res.full = strings.to_string(b)
return res, true
}
@(private)
_ram_stats :: proc "contextless" () -> (total_ram, free_ram, total_swap, free_swap: i64, ok: bool) {
NS.scoped_autoreleasepool()
info := NS.ProcessInfo.processInfo()
return i64(info->physicalMemory()), 0, 0, 0, true
}

View File

@@ -2,18 +2,15 @@ package sysinfo
import sys "core:sys/unix"
import "core:strings"
import "core:strconv"
import "base:runtime"
@(init, private)
init_os_version :: proc "contextless" () {
context = runtime.default_context()
os_version.platform = .FreeBSD
@(private)
_os_version :: proc (allocator: runtime.Allocator, loc := #caller_location) -> (res: OS_Version, ok: bool) {
res.platform = .FreeBSD
kernel_version_buf: [1024]u8
b := strings.builder_from_bytes(version_string_buf[:])
b := strings.builder_make_none(allocator = allocator, loc = loc)
// Retrieve kernel info using `sysctl`, e.g. FreeBSD 13.1-RELEASE-p2 GENERIC
mib := []i32{sys.CTL_KERN, sys.KERN_VERSION}
if !sys.sysctl(mib, &kernel_version_buf) {
@@ -24,21 +21,18 @@ init_os_version :: proc "contextless" () {
pretty_name = strings.trim(pretty_name, "\n")
strings.write_string(&b, pretty_name)
// l := strings.builder_len(b)
// Retrieve kernel revision using `sysctl`, e.g. 199506
mib = []i32{sys.CTL_KERN, sys.KERN_OSREV}
revision: int
if !sys.sysctl(mib, &revision) {
return
}
os_version.patch = revision
strings.write_string(&b, ", revision ")
strings.write_int(&b, revision)
// Finalize pretty name.
os_version.as_string = strings.to_string(b)
res.full = strings.to_string(b)
// Retrieve kernel release using `sysctl`, e.g. 13.1-RELEASE-p2
mib = []i32{sys.CTL_KERN, sys.KERN_OSRELEASE}
@@ -46,32 +40,28 @@ init_os_version :: proc "contextless" () {
return
}
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
// Parse kernel version
release := string(cstring(raw_data(kernel_version_buf[:])))
version_bits := strings.split_n(release, "-", 2, context.temp_allocator)
if len(version_bits) > 1 {
// Parse major, minor from KERN_OSRELEASE
triplet := strings.split(version_bits[0], ".", context.temp_allocator)
if len(triplet) == 2 {
major, major_ok := strconv.parse_int(triplet[0])
minor, minor_ok := strconv.parse_int(triplet[1])
version_bits, _, _ := strings.partition(release, "-")
res.kernel = _parse_version(version_bits)
res.kernel.patch = revision
if major_ok && minor_ok {
os_version.major = major
os_version.minor = minor
}
}
}
res.os = res.kernel
return res, true
}
@(init, private)
init_ram :: proc "contextless" () {
@(private)
_ram_stats :: proc "contextless" () -> (total_ram, free_ram, total_swap, free_swap: i64, ok: bool) {
// Retrieve RAM info using `sysctl`
mib := []i32{sys.CTL_HW, sys.HW_PHYSMEM}
mem_size: u64
if sys.sysctl(mib, &mem_size) {
ram.total_ram = int(mem_size)
if sys.sysctl(mib, &total_ram) {
ok = true
}
}
mib = []i32{sys.CTL_HW, sys.HW_USERMEM}
if sys.sysctl(mib, &free_ram) {
ok = true
}
return
}

View File

@@ -2,18 +2,14 @@ package sysinfo
import "base:intrinsics"
import "base:runtime"
import "core:strconv"
import "core:strings"
import "core:sys/linux"
@(init, private)
init_os_version :: proc "contextless" () {
context = runtime.default_context()
@(private)
_os_version :: proc (allocator: runtime.Allocator, loc := #caller_location) -> (res: OS_Version, ok: bool) {
res.platform = .Linux
os_version.platform = .Linux
b := strings.builder_from_bytes(version_string_buf[:])
b := strings.builder_make_none(allocator = allocator, loc = loc)
// Try to parse `/etc/os-release` for `PRETTY_NAME="Ubuntu 20.04.3 LTS`
pretty_parse: {
@@ -23,10 +19,7 @@ init_os_version :: proc "contextless" () {
break pretty_parse
}
defer {
cerrno := linux.close(fd)
assert(cerrno == .NONE, "Failed to close the file descriptor")
}
defer linux.close(fd)
os_release_buf: [2048]u8
n, read_errno := linux.read(fd, os_release_buf[:])
@@ -36,17 +29,27 @@ init_os_version :: proc "contextless" () {
}
release := string(os_release_buf[:n])
// Search the line in the file until we find "PRETTY_NAME="
NEEDLE :: "PRETTY_NAME=\""
_, _, post := strings.partition(release, NEEDLE)
if len(post) > 0 {
end := strings.index_any(post, "\"\n")
if end > -1 && post[end] == '"' {
strings.write_string(&b, post[:end])
{
// Search the line in the file until we find "PRETTY_NAME="
_, _, post := strings.partition(release, `PRETTY_NAME="`)
if len(post) > 0 {
end := strings.index_any(post, "\"\n")
if end > -1 && post[end] == '"' {
strings.write_string(&b, post[:end])
}
}
if strings.builder_len(b) == 0 {
strings.write_string(&b, "Unknown Linux Distro")
}
}
if strings.builder_len(b) == 0 {
strings.write_string(&b, "Unknown Linux Distro")
{
// Search the line in the file until we find "VERSION="
_, _, post := strings.partition(release, `VERSION="`)
if len(post) > 0 {
pre, _, _ := strings.partition(post, ` `)
res.os = _parse_version(pre)
}
}
}
@@ -63,43 +66,30 @@ init_os_version :: proc "contextless" () {
strings.write_string(&b, string(cstring(&uts.release[0])))
release_str := string(b.buf[release_i:])
os_version.as_string = strings.to_string(b)
res.full = strings.to_string(b)
// Parse the Linux version out of the release string
version_loop: {
version_num, _, version_suffix := strings.partition(release_str, "-")
os_version.version = version_suffix
res.release = version_suffix
res.kernel = _parse_version(version_num)
i: int
for part in strings.split_iterator(&version_num, ".") {
defer i += 1
dst: ^int
switch i {
case 0: dst = &os_version.major
case 1: dst = &os_version.minor
case 2: dst = &os_version.patch
case: break version_loop
}
num, ok := strconv.parse_int(part)
if !ok { break version_loop }
dst^ = num
}
}
return res, true
}
@(init, private)
init_ram :: proc "contextless" () {
@(private)
_ram_stats :: proc "contextless" () -> (total_ram, free_ram, total_swap, free_swap: i64, ok: bool) {
// Retrieve RAM info using `sysinfo`
sys_info: linux.Sys_Info
errno := linux.sysinfo(&sys_info)
assert_contextless(errno == .NONE, "Good luck to whoever's debugging this, something's seriously cucked up!")
ram = RAM{
total_ram = int(sys_info.totalram) * int(sys_info.mem_unit),
free_ram = int(sys_info.freeram) * int(sys_info.mem_unit),
total_swap = int(sys_info.totalswap) * int(sys_info.mem_unit),
free_swap = int(sys_info.freeswap) * int(sys_info.mem_unit),
}
}
total_ram = i64(sys_info.totalram) * i64(sys_info.mem_unit)
free_ram = i64(sys_info.freeram) * i64(sys_info.mem_unit)
total_swap = i64(sys_info.totalswap) * i64(sys_info.mem_unit)
free_swap = i64(sys_info.freeswap) * i64(sys_info.mem_unit)
ok = true
return
}

View File

@@ -0,0 +1,14 @@
#+build essence, haiku
package sysinfo
import "base:runtime"
@(private)
_ram_stats :: proc "contextless" () -> (total_ram, free_ram, total_swap, free_swap: i64, ok: bool) {
return
}
@(private)
_os_version :: proc(allocator: runtime.Allocator, loc := #caller_location) -> (res: OS_Version, ok: bool) {
return {}, false
}

View File

@@ -0,0 +1,14 @@
#+build wasm32, wasm64p32
package sysinfo
import "base:runtime"
@(private)
_ram_stats :: proc "contextless" () -> (total_ram, free_ram, total_swap, free_swap: i64, ok: bool) {
return
}
@(private)
_os_version :: proc(allocator: runtime.Allocator, loc := #caller_location) -> (res: OS_Version, ok: bool) {
return {}, false
}

View File

@@ -1,33 +1,27 @@
package sysinfo
import "base:intrinsics"
import "base:runtime"
import "core:strings"
import "core:unicode/utf16"
import sys "core:sys/windows"
import "base:intrinsics"
import "core:strings"
import "core:unicode/utf16"
import "base:runtime"
@(init, private)
init_os_version :: proc "contextless" () {
// NOTE(Jeroen): Only needed for the string builder.
context = runtime.default_context()
@(private)
_os_version :: proc (allocator: runtime.Allocator, loc := #caller_location) -> (res: OS_Version, ok: bool) {
/*
NOTE(Jeroen):
`GetVersionEx` will return 6.2 for Windows 10 unless the program is manifested for Windows 10.
`RtlGetVersion` will return the true version.
Rather than include the WinDDK, we ask the kernel directly.
`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion` is for the minor build version (Update Build Release)
NOTE(Jeroen):
`GetVersionEx` will return 6.2 for Windows 10 unless the program is manifested for Windows 10.
`RtlGetVersion` will return the true version.
Rather than include the WinDDK, we ask the kernel directly.
`HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion` is for the minor build version (Update Build Release)
*/
os_version.platform = .Windows
res.platform = .Windows
osvi: sys.OSVERSIONINFOEXW
osvi.dwOSVersionInfoSize = size_of(osvi)
status := sys.RtlGetVersion(&osvi)
if status != 0 {
return
if status := sys.RtlGetVersion(&osvi); status != 0 {
return res, false
}
product_type: sys.Windows_Product_Type
@@ -37,12 +31,13 @@ init_os_version :: proc "contextless" () {
&product_type,
)
os_version.major = int(osvi.dwMajorVersion)
os_version.minor = int(osvi.dwMinorVersion)
os_version.build[0] = int(osvi.dwBuildNumber)
res.os.major = int(osvi.dwMajorVersion)
res.os.minor = int(osvi.dwMinorVersion)
res.kernel.major = int(osvi.dwMajorVersion)
res.kernel.minor = int(osvi.dwBuildNumber)
b := strings.builder_make_none(allocator = allocator, loc = loc)
b := strings.builder_from_bytes(version_string_buf[:])
strings.write_string(&b, "Windows ")
switch osvi.dwMajorVersion {
@@ -120,13 +115,13 @@ init_os_version :: proc "contextless" () {
}
// Grab DisplayVersion
os_version.version = format_display_version(&b)
res.release = format_display_version(&b)
// Grab build number and UBR
os_version.build[1] = format_build_number(&b, int(osvi.dwBuildNumber))
res.kernel.patch = format_build_number(&b, int(osvi.dwBuildNumber))
// Finish the string
os_version.as_string = strings.to_string(b)
res.full = strings.to_string(b)
format_windows_product_type :: proc (b: ^strings.Builder, prod_type: sys.Windows_Product_Type) {
#partial switch prod_type {
@@ -253,29 +248,33 @@ init_os_version :: proc "contextless" () {
}
return
}
return res, true
}
@(init, private)
init_ram :: proc "contextless" () {
@(private)
_ram_stats :: proc "contextless" () -> (total_ram, free_ram, total_swap, free_swap: i64, ok: bool) {
state: sys.MEMORYSTATUSEX
state.dwLength = size_of(state)
ok := sys.GlobalMemoryStatusEx(&state)
if !ok {
if ok := sys.GlobalMemoryStatusEx(&state); !ok {
return
}
ram = RAM{
total_ram = int(state.ullTotalPhys),
free_ram = int(state.ullAvailPhys),
total_swap = int(state.ullTotalPageFil),
free_swap = int(state.ullAvailPageFil),
}
total_ram = i64(state.ullTotalPhys)
free_ram = i64(state.ullAvailPhys)
total_swap = i64(state.ullTotalPageFil)
free_swap = i64(state.ullAvailPageFil)
ok = true
return
}
@(init, private)
init_gpu_info :: proc "contextless" () {
_iterate_gpus :: proc(it: ^GPU_Iterator, minimum_vram := i64(256 * 1024 * 1024)) -> (gpu: GPU, index: int, ok: bool) {
GPU_ROOT_KEY :: `SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}`
defer it.index += 1
gpu_key: sys.HKEY
if status := sys.RegOpenKeyExW(
sys.HKEY_LOCAL_MACHINE,
@@ -284,75 +283,64 @@ init_gpu_info :: proc "contextless" () {
sys.KEY_ENUMERATE_SUB_KEYS,
&gpu_key,
); status != i32(sys.ERROR_SUCCESS) {
return
return {}, it.index, false
}
defer sys.RegCloseKey(gpu_key)
gpu: ^GPU
buf_wstring: [100]u16
buf_len := u32(len(buf_wstring))
buf_key: [4 * len(buf_wstring)]u8
buf_leaf: [100]u8
leaf: string
index := sys.DWORD(0)
gpu_count := 0
gpu_loop: for gpu_count < MAX_GPUS {
defer index += 1
buf_wstring: [100]u16
buf_len := u32(len(buf_wstring))
buf_key: [4 * len(buf_wstring)]u8
buf_leaf: [100]u8
buf_scratch: [100]u8
gpu_loop: {
defer it._index += 1
if status := sys.RegEnumKeyW(
gpu_key,
index,
auto_cast it._index,
&buf_wstring[0],
&buf_len,
); status != i32(sys.ERROR_SUCCESS) {
break gpu_loop
return {}, it.index, false
}
utf16.decode_to_utf8(buf_leaf[:], buf_wstring[:])
leaf := string(cstring(&buf_leaf[0]))
leaf = string(cstring(&buf_leaf[0]))
// Skip leaves that are not of the form 000x
if !is_integer(leaf) {
continue
if is_integer(leaf) {
break gpu_loop
}
n := copy(buf_key[:], GPU_ROOT_KEY)
buf_key[n] = '\\'
copy(buf_key[n+1:], leaf)
key_len := len(GPU_ROOT_KEY) + len(leaf) + 1
utf16.encode_string(buf_wstring[:], string(buf_key[:key_len]))
key := cstring16(&buf_wstring[0])
if res, ok := read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "ProviderName", buf_scratch[:]); ok {
if vendor, s_ok := intern_gpu_string(res); s_ok {
gpu = &_gpus[gpu_count]
gpu.vendor_name = vendor
} else {
break gpu_loop
}
} else {
continue
}
if res, ok := read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "DriverDesc", buf_scratch[:]); ok {
if model_name, s_ok := intern_gpu_string(res); s_ok {
gpu = &_gpus[gpu_count]
gpu.model_name = model_name
} else {
break gpu_loop
}
}
if vram, ok := read_reg_i64(sys.HKEY_LOCAL_MACHINE, key, "HardwareInformation.qwMemorySize"); ok {
gpu.total_ram = int(vram)
}
gpu_count += 1 // not deferred at the top because it only increments if we get this far.
}
gpus = _gpus[:gpu_count]
n := copy(buf_key[:], GPU_ROOT_KEY)
buf_key[n] = '\\'
copy(buf_key[n+1:], leaf)
key_len := len(GPU_ROOT_KEY) + len(leaf) + 1
utf16.encode_string(buf_wstring[:], string(buf_key[:key_len]))
key := cstring16(&buf_wstring[0])
// Determine if this is a real GPU, or perhaps a screen mirroring or RDP driver
// Real devices tend to have more than 256 MiB of VRAM
gpu.vram, _ = read_reg_i64 (sys.HKEY_LOCAL_MACHINE, key, "HardwareInformation.qwMemorySize")
if gpu.vram < minimum_vram {
return
}
// Real devices tend to have a matching PCI device
matching, _ := read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "MatchingDeviceId", it._buffer[:100])
if !strings.has_prefix(matching, "PCI\\VEN") {
return
}
gpu.vendor, _ = read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "ProviderName", it._buffer[ 0:][:100])
gpu.model, _ = read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "DriverDesc", it._buffer[100:][:100])
gpu.driver, _ = read_reg_string(sys.HKEY_LOCAL_MACHINE, key, "DriverVersion", it._buffer[200:][:100])
return gpu, it.index, true
}
@(private)

View File

@@ -1,15 +1,110 @@
package sysinfo
when !(ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64 || ODIN_ARCH == .riscv64 || ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) {
#assert(false, "This package is unsupported on this architecture.")
import "base:runtime"
import "core:strings"
import "core:strconv"
#assert(
ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 || \
ODIN_ARCH == .arm32 || ODIN_ARCH == .arm64 || \
ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 || \
ODIN_ARCH == .riscv64,
"This package is unsupported on this architecture.")
/*
Retrieves the number of physical and logical CPU cores
Returns:
- physical: The number of physical cores
- logical: The number of logical cores
- ok: `true` when we could retrieve the CPU information, `false` otherwise
*/
cpu_core_count :: proc "contextless" () -> (physical: int, logical: int, ok: bool) {
return _cpu_core_count()
}
os_version: OS_Version
ram: RAM
gpus: []GPU
/*
Retrieves CPU features where available
// Only on MacOS, contains the actual MacOS version, while the `os_version` contains the kernel version.
macos_version: Version
The results are cached
Returns:
- features: An architecture-specific `bit_set`
- ok: `true` when we could retrieve the CPU features, `false` otherwise
*/
cpu_features :: proc "contextless" () -> (features: CPU_Features, ok: bool) {
return _cpu_features()
}
/*
Retrieves the CPU's name
The results are cached
Returns:
- name: A `string` containing the CPU model name, empty if the lookup failed
*/
cpu_name :: proc() -> (name: string) {
return _cpu_name()
}
/*
Retrieves RAM statistics
Unavailable stats will be returned as `0` bytes
Returns:
- total_ram: Total RAM reported by the operating system, in bytes
- free_ram: Free RAM reported by the operating system, in bytes
- total_swap: Total SWAP reported by the operating system, in bytes
- free_swap: Free SWAP reported by the operating system, in bytes
- ok: `true` when we could retrieve RAM statistics, `false` otherwise
*/
ram_stats :: proc "contextless" () -> (total_ram, free_ram, total_swap, free_swap: i64, ok: bool) {
return _ram_stats()
}
/*
Retrieves OS version information
*Allocates Using Provided Allocator*
You can use `destroy_os_version` to free the results
Inputs:
- allocator: A `runtime.Allocator` on which the version strings will be allocated
- loc: The caller location
Returns:
- res: An `OS_Version` struct
- ok: `true` when we could retrieve OS version information, `false` otherwise
*/
os_version :: proc(allocator: runtime.Allocator, loc := #caller_location) -> (res: OS_Version, ok: bool) {
return _os_version(allocator = allocator, loc = loc)
}
/*
Releases an `OS_Version`'s strings
*Frees Using Provided Allocator*
Inputs:
- version: An `OS_Version` struct
- allocator: A `runtime.Allocator` on which the version strings will be freed
*/
destroy_os_version :: proc(version: OS_Version, allocator: runtime.Allocator) {
delete(version.full, allocator)
// `version.release` is part of `version.full` and does not need to be freed separately.
}
OS_Version :: struct {
platform: OS_Version_Platform, // Windows, Linux, MacOS, iOS, etc.
full: string, // e.g. Windows 10 Professional (version: 22H2), build: 19045.6575
release: string, // e.g. 22H2
os: Version, // e.g. {major = 10, minor = 10, patch = 0}
kernel: Version, // e.g. {major = 10, minor = 19045, patch = 6575}
}
OS_Version_Platform :: enum {
Unknown,
@@ -26,54 +121,69 @@ Version :: struct {
major, minor, patch: int,
}
OS_Version :: struct {
platform: OS_Version_Platform,
/*
Iterates over GPU adapters
using _: Version,
build: [2]int,
version: string,
On Windows: Enumerates `Computer\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}`
Elsewhere: Unsupported at the moment, returns `{}, 0, false`
as_string: string,
}
Important: The `vendor` name, `model` name and `driver` version strings are backed by the `GPU_Iterator`.
Clone them if you want to these to persist.
RAM :: struct {
total_ram: int,
free_ram: int,
total_swap: int,
free_swap: int,
Inputs:
- it: A pointer to a `GPU_Iterator`
- minimum_vram: The number of bytes of VRAM an adapter has to have to be considered, default 256 MiB
(This excludes most screen mirroring / remote desktop drivers)
Returns:
gpu: A `GPU` struct which contains `vendor` name, `model` name, `driver` version and `vram` in bytes
index: Loop index, optional
ok: `true` if this was a success and we should continue, `false` otherwise
*/
iterate_gpus :: proc(it: ^GPU_Iterator, minimum_vram := i64(256 * 1024 * 1024)) -> (gpu: GPU, index: int, ok: bool) {
when ODIN_OS == .Windows {
return _iterate_gpus(it, minimum_vram)
} else {
// Not implemented on another OS, yet
return {}, 0, false
}
}
GPU :: struct {
vendor_name: string,
model_name: string,
total_ram: int,
vendor: string,
model: string,
driver: string,
vram: i64,
}
GPU_Iterator :: struct {
// Public iterator index
index: int,
// Internal buffer + index
_buffer: [512]u8,
_index: int,
}
@(private)
version_string_buf: [1024]u8
_parse_version :: proc (str: string) -> (res: Version) {
str := str
i: int
for part in strings.split_iterator(&str, ".") {
defer i += 1
dst: ^int
switch i {
case 0: dst = &res.major
case 1: dst = &res.minor
case 2: dst = &res.patch
case: return
}
@(private)
MAX_GPUS :: 16
@(private)
_gpus: [MAX_GPUS]GPU
@(private)
_gpu_string_buf: [MAX_GPUS * 256 * 2]u8 // Reserve up to 256 bytes for each GPU's vendor and model name
@(private)
_gpu_string_offset: int
@(private)
intern_gpu_string :: proc "contextless" (str: string) -> (res: string, ok: bool) {
if _gpu_string_offset + len(str) + 1 > size_of(_gpu_string_buf) {
return "", false
if num, num_ok := strconv.parse_int(part); !num_ok {
return
} else {
dst^ = num
}
}
n := copy(_gpu_string_buf[_gpu_string_offset:], str)
_gpu_string_buf[_gpu_string_offset + len(str)] = 0
res = string(_gpu_string_buf[_gpu_string_offset:][:len(str)])
_gpu_string_offset += n + 1
return res, true
return
}

View File

@@ -46,4 +46,5 @@ CTL_HW :: 6
HW_VERSION :: 16
HW_SERIALNO :: 17
HW_UUID :: 18
HW_PHYSMEM64 :: 19
HW_PHYSMEM64 :: 19
HW_USERMEM64 :: 20