-
Notifications
You must be signed in to change notification settings - Fork 707
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
Replace DeferredCall
and DynamicDeferredCall
with a more general-purpose implementation
#3382
Conversation
cf4f0b6
to
c19cf7d
Compare
bc011ea
to
a8ba255
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just done another pass over all changes to boards
, components
and chips
in an effort to ensure that
- all register calls use the fully-qualified module path, to avoid a call to
.register()
and imports ofDeferredCallClient
without additional context. - all implementations of
.init()
,.register_circular_deps()
, etc. register all required deferred calls. - each board which retrieves a peripheral struct that has a required initialization routine calls this function.
Some additional noteworthy consequences of this proposed architecture which I've observed from this code review are:
- deregistration of deferred calls no longer trivially possible.
- constructors of peripherals can no longer be const
Both of these points don't seem like blockers to me.
Open questions:
- should every peripheral struct have an initialization method? Should we unify their names? This makes code review simpler. Sometimes, chips which use a common base-crate and peripheral struct, and then extend that with other peripherals will even go as far as to implicitly rename the peripheral initialization method.
- Can we somehow enforce that this method is called? It's easy to forget and causes headaches. Maybe with an additional struct that is
#[must_use]
?
- Can we somehow enforce that this method is called? It's easy to forget and causes headaches. Maybe with an additional struct that is
We could pretty easily support deregistration, but I am not sure it is worth it -- it is not currently used anywhere, and any capsule could manually implement it with a flag at the cost of a word of memory. I think average capsules not having to consider the possibility of a deferred call somehow being deregistered is preferable.
Hmm, this is a neat idea -- we could make Either way, I don't think that this PR needs to block on either of these, though they might make nice improvements. |
cdff2f4
to
8f83800
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't check every use, but overall this seems like a big improvement!
Note: The current diff on the PR looks like much less than -1300 LOC, that is because the current TRD rules required me to duplicate the entire time TRD in order to adjust the syntax of its code examples |
8f83800
to
c43557b
Compare
Rebased to resolve conflicts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed for soundness, nothing stands out to me as concerning.
0b7a7ec
c43557b
to
0b7a7ec
Compare
This switches to using the fully-qualified module path to call the DeferredCallClient::register trait function. It makes it obvious that a deferred call is registered and avoid an otherwise context-less import of DeferredCallClient. Signed-off-by: Leon Schuermann <[email protected]>
Signed-off-by: Leon Schuermann <[email protected]>
Signed-off-by: Leon Schuermann <[email protected]>
2fadfae
to
033736b
Compare
rebased |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a big improvement, to code clarity if nothing else. Great work.
bors r+ |
Build succeeded:
|
Pull Request Overview
This PR replaces both the existing
DeferredCall
implementation (which was only usable for chip peripherals) and the existingDynamicDeferredCall
implementation (which was used by capsules, kernel components, and even some chip peripherals) with a single, newDeferredCall
. The newDeferredCall
has higher size/cycle overhead than the originalDeferredCall
, but can be used anywhere in Tock. The newDeferredCall
has lower size overhead thanDynamicDeferredCall
, and a much less verbose interface.TL;DR SUMMARY:
Pros of this change
DynamicDeferredCall
-- this change has a net delta of 1300 LOC removed!DeferredCall
checks whether moreDeferredCall
s have been created than there is space for, and checks that exactly as manyDeferredCall
s have been registered as were created. This check happens at the beginning of the kernel loop, rather than asDeferredCall
s are created. This has two major advantages: first, DeferredCall errors will not lead to hard-to-debug hangs (before, a common bug was for too manyDynamicDeferredCall
s to be created, but the panic message could not be printed because the check happened at creation, before the debug writer had been setup). Second, it is now much more difficult to forget to register aDeferredCall
-- as a result, there is no longer a need for capsules to verify that aDeferredCall
has been initialized and registered before using it.DynamicDeferredCall
, and only marginally higher overhead than our oldDeferredCall
. For Imix, this change reduces code size by 524 bytes.#[core_intrinsics]
nightly feature (notably, I think we could have made this change to the oldDeferredCall
as well).DeferredCall
).Cons of this change
DeferredCall
, so for a board with no use forDynamicDeferredCall
and no need for out-of-tree peripherals, this change can have a negative impact. In practice, any real Tock system will use enoughDynamicDeferredCall
(the console requires it if you useMuxUart
!) that this is unlikely to have a negative impact for any systems.DeferredCall
s whether they are used or not (fixed 256 byte cost). Overall, Imix RAM use went up by 108 bytes (there were some RAM savings thanks to the newDeferredCall
being smaller in size than the oldDynamicDeferredCall
, which show up directly in .bss thanks to many of these types being allocated as fields on global static objects via static_init).DeferredCall
s supported. We could increase this limit to 64 or 128 at some reasonably small cost, but it seems unlikely that any Tock boards today will require that many. For example, Imix uses only 11 and is already near its code size limit.Acknowledgements
This PR involved significant collaboration with @lschuermann , who helped port many of the boards and chips over, wrote the original
DynamicDeferredCall
implementation, and collaborated with me on a bunch of different designs before we arrived at this one.@kupiakos authored the first draft of the neat
DynDefCallRef
type, which helped to limit the code size overhead of this approach relative to simply using trait objects, which carry a bunch of information in vtables that we do not need for this.Testing Strategy
This pull request was tested by running
blink
andimix
onImix
. We will probably need to test across a few more boards, as it is easy to miss spots where we need to callDeferredCallClient::register()
inmain.rs
. Fortunately, any board where we have forgotten to do this will panic at the start of the kernel loop with a useful message.TODO or Help Wanted
Help wanted: Soundness review of how this type uses a (non-public)
static mut Cell<usize>
instead ofAtomicUsize
.Documentation Updated
/docs
(I think, may not have gotten everything).Formatting
make prepush
.