[m-rev.] for review: make clock_t abstract and widen its representation

Julien Fischer jfischer at opturion.com
Wed Apr 22 13:10:21 AEST 2026


For review by anyone.

Question for reviewers: should the newly added clock_t_to_int/0 function be
marked as obsolete from the outset?

----------------------------------------------------------------------

Make clock_t abstract and widen its representation.

Currently, the clock_t type in the time module of the standard library is
an equivalence type for int. This overflows on 32-bit C backends, and on
the Java and C# backends it overflowed within minutes of CPU time because
the foreign_proc implementations of target_clock/1 and target_times/5
were narrowing their (already 64-bit) underlying values down to int.

library/time.m:
    Make clock_t/0 abstract.

    Use int64 as the underlying representation of clock_t values, not int.

    Widen the foreign_proc implementations of target_clock/1 and
    target_times/5 to return 64-bit values on all backends. Remove the
    31-bit masking of the real-time clock in target_times/5 on Java.

    Add functions for accessing the raw clock_t value as int or int64.

    Simplify the definition of clk_tck/0 for C#.

    s/can't/cannot/ throughout.

NEWS.md:
    Announce the change to clock_t/0 and the addition of the new functions.

Julien.

diff --git a/NEWS.md b/NEWS.md
index 33715b850..cb9905927 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -113,6 +113,13 @@ Changes that may break compatibility
 * We have disabled support for concurrency in non-parallel low-level C grades.
   It was never useful for anything other than trivial programs.

+* The `clock_t` type in the `time` module of the Mercury standard library is
+  now abstract, with an underlying representation of `int64` rather than `int`.
+  This avoids overflow on backends where Mercury's `int` is 32 bits, and on
+  the Java and C# backends where CPU-time values previously overflowed within
+  minutes. Code that performs arithmetic on `clock_t` values directly will need
+  to use the new `clock_t_to_int64/1` access function.
+
 Changes to the Mercury standard library
 ---------------------------------------

@@ -1439,6 +1446,12 @@ Changes to the Mercury standard library
 *  The `tm_sec` field of the `tm/0` type now only allows for a single positive
    leap second for any given minute.

+* The `clock_t` type is now abstract (see the compatibility note above).
+  The following functions have been added for accessing its representation:
+
+    - func `clock_t_to_int/1`
+    - func `clock_t_to_int64/1`
+
 ### Changes to the `tree_bitset` module

 * The following predicates and functions have been added:
diff --git a/library/time.m b/library/time.m
index 1962762fe..468390787 100644
--- a/library/time.m
+++ b/library/time.m
@@ -5,7 +5,7 @@
 % "Feel free to use this code or parts of it any way you want."
 %
 % Some portions are Copyright (C) 1999-2007,2009-2012 The University
of Melbourne.
-% Copyright (C) 2014-2025 The Mercury team.
+% Copyright (C) 2014-2026 The Mercury team.
 % This file is distributed under the terms specified in COPYING.LIB.
 %---------------------------------------------------------------------------%
 %
@@ -31,7 +31,7 @@
     % returned by `clock' or `times'. See the comments on these
     % predicates below.
     %
