[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