summaryrefslogtreecommitdiffstats
path: root/win/tclWinTime.c
diff options
context:
space:
mode:
Diffstat (limited to 'win/tclWinTime.c')
-rw-r--r--win/tclWinTime.c1029
1 files changed, 876 insertions, 153 deletions
diff --git a/win/tclWinTime.c b/win/tclWinTime.c
index db2affd..7045c72 100644
--- a/win/tclWinTime.c
+++ b/win/tclWinTime.c
@@ -1,33 +1,37 @@
-/*
+/*
* tclWinTime.c --
*
- * Contains Windows specific versions of Tcl functions that
- * obtain time values from the operating system.
+ * Contains Windows specific versions of Tcl functions that obtain time
+ * values from the operating system.
*
* Copyright 1995-1998 by Sun Microsystems, Inc.
*
- * See the file "license.terms" for information on usage and redistribution
- * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
- *
- * RCS: @(#) $Id: tclWinTime.c,v 1.5 1999/12/01 00:08:43 hobbs Exp $
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
-#include "tclWinInt.h"
+#include "tclInt.h"
+
+#define SECSPERDAY (60L * 60L * 24L)
+#define SECSPERYEAR (SECSPERDAY * 365L)
+#define SECSPER4YEAR (SECSPERYEAR * 4L + SECSPERDAY)
+
+/*
+ * Number of samples over which to estimate the performance counter.
+ */
-#define SECSPERDAY (60L * 60L * 24L)
-#define SECSPERYEAR (SECSPERDAY * 365L)
-#define SECSPER4YEAR (SECSPERYEAR * 4L + SECSPERDAY)
+#define SAMPLES 64
/*
- * The following arrays contain the day of year for the last day of
- * each month, where index 1 is January.
+ * The following arrays contain the day of year for the last day of each
+ * month, where index 1 is January.
*/
-static int normalDays[] = {
+static const int normalDays[] = {
-1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364
};
-static int leapDays[] = {
+static const int leapDays[] = {
-1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
};
@@ -38,18 +42,105 @@ typedef struct ThreadSpecificData {
static Tcl_ThreadDataKey dataKey;
/*
+ * Data for managing high-resolution timers.
+ */
+
+typedef struct TimeInfo {
+ CRITICAL_SECTION cs; /* Mutex guarding this structure. */
+ int initialized; /* Flag == 1 if this structure is
+ * initialized. */
+ int perfCounterAvailable; /* Flag == 1 if the hardware has a performance
+ * counter. */
+ HANDLE calibrationThread; /* Handle to the thread that keeps the virtual
+ * clock calibrated. */
+ HANDLE readyEvent; /* System event used to trigger the requesting
+ * thread when the clock calibration procedure
+ * is initialized for the first time. */
+ HANDLE exitEvent; /* Event to signal out of an exit handler to
+ * tell the calibration loop to terminate. */
+ LARGE_INTEGER nominalFreq; /* Nominal frequency of the system performance
+ * counter, that is, the value returned from
+ * QueryPerformanceFrequency. */
+
+ /*
+ * The following values are used for calculating virtual time. Virtual
+ * time is always equal to:
+ * lastFileTime + (current perf counter - lastCounter)
+ * * 10000000 / curCounterFreq
+ * and lastFileTime and lastCounter are updated any time that virtual time
+ * is returned to a caller.
+ */
+
+ ULARGE_INTEGER fileTimeLastCall;
+ LARGE_INTEGER perfCounterLastCall;
+ LARGE_INTEGER curCounterFreq;
+
+ /*
+ * Data used in developing the estimate of performance counter frequency
+ */
+
+ Tcl_WideUInt fileTimeSample[SAMPLES];
+ /* Last 64 samples of system time. */
+ Tcl_WideInt perfCounterSample[SAMPLES];
+ /* Last 64 samples of performance counter. */
+ int sampleNo; /* Current sample number. */
+} TimeInfo;
+
+static TimeInfo timeInfo = {
+ { NULL, 0, 0, NULL, NULL, 0 },
+ 0,
+ 0,
+ (HANDLE) NULL,
+ (HANDLE) NULL,
+ (HANDLE) NULL,
+#ifdef HAVE_CAST_TO_UNION
+ (LARGE_INTEGER) (Tcl_WideInt) 0,
+ (ULARGE_INTEGER) (DWORDLONG) 0,
+ (LARGE_INTEGER) (Tcl_WideInt) 0,
+ (LARGE_INTEGER) (Tcl_WideInt) 0,
+#else
+ 0,
+ 0,
+ 0,
+ 0,
+#endif
+ { 0 },
+ { 0 },
+ 0
+};
+
+/*
* Declarations for functions defined later in this file.
*/
-static struct tm * ComputeGMT _ANSI_ARGS_((const time_t *tp));
+static struct tm * ComputeGMT(const time_t *tp);
+static void StopCalibration(ClientData clientData);
+static DWORD WINAPI CalibrationThread(LPVOID arg);
+static void UpdateTimeEachSecond(void);
+static void ResetCounterSamples(Tcl_WideUInt fileTime,
+ Tcl_WideInt perfCounter, Tcl_WideInt perfFreq);
+static Tcl_WideInt AccumulateSample(Tcl_WideInt perfCounter,
+ Tcl_WideUInt fileTime);
+static void NativeScaleTime(Tcl_Time* timebuf,
+ ClientData clientData);
+static void NativeGetTime(Tcl_Time* timebuf,
+ ClientData clientData);
+
+/*
+ * TIP #233 (Virtualized Time): Data for the time hooks, if any.
+ */
+
+Tcl_GetTimeProc *tclGetTimeProcPtr = NativeGetTime;
+Tcl_ScaleTimeProc *tclScaleTimeProcPtr = NativeScaleTime;
+ClientData tclTimeClientData = NULL;
/*
*----------------------------------------------------------------------
*
* TclpGetSeconds --
*
- * This procedure returns the number of seconds from the epoch.
- * On most Unix systems the epoch is Midnight Jan 1, 1970 GMT.
+ * This procedure returns the number of seconds from the epoch. On most
+ * Unix systems the epoch is Midnight Jan 1, 1970 GMT.
*
* Results:
* Number of seconds from the epoch.
@@ -61,9 +152,12 @@ static struct tm * ComputeGMT _ANSI_ARGS_((const time_t *tp));
*/
unsigned long
-TclpGetSeconds()
+TclpGetSeconds(void)
{
- return (unsigned long) time((time_t *) NULL);
+ Tcl_Time t;
+
+ tclGetTimeProcPtr(&t, tclTimeClientData); /* Tcl_GetTime inlined. */
+ return t.sec;
}
/*
@@ -71,11 +165,10 @@ TclpGetSeconds()
*
* TclpGetClicks --
*
- * This procedure returns a value that represents the highest
- * resolution clock available on the system. There are no
- * guarantees on what the resolution will be. In Tcl we will
- * call this value a "click". The start time is also system
- * dependant.
+ * This procedure returns a value that represents the highest resolution
+ * clock available on the system. There are no guarantees on what the
+ * resolution will be. In Tcl we will call this value a "click". The
+ * start time is also system dependant.
*
* Results:
* Number of clicks from some start time.
@@ -87,153 +180,310 @@ TclpGetSeconds()
*/
unsigned long
-TclpGetClicks()
+TclpGetClicks(void)
{
- return GetTickCount();
+ /*
+ * Use the Tcl_GetTime abstraction to get the time in microseconds, as
+ * nearly as we can, and return it.
+ */
+
+ Tcl_Time now; /* Current Tcl time */
+ unsigned long retval; /* Value to return */
+
+ tclGetTimeProcPtr(&now, tclTimeClientData); /* Tcl_GetTime inlined */
+
+ retval = (now.sec * 1000000) + now.usec;
+ return retval;
+
}
/*
*----------------------------------------------------------------------
*
- * TclpGetTimeZone --
+ * Tcl_GetTime --
*
- * Determines the current timezone. The method varies wildly
- * between different Platform implementations, so its hidden in
- * this function.
+ * Gets the current system time in seconds and microseconds since the
+ * beginning of the epoch: 00:00 UCT, January 1, 1970.
*
* Results:
- * Minutes west of GMT.
+ * Returns the current time in timePtr.
*
* Side effects:
- * None.
+ * On the first call, initializes a set of static variables to keep track
+ * of the base value of the performance counter, the corresponding wall
+ * clock (obtained through ftime) and the frequency of the performance
+ * counter. Also spins a thread whose function is to wake up periodically
+ * and monitor these values, adjusting them as necessary to correct for
+ * drift in the performance counter's oscillator.
*
*----------------------------------------------------------------------
*/
-int
-TclpGetTimeZone (currentTime)
- unsigned long currentTime;
+void
+Tcl_GetTime(
+ Tcl_Time *timePtr) /* Location to store time information. */
{
- int timeZone;
-
- tzset();
- timeZone = _timezone / 60;
-
- return timeZone;
+ tclGetTimeProcPtr(timePtr, tclTimeClientData);
}
/*
*----------------------------------------------------------------------
*
- * TclpGetTime --
+ * NativeScaleTime --
*
- * Gets the current system time in seconds and microseconds
- * since the beginning of the epoch: 00:00 UCT, January 1, 1970.
+ * TIP #233: Scale from virtual time to the real-time. For native scaling
+ * the relationship is 1:1 and nothing has to be done.
*
* Results:
- * Returns the current time in timePtr.
+ * Scales the time in timePtr.
*
* Side effects:
- * None.
+ * See above.
*
*----------------------------------------------------------------------
*/
-void
-TclpGetTime(timePtr)
- Tcl_Time *timePtr; /* Location to store time information. */
+static void
+NativeScaleTime(
+ Tcl_Time *timePtr,
+ ClientData clientData)
{
- struct timeb t;
-
- ftime(&t);
- timePtr->sec = t.time;
- timePtr->usec = t.millitm * 1000;
+ /*
+ * Native scale is 1:1. Nothing is done.
+ */
}
/*
*----------------------------------------------------------------------
*
- * TclpGetTZName --
+ * NativeGetTime --
*
- * Gets the current timezone string.
+ * TIP #233: Gets the current system time in seconds and microseconds
+ * since the beginning of the epoch: 00:00 UCT, January 1, 1970.
*
* Results:
- * Returns a pointer to a static string, or NULL on failure.
+ * Returns the current time in timePtr.
*
* Side effects:
- * None.
+ * On the first call, initializes a set of static variables to keep track
+ * of the base value of the performance counter, the corresponding wall
+ * clock (obtained through ftime) and the frequency of the performance
+ * counter. Also spins a thread whose function is to wake up periodically
+ * and monitor these values, adjusting them as necessary to correct for
+ * drift in the performance counter's oscillator.
*
*----------------------------------------------------------------------
*/
-char *
-TclpGetTZName(int dst)
+static void
+NativeGetTime(
+ Tcl_Time *timePtr,
+ ClientData clientData)
{
- int len;
- char *zone, *p;
- TIME_ZONE_INFORMATION tz;
- Tcl_Encoding encoding;
- ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
- char *name = tsdPtr->tzName;
+ struct _timeb t;
+ int useFtime = 1; /* Flag == TRUE if we need to fall back on
+ * ftime rather than using the perf counter. */
/*
- * tzset() under Borland doesn't seem to set up tzname[] at all.
- * tzset() under MSVC has the following weird observed behavior:
- * First time we call "clock format [clock seconds] -format %Z -gmt 1"
- * we get "GMT", but on all subsequent calls we get the current time
- * zone string, even though env(TZ) is GMT and the variable _timezone
- * is 0.
+ * Initialize static storage on the first trip through.
+ *
+ * Note: Outer check for 'initialized' is a performance win since it
+ * avoids an extra mutex lock in the common case.
*/
- name[0] = '\0';
-
- zone = getenv("TZ");
- if (zone != NULL) {
- /*
- * TZ is of form "NST-4:30NDT", where "NST" would be the
- * name of the standard time zone for this area, "-4:30" is
- * the offset from GMT in hours, and "NDT is the name of
- * the daylight savings time zone in this area. The offset
- * and DST strings are optional.
- */
+ if (!timeInfo.initialized) {
+ TclpInitLock();
+ if (!timeInfo.initialized) {
+ timeInfo.perfCounterAvailable =
+ QueryPerformanceFrequency(&timeInfo.nominalFreq);
- len = strlen(zone);
- if (len > 3) {
- len = 3;
- }
- if (dst != 0) {
/*
- * Skip the offset string and get the DST string.
+ * Some hardware abstraction layers use the CPU clock in place of
+ * the real-time clock as a performance counter reference. This
+ * results in:
+ * - inconsistent results among the processors on
+ * multi-processor systems.
+ * - unpredictable changes in performance counter frequency on
+ * "gearshift" processors such as Transmeta and SpeedStep.
+ *
+ * There seems to be no way to test whether the performance
+ * counter is reliable, but a useful heuristic is that if its
+ * frequency is 1.193182 MHz or 3.579545 MHz, it's derived from a
+ * colorburst crystal and is therefore the RTC rather than the
+ * TSC.
+ *
+ * A sloppier but serviceable heuristic is that the RTC crystal is
+ * normally less than 15 MHz while the TSC crystal is virtually
+ * assured to be greater than 100 MHz. Since Win98SE appears to
+ * fiddle with the definition of the perf counter frequency
+ * (perhaps in an attempt to calibrate the clock?), we use the
+ * latter rule rather than an exact match.
+ *
+ * We also assume (perhaps questionably) that the vendors have
+ * gotten their act together on Win64, so bypass all this rubbish
+ * on that platform.
*/
- p = zone + len;
- p += strspn(p, "+-:0123456789");
- if (*p != '\0') {
- zone = p;
- len = strlen(zone);
- if (len > 3) {
- len = 3;
+#if !defined(_WIN64)
+ if (timeInfo.perfCounterAvailable
+ /*
+ * The following lines would do an exact match on crystal
+ * frequency:
+ * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt)1193182
+ * && timeInfo.nominalFreq.QuadPart != (Tcl_WideInt)3579545
+ */
+ && timeInfo.nominalFreq.QuadPart > (Tcl_WideInt) 15000000){
+ /*
+ * As an exception, if every logical processor on the system
+ * is on the same chip, we use the performance counter anyway,
+ * presuming that everyone's TSC is locked to the same
+ * oscillator.
+ */
+
+ SYSTEM_INFO systemInfo;
+ unsigned int regs[4];
+
+ GetSystemInfo(&systemInfo);
+ if (TclWinCPUID(0, regs) == TCL_OK
+ && regs[1] == 0x756e6547 /* "Genu" */
+ && regs[3] == 0x49656e69 /* "ineI" */
+ && regs[2] == 0x6c65746e /* "ntel" */
+ && TclWinCPUID(1, regs) == TCL_OK
+ && ((regs[0]&0x00000F00) == 0x00000F00 /* Pentium 4 */
+ || ((regs[0] & 0x00F00000) /* Extended family */
+ && (regs[3] & 0x10000000))) /* Hyperthread */
+ && (((regs[1]&0x00FF0000) >> 16)/* CPU count */
+ == systemInfo.dwNumberOfProcessors)) {
+ timeInfo.perfCounterAvailable = TRUE;
+ } else {
+ timeInfo.perfCounterAvailable = FALSE;
}
}
- }
- Tcl_ExternalToUtf(NULL, NULL, zone, len, 0, NULL, name,
- sizeof(tsdPtr->tzName), NULL, NULL, NULL);
- }
- if (name[0] == '\0') {
- if (GetTimeZoneInformation(&tz) == TIME_ZONE_ID_UNKNOWN) {
+#endif /* above code is Win32 only */
+
/*
- * MSDN: On NT this is returned if DST is not used in
- * the current TZ
+ * If the performance counter is available, start a thread to
+ * calibrate it.
*/
- dst = 0;
+
+ if (timeInfo.perfCounterAvailable) {
+ DWORD id;
+
+ InitializeCriticalSection(&timeInfo.cs);
+ timeInfo.readyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ timeInfo.exitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ timeInfo.calibrationThread = CreateThread(NULL, 256,
+ CalibrationThread, (LPVOID) NULL, 0, &id);
+ SetThreadPriority(timeInfo.calibrationThread,
+ THREAD_PRIORITY_HIGHEST);
+
+ /*
+ * Wait for the thread just launched to start running, and
+ * create an exit handler that kills it so that it doesn't
+ * outlive unloading tclXX.dll
+ */
+
+ WaitForSingleObject(timeInfo.readyEvent, INFINITE);
+ CloseHandle(timeInfo.readyEvent);
+ Tcl_CreateExitHandler(StopCalibration, NULL);
+ }
+ timeInfo.initialized = TRUE;
+ }
+ TclpInitUnlock();
+ }
+
+ if (timeInfo.perfCounterAvailable && timeInfo.curCounterFreq.QuadPart!=0) {
+ /*
+ * Query the performance counter and use it to calculate the current
+ * time.
+ */
+
+ LARGE_INTEGER curCounter;
+ /* Current performance counter. */
+ Tcl_WideInt curFileTime;/* Current estimated time, expressed as 100-ns
+ * ticks since the Windows epoch. */
+ static LARGE_INTEGER posixEpoch;
+ /* Posix epoch expressed as 100-ns ticks since
+ * the windows epoch. */
+ Tcl_WideInt usecSincePosixEpoch;
+ /* Current microseconds since Posix epoch. */
+
+ posixEpoch.LowPart = 0xD53E8000;
+ posixEpoch.HighPart = 0x019DB1DE;
+
+ EnterCriticalSection(&timeInfo.cs);
+
+ QueryPerformanceCounter(&curCounter);
+
+ /*
+ * If it appears to be more than 1.1 seconds since the last trip
+ * through the calibration loop, the performance counter may have
+ * jumped forward. (See MSDN Knowledge Base article Q274323 for a
+ * description of the hardware problem that makes this test
+ * necessary.) If the counter jumps, we don't want to use it directly.
+ * Instead, we must return system time. Eventually, the calibration
+ * loop should recover.
+ */
+
+ if (curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart <
+ 11 * timeInfo.curCounterFreq.QuadPart / 10) {
+ curFileTime = timeInfo.fileTimeLastCall.QuadPart +
+ ((curCounter.QuadPart - timeInfo.perfCounterLastCall.QuadPart)
+ * 10000000 / timeInfo.curCounterFreq.QuadPart);
+ timeInfo.fileTimeLastCall.QuadPart = curFileTime;
+ timeInfo.perfCounterLastCall.QuadPart = curCounter.QuadPart;
+ usecSincePosixEpoch = (curFileTime - posixEpoch.QuadPart) / 10;
+ timePtr->sec = (long) (usecSincePosixEpoch / 1000000);
+ timePtr->usec = (unsigned long) (usecSincePosixEpoch % 1000000);
+ useFtime = 0;
}
- encoding = Tcl_GetEncoding(NULL, "unicode");
- Tcl_ExternalToUtf(NULL, encoding,
- (char *) ((dst) ? tz.DaylightName : tz.StandardName), -1,
- 0, NULL, name, sizeof(tsdPtr->tzName), NULL, NULL, NULL);
- Tcl_FreeEncoding(encoding);
- }
- return name;
+
+ LeaveCriticalSection(&timeInfo.cs);
+ }
+
+ if (useFtime) {
+ /*
+ * High resolution timer is not available. Just use ftime.
+ */
+
+ _ftime(&t);
+ timePtr->sec = (long)t.time;
+ timePtr->usec = t.millitm * 1000;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * StopCalibration --
+ *
+ * Turns off the calibration thread in preparation for exiting the
+ * process.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Sets the 'exitEvent' event in the 'timeInfo' structure to ask the
+ * thread in question to exit, and waits for it to do so.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+StopCalibration(
+ ClientData unused) /* Client data is unused */
+{
+ SetEvent(timeInfo.exitEvent);
+
+ /*
+ * If Tcl_Finalize was called from DllMain, the calibration thread is in a
+ * paused state so we need to timeout and continue.
+ */
+
+ WaitForSingleObject(timeInfo.calibrationThread, 100);
+ CloseHandle(timeInfo.exitEvent);
+ CloseHandle(timeInfo.calibrationThread);
}
/*
@@ -241,9 +491,9 @@ TclpGetTZName(int dst)
*
* TclpGetDate --
*
- * This function converts between seconds and struct tm. If
- * useGMT is true, then the returned date will be in Greenwich
- * Mean Time (GMT). Otherwise, it will be in the local time zone.
+ * This function converts between seconds and struct tm. If useGMT is
+ * true, then the returned date will be in Greenwich Mean Time (GMT).
+ * Otherwise, it will be in the local time zone.
*
* Results:
* Returns a static tm structure.
@@ -255,40 +505,55 @@ TclpGetTZName(int dst)
*/
struct tm *
-TclpGetDate(t, useGMT)
- TclpTime_t t;
- int useGMT;
+TclpGetDate(
+ const time_t *t,
+ int useGMT)
{
- const time_t *tp = (const time_t *) t;
struct tm *tmPtr;
- long time;
+ time_t time;
if (!useGMT) {
tzset();
/*
- * If we are in the valid range, let the C run-time library
- * handle it. Otherwise we need to fake it. Note that this
- * algorithm ignores daylight savings time before the epoch.
+ * If we are in the valid range, let the C run-time library handle it.
+ * Otherwise we need to fake it. Note that this algorithm ignores
+ * daylight savings time before the epoch.
*/
- if (*tp >= 0) {
- return localtime(tp);
+ /*
+ * Hm, Borland's localtime manages to return NULL under certain
+ * circumstances (e.g. wintime.test, test 1.2). Nobody tests for this,
+ * since 'localtime' isn't supposed to do this, possibly leading to
+ * crashes.
+ *
+ * Patch: We only call this function if we are at least one day into
+ * the epoch, else we handle it ourselves (like we do for times < 0).
+ * H. Giese, June 2003
+ */
+
+#ifdef __BORLANDC__
+#define LOCALTIME_VALIDITY_BOUNDARY SECSPERDAY
+#else
+#define LOCALTIME_VALIDITY_BOUNDARY 0
+#endif
+
+ if (*t >= LOCALTIME_VALIDITY_BOUNDARY) {
+ return TclpLocaltime(t);
}
- time = *tp - _timezone;
-
+ time = *t - timezone;
+
/*
* If we aren't near to overflowing the long, just add the bias and
- * use the normal calculation. Otherwise we will need to adjust
- * the result at the end.
+ * use the normal calculation. Otherwise we will need to adjust the
+ * result at the end.
*/
- if (*tp < (LONG_MAX - 2 * SECSPERDAY)
- && *tp > (LONG_MIN + 2 * SECSPERDAY)) {
+ if (*t < (LONG_MAX - 2*SECSPERDAY) && *t > (LONG_MIN + 2*SECSPERDAY)) {
tmPtr = ComputeGMT(&time);
} else {
- tmPtr = ComputeGMT(tp);
+ tmPtr = ComputeGMT(t);
tzset();
@@ -297,13 +562,13 @@ TclpGetDate(t, useGMT)
* Propagate seconds overflow into minutes, hours and days.
*/
- time = tmPtr->tm_sec - _timezone;
+ time = tmPtr->tm_sec - timezone;
tmPtr->tm_sec = (int)(time % 60);
if (tmPtr->tm_sec < 0) {
tmPtr->tm_sec += 60;
time -= 60;
}
-
+
time = tmPtr->tm_min + time/60;
tmPtr->tm_min = (int)(time % 60);
if (tmPtr->tm_min < 0) {
@@ -319,12 +584,12 @@ TclpGetDate(t, useGMT)
}
time /= 24;
- tmPtr->tm_mday += time;
- tmPtr->tm_yday += time;
- tmPtr->tm_wday = (tmPtr->tm_wday + time) % 7;
+ tmPtr->tm_mday += (int)time;
+ tmPtr->tm_yday += (int)time;
+ tmPtr->tm_wday = (tmPtr->tm_wday + (int)time) % 7;
}
} else {
- tmPtr = ComputeGMT(tp);
+ tmPtr = ComputeGMT(t);
}
return tmPtr;
}
@@ -334,8 +599,8 @@ TclpGetDate(t, useGMT)
*
* ComputeGMT --
*
- * This function computes GMT given the number of seconds since
- * the epoch (midnight Jan 1 1970).
+ * This function computes GMT given the number of seconds since the epoch
+ * (midnight Jan 1 1970).
*
* Results:
* Returns a (per thread) statically allocated struct tm.
@@ -347,13 +612,13 @@ TclpGetDate(t, useGMT)
*/
static struct tm *
-ComputeGMT(tp)
- const time_t *tp;
+ComputeGMT(
+ const time_t *tp)
{
struct tm *tmPtr;
long tmp, rem;
int isLeap;
- int *days;
+ const int *days;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
tmPtr = &tsdPtr->tm;
@@ -362,8 +627,8 @@ ComputeGMT(tp)
* Compute the 4 year span containing the specified time.
*/
- tmp = *tp / SECSPER4YEAR;
- rem = *tp % SECSPER4YEAR;
+ tmp = (long)(*tp / SECSPER4YEAR);
+ rem = (long)(*tp % SECSPER4YEAR);
/*
* Correct for weird mod semantics so the remainder is always positive.
@@ -375,9 +640,9 @@ ComputeGMT(tp)
}
/*
- * Compute the year after 1900 by taking the 4 year span and adjusting
- * for the remainder. This works because 2000 is a leap year, and
- * 1900/2100 are out of the range.
+ * Compute the year after 1900 by taking the 4 year span and adjusting for
+ * the remainder. This works because 2000 is a leap year, and 1900/2100
+ * are out of the range.
*/
tmp = (tmp * 4) + 70;
@@ -399,13 +664,13 @@ ComputeGMT(tp)
tmPtr->tm_year = tmp;
/*
- * Compute the day of year and leave the seconds in the current day in
- * the remainder.
+ * Compute the day of year and leave the seconds in the current day in the
+ * remainder.
*/
tmPtr->tm_yday = rem / SECSPERDAY;
rem %= SECSPERDAY;
-
+
/*
* Compute the time of day.
*/
@@ -421,6 +686,7 @@ ComputeGMT(tp)
days = (isLeap) ? leapDays : normalDays;
for (tmp = 1; days[tmp] < tmPtr->tm_yday; tmp++) {
+ /* empty body */
}
tmPtr->tm_mon = --tmp;
tmPtr->tm_mday = tmPtr->tm_yday - days[tmp];
@@ -429,7 +695,7 @@ ComputeGMT(tp)
* Compute day of week. Epoch started on a Thursday.
*/
- tmPtr->tm_wday = (*tp / SECSPERDAY) + 4;
+ tmPtr->tm_wday = (long)(*tp / SECSPERDAY) + 4;
if ((*tp % SECSPERDAY) < 0) {
tmPtr->tm_wday--;
}
@@ -440,3 +706,460 @@ ComputeGMT(tp)
return tmPtr;
}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * CalibrationThread --
+ *
+ * Thread that manages calibration of the hi-resolution time derived from
+ * the performance counter, to keep it synchronized with the system
+ * clock.
+ *
+ * Parameters:
+ * arg - Client data from the CreateThread call. This parameter points to
+ * the static TimeInfo structure.
+ *
+ * Return value:
+ * None. This thread embeds an infinite loop.
+ *
+ * Side effects:
+ * At an interval of 1s, this thread performs virtual time discipline.
+ *
+ * Note: When this thread is entered, TclpInitLock has been called to
+ * safeguard the static storage. There is therefore no synchronization in the
+ * body of this procedure.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static DWORD WINAPI
+CalibrationThread(
+ LPVOID arg)
+{
+ FILETIME curFileTime;
+ DWORD waitResult;
+
+ /*
+ * Get initial system time and performance counter.
+ */
+
+ GetSystemTimeAsFileTime(&curFileTime);
+ QueryPerformanceCounter(&timeInfo.perfCounterLastCall);
+ QueryPerformanceFrequency(&timeInfo.curCounterFreq);
+ timeInfo.fileTimeLastCall.LowPart = curFileTime.dwLowDateTime;
+ timeInfo.fileTimeLastCall.HighPart = curFileTime.dwHighDateTime;
+
+ ResetCounterSamples(timeInfo.fileTimeLastCall.QuadPart,
+ timeInfo.perfCounterLastCall.QuadPart,
+ timeInfo.curCounterFreq.QuadPart);
+
+ /*
+ * Wake up the calling thread. When it wakes up, it will release the
+ * initialization lock.
+ */
+
+ SetEvent(timeInfo.readyEvent);
+
+ /*
+ * Run the calibration once a second.
+ */
+
+ while (timeInfo.perfCounterAvailable) {
+ /*
+ * If the exitEvent is set, break out of the loop.
+ */
+
+ waitResult = WaitForSingleObjectEx(timeInfo.exitEvent, 1000, FALSE);
+ if (waitResult == WAIT_OBJECT_0) {
+ break;
+ }
+ UpdateTimeEachSecond();
+ }
+
+ /* lint */
+ return (DWORD) 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * UpdateTimeEachSecond --
+ *
+ * Callback from the waitable timer in the clock calibration thread that
+ * updates system time.
+ *
+ * Parameters:
+ * info - Pointer to the static TimeInfo structure
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Performs virtual time calibration discipline.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+UpdateTimeEachSecond(void)
+{
+ LARGE_INTEGER curPerfCounter;
+ /* Current value returned from
+ * QueryPerformanceCounter. */
+ FILETIME curSysTime; /* Current system time. */
+ LARGE_INTEGER curFileTime; /* File time at the time this callback was
+ * scheduled. */
+ Tcl_WideInt estFreq; /* Estimated perf counter frequency. */
+ Tcl_WideInt vt0; /* Tcl time right now. */
+ Tcl_WideInt vt1; /* Tcl time one second from now. */
+ Tcl_WideInt tdiff; /* Difference between system clock and Tcl
+ * time. */
+ Tcl_WideInt driftFreq; /* Frequency needed to drift virtual time into
+ * step over 1 second. */
+
+ /*
+ * Sample performance counter and system time.
+ */
+
+ QueryPerformanceCounter(&curPerfCounter);
+ GetSystemTimeAsFileTime(&curSysTime);
+ curFileTime.LowPart = curSysTime.dwLowDateTime;
+ curFileTime.HighPart = curSysTime.dwHighDateTime;
+
+ EnterCriticalSection(&timeInfo.cs);
+
+ /*
+ * We devide by timeInfo.curCounterFreq.QuadPart in several places. That
+ * value should always be positive on a correctly functioning system. But
+ * it is good to be defensive about such matters. So if something goes
+ * wrong and the value does goes to zero, we clear the
+ * timeInfo.perfCounterAvailable in order to cause the calibration thread
+ * to shut itself down, then return without additional processing.
+ */
+
+ if (timeInfo.curCounterFreq.QuadPart == 0){
+ LeaveCriticalSection(&timeInfo.cs);
+ timeInfo.perfCounterAvailable = 0;
+ return;
+ }
+
+ /*
+ * Several things may have gone wrong here that have to be checked for.
+ * (1) The performance counter may have jumped.
+ * (2) The system clock may have been reset.
+ *
+ * In either case, we'll need to reinitialize the circular buffer with
+ * samples relative to the current system time and the NOMINAL performance
+ * frequency (not the actual, because the actual has probably run slow in
+ * the first case). Our estimated frequency will be the nominal frequency.
+ *
+ * Store the current sample into the circular buffer of samples, and
+ * estimate the performance counter frequency.
+ */
+
+ estFreq = AccumulateSample(curPerfCounter.QuadPart,
+ (Tcl_WideUInt) curFileTime.QuadPart);
+
+ /*
+ * We want to adjust things so that time appears to be continuous.
+ * Virtual file time, right now, is
+ *
+ * vt0 = 10000000 * (curPerfCounter - perfCounterLastCall)
+ * / curCounterFreq
+ * + fileTimeLastCall
+ *
+ * Ideally, we would like to drift the clock into place over a period of 2
+ * sec, so that virtual time 2 sec from now will be
+ *
+ * vt1 = 20000000 + curFileTime
+ *
+ * The frequency that we need to use to drift the counter back into place
+ * is estFreq * 20000000 / (vt1 - vt0)
+ */
+
+ vt0 = 10000000 * (curPerfCounter.QuadPart
+ - timeInfo.perfCounterLastCall.QuadPart)
+ / timeInfo.curCounterFreq.QuadPart
+ + timeInfo.fileTimeLastCall.QuadPart;
+ vt1 = 20000000 + curFileTime.QuadPart;
+
+ /*
+ * If we've gotten more than a second away from system time, then drifting
+ * the clock is going to be pretty hopeless. Just let it jump. Otherwise,
+ * compute the drift frequency and fill in everything.
+ */
+
+ tdiff = vt0 - curFileTime.QuadPart;
+ if (tdiff > 10000000 || tdiff < -10000000) {
+ timeInfo.fileTimeLastCall.QuadPart = curFileTime.QuadPart;
+ timeInfo.curCounterFreq.QuadPart = estFreq;
+ } else {
+ driftFreq = estFreq * 20000000 / (vt1 - vt0);
+
+ if (driftFreq > 1003*estFreq/1000) {
+ driftFreq = 1003*estFreq/1000;
+ } else if (driftFreq < 997*estFreq/1000) {
+ driftFreq = 997*estFreq/1000;
+ }
+
+ timeInfo.fileTimeLastCall.QuadPart = vt0;
+ timeInfo.curCounterFreq.QuadPart = driftFreq;
+ }
+
+ timeInfo.perfCounterLastCall.QuadPart = curPerfCounter.QuadPart;
+
+ LeaveCriticalSection(&timeInfo.cs);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ResetCounterSamples --
+ *
+ * Fills the sample arrays in 'timeInfo' with dummy values that will
+ * yield the current performance counter and frequency.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The array of samples is filled in so that it appears that there are
+ * SAMPLES samples at one-second intervals, separated by precisely the
+ * given frequency.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ResetCounterSamples(
+ Tcl_WideUInt fileTime, /* Current file time */
+ Tcl_WideInt perfCounter, /* Current performance counter */
+ Tcl_WideInt perfFreq) /* Target performance frequency */
+{
+ int i;
+ for (i=SAMPLES-1 ; i>=0 ; --i) {
+ timeInfo.perfCounterSample[i] = perfCounter;
+ timeInfo.fileTimeSample[i] = fileTime;
+ perfCounter -= perfFreq;
+ fileTime -= 10000000;
+ }
+ timeInfo.sampleNo = 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * AccumulateSample --
+ *
+ * Updates the circular buffer of performance counter and system time
+ * samples with a new data point.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The new data point replaces the oldest point in the circular buffer,
+ * and the descriptive statistics are updated to accumulate the new
+ * point.
+ *
+ * Several things may have gone wrong here that have to be checked for.
+ * (1) The performance counter may have jumped.
+ * (2) The system clock may have been reset.
+ *
+ * In either case, we'll need to reinitialize the circular buffer with samples
+ * relative to the current system time and the NOMINAL performance frequency
+ * (not the actual, because the actual has probably run slow in the first
+ * case).
+ */
+
+static Tcl_WideInt
+AccumulateSample(
+ Tcl_WideInt perfCounter,
+ Tcl_WideUInt fileTime)
+{
+ Tcl_WideUInt workFTSample; /* File time sample being removed from or
+ * added to the circular buffer. */
+ Tcl_WideInt workPCSample; /* Performance counter sample being removed
+ * from or added to the circular buffer. */
+ Tcl_WideUInt lastFTSample; /* Last file time sample recorded */
+ Tcl_WideInt lastPCSample; /* Last performance counter sample recorded */
+ Tcl_WideInt FTdiff; /* Difference between last FT and current */
+ Tcl_WideInt PCdiff; /* Difference between last PC and current */
+ Tcl_WideInt estFreq; /* Estimated performance counter frequency */
+
+ /*
+ * Test for jumps and reset the samples if we have one.
+ */
+
+ if (timeInfo.sampleNo == 0) {
+ lastPCSample =
+ timeInfo.perfCounterSample[timeInfo.sampleNo + SAMPLES - 1];
+ lastFTSample =
+ timeInfo.fileTimeSample[timeInfo.sampleNo + SAMPLES - 1];
+ } else {
+ lastPCSample = timeInfo.perfCounterSample[timeInfo.sampleNo - 1];
+ lastFTSample = timeInfo.fileTimeSample[timeInfo.sampleNo - 1];
+ }
+
+ PCdiff = perfCounter - lastPCSample;
+ FTdiff = fileTime - lastFTSample;
+ if (PCdiff < timeInfo.nominalFreq.QuadPart * 9 / 10
+ || PCdiff > timeInfo.nominalFreq.QuadPart * 11 / 10
+ || FTdiff < 9000000 || FTdiff > 11000000) {
+ ResetCounterSamples(fileTime, perfCounter,
+ timeInfo.nominalFreq.QuadPart);
+ return timeInfo.nominalFreq.QuadPart;
+ } else {
+ /*
+ * Estimate the frequency.
+ */
+
+ workPCSample = timeInfo.perfCounterSample[timeInfo.sampleNo];
+ workFTSample = timeInfo.fileTimeSample[timeInfo.sampleNo];
+ estFreq = 10000000 * (perfCounter - workPCSample)
+ / (fileTime - workFTSample);
+ timeInfo.perfCounterSample[timeInfo.sampleNo] = perfCounter;
+ timeInfo.fileTimeSample[timeInfo.sampleNo] = (Tcl_WideInt) fileTime;
+
+ /*
+ * Advance the sample number.
+ */
+
+ if (++timeInfo.sampleNo >= SAMPLES) {
+ timeInfo.sampleNo = 0;
+ }
+
+ return estFreq;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclpGmtime --
+ *
+ * Wrapper around the 'gmtime' library function to make it thread safe.
+ *
+ * Results:
+ * Returns a pointer to a 'struct tm' in thread-specific data.
+ *
+ * Side effects:
+ * Invokes gmtime or gmtime_r as appropriate.
+ *
+ *----------------------------------------------------------------------
+ */
+
+struct tm *
+TclpGmtime(
+ const time_t *timePtr) /* Pointer to the number of seconds since the
+ * local system's epoch */
+{
+ /*
+ * The MS implementation of gmtime is thread safe because it returns the
+ * time in a block of thread-local storage, and Windows does not provide a
+ * Posix gmtime_r function.
+ */
+
+ return gmtime(timePtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclpLocaltime --
+ *
+ * Wrapper around the 'localtime' library function to make it thread
+ * safe.
+ *
+ * Results:
+ * Returns a pointer to a 'struct tm' in thread-specific data.
+ *
+ * Side effects:
+ * Invokes localtime or localtime_r as appropriate.
+ *
+ *----------------------------------------------------------------------
+ */
+
+struct tm *
+TclpLocaltime(
+ const time_t *timePtr) /* Pointer to the number of seconds since the
+ * local system's epoch */
+{
+ /*
+ * The MS implementation of localtime is thread safe because it returns
+ * the time in a block of thread-local storage, and Windows does not
+ * provide a Posix localtime_r function.
+ */
+
+ return localtime(timePtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_SetTimeProc --
+ *
+ * TIP #233 (Virtualized Time): Registers two handlers for the
+ * virtualization of Tcl's access to time information.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Remembers the handlers, alters core behaviour.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+Tcl_SetTimeProc(
+ Tcl_GetTimeProc *getProc,
+ Tcl_ScaleTimeProc *scaleProc,
+ ClientData clientData)
+{
+ tclGetTimeProcPtr = getProc;
+ tclScaleTimeProcPtr = scaleProc;
+ tclTimeClientData = clientData;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_QueryTimeProc --
+ *
+ * TIP #233 (Virtualized Time): Query which time handlers are registered.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+Tcl_QueryTimeProc(
+ Tcl_GetTimeProc **getProc,
+ Tcl_ScaleTimeProc **scaleProc,
+ ClientData *clientData)
+{
+ if (getProc) {
+ *getProc = tclGetTimeProcPtr;
+ }
+ if (scaleProc) {
+ *scaleProc = tclScaleTimeProcPtr;
+ }
+ if (clientData) {
+ *clientData = tclTimeClientData;
+ }
+}
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 78
+ * End:
+ */