aboutsummaryrefslogblamecommitdiffstats
path: root/erts/emulator/beam/erl_time_sup.c
blob: d47d1682d718fccf6d27c666604656f76ecf27fb (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                   
  
                                                        
  




                                                                      
  



                                                                         
  



                 

                                







                     
 
                                         
                                        

                                                        




                                                                                            
 











                                                       

           


































































































































































































                                                                                                     
 



                                          

 

                                          
 
                                                                           


                       
                                         
 
                                                                           

 

                                    
 
                                                                               
 
 
 











                                          
 


                                                                  
 






                                                                
 



                                                                  
      





                                                                  

 
                                                 
 








                                                       
 








                                                              
     
 























































































































                                                                                          

     



















































                                                                                           
             























                                                                                               
             
         





























                                                                                

         



                                                                           
     


                                                            
 
                                            
 

                                                
 




                                                    
 





                                                                  
 





                                                                     
 







                                                              

 
                                        
 

                                        
 
































                                                                                 

     





































                                                          
                                                      


                                                    
     
                                             
      


                                                                                 
     
 
 
                                                
 



                                                     
 
                                          
 



















                                                                            
             
         
 
     





                                                                  

 




                                                                
 



                                              
     
             
      
 
 

                              
 
                                   

 

                              
 

                                           
 
                                      
 

                                                   
 




                                                  
 











                                                         
 

    
                                                                        
 





                                                              
                                                        



























                                                             
 






















                                                                    
 
      
 




                                                                  
     




















                                                                            
    

















                                                                        
      











                                                                       
 
                                                             
 


                             
 
                                       
     

























































































































                                                                                  


                    

                                                          
 

                                          





























                                                               
                                                             
 
                                   


                                           
                                  
 



                                                           















                                             
                                         




















                                         
                                         




































































                                                         


                                                             
 

































                                                 







                                                              
                                                   
 

                       













                                                                    
                                                                     

 


                                                     
 


                                                                     










                                       





                                        
                                                        











                                                     

 




















                                                                                                         
 

             
 




                                                                  




























                                                                             





                                                         




                                                                             
                                                 







                                                                             




                                      
                    
                                      


                            


               







                               



                                                                  








































                                                                             
                                         


                               









                                     

 



                                                 
                                                                    
    



                                                      
                                           





                                                    

                                             
 














                                                                   






                                                     

                           




                                             



                                                                 
 






                                        
 
                
 



























                                                                           
 
 









                                           
 

                                                        
 











































                                                              
        




































































                                                                                  
         

                                       


     

                                                                             
 










                                                                       

 

                                                       
 


                                   
 

                               
 


                                        

 




                                                            
 

































                                                                                 
 




















































                                                                                               
      


























































































































































                                                                                
 
/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 1999-2012. 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%
 */

/*
 * Support routines for the time
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "sys.h"
#include "erl_vm.h"
#include "global.h"
 
static erts_smp_mtx_t erts_timeofday_mtx;
static erts_smp_mtx_t erts_get_time_mtx;

static SysTimes t_start; /* Used in elapsed_time_both */
static ErtsMonotonicTime prev_wall_clock_elapsed; /* Used in wall_clock_elapsed_time_both */
static ErtsMonotonicTime previous_now; /* Used in get_now */

static ErtsMonitor *time_offset_monitors = NULL;
static Uint no_time_offset_monitors = 0;

#ifdef DEBUG
static int time_sup_initialized = 0;
#endif

#define ERTS_MONOTONIC_TIME_KILO \
    ((ErtsMonotonicTime) 1000)
#define ERTS_MONOTONIC_TIME_MEGA \
    (ERTS_MONOTONIC_TIME_KILO*ERTS_MONOTONIC_TIME_KILO)
#define ERTS_MONOTONIC_TIME_GIGA \
    (ERTS_MONOTONIC_TIME_MEGA*ERTS_MONOTONIC_TIME_KILO)
#define ERTS_MONOTONIC_TIME_TERA \
    (ERTS_MONOTONIC_TIME_GIGA*ERTS_MONOTONIC_TIME_KILO)

static void
schedule_send_time_offset_changed_notifications(ErtsMonotonicTime new_offset);

/*
 * NOTE! ERTS_MONOTONIC_TIME_START *need* to be a multiple
 *       of ERTS_MONOTONIC_TIME_UNIT.
 */

#if ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT

#ifdef ARCH_32
/*
 * Want to use a big-num of arity 2 as long as possible (584 years
 * in the nano-second time unit case).
 */
#define ERTS_MONOTONIC_TIME_START		\
    (((((((ErtsMonotonicTime) 1) << 32)-1)	\
       / ERTS_MONOTONIC_TIME_UNIT)		\
      * ERTS_MONOTONIC_TIME_UNIT)		\
     + ERTS_MONOTONIC_TIME_UNIT)

#else /* ARCH_64 */

#if ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT <= 1000*1000

/*
 * Using micro second time unit or lower. Start at zero since
 * time will remain an immediate for a very long time anyway
 * (18279 years in the micro second case)...
 */
#define ERTS_MONOTONIC_TIME_START ((ErtsMonotonicTime) 0)

#else /* ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT > 1000*1000 */

/*
 * Want to use an immediate as long as possible (36 years in the
 * nano-second time unit case).
*/
#define ERTS_MONOTONIC_TIME_START 		\
    ((((ErtsMonotonicTime) MIN_SMALL)		\
      / ERTS_MONOTONIC_TIME_UNIT)		\
     * ERTS_MONOTONIC_TIME_UNIT)

#endif /* ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT > 1000*1000 */

#endif /* ARCH_64 */

#define ERTS_MONOTONIC_OFFSET_NATIVE ERTS_MONOTONIC_TIME_START
#define ERTS_MONOTONIC_OFFSET_NSEC ERTS_MONOTONIC_TO_NSEC__(ERTS_MONOTONIC_TIME_START)
#define ERTS_MONOTONIC_OFFSET_USEC ERTS_MONOTONIC_TO_USEC__(ERTS_MONOTONIC_TIME_START)
#define ERTS_MONOTONIC_OFFSET_MSEC ERTS_MONOTONIC_TO_MSEC__(ERTS_MONOTONIC_TIME_START)
#define ERTS_MONOTONIC_OFFSET_SEC ERTS_MONOTONIC_TO_SEC__(ERTS_MONOTONIC_TIME_START)

#else /* ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT */

/*
 * Initialized in erts_init_time_sup()...
 */

#define ERTS_MONOTONIC_TIME_START (time_sup.r.o.start)
#define ERTS_MONOTONIC_OFFSET_NATIVE (time_sup.r.o.start_offset.native)
#define ERTS_MONOTONIC_OFFSET_NSEC (time_sup.r.o.start_offset.nsec)
#define ERTS_MONOTONIC_OFFSET_USEC (time_sup.r.o.start_offset.usec)
#define ERTS_MONOTONIC_OFFSET_MSEC (time_sup.r.o.start_offset.msec)
#define ERTS_MONOTONIC_OFFSET_SEC (time_sup.r.o.start_offset.sec)

#endif /* ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT */

#define ERTS_MONOTONIC_TO_SYS_TIME_VAL(TVP, MT)				\
    do {								\
	ErtsMonotonicTime sec__, usec__;				\
	sec__ = ERTS_MONOTONIC_TO_SEC((MT));				\
	usec__ = ERTS_MONOTONIC_TO_USEC((MT)) - sec__*1000000;		\
	ASSERT(usec__ < 1000000);					\
	(TVP)->tv_sec = sec__;						\
	(TVP)->tv_usec = usec__;					\
    } while (0)

#define ERTS_MAX_SYSTEM_TIME_DIFF ERTS_MSEC_TO_MONOTONIC(10)
#define ERTS_SYSTEM_TIME_DIFF_EXCEED_LIMIT(ESYSTIME, OSSYSTIME)		\
    (((Uint64) (ESYSTIME)) - (((Uint64) (OSSYSTIME))			\
			      - ERTS_MAX_SYSTEM_TIME_DIFF)		\
     > 2*ERTS_MAX_SYSTEM_TIME_DIFF)

#define ERTS_TIME_CORRECTION_LARGE_ADJ_DIFF (ERTS_MAX_SYSTEM_TIME_DIFF/2)
#define ERTS_TIME_CORRECTION_SMALL_ADJ_DIFF ERTS_USEC_TO_MONOTONIC(500)

struct time_sup_read_only__ {
    ErtsMonotonicTime (*get_time)(void);
    int correction;
    ErtsTimeWarpMode warp_mode;
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
    ErtsMonotonicTime moffset;
    int os_monotonic_disable;
    char *os_monotonic_func;
    char *os_monotonic_clock_id;
    int os_monotonic_locked;
    Uint64 os_monotonic_resolution;
#endif
#if !ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT
    ErtsMonotonicTime start;
    struct {
	ErtsMonotonicTime native;
	ErtsMonotonicTime nsec;
	ErtsMonotonicTime usec;
	ErtsMonotonicTime msec;
	ErtsMonotonicTime sec;
    } start_offset;
#endif
};

typedef struct {
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
    ErtsMonotonicTime drift; /* Correction for os monotonic drift */
#endif
    ErtsMonotonicTime error; /* Correction for error between system times */
} ErtsMonotonicCorrection;

typedef struct {
    ErtsMonotonicTime erl_mtime;
    ErtsMonotonicTime os_mtime;
    ErtsMonotonicCorrection correction;
} ErtsMonotonicCorrectionInstance;

#define ERTS_DRIFT_INTERVALS 5
typedef struct {
    struct {
	struct {
	    ErtsMonotonicTime sys;
	    ErtsMonotonicTime mon;
	} diff;
	struct {
	    ErtsMonotonicTime sys;
	    ErtsMonotonicTime mon;
	} time;
    } intervals[ERTS_DRIFT_INTERVALS];
    struct {
	ErtsMonotonicTime sys;
	ErtsMonotonicTime mon;
    } acc;
    int ix;
    int dirty_counter;
} ErtsMonotonicDriftData;

typedef struct {
    ErtsMonotonicCorrectionInstance prev;
    ErtsMonotonicCorrectionInstance curr;    
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
    ErtsMonotonicDriftData drift;
#endif
    ErtsMonotonicTime last_check;
    int short_check_interval;
} ErtsMonotonicCorrectionData;

struct time_sup_infrequently_changed__ {
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
    struct {
	erts_smp_rwmtx_t rwmtx;
	ErlTimer timer;
	ErtsMonotonicCorrectionData cdata;
    } parmon;
    ErtsMonotonicTime minit;
#endif
    int finalized_offset;
    SysTimeval inittv; /* Used everywhere, the initial time-of-day */
    ErtsMonotonicTime not_corrected_moffset;
    erts_atomic64_t offset;
};

struct time_sup_frequently_changed__ {
    ErtsMonotonicTime last_not_corrected_time;
};

static struct {
    union {
	struct time_sup_read_only__ o;
	char align__[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(struct time_sup_read_only__))];
    } r;
    union {
	struct time_sup_infrequently_changed__ c;
	char align__[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(struct time_sup_infrequently_changed__))];
    } inf;
    union {
	struct time_sup_frequently_changed__ c;
	char align__[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(struct time_sup_frequently_changed__))];
    } f;
} time_sup erts_align_attribute(ERTS_CACHE_LINE_SIZE);

