8000 Rename `matches_any()` to `any_matching_bits_set()`, implement new `matches_any()` by hudson-ayers · Pull Request #3336 · tock/tock · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Rename matches_any() to any_matching_bits_set(), implement new matches_any() #3336

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 8, 2023

Conversation

hudson-ayers
Copy link
Contributor
@hudson-ayers hudson-ayers commented Nov 28, 2022

Pull Request Overview

The current implementation of matches_any() does not implement the functionality the name implies. This PR renames the existing implementation to a name which better describes its functionality, and introduces a new matches_any() function (with a different interface) that actually correctly implements the functionality suggested by the name.

This PR also adds several tests to the tock-registers test suite to verify the new version works as expected, and removes a feature gate on a feature that no longer exists for the crate which was preventing some of the tock-registers test suite from being run as part of cargo test.

#3311 (comment) contains a complete description of the issues with the current method.

Fixes #3311

Testing Strategy

This pull request was tested by cargo test.

TODO or Help Wanted

This pull request still needs feedback on method names

Documentation Updated

  • README.md updated.

Formatting

  • Ran make prepush.

@github-actions github-actions bot added nrf Change pertains to the nRF5x family of MCUs. sam4l Change pertains to the SAM4L MCU. tock-libraries This affects libraries supported by the Tock project labels Nov 28, 2022
@bradjc
Copy link
Contributor
bradjc commented Nov 28, 2022

Need to update readme.

lschuermann
lschuermann previously approved these changes Nov 28, 2022
Copy link
Member
@lschuermann lschuermann left a comment

Choose a reason for hiding this comment

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

Looks good except for the pending README update. Thanks @hudson-ayers!

@hudson-ayers
Copy link
Contributor Author

README updated

Copy link
Contributor
@bradjc bradjc left a comment

Choose a reason for hiding this comment

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

Fixing this is a little confusing.

If we look at what is probably a very old use of this API (in SAM4L USART), it makes it more clear what matches_any() was trying to do do:

        let ints_active = self.registers.imr.matches_any(
            Interrupt::RXBUFF::SET
                + Interrupt::TXEMPTY::SET
                + Interrupt::TIMEOUT::SET
                + Interrupt::PARE::SET
                + Interrupt::FRAME::SET
                + Interrupt::OVRE::SET
                + Interrupt::TXRDY::SET
                + Interrupt::RXRDY::SET,
        );

...

        if !(rx_active || tx_active || ints_active || is_panic) {
            pm::disable_clock(self.clock);
        }

Beside my point about matches_all() no longer being consistent, do we actually want two functions? It seems like what we want from an API point of view is the new matches_any(), and to update all current uses to the new version.

For example, this code in apollo3 stimer looks suspicious:

    fn is_running(&self) -> bool {
        let regs = self.registers;
        regs.stcfg.any_matching_bits_set(STCFG::CLKSEL::XTAL_DIV2)
    }

Particularly when given what start() does:

    fn start(&self) -> Result<(), ErrorCode> {
        // Set the clock source
        self.registers.stcfg.write(STCFG::CLKSEL::XTAL_DIV2);
        Ok(())
    }

Shouldn't the is_running() check be regs.stcfg.matches_all([STCFG::CLKSEL::XTAL_DIV2])?

I can see an efficiency argument for including any_matching_bits_set(), but I'm worried that if we can't restrict it to only single bit fields then it can be used in ways it shouldn't be and lead to very strange behavior.

