hal: Add halcompupdate to migrate .comp files to the new HAL API - #4256
grandixximo wants to merge 6 commits into
Conversation
3a9267a to
5865295
Compare
|
@BsAtHome If this is too ugly I'll drop it, this does most of your careful hand conversion, to alleviate the porting churn for comps out of tree, let me know how much you hate it ;-) |
|
The old hal types should never be used anywhere anymore. All places I've see they were used wrongly as variable types and that fails the smell-test miserably because they are volatile. For components (.comp files), that only declare their pins and params through halcompile means, will not see a difference (except for the _set() suffix for writing). However, those constructs that add their own pins/params need more care in the conversion. |
5865295 to
937f5be
Compare
|
Thanks for the review. Aligned the C-type conversion with that:
|
|
FWIW, I don't think we should even attempt to fix the component files automatically. The only risk-free change is to fixup pins/params to use the new types. Everything else is intrinsically a huge risk in an auto conversion that needs to consider the context it is used in. That is not something you can do automatically. You could do considerable damage. IMO, the risks outweigh the benefits. |
Partially I agree, but I don't like what the lack of an attempt says...
The tool is strictly opt-in. It does not pop up and ask you to convert like the INI converter does, and doesn't convert automatically on build, it sits there until someone runs it, and its default mode only prints a diff for review.
Types and writes are inseparable. Converting The tool already stays inside the risk-free zone you describe, it converts only the mechanical subset (type renames and direct writes. The writable namespace is exactly the declared pins/params, no aliasing, no pointers beyond |
80f55fb to
1e54db9
Compare
1e54db9 to
5e47849
Compare
|
@BsAtHome This needs a decision now that #4472 is merged. I've rebased and added a commit pointing the hal_*_t deprecation warnings at halcompupdate(1), so merging or closing this PR settles the question in one action. My position is unchanged: the tool is strictly opt-in, diff-only by default, converts only the mechanical subset, and is verified against all 124 in-tree comps. Not perfect, but for comps that only declare pins/params through halcompile it beats hand-editing every write. And "only fix the types" is not a middle ground, renaming types without converting the writes breaks the comp immediately. If you still think the risks outweigh the benefits even in this form, I'll close it. But I'd like to hear why, and what would have to change for it to be acceptable. |
|
I don't think it is about the risk, at least not for us. That would be a user problem ;-) The real question is whether it is worth the bother. Is the added value greater than the user-effort required to do a manual conversion? I'm still quite ambivalent. Yes, it does help the user. No, it may cause hard problems. Do the advantages outweigh the (potential) problems? |
5e47849 to
417219a
Compare
|
On "worth the bother": a manual conversion is not just the type renames, it is every write to every pin and param, each one a chance to typo a setter or miss an array-index form. For a comp with a handful of writes that is trivial. For the long tail of comps with dozens of writes it is exactly the kind of mechanical, error-prone editing a script does better than a human. The in-tree comps got expert hand conversion; out-of-tree authors do not get that attention. On false sense of security: the tool now ends every file with a summary, "N mechanical changes, M constructs left for manual review; review the diff and test before use". Default mode prints only a diff, nothing is written unless asked, --in-place keeps a .bak. Everything the tool cannot prove safe (pointers, volatile qualifiers, raw creation API, side-effect indices, EXTRA_SETUP writes) is left unchanged with a specific warning. I do not claim it makes conversion safe; it makes the safe part cheap and the unsafe part loud. And the alternative to the tool is not careful manual conversion of out-of-tree comps, it is no conversion at all: they simply fail to build after the API break. For those authors an imperfect opt-in helper beats nothing. |
|
Okay, fine then. Let me take another look and see if I can warm to the idea of auto conversion ;-) |
417219a to
8e36763
Compare
|
There is one more thing that I noticed when doing conversion. There are several pins/params that have |
|
One thing I found while reading On the Would the directive cover params as well as pins, or pins only? Which name becomes canonical? In And what happens to I ask because there is no precedent for a component shipping aliases; so far they have been purely a user-side tool. For halcompupdate none of this changes anything: it does not rename and I do not intend it to, an alias makes a rename non-breaking but not mechanical. Would you want it to at least point the names out, a gentle warning naming pins and params whose name mentions a type that no longer exists, leaving the decision to the author? |
|
Yeah, there is some copy/paste in there... :-) Both pins and params should be aliased when using halcompile. Working on it in my tree (pins done). The current format: That generates the same "visible" pin list and moves the alias name into oldname. The
It is not always easy to get rid of the type name. See for example The question is whether it should be:
The first keeps the same names visible as everybody was used to. The second makes the new name visible while still allowing the old name. Either way works. Not sure which is the best (probably the second). What happens to halcmd... Well, I renamed the types, all of them. So there are already a lot of changes coming. Saving the list will already change the line. The user alias question is a good one. No opinion as of yet. The real question is how hard we are going to break every config out there. Probably best to break it completely. There also is this issue of the And, yes, halcompupdate should probably warm if it detects type names in the pin/param name. |
|
You are right. Removed the in-tree component aliases. Still implemented it in halcompile for integrators if they wish that much. The conv-X components rename will break some substantial portion of user's configs. Therefore, the aliases are a patch that would hurt more than you get out of it. Once you need to redo the config, then you should redo it thoroughly. That makes me wonder whether a similar tool as this will be helping to rewrite .hal files for the user. Could even get the file list from an INI file... Want to hack a bit more? ;-) |
|
Yes, happy to hack on that. Here is the shape I would propose before I write any of it, because it deliberately does not work like the INI updater. The trigger is the error, not the tool. No dialog, no prompt on startup, nothing runs automatically. A user starts the config that worked yesterday, hits the failure at the exact line that uses a name we removed, and the error message itself names the replacement and mentions the converter. Users who never touch a renamed name never hear about it. This is safe here in a way the INI updater is not: the old names are gone, so every affected config fails deterministically. There is no silent-wrong-behaviour class to worry about, and the mapping is not a guess: Pieces: A generated One lookup on three existing error paths, only after the call has already failed, so nothing costs anything when the config is fine:
Output would read roughly: And the converter itself, shaped exactly like The name is not precious to me, so if you would rather it were called something else, suggest away. Two limitations I want to state up front rather than discover later. Not every failure goes through halcmd: the Python Does this shape look right to you? |
5473e87 to
1294d8c
Compare
|
Embedding lists with things that changed in an executable may not be the best of ways to "fix" a situation. Reminds me of eternal backward compatibility code. You also say, you cannot catch al instances anyway, so the argument for "safe conversion" is not really applicable because you cannot fix everything automatically. Having a table of names in hal_lib is even worse and reminds me of the "wonderful" world of (shattered) windows. We decided we were going to break people's configs. We can help them overcome the version change. We should not try to fix all problems. Making the conversion easier is all that we can do and should not try to burden our own code with more legacy. Going through the .hal files is probably the best estimate to fix most of the user's pain. If people are using pins from other code, then that cannot be fixed automatically without full semantic analysis. Just like halcompupdate, you will not be doing this automatically. However, there will be more configurations that need a .hal file update than there will be local out-of-tree components. You really don't know the pin names for sure without going through all the logic. Components can be loaded with a I'm not sure we need a CI check. We do all conversion beforehand and all sims should be tested. There are some that even today need updating. However, a dry-run mode that just tells you what would be done is nice. That would be nice for a single .hal file as well as ini-file batch mode. I think you called it "diff" ;-) My color is We may be somewhat fortunate in that there will be an INI-file version update too (if we get that code working). We can attach the |
1294d8c to
52f3191
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.
Writes to pins/params inside EXTRA_SETUP convert cleanly to setters, but the setter uses a reference that halcompile initializes only after extra_setup() has run, so the converted component crashes when loaded (seen with multiswitch: top_position_set() on a NULL ref in extra_setup, LinuxCNC#4272). Track the current body section while rewriting and warn when a converted write lands in EXTRA_SETUP, distinguishing params (direct assignment worked before, this is a regression) from pins (direct writes were already invalid). EXTRA_CLEANUP and FUNCTION writes stay silent; the conversion itself is unchanged.
POST_EXPORT() (halcompile option post_export yes) is now the supported place for pin/param initialization that must happen after creation, so advise it instead of the first FUNCTION pass. Assert the advice in the test.
Body code converts legacy hal_*_t types to the volatile-preserving form ('hal_bit_t x' -> 'volatile rtapi_bool x'), semantics-identical whether or not the qualifier is load-bearing; each such conversion is reported with its line number so the spots can be reviewed. 'variable' declarations cannot express volatile, so legacy hal_*_t types there are reported and left unchanged for manual conversion.
Body warnings now report file line numbers instead of body-relative ones.
Each file ends with a summary of mechanical changes applied and constructs left for manual review.
A pin, param, component or function name containing a legacy type as a segment (out_s32, input_bit, conv_s32_float) is stale once the type is removed at the API break. Note it and suggest the new-style spelling (out_si32, input_bool, conv_si32_real), leaving the rename to the author: names are part of the HAL configuration interface - the component name is the loadrt/loadusr argument and module name, functions are exported as comp.N.name - so they are never changed, and the note does not affect the --check exit status or the manual-review count. The word in a name may not mean the type (plasmac's float_switch is a hardware float switch, demux's sel-bit counts bits of a value); the note is then simply dismissed. Only full segments match, so 'floating' is not noted for 'float', and duplicated segments are mentioned once. 'signed' and 'unsigned' are not checked in names: no sensible word replaces them there. The note points at the line of the declaration itself, and the per-file summary reports the note count even when nothing else needed conversion. Run over all in-tree components: 54 names are noted (the 34 conv_*, abs_*, scaled_s32_sums and tristate_* component names, and 20 pins in demux, histobins, histobinstream, multiswitch, plasmac, reset, mesa_7i65), each a legacy type word used as a name segment.
52f3191 to
4beafb4
Compare
|
@BsAtHome Rebased onto the break. With #4565 in, halcompile still takes the old declaration types with a warning, but a comp that writes a pin with plain assignment no longer compiles, which is most out-of-tree comps. That is the part this tool converts. Two changes since you last looked:
I checked it on the 120 in-tree comps as they were before your conversion: converted and compiled with today's halcompile, 101 build, and the other 19 hit exactly what the tool leaves to the author with a warning, or need in-tree headers. |
|
|
||
| The tool converts the declaration types and rewrites writes to pins and parameters | ||
| (including compound assignments and dereferences of 'name_ptr') to the '_set' form. | ||
| A 32-bit pin or parameter becomes 'si32' or 'ui32', which keeps its behaviour; the tool points out each one, since 'sint' and 'uint' are the types to move to where the code does not rely on the 32-bit range or on wrap-around. |
There was a problem hiding this comment.
I think this needs to include a strong note that they need to do a 64-bit clean analysis and move the [su]i32 types to proper [su]int types. The note should include that a code analysis is required to prevent inadvertent truncation in intermediate storage and calculation.
4beafb4 to
6330001
Compare
|
@BsAtHome A question on the docs: hal.h says the si32/ui32 getters and setters are only there for compatibility and may be removed once the remaining code is updated, but also that there's a case for keeping them. Are you still heading towards removing si32 and ui32? If so, I'll describe them in comp.adoc as due for removal rather than kept for compatibility. |
6330001 to
1e4e7d5
Compare
|
That is a hard question. I'm undecided whether we leave it to the compiler or that we do this by name. The setters can, in principle, be removed. The compiler will do the word size extension. However, there is a possibility of sign mismatch because the setters are "functions" and the arguments are first cast and then sign-extended. Using the 64-bit setter would probably do the sign-extension before the cast. This needs to be tested. |
HAL is 64-bit only now. The tool still converts s32 and u32 to si32 and ui32, which keep the component's 32-bit behaviour for now, but that is a stopgap: the component has to be made 64-bit clean, and that takes an analysis of its code, since a value that passes through 32-bit intermediate storage or a 32-bit calculation is silently truncated. Each such declaration is noted with its line and counted in the summary, the documentation says so as a requirement, and a name that spells a type is suggested with sint and uint, as the in-tree renames spell it. The documentation now describes HAL after the API break: plain assignment to a pin no longer compiles and the legacy declaration types are accepted with a warning.
1e4e7d5 to
56fee69
Compare
|
Ok, will keep it on a softer
We will see how thing go... |
Problem
The HAL API break is in (#4565): pins and parameters are read and written through typed getters and setters, and HAL is 64-bit only.
halcompilestill accepts the legacy declaration types (float,bit,s32,u32,s64,u64,signed,unsigned) with a warning, but an out-of-tree.compthat writes a pin or parameter with plain C assignment (out = x;,count++;,*out_ptr = x;) no longer compiles. That is nearly every component that has an output.Solution
A new tool,
halcompupdate, rewrites legacy.compfiles to the new API:float->real,bit->bool,s32->si32,u32->ui32,s64->sint,u64->uint,signed->si32,unsigned->ui32(portis left alone, no new-style replacement yet)x = e->x_set(e),x += e->x_set(x + (e)),x++->x_set(x + 1), arraysx(i) = e->x_set(i, e), chaineda = b = e->a_set(b_set(e)),*x_ptr = e->x_set(e),*x_ptrreads ->(x)#definemacrosdouble/real_t->rtapi_real;hal_bit_t->volatile rtapi_booletc., thevolatilequalifier is kept so the conversion is semantics-identical, and each conversion is reported with its line number for review (--no-c-typesto skip)name(idx)) works in both APIss32andu32becomesi32andui32, which keep the component's 32-bit behaviour. Moving on to the 64-bitsintanduintdepends on whether the code relies on the 32-bit range or on wrap-around, so the tool leaves that to the author and notes each such declaration with its line.Pin and parameter names are never renamed, because they are part of the HAL configuration interface, and existing
.halfiles keep working. A name that spells a type word is noted with the spelling the in-tree renames use (out_s32->out_sint), and the rename is left to the author.Usage:
halcompile's own warning for a legacy declaration type now ends with "halcompupdate(1) converts a .comp."Safety
os.replace(), original mode preserved). Backups are created withO_EXCL|O_NOFOLLOWand a unique-name fallback, so a pre-existing.bak(or a planted symlink) is never clobbered.&x), direct use of the legacyhal_pin_*_new/hal_param_*_newcreation API, postfix++/--whose value is used (the setter yields the new value, postfix must yield the old), array indices with side effects, pointers to legacy HAL types, andhal_*_ttypes invariabledeclarations (the grammar allows only a single-word type there, so the volatile qualifier cannot be preserved).EXTRA_SETUPare converted but warned about: the setter references are initialized only afterextra_setup()runs.Validation
halcompilefrom a plain directory (the out-of-tree case): 101 compile cleanly. Of the other 19, 5 (spindle,raster,mesa_pktgyro_test,homecomp,tpcomp) need in-tree headers orTOPDIRto build at all, and 14 use exactly what the tool leaves to the author with a warning: the legacy creation API,hal_*_tin avariabledeclaration, or a pointer into HAL memory.tests/halcompile/update-apicovers declarations, writes, unsafe constructs, EXTRA_SETUP writes, volatile preservation, the 32-bit notes, name notes and the summary output.Docs
Migration section in
docs/src/hal/comp.adoc(type table and_set()accessor documentation),halcompupdate(1)manpage, and a SEE ALSO link inhalcompile(1).