ErtsTimeSupData erts_time_sup__ erts_align_attribute(ERTS_CACHE_LINE_SIZE);

/*
 * erts_get_approx_time() returns an *approximate* time
 * in seconds. NOTE that this time may jump backwards!!!
 */
erts_approx_time_t
erts_get_approx_time(void)
{
    SysTimeval tv;
    sys_gettimeofday(&tv);

    return (erts_approx_time_t) tv.tv_sec;
}

static ERTS_INLINE void
init_time_offset(ErtsMonotonicTime offset)
{
    erts_atomic64_init_nob(&time_sup.inf.c.offset, (erts_aint64_t) offset);
}

static ERTS_INLINE void
set_time_offset(ErtsMonotonicTime offset)
{
    erts_atomic64_set_relb(&time_sup.inf.c.offset, (erts_aint64_t) offset);
}

static ERTS_INLINE ErtsMonotonicTime
get_time_offset(void)
{
    return (ErtsMonotonicTime) erts_atomic64_read_acqb(&time_sup.inf.c.offset);
}


#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT

/*
 * Time correction adjustments made due to
 * error between Erlang system time and OS
 * system time:
 * - Large adjustment ~1%
 * - Small adjustment ~0.05%
 */
#define ERTS_TCORR_ERR_UNIT 2048
#define ERTS_TCORR_ERR_LARGE_ADJ 20
#define ERTS_TCORR_ERR_SMALL_ADJ 1

#define ERTS_INIT_SHORT_INTERVAL_COUNTER 10
#define ERTS_LONG_TIME_CORRECTION_CHECK ERTS_SEC_TO_MONOTONIC(60)
#define ERTS_SHORT_TIME_CORRECTION_CHECK ERTS_SEC_TO_MONOTONIC(15)

#define ERTS_TIME_DRIFT_MAX_ADJ_DIFF ERTS_USEC_TO_MONOTONIC(100)
#define ERTS_TIME_DRIFT_MIN_ADJ_DIFF ERTS_USEC_TO_MONOTONIC(5)

static ERTS_INLINE ErtsMonotonicTime
calc_corrected_erl_mtime(ErtsMonotonicTime os_mtime,
			 ErtsMonotonicCorrectionInstance *cip,
			 ErtsMonotonicTime *os_mdiff_p)
{
    ErtsMonotonicTime erl_mtime, diff = os_mtime - cip->os_mtime;
    ERTS_TIME_ASSERT(diff >= 0);
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
    diff += (cip->correction.drift*diff)/ERTS_MONOTONIC_TIME_UNIT;
#endif
    erl_mtime = cip->erl_mtime;
    erl_mtime += diff;
    erl_mtime += cip->correction.error*(diff/ERTS_TCORR_ERR_UNIT);
    if (os_mdiff_p)
	*os_mdiff_p = diff;
    return erl_mtime;
}

static ErtsMonotonicTime get_corrected_time(void)
{
    ErtsMonotonicTime os_mtime;
    ErtsMonotonicCorrectionData cdata;
    ErtsMonotonicCorrectionInstance *cip;

    erts_smp_rwmtx_rlock(&time_sup.inf.c.parmon.rwmtx);

    os_mtime = erts_os_monotonic_time();

    cdata = time_sup.inf.c.parmon.cdata;

    erts_smp_rwmtx_runlock(&time_sup.inf.c.parmon.rwmtx);

    if (os_mtime >= cdata.curr.os_mtime)
	cip = &cdata.curr;
    else {
	if (os_mtime < cdata.prev.os_mtime)
	    erl_exit(ERTS_ABORT_EXIT,
		     "OS monotonic time stepped backwards\n");
	cip = &cdata.prev;
    }

    return calc_corrected_erl_mtime(os_mtime, cip, NULL);
}

