diff --git a/src/ThreadPinning.jl b/src/ThreadPinning.jl index a0b8992f..859647c4 100644 --- a/src/ThreadPinning.jl +++ b/src/ThreadPinning.jl @@ -14,6 +14,8 @@ include("lscpu_examples.jl") include("libs/libc.jl") include("libs/libuv.jl") include("libs/libpthread.jl") +include("windows/windows.jl") +import .Windows include("omp.jl") include("blas.jl") include("querying.jl") @@ -70,45 +72,48 @@ end # initialization function __init__() forget_pin_attempts() - if AUTOUPDATE + if false + # if AUTOUPDATE update_sysinfo!(; fromscratch = true) end maybe_autopin() return nothing end -# precompile -import SnoopPrecompile -SnoopPrecompile.@precompile_all_calls begin - ThreadPinning.lscpu2sysinfo(LSCPU_STRING) - update_sysinfo!() - lscpu_string() - sysinfo() - pinthread(0) - pinthreads(0:(nthreads() - 1)) - pinthreads(collect(0:(nthreads() - 1))) - pinthreads(:compact; nthreads=1) - pinthreads(:spread; nthreads=1) - pinthreads(:random; nthreads=1) - pinthreads(:current; nthreads=1) - pinthreads(:compact; places = Cores(), nthreads=1) - pinthreads(:compact; places = CPUThreads(), nthreads=1) - pinthreads(:spread; places = NUMA(), nthreads=1) - pinthreads(:spread; places = Sockets(), nthreads=1) - getcpuid() - getcpuids() - nsockets() - nnuma() - cpuids_all() - cpuids_per_socket() - cpuids_per_numa() - ncputhreads() - ncputhreads_per_socket() - ncputhreads_per_numa() - ncores() - ncores_per_socket() - ncores_per_numa() -end +# # precompile +# import SnoopPrecompile +# SnoopPrecompile.@precompile_all_calls begin +# @static if Sys.islinux() +# ThreadPinning.lscpu2sysinfo(LSCPU_STRING) +# lscpu_string() +# end +# update_sysinfo!() +# sysinfo() +# pinthread(0) +# pinthreads(0:(nthreads() - 1)) +# pinthreads(collect(0:(nthreads() - 1))) +# pinthreads(:compact; nthreads=1) +# pinthreads(:spread; nthreads=1) +# pinthreads(:random; nthreads=1) +# pinthreads(:current; nthreads=1) +# pinthreads(:compact; places = Cores(), nthreads=1) +# pinthreads(:compact; places = CPUThreads(), nthreads=1) +# pinthreads(:spread; places = NUMA(), nthreads=1) +# pinthreads(:spread; places = Sockets(), nthreads=1) +# getcpuid() +# getcpuids() +# nsockets() +# nnuma() +# cpuids_all() +# cpuids_per_socket() +# cpuids_per_numa() +# ncputhreads() +# ncputhreads_per_socket() +# ncputhreads_per_numa() +# ncores() +# ncores_per_socket() +# ncores_per_numa() +# end # exports export threadinfo, diff --git a/src/querying.jl b/src/querying.jl index d60fafdf..ea895eee 100644 --- a/src/querying.jl +++ b/src/querying.jl @@ -1,10 +1,14 @@ """ Returns the ID of the CPU on which the calling thread is currently executing. - -See `sched_getcpu` for more information. """ -getcpuid() = Int(sched_getcpu()) +function getcpuid() + @static if Sys.iswindows() + return Int(Windows.get_current_processor_number()) + else + return Int(sched_getcpu()) + end +end """ Returns the ID of the CPU on which the given Julia thread diff --git a/src/sysinfo.jl b/src/sysinfo.jl index bc97c739..1aff2f8c 100644 --- a/src/sysinfo.jl +++ b/src/sysinfo.jl @@ -29,21 +29,25 @@ function update_sysinfo!(; fromscratch = false, lscpustr = nothing, verbose = fa SYSINFO[] = SysInfo() else local sysinfo - try - if !isnothing(lscpustr) - # explicit lscpu string given - sysinfo = lscpu2sysinfo(lscpustr; verbose) - else - if !fromscratch - # use precompiled lscpu string - sysinfo = lscpu2sysinfo(LSCPU_STRING; verbose) + @static if Sys.iswindows() + sysinfo = Windows.get_sysinfo() + else + try + if !isnothing(lscpustr) + # explicit lscpu string given + sysinfo = lscpu2sysinfo(lscpustr; verbose) else - # from scratch: query lscpu again - sysinfo = lscpu2sysinfo(lscpu_string(); verbose) + if !fromscratch + # use precompiled lscpu string + sysinfo = lscpu2sysinfo(LSCPU_STRING; verbose) + else + # from scratch: query lscpu again + sysinfo = lscpu2sysinfo(lscpu_string(); verbose) + end end + catch err + throw(ArgumentError("Couldn't parse the given lscpu string:\n\n $lscpustr \n\n")) end - catch err - throw(ArgumentError("Couldn't parse the given lscpu string:\n\n $lscpustr \n\n")) end SYSINFO[] = sysinfo end @@ -173,4 +177,8 @@ end # global "constant" const SYSINFO = Ref{SysInfo}(SysInfo()) -const LSCPU_STRING = lscpu_string() +@static if Sys.iswindows() + const LSCPU_STRING = "windows" +else + const LSCPU_STRING = lscpu_string() +end diff --git a/src/windows/error_codes.jl b/src/windows/error_codes.jl new file mode 100644 index 00000000..052dab13 --- /dev/null +++ b/src/windows/error_codes.jl @@ -0,0 +1,131 @@ +# SOURCE: https://learn.microsoft.com/en-gb/windows/win32/debug/system-error-codes--0-499-?redirectedfrom=MSDN + +### Parsing +# lines = readlines(IOBuffer(s)) +# lines = filter(!isempty, lines) +# +# error_codes = Dict{Int, String}() +# for (li, l) in pairs(lines) +# if startswith(l, "ERROR") || startswith(l, "WAIT") +# code = parse(Int, strip(split(lines[li+1], "(")[1])) +# error_codes[code] = l +# end +# end +# +# write("errors.txt", string(error_codes)) + +### OUTPUT +const ERROR_CODES_LOOKUP = Dict(56 => "ERROR_TOO_MANY_CMDS", 60 => "ERROR_BAD_REM_ADAP", + 220 => "ERROR_FILE_CHECKED_OUT", 308 => "ERROR_IMAGE_SUBSYSTEM_NOT_PRESENT", + 67 => "ERROR_BAD_NET_NAME", 215 => "ERROR_NESTING_NOT_ALLOWED", + 319 => "ERROR_UNDEFINED_SCOPE", 112 => "ERROR_DISK_FULL", + 333 => "ERROR_NOT_REDUNDANT_STORAGE", 86 => "ERROR_INVALID_PASSWORD", + 207 => "ERROR_RING2_STACK_IN_USE", 183 => "ERROR_ALREADY_EXISTS", + 224 => "ERROR_FORMS_AUTH_REQUIRED", 12 => "ERROR_INVALID_ACCESS", 23 => "ERROR_CRC", + 111 => "ERROR_BUFFER_OVERFLOW", 68 => "ERROR_TOO_MANY_NAMES", + 82 => "ERROR_CANNOT_MAKE", 130 => "ERROR_DIRECT_ACCESS_HANDLE", + 125 => "ERROR_NO_VOLUME_LABEL", 71 => "ERROR_REQ_NOT_ACCEP", + 66 => "ERROR_BAD_DEV_TYPE", 103 => "ERROR_TOO_MANY_SEM_REQUESTS", + 59 => "ERROR_UNEXP_NET_ERR", 208 => "ERROR_META_EXPANSION_TOO_LONG", + 336 => "ERROR_DIRECTORY_NOT_SUPPORTED", 26 => "ERROR_NOT_DOS_DISK", + 127 => "ERROR_PROC_NOT_FOUND", 100 => "ERROR_TOO_MANY_SEMAPHORES", + 230 => "ERROR_BAD_PIPE", 195 => "ERROR_INVALID_MINALLOCSIZE", + 141 => "ERROR_SUBST_TO_JOIN", 278 => "ERROR_INVALID_EA_HANDLE", + 135 => "ERROR_IS_SUBSTED", 138 => "ERROR_JOIN_TO_JOIN", 222 => "ERROR_BAD_FILE_TYPE", + 107 => "ERROR_DISK_CHANGE", 276 => "ERROR_EA_FILE_CORRUPT", 57 => "ERROR_ADAP_HDW_ERR", + 152 => "ERROR_TOO_MANY_MUXWAITERS", 170 => "ERROR_BUSY", + 129 => "ERROR_CHILD_NOT_COMPLETE", 133 => "ERROR_IS_JOIN_TARGET", + 72 => "ERROR_REDIR_PAUSED", 258 => "WAIT_TIMEOUT", 1 => "ERROR_INVALID_FUNCTION", + 137 => "ERROR_NOT_SUBSTED", 22 => "ERROR_BAD_COMMAND", 154 => "ERROR_LABEL_TOO_LONG", + 313 => "ERROR_NOT_ALLOWED_ON_SYSTEM_FILE", 206 => "ERROR_FILENAME_EXCED_RANGE", + 288 => "ERROR_NOT_OWNER", 299 => "ERROR_PARTIAL_COPY", 33 => "ERROR_LOCK_VIOLATION", + 400 => "ERROR_THREAD_MODE_ALREADY_BACKGROUND", 113 => "ERROR_NO_MORE_SEARCH_HANDLES", + 231 => "ERROR_PIPE_BUSY", 254 => "ERROR_INVALID_EA_NAME", + 309 => "ERROR_NOTIFICATION_GUID_ALREADY_DEFINED", 142 => "ERROR_BUSY_DRIVE", + 5 => "ERROR_ACCESS_DENIED", 55 => "ERROR_DEV_NOT_EXIST", + 114 => "ERROR_INVALID_TARGET_HANDLE", 136 => "ERROR_NOT_JOINED", + 117 => "ERROR_INVALID_CATEGORY", 145 => "ERROR_DIR_NOT_EMPTY", + 282 => "ERROR_EAS_NOT_SUPPORTED", 275 => "ERROR_EAS_DIDNT_FIT", + 337 => "ERROR_NOT_READ_FROM_COPY", 351 => "ERROR_FAIL_SHUTDOWN", + 158 => "ERROR_NOT_LOCKED", 218 => "ERROR_EXE_CANNOT_MODIFY_STRONG_SIGNED_BINARY", + 28 => "ERROR_OUT_OF_PAPER", 148 => "ERROR_PATH_BUSY", + 36 => "ERROR_SHARING_BUFFER_EXCEEDED", 118 => "ERROR_INVALID_VERIFY_SWITCH", + 162 => "ERROR_SIGNAL_PENDING", 84 => "ERROR_OUT_OF_STRUCTURES", + 7 => "ERROR_ARENA_TRASHED", 25 => "ERROR_SEEK", 203 => "ERROR_ENVVAR_NOT_FOUND", + 353 => "ERROR_MAX_SESSIONS_REACHED", 232 => "ERROR_NO_DATA", + 304 => "ERROR_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING", + 18 => "ERROR_NO_MORE_FILES", 240 => "ERROR_VC_DISCONNECTED", + 147 => "ERROR_IS_JOIN_PATH", 157 => "ERROR_DISCARDED", 16 => "ERROR_CURRENT_DIRECTORY", + 19 => "ERROR_WRITE_PROTECT", 266 => "ERROR_CANNOT_COPY", 31 => "ERROR_GEN_FAILURE", + 217 => "ERROR_EXE_CANNOT_MODIFY_SIGNED_BINARY", 146 => "ERROR_IS_SUBST_PATH", + 61 => "ERROR_PRINTQ_FULL", 29 => "ERROR_WRITE_FAULT", 212 => "ERROR_LOCKED", + 303 => "ERROR_DELETE_PENDING", 159 => "ERROR_BAD_THREADID_ADDR", + 193 => "ERROR_BAD_EXE_FORMAT", 226 => "ERROR_VIRUS_DELETED", + 101 => "ERROR_EXCL_SEM_ALREADY_OWNED", 105 => "ERROR_SEM_OWNER_DIED", + 223 => "ERROR_FILE_TOO_LARGE", 17 => "ERROR_NOT_SAME_DEVICE", + 335 => "ERROR_COMPRESSED_FILE_NOT_SUPPORTED", 198 => "ERROR_INVALID_SEGDPL", + 89 => "ERROR_NO_PROC_SLOTS", 214 => "ERROR_TOO_MANY_MODULES", + 331 => "ERROR_TOO_MANY_DESCRIPTORS", 80 => "ERROR_FILE_EXISTS", + 51 => "ERROR_REM_NOT_LIST", 143 => "ERROR_SAME_DRIVE", 15 => "ERROR_INVALID_DRIVE", + 330 => "ERROR_BAD_DEVICE_PATH", 134 => "ERROR_IS_JOINED", + 402 => "ERROR_PROCESS_MODE_ALREADY_BACKGROUND", 110 => "ERROR_OPEN_FAILED", + 30 => "ERROR_READ_FAULT", 6 => "ERROR_INVALID_HANDLE", 234 => "ERROR_MORE_DATA", + 182 => "ERROR_INVALID_ORDINAL", 164 => "ERROR_MAX_THRDS_REACHED", + 153 => "ERROR_INVALID_LIST_FORMAT", 186 => "ERROR_INVALID_FLAG_NUMBER", + 64 => "ERROR_NETNAME_DELETED", 267 => "ERROR_DIRECTORY", 139 => "ERROR_SUBST_TO_SUBST", + 4 => "ERROR_TOO_MANY_OPEN_FILES", 13 => "ERROR_INVALID_DATA", + 104 => "ERROR_INVALID_AT_INTERRUPT_TIME", 316 => "ERROR_DEVICE_FEATURE_NOT_SUPPORTED", + 328 => "ERROR_INVALID_FIELD_IN_PARAMETER_LIST", 52 => "ERROR_DUP_NAME", + 300 => "ERROR_OPLOCK_NOT_GRANTED", 171 => "ERROR_DEVICE_SUPPORT_IN_PROGRESS", + 11 => "ERROR_BAD_FORMAT", 69 => "ERROR_TOO_MANY_SESS", + 302 => "ERROR_DISK_TOO_FRAGMENTED", 487 => "ERROR_INVALID_ADDRESS", + 85 => "ERROR_ALREADY_ASSIGNED", 119 => "ERROR_BAD_DRIVER_LEVEL", + 39 => "ERROR_HANDLE_DISK_FULL", 216 => "ERROR_EXE_MACHINE_TYPE_MISMATCH", + 126 => "ERROR_MOD_NOT_FOUND", 108 => "ERROR_DRIVE_LOCKED", + 156 => "ERROR_SIGNAL_REFUSED", 2 => "ERROR_FILE_NOT_FOUND", + 10 => "ERROR_BAD_ENVIRONMENT", 27 => "ERROR_SECTOR_NOT_FOUND", + 124 => "ERROR_INVALID_LEVEL", 307 => "ERROR_INVALID_LOCK_RANGE", + 144 => "ERROR_DIR_NOT_ROOT", 352 => "ERROR_FAIL_RESTART", + 200 => "ERROR_RING2SEG_MUST_BE_MOVABLE", 312 => "ERROR_NO_RANGES_PROCESSED", + 20 => "ERROR_BAD_UNIT", 187 => "ERROR_SEM_NOT_FOUND", 0 => "ERROR_SUCCESS", + 329 => "ERROR_OPERATION_IN_PROGRESS", 9 => "ERROR_INVALID_BLOCK", + 189 => "ERROR_INVALID_STACKSEG", 109 => "ERROR_BROKEN_PIPE", + 161 => "ERROR_BAD_PATHNAME", 401 => "ERROR_THREAD_MODE_NOT_BACKGROUND", + 88 => "ERROR_NET_WRITE_FAULT", 209 => "ERROR_INVALID_SIGNAL_NUMBER", + 120 => "ERROR_CALL_NOT_IMPLEMENTED", 323 => "ERROR_DATA_CHECKSUM_ERROR", + 24 => "ERROR_BAD_LENGTH", 8 => "ERROR_NOT_ENOUGH_MEMORY", 83 => "ERROR_FAIL_I24", + 190 => "ERROR_INVALID_MODULETYPE", 201 => "ERROR_RELOC_CHAIN_XEEDS_SEGLIM", + 121 => "ERROR_SEM_TIMEOUT", 311 => "ERROR_DUPLICATE_PRIVILEGES", + 14 => "ERROR_OUTOFMEMORY", 314 => "ERROR_DISK_RESOURCES_EXHAUSTED", + 334 => "ERROR_RESIDENT_FILE_NOT_SUPPORTED", 403 => "ERROR_PROCESS_MODE_NOT_BACKGROUND", + 174 => "ERROR_ATOMIC_LOCKS_NOT_SUPPORTED", 322 => "ERROR_DEVICE_NO_RESOURCES", + 315 => "ERROR_INVALID_TOKEN", 123 => "ERROR_INVALID_NAME", + 305 => "ERROR_SHORT_NAMES_NOT_ENABLED_ON_VOLUME", 32 => "ERROR_SHARING_VIOLATION", + 197 => "ERROR_IOPL_NOT_ENABLED", 233 => "ERROR_PIPE_NOT_CONNECTED", + 196 => "ERROR_DYNLINK_FROM_INVALID_RING", 320 => "ERROR_INVALID_CAP", + 324 => "ERROR_INTERMIXED_KERNEL_EA_OPERATION", 210 => "ERROR_THREAD_1_INACTIVE", + 151 => "ERROR_INVALID_EVENT_COUNT", 54 => "ERROR_NETWORK_BUSY", + 63 => "ERROR_PRINT_CANCELLED", 191 => "ERROR_INVALID_EXE_SIGNATURE", + 62 => "ERROR_NO_SPOOL_SPACE", 205 => "ERROR_NO_SIGNAL_SENT", + 327 => "ERROR_OFFSET_ALIGNMENT_VIOLATION", 150 => "ERROR_SYSTEM_TRACE", + 122 => "ERROR_INSUFFICIENT_BUFFER", 58 => "ERROR_BAD_NET_RESP", + 199 => "ERROR_AUTODATASEG_EXCEEDS_64k", 173 => "ERROR_CANCEL_VIOLATION", + 188 => "ERROR_INVALID_STARTING_CODESEG", 277 => "ERROR_EA_TABLE_FULL", + 310 => "ERROR_INVALID_EXCEPTION_HANDLER", 321 => "ERROR_DEVICE_UNREACHABLE", + 34 => "ERROR_WRONG_DISK", 50 => "ERROR_NOT_SUPPORTED", 318 => "ERROR_SCOPE_NOT_FOUND", + 194 => "ERROR_ITERATED_DATA_EXCEEDS_64k", 167 => "ERROR_LOCK_FAILED", + 87 => "ERROR_INVALID_PARAMETER", 301 => "ERROR_INVALID_OPLOCK_PROTOCOL", + 132 => "ERROR_SEEK_ON_DEVICE", 140 => "ERROR_JOIN_TO_SUBST", + 202 => "ERROR_INFLOOP_IN_RELOC_CHAIN", 317 => "ERROR_MR_MID_NOT_FOUND", + 180 => "ERROR_INVALID_SEGMENT_NUMBER", 255 => "ERROR_EA_LIST_INCONSISTENT", + 160 => "ERROR_BAD_ARGUMENTS", 106 => "ERROR_SEM_USER_LIMIT", + 225 => "ERROR_VIRUS_INFECTED", 102 => "ERROR_SEM_IS_SET", + 128 => "ERROR_WAIT_NO_CHILDREN", 259 => "ERROR_NO_MORE_ITEMS", + 70 => "ERROR_SHARING_PAUSED", 350 => "ERROR_FAIL_NOACTION_REBOOT", + 332 => "ERROR_SCRUB_DATA_DISABLED", 21 => "ERROR_NOT_READY", 229 => "ERROR_PIPE_LOCAL", + 38 => "ERROR_HANDLE_EOF", 131 => "ERROR_NEGATIVE_SEEK", + 192 => "ERROR_EXE_MARKED_INVALID", 326 => "ERROR_FILE_LEVEL_TRIM_NOT_SUPPORTED", + 221 => "ERROR_CHECKOUT_REQUIRED", 53 => "ERROR_BAD_NETPATH", + 3 => "ERROR_PATH_NOT_FOUND", 306 => "ERROR_SECURITY_STREAM_IS_INCONSISTENT", + 149 => "ERROR_IS_SUBST_TARGET", 155 => "ERROR_TOO_MANY_TCBS", + 65 => "ERROR_NETWORK_ACCESS_DENIED", 298 => "ERROR_TOO_MANY_POSTS") diff --git a/src/windows/types.jl b/src/windows/types.jl new file mode 100644 index 00000000..7a577e1b --- /dev/null +++ b/src/windows/types.jl @@ -0,0 +1,95 @@ +# windows datatypes (https://learn.microsoft.com/de-de/windows/win32/winprog/windows-data-types) +const DWORD = Culong +const DWORD_PTR = Ptr{Culong} +const PDWORD_PTR = Ptr{DWORD_PTR} +const WORD = Cushort +const LPVOID = Ptr{Cvoid} +const BYTE = Cuchar +const ULONG_PTR = Ptr{Culong} +const THREAD_HANDLE = UInt32 +# const THREAD_HANDLE = Ptr{UInt32} + +const PROCESS_SET_INFORMATION = 0x0200 +const PROCESS_QUERY_INFORMATION = 0x0400 +const THREAD_SET_INFORMATION = 0x0020 +const THREAD_QUERY_INFORMATION = 0x0040 + +struct SYSTEM_INFO_0_0 + wProcessorArchitecture::WORD + wReserved::WORD +end + +struct SYSTEM_INFO_0 # necessary?! + dwOemId::DWORD + DUMMYSTRUCTNAME::SYSTEM_INFO_0_0 +end + +struct SYSTEM_INFO + # Definition: https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info + DUMMYUNIONNAME::Union{DWORD, SYSTEM_INFO_0_0}; + dwPageSize::DWORD + lpMinimumApplicationAddress::LPVOID + lpMaximumApplicationAddress::LPVOID + dwActiveProcessorMask::DWORD_PTR + dwNumberOfProcessors::DWORD + dwProcessorType::DWORD + dwAllocationGranularity::DWORD + wProcessorLevel::WORD + wProcessorRevision::WORD +end + +struct ProcessorCore + Flags::BYTE +end + +struct NumaNode + NodeNumber::DWORD +end + +# const LOGICAL_PROCESSOR_RELATIONSHIP = Cint +@enum LOGICAL_PROCESSOR_RELATIONSHIP begin + RelationProcessorCore + RelationNumaNode + RelationCache + RelationProcessorPackage + RelationGroup + RelationProcessorDie + RelationNumaNodeEx + RelationProcessorModule + RelationAll = 0xffff +end + +@enum PROCESSOR_CACHE_TYPE begin + CacheUnified + CacheInstruction + CacheData + CacheTrace +end +# const PROCESSOR_CACHE_TYPE = Cint + +struct CACHE_DESCRIPTOR + Level::BYTE + Associativity::BYTE + LineSize::WORD + Size::DWORD + Type::PROCESSOR_CACHE_TYPE +end + +struct SYSTEM_LOGICAL_PROCESSOR_INFORMATION + ProcessorMask::ULONG_PTR + Relationship::LOGICAL_PROCESSOR_RELATIONSHIP + # DUMMYUNIONNAME::Union{ProcessorCore, NumaNode, CACHE_DESCRIPTOR, Culonglong} + DUMMYUNIONNAME::Union{Ptr{ProcessorCore}, Ptr{NumaNode}, Ptr{CACHE_DESCRIPTOR}, Ptr{Culonglong}} + # DUMMYUNIONNAME::Union{ProcessorCore, NumaNode, CACHE_DESCRIPTOR} + # DUMMYUNIONNAME::Union{ProcessorCore, NumaNode, CACHE_DESCRIPTOR, NTuple{2, Culonglong}} + # union { + # struct { + # BYTE Flags; + # } ProcessorCore; + # struct { + # DWORD NodeNumber; + # } NumaNode; + # CACHE_DESCRIPTOR Cache; + # ULONGLONG Reserved[2]; + # } ; +end diff --git a/src/windows/windows.jl b/src/windows/windows.jl new file mode 100644 index 00000000..d29d0117 --- /dev/null +++ b/src/windows/windows.jl @@ -0,0 +1,213 @@ +module Windows + +import ..ThreadPinning: SysInfo + +include("types.jl") +include("error_codes.jl") + +const kernel32 = "kernel32" + +get_current_processor_number() = @ccall kernel32.GetCurrentProcessorNumber()::DWORD + +get_current_processid() = @ccall kernel32.GetCurrentProcessId()::DWORD +function get_processid(process_handle) + Int(@ccall kernel32.GetProcessId(process_handle::THREAD_HANDLE)::DWORD) +end + +# get_current_process() = @ccall kernel32.GetCurrentProcess()::THREAD_HANDLE # doesn't work properly?! +function get_process_handle(procid = get_current_processid()) + dwDesiredAccess = PROCESS_SET_INFORMATION | PROCESS_QUERY_INFORMATION + # dwDesiredAccess = 0xffff + @ccall kernel32.OpenProcess(dwDesiredAccess::DWORD, true::Bool, + procid::DWORD)::THREAD_HANDLE +end + +function get_process_affinity_mask(process_handle = get_process_handle()) + # BOOL GetProcessAffinityMask( + # [in] HANDLE hProcess, + # [out] PDWORD_PTR lpProcessAffinityMask, + # [out] PDWORD_PTR lpSystemAffinityMask + # ); + lpProcessAffinityMask = Ref{DWORD_PTR}() + lpSystemAffinityMask = Ref{DWORD_PTR}() + ret = @ccall kernel32.GetProcessAffinityMask(process_handle::THREAD_HANDLE, + lpProcessAffinityMask::PDWORD_PTR, + lpSystemAffinityMask::PDWORD_PTR)::Bool + if ret == true + return DWORD(UInt(lpProcessAffinityMask[])), DWORD(UInt(lpSystemAffinityMask[])) + else + error("Error: $(get_last_error())") + end +end + +# get_current_thread() = @ccall kernel32.GetCurrentThread()::THREAD_HANDLE # doesn't work properly?! +function get_thread_handle(threadid = get_current_threadid()) + dwDesiredAccess = THREAD_SET_INFORMATION | THREAD_QUERY_INFORMATION + # dwDesiredAccess = 0xffff + @ccall kernel32.OpenThread(dwDesiredAccess::DWORD, true::Bool, + threadid::DWORD)::THREAD_HANDLE +end + +function set_thread_affinity_mask(thread_handle, mask) + @ccall kernel32.SetThreadAffinityMask(thread_handle::THREAD_HANDLE, + mask::DWORD_PTR)::DWORD_PTR +end +function set_thread_affinity(procid::Integer) + mask = DWORD(1 << procid) + # @show bitstring(mask) + ret = set_thread_affinity_mask(get_thread_handle(), Ref(mask)) + if ret == 0 + error("Error: $(get_last_error())") + else + # success: return thread's previous affinity mask. + return DWORD(UInt(ret)) + end +end + +function get_thread_affinity_mask(thread_handle::THREAD_HANDLE = get_thread_handle()) + # Based on https://stackoverflow.com/a/6601917/2365675 + mask = DWORD(1) + old = DWORD(0) + + # try every CPU one by one until one works or none are left + while mask == true + old = DWORD(UInt(set_thread_affinity_mask(thread_handle, Ref(mask)))) + if old != false + # this one worked + set_thread_affinity_mask(thread_handle, Ref(old)) # restore original + return old + else + if get_last_error() != "ERROR_INVALID_PARAMETER" + error("unknown error") + end + end + mask <<= 1 + end + error("failed") +end + +function get_threadid(thread_handle) + Int(@ccall kernel32.GetThreadId(thread_handle::THREAD_HANDLE)::DWORD) +end +get_current_threadid() = Int(@ccall kernel32.GetCurrentThreadId()::DWORD) +function get_threadids() + nt = Threads.nthreads() + ids = zeros(Int, nt) + Threads.@threads :static for i in 1:nt + ids[i] = get_current_threadid() + end + return ids +end + +# function get_system_info() +# sysinfo = Ref{SYSTEM_INFO}() +# @ccall kernel32.GetSystemInfo(sysinfo::Ptr{SYSTEM_INFO})::Cvoid +# return sysinfo[] +# end + +# function get_native_system_info() +# sysinfo = Ref{SYSTEM_INFO}() +# @ccall kernel32.GetNativeSystemInfo(sysinfo::Ptr{SYSTEM_INFO})::Cvoid +# @show get_last_error() +# return sysinfo[] +# end + +function get_logical_processor_information() + # based on https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformation#examples + buffer = Vector{SYSTEM_LOGICAL_PROCESSOR_INFORMATION}(undef, 0) + returned_length = Ref(zero(DWORD)) + done = false + while !done + rc = @ccall kernel32.GetLogicalProcessorInformation(buffer::Ptr{ + SYSTEM_LOGICAL_PROCESSOR_INFORMATION + }, + returned_length::DWORD_PTR)::DWORD + if rc == false + if get_last_error() == "ERROR_INSUFFICIENT_BUFFER" + nelements = returned_length[] รท sizeof(eltype(buffer)) + buffer = Vector{SYSTEM_LOGICAL_PROCESSOR_INFORMATION}(undef, nelements) + else + error("Error: ", get_last_error()) + end + else + done = true # leave while loop + end + end + return buffer +end + +"Helper function to count set bits in the processor mask." +_countsetbits(proc_mask::ULONG_PTR) = count(isequal('1'), bitstring(proc_mask)) + +function get_sysinfo() + buffer = get_logical_processor_information() + current_cpuid = 0 + current_socket = 0 + current_numa = 0 + nnuma = 0 + ncores = 0 + ncputhreads = 0 + nsockets = 0 + ishyperthread = Bool[] + cpuids_sockets = Vector{Int}[Int[] for _ in 1:1] # only single socket supported for now + cpuids_numa = Vector{Int}[Int[] for _ in 1:1] # only single numa domain supported for now + for elem in buffer + if elem.Relationship == RelationNumaNode + # Non-NUMA systems report a single record of this type. + nnuma += 1 + elseif elem.Relationship == RelationProcessorCore + ncores += 1 + # A hyperthreaded core supplies more than one logical processor. + logical_procs_in_this_core = _countsetbits(elem.ProcessorMask) + for i in 1:logical_procs_in_this_core + if i == 1 + push!(ishyperthread, false) + else + push!(ishyperthread, true) + end + push!(cpuids_sockets[current_socket + 1], current_cpuid) + push!(cpuids_numa[current_numa + 1], current_cpuid) + ncputhreads += 1 + current_cpuid += 1 + end + # ncputhreads += logical_procs_in_this_core + + elseif elem.Relationship == RelationCache + # Cache data is in elem.DUMMYUNIONNAME, one CACHE_DESCRIPTOR structure for each cache. + # TODO. + # Cache = &ptr->Cache; + # if (Cache->Level == 1) + # { + # processorL1CacheCount++; + # } + # else if (Cache->Level == 2) + # { + # processorL2CacheCount++; + # } + # else if (Cache->Level == 3) + # { + # processorL3CacheCount++; + # } + # break; + nothing + elseif elem.Relationship == RelationProcessorPackage + # Logical processors share a physical package. + nsockets += 1 + else + error("Unsupported LOGICAL_PROCESSOR_RELATIONSHIP value: $(elem.Relationship)") + end + end + @assert ncputhreads == Sys.CPU_THREADS + if nsockets > 1 || nnuma > 1 + error("On windows, ThreadPinning.jl does currently only support single socket and single NUMA domain system. Your system seems to have $nsockets sockets and $nnuma NUMA domains.") + end + return SysInfo(nsockets, nnuma, ncputhreads > ncores, collect(0:(ncputhreads - 1)), + cpuids_sockets, cpuids_numa, ishyperthread) +end + +function get_last_error() + err_code = @ccall kernel32.GetLastError()::DWORD + return get(ERROR_CODES_LOOKUP, Int(err_code), "Unknown error code: $err_code") +end + +end #module