aboutsummaryrefslogblamecommitdiffstats
path: root/erts/emulator/sys/win32/sys_time.c
blob: ea180f067f68eee6f9051abc32ab927ac4edd04c (plain) (tree)
1
2
3
4


                   
                                                        

















                                                                         


                     

                   
                                           
                     
 
                                         



                                                                         














                                                 




                                                                            
                                  




                                      
                                  


                                                                            









                                                                            






                                                             
               

  



                                                 













                                                                               





                                                                                 










                                                                                  
 







                                                                        





                                   


                              




                                                                            





                                                              
                                           

 



































                                                                                            







                                               
                                                  
                                


                                        

                                                 
                                              








                                                           


                                                                      
                                                



                                                                                 

                                               

















                                                                 


                                                                          
                                                    

                                                   











                                                                        
 





                                                                      














                                                                   











                                                                          
 




                                              
 

 






                                                                                      













































































                                                                               




                                                                     
                                                



                                  

































































                                                                               
    
                                                                     


                    


                                         








                                         















                                                            



               






































































                                                                             




                                
                       


                                  




                                                                 

 




















                                                                       
/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 1997-2013. All Rights Reserved.
 * 
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * %CopyrightEnd%
 */
/*
 * Purpose: System-dependent time functions.
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#include "sys.h"
#include "assert.h"
#include "erl_os_monotonic_time_extender.h"
#include "erl_time.h"

#define LL_LITERAL(X) ERTS_I64_LITERAL(X)

/******************* Routines for time measurement *********************/

#define EPOCH_JULIAN_DIFF LL_LITERAL(11644473600)
#define TICKS_PER_SECOND LL_LITERAL(10000000)
#define SECONDS_PER_DAY LL_LITERAL(86400)

#define ULI_TO_FILETIME(ft,ull)			\
 do {						\
     (ft).dwLowDateTime = (ull).LowPart;	\
     (ft).dwHighDateTime = (ull).HighPart;	\
 } while (0)

#define FILETIME_TO_ULI(ull,ft)			\
 do {						\
     (ull).LowPart = (ft).dwLowDateTime;	\
     (ull).HighPart = (ft).dwHighDateTime;	\
 } while (0)


#define EPOCH_TO_FILETIME(ft, epoch) \
    do { \
	ULARGE_INTEGER ull; \
	ull.QuadPart = (((epoch) + EPOCH_JULIAN_DIFF) * TICKS_PER_SECOND); \
	ULI_TO_FILETIME(ft,ull); \
    } while(0)

#define FILETIME_TO_EPOCH(epoch, ft) \
    do { \
	ULARGE_INTEGER ull; \
	FILETIME_TO_ULI(ull,ft); \
	(epoch) = ((ull.QuadPart / TICKS_PER_SECOND) - EPOCH_JULIAN_DIFF); \
    } while(0)
 
/* Getting timezone information is a heavy operation, so we want to do this 
   only once */

static TIME_ZONE_INFORMATION static_tzi;
static int have_static_tzi = 0;

static int days_in_month[2][13] = {
    {0,31,28,31,30,31,30,31,31,30,31,30,31},
    {0,31,29,31,30,31,30,31,31,30,31,30,31}};

/*
 * erts_os_monotonic_time()
 */

struct sys_time_internal_state_read_only__ {
    ULONGLONG (WINAPI *pGetTickCount64)(void);
    BOOL (WINAPI *pQueryPerformanceCounter)(LARGE_INTEGER *);
    Sint32 pcf;
};

struct sys_time_internal_state_read_mostly__ {
    ErtsOsMonotonicTimeExtendState os_mtime_xtnd;
};

struct sys_time_internal_state_write_freq__ {
    erts_smp_mtx_t mtime_mtx;
    ULONGLONG wrap;
    ULONGLONG last_tick_count;
};