static void
check_time_correction(void *unused)
{
    ErtsMonotonicCorrectionData cdata;
    ErtsMonotonicCorrection new_correction;
    ErtsMonotonicCorrectionInstance *cip;
    ErtsMonotonicTime mdiff, sdiff, os_mtime, erl_mtime, os_stime, erl_stime, time_offset;
    Uint timeout;
    SysTimeval tod;
    int set_new_correction, begin_short_intervals = 0;

    erts_smp_rwmtx_rlock(&time_sup.inf.c.parmon.rwmtx);

    ASSERT(time_sup.inf.c.finalized_offset);

    os_mtime = erts_os_monotonic_time();
    sys_gettimeofday(&tod);

    cdata = time_sup.inf.c.parmon.cdata;

    erts_smp_rwmtx_runlock(&time_sup.inf.c.parmon.rwmtx);

    if (os_mtime < cdata.curr.os_mtime)
	erl_exit(ERTS_ABORT_EXIT,
		 "OS monotonic time stepped backwards\n");
    cip = &cdata.curr;

    erl_mtime = calc_corrected_erl_mtime(os_mtime, cip, &mdiff);
    time_offset = get_time_offset();
    erl_stime = erl_mtime + time_offset;

    os_stime = ERTS_SEC_TO_MONOTONIC(tod.tv_sec);
    os_stime += ERTS_USEC_TO_MONOTONIC(tod.tv_usec);

    sdiff = erl_stime - os_stime;

    new_correction = cip->correction;
    
    if (time_sup.r.o.warp_mode == ERTS_MULTI_TIME_WARP_MODE
	&& (sdiff < -2*ERTS_TIME_CORRECTION_SMALL_ADJ_DIFF
	    || 2*ERTS_TIME_CORRECTION_SMALL_ADJ_DIFF < sdiff)) {
	/* System time diff exeeded limits; change time offset... */
	time_offset -= sdiff;
	sdiff = 0;
	set_time_offset(time_offset);
	schedule_send_time_offset_changed_notifications(time_offset);
	begin_short_intervals = 1;
	if (cdata.curr.correction.error == 0)
	    set_new_correction = 0;
	else {
	    set_new_correction = 1;
	    new_correction.error = 0;
	}
    }
    else if (cdata.curr.correction.error == 0) {
	if (sdiff < -ERTS_TIME_CORRECTION_SMALL_ADJ_DIFF) {
	    set_new_correction = 1;
	    if (sdiff < -ERTS_TIME_CORRECTION_LARGE_ADJ_DIFF)
		new_correction.error = ERTS_TCORR_ERR_LARGE_ADJ;
	    else
		new_correction.error = ERTS_TCORR_ERR_SMALL_ADJ;
	}
	else if (sdiff > ERTS_TIME_CORRECTION_SMALL_ADJ_DIFF) {
	    set_new_correction = 1;
	    if (sdiff > ERTS_TIME_CORRECTION_LARGE_ADJ_DIFF)
		new_correction.error = -ERTS_TCORR_ERR_LARGE_ADJ;
	    else
		new_correction.error = -ERTS_TCORR_ERR_SMALL_ADJ;
	}
	else {
	    set_new_correction = 0;
	}
    }
    else if (cdata.curr.correction.error > 0) {
	if (sdiff < 0) {
	    if (cdata.curr.correction.error == ERTS_TCORR_ERR_LARGE_ADJ
		|| -ERTS_TIME_CORRECTION_LARGE_ADJ_DIFF <= sdiff)
		set_new_correction = 0;
	    else {
		new_correction.error = ERTS_TCORR_ERR_LARGE_ADJ;
		set_new_correction = 1;
	    }
	}
	else if (sdiff > ERTS_TIME_CORRECTION_SMALL_ADJ_DIFF) {
	    set_new_correction = 1;
	    if (sdiff > ERTS_TIME_CORRECTION_LARGE_ADJ_DIFF)
		new_correction.error = -ERTS_TCORR_ERR_LARGE_ADJ;
	    else
		new_correction.error = -ERTS_TCORR_ERR_SMALL_ADJ;
	}
	else {
	    set_new_correction = 1;
	    new_correction.error = 0;
	}
    }
    else /* if (cdata.curr.correction.error < 0) */ { 
	if (0 < sdiff) {
	    if (cdata.curr.correction.error == -ERTS_TCORR_ERR_LARGE_ADJ
		|| sdiff <= ERTS_TIME_CORRECTION_LARGE_ADJ_DIFF)
		set_new_correction = 0;
	    else {
		new_correction.error = -ERTS_TCORR_ERR_LARGE_ADJ;
		set_new_correction = 1;
	    }
	    set_new_correction = 0;
	}
	else if (sdiff < -ERTS_TIME_CORRECTION_SMALL_ADJ_DIFF) {
	    set_new_correction = 1;
	    if (sdiff < -ERTS_TIME_CORRECTION_LARGE_ADJ_DIFF)
		new_correction.error = ERTS_TCORR_ERR_LARGE_ADJ;
	    else
		new_correction.error = ERTS_TCORR_ERR_SMALL_ADJ;
	}
	else {
	    set_new_correction = 1;
	    new_correction.error = 0;
	}
    }

#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
    {
	ErtsMonotonicDriftData *ddp = &time_sup.inf.c.parmon.cdata.drift;
	int ix = ddp->ix;
	ErtsMonotonicTime mtime_diff, old_os_mtime;

	old_os_mtime = ddp->intervals[ix].time.mon;
	mtime_diff = os_mtime - old_os_mtime;

	if (mtime_diff >= ERTS_SEC_TO_MONOTONIC(10)) {
	    ErtsMonotonicTime drift_adj, drift_adj_diff, old_os_stime,
		stime_diff, mtime_acc, stime_acc, avg_drift_adj;

	    old_os_stime = ddp->intervals[ix].time.sys;

	    mtime_acc = ddp->acc.mon;
	    stime_acc = ddp->acc.sys;

	    avg_drift_adj = ((stime_acc - mtime_acc)*ERTS_MONOTONIC_TIME_UNIT) / mtime_acc;

	    mtime_diff = os_mtime - old_os_mtime;
	    stime_diff = os_stime - old_os_stime;
	    drift_adj = ((stime_diff - mtime_diff)*ERTS_MONOTONIC_TIME_UNIT) / mtime_diff;
	    
	    ix++;
	    if (ix >= ERTS_DRIFT_INTERVALS)
		ix = 0;
	    mtime_acc -= ddp->intervals[ix].diff.mon;
	    mtime_acc += mtime_diff;
	    stime_acc -= ddp->intervals[ix].diff.sys;
	    stime_acc += stime_diff;

	    ddp->intervals[ix].diff.mon = mtime_diff;
	    ddp->intervals[ix].diff.sys = stime_diff;
	    ddp->intervals[ix].time.mon = os_mtime;
	    ddp->intervals[ix].time.sys = os_stime;

	    ddp->ix = ix;
	    ddp->acc.mon = mtime_acc;
	    ddp->acc.sys = stime_acc;

	    /*
	     * If calculated drift adjustment is if off by more than 20% from the
	     * average drift we interpret this as a discontinous leap in system
	     * time and ignore it. If it actually is a change in drift we will
	     * later detect this when the average drift change.
	     */
	    drift_adj_diff = avg_drift_adj - drift_adj;
	    if (drift_adj_diff < -ERTS_TIME_DRIFT_MAX_ADJ_DIFF
		|| ERTS_TIME_DRIFT_MAX_ADJ_DIFF < drift_adj_diff) {
		ddp->dirty_counter = ERTS_DRIFT_INTERVALS;
		begin_short_intervals = 1;
	    }
	    else {
		if (ddp->dirty_counter <= 0) {
		    drift_adj = ((stime_acc - mtime_acc)*ERTS_MONOTONIC_TIME_UNIT) / mtime_acc;
		}
		if (ddp->dirty_counter >= 0) {
		    if (ddp->dirty_counter == 0) {
			/* Force set new drift correction... */
			set_new_correction = 1;
		    }
		    ddp->dirty_counter--;
		}
		drift_adj_diff = drift_adj - new_correction.drift;
		if (drift_adj_diff) {
		    if (drift_adj_diff > ERTS_TIME_DRIFT_MAX_ADJ_DIFF)
			drift_adj_diff = ERTS_TIME_DRIFT_MAX_ADJ_DIFF;
		    else if (drift_adj_diff < -ERTS_TIME_DRIFT_MAX_ADJ_DIFF)
			drift_adj_diff = -ERTS_TIME_DRIFT_MAX_ADJ_DIFF;
		    new_correction.drift += drift_adj_diff;

		    if (drift_adj_diff < -ERTS_TIME_DRIFT_MIN_ADJ_DIFF
			|| ERTS_TIME_DRIFT_MIN_ADJ_DIFF < drift_adj_diff) {
			set_new_correction = 1;
		    }
		}
	    }
	}
    }
#endif

    begin_short_intervals |= set_new_correction;
    
    if (begin_short_intervals) {
	time_sup.inf.c.parmon.cdata.short_check_interval
	    = ERTS_INIT_SHORT_INTERVAL_COUNTER;
    }
    else if ((os_mtime - time_sup.inf.c.parmon.cdata.last_check
	      >= ERTS_SHORT_TIME_CORRECTION_CHECK - ERTS_MONOTONIC_TIME_UNIT)
	     && time_sup.inf.c.parmon.cdata.short_check_interval > 0) {
	time_sup.inf.c.parmon.cdata.short_check_interval--;
    }
    time_sup.inf.c.parmon.cdata.last_check = os_mtime;

    if (new_correction.error == 0)
	timeout = ERTS_MONOTONIC_TO_MSEC(ERTS_LONG_TIME_CORRECTION_CHECK);
    else {
	ErtsMonotonicTime ecorr = new_correction.error;
	if (sdiff < 0)
	    sdiff = -1*sdiff;
	if (ecorr < 0)
	    ecorr = -1*ecorr;
	if (sdiff > ecorr*(ERTS_LONG_TIME_CORRECTION_CHECK/ERTS_TCORR_ERR_UNIT))
	    timeout = ERTS_MONOTONIC_TO_MSEC(ERTS_LONG_TIME_CORRECTION_CHECK);
	else {
	    timeout = ERTS_MONOTONIC_TO_MSEC((ERTS_TCORR_ERR_UNIT*sdiff)/ecorr);
	    if (timeout < 10)
		timeout = 10;
	}
    }

    if (timeout > ERTS_MONOTONIC_TO_MSEC(ERTS_SHORT_TIME_CORRECTION_CHECK)
	&& time_sup.inf.c.parmon.cdata.short_check_interval) {
	timeout = ERTS_MONOTONIC_TO_MSEC(ERTS_SHORT_TIME_CORRECTION_CHECK);
    }
    
    if (set_new_correction) {
	erts_smp_rwmtx_rwlock(&time_sup.inf.c.parmon.rwmtx);

	os_mtime = erts_os_monotonic_time();

	/* Save previous correction instance */
	time_sup.inf.c.parmon.cdata.prev = *cip;

	/*
	 * Current correction instance begin when
	 * OS monotonic time has increased one unit.
	 */
	os_mtime++;

	/*
	 * Erlang monotonic time corresponding to
	 * next OS monotonic time using previous
	 * correction.
	 */
	erl_mtime = calc_corrected_erl_mtime(os_mtime, cip, NULL);

	/*
	 * Save new current correction instance.
	 */
	time_sup.inf.c.parmon.cdata.curr.erl_mtime = erl_mtime;
	time_sup.inf.c.parmon.cdata.curr.os_mtime = os_mtime;
	time_sup.inf.c.parmon.cdata.curr.correction = new_correction;

	erts_smp_rwmtx_rwunlock(&time_sup.inf.c.parmon.rwmtx);
    }

    erts_set_timer(&time_sup.inf.c.parmon.timer,
		   check_time_correction,
		   NULL,
		   NULL,
		   timeout);
}

#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC

static void
init_check_time_correction(void *unused)
{
    ErtsMonotonicDriftData *ddp;
    ErtsMonotonicTime old_mtime, old_stime, mtime, stime, mtime_diff, stime_diff;
    int ix;
    SysTimeval tod;

    ddp = &time_sup.inf.c.parmon.cdata.drift;
    ix = ddp->ix;
    old_mtime = ddp->intervals[0].time.mon;
    old_stime = ddp->intervals[0].time.sys;

    mtime = erts_os_monotonic_time();
    sys_gettimeofday(&tod);

    stime = ERTS_SEC_TO_MONOTONIC(tod.tv_sec);
    stime += ERTS_USEC_TO_MONOTONIC(tod.tv_usec);

    mtime_diff = mtime - old_mtime;
    stime_diff = stime - old_stime;
    if (100*stime_diff  < 80*mtime_diff || 120*mtime_diff < 100*stime_diff ) {
	/* Had a system time leap... pretend no drift... */
	stime_diff = mtime_diff;
    }
    
    /*
     * We use old time values in order to trigger
     * a drift adjustment, and repeat this interval
     * in all slots...
     */
    for (ix = 0; ix < ERTS_DRIFT_INTERVALS; ix++) {
	ddp->intervals[ix].diff.mon = mtime_diff;
	ddp->intervals[ix].diff.sys = stime_diff;
	ddp->intervals[ix].time.mon = old_mtime;
	ddp->intervals[ix].time.sys = old_stime;
    }

    ddp->acc.sys = stime_diff*ERTS_DRIFT_INTERVALS;
    ddp->acc.mon = mtime_diff*ERTS_DRIFT_INTERVALS;
    ddp->ix = 0;
    ddp->dirty_counter = ERTS_DRIFT_INTERVALS;

    check_time_correction(NULL);
}