fn matches_any(&self, field: FieldValue<Self::T, Self::R>) -> bool {
field.matches_any(self.get())
fn any_matching_bits_set(&self, field: FieldValue<Self::T, Self::R>) -> bool {< 8000 /span>
field.any_matching_bits_set(self.get())
}

#[inline]
/// Check if all specified parts of a field match
fn matches_all(&self, field: FieldValue<Self::T, Self::R>) -> bool {
field.matches_all(self.get())
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like matches_all() needs to change as well. This is now all_matching_bits_set(), right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

matches_all() is actually already correct -- since it was already checking for an exact match across the entire passed FieldValue, it works correctly for multi-bit fields and for matching on cleared bits as well as set bits.

For convenience here are the two implementations:

    /// Check if any of the bits covered by the mask for this
    /// `FieldValue` are set in the passed value
    #[inline]
    pub fn any_matching_bits_set(&self, val: T) -> bool {
        val & self.mask != T::zero()
    }

    /// Check if all specified parts of a field match
    #[inline]
    pub fn matches_all(&self, val: T) -> bool {
        val & self.mask == self.value
    }

matches_all() actually looks at self.value() and makes sure that the passed value, when masked, matches it. So the problem in #3311 does not apply.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok good point. Seems like if 8000 there is only one FieldValue, then it should be .matches().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that makes sense to me, but I don't think the current name is really wrong ... not sure how to balance that with a desire not to modify the API of tock-registers more than needed, especially since it is the one library with non-Tock downstream users

@hudson-ayers
Copy link
Contributor Author

Beside my point about matches_all() no longer being consistent, do we actually want two functions? It seems like what we want from an API point of view is the new matches_any(), and to update all current uses to the new version.

For example, this code in apollo3 stimer looks suspicious:

    fn is_running(&self) -> bool {
        let regs = self.registers;
        regs.stcfg.any_matching_bits_set(STCFG::CLKSEL::XTAL_DIV2)
    }

Yeah, good call, this is incorrect, because CLKSEL is a multi-bit field.

Shouldn't the is_running() check be regs.stcfg.matches_all([STCFG::CLKSEL::XTAL_DIV2])?

For the interfaces currently defined in this PR, the correct change would be either regs.stcfg.matches_all(STCFG::CLKSEL::XTAL_DIV2) or regs.stcfg.matches_any([STCFG::CLKSEL::XTAL_DIV2])

I can see an efficiency argument for including any_matching_bits_set(), but I'm worried that if we can't restrict it to only single bit fields then it can be used in ways it shouldn't be and lead to very strange behavior.

Yeah, the reason I included it is for efficiency -- when just matching on a bunch of single bit fields, it is much more efficient than the new matches_any(), which has to loop through each passed field. I don't think there is an easy way to enforce that it can only be called with a FieldValue composed of multiple single-bit FieldValues added together though.

@hudson-ayers
Copy link
Contributor Author

Pushed an update to fix that stimer example (which was broken before this PR, to be clear)

@hudson-ayers
Copy link
Contributor Author
hudson-ayers commented Nov 29, 2022

I would be onboard with removing any_matching_bits_set() and just forcing users to use the less-efficient, more correct matches_any() if people prefer that. If someone really needs the efficient implementation it is easy enough to handwrite

    // current approach
    // let any_bits_set_efficient = self.regs.srp.any_matching_bits_set(TX::INT::SET + RX::INT::SET);
  
    // new approach if we remove `any_matching_bits_set()`
    let any_bits_set_efficient = self.regs.srp.get() & (TX::INT::SET + RX::INT::SET).mask() != 0;

@hudson-ayers
Copy link
Contributor Author

Any thoughts on these two approaches? I personally am in favor of what is currently in the PR, but don't feel too strongly

@bradjc
Copy link
Contributor
bradjc commented Dec 16, 2022

What I still feel like is swapping one quirk for another is that reg.any_matching_bit_set(INTERRUPT::BIT1::CLR + INTERRUPT::BIT2::CLR) works the same as reg.any_matching_bit_set(INTERRUPT::BIT1::SET + INTERRUPT::BIT2::SET). This API really shouldn't use FieldValues it should use Fields, but we can't do concatenation on Fields (INTERRUPT::BIT1 + INTERRUPT::BIT2 doesn't compile) so if we want this efficient functionality we need to use FieldValues.

Can we & in the FieldValue value? That way it 1) forces the more intuitive use case (::SET rather than ::CLR) and 2) makes the adjective "set" apply to both the FieldValue and the underlying register value, rather than just one (leaving the caller to understand which applies).

#[inline]
    pub fn any_matching_bits_set(&self, val: T) -> bool {
        val & self.mask & self.value != T::zero()
    }

We could also call it any_set()

           if interrupts.any_set(
                Status::BUSERR::SET
                    + Status::SMBPECERR::SET
                    + Status::SMBTOUT::SET
                    + Status::ORUN::SET
                    + Status::URUN::SET,
            ) {...}

@hudson-ayers
Copy link
Contributor Author

What I still feel like is swapping one quirk for another is that reg.any_matching_bit_set(INTERRUPT::BIT1::CLR + INTERRUPT::BIT2::CLR) works the same as reg.any_matching_bit_set(INTERRUPT::BIT1::SET + INTERRUPT::BIT2::SET).

Can we & in the FieldValue value? That way it 1) forces the more intuitive use case (::SET rather than ::CLR) and 2) makes the adjective "set" apply to both the FieldValue and the underlying register value, rather than just one (leaving the caller to understand which applies).

#[inline]
    pub fn any_matching_bits_set(&self, val: T) -> bool {
        val & self.mask & self.value != T::zero()
    }

We could also call it any_set()

           if interrupts.any_set(
                Status::BUSERR::SET
                    + Status::SMBPECERR::SET
                    + Status::SMBTOUT::SET
                    + Status::ORUN::SET
                    + Status::URUN::SET,
            ) {...}

Yeah, I think that seems like a reasonable approach. We will have to re-write the unit tests for the function, as this change breaks the current unit tests, but I find the current unit tests somewhat incomprehensible anyway

@lschuermann
Copy link
Member