-:- type clock_t == int.
+:- type clock_t.

     % The `tms' type holds information about the amount of processor time
     % that a process and its child processes have consumed.
@@ -84,7 +84,7 @@
     ;       daylight_time.  % yes, DST is in effect

     % Some of the procedures in this module throw this type
-    % as an exception if they can't obtain a result.
+    % as an exception if they cannot obtain a result.
     %
 :- type time_error
     --->    time_error(string). % Error message
@@ -112,6 +112,18 @@
     %
 :- func clocks_per_sec = int.

+    % Return the number of clock ticks in a `clock_t' value as an int.
+    %
+    % This function exists for compatibility with code written when
+    % `clock_t' was equivalent to `int'. On platforms where int is 32-bit,
+    % the result may be silently truncated; use clock_t_to_int64/1 instead.
+    %
+:- func clock_t_to_int(clock_t) = int.
+
+    % Return the number of clock ticks in a `clock_t' value as an int64.
+    %
+:- func clock_t_to_int64(clock_t) = int64.
+
 %---------------------------------------------------------------------------%

     % time(Result, !IO):
@@ -198,6 +210,7 @@
 :- import_module bool.
 :- import_module exception.
 :- import_module int.
+:- import_module int64.
 :- import_module list.
 :- import_module require.
 :- import_module string.
@@ -221,6 +234,11 @@
     #include ""mercury_string.h""       // For MR_make_aligned_string_copy etc.
 ").

+%---------------------------------------------------------------------------%
+
+:- type clock_t
+    --->    clock_t(int64).
+
     % We use a no-tag wrapper type for time_t, rather than defining it as an
     % equivalence type or just using a d.u./pragma foreign_type directly,
     % to avoid the following problems:
@@ -229,7 +247,7 @@
     %     the abstract type, but the callee seeing the equivalence type
     %     definition or the foreign_type definition.
     %
-    %   - users can't define instance declarations for abstract equiv. types.
+    %   - users cannot define instance declarations for abstract equiv. types.
     %
 :- type time_t
     --->    time_t(time_t_rep).
@@ -254,26 +272,25 @@ compare_time_t_reps(Result, X, Y) :-

 clock(Result, !IO) :-
     target_clock(Ret, !IO),
-    ( if Ret = -1 then
-        throw(time_error("can't get clock value"))
+    ( if Ret = -1i64 then
+        throw(time_error("cannot get clock value"))
     else
-        Result = Ret
+        Result = clock_t(Ret)
     ).

-:- pred target_clock(int::out, io::di, io::uo) is det.
+:- pred target_clock(int64::out, io::di, io::uo) is det.

 :- pragma foreign_proc("C",
     target_clock(Ret::out, _IO0::di, _IO::uo),
     [will_not_call_mercury, promise_pure, thread_safe, tabled_for_io],
 "
-    Ret = (MR_Integer) clock();
+    Ret = (int64_t) clock();
 ").
 :- pragma foreign_proc("C#",
     target_clock(Ret::out, _IO0::di, _IO::uo),
     [will_not_call_mercury, promise_pure],
 "
-    // XXX Ticks is long in .NET!
-    Ret = (int) System.Diagnostics.Process.GetCurrentProcess().
+    Ret = System.Diagnostics.Process.GetCurrentProcess().
         UserProcessorTime.Ticks;
 ").
 :- pragma foreign_proc("Java",
@@ -284,10 +301,10 @@ clock(Result, !IO) :-
         java.lang.management.ManagementFactory.getThreadMXBean();
     long nsecs = bean.getCurrentThreadCpuTime();
     if (nsecs == -1) {
-        Ret = -1;
+        Ret = -1L;
     } else {
         // This must match the definition of clocks_per_sec.
-        Ret = (int) (nsecs / 1000L);
+        Ret = nsecs / 1000L;
     }
 ").

@@ -316,10 +333,17 @@ clock(Result, !IO) :-

 %---------------------------------------------------------------------------%

+
+clock_t_to_int(clock_t(Int64)) = int64.cast_to_int(Int64).
+
+clock_t_to_int64(clock_t(Int64)) = Int64.
+
+%---------------------------------------------------------------------------%
+
 time(Result, !IO) :-
     target_time(Ret, !IO),
     ( if time_t_is_invalid(Ret) then
-        throw(time_error("can't get time value"))
+        throw(time_error("cannot get time value"))
     else
         Result = time_t(Ret)
     ).
@@ -369,10 +393,15 @@ time(Result, !IO) :-
 %---------------------------------------------------------------------------%

 times(Tms, Result, !IO) :-
-    target_times(Ret, Ut, St, CUt, CSt, !IO),
-    ( if Ret = -1 then
-        throw(time_error("can't get times value"))
+    target_times(Ret0, Ut0, St0, CUt0, CSt0, !IO),
+    ( if Ret0 = -1i64 then
+        throw(time_error("cannot get times value"))
     else
+        Ret = clock_t(Ret0),
+        Ut = clock_t(Ut0),
+        St = clock_t(St0),
+        CUt = clock_t(CUt0),
+        CSt = clock_t(CSt0),
         Tms = tms(Ut, St, CUt, CSt),
         Result = Ret
     ).
@@ -388,8 +417,8 @@ times(Tms, Result, !IO) :-
 #endif
 ").

-:- pred target_times(int::out, int::out, int::out, int::out, int::out,
-    io::di, io::uo) is det.
+:- pred target_times(int64::out, int64::out, int64::out, int64::out,
+    int64::out, io::di, io::uo) is det.

 :- pragma foreign_proc("C",
     target_times(Ret::out, Ut::out, St::out, CUt::out, CSt::out,
@@ -400,12 +429,12 @@ times(Tms, Result, !IO) :-
 #ifdef MR_HAVE_POSIX_TIMES
     struct tms t;

-    Ret = (MR_Integer) times(&t);
+    Ret = (int64_t) times(&t);

-    Ut = (MR_Integer) t.tms_utime;
-    St = (MR_Integer) t.tms_stime;
-    CUt = (MR_Integer) t.tms_cutime;
-    CSt = (MR_Integer) t.tms_cstime;
+    Ut = (int64_t) t.tms_utime;
+    St = (int64_t) t.tms_stime;
+    CUt = (int64_t) t.tms_cutime;
+    CSt = (int64_t) t.tms_cstime;
 #else
   #if defined(MR_WIN32) && defined(MR_CLOCK_TICKS_PER_SECOND)
     HANDLE hProcess;
@@ -422,8 +451,8 @@ times(Tms, Result, !IO) :-
     user.ft = ftUser;
     kernel.ft = ftKernel;

-    Ut = (MR_Integer) (user.i64 / factor);
-    St = (MR_Integer) (kernel.i64 / factor);
+    Ut = (int64_t) (user.i64 / factor);
+    St = (int64_t) (kernel.i64 / factor);

     // XXX Not sure how to return children times.
     CUt = 0;
@@ -439,8 +468,7 @@ times(Tms, Result, !IO) :-
         _IO0::di, _IO::uo),
     [will_not_call_mercury, promise_pure, may_not_duplicate],
 "
-    // We can only keep the lower 31 bits of the timestamp.
-    Ret = (int) (System.currentTimeMillis() & 0x7fffffff);
+    Ret = System.currentTimeMillis();

     try {
         java.lang.management.ThreadMXBean bean =
@@ -452,16 +480,16 @@ times(Tms, Result, !IO) :-
             St = -1;
         } else {
             // These units must match the definition of clk_tck.
-            Ut = (int) (user_nsecs / 1000000L);
-            St = (int) ((cpu_nsecs - user_nsecs) / 1000000L);
+            Ut = user_nsecs / 1000000L;
+            St = (cpu_nsecs - user_nsecs) / 1000000L;
         }
     } catch (java.lang.UnsupportedOperationException e) {
-        Ut = -1;
-        St = -1;
+        Ut = -1L;
+        St = -1L;
     }

-    CUt = 0;
-    CSt = 0;
+    CUt = 0L;
+    CSt = 0L;
 ").

 :- pragma foreign_proc("C#",
@@ -469,18 +497,15 @@ times(Tms, Result, !IO) :-
         _IO0::di, _IO::uo),
     [will_not_call_mercury, promise_pure, may_not_duplicate],
 "
-    Ret = (int) System.DateTime.UtcNow.Ticks;
-
-    // Should we keep only the lower 31 bits of the timestamp, like in java?
-    // Ret = Ret & 0x7fffffff;
+    Ret = System.DateTime.UtcNow.Ticks;

     long user =
         System.Diagnostics.Process.GetCurrentProcess().UserProcessorTime.Ticks;
     long total =
         System.Diagnostics.Process.GetCurrentProcess().TotalProcessorTime.Ticks;

-    Ut = (int) user;
-    St = (int) (total - user);
+    Ut = user;
+    St = total - user;

     CUt = 0;
     CSt = 0;
@@ -488,24 +513,15 @@ times(Tms, Result, !IO) :-

 %---------------------------------------------------------------------------%

-:- pragma foreign_proc("C#",
-    clk_tck = (Ret::out),
-    [will_not_call_mercury, promise_pure, thread_safe],
-"
-    // TicksPerSecond is guaranteed to be 10,000,000.
-    Ret = (int) System.TimeSpan.TicksPerSecond;
-").
-
 clk_tck = Ret :-
     Ret0 = target_clk_tck,
     ( if Ret0 = -1 then
-        throw(time_error("can't get clk_tck value"))
+        throw(time_error("cannot get clk_tck value"))
     else
         Ret = Ret0
     ).

 :- func target_clk_tck = int.
-:- pragma consider_used(func(target_clk_tck/0)).
 :- pragma foreign_proc("C",
     target_clk_tck = (Ret::out),
     [will_not_call_mercury, promise_pure, thread_safe],
@@ -516,6 +532,15 @@ clk_tck = Ret :-
     Ret = -1;
 #endif
 ").
+
+:- pragma foreign_proc("C#",
+    target_clk_tck = (Ret::out),
+    [will_not_call_mercury, promise_pure, thread_safe],
+"
+    // TicksPerSecond is guaranteed to be 10,000,000.
+    Ret = (int) System.TimeSpan.TicksPerSecond;
+").
+
 :- pragma foreign_proc("Java",
     target_clk_tck = (Ret::out),
     [will_not_call_mercury, promise_pure, thread_safe],
@@ -525,8 +550,6 @@ clk_tck = Ret :-
     Ret = 1000;
 ").

-target_clk_tck = -1.   % default, to get clk_tck to throw an exception.
-
 %---------------------------------------------------------------------------%

 difftime(time_t(T1), time_t(T0)) = Diff :-


More information about the reviews mailing list