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

                   
  
                                                        
  


                                                                   
  






                                                                           
  



                 

                                
 

                                        






                     

                                 

                       
 
                                         
                                        

                                                        




                                                                                            
 











                                                       
 
                                 
           

                                                                              





                                          
                                    





                                        
      




                                     



                                     





                                     


                
                                                                    








                                                                            
                                   









                                  
                                          









                                         




                                           
                                 







                                          
                              



                                          
                         
                                            


                                           




























                                                                                                     
 

                                                             

 

                                          
 
                                                                               


                       
                                         
 
                                                                               

 

                                    
 
                                                                                   
 
 










                                                                   
 











                                          
 


                                                                  
 
                                                               

                                                              





















                                                                   


                                                              

                                                       
 

                                                                 

                                                                      





                                                                  

 

                                           
 
                               
                                       




                                                       

                                                                    
          
                                                                       
                                      
                                                              
                                                    
     
 


                                                         










                                                          

 

















                                                         






                                                                           
        








                                                                             



      









                                                             
           
                                  
 

                                                          
                                           
                                       
                                                                  
                                            
                 

                                                                      


                                                       
                                        
 
                                                


                                                         
                               
                                  
                                                          
 
                                                               
                                                             


                                        

                                 







                                                                               
                                   
 
                                                           

                                                        





                                                                     
                                       















                                                                                
                                       



                                     
                                        
                                                   
                                   
                                                     



                                                                
                                                       
                                   
                                                    



                                                                 
     
                                       
                        
                                                               
                                                          



                                                                
                                                       
                                   
                                                    








                                                                 
                                              
                        
                                                                
                                                         


                                                                 
         
                                                        
                                   
                                                     







                                                                

     
                              






                                                                         

                                                                
                                                                      

                                                              





                                                       

                                                         
                                          


                                                 
                                                  
                 
                                                       














                                                     











                                                                               
                                          
             





                                                                        
                 





                                                           

                                         














                                                                     






                                                                            




                                                                           
             
         
     

















                                                                             

                                                   

                             
                                                                                    

                                                                              
                                                                                    

                             

         

                                                                          

                                                                       
                                                                           
     
 

                                                      


                                        

                                         



                                          
 

                                                            
 
                                            
 
                                               
                                                    
 

                                                 
                                                     
           
                      
 




                                                 
                                                                 
                                                                 
 


                                                


                                                                           
 


                                                              








                                                       
 
 



                                                                

 
           
                              
 
                                                          
                                                           
                                                     



























                                                                     








                                                                      
 
 
           
                                       
 
                                
                                                                     
                                           
           





                                             
                                  


                                   






                                                                       








                                                           
                                                               



                                                 

     

                                                               
                
                                                          
 
                                 

 
                        
                                                      

                               
                                       
                                                                      


                                                       
                                     
 
                                                


                                                         
                               
                                  
                                                          
 
                                                        
                                                        


           
                                                  
 
                             
                               
                                           
 
                          
                                                                  







                                                                              

                                                              
                                       
     


                                           











                                                                
 
 
                                                
 

                                                     
                                   
 
                                          
 
                                  















                                                                            
             
         
 
     





                                                                  

 




                                                                
 


                                          
                                                   
     
             
      
 
 

                              
 
                                   

 
                                 
 

                                           
 
                                      
 

                                                   
 




                                                  
 
                                          

                                                    

                                                           









                                                              
      







                                                           
 

    
                                                                        
 
                                                                  
                                          
                                                       

      

                     

                         

                                                              
                                                        





                                                      
                                                                          
        

                                                                          


                                          




                                                              
              




                                                                     
                                      
                   
                                                   


                                                 
     
          



                                                                         
                                             

      
 

                                                               
                                                             

                                                                    
                                                               
                                                             

                                                                    
                                                               
                                                             

                                                                    
                                                              
                                                             

                                                                    
                            



                                                    
     
 
      
 




                                                               
 

                                                                          


                                                                  

                                                                 











































                                                                              
                                 














                                                                           

      




                                                                  
     
                                               





                                                                            

                                             
                                                       
                                                     
                                      
                                       








                                                                      
    
                                                                   
                                                                   



                                                            

                                                                        
                                                
 





                                                                     

        
      


                                                       
                                                             
                                              





                                                      
 
                                                             
 


                             
 
                                       
     



                             
                              

 














                                                                            










                                                


                                                                           































                                                                       
                                                                             
                                                




                                          
                                                                

















                                                                                  


                                                               







                                                                        
                                                                             




                                                







                                                      


                    

                                                          
 

                                          





























                                                               
                                                             
 
                                   


                                           
                                  
                                 
 



                                                           















                                             
                                         




















                                         
                                         




































































                                                         


                                                             
 

































                                                 







                                                              
                                                   
 

                       













                                                                    
                                                                     

 


                                                     
 


                                                                     










                                       





                                        
                                                        











                                                     

 




















                                                                                                         
 

             
 




                                                                  




























                                                                             





                                                         




                                                                             
                                                 







                                                                             




                                      
                    
                                      


                            


               







                               



                                                                  








































                                                                             
                                         


                               









                                     

 



                                                 
                                                                    
    

                                    
                                   

                                                      
                                           





                                                    

                                             
 











                                                                   
                                                
 

                                                      
                 

 






























                                                                            




                                                     









                                                 

 



                                                                 
 






                                        
 
                
 



























                                                                           
 
 









                                           
 

                                                        
 

























                                                              
                                                                               
















                                                       
        




















































                                                                                  
                                    


                                    

                                                                 


                                                                           
                                                                        


                                                   
         

                                       


     

                                                                             
 










                                                                       

 

                                                       
 


                                   
 

                               
 


                                        

 


                                                  






                                                                  
 
 






                                                                 

               
 
                                               


                                               

                                                                
 
                                                  
                                                   

                                                                        

     
                                                 

                                                                        

                                               
                                                                      

                                               
                                                                    





                                                        
 







                                               
                                                








                                                          










































                                                                     


































                                                                                               
      















































                                                                              











































































































































                                                                         




                                                      
                                                           





                                          


                                                              






                                      
                                                           







                                                  
                                                           























                                                                           
                                    



                                    
                                                           
 
                                                                     























                                                        
                                                 


                                         
                                         
 
                                                 

                                                              
 


                              
                                                           



                                                          
                                                                
 
/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 1999-2016. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 */