__declspec(align(ASSUMED_CACHE_LINE_SIZE)) struct {
    union {
	struct sys_time_internal_state_read_only__ o;
	char align__[(((sizeof(struct sys_time_internal_state_read_only__) - 1)
		       / ASSUMED_CACHE_LINE_SIZE) + 1)
		     * ASSUMED_CACHE_LINE_SIZE];
    } r;
    union {
	struct sys_time_internal_state_read_mostly__ m;
	char align__[(((sizeof(struct sys_time_internal_state_read_mostly__) - 1)
		       / ASSUMED_CACHE_LINE_SIZE) + 1)
		     * ASSUMED_CACHE_LINE_SIZE];
    } wr;
    union {
	struct sys_time_internal_state_write_freq__ f;
	char align__[(((sizeof(struct sys_time_internal_state_write_freq__) - 1)
		       / ASSUMED_CACHE_LINE_SIZE) + 1)
		     * ASSUMED_CACHE_LINE_SIZE];
    } w;
} internal_state;

__declspec(align(ASSUMED_CACHE_LINE_SIZE)) ErtsSysTimeData__ erts_sys_time_data__;

static ErtsMonotonicTime
os_monotonic_time_qpc(void)
{
    LARGE_INTEGER pc;

    if (!(*internal_state.r.o.pQueryPerformanceCounter)(&pc))
	erl_exit(ERTS_ABORT_EXIT, "QueryPerformanceCounter() failed\n");

    return (ErtsMonotonicTime) pc.QuadPart;
}

static Uint32
get_tick_count(void)
{
    return (Uint32) GetTickCount();
}

static ErtsMonotonicTime
os_monotonic_time_gtc32(void) 
{
    Uint32 ticks = (Uint32) GetTickCount();
    ERTS_CHK_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd,
				      tick_count);
    return ERTS_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd,
					 ticks) << 10;
}

static ErtsMonotonicTime
os_monotonic_time_gtc64(void) 
{
    ULONGLONG ticks = (*internal_state.r.o.pGetTickCount64)();
    return (ErtsMonotonicTime) ticks << 10;
}

static ErtsSysHrTime
sys_hrtime_qpc(void)
{
    LARGE_INTEGER pc;

    if (!(*internal_state.r.o.pQueryPerformanceCounter)(&pc))
	erl_exit(ERTS_ABORT_EXIT, "QueryPerformanceCounter() failed\n");

    ASSERT(pc.QuadPart > 0);

    return (ErtsSysHrTime) erts_time_unit_conversion((Uint64) pc.QuadPart,
						     internal_state.r.o.pcf,
						     (Uint32) 1000*1000*1000);
}

static ErtsSysHrTime
sys_hrtime_gtc32(void)
{
    ErtsSysHrTime time;
    Uint32 ticks = (Uint32) GetTickCount();
    ERTS_CHK_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd,
				      tick_count);
    time = (ErtsSysHrTime) ERTS_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd,
							 ticks);
    time *= (ErtsSysHrTime) (1000 * 1000);
    return time;
}

static ErtsSysHrTime
sys_hrtime_gtc64(void)
{
    ErtsSysHrTime time = (*internal_state.r.o.pGetTickCount64)();
    time *= (ErtsSysHrTime) (1000*1000);
    return time;
}

/*
 * Init
 */