#endif

static ErtsMonotonicTime
finalize_corrected_time_offset(SysTimeval *todp)
{
    ErtsMonotonicTime os_mtime;
    ErtsMonotonicCorrectionData cdata;
    ErtsMonotonicCorrectionInstance *cip;

    erts_smp_rwmtx_rlock(&time_sup.inf.c.parmon.rwmtx);

    os_mtime = erts_os_monotonic_time();
    sys_gettimeofday(todp);

    cdata = time_sup.inf.c.parmon.cdata;

    erts_smp_rwmtx_runlock(&time_sup.inf.c.parmon.rwmtx);

    if (os_mtime < cdata.curr.os_mtime)
	erl_exit(ERTS_ABORT_EXIT,
		 "OS monotonic time stepped backwards\n");
    cip = &cdata.curr;

    return calc_corrected_erl_mtime(os_mtime, cip, NULL);
}

static void
late_init_time_correction(void)
{
    if (time_sup.inf.c.finalized_offset) {
	erts_init_timer(&time_sup.inf.c.parmon.timer);
	erts_set_timer(&time_sup.inf.c.parmon.timer,
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
		       init_check_time_correction,
#else
		       check_time_correction,
#endif
		       NULL,
		       NULL,
		       ERTS_MONOTONIC_TO_MSEC(ERTS_SHORT_TIME_CORRECTION_CHECK));
    }
}

#endif /* ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT */

static ErtsMonotonicTime get_not_corrected_time(void)
{
    SysTimeval tmp_tv;
    ErtsMonotonicTime stime, mtime;

    erts_smp_mtx_lock(&erts_get_time_mtx);

    sys_gettimeofday(&tmp_tv);

    stime = ERTS_SEC_TO_MONOTONIC(tmp_tv.tv_sec);
    stime += ERTS_USEC_TO_MONOTONIC(tmp_tv.tv_usec);

    mtime = stime - time_sup.inf.c.not_corrected_moffset;

    if (mtime >= time_sup.f.c.last_not_corrected_time)
	time_sup.f.c.last_not_corrected_time = mtime;
    else {
	mtime = time_sup.f.c.last_not_corrected_time;

	if (time_sup.r.o.warp_mode == ERTS_MULTI_TIME_WARP_MODE) {
	    ErtsMonotonicTime new_offset = stime - mtime;
	    new_offset = ERTS_MONOTONIC_TO_USEC(new_offset);
	    new_offset = ERTS_USEC_TO_MONOTONIC(new_offset);
	    if (time_sup.inf.c.not_corrected_moffset != new_offset) {
		time_sup.inf.c.not_corrected_moffset = new_offset;
		set_time_offset(new_offset);
		schedule_send_time_offset_changed_notifications(new_offset);
	    }
	}

    }

    ASSERT(stime == mtime + time_sup.inf.c.not_corrected_moffset);

    erts_smp_mtx_unlock(&erts_get_time_mtx);

    return mtime;
}

int erts_check_time_adj_support(int time_correction,
				ErtsTimeWarpMode time_warp_mode)
{
    if (!time_correction)
	return 1;

    /* User wants time correction */

#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
    return !time_sup.r.o.os_monotonic_disable;
#else
    return 0;
#endif
}

int
erts_has_time_correction(void)
{
    return time_sup.r.o.correction;
}

void
erts_early_init_time_sup(void)
{
    ErtsSysInitTimeResult sys_init_time_res
	= ERTS_SYS_INIT_TIME_RESULT_INITER;

    sys_init_time(&sys_init_time_res);

    erts_time_sup__.r.o.monotonic_time_unit
	= sys_init_time_res.os_monotonic_time_unit;

#ifndef SYS_CLOCK_RESOLUTION
    erts_time_sup__.r.o.clktck_resolution
	= sys_init_time_res.sys_clock_resolution;
    erts_time_sup__.r.o.clktck_resolution *= 1000;
#endif

#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
    time_sup.r.o.os_monotonic_disable
	= !sys_init_time_res.have_os_monotonic;
    time_sup.r.o.os_monotonic_func
	= sys_init_time_res.os_monotonic_info.func;
    time_sup.r.o.os_monotonic_clock_id
	= sys_init_time_res.os_monotonic_info.clock_id;
    time_sup.r.o.os_monotonic_locked
	= sys_init_time_res.os_monotonic_info.locked_use;
    time_sup.r.o.os_monotonic_resolution
	= sys_init_time_res.os_monotonic_info.resolution;
#endif
}

int 
erts_init_time_sup(int time_correction, ErtsTimeWarpMode time_warp_mode)
{
#if !ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT
    ErtsMonotonicTime abs_start;
#endif

    ASSERT(ERTS_MONOTONIC_TIME_MIN < ERTS_MONOTONIC_TIME_MAX);

    erts_smp_mtx_init(&erts_timeofday_mtx, "timeofday");
    erts_smp_mtx_init(&erts_get_time_mtx, "get_time");

    time_sup.r.o.correction = time_correction;
    time_sup.r.o.warp_mode = time_warp_mode;
 
    if (time_warp_mode == ERTS_SINGLE_TIME_WARP_MODE)
	time_sup.inf.c.finalized_offset = 0;
    else
	time_sup.inf.c.finalized_offset = ~0;

#if !ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT

#ifdef ARCH_32
    time_sup.r.o.start = ((((ErtsMonotonicTime) 1) << 32)-1);
    time_sup.r.o.start /= ERTS_MONOTONIC_TIME_UNIT;
    time_sup.r.o.start *= ERTS_MONOTONIC_TIME_UNIT;
    time_sup.r.o.start += ERTS_MONOTONIC_TIME_UNIT;
    abs_start = time_sup.r.o.start;
#else /* ARCH_64 */
    if (ERTS_MONOTONIC_TIME_UNIT <= 1000*1000)
	abs_start = time_sup.r.o.start = 0;
    else {
	time_sup.r.o.start = ((ErtsMonotonicTime) MIN_SMALL);
	time_sup.r.o.start /= ERTS_MONOTONIC_TIME_UNIT;
	time_sup.r.o.start *= ERTS_MONOTONIC_TIME_UNIT;
	abs_start = -1*time_sup.r.o.start;
    }
#endif

    time_sup.r.o.start_offset.native = time_sup.r.o.start;
    time_sup.r.o.start_offset.nsec = (ErtsMonotonicTime)
	erts_time_unit_conversion((Uint64) abs_start,
				  (Uint32) ERTS_MONOTONIC_TIME_UNIT,
				  (Uint32) 1000*1000*1000);
    time_sup.r.o.start_offset.usec = (ErtsMonotonicTime)
	erts_time_unit_conversion((Uint64) abs_start,
				  (Uint32) ERTS_MONOTONIC_TIME_UNIT,
				  (Uint32) 1000*1000);
    time_sup.r.o.start_offset.msec = (ErtsMonotonicTime)
	erts_time_unit_conversion((Uint64) abs_start,
				  (Uint32) ERTS_MONOTONIC_TIME_UNIT,
				  (Uint32) 1000);
    time_sup.r.o.start_offset.sec = (ErtsMonotonicTime)
	erts_time_unit_conversion((Uint64) abs_start,
				  (Uint32) ERTS_MONOTONIC_TIME_UNIT,
				  (Uint32) 1);
    if (time_sup.r.o.start < 0) {
	time_sup.r.o.start_offset.nsec *= -1;
	time_sup.r.o.start_offset.usec *= -1;
	time_sup.r.o.start_offset.msec *= -1;
	time_sup.r.o.start_offset.sec *= -1;
    }

#endif

    if (ERTS_MONOTONIC_TIME_UNIT < ERTS_CLKTCK_RESOLUTION)
	ERTS_INTERNAL_ERROR("Too small monotonic time time unit");

#ifndef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
    time_sup.r.o.correction = 0;
#else
    if (time_sup.r.o.os_monotonic_disable)
	time_sup.r.o.correction = 0;

    if (time_sup.r.o.correction) {
	ErtsMonotonicCorrectionData *cdatap;
	erts_smp_rwmtx_opt_t rwmtx_opts = ERTS_SMP_RWMTX_OPT_DEFAULT_INITER;
	ErtsMonotonicTime offset;
	time_sup.inf.c.minit = erts_os_monotonic_time();
	sys_gettimeofday(&time_sup.inf.c.inittv);
	time_sup.r.o.moffset = -1*time_sup.inf.c.minit;
	offset = ERTS_SEC_TO_MONOTONIC(time_sup.inf.c.inittv.tv_sec);
	offset += ERTS_USEC_TO_MONOTONIC(time_sup.inf.c.inittv.tv_usec);
	init_time_offset(offset);

	rwmtx_opts.type = ERTS_SMP_RWMTX_TYPE_EXTREMELY_FREQUENT_READ;
	rwmtx_opts.lived = ERTS_SMP_RWMTX_LONG_LIVED;

	erts_smp_rwmtx_init_opt(&time_sup.inf.c.parmon.rwmtx,
				&rwmtx_opts, "get_corrected_time");

	cdatap = &time_sup.inf.c.parmon.cdata;
    
#ifndef ERTS_HAVE_CORRECTED_OS_MONOTONIC
	cdatap->drift.intervals[0].time.sys
	    = ERTS_SEC_TO_MONOTONIC(time_sup.inf.c.inittv.tv_sec);
	cdatap->drift.intervals[0].time.sys
	    += ERTS_USEC_TO_MONOTONIC(time_sup.inf.c.inittv.tv_usec);
	cdatap->drift.intervals[0].time.mon = time_sup.inf.c.minit;
	cdatap->curr.correction.drift = 0;
#endif
	cdatap->curr.correction.error = 0;
	cdatap->curr.erl_mtime = 0;
	cdatap->curr.os_mtime = time_sup.inf.c.minit;
	cdatap->last_check = time_sup.inf.c.minit;
	cdatap->short_check_interval = ERTS_INIT_SHORT_INTERVAL_COUNTER;
	cdatap->prev = cdatap->curr;

	time_sup.r.o.get_time = get_corrected_time;
    }
    else
#endif
    {
	ErtsMonotonicTime stime, offset;
	time_sup.r.o.get_time = get_not_corrected_time;
	stime = ERTS_SEC_TO_MONOTONIC(time_sup.inf.c.inittv.tv_sec);
	stime += ERTS_USEC_TO_MONOTONIC(time_sup.inf.c.inittv.tv_usec);
	offset = stime;
	time_sup.inf.c.not_corrected_moffset = offset;
	init_time_offset(offset);
	time_sup.f.c.last_not_corrected_time = 0;
    }

    prev_wall_clock_elapsed = 0;

    previous_now = ERTS_MONOTONIC_TO_USEC(get_time_offset());

#ifdef DEBUG
    time_sup_initialized = 1;
#endif

    return ERTS_CLKTCK_RESOLUTION/1000;
}    