/*
 * Support routines for the time
 */

/* #define ERTS_TIME_CORRECTION_PRINT */

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

#include "sys.h"
#include "erl_vm.h"
#include "global.h"
#define ERTS_WANT_TIMER_WHEEL_API
#include "erl_time.h"
#include "erl_driver.h"
#include "erl_nif.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 init_time_napi(void);
static void
schedule_send_time_offset_changed_notifications(ErtsMonotonicTime new_offset);

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_corrected_monotonic_time;
    int os_monotonic_time_disable;
    char *os_monotonic_time_func;
    char *os_monotonic_time_clock_id;
    int os_monotonic_time_locked;
    Uint64 os_monotonic_time_resolution;
    Uint64 os_monotonic_time_extended;
#endif
    char *os_system_time_func;
    char *os_system_time_clock_id;
    int os_system_time_locked;
    Uint64 os_system_time_resolution;
    Uint64 os_system_time_extended;
    struct {
	ErtsMonotonicTime large_diff;
	ErtsMonotonicTime small_diff;
    } adj;
    struct {
	ErtsMonotonicTime error;
	ErtsMonotonicTime resolution;
	int intervals;
	int use_avg;
    } drift_adj;
};

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

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

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

typedef struct {
    ErtsMonotonicCorrectionInstance prev;
    ErtsMonotonicCorrectionInstance curr;
} ErtsMonotonicCorrectionInstances;

typedef struct {
    ErtsMonotonicCorrectionInstances insts;
    ErtsMonotonicDriftData drift;
    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;
	ErtsTWheelTimer timer;
	ErtsMonotonicCorrectionData cdata;
    } parmon;
    ErtsMonotonicTime minit;
#endif
    ErtsSystemTime sinit;
    ErtsMonotonicTime not_corrected_moffset;
    erts_smp_atomic64_t offset;
    ErtsMonotonicTime shadow_offset;
    erts_smp_atomic32_t preliminary_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)
{
    ErtsSystemTime stime = erts_os_system_time();
    return (erts_approx_time_t) ERTS_MONOTONIC_TO_SEC(stime);
}

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

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

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

static ERTS_INLINE void
update_last_mtime(ErtsSchedulerData *esdp, ErtsMonotonicTime mtime)
{
    if (!esdp)
	esdp = erts_get_scheduler_data();
    if (esdp) {
	ASSERT(mtime >= esdp->last_monotonic_time);
	esdp->last_monotonic_time = mtime;
	esdp->check_time_reds = 0;
    }
}

#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(50)
#define ERTS_TIME_DRIFT_MIN_ADJ_DIFF ERTS_USEC_TO_MONOTONIC(5)

/*
 * Maximum drift of the OS monotonic clock expected.
 *
 * We use 1 milli second per second. If the monotonic
 * clock drifts more than this we will fail to adjust for
 * drift, and error correction will kick in instead.
 * If it is larger than this, one could argue that the
 * primitive is to poor to be used...
 */
#define ERTS_MAX_MONOTONIC_DRIFT ERTS_MSEC_TO_MONOTONIC(1)

/*
 * We assume that precision is 32 times worse than the
 * resolution. This is a wild guess, but there are no
 * practical way to determine actual precision.
 */
#define ERTS_ASSUMED_PRECISION_DROP 32

#define ERTS_MIN_MONOTONIC_DRIFT_MEASUREMENT \
    (ERTS_SHORT_TIME_CORRECTION_CHECK - 2*ERTS_MAX_MONOTONIC_DRIFT)


