Draft - HAL types update and HAL isolation - #4247
Conversation
7bb25b0 to
c4f0b6f
Compare
|
On Fr, 2026-07-17 at 07:08 -0700, Luca Toniolo wrote:
grandixximo left a comment (LinuxCNC/linuxcnc#4247)
Never cease to amaze me...
Are we having a meeting with #4099 as central discussion? We skipped
a few weeks, not sure why @rmu75
Seems about time... or is everyone on summer vacation? Probably
better send to the mailing list...
I had some unexpected troubles with "remote" internet access around
21st of june and it seems I got a bit sidetracked... sorry for that.
Last meeting I have notes for was 2026-06-07 10:00 CEST
According to our cadence, 2026-06-21 would have been late, 2026-07-05
early and the next meeting would be tomorrow "late" at 20:00 CEST.
I'm not sure I can make it this weekend but I will try.
I think luca wants to discuss the hal interface redesign, so maybe
change of plan and meeting at 10:00 would be better? please reply
whoever can make it at what time.
https://greenlight.bbb.uni-rostock.de/b/ste-c4d-brs-3k6
Access code: 869782
(hope this is still active)
best regards
…--
Robert Schöftner ***@***.***>
|
|
Should send to the mailing list, my bad for starting it here 😞 |
c4f0b6f to
bce8942
Compare
|
@grandixximo the whole |
2f2ddb5 to
64739e1
Compare
|
Thanks for the quick turnaround, checked the new push. The Two Out-of-Bounds of the same family as ADDF remain:
Minor: literal Loadusr: agreed, the whole path wants a proper rewrite, here or in a follow up. |
64739e1 to
695ac8f
Compare
|
Should be all fixed now, except the param write in hal_set_p, which will take more than a mere moment to alter. |
c6f9e2d to
7f5b5ae
Compare
|
I just tested this branch on a machine with smartserial slaves. the driver loads, and creates all the pins, but nothing works. IO are not getting to the hardware. I cannot see any error messages. |
Thanks for testing. But, I haven't run locally on the hardware for some time. Need to check up on that. Also, the changes in hostmot2 have not yet been properly reviewed, so there can be lurking any detail in any layer of the hm2 driver stack. I'll give a peep when this can be tested again. |
7f5b5ae to
6d5e2e2
Compare
|
Does your test config string enable absenc (ssi/biss/fanuc formats)? |
Out-of-tree .comp components using the legacy HAL types (float, bit, s32, u32, s64, u64, signed, unsigned) and direct pin/param assignment stop working when the HAL API break is performed (LinuxCNC#4099, LinuxCNC#4247). halcompupdate rewrites them to the new API automatically: * declaration types are converted: float->real, bit->bool, s32->si32, u32->ui32, s64->sint, u64->uint, signed->si32, unsigned->ui32 ('port' is left alone, it has no new-style replacement yet) * writes to out/io pins and to params become <name>_set(...) calls, including compound assignments, ++/--, array pins, chained assignments, *<name>_ptr dereferences and writes inside #define macros (macro parameters shadow same-named pins) * legacy C types are modernized (double/real_t -> rtapi_real, hal_bit_t -> volatile rtapi_bool, ...) * reads are unchanged and pins are never renamed, so existing HAL configurations keep working Constructs that cannot be converted safely are left unchanged with a warning for manual conversion: taking the address of a pin/param, direct use of the legacy hal_pin_*_new/hal_param_*_new creation API, postfix ++/-- whose value is used, and array indices with side effects. In-place rewriting is atomic (temp file + rename) and keeps a .bak backup created with O_EXCL|O_NOFOLLOW. halcompile now warns once per deprecated type, pointing to halcompupdate(1), at the spot previously marked for this warning. Docs: migration section in comp.adoc, new halcompupdate(1) manpage, SEE ALSO in halcompile(1). Regression test in tests/halcompile/update-api. Validated by converting all in-tree components from master: 119/124 compile (the other 5 need in-tree headers and fail identically for the already-converted versions), and the output matches the hand conversions in LinuxCNC#4247 functionally.
6d5e2e2 to
ce3117d
Compare
Out-of-tree .comp components using the legacy HAL types (float, bit, s32, u32, s64, u64, signed, unsigned) and direct pin/param assignment stop working when the HAL API break is performed (LinuxCNC#4099, LinuxCNC#4247). halcompupdate rewrites them to the new API automatically: * declaration types are converted: float->real, bit->bool, s32->si32, u32->ui32, s64->sint, u64->uint, signed->si32, unsigned->ui32 ('port' is left alone, it has no new-style replacement yet) * writes to out/io pins and to params become <name>_set(...) calls, including compound assignments, ++/--, array pins, chained assignments, *<name>_ptr dereferences and writes inside #define macros (macro parameters shadow same-named pins) * legacy C types are modernized (double/real_t -> rtapi_real, hal_bit_t -> volatile rtapi_bool, ...) * reads are unchanged and pins are never renamed, so existing HAL configurations keep working Constructs that cannot be converted safely are left unchanged with a warning for manual conversion: taking the address of a pin/param, direct use of the legacy hal_pin_*_new/hal_param_*_new creation API, postfix ++/-- whose value is used, and array indices with side effects. In-place rewriting is atomic (temp file + rename) and keeps a .bak backup created with O_EXCL|O_NOFOLLOW. halcompile now warns once per deprecated type, pointing to halcompupdate(1), at the spot previously marked for this warning. Docs: migration section in comp.adoc, new halcompupdate(1) manpage, SEE ALSO in halcompile(1). Regression test in tests/halcompile/update-api. Validated by converting all in-tree components from master: 119/124 compile (the other 5 need in-tree headers and fail identically for the already-converted versions), and the output matches the hand conversions in LinuxCNC#4247 functionally.
7de3e27 to
9783519
Compare
e5bfa66 to
bc336b9
Compare
5e66d0b to
7cd86bc
Compare
a2bf462 to
51a8a86
Compare
There was a problem hiding this comment.
Checked and not a problem: value_object() has no duplicate-case risk (HAL_SINT/HAL_UINT are unconditionally HAL_S64/HAL_U64 on master now). The piecemeal py::dict style dodges the cppcheck portability warning as commented in pm. The get_info_* semantics in halmodule.cc (list of dicts, same keys) are unchanged, only the collection moved behind the HalQRec snapshot. CI green on a7a528d.
| $(Q)$(CXX) $(LDFLAGS) -shared -o $@ $^ | ||
| $(Q)$(CXX) $(LDFLAGS) -shared -o $@ $^ -lfmt | ||
|
|
||
| HALQUERYSRCS := hal/halquery.cc |
There was a problem hiding this comment.
halquery.so builds unconditionally. What does a source build on a host without python3-pybind11 do now, given configure never probes for the headers? Debian is covered via debian/control, but elsewhere it looks like a mid-compile missing-header failure rather than a clean configure diagnostic. Worth a configure probe, or a note in the build docs?
There was a problem hiding this comment.
Yes, this is a test build so far.
Need to have a configure test to make sure.
There was a problem hiding this comment.
Now, the real problem I'm having is that the halquery.cc file is a normal module that has a a PyInit_halquery entry.
I'm currently at a loss how to create only the submodule with pybind11 that can be added in halmodule using a PyModule_AddObject() call (and does it need a PyType_Ready() call too?).
Your version uses a fake module created within the bounds of halmodule. That does not work here, I believe.
There was a problem hiding this comment.
PYBIND11_MODULE(halquery, m) is only a macro: it emits PyInit_halquery plus a function body that takes a py::module_ &. Nothing else in pybind11 depends on that entry point, so you can write the function yourself and hand it the _hal module object.
In halquery.cc, drop the macro and keep the body:
extern "C" int halquery_add_submodule(PyObject *parent)
{
try {
py::module_ p = py::reinterpret_borrow<py::module_>(parent);
py::module_ m = p.def_submodule("query", halquery_module_doc);
m.def("pin", &get_pin, "Get information about the named pin or None if not found.");
// ... the rest of the defs verbatim
halenumtype = p.attr("type");
halenumdir = p.attr("dir");
halenumcomp = p.attr("comptype");
return 0;
} catch(const std::exception &e) {
PyErr_SetString(PyExc_ImportError, e.what());
return -1;
}
}def_submodule() already does the registration you were going to do by hand: it builds PyModule_GetName(parent) + ".query", calls PyImport_AddModule() on it, so _hal.query lands in sys.modules and import _hal.query resolves, and it assigns the result as the query attribute of the parent. So no PyModule_AddObject() of your own.
No PyType_Ready() either. That is for static types you define with the plain C API; pybind11 finalizes its own types inside py::class_, and halquery has no classes, only free functions.
Then in halmodule.cc, in place of my placeholder block:
if(halquery_add_submodule(m) < 0) {
Py_DECREF(m);
return NULL;
}Two ordering points:
- Call it after
type,dirandcomptypehave been added tom. That is also why the enums are read fromparentabove rather than withpy::module_::import("_hal"): duringPyInit_halthe module is not insys.modulesyet, so that import would re-enter the import machinery. - The GIL is held throughout module init, which is all pybind11 needs, and
hal_lib_init()/hal_lib_exit()stay exactly as they are now that everything lives in one module.
Build side: fold hal/halquery.cc into HALMODULESRCS and drop the HALQUERY target and its PYTARGETS entry. One .so instead of two also removes the question of two pybind11 modules sharing internals across separate objects.
Unrelated to the mechanics, but it comes along for the ride: the three file-scope static py::object globals are destroyed at library unload, after Py_Finalize(), which is a Py_DECREF without an interpreter. Either release them from an atexit-style hook or make them intentionally leaked (static py::object *).
The fake module in my tree was only ever a placeholder for exactly this. Once the real bindings live in _hal, the PyModule_New() block goes away and the only piece that stays is the sys.modules["hal.query"] alias in hal.py, because hal is a plain module and not a package.
There was a problem hiding this comment.
PYBIND11_MODULE(halquery, m)is only a macro: it emitsPyInit_halqueryplus a function body that takes apy::module_ &. Nothing else in pybind11 depends on that entry point, so you can write the function yourself and hand it the_halmodule object.
That was what I was missing. Thanks!
I had the idea that pybind11 would have had a construct for this as it probably isn't an impossible situation.
Two ordering points: [snip]
yes... all fixed now...
Unrelated to the mechanics, but it comes along for the ride: the three file-scope
static py::objectglobals are destroyed at library unload, afterPy_Finalize(), which is aPy_DECREFwithout an interpreter. Either release them from an atexit-style hook or make them intentionally leaked (static py::object *).
Yes, I thought about that they would leak. If they are not decref'ed, then they hold the enums and therefore the _hal module. But, it does not prevent Py_AtExit to call the hal_lib_exit() function, which is the real issue. The only time halmodule is unloaded is at program exit (or crash). Not sure if it is essential to do a full deconstruction. Does it matter?
There was a problem hiding this comment.
No, it does not matter, and my remark was too strong: I rebuilt the pattern outside LinuxCNC and it is harmless.
What I am less sure of is whether we want to depend on why it is harmless. The static destructor does run after finalization, but the object still carries 10 references, so the decref never reaches tp_dealloc. That count is a CPython artifact: an IntEnum class is self-referential through its members and _value2member_map_, and the C-held reference sits outside the cycle, so the collector cannot take it either. It holds with the parent module on multi-phase init and dropped from sys.modules as well; the only shape I could get to abort was a plain acyclic object whose sole owner is the static.
That shape stops aborting when the global is a raw PyObject *, incref'd once at init and never released: same object, same sole ownership at refcount 1, exit 0 instead of Fatal Python error: PyThreadState_Get. It is py::object(...).release().ptr() at init, and the call sites take a py::handle(...) wrap; tagging still returns <HalType.BOOL: 1> as before.
So, a question I cannot answer for you: is "the refcount is never 1" something you want in the trust base, or would you rather those three globals never run a destructor at all? The pointer version is the same leak you have already accepted, without the dependency. Is there something that argues for keeping them as py::object?
There was a problem hiding this comment.
I'd hate to use raw PyObject pointers. Afaik, using py::object should have increased the refcount, so when the destructor (tries to) kills it, the underlying object should remain. Alternatively, we can enforce it by doing:
halenumtype = p.attr("type");
halenumdir = p.attr("dir");
halenumcomp = p.attr("comptype");
halenumtype.inc_ref();
halenumdir.inc_ref();
halenumcomp.inc_ref();Then we don't care about the C++ destructor and purposefully leak the reference.
There was a problem hiding this comment.
Agreed, and better than my version: no call site changes and the C++ type stays. I ran it against the shape that aborts, a plain acyclic object whose only owner is the static, and with the inc_ref() it goes from Fatal Python error: PyThreadState_Get to refcount 2 at init, 1 after the destructor, exit 0, clean under PYTHONMALLOC=debug.
One thing to be careful about in the reasoning, though. The reference py::object takes is exactly the one its destructor gives back, so on its own it nets to zero and protects nothing; the object survives today only because something else still holds it. Your inc_ref() works because it adds a second reference, not because the first one is there.
Which is why I would put a line of comment next to the three calls. They read as redundant, and the next person tidying up will delete them.
This is the full tree conversion for the HAL types where #4099 would be the first step.
Changes:
State of progress of these changes:
Missing in this tree: