working on per-PoolThread context loading and global context loading
This commit is contained in:
@@ -26,6 +26,8 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
using StgSharp.Collections;
|
||||
using StgSharp.HighPerformance.Memory;
|
||||
using StgSharp.Internal.Intrinsic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -41,45 +43,31 @@ namespace StgSharp.Mathematics.Numeric
|
||||
|
||||
private static CapacityFixedStack<MatrixParallelThread> _threads;
|
||||
|
||||
private static readonly
|
||||
#if NET10_0_OR_GREATER
|
||||
Lock
|
||||
#else
|
||||
object
|
||||
#endif
|
||||
_lock = new();
|
||||
private static SlabAllocator<IntrinsicContext> contextAllocator;
|
||||
|
||||
// private static ThreadPriority _AfterWork = ThreadPriority.Lowest;
|
||||
|
||||
public static bool SupportParallel { get; } = Environment.ProcessorCount > 6;
|
||||
public static bool IsParallelSupported { get; } = Environment.ProcessorCount > 6;
|
||||
|
||||
public static bool IsParallelMeaningful => Environment.ProcessorCount > 6 && IsInitialized;
|
||||
|
||||
private static bool IsInitialized { get; set; } = false;
|
||||
|
||||
private static Queue<IntPtr> LeaderTask { get; set; }
|
||||
|
||||
public static void EndParallel()
|
||||
{
|
||||
Monitor.Exit(_lock);
|
||||
}
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
if (IsInitialized) {
|
||||
return;
|
||||
}
|
||||
MatrixParallelFactory.Init();
|
||||
if (!SupportParallel)
|
||||
if (!IsParallelSupported)
|
||||
{
|
||||
IsInitialized = true;
|
||||
return;
|
||||
}
|
||||
MatrixParallelFactory.Init();
|
||||
contextAllocator = SlabAllocator<IntrinsicContext>.Create(4, SlabBufferLayout.Chunked, false);
|
||||
int count = Environment.ProcessorCount - 4;
|
||||
_threads = new(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
MatrixParallelThread thread = new MatrixParallelThread();
|
||||
MatrixParallelThread thread = new MatrixParallelThread(i);
|
||||
_threads.Push(thread);
|
||||
}
|
||||
_awaitingLeaderHandle = new AwaitingQueue();
|
||||
|
||||
@@ -62,6 +62,7 @@ namespace StgSharp.Mathematics.Numeric
|
||||
public enum MatrixIndexStyle : short
|
||||
{
|
||||
|
||||
SPECIAL = 0,
|
||||
BUFFER_INDEX = 1,
|
||||
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace StgSharp.Mathematics.Numeric
|
||||
{
|
||||
internal static class MatrixComputeModel
|
||||
public partial class MatrixParallelThread
|
||||
{
|
||||
|
||||
internal static unsafe void BufferComputeBinary(
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
using StgSharp.Internal;
|
||||
using StgSharp.Internal.Intrinsic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
@@ -46,34 +47,30 @@ namespace StgSharp.Mathematics.Numeric
|
||||
private readonly ManualResetEventSlim _resetEvent;
|
||||
private readonly Thread _managedThread;
|
||||
|
||||
public MatrixParallelThread()
|
||||
internal MatrixParallelThread(
|
||||
int poolId
|
||||
)
|
||||
{
|
||||
_resetEvent = new ManualResetEventSlim(false);
|
||||
_managedThread = new Thread(MatrixParallelWorker);
|
||||
_managedThread = new Thread(MatrixParallelWorkLoad);
|
||||
_managedThread.Start();
|
||||
PoolId = poolId;
|
||||
}
|
||||
|
||||
// ~MatrixComputeThread()
|
||||
// {
|
||||
// Dispose(disposing: false);
|
||||
// }
|
||||
public int PoolId { get; init; }
|
||||
|
||||
public ThreadPriority AfterWork { get; set; }
|
||||
|
||||
internal ManualResetEventSlim? LeaderEvent { get; set; }
|
||||
|
||||
[DllImport(
|
||||
dllName:Native.LibName,
|
||||
EntryPoint = "sn_set_thread_affinity",
|
||||
CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void SetThreadAffinity(
|
||||
ulong affinityMask
|
||||
);
|
||||
|
||||
#region wrap belong
|
||||
internal IntrinsicContext* Intrinsic { get; set; }
|
||||
|
||||
internal MatrixParallelWrap BindedWrap { get; set; }
|
||||
|
||||
internal RoleType Role { get; set; }
|
||||
|
||||
internal SIMDID Simd { get; set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing:true);
|
||||
@@ -146,6 +143,14 @@ namespace StgSharp.Mathematics.Numeric
|
||||
current.Priority = BindedWrap.AfterWork;
|
||||
}
|
||||
|
||||
[DllImport(
|
||||
dllName:Native.LibName,
|
||||
EntryPoint = "sn_set_thread_affinity",
|
||||
CallingConvention = CallingConvention.Cdecl)]
|
||||
internal static extern void SetThreadAffinity(
|
||||
int coreId
|
||||
);
|
||||
|
||||
protected virtual void Dispose(
|
||||
bool disposing
|
||||
)
|
||||
@@ -162,17 +167,19 @@ namespace StgSharp.Mathematics.Numeric
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void MatrixParallelWorker()
|
||||
private unsafe void MatrixParallelWorkLoad()
|
||||
{
|
||||
// Bind OS-thread affinity once when the worker starts running
|
||||
SetThreadAffinity(PoolId);
|
||||
Thread.Sleep(0);
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Thread self = Thread.CurrentThread;
|
||||
AfterWork = BindedWrap.AfterWork;
|
||||
_managedThread.Priority = ThreadPriority.Highest;
|
||||
_resetEvent.Wait();
|
||||
_resetEvent.Reset();
|
||||
AfterWork = BindedWrap.AfterWork;
|
||||
_managedThread.Priority = ThreadPriority.Highest;
|
||||
switch (Role)
|
||||
{
|
||||
case RoleType.Worker://worker
|
||||
@@ -183,7 +190,9 @@ namespace StgSharp.Mathematics.Numeric
|
||||
|
||||
// Console.WriteLine($"worker {Environment.CurrentManagedThreadId} end");
|
||||
break;
|
||||
case RoleType.Leader://leader, error because leader should be at outer thread but not here
|
||||
case RoleType.SingleExternal:
|
||||
|
||||
// TODO 将外部线程绑定到当前线程上,并执行任务包
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -192,16 +201,14 @@ namespace StgSharp.Mathematics.Numeric
|
||||
}
|
||||
}
|
||||
|
||||
public RoleType Role { get; internal set; }
|
||||
|
||||
public enum RoleType : int
|
||||
internal enum RoleType : int
|
||||
{
|
||||
|
||||
Worker = 0,
|
||||
Leader = 2,
|
||||
Leader = 1,
|
||||
SingleExternal = 2,
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
<Configurations>Debug;Release;PerformanceDebug</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Numeric\MatrixParallelHandle.cs.old" />
|
||||
<None Remove="Numeric\MatrixParallelWrap.cs.old" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StgSharp.Common\StgSharp.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -168,7 +168,8 @@
|
||||
<FileType>CCode</FileType>
|
||||
</ClCompile>
|
||||
<ClCompile Include="sn-src\load_glfw.c" />
|
||||
<ClCompile Include="sn-src\cpuid.x86.c" />
|
||||
<ClCompile Include="sn-src\simdid.x86.c" />
|
||||
<ClCompile Include="sn-src\simdid_compare.x86.c" />
|
||||
<ClCompile Include="sn-src\sn_native.c" />
|
||||
<ClCompile Include="sn-src\str_string.c" />
|
||||
<ClCompile Include="sn-src\t.affinity.c" />
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<ClCompile Include="sn-src\m.solve.x86.c">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="sn-src\cpuid.x86.c">
|
||||
<ClCompile Include="sn-src\simdid.x86.c">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="sn-src\m.fma_tile.x86.c">
|
||||
@@ -69,6 +69,9 @@
|
||||
<ClCompile Include="sn-src\t.affinity.c">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="sn-src\simdid_compare.x86.c">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="sn-head\gl.h">
|
||||
|
||||
@@ -66,10 +66,12 @@ SN_API void SN_DECL unloadImageData(Image *out);
|
||||
SN_API char *SN_DECL readLog(void);
|
||||
SN_API void SN_DECL load_glfw_functions(GLFWFunctionTable *table);
|
||||
SN_API SIMDID SN_DECL load_intrinsic_function(void *intrinsic_context, uint64_t id);
|
||||
SN_API SIMDID SN_DECL sn_get_simd_level();
|
||||
SN_API SIMDID SN_DECL sn_get_simd_level_global();
|
||||
/* Per-core variant: always re-executes CPUID, never caches.
|
||||
* Must be called on the target core (after affinity is set). */
|
||||
SN_API SIMDID SN_DECL sn_get_simd_level_local();
|
||||
SN_API SIMDID SN_DECL sn_get_unite_simd(SIMDID left, SIMDID right);
|
||||
SN_API int SN_DECL sn_compare_simd(SIMDID left, SIMDID right);
|
||||
SN_API void SN_DECL sn_set_thread_affinity(int core_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -27,7 +27,7 @@ SN_API SIMDID load_intrinsic_function(void *intrinsic_context, uint64_t id)
|
||||
if (id) {
|
||||
simdid.mask = id;
|
||||
} else {
|
||||
simdid = sn_get_simd_level();
|
||||
simdid = sn_get_simd_level_global();
|
||||
}
|
||||
|
||||
sn_intrinsic *context = (sn_intrinsic *)intrinsic_context;
|
||||
|
||||
@@ -1,244 +1,244 @@
|
||||
#include "sn_target.h"
|
||||
#include "sn_intrinsic.h"
|
||||
#include "StgSharpNative.h"
|
||||
|
||||
static SIMDID global_simdid = { 0 };
|
||||
|
||||
#if SN_IS_ARCH(SN_ARCH_X86_64)
|
||||
|
||||
#include "sn_intrincontext.x86.h"
|
||||
|
||||
#include <immintrin.h>
|
||||
#include <string.h>
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <cpuid.h>
|
||||
#endif
|
||||
|
||||
static void get_cpuid(int leaf, int subleaf, int *eax, int *ebx, int *ecx, int *edx)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
int regs[4];
|
||||
__cpuidex(regs, leaf, subleaf);
|
||||
*eax = regs[0];
|
||||
*ebx = regs[1];
|
||||
*ecx = regs[2];
|
||||
*edx = regs[3];
|
||||
#else
|
||||
unsigned int a, b, c, d;
|
||||
__cpuid_count((unsigned int)leaf, (unsigned int)subleaf, a, b, c, d);
|
||||
*eax = (int)a;
|
||||
*ebx = (int)b;
|
||||
*ecx = (int)c;
|
||||
*edx = (int)d;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int xgetbv_has_xmm_ymm(void)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
unsigned __int64 xcr0 = _xgetbv(0);
|
||||
#else
|
||||
unsigned long long xcr0 = __builtin_ia32_xgetbv(0);
|
||||
#endif
|
||||
return ((xcr0 & 0x6) == 0x6);
|
||||
}
|
||||
|
||||
static int xgetbv_has_avx512_state(void)
|
||||
{
|
||||
const unsigned long long mask = (1ull << 0) | (1ull << 1) | (1ull << 2) | (1ull << 5) |
|
||||
(1ull << 6);
|
||||
#if defined(_MSC_VER)
|
||||
unsigned __int64 xcr0 = _xgetbv(0);
|
||||
#else
|
||||
unsigned long long xcr0 = __builtin_ia32_xgetbv(0);
|
||||
#endif
|
||||
return ((xcr0 & mask) == mask);
|
||||
}
|
||||
|
||||
static int is_intel_dual_avx512(int family, int model)
|
||||
{
|
||||
return (family == 6) && (model == 0x55); /* SKX/CLX/CPX */
|
||||
}
|
||||
|
||||
static int is_intel_native_avx512(int family, int model)
|
||||
{
|
||||
if (family != 6)
|
||||
return 0;
|
||||
switch (model) {
|
||||
case 0x6A: /* ICX */
|
||||
case 0x6C: /* ICX stepping */
|
||||
case 0x8F: /* SPR/EMR */
|
||||
case 0xCF: /* GNR */
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
SN_API SIMDID SN_DECL sn_get_simd_level_local()
|
||||
{
|
||||
SIMDID _simdid = { 0 };
|
||||
|
||||
int eax = 0, ebx = 0, ecx = 0, edx = 0;
|
||||
|
||||
/* Vendor */
|
||||
get_cpuid(0, 0, &eax, &ebx, &ecx, &edx);
|
||||
char vendor[13];
|
||||
((int *)vendor)[0] = ebx;
|
||||
((int *)vendor)[1] = edx;
|
||||
((int *)vendor)[2] = ecx;
|
||||
vendor[12] = '\0';
|
||||
const int is_intel = (strcmp(vendor, "GenuineIntel") == 0);
|
||||
const int is_amd = (strcmp(vendor, "AuthenticAMD") == 0) ||
|
||||
(strcmp(vendor, "HygonGenuine") == 0);
|
||||
|
||||
/* CPUID.1 */
|
||||
get_cpuid(1, 0, &eax, &ebx, &ecx, &edx);
|
||||
int family = ((eax >> 8) & 0xF) + ((eax >> 20) & 0xFF0);
|
||||
int model = ((eax >> 4) & 0xF) | ((eax >> 12) & 0xF0);
|
||||
|
||||
int has_sse42 = (ecx & (1 << 20)) != 0;
|
||||
int has_avx = (ecx & (1 << 28)) != 0;
|
||||
int has_osxsave = (ecx & (1 << 27)) != 0;
|
||||
int has_fma = (ecx & (1 << 12)) != 0;
|
||||
int has_f16c = (ecx & (1 << 29)) != 0;
|
||||
|
||||
uint64_t root = SIMDID_PACK_ROOT(SIMDID_ROOT_X86_64);
|
||||
uint64_t manu = SIMDID_PACK_MANU(is_intel ? SIMDID_MANU_INTEL :
|
||||
is_amd ? SIMDID_MANU_AMD :
|
||||
SIMDID_MANU_UNKNOWN);
|
||||
uint64_t main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_NONE);
|
||||
uint64_t avx512_bits = 0;
|
||||
uint64_t amx_bits = 0;
|
||||
uint64_t avx_bits = 0;
|
||||
uint64_t avx10_bits = 0;
|
||||
|
||||
/* Baseline main level */
|
||||
if (has_sse42)
|
||||
main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_SSE);
|
||||
|
||||
if (has_avx && has_osxsave && xgetbv_has_xmm_ymm()) {
|
||||
avx_bits |= SIMDID_AVX;
|
||||
|
||||
/* CPUID.7.0 */
|
||||
get_cpuid(7, 0, &eax, &ebx, &ecx, &edx);
|
||||
const int has_bmi1 = (ebx & (1 << 3)) != 0;
|
||||
const int has_bmi2 = (ebx & (1 << 8)) != 0;
|
||||
const int has_avx2 = (ebx & (1 << 5)) != 0;
|
||||
const int has_avx512f = (ebx & (1 << 16)) != 0;
|
||||
const int has_avx512dq = (ebx & (1 << 17)) != 0;
|
||||
const int has_avx512bw = (ebx & (1 << 30)) != 0;
|
||||
const int has_avx512vl = (ebx & (1u << 31)) != 0;
|
||||
const int has_avx512_vnni = (ebx & (1 << 11)) != 0;
|
||||
const int has_vbmi = (ecx & (1 << 1)) != 0;
|
||||
const int has_vbmi2 = (ecx & (1 << 6)) != 0;
|
||||
|
||||
/* AMX in leaf 7 subleaf 0 EDX bits 24/25/26 — capture before
|
||||
overwriting regs with subleaf 1 */
|
||||
const int has_amx_tile = (edx & (1 << 24)) != 0;
|
||||
const int has_amx_int8 = (edx & (1 << 25)) != 0;
|
||||
const int has_amx_bf16 = (edx & (1 << 22)) != 0;
|
||||
|
||||
/* leaf 7, subleaf 1 for BF16/FP16/AVX10 convergence */
|
||||
get_cpuid(7, 1, &eax, &ebx, &ecx, &edx);
|
||||
const int has_bf16 = (eax & (1 << 5)) != 0; /* AVX512_BF16 */
|
||||
const int has_fp16 = (eax & (1 << 23)) != 0; /* AVX512_FP16 */
|
||||
const int has_avx10 = (edx & (1 << 19)) != 0; /* AVX10 convergence */
|
||||
|
||||
if (has_avx2) {
|
||||
avx_bits |= SIMDID_AVX2;
|
||||
if (has_fma)
|
||||
avx_bits |= SIMDID_AVX_FMA;
|
||||
if (has_f16c)
|
||||
avx_bits |= SIMDID_AVX_F16C;
|
||||
|
||||
main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_AVX2);
|
||||
}
|
||||
|
||||
if (has_avx512f && xgetbv_has_avx512_state()) {
|
||||
/* Ensure base subset present */
|
||||
if (has_avx512bw && has_avx512vl && has_avx512dq) {
|
||||
avx512_bits |= SIMDID_AVX512_BASE;
|
||||
}
|
||||
if (has_avx512_vnni)
|
||||
avx512_bits |= SIMDID_AVX512_VNNI;
|
||||
if (has_bf16)
|
||||
avx512_bits |= SIMDID_AVX512_BF16;
|
||||
if (has_fp16)
|
||||
avx512_bits |= SIMDID_AVX512_FP16;
|
||||
if (has_vbmi)
|
||||
avx512_bits |= SIMDID_AVX512_VBMI;
|
||||
if (has_vbmi2)
|
||||
avx512_bits |= SIMDID_AVX512_VBMI2;
|
||||
|
||||
/* impl classification */
|
||||
const int glue = is_amd || is_intel_dual_avx512(family, model);
|
||||
const int native512 = is_intel_native_avx512(family, model);
|
||||
if (glue) {
|
||||
avx512_bits |= SIMDID_AVX512_IMPL_DUAL;
|
||||
} else if (native512) {
|
||||
avx512_bits |= SIMDID_AVX512_IMPL_NATIVE;
|
||||
} else {
|
||||
avx512_bits |= SIMDID_AVX512_IMPL_UNKNOWN;
|
||||
}
|
||||
|
||||
/* main level for AVX512 */
|
||||
main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_AVX512);
|
||||
|
||||
/* AMX promotion if present */
|
||||
if (has_amx_tile) {
|
||||
amx_bits |= SIMDID_AMX_TILE;
|
||||
if (has_amx_int8)
|
||||
amx_bits |= SIMDID_AMX_INT8;
|
||||
if (has_amx_bf16)
|
||||
amx_bits |= SIMDID_AMX_BF16;
|
||||
main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_AMX);
|
||||
}
|
||||
|
||||
/* AVX10 detection — requires AVX10 convergence bit
|
||||
(leaf 7 subleaf 1 EDX[19]) and leaf 0x24 */
|
||||
if (has_avx10) {
|
||||
int a10_eax = 0, a10_ebx = 0, a10_ecx = 0, a10_edx = 0;
|
||||
get_cpuid(0x24, 0, &a10_eax, &a10_ebx, &a10_ecx, &a10_edx);
|
||||
const int avx10_ver = a10_ebx & 0xFF;
|
||||
if (avx10_ver >= 1) {
|
||||
avx10_bits |= SIMDID_AVX10 | SIMDID_AVX10_V1;
|
||||
if (avx10_ver >= 2)
|
||||
avx10_bits |= SIMDID_AVX10_V2;
|
||||
if (a10_ebx & (1 << 17))
|
||||
avx10_bits |= SIMDID_AVX10_256W;
|
||||
if (a10_ebx & (1 << 18))
|
||||
avx10_bits |= SIMDID_AVX10_512W;
|
||||
main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_AVX10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_simdid.mask = root | manu | main_lvl | SIMDID_PACK_AVX512(avx512_bits) |
|
||||
SIMDID_PACK_AMX(amx_bits) | SIMDID_PACK_AVX(avx_bits) |
|
||||
SIMDID_PACK_AVX10(avx10_bits);
|
||||
return _simdid;
|
||||
}
|
||||
|
||||
SN_API SIMDID SN_DECL sn_get_simd_level()
|
||||
{
|
||||
if (global_simdid.mask) {
|
||||
return global_simdid;
|
||||
}
|
||||
global_simdid = sn_get_simd_level_local();
|
||||
return global_simdid;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
SIMDID sn_get_simd_level()
|
||||
{
|
||||
SIMDID simd_id;
|
||||
simd_id.mask = 0;
|
||||
return simd_id; /* non-x86: no SIMD level probing */
|
||||
}
|
||||
|
||||
#endif
|
||||
#include "sn_target.h"
|
||||
#include "sn_intrinsic.h"
|
||||
#include "StgSharpNative.h"
|
||||
|
||||
static SIMDID global_simdid = { 0 };
|
||||
|
||||
#if SN_IS_ARCH(SN_ARCH_X86_64)
|
||||
|
||||
#include "sn_intrincontext.x86.h"
|
||||
|
||||
#include <immintrin.h>
|
||||
#include <string.h>
|
||||
#if defined(_MSC_VER)
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <cpuid.h>
|
||||
#endif
|
||||
|
||||
static void get_cpuid(int leaf, int subleaf, int *eax, int *ebx, int *ecx, int *edx)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
int regs[4];
|
||||
__cpuidex(regs, leaf, subleaf);
|
||||
*eax = regs[0];
|
||||
*ebx = regs[1];
|
||||
*ecx = regs[2];
|
||||
*edx = regs[3];
|
||||
#else
|
||||
unsigned int a, b, c, d;
|
||||
__cpuid_count((unsigned int)leaf, (unsigned int)subleaf, a, b, c, d);
|
||||
*eax = (int)a;
|
||||
*ebx = (int)b;
|
||||
*ecx = (int)c;
|
||||
*edx = (int)d;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int xgetbv_has_xmm_ymm(void)
|
||||
{
|
||||
#if defined(_MSC_VER)
|
||||
unsigned __int64 xcr0 = _xgetbv(0);
|
||||
#else
|
||||
unsigned long long xcr0 = __builtin_ia32_xgetbv(0);
|
||||
#endif
|
||||
return ((xcr0 & 0x6) == 0x6);
|
||||
}
|
||||
|
||||
static int xgetbv_has_avx512_state(void)
|
||||
{
|
||||
const unsigned long long mask = (1ull << 0) | (1ull << 1) | (1ull << 2) | (1ull << 5) |
|
||||
(1ull << 6);
|
||||
#if defined(_MSC_VER)
|
||||
unsigned __int64 xcr0 = _xgetbv(0);
|
||||
#else
|
||||
unsigned long long xcr0 = __builtin_ia32_xgetbv(0);
|
||||
#endif
|
||||
return ((xcr0 & mask) == mask);
|
||||
}
|
||||
|
||||
static int is_intel_dual_avx512(int family, int model)
|
||||
{
|
||||
return (family == 6) && (model == 0x55); /* SKX/CLX/CPX */
|
||||
}
|
||||
|
||||
static int is_intel_native_avx512(int family, int model)
|
||||
{
|
||||
if (family != 6)
|
||||
return 0;
|
||||
switch (model) {
|
||||
case 0x6A: /* ICX */
|
||||
case 0x6C: /* ICX stepping */
|
||||
case 0x8F: /* SPR/EMR */
|
||||
case 0xCF: /* GNR */
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
SN_API SIMDID SN_DECL sn_get_simd_level_local()
|
||||
{
|
||||
SIMDID _simdid = { 0 };
|
||||
|
||||
int eax = 0, ebx = 0, ecx = 0, edx = 0;
|
||||
|
||||
/* Vendor */
|
||||
get_cpuid(0, 0, &eax, &ebx, &ecx, &edx);
|
||||
char vendor[13];
|
||||
((int *)vendor)[0] = ebx;
|
||||
((int *)vendor)[1] = edx;
|
||||
((int *)vendor)[2] = ecx;
|
||||
vendor[12] = '\0';
|
||||
const int is_intel = (strcmp(vendor, "GenuineIntel") == 0);
|
||||
const int is_amd = (strcmp(vendor, "AuthenticAMD") == 0) ||
|
||||
(strcmp(vendor, "HygonGenuine") == 0);
|
||||
|
||||
/* CPUID.1 */
|
||||
get_cpuid(1, 0, &eax, &ebx, &ecx, &edx);
|
||||
int family = ((eax >> 8) & 0xF) + ((eax >> 20) & 0xFF0);
|
||||
int model = ((eax >> 4) & 0xF) | ((eax >> 12) & 0xF0);
|
||||
|
||||
int has_sse42 = (ecx & (1 << 20)) != 0;
|
||||
int has_avx = (ecx & (1 << 28)) != 0;
|
||||
int has_osxsave = (ecx & (1 << 27)) != 0;
|
||||
int has_fma = (ecx & (1 << 12)) != 0;
|
||||
int has_f16c = (ecx & (1 << 29)) != 0;
|
||||
|
||||
uint64_t root = SIMDID_PACK_ROOT(SIMDID_ROOT_X86_64);
|
||||
uint64_t manu = SIMDID_PACK_MANU(is_intel ? SIMDID_MANU_INTEL :
|
||||
is_amd ? SIMDID_MANU_AMD :
|
||||
SIMDID_MANU_UNKNOWN);
|
||||
uint64_t main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_NONE);
|
||||
uint64_t avx512_bits = 0;
|
||||
uint64_t amx_bits = 0;
|
||||
uint64_t avx_bits = 0;
|
||||
uint64_t avx10_bits = 0;
|
||||
|
||||
/* Baseline main level */
|
||||
if (has_sse42)
|
||||
main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_SSE);
|
||||
|
||||
if (has_avx && has_osxsave && xgetbv_has_xmm_ymm()) {
|
||||
avx_bits |= SIMDID_AVX;
|
||||
|
||||
/* CPUID.7.0 */
|
||||
get_cpuid(7, 0, &eax, &ebx, &ecx, &edx);
|
||||
const int has_bmi1 = (ebx & (1 << 3)) != 0;
|
||||
const int has_bmi2 = (ebx & (1 << 8)) != 0;
|
||||
const int has_avx2 = (ebx & (1 << 5)) != 0;
|
||||
const int has_avx512f = (ebx & (1 << 16)) != 0;
|
||||
const int has_avx512dq = (ebx & (1 << 17)) != 0;
|
||||
const int has_avx512bw = (ebx & (1 << 30)) != 0;
|
||||
const int has_avx512vl = (ebx & (1u << 31)) != 0;
|
||||
const int has_avx512_vnni = (ebx & (1 << 11)) != 0;
|
||||
const int has_vbmi = (ecx & (1 << 1)) != 0;
|
||||
const int has_vbmi2 = (ecx & (1 << 6)) != 0;
|
||||
|
||||
/* AMX in leaf 7 subleaf 0 EDX bits 24/25/26 — capture before
|
||||
overwriting regs with subleaf 1 */
|
||||
const int has_amx_tile = (edx & (1 << 24)) != 0;
|
||||
const int has_amx_int8 = (edx & (1 << 25)) != 0;
|
||||
const int has_amx_bf16 = (edx & (1 << 22)) != 0;
|
||||
|
||||
/* leaf 7, subleaf 1 for BF16/FP16/AVX10 convergence */
|
||||
get_cpuid(7, 1, &eax, &ebx, &ecx, &edx);
|
||||
const int has_bf16 = (eax & (1 << 5)) != 0; /* AVX512_BF16 */
|
||||
const int has_fp16 = (eax & (1 << 23)) != 0; /* AVX512_FP16 */
|
||||
const int has_avx10 = (edx & (1 << 19)) != 0; /* AVX10 convergence */
|
||||
|
||||
if (has_avx2) {
|
||||
avx_bits |= SIMDID_AVX2;
|
||||
if (has_fma)
|
||||
avx_bits |= SIMDID_AVX_FMA;
|
||||
if (has_f16c)
|
||||
avx_bits |= SIMDID_AVX_F16C;
|
||||
|
||||
main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_AVX2);
|
||||
}
|
||||
|
||||
if (has_avx512f && xgetbv_has_avx512_state()) {
|
||||
/* Ensure base subset present */
|
||||
if (has_avx512bw && has_avx512vl && has_avx512dq) {
|
||||
avx512_bits |= SIMDID_AVX512_BASE;
|
||||
}
|
||||
if (has_avx512_vnni)
|
||||
avx512_bits |= SIMDID_AVX512_VNNI;
|
||||
if (has_bf16)
|
||||
avx512_bits |= SIMDID_AVX512_BF16;
|
||||
if (has_fp16)
|
||||
avx512_bits |= SIMDID_AVX512_FP16;
|
||||
if (has_vbmi)
|
||||
avx512_bits |= SIMDID_AVX512_VBMI;
|
||||
if (has_vbmi2)
|
||||
avx512_bits |= SIMDID_AVX512_VBMI2;
|
||||
|
||||
/* impl classification */
|
||||
const int glue = is_amd || is_intel_dual_avx512(family, model);
|
||||
const int native512 = is_intel_native_avx512(family, model);
|
||||
if (glue) {
|
||||
avx512_bits |= SIMDID_AVX512_IMPL_DUAL;
|
||||
} else if (native512) {
|
||||
avx512_bits |= SIMDID_AVX512_IMPL_NATIVE;
|
||||
} else {
|
||||
avx512_bits |= SIMDID_AVX512_IMPL_UNKNOWN;
|
||||
}
|
||||
|
||||
/* main level for AVX512 */
|
||||
main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_AVX512);
|
||||
|
||||
/* AMX promotion if present */
|
||||
if (has_amx_tile) {
|
||||
amx_bits |= SIMDID_AMX_TILE;
|
||||
if (has_amx_int8)
|
||||
amx_bits |= SIMDID_AMX_INT8;
|
||||
if (has_amx_bf16)
|
||||
amx_bits |= SIMDID_AMX_BF16;
|
||||
main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_AMX);
|
||||
}
|
||||
|
||||
/* AVX10 detection — requires AVX10 convergence bit
|
||||
(leaf 7 subleaf 1 EDX[19]) and leaf 0x24 */
|
||||
if (has_avx10) {
|
||||
int a10_eax = 0, a10_ebx = 0, a10_ecx = 0, a10_edx = 0;
|
||||
get_cpuid(0x24, 0, &a10_eax, &a10_ebx, &a10_ecx, &a10_edx);
|
||||
const int avx10_ver = a10_ebx & 0xFF;
|
||||
if (avx10_ver >= 1) {
|
||||
avx10_bits |= SIMDID_AVX10 | SIMDID_AVX10_V1;
|
||||
if (avx10_ver >= 2)
|
||||
avx10_bits |= SIMDID_AVX10_V2;
|
||||
if (a10_ebx & (1 << 17))
|
||||
avx10_bits |= SIMDID_AVX10_256W;
|
||||
if (a10_ebx & (1 << 18))
|
||||
avx10_bits |= SIMDID_AVX10_512W;
|
||||
main_lvl = SIMDID_PACK_MAIN(SIMDID_MAIN_LVL_AVX10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_simdid.mask = root | manu | main_lvl | SIMDID_PACK_AVX512(avx512_bits) |
|
||||
SIMDID_PACK_AMX(amx_bits) | SIMDID_PACK_AVX(avx_bits) |
|
||||
SIMDID_PACK_AVX10(avx10_bits);
|
||||
return _simdid;
|
||||
}
|
||||
|
||||
SN_API SIMDID SN_DECL sn_get_simd_level_global()
|
||||
{
|
||||
if (global_simdid.mask) {
|
||||
return global_simdid;
|
||||
}
|
||||
global_simdid = sn_get_simd_level_local();
|
||||
return global_simdid;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
SIMDID sn_get_simd_level()
|
||||
{
|
||||
SIMDID simd_id;
|
||||
simd_id.mask = 0;
|
||||
return simd_id; /* non-x86: no SIMD level probing */
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,199 @@
|
||||
#include "sn_target.h"
|
||||
|
||||
#if SN_IS_ARCH(SN_ARCH_X86_64)
|
||||
|
||||
#include "StgSharpNative.h"
|
||||
#include "sn_intrinsic.h"
|
||||
#include "sn_intrincontext.x86.h"
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Field extraction helpers.
|
||||
*
|
||||
* SIMDID layout (64 bits, little-endian byte order in make_byte[]):
|
||||
* [3..0] Root ISA make_byte[0] low nibble
|
||||
* [7..4] Manufacture make_byte[0] high nibble
|
||||
* [15..8] MainLevel make_byte[1]
|
||||
* [31..16] AVX512 Feature make_byte[2..3] (16 bits)
|
||||
* [39..32] AMX Feature make_byte[4]
|
||||
* [47..40] AVX Feature make_byte[5]
|
||||
* [55..48] AVX10 Feature make_byte[6]
|
||||
* [63..56] uArchHi make_byte[7]
|
||||
* ----------------------------------------------------------------------- */
|
||||
|
||||
#define FIELD_MAIN(id) ((unsigned char)(id).make_byte[1])
|
||||
#define FIELD_AVX512(id) ((uint16_t)((unsigned char)(id).make_byte[2] | \
|
||||
((unsigned char)(id).make_byte[3] << 8)))
|
||||
#define FIELD_AMX(id) ((unsigned char)(id).make_byte[4])
|
||||
#define FIELD_AVX(id) ((unsigned char)(id).make_byte[5])
|
||||
#define FIELD_AVX10(id) ((unsigned char)(id).make_byte[6])
|
||||
#define FIELD_UARCH(id) ((unsigned char)(id).make_byte[7])
|
||||
|
||||
/* Portable popcount for a 16-bit value. */
|
||||
static int popcount16(uint16_t v)
|
||||
{
|
||||
int c = 0;
|
||||
while (v) { v &= v - 1; c++; }
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Map AVX512_IMPL 2-bit field to a strength ordinal for tie-breaking.
|
||||
* DUAL(1) => 0 (weakest), UNKNOWN(0) => 1, NATIVE(2) => 2 (strongest) */
|
||||
static int impl_strength(uint16_t avx512)
|
||||
{
|
||||
unsigned impl = (avx512 >> SIMDID_AVX512_IMPL_SHIFT) & 0x3u;
|
||||
switch (impl) {
|
||||
case 1: return 0; /* DUAL — glue / downclock risk */
|
||||
case 0: return 1; /* UNKNOWN — conservatively mid */
|
||||
case 2: return 2; /* NATIVE — full-width ALU */
|
||||
default: return 1; /* reserved, treat as unknown */
|
||||
}
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* sn_get_unite_simd
|
||||
*
|
||||
* Produce the conservative intersection of two SIMDIDs — the strongest
|
||||
* capability set that is safe to use on BOTH cores:
|
||||
* - MainLevel : min (only assume what both cores support)
|
||||
* - AVX512 features : bitwise AND (IMPL field: pick the weaker one)
|
||||
* - AMX features : bitwise AND
|
||||
* - AVX features : bitwise AND
|
||||
* - AVX10 features : bitwise AND
|
||||
* - Root / Manu : keep left (must be same arch in practice)
|
||||
* - uArchHi : min (most conservative policy)
|
||||
* ----------------------------------------------------------------------- */
|
||||
SN_API SIMDID SN_DECL sn_get_unite_simd(SIMDID left, SIMDID right)
|
||||
{
|
||||
SIMDID out;
|
||||
out.mask = 0;
|
||||
|
||||
/* Root + Manufacture (byte 0): prefer left (same arch assumed) */
|
||||
unsigned char lb0 = (unsigned char)left.make_byte[0];
|
||||
unsigned char rb0 = (unsigned char)right.make_byte[0];
|
||||
out.make_byte[0] = (char)(lb0 ? lb0 : rb0);
|
||||
|
||||
/* MainLevel (byte 1): min */
|
||||
unsigned char lm = FIELD_MAIN(left);
|
||||
unsigned char rm = FIELD_MAIN(right);
|
||||
out.make_byte[1] = (char)(lm < rm ? lm : rm);
|
||||
|
||||
/* AVX512 features (bytes 2-3): AND all bits, then fix IMPL to weaker */
|
||||
uint16_t l512 = FIELD_AVX512(left);
|
||||
uint16_t r512 = FIELD_AVX512(right);
|
||||
uint16_t merged = l512 & r512;
|
||||
|
||||
/* Clear the IMPL field, then set the weaker one */
|
||||
int ls = impl_strength(l512);
|
||||
int rs = impl_strength(r512);
|
||||
unsigned weak_impl = (ls <= rs)
|
||||
? ((l512 >> SIMDID_AVX512_IMPL_SHIFT) & 0x3u)
|
||||
: ((r512 >> SIMDID_AVX512_IMPL_SHIFT) & 0x3u);
|
||||
merged = (merged & ~(uint16_t)SIMDID_AVX512_IMPL_MASK)
|
||||
| (uint16_t)(weak_impl << SIMDID_AVX512_IMPL_SHIFT);
|
||||
out.make_byte[2] = (char)(merged & 0xFF);
|
||||
out.make_byte[3] = (char)((merged >> 8) & 0xFF);
|
||||
|
||||
/* AMX (byte 4): AND */
|
||||
out.make_byte[4] = left.make_byte[4] & right.make_byte[4];
|
||||
|
||||
/* AVX (byte 5): AND */
|
||||
out.make_byte[5] = left.make_byte[5] & right.make_byte[5];
|
||||
|
||||
/* AVX10 (byte 6): AND */
|
||||
out.make_byte[6] = left.make_byte[6] & right.make_byte[6];
|
||||
|
||||
/* uArchHi (byte 7): min */
|
||||
unsigned char lu = FIELD_UARCH(left);
|
||||
unsigned char ru = FIELD_UARCH(right);
|
||||
out.make_byte[7] = (char)(lu < ru ? lu : ru);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* sn_compare_simd
|
||||
*
|
||||
* Compare two SIMDIDs by ISA strength. Returns:
|
||||
* +1 if left is strictly stronger than right
|
||||
* -1 if left is strictly weaker than right
|
||||
* 0 if equal
|
||||
*
|
||||
* Comparison priority (most significant first):
|
||||
*
|
||||
* 1. MainLevel ([15..8])
|
||||
* Higher value = stronger. This is the primary discriminator:
|
||||
* NONE < SSE < AVX2 < AVX512 < AMX < AVX10
|
||||
*
|
||||
* 2. AVX10 vector width ([55..48])
|
||||
* Only meaningful when both MainLevel == AVX10.
|
||||
* 512W > 256W-only. Compared by the full AVX10 byte value since
|
||||
* width bits are in higher positions than version bits.
|
||||
*
|
||||
* 3. AVX512 BASE bit (bit 4 of [31..16])
|
||||
* Only meaningful when both MainLevel >= AVX512.
|
||||
* BASE=1 (F+CD+VL+DQ+BW all present) > BASE=0.
|
||||
*
|
||||
* 4. AVX512 extension popcount
|
||||
* Count of set extension bits (VNNI/BF16/FP16/VBMI/VBMI2/AI_RESERVED).
|
||||
* The IMPL and BASE bits are masked out before counting so they don't
|
||||
* inflate the extension count.
|
||||
*
|
||||
* 5. AVX512 IMPL strength (tie-break, performance hint)
|
||||
* DUAL(weakest) < UNKNOWN < NATIVE(strongest).
|
||||
* This is not ISA capability but execution-width risk.
|
||||
*
|
||||
* 6. uArchHi (final tie-break)
|
||||
* Higher value = more specific / stronger policy hint.
|
||||
* ----------------------------------------------------------------------- */
|
||||
SN_API int SN_DECL sn_compare_simd(SIMDID left, SIMDID right)
|
||||
{
|
||||
/* 1. MainLevel */
|
||||
unsigned lm = FIELD_MAIN(left);
|
||||
unsigned rm = FIELD_MAIN(right);
|
||||
if (lm != rm)
|
||||
return lm > rm ? 1 : -1;
|
||||
|
||||
/* 2. AVX10 width (only when MainLevel == AVX10) */
|
||||
if (lm >= SIMDID_MAIN_LVL_AVX10) {
|
||||
unsigned la = FIELD_AVX10(left);
|
||||
unsigned ra = FIELD_AVX10(right);
|
||||
if (la != ra)
|
||||
return la > ra ? 1 : -1;
|
||||
}
|
||||
|
||||
/* 3. AVX512 BASE (only when MainLevel >= AVX512) */
|
||||
if (lm >= SIMDID_MAIN_LVL_AVX512) {
|
||||
uint16_t l512 = FIELD_AVX512(left);
|
||||
uint16_t r512 = FIELD_AVX512(right);
|
||||
|
||||
int lb = (l512 & SIMDID_AVX512_BASE) != 0;
|
||||
int rb = (r512 & SIMDID_AVX512_BASE) != 0;
|
||||
if (lb != rb)
|
||||
return lb > rb ? 1 : -1;
|
||||
|
||||
/* 4. Extension popcount (mask out BASE + IMPL + reserved low nibble) */
|
||||
uint16_t ext_mask = (uint16_t)~(SIMDID_AVX512_BASE
|
||||
| SIMDID_AVX512_IMPL_MASK
|
||||
| 0x000Fu);
|
||||
int lp = popcount16(l512 & ext_mask);
|
||||
int rp = popcount16(r512 & ext_mask);
|
||||
if (lp != rp)
|
||||
return lp > rp ? 1 : -1;
|
||||
|
||||
/* 5. IMPL strength */
|
||||
int ls = impl_strength(l512);
|
||||
int rs = impl_strength(r512);
|
||||
if (ls != rs)
|
||||
return ls > rs ? 1 : -1;
|
||||
}
|
||||
|
||||
/* 6. uArchHi (final tie-break) */
|
||||
unsigned lu = FIELD_UARCH(left);
|
||||
unsigned ru = FIELD_UARCH(right);
|
||||
if (lu != ru)
|
||||
return lu > ru ? 1 : -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -5,27 +5,51 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
/* Pin the calling thread to a single logical processor.
|
||||
* core_id: zero-based logical processor index.
|
||||
* Silently ignored if core_id is out of range or the OS call fails. */
|
||||
* core_id >= 0 : bind to that logical processor (global index, across groups).
|
||||
* core_id < 0 : clear affinity constraint within the current group. */
|
||||
SN_API void SN_DECL sn_set_thread_affinity(int core_id)
|
||||
{
|
||||
if (core_id < 0)
|
||||
return;
|
||||
|
||||
#if SN_IS_OS(SN_OS_WINDOWS)
|
||||
/* windows.h already included via sn_internal.h */
|
||||
if (core_id >= 64)
|
||||
if (core_id < 0) {
|
||||
/* Query which group the thread currently belongs to */
|
||||
GROUP_AFFINITY current;
|
||||
GetThreadGroupAffinity(GetCurrentThread(), ¤t);
|
||||
/* Build a full mask for that group's actual core count */
|
||||
DWORD count = GetActiveProcessorCount(current.Group);
|
||||
GROUP_AFFINITY ga;
|
||||
memset(&ga, 0, sizeof(ga));
|
||||
ga.Group = current.Group;
|
||||
ga.Mask = (count >= 64) ? ~(KAFFINITY)0
|
||||
: ((KAFFINITY)1 << count) - 1;
|
||||
SetThreadGroupAffinity(GetCurrentThread(), &ga, NULL);
|
||||
return;
|
||||
DWORD_PTR mask = (DWORD_PTR)1 << core_id;
|
||||
SetThreadAffinityMask(GetCurrentThread(), mask);
|
||||
}
|
||||
WORD group = (WORD)(core_id / 64);
|
||||
int index = core_id % 64;
|
||||
DWORD count = GetActiveProcessorCount(group);
|
||||
if ((DWORD)index >= count)
|
||||
return;
|
||||
GROUP_AFFINITY ga;
|
||||
memset(&ga, 0, sizeof(ga));
|
||||
ga.Group = group;
|
||||
ga.Mask = (KAFFINITY)1 << index;
|
||||
SetThreadGroupAffinity(GetCurrentThread(), &ga, NULL);
|
||||
|
||||
#elif SN_IS_OS(SN_OS_LINUX)
|
||||
cpu_set_t set;
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
if (core_id < 0) {
|
||||
CPU_ZERO(&set);
|
||||
long ncpus = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
for (long i = 0; i < ncpus; i++)
|
||||
CPU_SET((int)i, &set);
|
||||
} else {
|
||||
CPU_ZERO(&set);
|
||||
CPU_SET(core_id, &set);
|
||||
}
|
||||
pthread_setaffinity_np(pthread_self(), sizeof(set), &set);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user