static ERTS_INLINE ErtsMonotonicTime
calc_corrected_erl_mtime(ErtsMonotonicTime os_mtime,
			 ErtsMonotonicCorrectionInstance *cip,
			 ErtsMonotonicTime *os_mdiff_p,
			 int os_drift_corrected)
{
    ErtsMonotonicTime erl_mtime, diff = os_mtime - cip->os_mtime;
    ERTS_TIME_ASSERT(diff >= 0);
    if (!os_drift_corrected)
	diff += (cip->correction.drift*diff)/ERTS_MONOTONIC_TIME_UNIT;
    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 ERTS_INLINE ErtsMonotonicTime
read_corrected_time(int os_drift_corrected)
{
    ErtsMonotonicTime os_mtime;
    ErtsMonotonicCorrectionInstance ci;

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

    os_mtime = erts_os_monotonic_time();

    if (os_mtime >= time_sup.inf.c.parmon.cdata.insts.curr.os_mtime)
	ci = time_sup.inf.c.parmon.cdata.insts.curr;
    else {
	if (os_mtime < time_sup.inf.c.parmon.cdata.insts.prev.os_mtime)
	    erts_exit(ERTS_ABORT_EXIT,
		     "OS monotonic time stepped backwards\n");
	ci = time_sup.inf.c.parmon.cdata.insts.prev;
    }

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

    return calc_corrected_erl_mtime(os_mtime, &ci, NULL,
				    os_drift_corrected);
}

static ErtsMonotonicTime get_os_drift_corrected_time(void)
{
    return read_corrected_time(!0);
}

static ErtsMonotonicTime get_corrected_time(void)
{
    return read_corrected_time(0);
}

#ifdef ERTS_TIME_CORRECTION_PRINT

static ERTS_INLINE void
print_correction(int change,
		 ErtsMonotonicTime sdiff,
		 ErtsMonotonicTime old_ecorr,
		 ErtsMonotonicTime old_dcorr,
		 ErtsMonotonicTime new_ecorr,
		 ErtsMonotonicTime new_dcorr,
		 Uint tmo)
{
    ErtsMonotonicTime usec_sdiff;
    if (sdiff < 0)
	usec_sdiff = -1*ERTS_MONOTONIC_TO_USEC(-1*sdiff);
    else
	usec_sdiff = ERTS_MONOTONIC_TO_USEC(sdiff);

    if (!change)
	erts_fprintf(stderr,
		     "sdiff = %b64d usec : [ec=%b64d ppm, dc=%b64d ppb] : "
		     "tmo = %bpu msec\r\n",
		     usec_sdiff,
		     (1000000*old_ecorr) / ERTS_TCORR_ERR_UNIT,
		     (1000000000*old_dcorr) / ERTS_MONOTONIC_TIME_UNIT,
		     tmo);
    else
	erts_fprintf(stderr,
		     "sdiff = %b64d usec : [ec=%b64d ppm, dc=%b64d ppb] "
		     "-> [ec=%b64d ppm, dc=%b64d ppb] : tmo = %bpu msec\r\n",
		     usec_sdiff,
		     (1000000*old_ecorr) / ERTS_TCORR_ERR_UNIT,
		     (1000000000*old_dcorr) / ERTS_MONOTONIC_TIME_UNIT,
		     (1000000*new_ecorr) / ERTS_TCORR_ERR_UNIT,
		     (1000000000*new_dcorr) / ERTS_MONOTONIC_TIME_UNIT,
		     tmo);
}

#endif

static ERTS_INLINE ErtsMonotonicTime
get_timeout_pos(ErtsMonotonicTime now, ErtsMonotonicTime tmo)
{
    ErtsMonotonicTime tpos;
    tpos = ERTS_MONOTONIC_TO_CLKTCKS(now - 1);
    tpos += ERTS_MSEC_TO_CLKTCKS(tmo);
    tpos += 1;
    return tpos;
}

static void
check_time_correction(void *vesdp)
{
    int init_drift_adj = !vesdp;
    ErtsSchedulerData *esdp = (ErtsSchedulerData *) vesdp;
    ErtsMonotonicCorrection new_correction;
    ErtsMonotonicCorrectionInstance ci;
    ErtsMonotonicTime mdiff, sdiff, os_mtime, erl_mtime, os_stime,
	erl_stime, time_offset, timeout_pos;
    Uint timeout;
    int os_drift_corrected = time_sup.r.o.os_corrected_monotonic_time;
    int set_new_correction = 0, begin_short_intervals = 0;

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

    erts_os_times(&os_mtime, &os_stime);

    ci = time_sup.inf.c.parmon.cdata.insts.curr;

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

    if (os_mtime < ci.os_mtime)
	erts_exit(ERTS_ABORT_EXIT,
		 "OS monotonic time stepped backwards\n");

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

    sdiff = erl_stime - os_stime;

    if (time_sup.inf.c.shadow_offset) {
	ERTS_TIME_ASSERT(time_sup.r.o.warp_mode == ERTS_SINGLE_TIME_WARP_MODE);
	if (erts_smp_atomic32_read_nob(&time_sup.inf.c.preliminary_offset))
	    sdiff += time_sup.inf.c.shadow_offset;
	else
	    time_sup.inf.c.shadow_offset = 0;
    }

    new_correction = ci.correction;

    if (time_sup.r.o.warp_mode == ERTS_MULTI_TIME_WARP_MODE
	&& (sdiff < -2*time_sup.r.o.adj.small_diff
	    || 2*time_sup.r.o.adj.small_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 (ci.correction.error != 0) {
	    set_new_correction = 1;
	    new_correction.error = 0;
	}
    }
    else if ((time_sup.r.o.warp_mode == ERTS_SINGLE_TIME_WARP_MODE
	      && erts_smp_atomic32_read_nob(&time_sup.inf.c.preliminary_offset))
	     && (sdiff < -2*time_sup.r.o.adj.small_diff
		 || 2*time_sup.r.o.adj.small_diff < sdiff)) {
	/*
	 * System time diff exeeded limits; change shadow offset
	 * and let OS system time leap away from Erlang system
	 * time.
	 */
	time_sup.inf.c.shadow_offset -= sdiff;
	sdiff = 0;
	begin_short_intervals = 1;
	if (ci.correction.error != 0) {
	    set_new_correction = 1;
	    new_correction.error = 0;
	}
    }
    else if (ci.correction.error == 0) {
	if (sdiff < -time_sup.r.o.adj.small_diff) {
	    set_new_correction = 1;
	    if (sdiff < -time_sup.r.o.adj.large_diff)
		new_correction.error = ERTS_TCORR_ERR_LARGE_ADJ;
	    else
		new_correction.error = ERTS_TCORR_ERR_SMALL_ADJ;
	}
	else if (sdiff > time_sup.r.o.adj.small_diff) {
	    set_new_correction = 1;
	    if (sdiff > time_sup.r.o.adj.large_diff)
		new_correction.error = -ERTS_TCORR_ERR_LARGE_ADJ;
	    else
		new_correction.error = -ERTS_TCORR_ERR_SMALL_ADJ;
	}
    }
    else if (ci.correction.error > 0) {
	if (sdiff < 0) {
	    if (ci.correction.error != ERTS_TCORR_ERR_LARGE_ADJ
		&& sdiff < -time_sup.r.o.adj.large_diff) {
		new_correction.error = ERTS_TCORR_ERR_LARGE_ADJ;
		set_new_correction = 1;
	    }
	}
	else if (sdiff > time_sup.r.o.adj.small_diff) {
	    set_new_correction = 1;
	    if (sdiff > time_sup.r.o.adj.large_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 (ci.correction.error < 0) */ { 
	if (0 < sdiff) {
	    if (ci.correction.error != -ERTS_TCORR_ERR_LARGE_ADJ
		&& time_sup.r.o.adj.large_diff < sdiff) {
		new_correction.error = -ERTS_TCORR_ERR_LARGE_ADJ;
		set_new_correction = 1;
	    }
	}
	else if (sdiff < -time_sup.r.o.adj.small_diff) {
	    set_new_correction = 1;
	    if (sdiff < -time_sup.r.o.adj.large_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;
	}
    }

    if (!os_drift_corrected) {
	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_MIN_MONOTONIC_DRIFT_MEASUREMENT)
	    | init_drift_adj) {
	    ErtsMonotonicTime drift_adj, drift_adj_diff, old_os_stime,
		smtime_diff, stime_diff, mtime_acc, stime_acc,
		avg_drift_adj, max_drift;

	    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;
	    smtime_diff = stime_diff - mtime_diff;
	    ix++;
	    if (ix >= time_sup.r.o.drift_adj.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;

	    max_drift = ERTS_MAX_MONOTONIC_DRIFT;
	    max_drift *= ERTS_MONOTONIC_TO_SEC(mtime_diff);

	    if (smtime_diff > time_sup.r.o.drift_adj.error + max_drift
		|| smtime_diff < -1*time_sup.r.o.drift_adj.error - max_drift) {
	    dirty_intervals:
		/*
		 * We had a leap in system time. Mark array as
		 * dirty to ensure that dirty values are rotated
		 * out before we use it again...
		 */
		ddp->dirty_counter = time_sup.r.o.drift_adj.intervals;
		begin_short_intervals = 1;
	    }
	    else if (ddp->dirty_counter > 0) {
		if (init_drift_adj) {
		    new_correction.drift = ((smtime_diff
					     * ERTS_MONOTONIC_TIME_UNIT)
					    / mtime_diff);
		    set_new_correction = 1;
		}
		ddp->dirty_counter--;
	    }
	    else {
		if (ddp->dirty_counter == 0) {
		    /* Force set new drift correction... */
		    set_new_correction = 1;
		    ddp->dirty_counter--;
		}

		if (time_sup.r.o.drift_adj.use_avg)
		    drift_adj = (((stime_acc - mtime_acc)
				  * ERTS_MONOTONIC_TIME_UNIT)
				 / mtime_acc);
		else
		    drift_adj = ((smtime_diff
				  * ERTS_MONOTONIC_TIME_UNIT)
				 / mtime_diff);

		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)
		    goto dirty_intervals;

		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;
		    }
		}
	    }
	}
    }

    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;
	ErtsMonotonicTime abs_sdiff;
	abs_sdiff = (sdiff < 0) ? -1*sdiff : sdiff;
	if (ecorr < 0)
	    ecorr = -1*ecorr;
	if (abs_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*abs_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
	    || time_sup.inf.c.parmon.cdata.drift.dirty_counter >= 0)) {
	timeout = ERTS_MONOTONIC_TO_MSEC(ERTS_SHORT_TIME_CORRECTION_CHECK);
    }

    timeout_pos = get_timeout_pos(erl_mtime, timeout);

#ifdef ERTS_TIME_CORRECTION_PRINT
    print_correction(set_new_correction,
		     sdiff,
		     ci.correction.error,
		     ci.correction.drift,
		     new_correction.error,
		     new_correction.drift,
		     timeout);
#endif

    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.insts.prev = ci;

	/*
	 * Current correction instance begin when
	 * OS monotonic time has increased two units.
	 */
	os_mtime += 2;

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

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

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

    if (!esdp)
	esdp = erts_get_scheduler_data();

    erts_twheel_set_timer(esdp->timer_wheel,
			  &time_sup.inf.c.parmon.timer,
			  check_time_correction,
			  NULL,
			  (void *) esdp,
			  timeout_pos);
}

static ErtsMonotonicTime get_os_corrected_time(void)
{
    ASSERT(time_sup.r.o.warp_mode == ERTS_MULTI_TIME_WARP_MODE);
    return erts_os_monotonic_time() + time_sup.r.o.moffset;
}

static void
check_time_offset(void *vesdp)
{
    ErtsSchedulerData *esdp = (ErtsSchedulerData *) vesdp;
    ErtsMonotonicTime sdiff, os_mtime, erl_mtime, os_stime,
	erl_stime, time_offset, timeout, timeout_pos;

    ASSERT(time_sup.r.o.warp_mode == ERTS_MULTI_TIME_WARP_MODE);

    erts_os_times(&os_mtime, &os_stime);

    erl_mtime =  os_mtime + time_sup.r.o.moffset;
    time_offset = get_time_offset();
    erl_stime = erl_mtime + time_offset;

    sdiff = erl_stime - os_stime;

    if ((sdiff < -2*time_sup.r.o.adj.small_diff
	 || 2*time_sup.r.o.adj.small_diff < sdiff)) {
	/* System time diff exeeded limits; change time offset... */
#ifdef ERTS_TIME_CORRECTION_PRINT
	erts_fprintf(stderr, "sdiff = %b64d nsec -> 0 nsec\n",
		     ERTS_MONOTONIC_TO_NSEC(sdiff));
#endif
	time_offset -= sdiff;
	sdiff = 0;
	set_time_offset(time_offset);
	schedule_send_time_offset_changed_notifications(time_offset);
    }
#ifdef ERTS_TIME_CORRECTION_PRINT
    else erts_fprintf(stderr, "sdiff = %b64d nsec\n",
		      ERTS_MONOTONIC_TO_NSEC(sdiff));
#endif

    timeout = ERTS_MONOTONIC_TO_MSEC(ERTS_LONG_TIME_CORRECTION_CHECK);
    timeout_pos = get_timeout_pos(erl_mtime, timeout);

    erts_twheel_set_timer(esdp->timer_wheel,
			  &time_sup.inf.c.parmon.timer,
			  check_time_offset,
			  NULL,
			  vesdp,
			  timeout_pos);
}

static void
init_check_time_correction(void *vesdp)
{
    ErtsMonotonicDriftData *ddp;
    ErtsMonotonicTime old_mtime, old_stime, mtime, stime, mtime_diff,
	stime_diff, smtime_diff, max_drift;
    int ix;

    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;

    erts_os_times(&mtime, &stime);

    mtime_diff = mtime - old_mtime;
    stime_diff = stime - old_stime;
    smtime_diff = stime_diff - mtime_diff;

    max_drift = ERTS_MAX_MONOTONIC_DRIFT;
    max_drift *= ERTS_MONOTONIC_TO_SEC(mtime_diff);

    if (smtime_diff > time_sup.r.o.drift_adj.error + max_drift
	|| smtime_diff < -1*time_sup.r.o.drift_adj.error - max_drift) {
	/* 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 < time_sup.r.o.drift_adj.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*time_sup.r.o.drift_adj.intervals;
    ddp->acc.mon = mtime_diff*time_sup.r.o.drift_adj.intervals;
    ddp->ix = 0;
    ddp->dirty_counter = time_sup.r.o.drift_adj.intervals;

    check_time_correction(vesdp);
}

static ErtsMonotonicTime
finalize_corrected_time_offset(ErtsSystemTime *stimep)
{
    ErtsMonotonicTime os_mtime;
    ErtsMonotonicCorrectionInstance ci;
    int os_drift_corrected = time_sup.r.o.os_corrected_monotonic_time;

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

    erts_os_times(&os_mtime, stimep);

    ci = time_sup.inf.c.parmon.cdata.insts.curr;

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

    if (os_mtime < ci.os_mtime)
	erts_exit(ERTS_ABORT_EXIT,
		 "OS monotonic time stepped backwards\n");

    return calc_corrected_erl_mtime(os_mtime, &ci, NULL,
				    os_drift_corrected);
}

static void
late_init_time_correction(ErtsSchedulerData *esdp)
{
    int quick_init_drift_adj;
    void (*check_func)(void *);
    ErtsMonotonicTime timeout, timeout_pos;

    quick_init_drift_adj =
	ERTS_MONOTONIC_TO_USEC(time_sup.r.o.drift_adj.error) == 0;

    if (quick_init_drift_adj)
	timeout = ERTS_MONOTONIC_TO_MSEC(ERTS_SHORT_TIME_CORRECTION_CHECK/10);
    else
	timeout = ERTS_MONOTONIC_TO_MSEC(ERTS_SHORT_TIME_CORRECTION_CHECK);

    if (!time_sup.r.o.os_corrected_monotonic_time)
	check_func = init_check_time_correction;
    else if (time_sup.r.o.get_time == get_os_corrected_time) {
	quick_init_drift_adj = 0;
	check_func = check_time_offset;
    }
    else
	check_func = check_time_correction;

    timeout_pos = get_timeout_pos(erts_get_monotonic_time(esdp),
				  timeout);

    erts_twheel_init_timer(&time_sup.inf.c.parmon.timer);
    erts_twheel_set_timer(esdp->timer_wheel,
			  &time_sup.inf.c.parmon.timer,
			  check_func,
			  NULL,
			  (quick_init_drift_adj
			   ? NULL
			   : esdp),
			  timeout_pos);
}

#endif /* ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT */

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

    erts_smp_mtx_lock(&erts_get_time_mtx);

    stime = erts_os_system_time();

    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_time_disable;
#else
    return 0;
#endif
}

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

void erts_init_sys_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_time_disable
	= !sys_init_time_res.have_os_monotonic_time;
    time_sup.r.o.os_corrected_monotonic_time =
	sys_init_time_res.have_corrected_os_monotonic_time;
    time_sup.r.o.os_monotonic_time_func
	= sys_init_time_res.os_monotonic_time_info.func;
    time_sup.r.o.os_monotonic_time_clock_id
	= sys_init_time_res.os_monotonic_time_info.clock_id;
    time_sup.r.o.os_monotonic_time_locked
	= sys_init_time_res.os_monotonic_time_info.locked_use;
    time_sup.r.o.os_monotonic_time_resolution
	= sys_init_time_res.os_monotonic_time_info.resolution;
    time_sup.r.o.os_monotonic_time_extended
	= sys_init_time_res.os_monotonic_time_info.extended;
#endif
    time_sup.r.o.os_system_time_func
	= sys_init_time_res.os_system_time_info.func;
    time_sup.r.o.os_system_time_clock_id
	= sys_init_time_res.os_system_time_info.clock_id;
    time_sup.r.o.os_system_time_locked
	= sys_init_time_res.os_system_time_info.locked_use;
    time_sup.r.o.os_system_time_resolution
	= sys_init_time_res.os_system_time_info.resolution;
}

int 
erts_init_time_sup(int time_correction, ErtsTimeWarpMode time_warp_mode)
{
    ErtsMonotonicTime resolution, ilength, intervals, short_isecs;
#if !ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT
    ErtsMonotonicTime abs_native_offset, native_offset;
#endif

    init_time_napi();

    erts_hl_timer_init();

    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)
	erts_smp_atomic32_init_nob(&time_sup.inf.c.preliminary_offset, 1);
    else
	erts_smp_atomic32_init_nob(&time_sup.inf.c.preliminary_offset, 0);
    time_sup.inf.c.shadow_offset = 0;

#if !ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT

    /*
     * NOTE! erts_time_sup__.r.o.start *need* to be a multiple
     *       of ERTS_MONOTONIC_TIME_UNIT.
     */

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

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

#endif

    resolution = time_sup.r.o.os_system_time_resolution;
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
    if (resolution > time_sup.r.o.os_monotonic_time_resolution)
	resolution = time_sup.r.o.os_monotonic_time_resolution;
#endif

    time_sup.r.o.adj.large_diff = erts_time_sup__.r.o.monotonic_time_unit;
    time_sup.r.o.adj.large_diff *= 50;
    time_sup.r.o.adj.large_diff /= resolution;
    if (time_sup.r.o.adj.large_diff < ERTS_USEC_TO_MONOTONIC(500))
	time_sup.r.o.adj.large_diff = ERTS_USEC_TO_MONOTONIC(500);
    time_sup.r.o.adj.small_diff = time_sup.r.o.adj.large_diff/10;

    time_sup.r.o.drift_adj.resolution = resolution;

    if (time_sup.r.o.os_corrected_monotonic_time) {
	time_sup.r.o.drift_adj.use_avg = 0;
	time_sup.r.o.drift_adj.intervals = 0;
	time_sup.r.o.drift_adj.error = 0;
	time_sup.inf.c.parmon.cdata.drift.dirty_counter = -1;
    }
    else {
	/*
	 * Calculate length of the interval in seconds needed
	 * in order to get an error that is at most 1 micro second.
	 * If this interval is longer than the short time correction
	 * check interval we use the average of all values instead
	 * of the latest value.
	 */
	short_isecs = ERTS_MONOTONIC_TO_SEC(ERTS_SHORT_TIME_CORRECTION_CHECK);
	ilength = ERTS_ASSUMED_PRECISION_DROP * ERTS_MONOTONIC_TIME_UNIT;
	ilength /= (resolution * ERTS_USEC_TO_MONOTONIC(1));
	time_sup.r.o.drift_adj.use_avg = ilength > short_isecs;

	if (ilength == 0)
	    intervals = 5;
	else {
	    intervals = ilength / short_isecs;
	    if (intervals > ERTS_MAX_DRIFT_INTERVALS)
		intervals = ERTS_MAX_DRIFT_INTERVALS;
	    else if (intervals < 5)
		intervals = 5;
	}
	time_sup.r.o.drift_adj.intervals = (int) intervals;

	/*
	 * drift_adj.error equals maximum assumed error
	 * over a short time interval. We use this value also
	 * when examining a large interval. In this case the
	 * error will be smaller, but we do not want to
	 * recalculate this over and over again.
	 */

	time_sup.r.o.drift_adj.error = ERTS_MONOTONIC_TIME_UNIT;
	time_sup.r.o.drift_adj.error *= ERTS_ASSUMED_PRECISION_DROP;
	time_sup.r.o.drift_adj.error /= resolution * short_isecs;
    }
#ifdef ERTS_TIME_CORRECTION_PRINT
    erts_fprintf(stderr, "resolution           = %b64d\n", resolution);
    erts_fprintf(stderr, "adj large diff       = %b64d usec\n",
		 ERTS_MONOTONIC_TO_USEC(time_sup.r.o.adj.large_diff));
    erts_fprintf(stderr, "adj small diff       = %b64d usec\n",
		 ERTS_MONOTONIC_TO_USEC(time_sup.r.o.adj.small_diff));
    if (!time_sup.r.o.os_corrected_monotonic_time) {
	erts_fprintf(stderr, "drift intervals      = %d\n",
		     time_sup.r.o.drift_adj.intervals);
	erts_fprintf(stderr, "drift adj error      = %b64d usec\n",
		     ERTS_MONOTONIC_TO_USEC(time_sup.r.o.drift_adj.error));
	erts_fprintf(stderr, "drift adj max diff   = %b64d nsec\n",
		     ERTS_MONOTONIC_TO_NSEC(ERTS_TIME_DRIFT_MAX_ADJ_DIFF));
	erts_fprintf(stderr, "drift adj min diff   = %b64d nsec\n",
		     ERTS_MONOTONIC_TO_NSEC(ERTS_TIME_DRIFT_MIN_ADJ_DIFF));
    }
#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_time_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;
	erts_os_times(&time_sup.inf.c.minit,
		      &time_sup.inf.c.sinit);
	time_sup.r.o.moffset = -1*time_sup.inf.c.minit;
	time_sup.r.o.moffset += ERTS_MONOTONIC_BEGIN;
	offset = time_sup.inf.c.sinit;
	offset -= ERTS_MONOTONIC_BEGIN;
	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;
    
	cdatap->drift.intervals[0].time.sys = time_sup.inf.c.sinit;
	cdatap->drift.intervals[0].time.mon = time_sup.inf.c.minit;
	cdatap->insts.curr.correction.drift = 0;
	cdatap->insts.curr.correction.error = 0;
	cdatap->insts.curr.erl_mtime = ERTS_MONOTONIC_BEGIN;
	cdatap->insts.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->insts.prev = cdatap->insts.curr;

	if (!time_sup.r.o.os_corrected_monotonic_time)
	    time_sup.r.o.get_time = get_corrected_time;
	else if (time_sup.r.o.warp_mode == ERTS_MULTI_TIME_WARP_MODE)
	    time_sup.r.o.get_time = get_os_corrected_time;
	else
	    time_sup.r.o.get_time = get_os_drift_corrected_time;
    }
    else