void
sys_init_time(ErtsSysInitTimeResult *init_resp)
{
    ErtsMonotonicTime (*os_mtime_func)(void);
    ErtsSysHrTime (*sys_hrtime_func)(void) = NULL;
    ErtsMonotonicTime time_unit;
    char kernel_dll_name[] = "kernel32";
    HMODULE module;

    init_resp->os_monotonic_info.clock_id = NULL;

    module = GetModuleHandle(kernel_dll_name);
    if (!module) {
    get_tick_count:
	erts_smp_mtx_init(&internal_state.w.f.mtime_mtx,
			  "os_monotonic_time");
	internal_state.w.f.wrap = 0;
	internal_state.w.f.last_tick_count = 0;

	init_resp->os_monotonic_info.func = "GetTickCount";
	init_resp->os_monotonic_info.locked_use = 1;
	/* 10-16 ms resolution according to MicroSoft documentation */
	init_resp->os_monotonic_info.resolution = 100; /* 10 ms */
	time_unit = (ErtsMonotonicTime) (1000 << 10);
	os_mtime_func = os_monotonic_time_gtc32;
	init_resp->os_monotonic_info.extended = 1;
	erts_init_os_monotonic_time_extender(&internal_state.wr.m.os_mtime_xtnd,
					     get_tick_count,
					     60*60*24*7); /* Check once a week */
	if (!sys_hrtime_func)
	    sys_hrtime_func = sys_hrtime_gtc32;
    }
    else {
	int major, minor, build;

	os_version(&major, &minor, &build);

	if (major < 6) {

	get_tick_count64:

	    internal_state.r.o.pGetTickCount64
		= ((ULONGLONG (WINAPI *)(void))
		   GetProcAddress(module, "GetTickCount64"));
	    if (!internal_state.r.o.pGetTickCount64)
		goto get_tick_count;

	    init_resp->os_monotonic_info.func = "GetTickCount64";
	    init_resp->os_monotonic_info.locked_use = 0;
	    /* 10-16 ms resolution according to MicroSoft documentation */
	    init_resp->os_monotonic_info.resolution = 100; /* 10 ms */
	    time_unit = (ErtsMonotonicTime) (1000 << 10);
	    os_mtime_func = os_monotonic_time_gtc64;
	    if (!sys_hrtime_func)
		sys_hrtime_func = sys_hrtime_gtc64;
	}
	else { /* Vista or newer... */

	    LARGE_INTEGER pf;
	    BOOL (WINAPI *QPF)(LARGE_INTEGER *);

	    QPF = ((BOOL (WINAPI *)(LARGE_INTEGER *))
		   GetProcAddress(module, "QueryPerformanceFrequency"));
	    if (!QPF)
		goto get_tick_count64;
	    if (!(*QPF)(&pf))
		goto get_tick_count64;

	    internal_state.r.o.pQueryPerformanceCounter
		= ((BOOL (WINAPI *)(LARGE_INTEGER *))
		   GetProcAddress(module, "QueryPerformanceCounter"));
	    if (!internal_state.r.o.pQueryPerformanceCounter)
		goto get_tick_count64;

	    if (pf.QuadPart < (((LONGLONG) 1) << 32)) {
		internal_state.r.o.pcf = (Uint32) pf.QuadPart;
		sys_hrtime_func = sys_hrtime_qpc;
	    }
	    
	    /*
	     * We only use QueryPerformanceCounter() for
	     * os-monotonic-time if its frequency is equal
	     * to, or larger than GHz in order to ensure
	     * that the user wont be able to observe faulty
	     * order between values retrieved on different threads.
	     */
	    if (pf.QuadPart < (LONGLONG) 1000*1000*1000)
		goto get_tick_count64;

	    init_resp->os_monotonic_info.func = "QueryPerformanceCounter";
	    init_resp->os_monotonic_info.locked_use = 0;
	    time_unit = (ErtsMonotonicTime) pf.QuadPart;
	    init_resp->os_monotonic_info.resolution = time_unit;
	    os_mtime_func = os_monotonic_time_qpc;
	}
    }

    erts_sys_time_data__.r.o.os_monotonic_time = os_mtime_func;
    init_resp->os_monotonic_time_unit = time_unit;
    init_resp->have_os_monotonic = 1;
    init_resp->sys_clock_resolution = 1;

    if(GetTimeZoneInformation(&static_tzi) && 
       static_tzi.StandardDate.wMonth != 0 &&
       static_tzi.DaylightDate.wMonth != 0) {
	have_static_tzi = 1;
    }

}

void
erts_late_sys_init_time(void)
{
    if (erts_sys_time_data__.r.o.os_monotonic_time == os_monotonic_time_gtc32)
	erts_late_init_os_monotonic_time_extender(&internal_state.wr.m.os_mtime_xtnd);
}

