Improve the speed of using COMPlus_JitTimeLogCsv (#49798)
[platform/upstream/dotnet/runtime.git] / src / coreclr / jit / utils.h
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3
4 /*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
5 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
6 XX                                                                           XX
7 XX                                  Utils.h                                  XX
8 XX                                                                           XX
9 XX   Has miscellaneous utility functions                                     XX
10 XX                                                                           XX
11 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
12 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
13 */
14
15 #ifndef _UTILS_H_
16 #define _UTILS_H_
17
18 #include "iallocator.h"
19 #include "hostallocator.h"
20 #include "cycletimer.h"
21
22 // Needed for unreached()
23 #include "error.h"
24
25 #ifdef TARGET_64BIT
26 #define BitScanForwardPtr BitScanForward64
27 #else
28 #define BitScanForwardPtr BitScanForward
29 #endif
30
31 template <typename T, int size>
32 unsigned ArrLen(T (&)[size])
33 {
34     return size;
35 }
36
37 // return true if arg is a power of 2
38 template <typename T>
39 inline bool isPow2(T i)
40 {
41     return (i > 0 && ((i - 1) & i) == 0);
42 }
43
44 // Adapter for iterators to a type that is compatible with C++11
45 // range-based for loops.
46 template <typename TIterator>
47 class IteratorPair
48 {
49     TIterator m_begin;
50     TIterator m_end;
51
52 public:
53     IteratorPair(TIterator begin, TIterator end) : m_begin(begin), m_end(end)
54     {
55     }
56
57     inline TIterator begin()
58     {
59         return m_begin;
60     }
61
62     inline TIterator end()
63     {
64         return m_end;
65     }
66 };
67
68 template <typename TIterator>
69 inline IteratorPair<TIterator> MakeIteratorPair(TIterator begin, TIterator end)
70 {
71     return IteratorPair<TIterator>(begin, end);
72 }
73
74 // Recursive template definition to calculate the base-2 logarithm
75 // of a constant value.
76 template <unsigned val, unsigned acc = 0>
77 struct ConstLog2
78 {
79     enum
80     {
81         value = ConstLog2<val / 2, acc + 1>::value
82     };
83 };
84
85 template <unsigned acc>
86 struct ConstLog2<0, acc>
87 {
88     enum
89     {
90         value = acc
91     };
92 };
93
94 template <unsigned acc>
95 struct ConstLog2<1, acc>
96 {
97     enum
98     {
99         value = acc
100     };
101 };
102
103 inline const char* dspBool(bool b)
104 {
105     return (b) ? "true" : "false";
106 }
107
108 template <typename T>
109 int signum(T val)
110 {
111     if (val < T(0))
112     {
113         return -1;
114     }
115     else if (val > T(0))
116     {
117         return 1;
118     }
119     else
120     {
121         return 0;
122     }
123 }
124
125 #if defined(DEBUG) || defined(INLINE_DATA)
126
127 // ConfigMethodRange describes a set of methods, specified via their
128 // hash codes. This can be used for binary search and/or specifying an
129 // explicit method set.
130 //
131 // Note method hash codes are not necessarily unique. For instance
132 // many IL stubs may have the same hash.
133 //
134 // If range string is null or just whitespace, range includes all
135 // methods.
136 //
137 // Parses values as decimal numbers.
138 //
139 // Examples:
140 //
141 //  [string with just spaces] : all methods
142 //                   12345678 : a single method
143 //          12345678-23456789 : a range of methods
144 // 99998888 12345678-23456789 : a range of methods plus a single method
145
146 class ConfigMethodRange
147 {
148
149 public:
150     // Default capacity
151     enum
152     {
153         DEFAULT_CAPACITY = 50
154     };
155
156     // Does the range include this hash?
157     bool Contains(unsigned hash);
158
159     // Ensure the range string has been parsed.
160     void EnsureInit(const WCHAR* rangeStr, unsigned capacity = DEFAULT_CAPACITY)
161     {
162         // Make sure that the memory was zero initialized
163         assert(m_inited == 0 || m_inited == 1);
164
165         if (!m_inited)
166         {
167             InitRanges(rangeStr, capacity);
168             assert(m_inited == 1);
169         }
170     }
171
172     bool IsEmpty() const
173     {
174         return m_lastRange == 0;
175     }
176
177     // Error checks
178     bool Error() const
179     {
180         return m_badChar != 0;
181     }
182
183     size_t BadCharIndex() const
184     {
185         return m_badChar - 1;
186     }
187
188     void Dump();
189
190 private:
191     struct Range
192     {
193         unsigned m_low;
194         unsigned m_high;
195     };
196
197     void InitRanges(const WCHAR* rangeStr, unsigned capacity);
198
199     unsigned m_entries;   // number of entries in the range array
200     unsigned m_lastRange; // count of low-high pairs
201     unsigned m_inited;    // 1 if range string has been parsed
202     size_t   m_badChar;   // index + 1 of any bad character in range string
203     Range*   m_ranges;    // ranges of functions to include
204 };
205
206 #endif // defined(DEBUG) || defined(INLINE_DATA)
207
208 class Compiler;
209
210 /*****************************************************************************
211  * Fixed bit vector class
212  */
213 class FixedBitVect
214 {
215 private:
216     UINT bitVectSize;
217     UINT bitVect[];
218
219     // bitChunkSize() - Returns number of bits in a bitVect chunk
220     static UINT bitChunkSize();
221
222     // bitNumToBit() - Returns a bit mask of the given bit number
223     static UINT bitNumToBit(UINT bitNum);
224
225 public:
226     // bitVectInit() - Initializes a bit vector of a given size
227     static FixedBitVect* bitVectInit(UINT size, Compiler* comp);
228
229     // bitVectSet() - Sets the given bit
230     void bitVectSet(UINT bitNum);
231
232     // bitVectTest() - Tests the given bit
233     bool bitVectTest(UINT bitNum);
234
235     // bitVectOr() - Or in the given bit vector
236     void bitVectOr(FixedBitVect* bv);
237
238     // bitVectAnd() - And with passed in bit vector
239     void bitVectAnd(FixedBitVect& bv);
240
241     // bitVectGetFirst() - Find the first bit on and return the bit num.
242     //                    Return -1 if no bits found.
243     UINT bitVectGetFirst();
244
245     // bitVectGetNext() - Find the next bit on given previous bit and return bit num.
246     //                    Return -1 if no bits found.
247     UINT bitVectGetNext(UINT bitNumPrev);
248
249     // bitVectGetNextAndClear() - Find the first bit on, clear it and return it.
250     //                            Return -1 if no bits found.
251     UINT bitVectGetNextAndClear();
252 };
253
254 /******************************************************************************
255  * A specialized version of sprintf_s to simplify conversion to SecureCRT
256  *
257  * pWriteStart -> A pointer to the first byte to which data is written.
258  * pBufStart -> the start of the buffer into which the data is written.  If
259  *              composing a complex string with multiple calls to sprintf, this
260  *              should not change.
261  * cbBufSize -> The size of the overall buffer (i.e. the size of the buffer
262  *              pointed to by pBufStart).  For subsequent calls, this does not
263  *              change.
264  * fmt -> The format string
265  * ... -> Arguments.
266  *
267  * returns -> number of bytes successfully written, not including the null
268  *            terminator.  Calls NO_WAY on error.
269  */
270 int SimpleSprintf_s(__in_ecount(cbBufSize - (pWriteStart - pBufStart)) char* pWriteStart,
271                     __in_ecount(cbBufSize) char*                             pBufStart,
272                     size_t                                                   cbBufSize,
273                     __in_z const char*                                       fmt,
274                     ...);
275
276 #ifdef DEBUG
277 void hexDump(FILE* dmpf, const char* name, BYTE* addr, size_t size);
278 #endif // DEBUG
279
280 /******************************************************************************
281  * ScopedSetVariable: A simple class to set and restore a variable within a scope.
282  * For example, it can be used to set a 'bool' flag to 'true' at the beginning of a
283  * function and automatically back to 'false' either at the end the function, or at
284  * any other return location. The variable should not be changed during the scope:
285  * the destructor asserts that the value at destruction time is the same one we set.
286  * Usage: ScopedSetVariable<bool> _unused_name(&variable, true);
287  */
288 template <typename T>
289 class ScopedSetVariable
290 {
291 public:
292     ScopedSetVariable(T* pVariable, T value) : m_pVariable(pVariable)
293     {
294         m_oldValue   = *m_pVariable;
295         *m_pVariable = value;
296         INDEBUG(m_value = value;)
297     }
298
299     ~ScopedSetVariable()
300     {
301         assert(*m_pVariable == m_value); // Assert that the value didn't change between ctor and dtor
302         *m_pVariable = m_oldValue;
303     }
304
305 private:
306 #ifdef DEBUG
307     T m_value;      // The value we set the variable to (used for assert).
308 #endif              // DEBUG
309     T  m_oldValue;  // The old value, to restore the variable to.
310     T* m_pVariable; // Address of the variable to change
311 };
312
313 /******************************************************************************
314  * PhasedVar: A class to represent a variable that has phases, in particular,
315  * a write phase where the variable is computed, and a read phase where the
316  * variable is used. Once the variable has been read, it can no longer be changed.
317  * Reading the variable essentially commits everyone to using that value forever,
318  * and it is assumed that subsequent changes to the variable would invalidate
319  * whatever assumptions were made by the previous readers, leading to bad generated code.
320  * These assumptions are asserted in DEBUG builds.
321  * The phase ordering is clean for AMD64, but not for x86/ARM. So don't do the phase
322  * ordering asserts for those platforms.
323  */
324 template <typename T>
325 class PhasedVar
326 {
327 public:
328     PhasedVar()
329 #ifdef DEBUG
330         : m_initialized(false), m_writePhase(true)
331 #endif // DEBUG
332     {
333     }
334
335     PhasedVar(T value)
336         : m_value(value)
337 #ifdef DEBUG
338         , m_initialized(true)
339         , m_writePhase(true)
340 #endif // DEBUG
341     {
342     }
343
344     ~PhasedVar()
345     {
346 #ifdef DEBUG
347         m_initialized = false;
348         m_writePhase  = true;
349 #endif // DEBUG
350     }
351
352     // Read the value. Change to the read phase.
353     // Marked 'const' because we don't change the encapsulated value, even though
354     // we do change the write phase, which is only for debugging asserts.
355
356     operator T() const
357     {
358 #ifdef DEBUG
359         assert(m_initialized);
360         (const_cast<PhasedVar*>(this))->m_writePhase = false;
361 #endif // DEBUG
362         return m_value;
363     }
364
365     // Mark the value as read only; explicitly change the variable to the "read" phase.
366     void MarkAsReadOnly() const
367     {
368 #ifdef DEBUG
369         assert(m_initialized);
370         (const_cast<PhasedVar*>(this))->m_writePhase = false;
371 #endif // DEBUG
372     }
373
374     // When dumping stuff we could try to read a PhasedVariable
375     // This method tells us whether we should read the PhasedVariable
376     bool HasFinalValue() const
377     {
378 #ifdef DEBUG
379         return (const_cast<PhasedVar*>(this))->m_writePhase == false;
380 #else
381         return true;
382 #endif // DEBUG
383     }
384
385     // Functions/operators to write the value. Must be in the write phase.
386
387     PhasedVar& operator=(const T& value)
388     {
389 #ifdef DEBUG
390         assert(m_writePhase);
391         m_initialized = true;
392 #endif // DEBUG
393         m_value = value;
394         return *this;
395     }
396
397     PhasedVar& operator&=(const T& value)
398     {
399 #ifdef DEBUG
400         assert(m_writePhase);
401         m_initialized = true;
402 #endif // DEBUG
403         m_value &= value;
404         return *this;
405     }
406
407     // Note: if you need more <op>= functions, you can define them here, like operator&=
408
409     // Assign a value, but don't assert if we're not in the write phase, and
410     // don't change the phase (if we're actually in the read phase, we'll stay
411     // in the read phase). This is a dangerous function, and overrides the main
412     // benefit of this class. Use it wisely!
413     void OverrideAssign(const T& value)
414     {
415 #ifdef DEBUG
416         m_initialized = true;
417 #endif // DEBUG
418         m_value = value;
419     }
420
421     // We've decided that this variable can go back to write phase, even if it has been
422     // written. This can be used, for example, for variables set and read during frame
423     // layout calculation, as long as it is before final layout, such that anything
424     // being calculated is just an estimate anyway. Obviously, it must be used carefully,
425     // since it overrides the main benefit of this class.
426     void ResetWritePhase()
427     {
428 #ifdef DEBUG
429         m_writePhase = true;
430 #endif // DEBUG
431     }
432
433 private:
434     // Don't allow a copy constructor. (This could be allowed, but only add it once it is actually needed.)
435
436     PhasedVar(const PhasedVar& o)
437     {
438         unreached();
439     }
440
441     T m_value;
442 #ifdef DEBUG
443     bool m_initialized; // true once the variable has been initialized, that is, written once.
444     bool m_writePhase;  // true if we are in the (initial) "write" phase. Once the value is read, this changes to false,
445                         // and can't be changed back.
446 #endif                  // DEBUG
447 };
448
449 class HelperCallProperties
450 {
451 private:
452     bool m_isPure[CORINFO_HELP_COUNT];
453     bool m_noThrow[CORINFO_HELP_COUNT];
454     bool m_alwaysThrow[CORINFO_HELP_COUNT];
455     bool m_nonNullReturn[CORINFO_HELP_COUNT];
456     bool m_isAllocator[CORINFO_HELP_COUNT];
457     bool m_mutatesHeap[CORINFO_HELP_COUNT];
458     bool m_mayRunCctor[CORINFO_HELP_COUNT];
459
460     void init();
461
462 public:
463     HelperCallProperties()
464     {
465         init();
466     }
467
468     bool IsPure(CorInfoHelpFunc helperId)
469     {
470         assert(helperId > CORINFO_HELP_UNDEF);
471         assert(helperId < CORINFO_HELP_COUNT);
472         return m_isPure[helperId];
473     }
474
475     bool NoThrow(CorInfoHelpFunc helperId)
476     {
477         assert(helperId > CORINFO_HELP_UNDEF);
478         assert(helperId < CORINFO_HELP_COUNT);
479         return m_noThrow[helperId];
480     }
481
482     bool AlwaysThrow(CorInfoHelpFunc helperId)
483     {
484         assert(helperId > CORINFO_HELP_UNDEF);
485         assert(helperId < CORINFO_HELP_COUNT);
486         return m_alwaysThrow[helperId];
487     }
488
489     bool NonNullReturn(CorInfoHelpFunc helperId)
490     {
491         assert(helperId > CORINFO_HELP_UNDEF);
492         assert(helperId < CORINFO_HELP_COUNT);
493         return m_nonNullReturn[helperId];
494     }
495
496     bool IsAllocator(CorInfoHelpFunc helperId)
497     {
498         assert(helperId > CORINFO_HELP_UNDEF);
499         assert(helperId < CORINFO_HELP_COUNT);
500         return m_isAllocator[helperId];
501     }
502
503     bool MutatesHeap(CorInfoHelpFunc helperId)
504     {
505         assert(helperId > CORINFO_HELP_UNDEF);
506         assert(helperId < CORINFO_HELP_COUNT);
507         return m_mutatesHeap[helperId];
508     }
509
510     bool MayRunCctor(CorInfoHelpFunc helperId)
511     {
512         assert(helperId > CORINFO_HELP_UNDEF);
513         assert(helperId < CORINFO_HELP_COUNT);
514         return m_mayRunCctor[helperId];
515     }
516 };
517
518 //*****************************************************************************
519 // AssemblyNamesList2: Parses and stores a list of Assembly names, and provides
520 // a function for determining whether a given assembly name is part of the list.
521 //
522 // This is a clone of the AssemblyNamesList class that exists in the VM's utilcode,
523 // modified to use the JIT's memory allocator and throw on out of memory behavior.
524 // It is named AssemblyNamesList2 to avoid a name conflict with the VM version.
525 // It might be preferable to adapt the VM's code to be more flexible (for example,
526 // by using an IAllocator), but the string handling code there is heavily macroized,
527 // and for the small usage we have of this class, investing in genericizing the VM
528 // implementation didn't seem worth it.
529 //*****************************************************************************
530
531 class AssemblyNamesList2
532 {
533     struct AssemblyName
534     {
535         char*         m_assemblyName;
536         AssemblyName* m_next;
537     };
538
539     AssemblyName* m_pNames; // List of names
540     HostAllocator m_alloc;  // HostAllocator to use in this class
541
542 public:
543     // Take a Unicode string list of assembly names, parse it, and store it.
544     AssemblyNamesList2(const WCHAR* list, HostAllocator alloc);
545
546     ~AssemblyNamesList2();
547
548     // Return 'true' if 'assemblyName' (in UTF-8 format) is in the stored list of assembly names.
549     bool IsInList(const char* assemblyName);
550
551     // Return 'true' if the assembly name list is empty.
552     bool IsEmpty()
553     {
554         return m_pNames == nullptr;
555     }
556 };
557
558 // MethodSet: Manage a list of methods that is read from a file.
559 //
560 // Methods are approximately in the format output by JitFunctionTrace, e.g.:
561 //
562 //     System.CLRConfig:GetBoolValue(ref,byref):bool (MethodHash=3c54d35e)
563 //       -- use the MethodHash, not the method name
564 //
565 //     System.CLRConfig:GetBoolValue(ref,byref):bool
566 //       -- use just the name
567 //
568 // Method names should not have any leading whitespace.
569 //
570 // TODO: Should this be more related to JitConfigValues::MethodSet?
571 //
572 class MethodSet
573 {
574     // TODO: use a hash table? or two: one on hash value, one on function name
575     struct MethodInfo
576     {
577         char*       m_MethodName;
578         int         m_MethodHash;
579         MethodInfo* m_next;
580
581         MethodInfo(char* methodName, int methodHash)
582             : m_MethodName(methodName), m_MethodHash(methodHash), m_next(nullptr)
583         {
584         }
585     };
586
587     MethodInfo*   m_pInfos; // List of function info
588     HostAllocator m_alloc;  // HostAllocator to use in this class
589
590 public:
591     // Take a Unicode string with the filename containing a list of function names, parse it, and store it.
592     MethodSet(const WCHAR* filename, HostAllocator alloc);
593
594     ~MethodSet();
595
596     // Return 'true' if 'functionName' (in UTF-8 format) is in the stored set of assembly names.
597     bool IsInSet(const char* functionName);
598
599     // Return 'true' if 'functionHash' (in UTF-8 format) is in the stored set of assembly names.
600     bool IsInSet(int functionHash);
601
602     // Return 'true' if this method is active. Prefer non-zero methodHash for check over (non-null) methodName.
603     bool IsActiveMethod(const char* methodName, int methodHash);
604
605     // Return 'true' if the assembly name set is empty.
606     bool IsEmpty()
607     {
608         return m_pInfos == nullptr;
609     }
610 };
611
612 #ifdef FEATURE_JIT_METHOD_PERF
613 // When Start() is called time is noted and when ElapsedTime
614 // is called we know how much time was spent in msecs.
615 //
616 class CycleCount
617 {
618 private:
619     double           cps;         // cycles per second
620     unsigned __int64 beginCycles; // cycles at stop watch construction
621 public:
622     CycleCount();
623
624     // Kick off the counter, and if re-entrant will use the latest cycles as starting point.
625     // If the method returns false, any other query yield unpredictable results.
626     bool Start();
627
628     // Return time elapsed in msecs, if Start returned true.
629     double ElapsedTime();
630
631 private:
632     // Return true if successful.
633     bool GetCycles(unsigned __int64* time);
634 };
635
636 // Uses win API QueryPerformanceCounter/QueryPerformanceFrequency.
637 class PerfCounter
638 {
639     LARGE_INTEGER beg;
640     double        freq;
641
642 public:
643     // If the method returns false, any other query yield unpredictable results.
644     bool Start();
645
646     // Return time elapsed from start in millis, if Start returned true.
647     double ElapsedTime();
648 };
649
650 #endif // FEATURE_JIT_METHOD_PERF
651
652 #ifdef DEBUG
653
654 /*****************************************************************************
655  * Return the number of digits in a number of the given base (default base 10).
656  * Used when outputting strings.
657  */
658 unsigned CountDigits(unsigned num, unsigned base = 10);
659 unsigned CountDigits(float num, unsigned base = 10);
660
661 #endif // DEBUG
662
663 /*****************************************************************************
664 * Floating point utility class
665 */
666 class FloatingPointUtils
667 {
668 public:
669     static double convertUInt64ToDouble(unsigned __int64 u64);
670
671     static float convertUInt64ToFloat(unsigned __int64 u64);
672
673     static unsigned __int64 convertDoubleToUInt64(double d);
674
675     static double round(double x);
676
677     static float round(float x);
678
679     static bool isNormal(double x);
680
681     static bool isNormal(float x);
682
683     static bool hasPreciseReciprocal(double x);
684
685     static bool hasPreciseReciprocal(float x);
686
687     static float infinite_float();
688 };
689
690 // The CLR requires that critical section locks be initialized via its ClrCreateCriticalSection API...but
691 // that can't be called until the CLR is initialized. If we have static data that we'd like to protect by a
692 // lock, and we have a statically allocated lock to protect that data, there's an issue in how to initialize
693 // that lock. We could insert an initialize call in the startup path, but one might prefer to keep the code
694 // more local. For such situations, CritSecObject solves the initialization problem, via a level of
695 // indirection. A pointer to the lock is initially null, and when we query for the lock pointer via "Val()".
696 // If the lock has not yet been allocated, this allocates one (here a leaf lock), and uses a
697 // CompareAndExchange-based lazy-initialization to update the field. If this fails, the allocated lock is
698 // destroyed. This will work as long as the first locking attempt occurs after enough CLR initialization has
699 // happened to make ClrCreateCriticalSection calls legal.
700
701 class CritSecObject
702 {
703 public:
704     CritSecObject()
705     {
706         m_pCs = nullptr;
707     }
708
709     CRITSEC_COOKIE Val()
710     {
711         if (m_pCs == nullptr)
712         {
713             // CompareExchange-based lazy init.
714             CRITSEC_COOKIE newCs    = ClrCreateCriticalSection(CrstLeafLock, CRST_DEFAULT);
715             CRITSEC_COOKIE observed = InterlockedCompareExchangeT(&m_pCs, newCs, NULL);
716             if (observed != nullptr)
717             {
718                 ClrDeleteCriticalSection(newCs);
719             }
720         }
721         return m_pCs;
722     }
723
724 private:
725     // CRITSEC_COOKIE is an opaque pointer type.
726     CRITSEC_COOKIE m_pCs;
727
728     // No copying or assignment allowed.
729     CritSecObject(const CritSecObject&) = delete;
730     CritSecObject& operator=(const CritSecObject&) = delete;
731 };
732
733 // Stack-based holder for a critial section lock.
734 // Ensures lock is released.
735
736 class CritSecHolder
737 {
738 public:
739     CritSecHolder(CritSecObject& critSec) : m_CritSec(critSec)
740     {
741         ClrEnterCriticalSection(m_CritSec.Val());
742     }
743
744     ~CritSecHolder()
745     {
746         ClrLeaveCriticalSection(m_CritSec.Val());
747     }
748
749 private:
750     CritSecObject& m_CritSec;
751
752     // No copying or assignment allowed.
753     CritSecHolder(const CritSecHolder&) = delete;
754     CritSecHolder& operator=(const CritSecHolder&) = delete;
755 };
756
757 namespace MagicDivide
758 {
759 uint32_t GetUnsigned32Magic(uint32_t d, bool* add /*out*/, int* shift /*out*/);
760 #ifdef TARGET_64BIT
761 uint64_t GetUnsigned64Magic(uint64_t d, bool* add /*out*/, int* shift /*out*/);
762 #endif
763 int32_t GetSigned32Magic(int32_t d, int* shift /*out*/);
764 #ifdef TARGET_64BIT
765 int64_t GetSigned64Magic(int64_t d, int* shift /*out*/);
766 #endif
767 }
768
769 //
770 // Profiling helpers
771 //
772
773 double CachedCyclesPerSecond();
774
775 #endif // _UTILS_H_