#endif
    {
	ErtsMonotonicTime stime, offset;
	time_sup.r.o.get_time = get_not_corrected_time;
	stime = time_sup.inf.c.sinit = erts_os_system_time();
	offset = stime - ERTS_MONOTONIC_BEGIN;
	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)
{
    erts_late_sys_init_time();
}

void
erts_sched_init_time_sup(ErtsSchedulerData *esdp)
{
    esdp->timer_wheel = erts_create_timer_wheel(esdp);
    esdp->next_tmo_ref = erts_get_next_timeout_reference(esdp->timer_wheel);
    esdp->timer_service = erts_create_timer_service();
#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
    if (esdp->no == 1) {
	/* A timer wheel to use must have beeen initialized */
	if (time_sup.r.o.get_time != get_not_corrected_time)
	    late_init_time_correction(esdp);
    }
#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 (erts_smp_atomic32_read_nob(&time_sup.inf.c.preliminary_offset))
	    return ERTS_TIME_OFFSET_PRELIMINARY;
	return ERTS_TIME_OFFSET_FINAL;
    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 (erts_smp_atomic32_read_nob(&time_sup.inf.c.preliminary_offset)) {
	    ErtsMonotonicTime mtime, new_offset;

#ifdef ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT
	    if (!time_sup.r.o.correction)
#endif
	    {
		ErtsMonotonicTime stime = erts_os_system_time();

		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 {
		ErtsSystemTime stime;
		mtime = finalize_corrected_time_offset(&stime);
		new_offset = stime - 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);

	    erts_smp_atomic32_set_nob(&time_sup.inf.c.preliminary_offset, 0);
	    res = ERTS_TIME_OFFSET_PRELIMINARY;
	}

	erts_smp_mtx_unlock(&erts_get_time_mtx);

	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();
    update_last_mtime(NULL, now);

    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();
    update_last_mtime(NULL, mtime);
    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(ErtsSchedulerData *esdp)
{
    ErtsMonotonicTime mtime = time_sup.r.o.get_time();
    update_last_mtime(esdp, mtime);
    return mtime;
}

ErtsMonotonicTime
erts_get_time_offset(void)
{
    return get_time_offset();
}

static ERTS_INLINE void
make_timestamp_value(Uint* megasec, Uint* sec, Uint* microsec,
		     ErtsMonotonicTime mtime, ErtsMonotonicTime offset)
{
    ErtsMonotonicTime stime, as;
    Uint ms;

    stime = ERTS_MONOTONIC_TO_USEC(mtime + offset);

    as = stime / ERTS_MONOTONIC_TIME_MEGA;
    *megasec = ms = (Uint) (stime / ERTS_MONOTONIC_TIME_TERA);
    *sec = (Uint) (as - (((ErtsMonotonicTime) ms)
			 * ERTS_MONOTONIC_TIME_MEGA));
    *microsec = (Uint) (stime - as*ERTS_MONOTONIC_TIME_MEGA);

    ASSERT(((ErtsMonotonicTime) ms)*ERTS_MONOTONIC_TIME_TERA
	   + ((ErtsMonotonicTime) *sec)*ERTS_MONOTONIC_TIME_MEGA
	   + *microsec == stime);
}

void
erts_make_timestamp_value(Uint* megasec, Uint* sec, Uint* microsec,
			  ErtsMonotonicTime mtime, ErtsMonotonicTime offset)
{
    make_timestamp_value(megasec, sec, microsec, mtime, offset);
}

void
get_sys_now(Uint* megasec, Uint* sec, Uint* microsec)
{
    ErtsSystemTime stime = erts_os_system_time();
    ErtsSystemTime ms, s, us;

    us = ERTS_MONOTONIC_TO_USEC(stime);
    s = us / (1000*1000);
    ms = s / (1000*1000);

    *megasec = (Uint) ms;
    *sec = (Uint) (s - ms*(1000*1000));
    *microsec = (Uint) (us - s*(1000*1000));
}

#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 = NULL; /* Shut up faulty warning */
    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)) {
		    ErtsMessage *mp;
		    ErlOffHeap *ohp;
		    Eterm message;

		    mp = erts_alloc_message_heap(rp, &rp_locks,
						 hsz, &hp, &ohp);
		    *patch_refp = ref;
		    ASSERT(hsz == size_object(message_template));
		    message = copy_struct(message_template, hsz, &hp, ohp);
		    erts_queue_message(rp, &rp_locks, mp, message, NIL);
		}
		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_TIME_START_EXTERNAL);
}