/* Returns a switchtimes for DST as UTC filetimes given data from a 
   TIME_ZONE_INFORMATION, see sys_localtime_r for usage. */ 
static void 
get_dst_switchtime(DWORD year, 
		   SYSTEMTIME dstinfo, LONG bias,
		   FILETIME *utc_switchtime) 
{
    DWORD occu;
    DWORD weekday,wday_1st;
    DWORD day, days_in;
    FILETIME tmp,tmp2;
    ULARGE_INTEGER ull;
    int leap_year = 0;
    if (dstinfo.wYear != 0) {
	/* A year specific transition, in which case the data in the structure
	   is already properly set for a specific year. Compare year
	   with parameter and see if they correspond, in that case generate a 
	   filetime directly, otherwise set the filetime to 0 */
	if (year != dstinfo.wYear) {
	    utc_switchtime->dwLowDateTime = utc_switchtime->dwHighDateTime = 0;
	    return;
	} 
    } else {
	occu = dstinfo.wDay;
	weekday = dstinfo.wDayOfWeek;

	dstinfo.wDayOfWeek = 0;
	dstinfo.wDay = 1;
	dstinfo.wYear = year;
	
	SystemTimeToFileTime(&dstinfo,&tmp);
	ull.LowPart = tmp.dwLowDateTime;
	ull.HighPart = tmp.dwHighDateTime;
    
	ull.QuadPart /= (TICKS_PER_SECOND*SECONDS_PER_DAY); /* Julian Day */
	wday_1st = (DWORD) ((ull.QuadPart + LL_LITERAL(1)) % LL_LITERAL(7));
	day = (weekday >= wday_1st) ? 
	    weekday - wday_1st + 1 :
	    weekday - wday_1st + 8;
	--occu;
	if (((dstinfo.wYear % 4) == 0 && (dstinfo.wYear % 100) > 0) ||
	    ((dstinfo.wYear % 400) == 0)) {
	    leap_year = 1;
	}
	days_in = days_in_month[leap_year][dstinfo.wMonth];
	while (occu > 0 && (day + 7 <= days_in)) {
	    --occu;
	    day += 7;
	}
	dstinfo.wDay = day;
    }
    SystemTimeToFileTime(&dstinfo,&tmp);
    /* correct for bias */
    ull.LowPart = tmp.dwLowDateTime;
    ull.HighPart = tmp.dwHighDateTime;
    ull.QuadPart += (((LONGLONG) bias) * LL_LITERAL(60) * TICKS_PER_SECOND);
    utc_switchtime->dwLowDateTime = ull.LowPart;
    utc_switchtime->dwHighDateTime = ull.HighPart;
    return;
}

/* This function gives approximately the correct year from a FILETIME
   Around the actual new year, it may return the wrong value, but that's OK
   as DST never switches around new year. */
static DWORD 
approx_year(FILETIME ft)
{
    ULARGE_INTEGER ull;
    FILETIME_TO_ULI(ull,ft);
    ull.QuadPart /= LL_LITERAL(1000);
    ull.QuadPart /= SECONDS_PER_DAY;
    ull.QuadPart /= LL_LITERAL(3652425);
    ull.QuadPart += 1601;
    return (DWORD) ull.QuadPart;
}

