diff options
Diffstat (limited to 'src/jit/utils.cpp')
-rw-r--r-- | src/jit/utils.cpp | 1767 |
1 files changed, 1767 insertions, 0 deletions
diff --git a/src/jit/utils.cpp b/src/jit/utils.cpp new file mode 100644 index 0000000000..9934416412 --- /dev/null +++ b/src/jit/utils.cpp @@ -0,0 +1,1767 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XX XX +XX Utils.cpp XX +XX XX +XX Has miscellaneous utility functions XX +XX XX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +*/ + +#include "jitpch.h" +#ifdef _MSC_VER +#pragma hdrstop +#endif + +#include "opcode.h" + +/*****************************************************************************/ +// Define the string platform name based on compilation #ifdefs. This is the +// same code for all platforms, hence it is here instead of in the targetXXX.cpp +// files. + +#ifdef PLATFORM_UNIX +// Should we distinguish Mac? Can we? +// Should we distinguish flavors of Unix? Can we? +const char* Target::g_tgtPlatformName = "Unix"; +#else // !PLATFORM_UNIX +const char* Target::g_tgtPlatformName = "Windows"; +#endif // !PLATFORM_UNIX + +/*****************************************************************************/ + +#define DECLARE_DATA + +// clang-format off +extern +const signed char opcodeSizes[] = +{ + #define InlineNone_size 0 + #define ShortInlineVar_size 1 + #define InlineVar_size 2 + #define ShortInlineI_size 1 + #define InlineI_size 4 + #define InlineI8_size 8 + #define ShortInlineR_size 4 + #define InlineR_size 8 + #define ShortInlineBrTarget_size 1 + #define InlineBrTarget_size 4 + #define InlineMethod_size 4 + #define InlineField_size 4 + #define InlineType_size 4 + #define InlineString_size 4 + #define InlineSig_size 4 + #define InlineRVA_size 4 + #define InlineTok_size 4 + #define InlineSwitch_size 0 // for now + #define InlinePhi_size 0 // for now + #define InlineVarTok_size 0 // remove + + #define OPDEF(name,string,pop,push,oprType,opcType,l,s1,s2,ctrl) oprType ## _size , + #include "opcode.def" + #undef OPDEF + + #undef InlineNone_size + #undef ShortInlineVar_size + #undef InlineVar_size + #undef ShortInlineI_size + #undef InlineI_size + #undef InlineI8_size + #undef ShortInlineR_size + #undef InlineR_size + #undef ShortInlineBrTarget_size + #undef InlineBrTarget_size + #undef InlineMethod_size + #undef InlineField_size + #undef InlineType_size + #undef InlineString_size + #undef InlineSig_size + #undef InlineRVA_size + #undef InlineTok_size + #undef InlineSwitch_size + #undef InlinePhi_size +}; +// clang-format on + +const BYTE varTypeClassification[] = { +#define DEF_TP(tn, nm, jitType, verType, sz, sze, asze, st, al, tf, howUsed) tf, +#include "typelist.h" +#undef DEF_TP +}; + +/*****************************************************************************/ +/*****************************************************************************/ +#ifdef DEBUG +extern const char* const opcodeNames[] = { +#define OPDEF(name, string, pop, push, oprType, opcType, l, s1, s2, ctrl) string, +#include "opcode.def" +#undef OPDEF +}; + +extern const BYTE opcodeArgKinds[] = { +#define OPDEF(name, string, pop, push, oprType, opcType, l, s1, s2, ctrl) (BYTE) oprType, +#include "opcode.def" +#undef OPDEF +}; +#endif + +/*****************************************************************************/ + +const char* varTypeName(var_types vt) +{ + static const char* const varTypeNames[] = { +#define DEF_TP(tn, nm, jitType, verType, sz, sze, asze, st, al, tf, howUsed) nm, +#include "typelist.h" +#undef DEF_TP + }; + + assert((unsigned)vt < sizeof(varTypeNames) / sizeof(varTypeNames[0])); + + return varTypeNames[vt]; +} + +#if defined(DEBUG) || defined(LATE_DISASM) +/***************************************************************************** + * + * Return the name of the given register. + */ + +const char* getRegName(regNumber reg, bool isFloat) +{ + // Special-case REG_NA; it's not in the regNames array, but we might want to print it. + if (reg == REG_NA) + { + return "NA"; + } +#if defined(_TARGET_X86_) && defined(LEGACY_BACKEND) + static const char* const regNames[] = { +#define REGDEF(name, rnum, mask, sname) sname, +#include "register.h" + }; + + static const char* const floatRegNames[] = { +#define REGDEF(name, rnum, mask, sname) sname, +#include "registerxmm.h" + }; + if (isFloat) + { + assert(reg < ArrLen(floatRegNames)); + return floatRegNames[reg]; + } + else + { + assert(reg < ArrLen(regNames)); + return regNames[reg]; + } +#elif defined(_TARGET_ARM64_) + static const char* const regNames[] = { +#define REGDEF(name, rnum, mask, xname, wname) xname, +#include "register.h" + }; + assert(reg < ArrLen(regNames)); + return regNames[reg]; +#else + static const char* const regNames[] = { +#define REGDEF(name, rnum, mask, sname) sname, +#include "register.h" + }; + assert(reg < ArrLen(regNames)); + return regNames[reg]; +#endif +} + +const char* getRegName(unsigned reg, + bool isFloat) // this is for gcencode.cpp and disasm.cpp that dont use the regNumber type +{ + return getRegName((regNumber)reg, isFloat); +} +#endif // defined(DEBUG) || defined(LATE_DISASM) + +#if defined(DEBUG) + +const char* getRegNameFloat(regNumber reg, var_types type) +{ +#ifdef _TARGET_ARM_ + assert(genIsValidFloatReg(reg)); + if (type == TYP_FLOAT) + return getRegName(reg); + else + { + const char* regName; + + switch (reg) + { + default: + assert(!"Bad double register"); + regName = "d??"; + break; + case REG_F0: + regName = "d0"; + break; + case REG_F2: + regName = "d2"; + break; + case REG_F4: + regName = "d4"; + break; + case REG_F6: + regName = "d6"; + break; + case REG_F8: + regName = "d8"; + break; + case REG_F10: + regName = "d10"; + break; + case REG_F12: + regName = "d12"; + break; + case REG_F14: + regName = "d14"; + break; + case REG_F16: + regName = "d16"; + break; + case REG_F18: + regName = "d18"; + break; + case REG_F20: + regName = "d20"; + break; + case REG_F22: + regName = "d22"; + break; + case REG_F24: + regName = "d24"; + break; + case REG_F26: + regName = "d26"; + break; + case REG_F28: + regName = "d28"; + break; + case REG_F30: + regName = "d30"; + break; + } + return regName; + } + +#elif defined(_TARGET_X86_) && defined(LEGACY_BACKEND) + + static const char* regNamesFloat[] = { +#define REGDEF(name, rnum, mask, sname) sname, +#include "registerxmm.h" + }; + assert((unsigned)reg < ArrLen(regNamesFloat)); + + return regNamesFloat[reg]; + +#elif defined(_TARGET_ARM64_) + + static const char* regNamesFloat[] = { +#define REGDEF(name, rnum, mask, xname, wname) xname, +#include "register.h" + }; + assert((unsigned)reg < ArrLen(regNamesFloat)); + + return regNamesFloat[reg]; + +#else + static const char* regNamesFloat[] = { +#define REGDEF(name, rnum, mask, sname) "x" sname, +#include "register.h" + }; +#ifdef FEATURE_AVX_SUPPORT + static const char* regNamesYMM[] = { +#define REGDEF(name, rnum, mask, sname) "y" sname, +#include "register.h" + }; +#endif // FEATURE_AVX_SUPPORT + assert((unsigned)reg < ArrLen(regNamesFloat)); + +#ifdef FEATURE_AVX_SUPPORT + if (type == TYP_SIMD32) + { + return regNamesYMM[reg]; + } +#endif // FEATURE_AVX_SUPPORT + + return regNamesFloat[reg]; +#endif +} + +/***************************************************************************** + * + * Displays a register set. + * TODO-ARM64-Cleanup: don't allow ip0, ip1 as part of a range. + */ + +void dspRegMask(regMaskTP regMask, size_t minSiz) +{ + const char* sep = ""; + + printf("["); + + bool inRegRange = false; + regNumber regPrev = REG_NA; + regNumber regHead = REG_NA; // When we start a range, remember the first register of the range, so we don't use + // range notation if the range contains just a single register. + for (regNumber regNum = REG_INT_FIRST; regNum <= REG_INT_LAST; regNum = REG_NEXT(regNum)) + { + regMaskTP regBit = genRegMask(regNum); + + if ((regMask & regBit) != 0) + { + // We have a register to display. It gets displayed now if: + // 1. This is the first register to display of a new range of registers (possibly because + // no register has ever been displayed). + // 2. This is the last register of an acceptable range (either the last integer register, + // or the last of a range that is displayed with range notation). + if (!inRegRange) + { + // It's the first register of a potential range. + const char* nam = getRegName(regNum); + printf("%s%s", sep, nam); + minSiz -= strlen(sep) + strlen(nam); + + // By default, we're not starting a potential register range. + sep = " "; + + // What kind of separator should we use for this range (if it is indeed going to be a range)? + CLANG_FORMAT_COMMENT_ANCHOR; + +#if defined(_TARGET_AMD64_) + // For AMD64, create ranges for int registers R8 through R15, but not the "old" registers. + if (regNum >= REG_R8) + { + regHead = regNum; + inRegRange = true; + sep = "-"; + } +#elif defined(_TARGET_ARM64_) + // R17 and R28 can't be the start of a range, since the range would include TEB or FP + if ((regNum < REG_R17) || ((REG_R19 <= regNum) && (regNum < REG_R28))) + { + regHead = regNum; + inRegRange = true; + sep = "-"; + } +#elif defined(_TARGET_ARM_) + if (regNum < REG_R12) + { + regHead = regNum; + inRegRange = true; + sep = "-"; + } +#elif defined(_TARGET_X86_) +// No register ranges +#else // _TARGET_* +#error Unsupported or unset target architecture +#endif // _TARGET_* + } + +#if defined(_TARGET_ARM64_) + // We've already printed a register. Is this the end of a range? + else if ((regNum == REG_INT_LAST) || (regNum == REG_R17) // last register before TEB + || (regNum == REG_R28)) // last register before FP +#else // _TARGET_ARM64_ + // We've already printed a register. Is this the end of a range? + else if (regNum == REG_INT_LAST) +#endif // _TARGET_ARM64_ + { + const char* nam = getRegName(regNum); + printf("%s%s", sep, nam); + minSiz -= strlen(sep) + strlen(nam); + inRegRange = false; // No longer in the middle of a register range + regHead = REG_NA; + sep = " "; + } + } + else // ((regMask & regBit) == 0) + { + if (inRegRange) + { + assert(regHead != REG_NA); + if (regPrev != regHead) + { + // Close out the previous range, if it included more than one register. + const char* nam = getRegName(regPrev); + printf("%s%s", sep, nam); + minSiz -= strlen(sep) + strlen(nam); + } + sep = " "; + inRegRange = false; + regHead = REG_NA; + } + } + + if (regBit > regMask) + { + break; + } + + regPrev = regNum; + } + +#if CPU_HAS_BYTE_REGS + if (regMask & RBM_BYTE_REG_FLAG) + { + const char* nam = "BYTE"; + printf("%s%s", sep, nam); + minSiz -= (strlen(sep) + strlen(nam)); + } +#endif + +#if !FEATURE_STACK_FP_X87 + if (strlen(sep) > 0) + { + // We've already printed something. + sep = " "; + } + inRegRange = false; + regPrev = REG_NA; + regHead = REG_NA; + for (regNumber regNum = REG_FP_FIRST; regNum <= REG_FP_LAST; regNum = REG_NEXT(regNum)) + { + regMaskTP regBit = genRegMask(regNum); + + if (regMask & regBit) + { + if (!inRegRange || (regNum == REG_FP_LAST)) + { + const char* nam = getRegName(regNum); + printf("%s%s", sep, nam); + minSiz -= strlen(sep) + strlen(nam); + sep = "-"; + regHead = regNum; + } + inRegRange = true; + } + else + { + if (inRegRange) + { + if (regPrev != regHead) + { + const char* nam = getRegName(regPrev); + printf("%s%s", sep, nam); + minSiz -= (strlen(sep) + strlen(nam)); + } + sep = " "; + } + inRegRange = false; + } + + if (regBit > regMask) + { + break; + } + + regPrev = regNum; + } +#endif + + printf("]"); + + while ((int)minSiz > 0) + { + printf(" "); + minSiz--; + } +} + +//------------------------------------------------------------------------ +// dumpILBytes: Helper for dumpSingleInstr() to dump hex bytes of an IL stream, +// aligning up to a minimum alignment width. +// +// Arguments: +// codeAddr - Pointer to IL byte stream to display. +// codeSize - Number of bytes of IL byte stream to display. +// alignSize - Pad out to this many characters, if fewer than this were written. +// +void dumpILBytes(const BYTE* const codeAddr, + unsigned codeSize, + unsigned alignSize) // number of characters to write, for alignment +{ + for (IL_OFFSET offs = 0; offs < codeSize; ++offs) + { + printf(" %02x", *(codeAddr + offs)); + } + + unsigned charsWritten = 3 * codeSize; + for (unsigned i = charsWritten; i < alignSize; i++) + { + printf(" "); + } +} + +//------------------------------------------------------------------------ +// dumpSingleInstr: Display a single IL instruction. +// +// Arguments: +// codeAddr - Base pointer to a stream of IL instructions. +// offs - Offset from codeAddr of the IL instruction to display. +// prefix - Optional string to prefix the IL instruction with (if nullptr, no prefix is output). +// +// Return Value: +// Size of the displayed IL instruction in the instruction stream, in bytes. (Add this to 'offs' to +// get to the next instruction.) +// +unsigned dumpSingleInstr(const BYTE* const codeAddr, IL_OFFSET offs, const char* prefix) +{ + const BYTE* opcodePtr = codeAddr + offs; + const BYTE* startOpcodePtr = opcodePtr; + const unsigned ALIGN_WIDTH = 3 * 6; // assume 3 characters * (1 byte opcode + 4 bytes data + 1 prefix byte) for + // most things + + if (prefix != nullptr) + { + printf("%s", prefix); + } + + OPCODE opcode = (OPCODE)getU1LittleEndian(opcodePtr); + opcodePtr += sizeof(__int8); + +DECODE_OPCODE: + + if (opcode >= CEE_COUNT) + { + printf("\nIllegal opcode: %02X\n", (int)opcode); + return (IL_OFFSET)(opcodePtr - startOpcodePtr); + } + + /* Get the size of additional parameters */ + + size_t sz = opcodeSizes[opcode]; + unsigned argKind = opcodeArgKinds[opcode]; + + /* See what kind of an opcode we have, then */ + + switch (opcode) + { + case CEE_PREFIX1: + opcode = OPCODE(getU1LittleEndian(opcodePtr) + 256); + opcodePtr += sizeof(__int8); + goto DECODE_OPCODE; + + default: + { + __int64 iOp; + double dOp; + int jOp; + DWORD jOp2; + + switch (argKind) + { + case InlineNone: + dumpILBytes(startOpcodePtr, (unsigned)(opcodePtr - startOpcodePtr), ALIGN_WIDTH); + printf(" %-12s", opcodeNames[opcode]); + break; + + case ShortInlineVar: + iOp = getU1LittleEndian(opcodePtr); + goto INT_OP; + case ShortInlineI: + iOp = getI1LittleEndian(opcodePtr); + goto INT_OP; + case InlineVar: + iOp = getU2LittleEndian(opcodePtr); + goto INT_OP; + case InlineTok: + case InlineMethod: + case InlineField: + case InlineType: + case InlineString: + case InlineSig: + case InlineI: + iOp = getI4LittleEndian(opcodePtr); + goto INT_OP; + case InlineI8: + iOp = getU4LittleEndian(opcodePtr); + iOp |= (__int64)getU4LittleEndian(opcodePtr + 4) << 32; + goto INT_OP; + + INT_OP: + dumpILBytes(startOpcodePtr, (unsigned)((opcodePtr - startOpcodePtr) + sz), ALIGN_WIDTH); + printf(" %-12s 0x%X", opcodeNames[opcode], iOp); + break; + + case ShortInlineR: + dOp = getR4LittleEndian(opcodePtr); + goto FLT_OP; + case InlineR: + dOp = getR8LittleEndian(opcodePtr); + goto FLT_OP; + + FLT_OP: + dumpILBytes(startOpcodePtr, (unsigned)((opcodePtr - startOpcodePtr) + sz), ALIGN_WIDTH); + printf(" %-12s %f", opcodeNames[opcode], dOp); + break; + + case ShortInlineBrTarget: + jOp = getI1LittleEndian(opcodePtr); + goto JMP_OP; + case InlineBrTarget: + jOp = getI4LittleEndian(opcodePtr); + goto JMP_OP; + + JMP_OP: + dumpILBytes(startOpcodePtr, (unsigned)((opcodePtr - startOpcodePtr) + sz), ALIGN_WIDTH); + printf(" %-12s %d (IL_%04x)", opcodeNames[opcode], jOp, (int)(opcodePtr + sz - codeAddr) + jOp); + break; + + case InlineSwitch: + jOp2 = getU4LittleEndian(opcodePtr); + opcodePtr += 4; + opcodePtr += jOp2 * 4; // Jump over the table + dumpILBytes(startOpcodePtr, (unsigned)(opcodePtr - startOpcodePtr), ALIGN_WIDTH); + printf(" %-12s", opcodeNames[opcode]); + break; + + case InlinePhi: + jOp2 = getU1LittleEndian(opcodePtr); + opcodePtr += 1; + opcodePtr += jOp2 * 2; // Jump over the table + dumpILBytes(startOpcodePtr, (unsigned)(opcodePtr - startOpcodePtr), ALIGN_WIDTH); + printf(" %-12s", opcodeNames[opcode]); + break; + + default: + assert(!"Bad argKind"); + } + + opcodePtr += sz; + break; + } + } + + printf("\n"); + return (IL_OFFSET)(opcodePtr - startOpcodePtr); +} + +//------------------------------------------------------------------------ +// dumpILRange: Display a range of IL instructions from an IL instruction stream. +// +// Arguments: +// codeAddr - Pointer to IL byte stream to display. +// codeSize - Number of bytes of IL byte stream to display. +// +void dumpILRange(const BYTE* const codeAddr, unsigned codeSize) // in bytes +{ + for (IL_OFFSET offs = 0; offs < codeSize;) + { + char prefix[100]; + sprintf(prefix, "IL_%04x ", offs); + unsigned codeBytesDumped = dumpSingleInstr(codeAddr, offs, prefix); + offs += codeBytesDumped; + } +} + +/***************************************************************************** + * + * Display a variable set (which may be a 32-bit or 64-bit number); only + * one or two of these can be used at once. + */ + +const char* genES2str(EXPSET_TP set) +{ + const int bufSize = 17; + static char num1[bufSize]; + + static char num2[bufSize]; + + static char* nump = num1; + + char* temp = nump; + + nump = (nump == num1) ? num2 : num1; + +#if EXPSET_SZ == 32 + sprintf_s(temp, bufSize, "%08X", set); +#else + sprintf_s(temp, bufSize, "%08X%08X", (int)(set >> 32), (int)set); +#endif + + return temp; +} + +const char* refCntWtd2str(unsigned refCntWtd) +{ + const int bufSize = 17; + static char num1[bufSize]; + + static char num2[bufSize]; + + static char* nump = num1; + + char* temp = nump; + + nump = (nump == num1) ? num2 : num1; + + unsigned valueInt = refCntWtd / BB_UNITY_WEIGHT; + unsigned valueFrac = refCntWtd % BB_UNITY_WEIGHT; + + if (valueFrac == 0) + { + sprintf_s(temp, bufSize, "%2u ", valueInt); + } + else + { + sprintf_s(temp, bufSize, "%2u.%1u", valueInt, (valueFrac * 10 / BB_UNITY_WEIGHT)); + } + + return temp; +} + +#endif // DEBUG + +#if defined(DEBUG) || defined(INLINE_DATA) + +//------------------------------------------------------------------------ +// Contains: check if the range includes a particular method +// +// Arguments: +// info -- jit interface pointer +// method -- method handle for the method of interest + +bool ConfigMethodRange::Contains(ICorJitInfo* info, CORINFO_METHOD_HANDLE method) +{ + _ASSERT(m_inited == 1); + + // No ranges specified means all methods included. + if (m_lastRange == 0) + { + return true; + } + + // Check the hash. Note we can't use the cached hash here since + // we may not be asking about the method currently being jitted. + const unsigned hash = info->getMethodHash(method); + + for (unsigned i = 0; i < m_lastRange; i++) + { + if ((m_ranges[i].m_low <= hash) && (hash <= m_ranges[i].m_high)) + { + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------ +// InitRanges: parse the range string and set up the range info +// +// Arguments: +// rangeStr -- string to parse (may be nullptr) +// capacity -- number ranges to allocate in the range array +// +// Notes: +// Does some internal error checking; clients can use Error() +// to determine if the range string couldn't be fully parsed +// because of bad characters or too many entries, or had values +// that were too large to represent. + +void ConfigMethodRange::InitRanges(const wchar_t* rangeStr, unsigned capacity) +{ + // Make sure that the memory was zero initialized + assert(m_inited == 0 || m_inited == 1); + assert(m_entries == 0); + assert(m_ranges == nullptr); + assert(m_lastRange == 0); + + // Flag any crazy-looking requests + assert(capacity < 100000); + + if (rangeStr == nullptr) + { + m_inited = 1; + return; + } + + // Allocate some persistent memory + ICorJitHost* jitHost = JitHost::getJitHost(); + m_ranges = (Range*)jitHost->allocateMemory(capacity * sizeof(Range)); + m_entries = capacity; + + const wchar_t* p = rangeStr; + unsigned lastRange = 0; + bool setHighPart = false; + + while ((*p != 0) && (lastRange < m_entries)) + { + while (*p == L' ') + { + p++; + } + + int i = 0; + + while (L'0' <= *p && *p <= L'9') + { + int j = 10 * i + ((*p++) - L'0'); + + // Check for overflow + if ((m_badChar != 0) && (j <= i)) + { + m_badChar = (p - rangeStr) + 1; + } + + i = j; + } + + // Was this the high part of a low-high pair? + if (setHighPart) + { + // Yep, set it and move to the next range + m_ranges[lastRange].m_high = i; + + // Sanity check that range is proper + if ((m_badChar != 0) && (m_ranges[lastRange].m_high < m_ranges[lastRange].m_low)) + { + m_badChar = (p - rangeStr) + 1; + } + + lastRange++; + setHighPart = false; + continue; + } + + // Must have been looking for the low part of a range + m_ranges[lastRange].m_low = i; + + while (*p == L' ') + { + p++; + } + + // Was that the low part of a low-high pair? + if (*p == L'-') + { + // Yep, skip the dash and set high part next time around. + p++; + setHighPart = true; + continue; + } + + // Else we have a point range, so set high = low + m_ranges[lastRange].m_high = i; + lastRange++; + } + + // If we didn't parse the full range string, note index of the the + // first bad char. + if ((m_badChar != 0) && (*p != 0)) + { + m_badChar = (p - rangeStr) + 1; + } + + // Finish off any remaining open range + if (setHighPart) + { + m_ranges[lastRange].m_high = UINT_MAX; + lastRange++; + } + + assert(lastRange <= m_entries); + m_lastRange = lastRange; + m_inited = 1; +} + +#endif // defined(DEBUG) || defined(INLINE_DATA) + +#if CALL_ARG_STATS || COUNT_BASIC_BLOCKS || COUNT_LOOPS || EMITTER_STATS || MEASURE_NODE_SIZE + +/***************************************************************************** + * Histogram class. + */ + +Histogram::Histogram(IAllocator* allocator, const unsigned* const sizeTable) + : m_allocator(allocator), m_sizeTable(sizeTable), m_counts(nullptr) +{ + unsigned sizeCount = 0; + do + { + sizeCount++; + } while ((sizeTable[sizeCount] != 0) && (sizeCount < 1000)); + + m_sizeCount = sizeCount; +} + +Histogram::~Histogram() +{ + m_allocator->Free(m_counts); +} + +// We need to lazy allocate the histogram data so static `Histogram` variables don't try to +// call the host memory allocator in the loader lock, which doesn't work. +void Histogram::ensureAllocated() +{ + if (m_counts == nullptr) + { + m_counts = new (m_allocator) unsigned[m_sizeCount + 1]; + memset(m_counts, 0, (m_sizeCount + 1) * sizeof(*m_counts)); + } +} + +void Histogram::dump(FILE* output) +{ + ensureAllocated(); + + unsigned t = 0; + for (unsigned i = 0; i < m_sizeCount; i++) + { + t += m_counts[i]; + } + + for (unsigned c = 0, i = 0; i <= m_sizeCount; i++) + { + if (i == m_sizeCount) + { + if (m_counts[i] == 0) + { + break; + } + + fprintf(output, " > %7u", m_sizeTable[i - 1]); + } + else + { + if (i == 0) + { + fprintf(output, " <= "); + } + else + { + fprintf(output, "%7u .. ", m_sizeTable[i - 1] + 1); + } + + fprintf(output, "%7u", m_sizeTable[i]); + } + + c += m_counts[i]; + + fprintf(output, " ===> %7u count (%3u%% of total)\n", m_counts[i], (int)(100.0 * c / t)); + } +} + +void Histogram::record(unsigned size) +{ + ensureAllocated(); + + unsigned i; + for (i = 0; i < m_sizeCount; i++) + { + if (m_sizeTable[i] >= size) + { + break; + } + } + + m_counts[i]++; +} + +#endif // CALL_ARG_STATS || COUNT_BASIC_BLOCKS || COUNT_LOOPS || EMITTER_STATS || MEASURE_NODE_SIZE + +/***************************************************************************** + * Fixed bit vector class + */ + +// bitChunkSize() - Returns number of bits in a bitVect chunk +inline UINT FixedBitVect::bitChunkSize() +{ + return sizeof(UINT) * 8; +} + +// bitNumToBit() - Returns a bit mask of the given bit number +inline UINT FixedBitVect::bitNumToBit(UINT bitNum) +{ + assert(bitNum < bitChunkSize()); + assert(bitChunkSize() <= sizeof(int) * 8); + + return 1 << bitNum; +} + +// bitVectInit() - Initializes a bit vector of a given size +FixedBitVect* FixedBitVect::bitVectInit(UINT size, Compiler* comp) +{ + UINT bitVectMemSize, numberOfChunks; + FixedBitVect* bv; + + assert(size != 0); + + numberOfChunks = (size - 1) / bitChunkSize() + 1; + bitVectMemSize = numberOfChunks * (bitChunkSize() / 8); // size in bytes + + assert(bitVectMemSize * bitChunkSize() >= size); + + bv = (FixedBitVect*)comp->compGetMemA(sizeof(FixedBitVect) + bitVectMemSize, CMK_FixedBitVect); + memset(bv->bitVect, 0, bitVectMemSize); + + bv->bitVectSize = size; + + return bv; +} + +// bitVectSet() - Sets the given bit +void FixedBitVect::bitVectSet(UINT bitNum) +{ + UINT index; + + assert(bitNum <= bitVectSize); + + index = bitNum / bitChunkSize(); + bitNum -= index * bitChunkSize(); + + bitVect[index] |= bitNumToBit(bitNum); +} + +// bitVectTest() - Tests the given bit +bool FixedBitVect::bitVectTest(UINT bitNum) +{ + UINT index; + + assert(bitNum <= bitVectSize); + + index = bitNum / bitChunkSize(); + bitNum -= index * bitChunkSize(); + + return (bitVect[index] & bitNumToBit(bitNum)) != 0; +} + +// bitVectOr() - Or in the given bit vector +void FixedBitVect::bitVectOr(FixedBitVect* bv) +{ + UINT bitChunkCnt = (bitVectSize - 1) / bitChunkSize() + 1; + + assert(bitVectSize == bv->bitVectSize); + + // Or each chunks + for (UINT i = 0; i < bitChunkCnt; i++) + { + bitVect[i] |= bv->bitVect[i]; + } +} + +// bitVectAnd() - And with passed in bit vector +void FixedBitVect::bitVectAnd(FixedBitVect& bv) +{ + UINT bitChunkCnt = (bitVectSize - 1) / bitChunkSize() + 1; + + assert(bitVectSize == bv.bitVectSize); + + // And each chunks + for (UINT i = 0; i < bitChunkCnt; i++) + { + bitVect[i] &= bv.bitVect[i]; + } +} + +// bitVectGetFirst() - Find the first bit on and return bit num, +// Return -1 if no bits found. +UINT FixedBitVect::bitVectGetFirst() +{ + return bitVectGetNext((UINT)-1); +} + +// bitVectGetNext() - Find the next bit on given previous position and return bit num. +// Return -1 if no bits found. +UINT FixedBitVect::bitVectGetNext(UINT bitNumPrev) +{ + UINT bitNum = (UINT)-1; + UINT index; + UINT bitMask; + UINT bitChunkCnt = (bitVectSize - 1) / bitChunkSize() + 1; + UINT i; + + if (bitNumPrev == (UINT)-1) + { + index = 0; + bitMask = (UINT)-1; + } + else + { + UINT bit; + + index = bitNumPrev / bitChunkSize(); + bitNumPrev -= index * bitChunkSize(); + bit = bitNumToBit(bitNumPrev); + bitMask = ~(bit | (bit - 1)); + } + + // Find first bit + for (i = index; i < bitChunkCnt; i++) + { + UINT bitChunk = bitVect[i] & bitMask; + + if (bitChunk != 0) + { + BitScanForward((ULONG*)&bitNum, bitChunk); + break; + } + + bitMask = 0xFFFFFFFF; + } + + // Empty bit vector? + if (bitNum == (UINT)-1) + { + return (UINT)-1; + } + + bitNum += i * bitChunkSize(); + + assert(bitNum <= bitVectSize); + + return bitNum; +} + +// bitVectGetNextAndClear() - Find the first bit on, clear it and return it. +// Return -1 if no bits found. +UINT FixedBitVect::bitVectGetNextAndClear() +{ + UINT bitNum = (UINT)-1; + UINT bitChunkCnt = (bitVectSize - 1) / bitChunkSize() + 1; + UINT i; + + // Find first bit + for (i = 0; i < bitChunkCnt; i++) + { + if (bitVect[i] != 0) + { + BitScanForward((ULONG*)&bitNum, bitVect[i]); + break; + } + } + + // Empty bit vector? + if (bitNum == (UINT)-1) + { + return (UINT)-1; + } + + // Clear the bit in the right chunk + bitVect[i] &= ~bitNumToBit(bitNum); + + bitNum += i * bitChunkSize(); + + assert(bitNum <= bitVectSize); + + return bitNum; +} + +int SimpleSprintf_s(__in_ecount(cbBufSize - (pWriteStart - pBufStart)) char* pWriteStart, + __in_ecount(cbBufSize) char* pBufStart, + size_t cbBufSize, + __in_z const char* fmt, + ...) +{ + assert(fmt); + assert(pBufStart); + assert(pWriteStart); + assert((size_t)pBufStart <= (size_t)pWriteStart); + int ret; + + // compute the space left in the buffer. + if ((pBufStart + cbBufSize) < pWriteStart) + { + NO_WAY("pWriteStart is past end of buffer"); + } + size_t cbSpaceLeft = (size_t)((pBufStart + cbBufSize) - pWriteStart); + va_list args; + va_start(args, fmt); + ret = vsprintf_s(pWriteStart, cbSpaceLeft, const_cast<char*>(fmt), args); + va_end(args); + if (ret < 0) + { + NO_WAY("vsprintf_s failed."); + } + return ret; +} + +#ifdef DEBUG + +void hexDump(FILE* dmpf, const char* name, BYTE* addr, size_t size) +{ + if (!size) + { + return; + } + + assert(addr); + + fprintf(dmpf, "Hex dump of %s:\n", name); + + for (unsigned i = 0; i < size; i++) + { + if ((i % 16) == 0) + { + fprintf(dmpf, "\n %04X: ", i); + } + + fprintf(dmpf, "%02X ", *addr++); + } + + fprintf(dmpf, "\n\n"); +} + +#endif // DEBUG + +void HelperCallProperties::init() +{ + for (CorInfoHelpFunc helper = CORINFO_HELP_UNDEF; // initialize helper + (helper < CORINFO_HELP_COUNT); // test helper for loop exit + helper = CorInfoHelpFunc(int(helper) + 1)) // update helper to next + { + // Generally you want initialize these to their most typical/safest result + // + bool isPure = false; // true if the result only depends upon input args and not any global state + bool noThrow = false; // true if the helper will never throw + bool nonNullReturn = false; // true if the result will never be null or zero + bool isAllocator = false; // true if the result is usually a newly created heap item, or may throw OutOfMemory + bool mutatesHeap = false; // true if any previous heap objects [are|can be] modified + bool mayRunCctor = false; // true if the helper call may cause a static constructor to be run. + bool mayFinalize = false; // true if the helper call allocates an object that may need to run a finalizer + + switch (helper) + { + // Arithmetic helpers that cannot throw + case CORINFO_HELP_LLSH: + case CORINFO_HELP_LRSH: + case CORINFO_HELP_LRSZ: + case CORINFO_HELP_LMUL: + case CORINFO_HELP_LNG2DBL: + case CORINFO_HELP_ULNG2DBL: + case CORINFO_HELP_DBL2INT: + case CORINFO_HELP_DBL2LNG: + case CORINFO_HELP_DBL2UINT: + case CORINFO_HELP_DBL2ULNG: + case CORINFO_HELP_FLTREM: + case CORINFO_HELP_DBLREM: + case CORINFO_HELP_FLTROUND: + case CORINFO_HELP_DBLROUND: + + isPure = true; + noThrow = true; + break; + + // Arithmetic helpers that *can* throw. + + // This (or these) are not pure, in that they have "VM side effects"...but they don't mutate the heap. + case CORINFO_HELP_ENDCATCH: + break; + + // Arithmetic helpers that may throw + case CORINFO_HELP_LMOD: // Mods throw div-by zero, and signed mods have problems with the smallest integer + // mod -1, + case CORINFO_HELP_MOD: // which is not representable as a positive integer. + case CORINFO_HELP_UMOD: + case CORINFO_HELP_ULMOD: + + case CORINFO_HELP_UDIV: // Divs throw divide-by-zero. + case CORINFO_HELP_DIV: + case CORINFO_HELP_LDIV: + case CORINFO_HELP_ULDIV: + + case CORINFO_HELP_LMUL_OVF: + case CORINFO_HELP_ULMUL_OVF: + case CORINFO_HELP_DBL2INT_OVF: + case CORINFO_HELP_DBL2LNG_OVF: + case CORINFO_HELP_DBL2UINT_OVF: + case CORINFO_HELP_DBL2ULNG_OVF: + + isPure = true; + break; + + // Heap Allocation helpers, these all never return null + case CORINFO_HELP_NEWSFAST: + case CORINFO_HELP_NEWSFAST_ALIGN8: + + isAllocator = true; + nonNullReturn = true; + noThrow = true; // only can throw OutOfMemory + break; + + case CORINFO_HELP_NEW_CROSSCONTEXT: + case CORINFO_HELP_NEWFAST: + case CORINFO_HELP_READYTORUN_NEW: + + mayFinalize = true; // These may run a finalizer + isAllocator = true; + nonNullReturn = true; + noThrow = true; // only can throw OutOfMemory + break; + + // These allocation helpers do some checks on the size (and lower bound) inputs, + // and can throw exceptions other than OOM. + case CORINFO_HELP_NEWARR_1_VC: + case CORINFO_HELP_NEWARR_1_ALIGN8: + + isAllocator = true; + nonNullReturn = true; + break; + + // These allocation helpers do some checks on the size (and lower bound) inputs, + // and can throw exceptions other than OOM. + case CORINFO_HELP_NEW_MDARR: + case CORINFO_HELP_NEWARR_1_DIRECT: + case CORINFO_HELP_NEWARR_1_OBJ: + case CORINFO_HELP_READYTORUN_NEWARR_1: + + mayFinalize = true; // These may run a finalizer + isAllocator = true; + nonNullReturn = true; + break; + + // Heap Allocation helpers that are also pure + case CORINFO_HELP_STRCNS: + + isPure = true; + isAllocator = true; + nonNullReturn = true; + noThrow = true; // only can throw OutOfMemory + break; + + case CORINFO_HELP_BOX: + nonNullReturn = true; + isAllocator = true; + noThrow = true; // only can throw OutOfMemory + break; + + case CORINFO_HELP_BOX_NULLABLE: + // Box Nullable is not a 'pure' function + // It has a Byref argument that it reads the contents of. + // + // So two calls to Box Nullable that pass the same address (with the same Value Number) + // will produce different results when the contents of the memory pointed to by the Byref changes + // + isAllocator = true; + noThrow = true; // only can throw OutOfMemory + break; + + case CORINFO_HELP_RUNTIMEHANDLE_METHOD: + case CORINFO_HELP_RUNTIMEHANDLE_CLASS: + case CORINFO_HELP_RUNTIMEHANDLE_METHOD_LOG: + case CORINFO_HELP_RUNTIMEHANDLE_CLASS_LOG: + // logging helpers are not technically pure but can be optimized away + isPure = true; + noThrow = true; + nonNullReturn = true; + break; + + // type casting helpers + case CORINFO_HELP_ISINSTANCEOFINTERFACE: + case CORINFO_HELP_ISINSTANCEOFARRAY: + case CORINFO_HELP_ISINSTANCEOFCLASS: + case CORINFO_HELP_ISINSTANCEOFANY: + case CORINFO_HELP_READYTORUN_ISINSTANCEOF: + + isPure = true; + noThrow = true; // These return null for a failing cast + break; + + // type casting helpers that throw + case CORINFO_HELP_CHKCASTINTERFACE: + case CORINFO_HELP_CHKCASTARRAY: + case CORINFO_HELP_CHKCASTCLASS: + case CORINFO_HELP_CHKCASTANY: + case CORINFO_HELP_CHKCASTCLASS_SPECIAL: + case CORINFO_HELP_READYTORUN_CHKCAST: + + // These throw for a failing cast + // But if given a null input arg will return null + isPure = true; + break; + + // helpers returning addresses, these can also throw + case CORINFO_HELP_UNBOX: + case CORINFO_HELP_GETREFANY: + case CORINFO_HELP_LDELEMA_REF: + + isPure = true; + break; + + // helpers that return internal handle + // TODO-ARM64-Bug?: Can these throw or not? + case CORINFO_HELP_GETCLASSFROMMETHODPARAM: + case CORINFO_HELP_GETSYNCFROMCLASSHANDLE: + + isPure = true; + break; + + // Helpers that load the base address for static variables. + // We divide these between those that may and may not invoke + // static class constructors. + case CORINFO_HELP_GETSHARED_GCSTATIC_BASE: + case CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE: + case CORINFO_HELP_GETSHARED_GCSTATIC_BASE_DYNAMICCLASS: + case CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE_DYNAMICCLASS: + case CORINFO_HELP_GETGENERICS_GCTHREADSTATIC_BASE: + case CORINFO_HELP_GETGENERICS_NONGCTHREADSTATIC_BASE: + case CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE: + case CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE: + case CORINFO_HELP_CLASSINIT_SHARED_DYNAMICCLASS: + case CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE_DYNAMICCLASS: + case CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE_DYNAMICCLASS: + case CORINFO_HELP_GETSTATICFIELDADDR_CONTEXT: + case CORINFO_HELP_GETSTATICFIELDADDR_TLS: + case CORINFO_HELP_GETGENERICS_GCSTATIC_BASE: + case CORINFO_HELP_GETGENERICS_NONGCSTATIC_BASE: + case CORINFO_HELP_READYTORUN_STATIC_BASE: + + // These may invoke static class constructors + // These can throw InvalidProgram exception if the class can not be constructed + // + isPure = true; + nonNullReturn = true; + mayRunCctor = true; + break; + + case CORINFO_HELP_GETSHARED_GCSTATIC_BASE_NOCTOR: + case CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE_NOCTOR: + case CORINFO_HELP_GETSHARED_GCTHREADSTATIC_BASE_NOCTOR: + case CORINFO_HELP_GETSHARED_NONGCTHREADSTATIC_BASE_NOCTOR: + + // These do not invoke static class constructors + // + isPure = true; + noThrow = true; + nonNullReturn = true; + break; + + // GC Write barrier support + // TODO-ARM64-Bug?: Can these throw or not? + case CORINFO_HELP_ASSIGN_REF: + case CORINFO_HELP_CHECKED_ASSIGN_REF: + case CORINFO_HELP_ASSIGN_REF_ENSURE_NONHEAP: + case CORINFO_HELP_ASSIGN_BYREF: + case CORINFO_HELP_ASSIGN_STRUCT: + + mutatesHeap = true; + break; + + // Accessing fields (write) + case CORINFO_HELP_SETFIELD32: + case CORINFO_HELP_SETFIELD64: + case CORINFO_HELP_SETFIELDOBJ: + case CORINFO_HELP_SETFIELDSTRUCT: + case CORINFO_HELP_SETFIELDFLOAT: + case CORINFO_HELP_SETFIELDDOUBLE: + case CORINFO_HELP_ARRADDR_ST: + + mutatesHeap = true; + break; + + // These helper calls always throw an exception + case CORINFO_HELP_OVERFLOW: + case CORINFO_HELP_VERIFICATION: + case CORINFO_HELP_RNGCHKFAIL: + case CORINFO_HELP_THROWDIVZERO: +#if COR_JIT_EE_VERSION > 460 + case CORINFO_HELP_THROWNULLREF: +#endif // COR_JIT_EE_VERSION + case CORINFO_HELP_THROW: + case CORINFO_HELP_RETHROW: + + break; + + // These helper calls may throw an exception + case CORINFO_HELP_METHOD_ACCESS_CHECK: + case CORINFO_HELP_FIELD_ACCESS_CHECK: + case CORINFO_HELP_CLASS_ACCESS_CHECK: + case CORINFO_HELP_DELEGATE_SECURITY_CHECK: + + break; + + // This is a debugging aid; it simply returns a constant address. + case CORINFO_HELP_LOOP_CLONE_CHOICE_ADDR: + isPure = true; + noThrow = true; + break; + + // Not sure how to handle optimization involving the rest of these helpers + default: + + // The most pessimistic results are returned for these helpers + mutatesHeap = true; + break; + } + + m_isPure[helper] = isPure; + m_noThrow[helper] = noThrow; + m_nonNullReturn[helper] = nonNullReturn; + m_isAllocator[helper] = isAllocator; + m_mutatesHeap[helper] = mutatesHeap; + m_mayRunCctor[helper] = mayRunCctor; + m_mayFinalize[helper] = mayFinalize; + } +} + +//============================================================================= +// AssemblyNamesList2 +//============================================================================= +// The string should be of the form +// MyAssembly +// MyAssembly;mscorlib;System +// MyAssembly;mscorlib System + +AssemblyNamesList2::AssemblyNamesList2(const wchar_t* list, IAllocator* alloc) : m_alloc(alloc) +{ + assert(m_alloc != nullptr); + + WCHAR prevChar = '?'; // dummy + LPWSTR nameStart = nullptr; // start of the name currently being processed. nullptr if no current name + AssemblyName** ppPrevLink = &m_pNames; + + for (LPWSTR listWalk = const_cast<LPWSTR>(list); prevChar != '\0'; prevChar = *listWalk, listWalk++) + { + WCHAR curChar = *listWalk; + + if (iswspace(curChar) || curChar == W(';') || curChar == W('\0')) + { + // + // Found white-space + // + + if (nameStart) + { + // Found the end of the current name; add a new assembly name to the list. + + AssemblyName* newName = new (m_alloc) AssemblyName(); + + // Null out the current character so we can do zero-terminated string work; we'll restore it later. + *listWalk = W('\0'); + + // How much space do we need? + int convertedNameLenBytes = + WszWideCharToMultiByte(CP_UTF8, 0, nameStart, -1, nullptr, 0, nullptr, nullptr); + newName->m_assemblyName = new (m_alloc) char[convertedNameLenBytes]; // convertedNameLenBytes includes + // the trailing null character + if (WszWideCharToMultiByte(CP_UTF8, 0, nameStart, -1, newName->m_assemblyName, convertedNameLenBytes, + nullptr, nullptr) != 0) + { + *ppPrevLink = newName; + ppPrevLink = &newName->m_next; + } + else + { + // Failed to convert the string. Ignore this string (and leak the memory). + } + + nameStart = nullptr; + + // Restore the current character. + *listWalk = curChar; + } + } + else if (!nameStart) + { + // + // Found the start of a new name + // + + nameStart = listWalk; + } + } + + assert(nameStart == nullptr); // cannot be in the middle of a name + *ppPrevLink = nullptr; // Terminate the last element of the list. +} + +AssemblyNamesList2::~AssemblyNamesList2() +{ + for (AssemblyName* pName = m_pNames; pName != nullptr; /**/) + { + AssemblyName* cur = pName; + pName = pName->m_next; + + m_alloc->Free(cur->m_assemblyName); + m_alloc->Free(cur); + } +} + +bool AssemblyNamesList2::IsInList(const char* assemblyName) +{ + for (AssemblyName* pName = m_pNames; pName != nullptr; pName = pName->m_next) + { + if (_stricmp(pName->m_assemblyName, assemblyName) == 0) + { + return true; + } + } + + return false; +} + +#ifdef FEATURE_JIT_METHOD_PERF +CycleCount::CycleCount() : cps(CycleTimer::CyclesPerSecond()) +{ +} + +bool CycleCount::GetCycles(unsigned __int64* time) +{ + return CycleTimer::GetThreadCyclesS(time); +} + +bool CycleCount::Start() +{ + return GetCycles(&beginCycles); +} + +double CycleCount::ElapsedTime() +{ + unsigned __int64 nowCycles; + (void)GetCycles(&nowCycles); + return ((double)(nowCycles - beginCycles) / cps) * 1000.0; +} + +bool PerfCounter::Start() +{ + bool result = QueryPerformanceFrequency(&beg) != 0; + if (!result) + { + return result; + } + freq = (double)beg.QuadPart / 1000.0; + (void)QueryPerformanceCounter(&beg); + return result; +} + +// Return elapsed time from Start() in millis. +double PerfCounter::ElapsedTime() +{ + LARGE_INTEGER li; + (void)QueryPerformanceCounter(&li); + return (double)(li.QuadPart - beg.QuadPart) / freq; +} + +#endif + +#ifdef DEBUG + +/***************************************************************************** + * Return the number of digits in a number of the given base (default base 10). + * Used when outputting strings. + */ +unsigned CountDigits(unsigned num, unsigned base /* = 10 */) +{ + assert(2 <= base && base <= 16); // sanity check + unsigned count = 1; + while (num >= base) + { + num /= base; + ++count; + } + return count; +} + +#endif // DEBUG + +double FloatingPointUtils::convertUInt64ToDouble(unsigned __int64 uIntVal) +{ + __int64 s64 = uIntVal; + double d; + if (s64 < 0) + { +#if defined(_TARGET_XARCH_) + // RyuJIT codegen and clang (or gcc) may produce different results for casting uint64 to + // double, and the clang result is more accurate. For example, + // 1) (double)0x84595161401484A0UL --> 43e08b2a2c280290 (RyuJIT codegen or VC++) + // 2) (double)0x84595161401484A0UL --> 43e08b2a2c280291 (clang or gcc) + // If the folding optimization below is implemented by simple casting of (double)uint64_val + // and it is compiled by clang, casting result can be inconsistent, depending on whether + // the folding optimization is triggered or the codegen generates instructions for casting. // + // The current solution is to force the same math as the codegen does, so that casting + // result is always consistent. + + // d = (double)(int64_t)uint64 + 0x1p64 + uint64_t adjHex = 0x43F0000000000000UL; + d = (double)s64 + *(double*)&adjHex; +#else + d = (double)uIntVal; +#endif + } + else + { + d = (double)uIntVal; + } + return d; +} + +float FloatingPointUtils::convertUInt64ToFloat(unsigned __int64 u64) +{ + double d = convertUInt64ToDouble(u64); + return (float)d; +} + +unsigned __int64 FloatingPointUtils::convertDoubleToUInt64(double d) +{ + unsigned __int64 u64; + if (d >= 0.0) + { + // Work around a C++ issue where it doesn't properly convert large positive doubles + const double two63 = 2147483648.0 * 4294967296.0; + if (d < two63) + { + u64 = UINT64(d); + } + else + { + // subtract 0x8000000000000000, do the convert then add it back again + u64 = INT64(d - two63) + I64(0x8000000000000000); + } + return u64; + } + +#ifdef _TARGET_XARCH_ + + // While the Ecma spec does not specifically call this out, + // the case of conversion from negative double to unsigned integer is + // effectively an overflow and therefore the result is unspecified. + // With MSVC for x86/x64, such a conversion results in the bit-equivalent + // unsigned value of the conversion to integer. Other compilers convert + // negative doubles to zero when the target is unsigned. + // To make the behavior consistent across OS's on TARGET_XARCH, + // this double cast is needed to conform MSVC behavior. + + u64 = UINT64(INT64(d)); +#else + u64 = UINT64(d); +#endif // _TARGET_XARCH_ + + return u64; +} + +// Rounds a double-precision floating-point value to the nearest integer, +// and rounds midpoint values to the nearest even number. +// Note this should align with classlib in floatdouble.cpp +// Specializing for x86 using a x87 instruction is optional since +// this outcome is identical across targets. +double FloatingPointUtils::round(double x) +{ + // If the number has no fractional part do nothing + // This shortcut is necessary to workaround precision loss in borderline cases on some platforms + if (x == ((double)((__int64)x))) + { + return x; + } + + // We had a number that was equally close to 2 integers. + // We need to return the even one. + + double tempVal = (x + 0.5); + double flrTempVal = floor(tempVal); + + if ((flrTempVal == tempVal) && (fmod(tempVal, 2.0) != 0)) + { + flrTempVal -= 1.0; + } + + return _copysign(flrTempVal, x); +} |