This API really shouldn't use FieldValues it should use Fields, but we can't do concatenation on Fields (INTERRUPT::BIT1 + INTERRUPT::BIT2 doesn't compile) so if we want this efficient functionality we need to use FieldValues.

@bradjc I mean, presumably we could override the bitwise-OR operator for Fields such that we could do something like the following?

interrupts.any_set(
    Status::BUSERR
    | Status::SMBPECERR
    | Status::SMBTOUT
    | Status::ORUN
    | Status::URUN
)

Although this may also make it more confusing, as we don't know specifically which values we're using (presumably this would be T::max() & self.mask).

We could also call it any_set()

Not a fan of that as long as we're accepting any T, given that nothing's stopping a user from composing arbitrary things as the function argument, or just using raw integer types. Then "any" looses all meaning.

@hudson-ayers
Copy link
Contributor Author

This API really shouldn't use FieldValues it should use Fields, but we can't do concatenation on Fields (INTERRUPT::BIT1 + INTERRUPT::BIT2 doesn't compile) so if we want this efficient functionality we need to use FieldValues.

@bradjc I mean, presumably we could override the bitwise-OR operator for Fields such that we could do something like the following?

interrupts.any_set(
    Status::BUSERR
    | Status::SMBPECERR
    | Status::SMBTOUT
    | Status::ORUN
    | Status::URUN
)

Although this may also make it more confusing, as we don't know specifically which values we're using (presumably this would be T::max() & self.mask).

Yeah, I think that overriding the bitwise OR operator is probably going to make things more confusing, I would vote against that.

We could also call it any_set()

Not a fan of that as long as we're accepting any T, given that nothing's stopping a user from composing arbitrary things as the function argument, or just using raw integer types. Then "any" looses all meaning.

In practice, I think it was a mistake to make most of the functions in fields.rs pub, instead of pub(crate). It really only makes sense for these functions to be called by the outer functions on the Readable/Writable traits. Those functions do not accept arbitrary T anyway, which would resolve that concern. Would you like any_set() if the implementation in fields.rs was pub(crate)?

@lschuermann
Copy link
Member

In practice, I think it was a mistake to make most of the functions in fields.rs pub, instead of pub(crate). It really only makes sense for these functions to be called by the outer functions on the Readable/Writable traits. Those functions do not accept arbitrary T anyway, which would resolve that concern.

Yes, I agree with that. This function seem destined for misunderstanding their implementation and do not constrain the input types well to limit it to a reasonable subset of possible behaviors.

Would you like any_set() if the implementation in fields.rs was pub(crate)?

I like &ing self.value() in its implementation as proposed by @bradjc, but I'm not sold on the any_set name. I think something more explicit like any_matching_bits_set is much more expressive.

As a side note, something that made me skittish when seeing the example above was the use of the + operator. Even though I'd consider myself very familiar with the library, my intuition was that presumably something like Status::BUSERR::SET must return a T (instead of our FieldValue), and then + would only be equal to a bitwise-or if these were distinct, non-overlapping fields. Considering the usual add-operator semantics, it seems that overloading Add instead of BitOr on FieldValue was a mistake.

The current implementation of matches_any() does not implement the
functionality the name implies. This commit renames the existing
implementation to a name which better describes its functionality,
and introduces a new matches_any() function (with a different interface)
that actually correctly implements the functionality suggested by the
name. This commit also adds several tests to the tock-registers test
suite to verify the new version works as expected, and removes a feature
gate on a feature that no longer exists for the crate which was
preventing some of the tock-registers teste suite from being run as part
of `cargo test`.
@hudson-ayers
Copy link
Contributor Author

I rebased this PR, and made the following updates:
2. FieldValue::any_matching_bits_set() now &'s in self.value, forcing the more intuitive use case (i.e. my_interrupts.any_matching_bits_set(MY_FIELD::CLEAR) will never return true).
3. Updated the tests to reflect this functionality change

I did not make the following changes:

  1. I did not change the name to any_set() based on Leon's pushback.
  2. I did not make FieldValue::any_matching_bits_set() pub(crate), since it turns out that litex_registers.rs has to be able to call it. This bums me out a bit, but I didn't see an easy fix for this that did not involve moving much of the logic from litex_registers into tock_registers. Still, it doesn't really feel right that we are basing the interface to tock_registers off the peculiarities of litex_registers, especially in this case where the function being public makes it much more likely to be used incorrectly in place of the correct method on the Field struct

@hudson-ayers
Copy link
Contributor Author

ping? @lschuermann @bradjc

Copy link
Contributor
@bradjc bradjc left a comment

Choose a reason for hiding this comment

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

I've stood in the way long enough!

@bradjc
Copy link
Contributor
bradjc commented Mar 8, 2023

bors r+

@bors
Copy link
Contributor
bors bot commented Mar 8, 2023

@bors bors bot merged commit 2ab8466 into tock:master Mar 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
nrf Change pertains to the nRF5x family of MCUs. sam4l Change pertains to the SAM4L MCU. tock-libraries This affects libraries supported by the Tock project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

tock-registers: CLEAR FieldValue does not perform match check with inverted mask
4 participants
0