void
erts_late_init_time_sup(void)
{
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
    /* Timer wheel must be initialized */
    if (time_sup.r.o.get_time == get_corrected_time)
	late_init_time_correction();
#endif
}

ErtsTimeWarpMode erts_time_warp_mode(void)
{
    return time_sup.r.o.warp_mode;
}

ErtsTimeOffsetState erts_time_offset_state(void)
{
    switch (time_sup.r.o.warp_mode) {
    case ERTS_NO_TIME_WARP_MODE:
	return ERTS_TIME_OFFSET_FINAL;
    case ERTS_SINGLE_TIME_WARP_MODE:
	if (time_sup.inf.c.finalized_offset)
	    return ERTS_TIME_OFFSET_FINAL;
	return ERTS_TIME_OFFSET_PRELIMINARY;
    case ERTS_MULTI_TIME_WARP_MODE:
	return ERTS_TIME_OFFSET_VOLATILE;
    default:
	ERTS_INTERNAL_ERROR("Invalid time warp mode");
	return ERTS_TIME_OFFSET_VOLATILE;
    }
}

/*
 * erts_finalize_time_offset() will only change time offset
 * the first time it is called when the emulator has been
 * started in "single time warp" mode. Returns previous
 * state:
 * * ERTS_TIME_OFFSET_PRELIMINARY - Finalization performed
 * * ERTS_TIME_OFFSET_FINAL - Already finialized; nothing changed
 * * ERTS_TIME_OFFSET_VOLATILE - Not supported, either in
 * * no correction mode (or multi time warp mode; not yet implemented).
 */

ErtsTimeOffsetState
erts_finalize_time_offset(void)
{
    switch (time_sup.r.o.warp_mode) {
    case ERTS_NO_TIME_WARP_MODE:
	return ERTS_TIME_OFFSET_FINAL;
    case ERTS_MULTI_TIME_WARP_MODE:
	return ERTS_TIME_OFFSET_VOLATILE;
    case ERTS_SINGLE_TIME_WARP_MODE: {
	ErtsTimeOffsetState res = ERTS_TIME_OFFSET_FINAL;

	erts_smp_mtx_lock(&erts_get_time_mtx);

	if (!time_sup.inf.c.finalized_offset) {
	    ErtsMonotonicTime mtime, new_offset;
	    SysTimeval tv;

#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
	    if (!time_sup.r.o.correction)
#endif
	    {
		ErtsMonotonicTime stime;
		sys_gettimeofday(&tv);

		stime = ERTS_SEC_TO_MONOTONIC(tv.tv_sec);
		stime += ERTS_USEC_TO_MONOTONIC(tv.tv_usec);

		mtime = stime - time_sup.inf.c.not_corrected_moffset;

		if (mtime >= time_sup.f.c.last_not_corrected_time) {
		    time_sup.f.c.last_not_corrected_time = mtime;
		    new_offset = time_sup.inf.c.not_corrected_moffset;
		}
		else {
		    mtime = time_sup.f.c.last_not_corrected_time;

		    ASSERT(time_sup.inf.c.not_corrected_moffset != stime - mtime);
		    new_offset = stime - mtime;
		    time_sup.inf.c.not_corrected_moffset = new_offset;
		}

	    }
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
	    else {
		mtime = finalize_corrected_time_offset(&tv);
		new_offset = ERTS_SEC_TO_MONOTONIC(tv.tv_sec);
		new_offset += ERTS_USEC_TO_MONOTONIC(tv.tv_usec);
		new_offset -= mtime;

	    }
#endif
	    new_offset = ERTS_MONOTONIC_TO_USEC(new_offset);
	    new_offset = ERTS_USEC_TO_MONOTONIC(new_offset);

	    set_time_offset(new_offset);
	    schedule_send_time_offset_changed_notifications(new_offset);

	    time_sup.inf.c.finalized_offset = ~0;
	    res = ERTS_TIME_OFFSET_PRELIMINARY;
	}

	erts_smp_mtx_unlock(&erts_get_time_mtx);

#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
	if (res == ERTS_TIME_OFFSET_PRELIMINARY
	    && time_sup.r.o.get_time == get_corrected_time) {
	    late_init_time_correction();
	}
#endif

	return res;
    }
    default:
	ERTS_INTERNAL_ERROR("Invalid time warp mode");
	return ERTS_TIME_OFFSET_VOLATILE;
    }
}

/* info functions */

void 
elapsed_time_both(UWord *ms_user, UWord *ms_sys, 
		  UWord *ms_user_diff, UWord *ms_sys_diff)
{
    UWord prev_total_user, prev_total_sys;
    UWord total_user, total_sys;
    SysTimes now;

    sys_times(&now);
    total_user = (now.tms_utime * 1000) / SYS_CLK_TCK;
    total_sys = (now.tms_stime * 1000) / SYS_CLK_TCK;

    if (ms_user != NULL)
	*ms_user = total_user;
    if (ms_sys != NULL)
	*ms_sys = total_sys;

    erts_smp_mtx_lock(&erts_timeofday_mtx);
    
    prev_total_user = (t_start.tms_utime * 1000) / SYS_CLK_TCK;
    prev_total_sys = (t_start.tms_stime * 1000) / SYS_CLK_TCK;
    t_start = now;
    
    erts_smp_mtx_unlock(&erts_timeofday_mtx);

    if (ms_user_diff != NULL)
	*ms_user_diff = total_user - prev_total_user;
	  
    if (ms_sys_diff != NULL)
	*ms_sys_diff = total_sys - prev_total_sys;
}


/* wall clock routines */

void 
wall_clock_elapsed_time_both(UWord *ms_total, UWord *ms_diff)
{
    ErtsMonotonicTime now, elapsed;

    erts_smp_mtx_lock(&erts_timeofday_mtx);

    now = time_sup.r.o.get_time();

    elapsed = ERTS_MONOTONIC_TO_MSEC(now);
    *ms_total = (UWord) elapsed;
    *ms_diff = (UWord) (elapsed - prev_wall_clock_elapsed);
    prev_wall_clock_elapsed = elapsed;

    erts_smp_mtx_unlock(&erts_timeofday_mtx);
}

/* get current time */
void 
get_time(int *hour, int *minute, int *second)
{
    time_t the_clock;
    struct tm *tm;
#ifdef HAVE_LOCALTIME_R
    struct tm tmbuf;
#endif
    
    the_clock = time((time_t *)0);
#ifdef HAVE_LOCALTIME_R
    tm = localtime_r(&the_clock, &tmbuf);
#else
    tm = localtime(&the_clock);
#endif
    *hour = tm->tm_hour;
    *minute = tm->tm_min;
    *second = tm->tm_sec;
}

/* get current date */
void 
get_date(int *year, int *month, int *day)
{
    time_t the_clock;
    struct tm *tm;
#ifdef HAVE_LOCALTIME_R
    struct tm tmbuf;
#endif


    the_clock = time((time_t *)0);
#ifdef HAVE_LOCALTIME_R
    tm = localtime_r(&the_clock, &tmbuf);
#else
    tm = localtime(&the_clock);
#endif
    *year = tm->tm_year + 1900;
    *month = tm->tm_mon +1;
    *day = tm->tm_mday;
}

/* get localtime */
void 
get_localtime(int *year, int *month, int *day, 
	      int *hour, int *minute, int *second)
{
    time_t the_clock;
    struct tm *tm;
#ifdef HAVE_LOCALTIME_R
    struct tm tmbuf;
#endif

    the_clock = time((time_t *)0);
#ifdef HAVE_LOCALTIME_R
    localtime_r(&the_clock, (tm = &tmbuf));
#else
    tm = localtime(&the_clock);
#endif
    *year = tm->tm_year + 1900;
    *month = tm->tm_mon +1;
    *day = tm->tm_mday;
    *hour = tm->tm_hour;
    *minute = tm->tm_min;
    *second = tm->tm_sec;
}