struct tm *
sys_localtime_r(time_t *epochs, struct tm *ptm)
{
    FILETIME ft,lft;
    SYSTEMTIME st;
    
    if ((((*epochs) + EPOCH_JULIAN_DIFF) * TICKS_PER_SECOND) < 0LL) {
	fprintf(stderr,"1\r\n"); fflush(stderr);
	return NULL;
    }
    
    EPOCH_TO_FILETIME(ft,*epochs);
    ptm->tm_isdst = 0;
    if (have_static_tzi) {
	FILETIME dst_start, dst_stop;
	ULARGE_INTEGER ull;
	DWORD year = approx_year(ft);
	get_dst_switchtime(year,static_tzi.DaylightDate,
			   static_tzi.Bias+static_tzi.StandardBias,&dst_start);
	get_dst_switchtime(year,static_tzi.StandardDate,
			   static_tzi.Bias+static_tzi.StandardBias+
			   static_tzi.DaylightBias,
			   &dst_stop);
	FILETIME_TO_ULI(ull,ft);

	if (CompareFileTime(&ft,&dst_start) >= 0 && 
	    CompareFileTime(&ft,&dst_stop) < 0) {
	    ull.QuadPart -= 
		((LONGLONG) static_tzi.Bias+static_tzi.StandardBias+
		 static_tzi.DaylightBias) * 
		LL_LITERAL(60) * TICKS_PER_SECOND;
	    ptm->tm_isdst = 1;
	} else {
	    ull.QuadPart -= 
		((LONGLONG) static_tzi.Bias+static_tzi.StandardBias) 
		* LL_LITERAL(60) * TICKS_PER_SECOND;
	}
	ULI_TO_FILETIME(ft,ull);
    } else {
	if (!FileTimeToLocalFileTime(&ft,&lft)) {
	    return NULL;
	}
	ft = lft;
    }
    
    if (!FileTimeToSystemTime(&ft,&st)) {
	return NULL;
    }
    
    ptm->tm_year = (int) st.wYear - 1900;
    ptm->tm_mon  = (int) st.wMonth - 1;
    ptm->tm_mday = (int) st.wDay;
    ptm->tm_hour = (int) st.wHour;
    ptm->tm_min  = (int) st.wMinute;
    ptm->tm_sec  = (int) st.wSecond;
    ptm->tm_wday  = (int) st.wDayOfWeek;
    {
	int yday = ptm->tm_mday - 1;
	int m =  ptm->tm_mon;
	int leap_year = 0;
	if (((st.wYear % 4) == 0 && (st.wYear % 100) > 0) ||
	    ((st.wYear % 400) == 0)) {
	    leap_year = 1;
	}
	while (m > 0) {
	    yday +=days_in_month[leap_year][m];
	    --m;
	}
	ptm->tm_yday = yday;
    }
    return ptm;
}

struct tm * 
sys_gmtime_r(time_t *epochs, struct tm *ptm)
{
    FILETIME ft;
    SYSTEMTIME st;
    
    if ((((*epochs) + EPOCH_JULIAN_DIFF) * TICKS_PER_SECOND) < 0LL) {
	return NULL;
    }
    
    EPOCH_TO_FILETIME(ft,*epochs);
    
    if (!FileTimeToSystemTime(&ft,&st)) {
	return NULL;
    }
    
    ptm->tm_year = (int) st.wYear - 1900;
    ptm->tm_mon  = (int) st.wMonth - 1;
    ptm->tm_mday = (int) st.wDay;
    ptm->tm_hour = (int) st.wHour;
    ptm->tm_min  = (int) st.wMinute;
    ptm->tm_sec  = (int) st.wSecond;
    ptm->tm_wday  = (int) st.wDayOfWeek;
    ptm->tm_isdst  = 0;
    {
	int yday = ptm->tm_mday - 1;
	int m =  ptm->tm_mon;
	int leap_year = 0;
	if (((st.wYear % 4) == 0 && (st.wYear % 100) > 0) ||
	    ((st.wYear % 400) == 0)) {
	    leap_year = 1;
	}
	while (m > 0) {
	    yday +=days_in_month[leap_year][m];
	    --m;
	}
	ptm->tm_yday = yday;
    }

    return ptm;
}

