Files
0ad/source/lib/sysdep/ia32/ia32.cpp
T
janwas 7a5655edde # major refactoring of system-dependent code (simplifies build system)
cpu.cpp: avoided the need for wrapper functions by calling the
OS-specific function directly (declared in central header, implemented
in the platform's cpp file)

avoid the need for init in cpu and ia32 via if(!init) Init() pattern.

optimized memcpy now requires SSE support

remove error-prone CAS macro; replace with cpu_CAS
config: no longer require inline asm for float->int conversions
lib_error: remove special-case in CHECK_ERR for windows (no longer
needed)

This was SVN commit r5365.
2007-09-23 15:36:29 +00:00

743 lines
21 KiB
C++

/**
* =========================================================================
* File : ia32.cpp
* Project : 0 A.D.
* Description : C++ and inline asm implementations of IA-32 functions
* =========================================================================
*/
// license: GPL; see lib/license.txt
#include "precompiled.h"
#include "ia32.h"
#include <string.h>
#include <stdio.h>
#include <vector>
#include <set>
#include <algorithm>
#include "lib/posix/posix.h" // pthread
#include "lib/bits.h"
#include "lib/timer.h"
#include "lib/sysdep/cpu.h"
#if !HAVE_MS_ASM && !HAVE_GNU_ASM
#error ia32.cpp needs inline assembly support!
#endif
//-----------------------------------------------------------------------------
// capability bits
static void DetectFeatureFlags(u32 caps[4])
{
u32 regs[4];
if(ia32_asm_cpuid(1, regs))
{
caps[0] = regs[ECX];
caps[1] = regs[EDX];
}
if(ia32_asm_cpuid(0x80000001, regs))
{
caps[2] = regs[ECX];
caps[3] = regs[EDX];
}
}
bool ia32_cap(IA32Cap cap)
{
// treated as 128 bit field; order: std ecx, std edx, ext ecx, ext edx
// keep in sync with enum CpuCap!
static u32 ia32_caps[4];
// (since relevant CPUs will surely advertise at least one standard flag,
// they are zero iff we haven't been initialized yet)
if(!ia32_caps[1])
DetectFeatureFlags(ia32_caps);
const uint tbl_idx = cap >> 5;
const uint bit_idx = cap & 0x1f;
if(tbl_idx > 3)
{
DEBUG_WARN_ERR(ERR::INVALID_PARAM);
return false;
}
return (ia32_caps[tbl_idx] & BIT(bit_idx)) != 0;
}
//-----------------------------------------------------------------------------
// CPU identification
static Ia32Vendor DetectVendor()
{
u32 regs[4];
if(!ia32_asm_cpuid(0, regs))
DEBUG_WARN_ERR(ERR::CPU_FEATURE_MISSING);
// copy regs to string
// note: 'strange' ebx,edx,ecx reg order is due to ModR/M encoding order.
char vendor_str[13];
u32* vendor_str_u32 = (u32*)vendor_str;
vendor_str_u32[0] = regs[EBX];
vendor_str_u32[1] = regs[EDX];
vendor_str_u32[2] = regs[ECX];
vendor_str[12] = '\0'; // 0-terminate
if(!strcmp(vendor_str, "AuthenticAMD"))
return IA32_VENDOR_AMD;
else if(!strcmp(vendor_str, "GenuineIntel"))
return IA32_VENDOR_INTEL;
else
{
DEBUG_WARN_ERR(ERR::CPU_UNKNOWN_VENDOR);
return IA32_VENDOR_UNKNOWN;
}
}
Ia32Vendor ia32_Vendor()
{
static Ia32Vendor vendor = IA32_VENDOR_UNKNOWN;
if(vendor == IA32_VENDOR_UNKNOWN)
vendor = DetectVendor();
return vendor;
}
static void DetectSignature(uint* model, uint* family)
{
u32 regs[4];
if(!ia32_asm_cpuid(1, regs))
DEBUG_WARN_ERR(ERR::CPU_FEATURE_MISSING);
*model = bits(regs[EAX], 4, 7);
*family = bits(regs[EAX], 8, 11);
}
static uint DetectGeneration()
{
uint model, family;
DetectSignature(&model, &family);
switch(family)
{
case 5:
case 6:
case 7:
return family;
case 0xF:
return 8;
default:
debug_assert(0);
return 0;
}
}
uint ia32_Generation()
{
static uint generation;
if(!generation)
generation = DetectGeneration();
return generation;
}
//-----------------------------------------------------------------------------
// identifier string
/// functor to remove substrings from the CPU identifier string
class StringStripper
{
char* m_string;
size_t m_max_chars;
public:
StringStripper(char* string, size_t max_chars)
: m_string(string), m_max_chars(max_chars)
{
}
// remove all instances of substring from m_string
void operator()(const char* substring)
{
const size_t substring_length = strlen(substring);
for(;;)
{
char* substring_pos = strstr(m_string, substring);
if(!substring_pos)
break;
const size_t substring_ofs = substring_pos - m_string;
const size_t num_chars = m_max_chars - substring_ofs - substring_length;
memmove(substring_pos, substring_pos+substring_length, num_chars);
}
}
};
static void DetectIdentifierString(char* identifierString)
{
// get brand string (if available)
// note: ia32_asm_cpuid writes 4 u32s directly to identifierString -
// be very careful with pointer arithmetic!
u32* u32_string = (u32*)identifierString;
bool have_brand_string = false;
if(ia32_asm_cpuid(0x80000002, u32_string+0 ) &&
ia32_asm_cpuid(0x80000003, u32_string+4) &&
ia32_asm_cpuid(0x80000004, u32_string+8))
have_brand_string = true;
// note: we previously verified max_chars is long enough, so copying
// short literals into it is safe.
// fall back to manual detect of CPU type because either:
// - CPU doesn't support brand string (we use a flag to indicate this
// rather than comparing against a default value because it is safer);
// - the brand string is useless, e.g. "Unknown". this happens on
// some older boards whose BIOS reprograms the string for CPUs it
// doesn't recognize.
if(!have_brand_string || strncmp(identifierString, "Unknow", 6) == 0)
{
uint model, family;
DetectSignature(&model, &family);
switch(ia32_Vendor())
{
case IA32_VENDOR_AMD:
// everything else is either too old, or should have a brand string.
if(family == 6)
{
if(model == 3 || model == 7)
SAFE_STRCPY(identifierString, "AMD Duron");
else if(model <= 5)
SAFE_STRCPY(identifierString, "AMD Athlon");
else
{
if(ia32_cap(IA32_CAP_AMD_MP))
SAFE_STRCPY(identifierString, "AMD Athlon MP");
else
SAFE_STRCPY(identifierString, "AMD Athlon XP");
}
}
break;
case IA32_VENDOR_INTEL:
// everything else is either too old, or should have a brand string.
if(family == 6)
{
if(model == 1)
SAFE_STRCPY(identifierString, "Intel Pentium Pro");
else if(model == 3 || model == 5)
SAFE_STRCPY(identifierString, "Intel Pentium II");
else if(model == 6)
SAFE_STRCPY(identifierString, "Intel Celeron");
else
SAFE_STRCPY(identifierString, "Intel Pentium III");
}
break;
}
}
// identifierString already holds a valid brand string; pretty it up.
else
{
const char* const undesired_strings[] = { "(tm)", "(TM)", "(R)", "CPU " };
std::for_each(undesired_strings, undesired_strings+ARRAY_SIZE(undesired_strings),
StringStripper(identifierString, ARRAY_SIZE(identifierString)));
// note: Intel brand strings include a frequency, but we can't rely
// on it because the CPU may be overclocked. we'll leave it in the
// string to show measurement accuracy and if SpeedStep is active.
}
}
const char* cpu_IdentifierString()
{
// 3 calls x 4 registers x 4 bytes = 48
static char identifierString[48+1] = {'\0'};
if(identifierString[0] == '\0')
DetectIdentifierString(identifierString);
return identifierString;
}
//-----------------------------------------------------------------------------
// CPU frequency
// set scheduling priority and restore when going out of scope.
class ScopedSetPriority
{
int m_old_policy;
sched_param m_old_param;
public:
ScopedSetPriority(int new_priority)
{
// get current scheduling policy and priority
pthread_getschedparam(pthread_self(), &m_old_policy, &m_old_param);
// set new priority
sched_param new_param = {0};
new_param.sched_priority = new_priority;
pthread_setschedparam(pthread_self(), SCHED_FIFO, &new_param);
}
~ScopedSetPriority()
{
// restore previous policy and priority.
pthread_setschedparam(pthread_self(), m_old_policy, &m_old_param);
}
};
// note: this function uses timer.cpp!get_time, which is implemented via
// whrt.cpp on Windows, which again calls ia32_Init. be careful that
// this function isn't called from there as well, else WHRT will be used
// before its init completes.
double ia32_ClockFrequency()
{
// if the TSC isn't available, there's really no good way to count the
// actual CPU clocks per known time interval, so bail.
// note: loop iterations ("bogomips") are not a reliable measure due
// to differing IPC and compiler optimizations.
if(!ia32_cap(IA32_CAP_TSC))
return -1.0; // impossible value
// increase priority to reduce interference while measuring.
const int priority = sched_get_priority_max(SCHED_FIFO)-1;
ScopedSetPriority ssp(priority);
// note: no need to "warm up" cpuid - it will already have been
// called several times by the time this code is reached.
// (background: it's used in ia32_rdtsc() to serialize instruction flow;
// the first call is documented to be slower on Intel CPUs)
int num_samples = 16;
// if clock is low-res, do less samples so it doesn't take too long.
// balance measuring time (~ 10 ms) and accuracy (< 1 0/00 error -
// ok for using the TSC as a time reference)
if(timer_res() >= 1e-3)
num_samples = 8;
std::vector<double> samples(num_samples);
for(int i = 0; i < num_samples; i++)
{
double dt;
i64 dc; // i64 because VC6 can't convert u64 -> double,
// and we don't need all 64 bits.
// count # of clocks in max{1 tick, 1 ms}:
// .. wait for start of tick.
const double t0 = get_time();
u64 c1; double t1;
do
{
// note: get_time effectively has a long delay (up to 5 us)
// before returning the time. we call it before ia32_rdtsc to
// minimize the delay between actually sampling time / TSC,
// thus decreasing the chance for interference.
// (if unavoidable background activity, e.g. interrupts,
// delays the second reading, inaccuracy is introduced).
t1 = get_time();
c1 = ia32_rdtsc();
}
while(t1 == t0);
// .. wait until start of next tick and at least 1 ms elapsed.
do
{
const double t2 = get_time();
const u64 c2 = ia32_rdtsc();
dc = (i64)(c2 - c1);
dt = t2 - t1;
}
while(dt < 1e-3);
// .. freq = (delta_clocks) / (delta_seconds);
// ia32_rdtsc/timer overhead is negligible.
const double freq = dc / dt;
samples[i] = freq;
}
std::sort(samples.begin(), samples.end());
// median filter (remove upper and lower 25% and average the rest).
// note: don't just take the lowest value! it could conceivably be
// too low, if background processing delays reading c1 (see above).
double sum = 0.0;
const int lo = num_samples/4, hi = 3*num_samples/4;
for(int i = lo; i < hi; i++)
sum += samples[i];
const double clock_frequency = sum / (hi-lo);
return clock_frequency;
}
//-----------------------------------------------------------------------------
// processor topology
uint ia32_ApicId()
{
u32 regs[4];
if(!ia32_asm_cpuid(1, regs))
DEBUG_WARN_ERR(ERR::CPU_FEATURE_MISSING);
const uint apicId = bits(regs[EBX], 24, 31);
return apicId;
}
// OSes report hyperthreading units and cores as "processors". we need to
// drill down and find out the exact counts (for thread pool dimensioning
// and cache sharing considerations).
// note: Intel Appnote 485 (CPUID) assures uniformity of coresPerPackage and
// logicalPerCore.
static uint DetectCoresPerPackage()
{
u32 regs[4];
switch(ia32_Vendor())
{
case IA32_VENDOR_INTEL:
if(ia32_asm_cpuid(4, regs))
return bits(regs[EAX], 26, 31)+1;
break;
case IA32_VENDOR_AMD:
if(ia32_asm_cpuid(0x80000008, regs))
return bits(regs[ECX], 0, 7)+1;
break;
}
return 1; // else: the CPU is single-core.
}
static uint CoresPerPackage()
{
static uint coresPerPackage = 0;
if(!coresPerPackage)
coresPerPackage = DetectCoresPerPackage();
return coresPerPackage;
}
static bool IsHyperthreadingCapable()
{
// definitely not
if(!ia32_cap(IA32_CAP_HT))
return false;
// AMD N-core systems falsely set the HT bit for compatibility reasons
// (don't bother resetting it, might confuse callers)
if(ia32_Vendor() == IA32_VENDOR_AMD && ia32_cap(IA32_CAP_AMD_CMP_LEGACY))
return false;
return true;
}
static uint DetectLogicalPerCore()
{
if(!IsHyperthreadingCapable())
return 1;
u32 regs[4];
if(!ia32_asm_cpuid(1, regs))
DEBUG_WARN_ERR(ERR::CPU_FEATURE_MISSING);
const uint logicalPerPackage = bits(regs[EBX], 16, 23);
// cores ought to be uniform WRT # logical processors
debug_assert(logicalPerPackage % CoresPerPackage() == 0);
return logicalPerPackage / CoresPerPackage();
}
static uint LogicalPerCore()
{
static uint logicalPerCore = 0;
if(!logicalPerCore)
logicalPerCore = DetectLogicalPerCore();
return logicalPerCore;
}
// the above two functions give the maximum number of cores/logical units.
// however, some of them may actually be disabled by the BIOS!
// what we can do is to analyze the APIC IDs. they are allocated sequentially
// for all "processors". treating the IDs as variable-width bitfields
// (according to the number of cores/logical units present) allows
// determining the exact topology as well as number of packages.
// these are set by DetectProcessorTopology.
static uint numPackages = 0; // i.e. sockets; > 1 => true SMP system
static uint enabledCoresPerPackage = 0;
static uint enabledLogicalPerCore = 0; // hyperthreading units
typedef std::vector<u8> Ids;
typedef std::set<u8> IdSet;
// add the currently running processor's APIC ID to a list of IDs.
static void StoreApicId(void* param)
{
Ids* apicIds = (Ids*)param;
apicIds->push_back(ia32_ApicId());
}
// field := a range of bits sufficient to represent <num_values> integers.
// for each id in apicIds: extract the value of the field at offset bit_pos
// and insert it into ids. afterwards, adjust bit_pos to the next field.
// used to gather e.g. all core IDs from all APIC IDs.
static void ExtractFieldsIntoSet(const Ids& apicIds, uint& bit_pos, uint num_values, IdSet& ids)
{
const uint id_bits = ceil_log2(num_values);
if(id_bits == 0)
return;
const uint mask = bit_mask(id_bits);
for(size_t i = 0; i < apicIds.size(); i++)
{
const u8 apic_id = apicIds[i];
const u8 field = (apic_id >> bit_pos) & mask;
ids.insert(field);
}
bit_pos += id_bits;
}
// @return false if unavailable / no information can be returned.
static bool DetectProcessorTopologyViaApicIds()
{
// old APIC (see ia32_ApicId for details)
if(ia32_Generation() < 8)
return false;
// get the set of all APIC IDs
Ids apicIds;
// .. OS affinity support is missing or excludes us from some processors
if(cpu_CallByEachCPU(StoreApicId, &apicIds) != INFO::OK)
return false;
// .. if IDs aren't unique, cpu_CallByEachCPU is broken.
std::sort(apicIds.begin(), apicIds.end());
debug_assert(std::unique(apicIds.begin(), apicIds.end()) == apicIds.end());
// extract values from all 3 ID bitfields into separate sets
uint bit_pos = 0;
IdSet logicalIds;
ExtractFieldsIntoSet(apicIds, bit_pos, LogicalPerCore(), logicalIds);
IdSet coreIds;
ExtractFieldsIntoSet(apicIds, bit_pos, CoresPerPackage(), coreIds);
IdSet packageIds;
ExtractFieldsIntoSet(apicIds, bit_pos, 0xFF, packageIds);
// (the set cardinality is representative of all packages/cores since
// their numbers are uniform across the system.)
numPackages = std::max((uint)packageIds.size(), 1u);
enabledCoresPerPackage = std::max((uint)coreIds .size(), 1u);
enabledLogicalPerCore = std::max((uint)logicalIds.size(), 1u);
// note: even though APIC IDs are assigned sequentially, we can't make any
// assumptions about the values/ordering because we get them according to
// the CPU affinity mask, which is unknown.
return true;
}
static void GuessProcessorTopologyViaOsCount()
{
const int numProcessors = cpu_NumProcessors();
// note: we cannot hope to always return correct results since disabled
// cores/logical units cannot be distinguished from the situation of the
// OS simply not reporting them as "processors". unfortunately this
// function won't always only be called for older (#core = #logical = 1)
// systems because DetectProcessorTopologyViaApicIds may fail due to
// lack of OS support. what we'll do is assume nothing is disabled; this
// is reasonable because we care most about #packages. it's fine to assume
// more cores (without inflating the total #processors) because that
// count only indicates memory barriers etc. ought to be used.
enabledCoresPerPackage = CoresPerPackage();
enabledLogicalPerCore = LogicalPerCore();
const long numPackagesTimesLogical = numProcessors / CoresPerPackage();
debug_assert(numPackagesTimesLogical != 0); // otherwise processors didn't include cores, which would be stupid
numPackages = numPackagesTimesLogical / LogicalPerCore();
if(!numPackages) // processors didn't include logical units (reasonable)
numPackages = numPackagesTimesLogical;
}
// determine how many CoresPerPackage and LogicalPerCore are
// actually enabled and also count numPackages.
static void DetectProcessorTopology()
{
// authoritative, but requires newer CPU, and OS support.
if(DetectProcessorTopologyViaApicIds())
return; // success, we're done.
GuessProcessorTopologyViaOsCount();
}
uint cpu_NumPackages()
{
if(!numPackages)
DetectProcessorTopology();
return (uint)numPackages;
}
uint cpu_CoresPerPackage()
{
if(!enabledCoresPerPackage)
DetectProcessorTopology();
return (uint)enabledCoresPerPackage;
}
uint cpu_LogicalPerCore()
{
if(!enabledLogicalPerCore)
DetectProcessorTopology();
return (uint)enabledLogicalPerCore;
}
//-----------------------------------------------------------------------------
// misc stateless functions
// this RDTSC implementation writes edx:eax to a temporary and returns that.
// rationale: this insulates against changing compiler calling conventions,
// at the cost of some efficiency.
// use ia32_asm_rdtsc_edx_eax instead if the return convention is known to be
// edx:eax (should be the case on all 32-bit x86).
u64 ia32_rdtsc_safe()
{
u64 c;
#if HAVE_MS_ASM
__asm
{
cpuid
rdtsc
mov dword ptr [c], eax
mov dword ptr [c+4], edx
}
#elif HAVE_GNU_ASM
// note: we save+restore EBX to avoid xcode complaining about a
// "PIC register" being clobbered, whatever that means.
__asm__ __volatile__ (
"pushl %%ebx; cpuid; popl %%ebx; rdtsc"
: "=A" (c)
: /* no input */
: "ecx" /* cpuid clobbers eax..edx, but the rest are covered */);
#endif
return c;
}
void ia32_DebugBreak()
{
#if HAVE_MS_ASM
__asm int 3
// note: this probably isn't necessary, since unix_debug_break
// (SIGTRAP) is most probably available if HAVE_GNU_ASM.
// we include it for completeness, though.
#elif HAVE_GNU_ASM
__asm__ __volatile__ ("int $3");
#endif
}
// enforce strong memory ordering.
void cpu_MemoryFence()
{
// Pentium IV
if(ia32_cap(IA32_CAP_SSE2))
#if HAVE_MS_ASM
__asm mfence
#elif HAVE_GNU_ASM
__asm__ __volatile__ ("mfence");
#endif
}
// checks if there is an IA-32 CALL instruction right before ret_addr.
// returns INFO::OK if so and ERR::FAIL if not.
// also attempts to determine the call target. if that is possible
// (directly addressed relative or indirect jumps), it is stored in
// target, which is otherwise 0.
//
// this is useful for walking the stack manually.
LibError ia32_GetCallTarget(void* ret_addr, void** target)
{
*target = 0;
// points to end of the CALL instruction (which is of unknown length)
const u8* c = (const u8*)ret_addr;
// this would allow for avoiding exceptions when accessing ret_addr
// close to the beginning of the code segment. it's not currently set
// because this is really unlikely and not worth the trouble.
const size_t len = ~0u;
// CALL rel32 (E8 cd)
if(len >= 5 && c[-5] == 0xE8)
{
*target = (u8*)ret_addr + *(i32*)(c-4);
return INFO::OK;
}
// CALL r/m32 (FF /2)
// .. CALL [r32 + r32*s] => FF 14 SIB
if(len >= 3 && c[-3] == 0xFF && c[-2] == 0x14)
return INFO::OK;
// .. CALL [disp32] => FF 15 disp32
if(len >= 6 && c[-6] == 0xFF && c[-5] == 0x15)
{
void* addr_of_target = *(void**)(c-4);
// there are no meaningful checks we can perform: we're called from
// the stack trace code, so ring0 addresses may be legit.
// even if the pointer is 0, it's better to pass its value on
// (may help in tracking down memory corruption)
*target = *(void**)addr_of_target;
return INFO::OK;
}
// .. CALL [r32] => FF 00-3F(!14/15)
if(len >= 2 && c[-2] == 0xFF && c[-1] < 0x40 && c[-1] != 0x14 && c[-1] != 0x15)
return INFO::OK;
// .. CALL [r32 + r32*s + disp8] => FF 54 SIB disp8
if(len >= 4 && c[-4] == 0xFF && c[-3] == 0x54)
return INFO::OK;
// .. CALL [r32 + disp8] => FF 50-57(!54) disp8
if(len >= 3 && c[-3] == 0xFF && (c[-2] & 0xF8) == 0x50 && c[-2] != 0x54)
return INFO::OK;
// .. CALL [r32 + r32*s + disp32] => FF 94 SIB disp32
if(len >= 7 && c[-7] == 0xFF && c[-6] == 0x94)
return INFO::OK;
// .. CALL [r32 + disp32] => FF 90-97(!94) disp32
if(len >= 6 && c[-6] == 0xFF && (c[-5] & 0xF8) == 0x90 && c[-5] != 0x94)
return INFO::OK;
// .. CALL r32 => FF D0-D7
if(len >= 2 && c[-2] == 0xFF && (c[-1] & 0xF8) == 0xD0)
return INFO::OK;
WARN_RETURN(ERR::CPU_UNKNOWN_OPCODE);
}
void cpu_ConfigureFloatingPoint()
{
// no longer set 24 bit (float) precision by default: for
// very long game uptimes (> 1 day; e.g. dedicated server),
// we need full precision when calculating the time.
// if there's a spot where we want to speed up divides|sqrts,
// we can temporarily change precision there.
//ia32_asm_control87(IA32_PC_24, IA32_MCW_PC);
// to help catch bugs, enable as many floating-point exceptions as
// possible. unfortunately SpiderMonkey triggers all of them.
// note: passing a flag *disables* that exception.
ia32_asm_control87(IA32_EM_ZERODIVIDE|IA32_EM_INVALID|IA32_EM_DENORMAL|IA32_EM_OVERFLOW|IA32_EM_UNDERFLOW|IA32_EM_INEXACT, IA32_MCW_EM);
// no longer round toward zero (truncate). changing this setting
// resulted in much faster float->int casts, because the compiler
// could be told (via /QIfist) to use FISTP while still truncating
// the result as required by ANSI C. however, FPU calculation
// results were changed significantly, so it had to be disabled.
//ia32_asm_control87(IA32_RC_CHOP, IA32_MCW_RC);
}