/* get universaltime */
void 
get_universaltime(int *year, int *month, int *day, 
		  int *hour, int *minute, int *second)
{
    time_t the_clock;
    struct tm *tm;
#ifdef HAVE_GMTIME_R
    struct tm tmbuf;
#endif

    the_clock = time((time_t *)0);
#ifdef HAVE_GMTIME_R
    gmtime_r(&the_clock, (tm = &tmbuf));
#else
    tm = gmtime(&the_clock);
#endif
    *year = tm->tm_year + 1900;
    *month = tm->tm_mon +1;
    *day = tm->tm_mday;
    *hour = tm->tm_hour;
    *minute = tm->tm_min;
    *second = tm->tm_sec;
}


/* days in month = 1, 2, ..., 12 */
static const int mdays[14] = {0, 31, 28, 31, 30, 31, 30,
                                 31, 31, 30, 31, 30, 31};

#define  IN_RANGE(a,x,b)  (((a) <= (x)) && ((x) <= (b)))
#define  is_leap_year(y)  (((((y) % 4) == 0) && \
                            (((y) % 100) != 0)) || \
                           (((y) % 400) == 0))

/* This is the earliest year we are sure to be able to handle
   on all platforms w/o problems */
#define  BASEYEAR       1902 

/* A more "clever" mktime
 * return  1, if successful
 * return -1, if not successful
 */

static int erl_mktime(time_t *c, struct tm *tm) {
    time_t clock;

    clock = mktime(tm);

    if (clock != -1) {
	*c = clock;
	return 1;
    }

    /* in rare occasions mktime returns -1
     * when a correct value has been entered
     *
     * decrease seconds with one second
     * if the result is -2, epochs should be -1
     */

    tm->tm_sec = tm->tm_sec - 1;
    clock = mktime(tm);
    tm->tm_sec = tm->tm_sec + 1;

    *c = -1;

    if (clock == -2) {
	return 1;
    }

    return -1;
}

/*
 * gregday
 *
 * Returns the number of days since Jan 1, 1600, if year is
 * greater of equal to 1600 , and month [1-12] and day [1-31] 
 * are within range. Otherwise it returns -1.
 */
static time_t gregday(int year, int month, int day)
{
  Sint ndays = 0;
  Sint gyear, pyear, m;
  
  /* number of days in previous years */
  gyear = year - 1600;
  if (gyear > 0) {
    pyear = gyear - 1;
    ndays = (pyear/4) - (pyear/100) + (pyear/400) + pyear*365 + 366;
  }
  /* number of days in all months preceeding month */
  for (m = 1; m < month; m++)
    ndays += mdays[m];
  /* Extra day if leap year and March or later */
  if (is_leap_year(year) && (month > 2))
    ndays++;
  ndays += day - 1;
  return (time_t) (ndays - 135140);        /* 135140 = Jan 1, 1970 */
}

#define SECONDS_PER_MINUTE  (60)
#define SECONDS_PER_HOUR    (60 * SECONDS_PER_MINUTE)
#define SECONDS_PER_DAY     (24 * SECONDS_PER_HOUR)

int seconds_to_univ(Sint64 time, Sint *year, Sint *month, Sint *day, 
	Sint *hour, Sint *minute, Sint *second) {

    Sint y,mi;
    Sint days = time / SECONDS_PER_DAY;
    Sint secs = time % SECONDS_PER_DAY;
    Sint tmp;

    if (secs < 0) {
	days--;
	secs += SECONDS_PER_DAY;
    }
    
    tmp     = secs % SECONDS_PER_HOUR;

    *hour   = secs / SECONDS_PER_HOUR;
    *minute = tmp  / SECONDS_PER_MINUTE;
    *second = tmp  % SECONDS_PER_MINUTE;

    days   += 719468;
    y       = (10000*((Sint64)days) + 14780) / 3652425; 
    tmp     = days - (365 * y + y/4 - y/100 + y/400);

    if (tmp < 0) {
	y--;
	tmp = days - (365*y + y/4 - y/100 + y/400);
    }
    mi = (100 * tmp + 52)/3060;
    *month = (mi + 2) % 12 + 1;
    *year  = y + (mi + 2) / 12;
    *day   = tmp - (mi * 306 + 5)/10 + 1;

    return 1;
}

int univ_to_seconds(Sint year, Sint month, Sint day, Sint hour, Sint minute, Sint second, Sint64 *time) {
    Sint days;

    if (!(IN_RANGE(1600, year, INT_MAX - 1) &&
          IN_RANGE(1, month, 12) &&
          IN_RANGE(1, day, (mdays[month] + 
                             (month == 2 
                              && (year % 4 == 0) 
                              && (year % 100 != 0 || year % 400 == 0)))) &&
          IN_RANGE(0, hour, 23) &&
          IN_RANGE(0, minute, 59) &&
          IN_RANGE(0, second, 59))) {
      return 0;
    }
 
    days   = gregday(year, month, day);
    *time  = SECONDS_PER_DAY;
    *time *= days;             /* don't try overflow it, it hurts */
    *time += SECONDS_PER_HOUR * hour;
    *time += SECONDS_PER_MINUTE * minute;
    *time += second;

    return 1;
}

#if defined(HAVE_TIME2POSIX) && defined(HAVE_DECL_TIME2POSIX) && \
    !HAVE_DECL_TIME2POSIX
extern time_t time2posix(time_t);
#endif

int 
local_to_univ(Sint *year, Sint *month, Sint *day, 
	      Sint *hour, Sint *minute, Sint *second, int isdst)
{
    time_t the_clock;
    struct tm *tm, t;
#ifdef HAVE_GMTIME_R
    struct tm tmbuf;
#endif
    
    if (!(IN_RANGE(BASEYEAR, *year, INT_MAX - 1) &&
          IN_RANGE(1, *month, 12) &&
          IN_RANGE(1, *day, (mdays[*month] + 
                             (*month == 2 
                              && (*year % 4 == 0) 
                              && (*year % 100 != 0 || *year % 400 == 0)))) &&
          IN_RANGE(0, *hour, 23) &&
          IN_RANGE(0, *minute, 59) &&
          IN_RANGE(0, *second, 59))) {
      return 0;
    }
    
    t.tm_year = *year - 1900;
    t.tm_mon = *month - 1;
    t.tm_mday = *day;
    t.tm_hour = *hour;
    t.tm_min = *minute;
    t.tm_sec = *second;
    t.tm_isdst = isdst;

    /* the nature of mktime makes this a bit interesting,
     * up to four mktime calls could happen here
     */

    if (erl_mktime(&the_clock, &t) < 0) {
	if (isdst) {
	    /* If this is a timezone without DST and the OS (correctly)
	       refuses to give us a DST time, we simulate the Linux/Solaris
	       behaviour of giving the same data as if is_dst was not set. */
	    t.tm_isdst = 0;
	    if (erl_mktime(&the_clock, &t) < 0) {
		/* Failed anyway, something else is bad - will be a badarg */
		return 0;
	    }
	} else {
	    /* Something else is the matter, badarg. */
	    return 0;
	}
    }

#ifdef HAVE_TIME2POSIX
    the_clock = time2posix(the_clock);
#endif

#ifdef HAVE_GMTIME_R
    tm = gmtime_r(&the_clock, &tmbuf);
#else
    tm = gmtime(&the_clock);
#endif
    if (!tm) {
      return 0;
    }
    *year = tm->tm_year + 1900;
    *month = tm->tm_mon +1;
    *day = tm->tm_mday;
    *hour = tm->tm_hour;
    *minute = tm->tm_min;
    *second = tm->tm_sec;
    return 1;
}
#if defined(HAVE_POSIX2TIME) && defined(HAVE_DECL_POSIX2TIME) && \
    !HAVE_DECL_POSIX2TIME
extern time_t posix2time(time_t);
#endif

int 
univ_to_local(Sint *year, Sint *month, Sint *day, 
	      Sint *hour, Sint *minute, Sint *second)
{
    time_t the_clock;
    struct tm *tm;
#ifdef HAVE_LOCALTIME_R
    struct tm tmbuf;
#endif
    
    if (!(IN_RANGE(BASEYEAR, *year, INT_MAX - 1) &&
          IN_RANGE(1, *month, 12) &&
          IN_RANGE(1, *day, (mdays[*month] + 
                             (*month == 2 
                              && (*year % 4 == 0) 
                              && (*year % 100 != 0 || *year % 400 == 0)))) &&
          IN_RANGE(0, *hour, 23) &&
          IN_RANGE(0, *minute, 59) &&
          IN_RANGE(0, *second, 59))) {
      return 0;
    }
    
    the_clock = *second + 60 * (*minute + 60 * (*hour + 24 *
                                            gregday(*year, *month, *day)));
#ifdef HAVE_POSIX2TIME
    /*
     * Addition from OpenSource - affects FreeBSD.
     * No valid test case /PaN
     *
     * leap-second correction performed
     * if system is configured so;
     * do nothing if not
     * See FreeBSD 6.x and 7.x
     * /usr/src/lib/libc/stdtime/localtime.c
     * for the details
     */
    the_clock = posix2time(the_clock);
#endif

#ifdef HAVE_LOCALTIME_R
    tm = localtime_r(&the_clock, &tmbuf);
#else
    tm = localtime(&the_clock);
#endif
    if (tm) {
	*year   = tm->tm_year + 1900;
	*month  = tm->tm_mon +1;
	*day    = tm->tm_mday;
	*hour   = tm->tm_hour;
	*minute = tm->tm_min;
	*second = tm->tm_sec;
	return 1;
    }
    return 0;
}

