Replace DeferredCall and DynamicDeferredCall with a more general-purpose implementation#3382
Replace DeferredCall and DynamicDeferredCall with a more general-purpose implementation#3382
DeferredCall and DynamicDeferredCall with a more general-purpose implementation#3382Conversation
cf4f0b6 to
c19cf7d
Compare
bc011ea to
a8ba255
Compare
lschuermann
left a comment
There was a problem hiding this comment.
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 ofDeferredCallClientwithout 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
alistair23
left a comment
There was a problem hiding this comment.
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 |
kupiakos
left a comment
There was a problem hiding this comment.
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 |
brghena
left a comment
There was a problem hiding this comment.
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
DeferredCallimplementation (which was only usable for chip peripherals) and the existingDynamicDeferredCallimplementation (which was used by capsules, kernel components, and even some chip peripherals) with a single, newDeferredCall. The newDeferredCallhas higher size/cycle overhead than the originalDeferredCall, but can be used anywhere in Tock. The newDeferredCallhas 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!DeferredCallchecks whether moreDeferredCalls have been created than there is space for, and checks that exactly as manyDeferredCalls have been registered as were created. This check happens at the beginning of the kernel loop, rather than asDeferredCalls 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 manyDynamicDeferredCalls 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 aDeferredCallhas 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 oldDeferredCallas well).DeferredCall).Cons of this change
DeferredCall, so for a board with no use forDynamicDeferredCalland 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.DeferredCalls 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 newDeferredCallbeing 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).DeferredCalls 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
DynamicDeferredCallimplementation, and collaborated with me on a bunch of different designs before we arrived at this one.@kupiakos authored the first draft of the neat
DynDefCallReftype, 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
blinkandimixonImix. 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.