Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
WIP librt.time
  • Loading branch information
JukkaL committed Feb 2, 2026
commit ef0fe384a0e20cfe94de39ac5a384607beb7d697
1 change: 1 addition & 0 deletions mypy/typeshed/stubs/librt/librt/time.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
def time() -> float: ...
8 changes: 7 additions & 1 deletion mypyc/codegen/emitmodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
short_id_from_name,
)
from mypyc.errors import Errors
from mypyc.ir.deps import LIBRT_BASE64, LIBRT_STRINGS, SourceDep
from mypyc.ir.deps import LIBRT_BASE64, LIBRT_STRINGS, LIBRT_TIME, SourceDep
from mypyc.ir.func_ir import FuncIR
from mypyc.ir.module_ir import ModuleIR, ModuleIRs, deserialize_modules
from mypyc.ir.ops import DeserMaps, LoadLiteral
Expand Down Expand Up @@ -632,6 +632,8 @@ def generate_c_for_modules(self) -> list[tuple[str, str]]:
ext_declarations.emit_line("#include <base64/librt_base64.h>")
if any(LIBRT_STRINGS in mod.dependencies for mod in self.modules.values()):
ext_declarations.emit_line("#include <strings/librt_strings.h>")
if any(LIBRT_TIME in mod.dependencies for mod in self.modules.values()):
ext_declarations.emit_line("#include <time/librt_time.h>")
# Include headers for conditional source files
source_deps = collect_source_dependencies(self.modules)
for source_dep in sorted(source_deps, key=lambda d: d.path):
Expand Down Expand Up @@ -1102,6 +1104,10 @@ def emit_module_exec_func(
emitter.emit_line("if (import_librt_strings() < 0) {")
emitter.emit_line("return -1;")
emitter.emit_line("}")
if LIBRT_TIME in module.dependencies:
emitter.emit_line("if (import_librt_time() < 0) {")
emitter.emit_line("return -1;")
emitter.emit_line("}")
emitter.emit_line("PyObject* modname = NULL;")
if self.multi_phase_init:
emitter.emit_line(f"{module_static} = module;")
Expand Down
1 change: 1 addition & 0 deletions mypyc/ir/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def get_header(self) -> str:
LIBRT_STRINGS: Final = Capsule("librt.strings")
LIBRT_BASE64: Final = Capsule("librt.base64")
LIBRT_VECS: Final = Capsule("librt.vecs")
LIBRT_TIME: Final = Capsule("librt.time")

BYTES_EXTRA_OPS: Final = SourceDep("bytes_extra_ops.c")
BYTES_WRITER_EXTRA_OPS: Final = SourceDep("byteswriter_extra_ops.c")
Expand Down
99 changes: 99 additions & 0 deletions mypyc/lib-rt/time/librt_time.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <time.h>
#include "librt_time.h"
#include "../pythoncapi_compat.h"
#include "../mypyc_util.h"

#ifdef MYPYC_EXPERIMENTAL

// Internal function that returns a C double for mypyc primitives
static double
time_time_internal(void) {
time_t t = time(NULL);
if (unlikely(t == (time_t)-1)) {
PyErr_SetString(PyExc_OSError, "time() failed");
return CPY_FLOAT_ERROR;
}
return (double)t;
}

// Wrapper function for normal Python extension usage
static PyObject*
time_time(PyObject *self, PyObject *const *args, size_t nargs) {
if (nargs != 0) {
PyErr_SetString(PyExc_TypeError, "time() takes no arguments");
return NULL;
}

double result = time_time_internal();
if (result == CPY_FLOAT_ERROR) {
return NULL;
}
return PyFloat_FromDouble(result);
}

#endif

static PyMethodDef librt_time_module_methods[] = {
#ifdef MYPYC_EXPERIMENTAL
{"time", (PyCFunction)time_time, METH_FASTCALL,
PyDoc_STR("Return the current time in seconds since the Unix epoch as a floating point number.")},
#endif
{NULL, NULL, 0, NULL}
};

#ifdef MYPYC_EXPERIMENTAL

static int
time_abi_version(void) {
return LIBRT_TIME_ABI_VERSION;
}

static int
time_api_version(void) {
return LIBRT_TIME_API_VERSION;
}

#endif

static int
librt_time_module_exec(PyObject *m)
{
#ifdef MYPYC_EXPERIMENTAL
// Export mypyc internal C API via capsule
static void *time_api[LIBRT_TIME_API_LEN] = {
(void *)time_abi_version,
(void *)time_api_version,
(void *)time_time_internal,
};
PyObject *c_api_object = PyCapsule_New((void *)time_api, "librt.time._C_API", NULL);
if (PyModule_Add(m, "_C_API", c_api_object) < 0) {
return -1;
}
#endif
return 0;
}

static PyModuleDef_Slot librt_time_module_slots[] = {
{Py_mod_exec, librt_time_module_exec},
#ifdef Py_MOD_GIL_NOT_USED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};

static PyModuleDef librt_time_module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "time",
.m_doc = "Fast time() function optimized for mypyc",
.m_size = 0,
.m_methods = librt_time_module_methods,
.m_slots = librt_time_module_slots,
};

PyMODINIT_FUNC
PyInit_time(void)
{
return PyModuleDef_Init(&librt_time_module);
}
62 changes: 62 additions & 0 deletions mypyc/lib-rt/time/librt_time.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#ifndef LIBRT_TIME_H
#define LIBRT_TIME_H

#ifndef MYPYC_EXPERIMENTAL

static int
import_librt_time(void)
{
// All librt.time features are experimental for now, so don't set up the API here
return 0;
}

#else // MYPYC_EXPERIMENTAL

#include <Python.h>

#define LIBRT_TIME_ABI_VERSION 1
#define LIBRT_TIME_API_VERSION 1
#define LIBRT_TIME_API_LEN 3

static void *LibRTTime_API[LIBRT_TIME_API_LEN];

#define LibRTTime_ABIVersion (*(int (*)(void)) LibRTTime_API[0])
#define LibRTTime_APIVersion (*(int (*)(void)) LibRTTime_API[1])
#define LibRTTime_time (*(double (*)(void)) LibRTTime_API[2])

static int
import_librt_time(void)
{
PyObject *mod = PyImport_ImportModule("librt.time");
if (mod == NULL)
return -1;
Py_DECREF(mod); // we import just for the side effect of making the below work.
void *capsule = PyCapsule_Import("librt.time._C_API", 0);
if (capsule == NULL)
return -1;
memcpy(LibRTTime_API, capsule, sizeof(LibRTTime_API));
if (LibRTTime_ABIVersion() != LIBRT_TIME_ABI_VERSION) {
char err[128];
snprintf(err, sizeof(err), "ABI version conflict for librt.time, expected %d, found %d",
LIBRT_TIME_ABI_VERSION,
LibRTTime_ABIVersion()
);
PyErr_SetString(PyExc_ValueError, err);
return -1;
}
if (LibRTTime_APIVersion() < LIBRT_TIME_API_VERSION) {
char err[128];
snprintf(err, sizeof(err),
"API version conflict for librt.time, expected %d or newer, found %d (hint: upgrade librt)",
LIBRT_TIME_API_VERSION,
LibRTTime_APIVersion()
);
PyErr_SetString(PyExc_ValueError, err);
return -1;
}
return 0;
}

#endif // MYPYC_EXPERIMENTAL

#endif // LIBRT_TIME_H
14 changes: 14 additions & 0 deletions mypyc/primitives/librt_time_ops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from mypyc.ir.deps import LIBRT_TIME
from mypyc.ir.ops import ERR_MAGIC_OVERLAPPING
from mypyc.ir.rtypes import float_rprimitive
from mypyc.primitives.registry import function_op

function_op(
name="librt.time.time",
arg_types=[],
return_type=float_rprimitive,
c_function_name="LibRTTime_time",
error_kind=ERR_MAGIC_OVERLAPPING,
experimental=True,
dependencies=[LIBRT_TIME],
)
1 change: 1 addition & 0 deletions mypyc/primitives/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ def load_address_op(name: str, type: RType, src: str) -> LoadAddressDescription:
import mypyc.primitives.float_ops
import mypyc.primitives.int_ops
import mypyc.primitives.librt_strings_ops
import mypyc.primitives.librt_time_ops
import mypyc.primitives.list_ops
import mypyc.primitives.misc_ops
import mypyc.primitives.str_ops
Expand Down
44 changes: 44 additions & 0 deletions mypyc/test-data/irbuild-time.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[case testTime_experimental]
from librt.time import time

def get_time() -> float:
return time()

def time_twice() -> float:
t1 = time()
t2 = time()
return t2 - t1
[out]
def get_time():
r0 :: float
L0:
r0 = LibRTTime_time()
return r0
def time_twice():
r0, t1, r1, t2, r2 :: float
L0:
r0 = LibRTTime_time()
t1 = r0
r1 = LibRTTime_time()
t2 = r1
r2 = t2 - t1
return r2

[case testTimeExperimentalDisabled]
from librt.time import time

def get_time() -> float:
return time()
[out]
def get_time():
r0 :: dict
r1 :: str
r2, r3 :: object
r4 :: float
L0:
r0 = __main__.globals :: static
r1 = 'time'
r2 = CPyDict_GetItem(r0, r1)
r3 = PyObject_Vectorcall(r2, 0, 0, 0)
r4 = unbox(float, r3)
return r4
1 change: 1 addition & 0 deletions mypyc/test/test_irbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"irbuild-weakref.test",
"irbuild-librt-strings.test",
"irbuild-base64.test",
"irbuild-time.test",
"irbuild-match.test",
]

Expand Down