Eterm
erts_get_monotonic_end_time(struct process *c_p)
{
    return make_time_val(c_p, ERTS_MONOTONIC_TIME_END_EXTERNAL);
}

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[6];
    Eterm v[6];

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

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

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

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

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

    k[i] = erts_bld_atom(hpp, szp, "parallel");
    v[i++] = time_sup.r.o.os_monotonic_time_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_time_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);
}

static Eterm
bld_system_time_source(Uint **hpp, Uint *szp, Sint64 os_stime)
{
    int i = 0;
    Eterm k[5];
    Eterm v[5];

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

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

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

    k[i] = erts_bld_atom(hpp, szp, "parallel");
    v[i++] = am_yes;

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

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

Eterm
erts_system_time_source(struct process *c_p)
{
    Uint hsz = 0;
    Eterm *hp = NULL;
    Sint64 os_stime = (Sint64) erts_os_system_time();

    bld_system_time_source(NULL, &hsz, os_stime);
    if (hsz)
	hp = HAlloc(c_p, hsz);
    return bld_system_time_source(&hp, NULL, os_stime);
}


#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;
}


/*
 * Time Native API (drivers and NIFs)
 */

#define ERTS_NAPI_TIME_ERROR ((ErtsMonotonicTime) ERTS_NAPI_TIME_ERROR__)