/* get a timestamp */
void
get_now(Uint* megasec, Uint* sec, Uint* microsec)
{
    ErtsMonotonicTime now_megasec, now_sec, now, mtime, time_offset;
    
    mtime = time_sup.r.o.get_time();
    time_offset = get_time_offset();
    now = ERTS_MONOTONIC_TO_USEC(mtime + time_offset);

    erts_smp_mtx_lock(&erts_timeofday_mtx);

    /* Make sure now time is later than last time */
    if (now <= previous_now)
	now = previous_now + 1;

    previous_now = now;
    
    erts_smp_mtx_unlock(&erts_timeofday_mtx);

    now_megasec = now / ERTS_MONOTONIC_TIME_TERA;
    now_sec = now / ERTS_MONOTONIC_TIME_MEGA;
    *megasec = (Uint) now_megasec;
    *sec = (Uint) (now_sec - now_megasec*ERTS_MONOTONIC_TIME_MEGA);
    *microsec = (Uint) (now - now_sec*ERTS_MONOTONIC_TIME_MEGA);

    ASSERT(((ErtsMonotonicTime) *megasec)*ERTS_MONOTONIC_TIME_TERA
	   + ((ErtsMonotonicTime) *sec)*ERTS_MONOTONIC_TIME_MEGA
	   + ((ErtsMonotonicTime) *microsec) == now);
}

ErtsMonotonicTime
erts_get_monotonic_time(void)
{
    return time_sup.r.o.get_time();
}

void
get_sys_now(Uint* megasec, Uint* sec, Uint* microsec)
{
    SysTimeval now;
    
    sys_gettimeofday(&now);
    
    *megasec = (Uint) (now.tv_sec / 1000000);
    *sec = (Uint) (now.tv_sec % 1000000);
    *microsec = (Uint) (now.tv_usec);
}

#ifdef HAVE_ERTS_NOW_CPU
void erts_get_now_cpu(Uint* megasec, Uint* sec, Uint* microsec) {
  SysCpuTime t;
  SysTimespec tp;

  sys_get_proc_cputime(t, tp);
  *microsec = (Uint)(tp.tv_nsec / 1000);
  t = (tp.tv_sec / 1000000);
  *megasec = (Uint)(t % 1000000);
  *sec = (Uint)(tp.tv_sec % 1000000);
}
#endif

#include "big.h"

void
erts_monitor_time_offset(Eterm id, Eterm ref)
{
    erts_smp_mtx_lock(&erts_get_time_mtx);
    erts_add_monitor(&time_offset_monitors, MON_TIME_OFFSET, ref, id, NIL);
    no_time_offset_monitors++;
    erts_smp_mtx_unlock(&erts_get_time_mtx);
}

int
erts_demonitor_time_offset(Eterm ref)
{
    int res;
    ErtsMonitor *mon;
    ASSERT(is_internal_ref(ref));
    erts_smp_mtx_lock(&erts_get_time_mtx);
    mon = erts_remove_monitor(&time_offset_monitors, ref);
    if (!mon)
	res = 0;
    else {
	ASSERT(no_time_offset_monitors > 0);
	no_time_offset_monitors--;
	res = 1;
    }
    erts_smp_mtx_unlock(&erts_get_time_mtx);
    if (res)
	erts_destroy_monitor(mon);
    return res;
}

typedef struct {
    Eterm pid;
    Eterm ref;
    Eterm heap[REF_THING_SIZE];
} ErtsTimeOffsetMonitorInfo;

typedef struct {
    Uint ix;
    ErtsTimeOffsetMonitorInfo *to_mon_info;
} ErtsTimeOffsetMonitorContext;

static void
save_time_offset_monitor(ErtsMonitor *mon, void *vcntxt)
{
    ErtsTimeOffsetMonitorContext *cntxt;
    Eterm *from_hp, *to_hp;
    Uint mix;
    int hix;

    cntxt = (ErtsTimeOffsetMonitorContext *) vcntxt;
    mix = (cntxt->ix)++;
    cntxt->to_mon_info[mix].pid = mon->pid;
    to_hp = &cntxt->to_mon_info[mix].heap[0];

    ASSERT(is_internal_ref(mon->ref));
    from_hp = internal_ref_val(mon->ref);
    ASSERT(thing_arityval(*from_hp) + 1 == REF_THING_SIZE);

    for (hix = 0; hix < REF_THING_SIZE; hix++)
	to_hp[hix] = from_hp[hix];

    cntxt->to_mon_info[mix].ref
	= make_internal_ref(&cntxt->to_mon_info[mix].heap[0]);

}

static void
send_time_offset_changed_notifications(void *new_offsetp)
{
    ErtsMonotonicTime new_offset;
    ErtsTimeOffsetMonitorInfo *to_mon_info;
    Uint no_monitors;
    char *tmp = NULL;

#ifdef ARCH_64
    new_offset = (ErtsMonotonicTime) new_offsetp;
#else
    new_offset = *((ErtsMonotonicTime *) new_offsetp);
    erts_free(ERTS_ALC_T_NEW_TIME_OFFSET, new_offsetp);
#endif
    new_offset -= ERTS_MONOTONIC_OFFSET_NATIVE;

    erts_smp_mtx_lock(&erts_get_time_mtx);

    no_monitors = no_time_offset_monitors;
    if (no_monitors) {
	ErtsTimeOffsetMonitorContext cntxt;
	Uint alloc_sz;
	
	/* Monitor info array size */
	alloc_sz = no_monitors*sizeof(ErtsTimeOffsetMonitorInfo);
	/* + template max size */
	alloc_sz += 6*sizeof(Eterm); /* 5-tuple */
	alloc_sz += ERTS_MAX_SINT64_HEAP_SIZE*sizeof(Eterm); /* max offset size */
	tmp = erts_alloc(ERTS_ALC_T_TMP, alloc_sz);

	to_mon_info = (ErtsTimeOffsetMonitorInfo *) tmp;
	cntxt.ix = 0;
	cntxt.to_mon_info = to_mon_info;

	erts_doforall_monitors(time_offset_monitors,
			       save_time_offset_monitor,
			       &cntxt);

	ASSERT(cntxt.ix == no_monitors);
    }

    erts_smp_mtx_unlock(&erts_get_time_mtx);

    if (no_monitors) {
	Eterm *hp, *patch_refp, new_offset_term, message_template;
	Uint mix, hsz;

	/* Make message template */

	hp = (Eterm *) (tmp + no_monitors*sizeof(ErtsTimeOffsetMonitorInfo));

	hsz = 6; /* 5-tuple */
	hsz += REF_THING_SIZE;
	hsz += ERTS_SINT64_HEAP_SIZE(new_offset);

	if (IS_SSMALL(new_offset))
	    new_offset_term = make_small(new_offset);
	else
	    new_offset_term = erts_sint64_to_big(new_offset, &hp);
	message_template = TUPLE5(hp,
				  am_CHANGE,
				  THE_NON_VALUE, /* Patch point for ref */
				  am_time_offset,
				  am_clock_service,
				  new_offset_term);
	patch_refp = &hp[2];

	ASSERT(*patch_refp == THE_NON_VALUE);

	for (mix = 0; mix < no_monitors; mix++) {
	    Process *rp = erts_proc_lookup(to_mon_info[mix].pid);
	    if (rp) {
		Eterm ref = to_mon_info[mix].ref;
		ErtsProcLocks rp_locks = ERTS_PROC_LOCK_LINK;
		erts_smp_proc_lock(rp, ERTS_PROC_LOCK_LINK);
		if (erts_lookup_monitor(ERTS_P_MONITORS(rp), ref)) {
		    ErlHeapFragment *bp;
		    ErlOffHeap *ohp;
		    Eterm message;

		    hp = erts_alloc_message_heap(hsz, &bp, &ohp, rp, &rp_locks);
		    *patch_refp = ref;
		    ASSERT(hsz == size_object(message_template));
		    message = copy_struct(message_template, hsz, &hp, ohp);
		    erts_queue_message(rp, &rp_locks, bp, message, NIL
#ifdef USE_VM_PROBES
				       , NIL
#endif
			);
		}
		erts_smp_proc_unlock(rp, rp_locks);
	    }
	}

	erts_free(ERTS_ALC_T_TMP, tmp);
    }
}

static void
schedule_send_time_offset_changed_notifications(ErtsMonotonicTime new_offset)
{
#ifdef ARCH_64
    void *new_offsetp = (void *) new_offset;
    ASSERT(sizeof(void *) == sizeof(ErtsMonotonicTime));
#else
    void *new_offsetp = erts_alloc(ERTS_ALC_T_NEW_TIME_OFFSET,
				   sizeof(ErtsMonotonicTime));
    *((ErtsMonotonicTime *) new_offsetp) = new_offset;
#endif
    erts_schedule_misc_aux_work(1,
				send_time_offset_changed_notifications,
				new_offsetp);
}

static ERTS_INLINE Eterm
make_time_val(Process *c_p, ErtsMonotonicTime time_val)
{
    Sint64 val = (Sint64) time_val;
    Eterm *hp;
    Uint sz;

    if (IS_SSMALL(val))
	return make_small(val);

    sz = ERTS_SINT64_HEAP_SIZE(val);
    hp = HAlloc(c_p, sz);
    return erts_sint64_to_big(val, &hp);
}