time_t 
sys_mktime(struct tm *ptm) 
{
    FILETIME ft;
    SYSTEMTIME st;
    int dst = 0;
    time_t epochs;

    memset(&st,0,sizeof(st));
    /* Convert relevant parts of truct tm to SYSTEMTIME */
    st.wYear =  (USHORT) (ptm->tm_year + 1900);
    st.wMonth =  (USHORT) (ptm->tm_mon + 1);
    st.wDay =  (USHORT) ptm->tm_mday;
    st.wHour =  (USHORT) ptm->tm_hour;
    st.wMinute =  (USHORT) ptm->tm_min;
    st.wSecond = (USHORT) ptm->tm_sec;

    SystemTimeToFileTime(&st,&ft);
    
    /* ft is now some kind of local file time, but it may be wrong depending 
       on what is in the tm_dst field. We need to manually convert it to
       UTC before turning it into epochs */

    if (have_static_tzi) {
	FILETIME dst_start, dst_stop;
	ULARGE_INTEGER ull_start,ull_stop,ull_ft;

	FILETIME_TO_ULI(ull_ft,ft);

	/* Correct everything except DST */
	ull_ft.QuadPart += (static_tzi.Bias+static_tzi.StandardBias) 
	    * LL_LITERAL(60) * TICKS_PER_SECOND;

	/* Determine if DST is active */
	if (ptm->tm_isdst >= 0) {
	    dst = ptm->tm_isdst;
	} else if (static_tzi.DaylightDate.wMonth != 0){
	    /* This is how windows mktime does it, meaning it does not
	       take nonexisting local times into account */
	    get_dst_switchtime(st.wYear,static_tzi.DaylightDate,
			       static_tzi.Bias+static_tzi.StandardBias,
			       &dst_start);
	    get_dst_switchtime(st.wYear,static_tzi.StandardDate,
			       static_tzi.Bias+static_tzi.StandardBias+
			       static_tzi.DaylightBias,
			       &dst_stop);
	    FILETIME_TO_ULI(ull_start,dst_start);
	    FILETIME_TO_ULI(ull_stop,dst_stop);
	    if ((ull_ft.QuadPart >= ull_start.QuadPart) &&
		(ull_ft.QuadPart < ull_stop.QuadPart)) {
		/* We are in DST */
		dst = 1;
	    }
	}
	/* Correct for DST */
	if (dst) {
	    ull_ft.QuadPart += static_tzi.DaylightBias * 
		LL_LITERAL(60) * TICKS_PER_SECOND;
	}
	epochs = ((ull_ft.QuadPart / TICKS_PER_SECOND) - EPOCH_JULIAN_DIFF);
    } else {
	/* No DST, life is easy... */
	FILETIME lft;
	LocalFileTimeToFileTime(&ft,&lft);
	FILETIME_TO_EPOCH(epochs,lft);
    }
    /* Normalize the struct tm */
    sys_localtime_r(&epochs,ptm);
    return epochs;
}

void 
sys_gettimeofday(SysTimeval *tv)
{
    SYSTEMTIME t;
    FILETIME ft;
    ULARGE_INTEGER ull;

    GetSystemTime(&t);
    SystemTimeToFileTime(&t, &ft);
    FILETIME_TO_ULI(ull,ft);
    tv->tv_usec = (long) ((ull.QuadPart / LL_LITERAL(10)) % 
			  LL_LITERAL(1000000));
    tv->tv_sec = (long) ((ull.QuadPart / LL_LITERAL(10000000)) - 
			 EPOCH_JULIAN_DIFF);
}

clock_t 
sys_times(SysTimes *buffer) {
    clock_t kernel_ticks = (GetTickCount() / 
			    (1000 / SYS_CLK_TCK)) & 0x7FFFFFFF;
    FILETIME dummy;
    LONGLONG user;
    LONGLONG system;
    
    buffer->tms_utime = buffer->tms_stime = buffer->tms_cutime = 
	buffer->tms_cstime = 0;

    if (GetProcessTimes(GetCurrentProcess(), &dummy, &dummy,
			(FILETIME *) &system, (FILETIME *) &user) == 0)
	return kernel_ticks;
    system /= (LONGLONG)(10000000 / SYS_CLK_TCK);
    user /= (LONGLONG)(10000000 / SYS_CLK_TCK);
    
    buffer->tms_utime = (clock_t) (user & LL_LITERAL(0x7FFFFFFF));
    buffer->tms_stime = (clock_t) (system & LL_LITERAL(0x7FFFFFFF));
    return kernel_ticks;
}