static void
init_time_napi(void)
{
    /* Verify that time native api constants are as expected... */

    ASSERT(sizeof(ErtsMonotonicTime) == sizeof(ErlDrvTime));
    ASSERT(ERL_DRV_TIME_ERROR == (ErlDrvTime) ERTS_NAPI_TIME_ERROR);
    ASSERT(ERL_DRV_TIME_ERROR < (ErlDrvTime) 0);
    ASSERT(ERTS_NAPI_SEC__ == (int) ERL_DRV_SEC);
    ASSERT(ERTS_NAPI_MSEC__ == (int) ERL_DRV_MSEC);
    ASSERT(ERTS_NAPI_USEC__ == (int) ERL_DRV_USEC);
    ASSERT(ERTS_NAPI_NSEC__ == (int) ERL_DRV_NSEC);

    ASSERT(sizeof(ErtsMonotonicTime) == sizeof(ErlNifTime));
    ASSERT(ERL_NIF_TIME_ERROR == (ErlNifTime) ERTS_NAPI_TIME_ERROR);
    ASSERT(ERL_NIF_TIME_ERROR < (ErlNifTime) 0);
    ASSERT(ERTS_NAPI_SEC__ == (int) ERL_NIF_SEC);
    ASSERT(ERTS_NAPI_MSEC__ == (int) ERL_NIF_MSEC);
    ASSERT(ERTS_NAPI_USEC__ == (int) ERL_NIF_USEC);
    ASSERT(ERTS_NAPI_NSEC__ == (int) ERL_NIF_NSEC);
}

