#+build riscv64
#+build linux
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" () {
	HWCAP_Bits :: enum u64 {
		I = 'I' - 'A',
		M = 'M' - 'A',
		A = 'A' - 'A',
		F = 'F' - 'A',
		D = 'D' - 'A',
		C = 'C' - 'A',
		V = 'V' - 'A',
	}
	HWCAP :: bit_set[HWCAP_Bits; u64]

	// Read HWCAP for base extensions, we can get this info through hwprobe too but that is Linux 6.4+ only.
	{
		fd, err := linux.open("/proc/self/auxv", {})
		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 }

		ulong     :: u64
		AT_HWCAP  :: 16

		auxv := buf[:n]
		for len(auxv) >= size_of(ulong)*2 {
			key := intrinsics.unaligned_load((^ulong)(&auxv[0]))
			val := intrinsics.unaligned_load((^ulong)(&auxv[size_of(ulong)]))
			auxv = auxv[2*size_of(ulong):]

			if key != AT_HWCAP {
				continue
			}

			cap := transmute(HWCAP)(val)
			if .I in cap {
				_features += { .I }
			}
			if .M in cap {
				_features += { .M }
			}
			if .A in cap {
				_features += { .A }
			}
			if .F in cap {
				_features += { .F }
			}
			if .D in cap {
				_features += { .D }
			}
			if .C in cap {
				_features += { .C }
			}
			if .V in cap {
				_features += { .V }
			}
			break
		}
	}

	// hwprobe for other features.
	{
		pairs := []linux.RISCV_HWProbe{
			{ key = .IMA_EXT_0 },
			{ key = .CPUPERF_0 },
			{ key = .MISALIGNED_SCALAR_PERF },
		}
		err := linux.riscv_hwprobe(raw_data(pairs), len(pairs), 0, nil, {})
		if err != nil {
			assert_contextless(err == .ENOSYS, "unexpected error from riscv_hwprobe()")
			return
		}

		assert_contextless(pairs[0].key == .IMA_EXT_0)
		exts := pairs[0].value.ima_ext_0
		exts -= { .FD, .C, .V }
		_features += transmute(CPU_Features)exts

		if pairs[2].key == .MISALIGNED_SCALAR_PERF {
			if pairs[2].value.misaligned_scalar_perf == .FAST {
				_features += { .Misaligned_Supported, .Misaligned_Fast }
			} else if pairs[2].value.misaligned_scalar_perf != .UNSUPPORTED {
				_features += { .Misaligned_Supported }
			}
		} else {
			assert_contextless(pairs[1].key == .CPUPERF_0)
			if .FAST in pairs[1].value.cpu_perf_0 {
				_features += { .Misaligned_Supported, .Misaligned_Fast }
			} else if .UNSUPPORTED not_in pairs[1].value.cpu_perf_0 {
				_features += { .Misaligned_Supported }
			}
		}
	}
}

@(private)
_cpu_features :: proc "contextless" () -> (features: CPU_Features) {
	return _features
}

@(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
}