Skip to content
Open
Changes from all commits
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
53 changes: 53 additions & 0 deletions src/hal/hal.h
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,24 @@ extern int hal_get_signal_value_by_name(
extern int hal_get_param_value_by_name(
const char *name, hal_type_t *type, hal_data_u **data);

/***********************************************************************
* MISC HELPER FUNCTIONS *
************************************************************************/

/** hal_extend_counter() extends a counter value with nbits to 64 bits.

For some hardware counters it may be desireable to extend their
range beyond their native width.

This function maintains a 64bit counter value and deals with wrap
arounds. Increments between updates need to be less than 2**(nbits-1).
Call with current 64bit counter value to be updated as @param old,
new lower-bit counter value read from hardware as @param newlow
and width of counter as @param nbits.
@returns new counter 64bit counter value.
Code by Jeff Epler. */
extern rtapi_s64 hal_extend_int(rtapi_s64 old, rtapi_s64 newlow, int nbits);

Comment on lines +711 to +712
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prototype is effectively superfluous unless you try to link in a system that does both not inline and not define that static function locally as static. But, since we are using a C compiler that actually does inline, I do not see the point of the prototype.

Or is there something that I missed?

/***********************************************************************
* EXECUTION RELATED FUNCTIONS *
************************************************************************/
Expand Down Expand Up @@ -983,6 +1001,41 @@ extern bool hal_stream_writable(hal_stream_t *stream);
extern void hal_stream_wait_writable(hal_stream_t *stream, sig_atomic_t *stop);
#endif


/***********************************************************************
* MISC HELPER FUNCTIONS *
************************************************************************/

static inline rtapi_s64 hal_extend_counter(rtapi_s64 old, rtapi_s64 newlow, int nbits)
{
/* Extend low-bit-width counter value to 64bit, counting wrap arounds resp.
"rotations" in additional bits.

see https://github.com/LinuxCNC/linuxcnc/pull/3932#issuecomment-4239206615
This code avoids branches and undefined behaviour due to signed overflow
Idea from MicroPython.

To avoid messy code to sign extend a lower-width value to 64bits, shift
the counter value such that the sign bit gets into the MSB.
Calculate the delta from the stored value in the shifted base.
Shifting back the delta assures the sign is extended properly.
Addition as unsigned avoids signed overflow, which would be undefined
behaviour.

Attention has to be paied that the magnitude of increments / decrements
of the counter stay within 1<<(nshift-1) between calls to this function,
else the wrap around will be counted in the wrong direction.
Comment on lines +1025 to +1027
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may think that I am pedantic here, but I'm just reading from a blanked mind (like one from 5 years in the future) and try to fit that into why I would need this function.

It still does not explain what the arguments old and newlow actually are in the context of extending a counter. Which of these arguments is the counter?
The names suggest that there is something "old", but it is unclear what is old. There also is something "new" and apparently also "low" at the same time. But I cannot fathom, from the description, how these names fit into the algorithm.

BTW, the name hal_extend_counter() does seem much more appropriate, but there is more going on than just extending the counter. It is calculating a counter increment from the difference of the arguments and adding that difference to one of the arguments. This exploits the behaviour of unsigned verses two's complement calculations. It uses smaller values as source into a larger register set as if the calculation was performed on a "nbits" register in an unsigned operation (exploiting overflow behaviour) casted to a two's complement result. The final calculation is furthermore (ab)using the unsigned/signed duality. How exactly is this an "easy" algorithm to follow?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment next to prototype. This comment here in the impl explains what the code is doing and is inside. IDE will display the other comment if oyu hover over a function call.

In fact it doesn't matter if you work with signed or unsigned counter values, the important thing is that the difference has the proper sign and is calculated with width nbits. instead of figuring out the high bits, shifting it up gets proper overflow behaviour and the "extra (0) bits" towards LSB don't matter.


Code contributed by Jeff Epler.
*/
int nshift = 64 - nbits;
rtapi_u64 oldlow_shifted = ((rtapi_u64)old << nshift);
rtapi_u64 newlow_shifted = ((rtapi_u64)newlow << nshift);
rtapi_s64 diff_shifted = newlow_shifted - oldlow_shifted;
return (rtapi_u64)old + (diff_shifted >> nshift);
}


RTAPI_END_DECLS

#endif /* HAL_H */
Loading