ErtsMonotonicTime
erts_napi_monotonic_time(int time_unit)
{
    ErtsSchedulerData *esdp;
    ErtsMonotonicTime mtime;

    /* At least for now only allow schedulers to do this... */
    esdp = erts_get_scheduler_data();
    if (!esdp)
	return ERTS_NAPI_TIME_ERROR;

    mtime = time_sup.r.o.get_time();
    update_last_mtime(esdp, mtime);

    switch (time_unit) {
    case ERTS_NAPI_SEC__:
	mtime = ERTS_MONOTONIC_TO_SEC(mtime);
	mtime += ERTS_MONOTONIC_OFFSET_SEC;
	break;
    case ERTS_NAPI_MSEC__:
	mtime = ERTS_MONOTONIC_TO_MSEC(mtime);
	mtime += ERTS_MONOTONIC_OFFSET_MSEC;
	break;
    case ERTS_NAPI_USEC__:
	mtime = ERTS_MONOTONIC_TO_USEC(mtime);
	mtime += ERTS_MONOTONIC_OFFSET_USEC;
	break;
    case ERTS_NAPI_NSEC__:
	mtime = ERTS_MONOTONIC_TO_NSEC(mtime);
	mtime += ERTS_MONOTONIC_OFFSET_NSEC;
	break;
    default:
	return ERTS_NAPI_TIME_ERROR;
    }

    return mtime;
}