Eterm
erts_get_monotonic_start_time(struct process *c_p)
{
    return make_time_val(c_p, ERTS_MONOTONIC_OFFSET_NATIVE);
}

static Eterm
bld_monotonic_time_source(Uint **hpp, Uint *szp, Sint64 os_mtime)
{
#ifndef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
    return NIL;
#else
    int i = 0;
    Eterm k[5];
    Eterm v[5];

    if (time_sup.r.o.os_monotonic_disable)
	return NIL;

    k[i] = erts_bld_atom(hpp, szp, "function");
    v[i++] = erts_bld_atom(hpp, szp, time_sup.r.o.os_monotonic_func);

    if (time_sup.r.o.os_monotonic_clock_id) {
	k[i] = erts_bld_atom(hpp, szp, "clock_id");
	v[i++] = erts_bld_atom(hpp, szp, time_sup.r.o.os_monotonic_clock_id);
    }

    if (time_sup.r.o.os_monotonic_resolution) {
	k[i] = erts_bld_atom(hpp, szp, "resolution");
	v[i++] = erts_bld_uint64(hpp, szp, time_sup.r.o.os_monotonic_resolution);
    }

    k[i] = erts_bld_atom(hpp, szp, "parallel");
    v[i++] = time_sup.r.o.os_monotonic_locked ? am_no : am_yes;

    k[i] = erts_bld_atom(hpp, szp, "time");
    v[i++] = erts_bld_sint64(hpp, szp, os_mtime);

    return erts_bld_2tup_list(hpp, szp, (Sint) i, k, v);
#endif
}

Eterm
erts_monotonic_time_source(struct process *c_p)
{
    Uint hsz = 0;
    Eterm *hp = NULL;
    Sint64 os_mtime = 0;
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
    if (!time_sup.r.o.os_monotonic_disable)
	os_mtime = (Sint64) erts_os_monotonic_time();
#endif

    bld_monotonic_time_source(NULL, &hsz, os_mtime);
    if (hsz)
	hp = HAlloc(c_p, hsz);
    return bld_monotonic_time_source(&hp, NULL, os_mtime);
}


#include "bif.h"

static ERTS_INLINE Eterm
time_unit_conversion(Process *c_p, Eterm term, ErtsMonotonicTime val, ErtsMonotonicTime muloff)
{
    ErtsMonotonicTime result;
    BIF_RETTYPE ret;

    if (val < 0)
	goto trap_to_erlang_code;

    /* Convert to common user specified time units */
    switch (term) {
    case am_seconds:
    case make_small(1):
	result = ERTS_MONOTONIC_TO_SEC(val) + muloff*ERTS_MONOTONIC_OFFSET_SEC;
	ERTS_BIF_PREP_RET(ret, make_time_val(c_p, result));
	break;
    case am_milli_seconds:
    case make_small(1000):
	result = ERTS_MONOTONIC_TO_MSEC(val) + muloff*ERTS_MONOTONIC_OFFSET_MSEC;
	ERTS_BIF_PREP_RET(ret, make_time_val(c_p, result));
	break;
    case am_micro_seconds:
    case make_small(1000*1000):
	result = ERTS_MONOTONIC_TO_USEC(val) + muloff*ERTS_MONOTONIC_OFFSET_USEC;
	ERTS_BIF_PREP_RET(ret, make_time_val(c_p, result));
	break;
#ifdef ARCH_64
    case am_nano_seconds:
    case make_small(1000*1000*1000):
	result = ERTS_MONOTONIC_TO_NSEC(val) + muloff*ERTS_MONOTONIC_OFFSET_NSEC;
	ERTS_BIF_PREP_RET(ret, make_time_val(c_p, result));
	break;
#endif
    default: {
	Eterm value, native_res;
#ifndef ARCH_64
	Sint user_res;
	if (term == am_nano_seconds)
	    goto to_nano_seconds;
	if (term_to_Sint(term, &user_res)) {
	    if (user_res == 1000*1000*1000) {
	    to_nano_seconds:
		result = (ERTS_MONOTONIC_TO_NSEC(val)
			  + muloff*ERTS_MONOTONIC_OFFSET_NSEC);
		ERTS_BIF_PREP_RET(ret, make_time_val(c_p, result));
		break;
	    }
	    if (user_res <= 0)
		goto badarg;
	}
#else
	if (is_small(term)) {
	    if (signed_val(term) <= 0)
		goto badarg;
	}
#endif
	else if (is_big(term)) {
	    if (big_sign(term))
		goto badarg;
	}
	else {
	badarg:
	    ERTS_BIF_PREP_ERROR(ret, c_p, BADARG);
	    break;
	}

	trap_to_erlang_code:
	/* Do it in erlang code instead; pass along values to use... */
	value = make_time_val(c_p, val + muloff*ERTS_MONOTONIC_OFFSET_NATIVE);
	native_res = make_time_val(c_p, ERTS_MONOTONIC_TIME_UNIT);

	ERTS_BIF_PREP_TRAP3(ret, erts_convert_time_unit_trap, c_p,
			    value, native_res, term);

	break;
    }
    }

    return ret;
}

/* Built in functions */

BIF_RETTYPE monotonic_time_0(BIF_ALIST_0)
{
    ErtsMonotonicTime mtime = time_sup.r.o.get_time();
    mtime += ERTS_MONOTONIC_OFFSET_NATIVE;
    BIF_RET(make_time_val(BIF_P, mtime));
}

BIF_RETTYPE monotonic_time_1(BIF_ALIST_1)
{
    BIF_RET(time_unit_conversion(BIF_P, BIF_ARG_1, time_sup.r.o.get_time(), 1));
}

BIF_RETTYPE system_time_0(BIF_ALIST_0)
{
    ErtsMonotonicTime mtime, offset;
    mtime = time_sup.r.o.get_time();
    offset = get_time_offset();
    BIF_RET(make_time_val(BIF_P, mtime + offset));
}

BIF_RETTYPE system_time_1(BIF_ALIST_0)
{
    ErtsMonotonicTime mtime, offset;
    mtime = time_sup.r.o.get_time();
    offset = get_time_offset();
    BIF_RET(time_unit_conversion(BIF_P, BIF_ARG_1, mtime + offset, 0));
}

BIF_RETTYPE erts_internal_time_unit_0(BIF_ALIST_0)
{
    BIF_RET(make_time_val(BIF_P, ERTS_MONOTONIC_TIME_UNIT));
}

BIF_RETTYPE time_offset_0(BIF_ALIST_0)
{
    ErtsMonotonicTime time_offset = get_time_offset();
    time_offset -= ERTS_MONOTONIC_OFFSET_NATIVE;
    BIF_RET(make_time_val(BIF_P, time_offset));
}

BIF_RETTYPE time_offset_1(BIF_ALIST_1)
{
    BIF_RET(time_unit_conversion(BIF_P, BIF_ARG_1, get_time_offset(), -1));
}


BIF_RETTYPE timestamp_0(BIF_ALIST_0)
{
    Eterm *hp, res;
    ErtsMonotonicTime stime, mtime, all_sec, offset;
    Uint mega_sec, sec, micro_sec;

    mtime = time_sup.r.o.get_time();
    offset = get_time_offset();
    stime = ERTS_MONOTONIC_TO_USEC(mtime + offset);
    all_sec = stime / ERTS_MONOTONIC_TIME_MEGA;
    mega_sec = (Uint) (stime / ERTS_MONOTONIC_TIME_TERA);
    sec = (Uint) (all_sec - (((ErtsMonotonicTime) mega_sec)
			     * ERTS_MONOTONIC_TIME_MEGA));
    micro_sec = (Uint) (stime - all_sec*ERTS_MONOTONIC_TIME_MEGA);

    ASSERT(((ErtsMonotonicTime) mega_sec)*ERTS_MONOTONIC_TIME_TERA
	   + ((ErtsMonotonicTime) sec)*ERTS_MONOTONIC_TIME_MEGA
	   + micro_sec == stime);

    /*
     * Mega seconds is the only value that potentially
     * ever could be a bignum. However, that wont happen
     * during at least the next 4 million years...
     *
     * (System time will also have wrapped in the
     * 64-bit integer before we get there...)
     */

    ASSERT(IS_USMALL(0, mega_sec));
    ASSERT(IS_USMALL(0, sec));
    ASSERT(IS_USMALL(0, micro_sec));

    hp = HAlloc(BIF_P, 4);
    res = TUPLE3(hp,
		 make_small(mega_sec),
		 make_small(sec),
		 make_small(micro_sec));
    BIF_RET(res);
}

BIF_RETTYPE os_system_time_0(BIF_ALIST_0)
{
    ErtsMonotonicTime stime;
    SysTimeval tod;
    sys_gettimeofday(&tod);
    stime = ERTS_SEC_TO_MONOTONIC(tod.tv_sec);
    stime += ERTS_USEC_TO_MONOTONIC(tod.tv_usec);
    BIF_RET(make_time_val(BIF_P, stime));
}

BIF_RETTYPE os_system_time_1(BIF_ALIST_0)
{
    ErtsMonotonicTime stime;
    SysTimeval tod;
    sys_gettimeofday(&tod);
    stime = ERTS_SEC_TO_MONOTONIC(tod.tv_sec);
    stime += ERTS_USEC_TO_MONOTONIC(tod.tv_usec);
    BIF_RET(time_unit_conversion(BIF_P, BIF_ARG_1, stime, 0));
}