ErtsMonotonicTime
erts_napi_time_offset(int time_unit)
{
    ErtsSchedulerData *esdp;
    ErtsSystemTime offs;

    /* At least for now only allow schedulers to do this... */
    esdp = erts_get_scheduler_data();
    if (!esdp)
	return ERTS_NAPI_TIME_ERROR;

    offs = get_time_offset();
    switch (time_unit) {
    case ERTS_NAPI_SEC__:
	offs = ERTS_MONOTONIC_TO_SEC(offs);
	offs -= ERTS_MONOTONIC_OFFSET_SEC;
	break;
    case ERTS_NAPI_MSEC__:
	offs = ERTS_MONOTONIC_TO_MSEC(offs);
	offs -= ERTS_MONOTONIC_OFFSET_MSEC;
	break;
    case ERTS_NAPI_USEC__:
	offs = ERTS_MONOTONIC_TO_USEC(offs);
	offs -= ERTS_MONOTONIC_OFFSET_USEC;
	break;
    case ERTS_NAPI_NSEC__:
	offs = ERTS_MONOTONIC_TO_NSEC(offs);
	offs -= ERTS_MONOTONIC_OFFSET_NSEC;
	break;
    default:
	return ERTS_NAPI_TIME_ERROR;
    }
    return offs;
}

ErtsMonotonicTime
erts_napi_convert_time_unit(ErtsMonotonicTime val, int from, int to)
{
    ErtsMonotonicTime ffreq, tfreq, denom;
    /*
     * Convertion between time units using floor function.
     *
     * Note that this needs to work also for negative
     * values. Ordinary integer division on a negative
     * value will give ceiling...
     */

    switch ((int) from) {
    case ERTS_NAPI_SEC__: ffreq = 1; break;
    case ERTS_NAPI_MSEC__: ffreq = 1000; break;
    case ERTS_NAPI_USEC__: ffreq = 1000*1000; break;
    case ERTS_NAPI_NSEC__: ffreq = 1000*1000*1000; break;
    default: return ERTS_NAPI_TIME_ERROR;
    }

    switch ((int) to) {
    case ERTS_NAPI_SEC__: tfreq = 1; break;
    case ERTS_NAPI_MSEC__: tfreq = 1000; break;
    case ERTS_NAPI_USEC__: tfreq = 1000*1000; break;
    case ERTS_NAPI_NSEC__: tfreq = 1000*1000*1000; break;
    default: return ERTS_NAPI_TIME_ERROR;
    }

    if (tfreq >= ffreq)
	return val * (tfreq / ffreq);

    denom = ffreq / tfreq;
    if (val >= 0)
	return val / denom;

    return (val - (denom - 1)) / denom;
}

/* Built in functions */

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

BIF_RETTYPE monotonic_time_1(BIF_ALIST_1)
{
    ErtsMonotonicTime mtime = time_sup.r.o.get_time();
    update_last_mtime(ERTS_PROC_GET_SCHDATA(BIF_P), mtime);
    BIF_RET(time_unit_conversion(BIF_P, BIF_ARG_1, mtime, 1));
}

BIF_RETTYPE system_time_0(BIF_ALIST_0)
{
    ErtsMonotonicTime mtime, offset;
    mtime = time_sup.r.o.get_time();
    offset = get_time_offset();
    update_last_mtime(ERTS_PROC_GET_SCHDATA(BIF_P), mtime);
    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();
    update_last_mtime(ERTS_PROC_GET_SCHDATA(BIF_P), mtime);
    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 mtime, offset;
    Uint mega_sec, sec, micro_sec;

    mtime = time_sup.r.o.get_time();
    offset = get_time_offset();
    update_last_mtime(ERTS_PROC_GET_SCHDATA(BIF_P), mtime);

    make_timestamp_value(&mega_sec, &sec, &micro_sec, mtime, offset);

    /*
     * 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)
{
    ErtsSystemTime stime = erts_os_system_time();
    BIF_RET(make_time_val(BIF_P, stime));
}

BIF_RETTYPE os_system_time_1(BIF_ALIST_1)
{
    ErtsSystemTime stime = erts_os_system_time();
    BIF_RET(time_unit_conversion(BIF_P, BIF_ARG_1, stime, 0));
}

BIF_RETTYPE
os_perf_counter_0(BIF_ALIST_0)
{
    BIF_RET(make_time_val(BIF_P, erts_sys_perf_counter()));
}

BIF_RETTYPE erts_internal_perf_counter_unit_0(BIF_ALIST_0)
{
    BIF_RET(make_time_val(BIF_P, erts_sys_perf_counter_unit()));
}